Files
test/Assets/PerfectWorld/Scripts/Managers/CECInventory.cs
T
2025-11-20 02:18:48 +07:00

799 lines
26 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
namespace BrewMonster.Scripts.Managers
{
/// <summary>
/// C# mirror of C++ CECInventory (EC_Inventory.h / EC_Inventory.cpp).
/// This is an instance-based inventory using a fixed-size item array.
/// </summary>
public class CECInventory
{
// Item array: index is slot, null means empty.
private EC_IvtrItem[] m_aItems = Array.Empty<EC_IvtrItem>();
public CECInventory()
{
}
public bool Init(int iSize)
{
Resize(iSize);
return true;
}
public void Release()
{
// In C++ this deletes all heap-allocated items.
// Here we simply clear references so GC can collect them.
RemoveAllItems();
m_aItems = Array.Empty<EC_IvtrItem>();
}
public void RemoveAllItems()
{
for (int i = 0; i < m_aItems.Length; i++)
{
m_aItems[i] = null;
}
}
public void Resize(int iNewSize)
{
int oldSize = m_aItems.Length;
if (iNewSize < 0) iNewSize = 0;
if (iNewSize == oldSize)
return;
var newArray = iNewSize > 0 ? new EC_IvtrItem[iNewSize] : Array.Empty<EC_IvtrItem>();
if (oldSize > 0 && iNewSize > 0)
{
Array.Copy(m_aItems, newArray, Math.Min(oldSize, iNewSize));
}
m_aItems = newArray;
}
public EC_IvtrItem PutItem(int iSlot, EC_IvtrItem pItem)
{
if (iSlot < 0 || iSlot >= m_aItems.Length)
{
return null;
}
var old = m_aItems[iSlot];
m_aItems[iSlot] = pItem;
return old;
}
public void SetItem(int iSlot, EC_IvtrItem pItem)
{
if (iSlot < 0 || iSlot >= m_aItems.Length)
{
return;
}
m_aItems[iSlot] = pItem;
}
public EC_IvtrItem GetItem(int iSlot, bool bRemove = false)
{
if (iSlot < 0 || iSlot >= m_aItems.Length)
{
return null;
}
var pItem = m_aItems[iSlot];
if (bRemove)
m_aItems[iSlot] = null;
return pItem;
}
public void ExchangeItem(int iSlot1, int iSlot2)
{
if (iSlot1 < 0 || iSlot1 >= m_aItems.Length ||
iSlot2 < 0 || iSlot2 >= m_aItems.Length)
{
return;
}
if (iSlot1 == iSlot2)
return;
var tmp = m_aItems[iSlot1];
m_aItems[iSlot1] = m_aItems[iSlot2];
m_aItems[iSlot2] = tmp;
}
public bool MergeItem(int tid, int iExpireDate, int iAmount, out int piLastSlot, out int piLastAmount)
{
piLastSlot = -1;
piLastAmount = 0;
int firstEmpty = -1;
for (int i = 0; i < m_aItems.Length; i++)
{
var slotItem = m_aItems[i];
if (slotItem != null)
{
if (slotItem.GetTemplateID() != tid)
continue;
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(tid));
int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.GetCount()));
if (canAdd <= 0) continue;
int add = Math.Min(canAdd, iAmount);
slotItem.AddAmount(add);
iAmount -= add;
if (iAmount == 0)
{
piLastSlot = i;
piLastAmount = slotItem.GetCount();
return true;
}
}
else if (firstEmpty < 0)
{
firstEmpty = i;
}
}
if (firstEmpty < 0 || iAmount <= 0)
{
return false;
}
var newItem = new EC_IvtrItem(tid, iExpireDate)
{
Slot = firstEmpty,
State = 0,
Crc = 0,
Content = null
};
newItem.SetCount(iAmount);
m_aItems[firstEmpty] = newItem;
piLastSlot = firstEmpty;
piLastAmount = iAmount;
return true;
}
public bool MoveItem(int iSrc, int iDest, int iAmount)
{
if (iSrc < 0 || iSrc >= m_aItems.Length ||
iDest < 0 || iDest >= m_aItems.Length)
{
return false;
}
var pSrc = m_aItems[iSrc];
var pDst = m_aItems[iDest];
if (pSrc == null)
return false;
if (iAmount == 0)
return false;
if (pDst == null)
{
var clone = new EC_IvtrItem(pSrc.GetTemplateID(), pSrc.GetExpireDate())
{
Slot = iDest,
Package = pSrc.Package,
State = pSrc.State,
Crc = pSrc.Crc,
Content = pSrc.Content != null ? (byte[])pSrc.Content.Clone() : null
};
clone.SetCount(iAmount);
m_aItems[iDest] = clone;
}
else
{
if (pSrc.GetTemplateID() != pDst.GetTemplateID())
return false;
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(pDst.GetTemplateID()));
int canAdd = Math.Max(0, pileLimit - Math.Max(0, pDst.GetCount()));
int add = Math.Min(canAdd, iAmount);
if (add <= 0) return false;
pDst.AddAmount(add);
iAmount = add;
}
RemoveItem(iSrc, iAmount);
return true;
}
public bool RemoveItem(int iSlot, int iAmount)
{
if (iSlot < 0 || iSlot >= m_aItems.Length)
{
return false;
}
var pItem = m_aItems[iSlot];
if (pItem == null)
return true;
int newCount = pItem.AddAmount(-Math.Max(0, iAmount));
if (newCount <= 0)
m_aItems[iSlot] = null;
return true;
}
public int FindItem(int idItem, int baseIdx = 0)
{
if (baseIdx < 0) baseIdx = 0;
for (int i = baseIdx; i < m_aItems.Length; i++)
{
var pItem = m_aItems[i];
if (pItem != null && pItem.GetTemplateID() == idItem)
return i;
}
return -1;
}
public int GetItemTotalNum(int idItem)
{
int count = 0;
for (int i = 0; i < m_aItems.Length; i++)
{
var pItem = m_aItems[i];
if (pItem != null && pItem.GetTemplateID() == idItem)
count += Math.Max(0, pItem.GetCount());
}
return count;
}
public int SearchEmpty()
{
for (int i = 0; i < m_aItems.Length; i++)
{
if (m_aItems[i] == null)
return i;
}
return -1;
}
public int GetEmptySlotNum()
{
int count = 0;
for (int i = 0; i < m_aItems.Length; i++)
{
if (m_aItems[i] == null)
count++;
}
return count;
}
public int CanAddItem(int idItem, int iAmount, bool tryPile)
{
int foundEmpty = -1;
for (int i = 0; i < m_aItems.Length; i++)
{
var pItem = m_aItems[i];
if (pItem == null)
{
if (!tryPile) return i;
if (foundEmpty < 0) foundEmpty = i;
}
else if (pItem.GetTemplateID() == idItem)
{
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(idItem));
if (pItem.GetCount() + iAmount <= pileLimit)
return i;
}
}
return foundEmpty;
}
public int GetSize()
{
return m_aItems.Length;
}
}
/// <summary>
/// Static inventory facade used by the current client code.
/// This implementation was originally in EC_Inventory.cs and is now colocated
/// with CECInventory so both static-style and instance-style code continue to work.
/// </summary>
public static class EC_Inventory
{
// We currently support exactly three inventory packs, matching legacy C++ semantics:
// 0 = normal pack, 1 = equip pack, 2 = task pack.
private const int PackageCount = 3;
// Fixed-size arrays per package, mimicking C++ CECInventory::m_aItems (APtrArray<CECIvtrItem*>).
// Index is the slot index; a null entry means the slot is empty.
private static readonly int[] _packSizeByPackage = new int[PackageCount];
private static readonly EC_IvtrItem[][] _itemsByPackage = new EC_IvtrItem[PackageCount][];
private const int MaxContentHexToLog = 64;
// Package constants to mirror legacy C++ names
public const byte IVTRTYPE_PACK = 0;
public const byte IVTRTYPE_EQUIPPACK = 1;
public const byte IVTRTYPE_TASKPACK = 2;
private static int GetPackageIndex(byte pkg)
{
switch (pkg)
{
case IVTRTYPE_PACK:
case IVTRTYPE_EQUIPPACK:
case IVTRTYPE_TASKPACK:
return pkg;
default:
// Only three legacy packages are currently supported; ignore others safely.
Debug.LogWarning($"[Inventory] Unsupported package id={pkg}, expected 0..2.");
return -1;
}
}
private static int GetPackSize(byte byPackage)
{
int idx = GetPackageIndex(byPackage);
if (idx < 0) return 0;
return _packSizeByPackage[idx];
}
private static EC_IvtrItem[] EnsureSlots(byte byPackage)
{
int idx = GetPackageIndex(byPackage);
if (idx < 0)
return Array.Empty<EC_IvtrItem>();
int size = Math.Max(0, _packSizeByPackage[idx]);
var slots = _itemsByPackage[idx];
if (slots == null || slots.Length != size)
{
var newSlots = size > 0 ? new EC_IvtrItem[size] : Array.Empty<EC_IvtrItem>();
// Preserve items that still fit into the resized pack, similar to C++ Resize.
if (slots != null && slots.Length > 0 && newSlots.Length > 0)
{
Array.Copy(slots, newSlots, Math.Min(slots.Length, newSlots.Length));
}
_itemsByPackage[idx] = newSlots;
slots = newSlots;
}
return slots;
}
private static string GetPackageName(byte pkg)
{
switch (pkg)
{
case 0: return "IVTRTYPE_PACK";
case 1: return "IVTRTYPE_EQUIPPACK";
case 2: return "IVTRTYPE_TASKPACK";
default: return "PACK_UNKNOWN";
}
}
private static string BytesToHex(byte[] bytes, int max)
{
if (bytes == null || bytes.Length == 0) return "";
int len = Math.Min(bytes.Length, max);
string hex = BitConverter.ToString(bytes, 0, len);
if (bytes.Length > len) hex += "-...";
return hex;
}
// C++ has CECInventory per pack; this helper returns a snapshot Dictionary view
// of the current slots for convenience where a map-like API is easier to use.
public static Dictionary<int, EC_IvtrItem> GetPack(byte byPackage)
{
var slots = EnsureSlots(byPackage);
var result = new Dictionary<int, EC_IvtrItem>(slots.Length);
for (int i = 0; i < slots.Length; i++)
{
var it = slots[i];
if (it != null)
result[i] = it;
}
return result;
}
public static EC_IvtrItem GetItem(int iSlot, bool bRemove)
{
// Backward-compatible overload defaulting to inventory package
return GetItem(IVTRTYPE_PACK, iSlot, bRemove);
}
public static EC_IvtrItem GetItem(byte byPackage, int slot, bool remove)
{
var slots = EnsureSlots(byPackage);
if (slot < 0 || slot >= slots.Length)
return null;
var item = slots[slot];
if (remove && slot >= 0 && slot < slots.Length)
slots[slot] = null;
return item;
}
public static void Resize(byte byPackage, int newSize)
{
int idx = GetPackageIndex(byPackage);
if (idx < 0)
return;
newSize = Math.Max(0, newSize);
int oldSize = _packSizeByPackage[idx];
_packSizeByPackage[idx] = newSize;
var oldSlots = _itemsByPackage[idx];
if (oldSlots == null)
{
_itemsByPackage[idx] = newSize > 0 ? new EC_IvtrItem[newSize] : Array.Empty<EC_IvtrItem>();
return;
}
if (oldSize == newSize && oldSlots.Length == newSize)
return;
var newSlots = newSize > 0 ? new EC_IvtrItem[newSize] : Array.Empty<EC_IvtrItem>();
if (oldSlots.Length > 0 && newSlots.Length > 0)
{
Array.Copy(oldSlots, newSlots, Math.Min(oldSlots.Length, newSlots.Length));
}
_itemsByPackage[idx] = newSlots;
}
public static EC_IvtrItem PutItem(byte byPackage, int slot, EC_IvtrItem item)
{
var slots = EnsureSlots(byPackage);
if (slot < 0 || slot >= slots.Length)
return null;
var oldItem = slots[slot];
slots[slot] = item;
return oldItem;
}
public static void SetItem(byte byPackage, int slot, EC_IvtrItem item)
{
var slots = EnsureSlots(byPackage);
if (slot < 0 || slot >= slots.Length)
return;
slots[slot] = item;
}
public static void ExchangeItem(byte byPackage, int slot1, int slot2)
{
if (slot1 == slot2) return;
var slots = EnsureSlots(byPackage);
if (slot1 < 0 || slot1 >= slots.Length || slot2 < 0 || slot2 >= slots.Length)
return;
var i1 = slots[slot1];
var i2 = slots[slot2];
slots[slot1] = i2;
slots[slot2] = i1;
}
public static bool RemoveItem(byte byPackage, int slot, int amount)
{
var slots = EnsureSlots(byPackage);
if (slot < 0 || slot >= slots.Length)
return false;
var item = slots[slot];
if (item == null)
return true;
int newCount = Math.Max(0, item.m_iCount - Math.Max(0, amount));
item.m_iCount = newCount;
if (newCount <= 0)
slots[slot] = null;
return true;
}
public static void RemoveAllItems(byte byPackage)
{
var slots = EnsureSlots(byPackage);
for (int i = 0; i < slots.Length; i++)
{
slots[i] = null;
}
}
public static int SearchEmpty(byte byPackage)
{
var slots = EnsureSlots(byPackage);
for (int i = 0; i < slots.Length; i++)
{
if (slots[i] == null)
return i;
}
return -1;
}
public static int GetEmptySlotNum(byte byPackage)
{
var slots = EnsureSlots(byPackage);
int empty = 0;
for (int i = 0; i < slots.Length; i++)
{
if (slots[i] == null)
empty++;
}
return empty;
}
public static int FindItem(byte byPackage, int templateId, int baseIdx = 0)
{
var slots = EnsureSlots(byPackage);
if (baseIdx < 0) baseIdx = 0;
for (int i = baseIdx; i < slots.Length; i++)
{
var it = slots[i];
if (it != null && it.m_tid == templateId)
return i;
}
return -1;
}
public static int GetItemTotalNum(byte byPackage, int templateId)
{
int total = 0;
var slots = EnsureSlots(byPackage);
for (int i = 0; i < slots.Length; i++)
{
var it = slots[i];
if (it != null && it.m_tid == templateId)
total += Math.Max(0, it.m_iCount);
}
return total;
}
public static int GetItemCanPileCount(byte byPackage, int templateId)
{
int ret = 0;
var slots = EnsureSlots(byPackage);
for (int i = 0; i < slots.Length; i++)
{
var it = slots[i];
if (it != null && it.m_tid == templateId)
{
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId));
ret += Math.Max(0, pileLimit - Math.Max(0, it.m_iCount));
}
}
return ret;
}
public static int CanAddItem(byte byPackage, int templateId, int amount, bool tryPile)
{
int firstEmpty = -1;
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId));
var slots = EnsureSlots(byPackage);
for (int i = 0; i < slots.Length; i++)
{
var it = slots[i];
if (it == null)
{
// return first empty slot if not trying to pile item
if (!tryPile) return i;
if (firstEmpty < 0) firstEmpty = i;
}
else if (it.m_tid == templateId && it.m_iCount + amount <= pileLimit)
{
return i;
}
}
return firstEmpty;
}
public static bool MergeItem(byte byPackage, int templateId, int expireDate, int amount, out int lastSlot, out int slotAmount)
{
lastSlot = -1;
slotAmount = 0;
var slots = EnsureSlots(byPackage);
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(templateId));
int firstEmpty = -1;
for (int i = 0; i < slots.Length && amount > 0; i++)
{
var slotItem = slots[i];
if (slotItem == null)
{
if (firstEmpty < 0) firstEmpty = i;
continue;
}
if (slotItem.m_tid != templateId) continue;
int canAdd = Math.Max(0, pileLimit - Math.Max(0, slotItem.m_iCount));
if (canAdd <= 0) continue;
int add = Math.Min(canAdd, amount);
slotItem.m_iCount += add;
amount -= add;
lastSlot = i;
slotAmount = slotItem.m_iCount;
}
if (amount <= 0) return true;
if (firstEmpty < 0) return false;
var newItem = new EC_IvtrItem
{
Package = byPackage,
Slot = firstEmpty,
m_tid = templateId,
m_expire_date = expireDate,
State = 0,
m_iCount = amount,
Crc = 0,
Content = null
};
slots[firstEmpty] = newItem;
lastSlot = firstEmpty;
slotAmount = amount;
return true;
}
public static bool MoveItem(byte byPackage, int src, int dest, int amount)
{
if (src == dest) return false;
if (amount <= 0) return false;
var slots = EnsureSlots(byPackage);
if (src < 0 || src >= slots.Length || dest < 0 || dest >= slots.Length) return false;
var srcItem = slots[src];
var dstItem = slots[dest];
if (srcItem == null) return false;
if (dstItem == null)
{
var clone = new EC_IvtrItem
{
Package = byPackage,
Slot = dest,
m_tid = srcItem.m_tid,
m_expire_date = srcItem.m_expire_date,
State = srcItem.State,
m_iCount = amount,
Crc = srcItem.Crc,
Content = srcItem.Content != null ? (byte[])srcItem.Content.Clone() : null
};
slots[dest] = clone;
}
else
{
if (dstItem.m_tid != srcItem.m_tid) return false;
int pileLimit = Math.Max(1, EC_IvtrItem.GetPileLimit(dstItem.m_tid));
int canAdd = Math.Max(0, pileLimit - Math.Max(0, dstItem.m_iCount));
int add = Math.Min(canAdd, amount);
if (add <= 0) return false;
dstItem.m_iCount += add;
amount = add;
}
RemoveItem(byPackage, src, amount);
return true;
}
public static void UpdatePack(byte byPackage, int ivtrSize, IEnumerable<EC_IvtrItem> items)
{
Resize(byPackage, ivtrSize);
var slots = EnsureSlots(byPackage);
// Clear existing entries; keep size.
for (int i = 0; i < slots.Length; i++)
slots[i] = null;
if (items != null)
{
foreach (var it in items)
{
if (it != null && it.Slot >= 0 && it.Slot < slots.Length)
{
slots[it.Slot] = it;
}
}
}
// Log this pack's items
LogPackInternal(byPackage, ivtrSize, slots);
}
public static bool ResetWithDetailData(byte byPackage, int ivtrSize, byte[] data)
{
// Uses EC_IvtrItem.TryParseInventoryDetail format
if (data == null)
{
Resize(byPackage, ivtrSize);
RemoveAllItems(byPackage);
return true;
}
if (!EC_IvtrItemUtils.Instance.TryParseInventoryDetail(data, out var pkg, out var size, out var items))
return false;
// Prefer header values when valid
byte finalPkg = byPackage;
if (pkg == byPackage) finalPkg = pkg;
int finalSize = ivtrSize > 0 ? ivtrSize : size;
UpdatePack(finalPkg, finalSize, items);
return true;
}
private static void LogPackInternal(byte byPackage, int ivtrSize, EC_IvtrItem[] slots)
{
//Debug.Log($"[Inventory] === Pack {GetPackageName(byPackage)}({byPackage}) size={ivtrSize}, items={slots?.Length ?? 0} ===");
if (slots == null || slots.Length == 0)
{
//Debug.Log("[Inventory] (empty)");
return;
}
for (int i = 0; i < slots.Length; i++)
{
var it = slots[i];
if (it == null)
continue;
string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(it.m_tid);
string extraHex = it.Content != null && it.Content.Length > 0 ? EC_IvtrItemUtils.Instance.BytesToHex(it.Content, MaxContentHexToLog) : "";
//int extraLen = it.Content?.Length ?? 0;
//Debug.Log(
// $"[Inventory] pkg={GetPackageName(it.Package)}({it.Package}) slot={it.Slot} tid={it.TemplateId}{(string.IsNullOrEmpty(itemName) ? "" : " \"" + itemName + "\"")} count={it.Count} state={it.State} expire={it.ExpireDate} crc={it.Crc} content_len={extraLen}{(extraLen > 0 ? ", content_hex=" + extraHex : "")}"
//);
}
}
public static void LogInventoryPacket(string tag, byte[] buffer, int hostId)
{
if (buffer == null)
{
return;
}
int index = 0;
if (buffer.Length < 6)
{
//LogInventoryRaw(tag, buffer);
return;
}
byte byPackage = buffer[index++];
byte ivtrSize = buffer[index++];
uint contentLength = BitConverter.ToUInt32(buffer, index); index += 4;
int remaining = buffer.Length - index;
int contentBytes = remaining;
if (contentLength < (uint)remaining)
{
contentBytes = (int)contentLength;
}
if (contentBytes > 0)
{
byte[] content = new byte[contentBytes];
Buffer.BlockCopy(buffer, index, content, 0, contentBytes);
}
int trailing = buffer.Length - (index + contentBytes);
if (trailing > 0)
{
byte[] tail = new byte[trailing];
Buffer.BlockCopy(buffer, index + contentBytes, tail, 0, trailing);
}
}
public static void LogInventoryRaw(string tag, byte[] buffer)
{
Debug.Log($"[Inventory] {tag}: RAW HEX (len={buffer?.Length ?? 0})=\n{(buffer == null ? "<null>" : BitConverter.ToString(buffer))}");
}
}
}