Files
test/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs
T
2025-09-22 19:01:45 +07:00

616 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
using BrewMonster.Network;
using BrewMonster;
using ModelRenderer.Scripts.GameData;
namespace PerfectWorld.Scripts.Managers
{
public class EC_InventoryUI : MonoBehaviour
{
[Header("Pack Buttons (assign in Inspector)")]
[SerializeField] private List<Button> inventoryPackButtons = new List<Button>(); // byPackage: 0
[SerializeField] private List<Button> equipmentPackButtons = new List<Button>(); // byPackage: 1
[SerializeField] private List<Button> fashionPackButtons = new List<Button>(); // byPackage: 3
[Header("Detail Panel (assign in Inspector)")]
[SerializeField] private GameObject detailPanelRoot;
[SerializeField] private bool hideDetailOnStart = true;
[SerializeField] private TextOutlet nameText;
[SerializeField] private TextOutlet templateIdText;
[SerializeField] private TextOutlet countText;
[SerializeField] private TextOutlet slotText;
[SerializeField] private TextOutlet packageText;
[SerializeField] private TextOutlet stateText;
[SerializeField] private TextOutlet expireText;
[SerializeField] private TextOutlet crcText;
[SerializeField] private TextOutlet contentLenText;
[SerializeField] private Button equipButton;
[Header("Inventory Settings")]
[SerializeField] private bool autoRefresh = true;
[SerializeField] private float refreshInterval = 1.0f;
private float lastRefreshTime;
private InventoryModel model;
private InventoryView view;
// Current selected item for equip/unequip operations
private byte currentSelectedPackage;
private int currentSelectedSlot;
private InventoryItemData currentSelectedItem;
private const byte PKG_INVENTORY = 0;
private const byte PKG_EQUIPMENT = 1;
private const byte PKG_FASHION = 3; // Note: byPackage 3 used for Fashion
private void Awake()
{
model = new InventoryModel();
view = new InventoryView();
}
private void Start()
{
RefreshAll();
if (hideDetailOnStart)
{
ShowDetailPanel(false);
}
}
private void Update()
{
if (autoRefresh && Time.time - lastRefreshTime >= refreshInterval)
{
RefreshAll();
}
}
public void RefreshAll()
{
lastRefreshTime = Time.time;
var invItems = model.GetInventoryData(PKG_INVENTORY);
var eqpItems = model.GetInventoryData(PKG_EQUIPMENT);
var fshItems = model.GetInventoryData(PKG_FASHION);
view.RenderPackage(inventoryPackButtons, invItems, PKG_INVENTORY, OnInventoryButtonClicked, GetDisplayTextForItem);
view.RenderPackage(equipmentPackButtons, eqpItems, PKG_EQUIPMENT, OnInventoryButtonClicked, GetDisplayTextForItem);
view.RenderPackage(fashionPackButtons, fshItems, PKG_FASHION, OnInventoryButtonClicked, GetDisplayTextForItem);
}
private void OnInventoryButtonClicked(byte package, int slot)
{
var data = model.GetInventoryData(package);
if (data != null && data.TryGetValue(slot, out var itemData))
{
// Store current selection for equip/unequip operations
currentSelectedPackage = package;
currentSelectedSlot = slot;
currentSelectedItem = itemData;
FillDetailPanel(package, itemData);
}
else
{
ShowDetailPanel(false);
}
}
private string GetDisplayTextForItem(int slot, InventoryItemData itemData)
{
if (itemData == null || itemData.Count <= 0)
{
return string.Empty;
}
string itemName = EC_IvtrItem.ResolveItemName(itemData.TemplateId);
string displayText = string.IsNullOrEmpty(itemName) ? $"Item {itemData.TemplateId}" : itemName;
if (itemData.Count > 1)
{
displayText += $" x{itemData.Count}";
}
return displayText;
}
public void ToggleAutoRefresh()
{
autoRefresh = !autoRefresh;
}
public void SetRefreshInterval(float interval)
{
refreshInterval = Mathf.Max(0.1f, interval);
}
public void OnEquipButtonClicked()
{
if (currentSelectedItem == null)
{
Debug.LogWarning("[InventoryUI] No item selected for equip/unequip operation");
return;
}
if (currentSelectedPackage == PKG_INVENTORY)
{
// Equipping from inventory
EquipItem();
}
else if (currentSelectedPackage == PKG_EQUIPMENT)
{
// Unequipping from equipment
UnequipItem();
}
else
{
Debug.LogWarning($"[InventoryUI] Equip/Unequip not supported for package {currentSelectedPackage}");
}
}
private void EquipItem()
{
if (currentSelectedItem == null) return;
// For equipping, we need to find an empty equipment slot
// The equip location should be determined by the item type or use a default
byte equipLocation = GetEquipLocationForItem(currentSelectedItem.TemplateId);
if (equipLocation >= (byte)IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR)
{
Debug.LogWarning($"[InventoryUI] Could not determine equip location for item {currentSelectedItem.TemplateId}");
return;
}
// Call RequestEquipItemAsync with inventory slot and equip location
UnityGameSession.RequestEquipItemAsync((byte)currentSelectedSlot, equipLocation, () =>
{
Debug.Log($"[InventoryUI] Equip request sent for item {currentSelectedItem.TemplateId} from slot {currentSelectedSlot} to equip location {equipLocation}");
// Refresh inventory after equip
RefreshAll();
});
}
private void UnequipItem()
{
if (currentSelectedItem == null) return;
// Find empty slot in PACK_INVENTORY
int emptySlot = FindEmptyInventorySlot();
if (emptySlot == -1)
{
Debug.LogWarning("[InventoryUI] No empty slots available in inventory for unequipping");
return;
}
// For unequipping, the equip location is the current equipment slot
// We can use the slot number as the equip location
byte equipLocation = (byte)currentSelectedSlot;
// Call RequestEquipItemAsync with empty inventory slot and current equip location
UnityGameSession.RequestEquipItemAsync((byte)emptySlot, equipLocation, () =>
{
Debug.Log($"[InventoryUI] Unequip request sent for item {currentSelectedItem.TemplateId} from equip location {equipLocation} to inventory slot {emptySlot}");
// Refresh inventory after unequip
RefreshAll();
});
}
private byte GetEquipLocationForItem(int templateId)
{
// Determine exact equipment slot index based on item type/essence
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null)
{
Debug.LogWarning("[InventoryUI] ElementDataMan not initialized");
return (byte)IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR;
}
uint id = unchecked((uint)templateId);
// Weapons
foreach (var it in edm.weapon_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_WEAPON;
}
// Projectiles (quiver / projectile essence)
foreach (var it in edm.quiver_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_PROJECTILE;
}
foreach (var it in edm.projectile_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_PROJECTILE;
}
// Flysword
foreach (var it in edm.flysword_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_FLYSWORD;
}
// Armor -> derive from sub-type mask
foreach (var it in edm.armor_essence_array)
{
if (it.id == id)
{
var slot = ResolveArmorSlotBySubtype(edm, it.id_sub_type);
if (slot < IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR)
{
return (byte)slot;
}
break;
}
}
// Fashion -> derive from sub-type mask
foreach (var it in edm.fashion_essence_array)
{
if (it.id == id)
{
var slot = ResolveFashionSlotBySubtype(edm, it.id_sub_type);
if (slot < IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR)
{
return (byte)slot;
}
break;
}
}
// Runes
foreach (var it in edm.damagerune_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_RUNE;
}
foreach (var it in edm.armorrune_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_RUNE;
}
// Special slots
foreach (var it in edm.bible_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_BIBLE;
}
foreach (var it in edm.speaker_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_SPEAKER;
}
foreach (var it in edm.autohp_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_AUTOHP;
}
foreach (var it in edm.automp_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_AUTOMP;
}
foreach (var it in edm.force_token_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_FORCE_TOKEN;
}
foreach (var it in edm.sell_certificate_essence_array)
{
if (it.id == id) return (byte)IndexOfIteminEquipmentInventory.EQUIPIVTR_CERTIFICATE;
}
// Fashion weapon (if present in your data as essence/config)
// If you add a dedicated essence array for fashion weapons, map it here
Debug.LogWarning($"[InventoryUI] Equip index not found for template {templateId}");
return (byte)IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR;
}
catch (Exception ex)
{
Debug.LogWarning($"[InventoryUI] Error resolving equip index for template {templateId}: {ex.Message}");
return (byte)IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR;
}
}
private byte GetDefaultEquipLocation(int templateId)
{
// Basic equip location mapping based on template ID
// Adjust these ranges based on your game's item system
if (templateId >= 1000 && templateId < 2000) return 0; // Weapon
if (templateId >= 2000 && templateId < 3000) return 2; // Armor
if (templateId >= 3000 && templateId < 4000) return 3; // Accessory
if (templateId >= 4000 && templateId < 5000) return 4; // Ring
if (templateId >= 5000 && templateId < 6000) return 5; // Necklace
// Default to slot 1 if no specific mapping
Debug.Log($"[InventoryUI] Using default equip location 1 for template {templateId}");
return 0;
}
private int FindEmptyInventorySlot()
{
var inventoryData = model.GetInventoryData(PKG_INVENTORY);
if (inventoryData == null) return -1;
// Find first empty slot (assuming slots are numbered 0, 1, 2, ...)
for (int i = 0; i < 100; i++) // Assuming max 100 inventory slots
{
if (!inventoryData.ContainsKey(i))
{
return i;
}
}
return -1;
}
// === Equipment indices (copy kept local for clarity) ===
private enum IndexOfIteminEquipmentInventory : byte
{
EQUIPIVTR_WEAPON = 0,
EQUIPIVTR_HEAD,
EQUIPIVTR_NECK,
EQUIPIVTR_SHOULDER,
EQUIPIVTR_BODY,
EQUIPIVTR_WAIST,
EQUIPIVTR_LEG,
EQUIPIVTR_FOOT,
EQUIPIVTR_WRIST,
EQUIPIVTR_FINGER1,
EQUIPIVTR_FINGER2,
EQUIPIVTR_PROJECTILE,
EQUIPIVTR_FLYSWORD,
EQUIPIVTR_FASHION_BODY,
EQUIPIVTR_FASHION_LEG,
EQUIPIVTR_FASHION_FOOT,
EQUIPIVTR_FASHION_WRIST,
EQUIPIVTR_RUNE,
EQUIPIVTR_BIBLE,
EQUIPIVTR_SPEAKER,
EQUIPIVTR_AUTOHP,
EQUIPIVTR_AUTOMP,
EQUIPIVTR_POCKET,
EQUIPIVTR_GOBLIN,
EQUIPIVTR_CERTIFICATE,
EQUIPIVTR_FASHION_HEAD,
EQUIPIVTR_FORCE_TOKEN,
EQUIPIVTR_DYNSKILLEQUIP1,
EQUIPIVTR_DYNSKILLEQUIP2,
EQUIPIVTR_FASHION_WEAPON,
SIZE_EQUIPIVTR,
}
private static IndexOfIteminEquipmentInventory ResolveArmorSlotBySubtype(elementdataman edm, uint armorSubTypeId)
{
foreach (var sub in edm.armor_sub_type_array)
{
if (sub.id != armorSubTypeId) continue;
uint mask = sub.equip_mask;
// Map first set bit to equipment index
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_HEAD)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_HEAD;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_NECK)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_NECK;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_SHOULDER)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_SHOULDER;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_BODY)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_BODY;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WAIST)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_WAIST;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_LEG)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_LEG;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FOOT)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FOOT;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_WRIST)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_WRIST;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER1;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FINGER2;
// Default not found
break;
}
return IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR;
}
private static IndexOfIteminEquipmentInventory ResolveFashionSlotBySubtype(elementdataman edm, uint fashionSubTypeId)
{
foreach (var sub in edm.fashion_sub_type_array)
{
if (sub.id != fashionSubTypeId) continue;
uint mask = sub.equip_fashion_mask;
// Map first set bit to fashion equipment index
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_HEAD)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_HEAD;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_BODY)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_BODY;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_LEG)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_LEG;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_FOOT)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_FOOT;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_WRIST)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_WRIST;
if ((mask & (1u << (int)IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_WEAPON)) != 0) return IndexOfIteminEquipmentInventory.EQUIPIVTR_FASHION_WEAPON;
// Default not found
break;
}
return IndexOfIteminEquipmentInventory.SIZE_EQUIPIVTR;
}
// === MVC: Model ===
private class InventoryModel
{
private readonly FieldInfo itemsByPackageField;
public InventoryModel()
{
var inventoryType = typeof(EC_Inventory);
itemsByPackageField = inventoryType.GetField("_itemsByPackage", BindingFlags.NonPublic | BindingFlags.Static);
if (itemsByPackageField == null)
{
Debug.LogError("[InventoryUI] Could not access _itemsByPackage field from EC_Inventory");
}
}
public Dictionary<int, InventoryItemData> GetInventoryData(byte package)
{
if (itemsByPackageField == null)
{
return new Dictionary<int, InventoryItemData>();
}
var itemsByPackage = itemsByPackageField.GetValue(null) as Dictionary<byte, Dictionary<int, InventoryItemData>>;
if (itemsByPackage == null)
{
return new Dictionary<int, InventoryItemData>();
}
if (itemsByPackage.TryGetValue(package, out var packageItems))
{
return packageItems;
}
return new Dictionary<int, InventoryItemData>();
}
}
// === MVC: View ===
private class InventoryView
{
public void RenderPackage(List<Button> buttons, Dictionary<int, InventoryItemData> items, byte package, System.Action<byte, int> onClick, System.Func<int, InventoryItemData, string> getDisplayText)
{
if (buttons == null)
{
return;
}
for (int slot = 0; slot < buttons.Count; slot++)
{
var button = buttons[slot];
if (button == null)
{
continue;
}
InventoryItemData itemData = null;
bool hasItem = items != null && items.TryGetValue(slot, out itemData);
button.onClick.RemoveAllListeners();
int capturedSlot = slot;
button.onClick.AddListener(() => onClick(package, capturedSlot));
// Optional visual tweaks based on state/count
var image = button.GetComponent<Image>();
if (image != null)
{
if (hasItem && itemData != null && itemData.State != 0)
{
image.color = Color.yellow;
}
else if (!hasItem)
{
image.color = Color.white;
}
else
{
image.color = Color.white;
}
}
}
}
}
// === Detail Panel Helpers ===
[System.Serializable]
private class TextOutlet
{
[SerializeField] public Text legacy;
[SerializeField] public TMPro.TextMeshProUGUI tmp;
public void Set(string value)
{
if (legacy != null)
{
legacy.text = value ?? string.Empty;
}
if (tmp != null)
{
tmp.text = value ?? string.Empty;
}
}
}
private void ShowDetailPanel(bool show)
{
if (detailPanelRoot != null)
{
detailPanelRoot.SetActive(show);
}
}
private void FillDetailPanel(byte package, InventoryItemData item)
{
if (item == null)
{
ShowDetailPanel(false);
return;
}
string name = EC_IvtrItem.ResolveItemName(item.TemplateId);
nameText?.Set(string.IsNullOrEmpty(name) ? $"Item {item.TemplateId}" : name);
templateIdText?.Set(item.TemplateId.ToString());
countText?.Set(item.Count.ToString());
slotText?.Set(item.Slot.ToString());
packageText?.Set(GetPackageName(package));
stateText?.Set(item.State.ToString());
expireText?.Set(item.ExpireDate.ToString());
crcText?.Set(item.Crc.ToString());
contentLenText?.Set((item.Content?.Length ?? 0).ToString());
// Setup equip button
SetupEquipButton(package, item);
ShowDetailPanel(true);
}
private void SetupEquipButton(byte package, InventoryItemData item)
{
if (equipButton == null) return;
// Clear previous listeners
equipButton.onClick.RemoveAllListeners();
equipButton.onClick.AddListener(OnEquipButtonClicked);
// Set button text and visibility based on package
var buttonText = equipButton.GetComponentInChildren<UnityEngine.UI.Text>();
if (buttonText == null)
{
var tmpText = equipButton.GetComponentInChildren<TMPro.TextMeshProUGUI>();
if (tmpText != null)
{
if (package == PKG_INVENTORY)
{
tmpText.text = "Equip";
equipButton.gameObject.SetActive(true);
}
else if (package == PKG_EQUIPMENT)
{
tmpText.text = "UnEquip";
equipButton.gameObject.SetActive(true);
}
else
{
equipButton.gameObject.SetActive(false);
}
}
}
else
{
if (package == PKG_INVENTORY)
{
buttonText.text = "Equip";
equipButton.gameObject.SetActive(true);
}
else if (package == PKG_EQUIPMENT)
{
buttonText.text = "UnEquip";
equipButton.gameObject.SetActive(true);
}
else
{
equipButton.gameObject.SetActive(false);
}
}
}
private static string GetPackageName(byte pkg)
{
switch (pkg)
{
case 0: return "PACK_INVENTORY";
case 1: return "PACK_EQUIPMENT";
case 3: return "PACK_FASHION";
default: return $"PACK_{pkg}";
}
}
}
}