Files
test/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrItem.cs
T
2026-03-24 10:53:05 +07:00

2185 lines
87 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
using BrewMonster;
using ModelRenderer.Scripts.Common;
using ModelRenderer.Scripts.GameData;
using UnityEngine;
using PerfectWorld.Scripts.Managers;
using BrewMonster.Network;
namespace BrewMonster.Scripts.Managers
{
// NOTE: The original lightweight EC_IvtrItem packet struct has been merged into the
// EC_IvtrItem class below (which mirrors C++ CECIvtrItem). Network-only fields such as
// Package / Slot / State / Crc / Content are now stored on that class.
/// <summary>
/// Non-static UI/data helper for inventory items.
/// This holds caches and helpers for names, icons, pile limits, etc.
/// </summary>
public class EC_IvtrItemUtils
{
// Simple singleton-style access so existing systems can use it globally.
public static readonly EC_IvtrItemUtils Instance = new EC_IvtrItemUtils();
private readonly Dictionary<int, string> _tidNameCache = new Dictionary<int, string>();
private readonly Dictionary<int, int> _pileLimitCache = new Dictionary<int, int>();
private readonly Dictionary<int, string> _tidIconKeyCache = new Dictionary<int, string>();
private readonly Dictionary<string, Sprite> _iconSpriteCache = new Dictionary<string, Sprite>(StringComparer.OrdinalIgnoreCase);
private Sprite[] _multiSpriteAtlas = null;
private readonly Dictionary<string, int> _spriteNameToIndexCache = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
private const int MaxContentHexToLog = 64;
public string ResolveItemName(int templateId)
{
if (templateId <= 0) return "";
//if (_tidNameCache.TryGetValue(templateId, out var cached)) return cached;
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm == null) return CacheAndReturn(templateId, "");
uint id = unchecked((uint)templateId);
DATA_TYPE dATA_TYPE = default;
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE, ref dATA_TYPE);
string name = ExtractNameFromElement(data);
if (string.IsNullOrEmpty(name))
{
name = TryFindNameByScanningArrays(edm, id);
}
return CacheAndReturn(templateId, name ?? "");
}
catch (Exception ex)
{
Debug.LogWarning($"[Inventory] ResolveItemName error for tid={templateId}: {ex.Message}");
return CacheAndReturn(templateId, "");
}
}
/// <summary>
/// Resolve and load the item's icon Sprite from Resources/UI/IconSprites based on its element data file_icon (DDS) name.
/// </summary>
public Sprite ResolveItemIconSprite(int templateId)
{
if (templateId <= 0) return null;
if (_tidIconKeyCache.TryGetValue(templateId, out var cachedKey))
{
if (string.IsNullOrEmpty(cachedKey)) return null;
if (_iconSpriteCache.TryGetValue(cachedKey, out var cachedSprite) && cachedSprite != null)
return cachedSprite;
var sprite = LoadIconSpriteByKey(cachedKey);
_iconSpriteCache[cachedKey] = sprite;
return sprite;
}
string key = string.Empty;
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm != null)
{
uint id = unchecked((uint)templateId);
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
if (data == null)
{
data = TryFindElementByScanningArrays(edm, id);
}
string iconPath = ExtractIconPathFromElement(data);
key = NormalizeIconResourceKeyFromPath(iconPath);
}
}
catch { }
_tidIconKeyCache[templateId] = key ?? string.Empty;
if (string.IsNullOrEmpty(key))
return null;
if (_iconSpriteCache.TryGetValue(key, out var spriteCached) && spriteCached != null)
return spriteCached;
var spriteLoaded = LoadIconSpriteByKey(key);
_iconSpriteCache[key] = spriteLoaded;
return spriteLoaded;
}
private Sprite LoadIconSpriteByKey(string key)
{
if (string.IsNullOrEmpty(key)) return null;
// Load multi-sprite atlas if not already loaded
if (_multiSpriteAtlas == null)
{
LoadMultiSpriteAtlas();
}
if (_multiSpriteAtlas == null) return null;
// Try to find sprite by name in the atlas
if (_spriteNameToIndexCache.TryGetValue(key, out var index))
{
if (index >= 0 && index < _multiSpriteAtlas.Length)
{
return _multiSpriteAtlas[index];
}
}
// Fallback: try to find by name directly in the atlas
foreach (var sprite in _multiSpriteAtlas)
{
if (sprite != null && string.Equals(sprite.name, key, StringComparison.OrdinalIgnoreCase))
{
return sprite;
}
}
// Try lowercase/uppercase variants as fallback
foreach (var sprite in _multiSpriteAtlas)
{
if (sprite != null && (string.Equals(sprite.name, key.ToLowerInvariant(), StringComparison.OrdinalIgnoreCase) ||
string.Equals(sprite.name, key.ToUpperInvariant(), StringComparison.OrdinalIgnoreCase)))
{
return sprite;
}
}
return null;
}
private void LoadMultiSpriteAtlas()
{
try
{
// Load the multi-sprite atlas from Resources
var atlasSprites = EC_Game.GetGameRun().GetUIManager().IconlistIvtr;
//Resources.LoadAll<Sprite>("UI/IconSprites/iconlist_ivtrm_multisprite");
if (atlasSprites != null && atlasSprites.Length > 0)
{
_multiSpriteAtlas = atlasSprites;
// Build name-to-index cache for faster lookups
_spriteNameToIndexCache.Clear();
for (int i = 0; i < atlasSprites.Length; i++)
{
if (atlasSprites[i] != null && !string.IsNullOrEmpty(atlasSprites[i].name))
{
_spriteNameToIndexCache[atlasSprites[i].name] = i;
}
}
}
else
{
_multiSpriteAtlas = new Sprite[0]; // Prevent repeated loading attempts
}
}
catch (Exception ex)
{
_multiSpriteAtlas = new Sprite[0]; // Prevent repeated loading attempts
}
}
private object TryFindElementByScanningArrays(object edm, uint id)
{
if (edm == null) return null;
try
{
var fields = edm.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var f in fields)
{
if (!f.FieldType.IsArray) continue;
var arr = f.GetValue(edm) as Array;
if (arr == null || arr.Length == 0) continue;
var elemType = f.FieldType.GetElementType();
var idField = elemType.GetField("id", BindingFlags.Public | BindingFlags.Instance);
if (idField == null || idField.FieldType != typeof(uint)) continue;
for (int i = 0; i < arr.Length; i++)
{
var element = arr.GetValue(i);
if (element == null) continue;
var value = (uint)idField.GetValue(element);
if (value == id)
{
return element;
}
}
}
}
catch { }
return null;
}
private string ExtractIconPathFromElement(object data)
{
if (data == null) return string.Empty;
var t = data.GetType();
// Common field/property name for icon path in element data is file_icon (byte[128])
var field = t.GetField("file_icon", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (field != null && typeof(byte[]).IsAssignableFrom(field.FieldType))
{
try
{
var bytes = field.GetValue(data) as byte[];
string s = ByteToStringUtils.ByteArrayToCP936String(bytes);
if (string.IsNullOrEmpty(s)) s = ByteToStringUtils.ByteArrayToUTF8String(bytes);
if (string.IsNullOrEmpty(s)) s = ByteToStringUtils.ByteArrayToUnicodeString(bytes);
return s ?? string.Empty;
}
catch { }
}
var prop = t.GetProperty("file_icon", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (prop != null)
{
try
{
if (prop.PropertyType == typeof(string))
{
var s = prop.GetValue(data, null) as string;
return s ?? string.Empty;
}
if (prop.PropertyType == typeof(byte[]))
{
var bytes = prop.GetValue(data, null) as byte[];
string s = ByteToStringUtils.ByteArrayToCP936String(bytes);
if (string.IsNullOrEmpty(s)) s = ByteToStringUtils.ByteArrayToUTF8String(bytes);
if (string.IsNullOrEmpty(s)) s = ByteToStringUtils.ByteArrayToUnicodeString(bytes);
return s ?? string.Empty;
}
}
catch { }
}
return string.Empty;
}
private string NormalizeIconResourceKeyFromPath(string iconPath)
{
if (string.IsNullOrEmpty(iconPath)) return string.Empty;
try
{
string p = iconPath.Replace('\\', '/');
// Some data might contain leading directories; take file name
string fileName = Path.GetFileName(p);
if (string.IsNullOrEmpty(fileName)) fileName = p;
// Remove extension (.dds/.tga/.png)
string name = Path.GetFileNameWithoutExtension(fileName);
if (string.IsNullOrEmpty(name)) name = fileName;
// Many PW icons are numeric (e.g., 8800). Use as-is
return name.Trim();
}
catch { }
return string.Empty;
}
private string CacheAndReturn(int tid, string name)
{
_tidNameCache[tid] = name ?? "";
return name ?? "";
}
private string ExtractNameFromElement(object data)
{
if (data == null) return "";
var t = data.GetType();
// Debug: Log all available fields and properties
// Debug.Log($"[Inventory] Data type: {t.Name}");
var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
// foreach (var f in fields)
// {
// Debug.Log($"[Inventory] Field: {f.Name} ({f.FieldType.Name})");
// }
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
// foreach (var p in props)
// {
// Debug.Log($"[Inventory] Property: {p.Name} ({p.PropertyType.Name})");
// }
var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Instance);
// foreach (var m in methods)
// {
// if (m.Name.ToLower().Contains("name") || m.Name.ToLower().Contains("getname"))
// {
// Debug.Log($"[Inventory] Method: {m.Name} ({m.ReturnType.Name})");
// }
// }
// Prefer decoding the raw fields first to control encoding (Unicode for Vietnamese),
// then fall back to any string properties if needed.
var fieldName = t.GetField("name", BindingFlags.Public | BindingFlags.Instance);
if (fieldName != null && fieldName.FieldType == typeof(ushort[]))
{
var arr = fieldName.GetValue(data) as ushort[];
// Debug: Log the raw ushort array data
if (arr != null && arr.Length > 0)
{
var rawData = string.Join(",", arr.Take(Math.Min(10, arr.Length)));
}
// Vietnamese names are stored as wide chars; decode as Unicode first.
var s = ByteToStringUtils.UshortArrayToUnicodeString(arr);
// Debug log to see what we're getting
if (!string.IsNullOrEmpty(s) && !string.IsNullOrWhiteSpace(s)) return s;
// Fallback to legacy CP936 if Unicode was empty
s = ByteToStringUtils.UshortArrayToCP936String(arr);
if (!string.IsNullOrEmpty(s)) return s;
}
// Try calling GetName method if it exists (similar to C++ pIt->GetName())
var getNameMethod = t.GetMethod("GetName", BindingFlags.Public | BindingFlags.Instance);
if (getNameMethod != null && getNameMethod.ReturnType == typeof(string))
{
try
{
var val = getNameMethod.Invoke(data, null) as string;
if (!string.IsNullOrEmpty(val) && !string.IsNullOrWhiteSpace(val)) return val;
}
catch (Exception ex)
{
Debug.LogWarning($"[Inventory] Error calling GetName method: {ex.Message}");
}
}
return "";
}
private string TryFindNameByScanningArrays(object edm, uint id)
{
try
{
var fields = edm.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var f in fields)
{
if (!f.FieldType.IsArray) continue;
var arr = f.GetValue(edm) as Array;
if (arr == null || arr.Length == 0) continue;
var elemType = f.FieldType.GetElementType();
var idField = elemType.GetField("id", BindingFlags.Public | BindingFlags.Instance);
if (idField == null || idField.FieldType != typeof(uint)) continue;
for (int i = 0; i < arr.Length; i++)
{
var element = arr.GetValue(i);
if (element == null) continue;
var value = (uint)idField.GetValue(element);
if (value == id)
{
var name = ExtractNameFromElement(element);
if (!string.IsNullOrEmpty(name)) return name;
var toStr = element.ToString();
if (!string.IsNullOrEmpty(toStr)) return toStr;
}
}
}
}
catch { }
return "";
}
public bool TryParseInventoryDetail(byte[] buffer, out byte byPackage, out byte ivtrSize, out List<EC_IvtrItem> items)
{
byPackage = 0;
ivtrSize = 0;
items = null;
if (buffer == null || buffer.Length < 6)
{
return false;
}
int index = 0;
byPackage = buffer[index++];
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)
{
items = new List<EC_IvtrItem>();
return true;
}
byte[] content = new byte[contentBytes];
Buffer.BlockCopy(buffer, index, content, 0, contentBytes);
// Parse S2C::cmd_own_ivtr_detail_info.content
int ci = 0;
if (contentBytes < 4)
{
return false;
}
int numItems = BitConverter.ToInt32(content, ci); ci += 4;
items = new List<EC_IvtrItem>(numItems > 0 ? numItems : 0);
for (int i = 0; i < numItems; i++)
{
// Ensure enough bytes for fixed part: index, tid, expire, state, amount, crc(2), len(2) => 4*5 + 2 + 2 = 24 bytes
if (ci + 24 > contentBytes)
{
return false;
}
int slotIndex = BitConverter.ToInt32(content, ci); ci += 4;
if (slotIndex < 0)
{
// Skip invalid slot but continue parsing to keep stream in sync
// Still need to consume fields even if slot is negative
int skipTid = BitConverter.ToInt32(content, ci); ci += 4;
int skipExpire = BitConverter.ToInt32(content, ci); ci += 4;
int skipState = BitConverter.ToInt32(content, ci); ci += 4;
int skipAmt = BitConverter.ToInt32(content, ci); ci += 4;
ushort skipCrc = BitConverter.ToUInt16(content, ci); ci += 2;
ushort skipLen = BitConverter.ToUInt16(content, ci); ci += 2;
if (skipLen > 0)
{
if (ci + skipLen > contentBytes) return false;
ci += skipLen;
}
continue;
}
int tid = BitConverter.ToInt32(content, ci); ci += 4;
int expireDate = BitConverter.ToInt32(content, ci); ci += 4;
int state = BitConverter.ToInt32(content, ci); ci += 4;
int amount = BitConverter.ToInt32(content, ci); ci += 4;
ushort crc = BitConverter.ToUInt16(content, ci); ci += 2;
ushort extraLen = BitConverter.ToUInt16(content, ci); ci += 2;
byte[] extra = null;
if (extraLen > 0)
{
if (ci + extraLen > contentBytes)
{
return false;
}
extra = new byte[extraLen];
Buffer.BlockCopy(content, ci, extra, 0, extraLen);
ci += extraLen;
}
var item = EC_IvtrItem.CreateItem(tid, expireDate, amount);
item.Package = byPackage;
item.Slot = slotIndex;
item.State = state;
item.Crc = crc;
item.Content = extra;
items.Add(item);
}
return true;
}
public 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;
}
public int GetPileLimit(int templateId)
{
if (templateId <= 0) return 1;
if (_pileLimitCache.TryGetValue(templateId, out var cached)) return cached;
int limit = 1;
try
{
var edm = ElementDataManProvider.GetElementDataMan();
if (edm != null)
{
uint id = unchecked((uint)templateId);
DATA_TYPE dt = DATA_TYPE.DT_INVALID;
object data = edm.get_data_ptr(id, ID_SPACE.ID_SPACE_ESSENCE, ref dt);
limit = ExtractPileLimitFromElement(data);
}
}
catch { }
if (limit <= 0) limit = 1;
_pileLimitCache[templateId] = limit;
return limit;
}
private int ExtractPileLimitFromElement(object data)
{
if (data == null) return 1;
var t = data.GetType();
// Common field/property names across item essences
string[] names = new[]
{
"pilelimit", "pile_limit", "pileLimit", "stack", "stack_max", "stackMax", "max_stack", "maxStack"
};
foreach (var name in names)
{
var f = t.GetField(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (f != null && (f.FieldType == typeof(int) || f.FieldType == typeof(uint) || f.FieldType == typeof(short) || f.FieldType == typeof(ushort) || f.FieldType == typeof(byte)))
{
try
{
var val = f.GetValue(data);
int limit = Convert.ToInt32(val);
if (limit > 0) return limit;
}
catch { }
}
var p = t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p != null && (p.PropertyType == typeof(int) || p.PropertyType == typeof(uint) || p.PropertyType == typeof(short) || p.PropertyType == typeof(ushort) || p.PropertyType == typeof(byte)))
{
try
{
var val = p.GetValue(data, null);
int limit = Convert.ToInt32(val);
if (limit > 0) return limit;
}
catch { }
}
}
return 1;
}
}
/// <summary>
/// C# mirror of C++ <c>CECIvtrItem</c> (defined in <c>EC_IvtrItem.h / EC_IvtrItem.cpp</c>).
/// This class intentionally keeps C++-style naming and layout so other C++ systems
/// can be ported over with minimal friction.
/// </summary>
/// TODO: have to rename to CECIvtrItem to match C++ naming
public class EC_IvtrItem
{
// NOTE: The nested enums and fields mirror the original C++ names and values.
// Inventory item class ID
public enum InventoryClassId
{
ICID_ITEM = -100,
ICID_EQUIP = -101,
ICID_ARMOR = 0,
ICID_ARMORRUNE,
ICID_ARROW,
ICID_DECORATION,
ICID_DMGRUNE,
ICID_ELEMENT,
ICID_FASHION,
ICID_FLYSWORD,
ICID_MATERIAL,
ICID_MEDICINE,
ICID_REVSCROLL,
ICID_SKILLTOME,
ICID_TOSSMAT,
ICID_TOWNSCROLL,
ICID_UNIONSCROLL,
ICID_WEAPON,
ICID_TASKITEM,
ICID_STONE,
ICID_WING,
ICID_TASKDICE,
ICID_TASKNMMATTER,
ICID_ERRORITEM,
ICID_FACETICKET,
ICID_FACEPILL,
ICID_GM_GENERATOR,
ICID_RECIPE,
ICID_PETEGG,
ICID_PETFOOD,
ICID_PETFACETICKET,
ICID_FIREWORK,
ICID_TANKCALLIN,
ICID_SKILLMATTER,
ICID_REFINETICKET,
ICID_DESTROYINGESSENCE,
ICID_BIBLE,
ICID_SPEAKER,
ICID_AUTOHP,
ICID_AUTOMP,
ICID_DOUBLEEXP,
ICID_TRANSMITSCROLL,
ICID_DYETICKET,
ICID_GOBLIN,
ICID_GOBLIN_EQUIP,
ICID_GOBLIN_EXPPILL,
ICID_CERTIFICATE,
ICID_TARGETITEM,
ICID_LOOKINFOITEM,
ICID_INCSKILLABILITY,
ICID_WEDDINGBOOKCARD,
ICID_WEDDINGINVITECARD,
ICID_SHARPENER,
ICID_FACTIONMATERIAL,
ICID_CONGREGATE,
ICID_FORCETOKEN,
ICID_DYNSKILLEQUIP,
ICID_MONEYCONVERTIBLE,
ICID_MONSTERSPIRIT,
ICID_GENERALCARD,
ICID_GENERALCARD_DICE,
ICID_SHOPTOKEN,
ICID_UNIVERSAL_TOKEN,
}
// Item price scale type
public enum ScaleType
{
SCALE_BUY = 1, // Buy from NPC
SCALE_SELL, // Sell to NPC
SCALE_BOOTH, // Booth item
SCALE_MAKE, // Make item
SCALE_OFFLINESHOP, // Offline shop
}
// Description type
public enum DescType
{
DESC_NORMAL = 0,
DESC_BOOTHBUY,
DESC_REPAIR,
DESC_REWARD,
DESC_PRODUCE,
}
// Item use conditions (bit flags)
[Flags]
public enum UseCondition
{
USE_ATKTARGET = 0x0001, // Attack target
USE_PERSIST = 0x0002, // Persist some time
USE_TARGET = 0x0004, // Normal target
}
// Proc-type (bit flags)
[Flags]
public enum ProcType
{
PROC_DROPWHENDIE = 0x0001,
PROC_DROPPABLE = 0x0002,
PROC_SELLABLE = 0x0004,
PROC_LOG = 0x0008,
PROC_TRADEABLE = 0x0010,
PROC_TASK = 0x0020,
PROC_BIND = 0x0040,
PROC_UNBINDABLE = 0x0080,
PROC_DISAPEAR = 0x0100,
PROC_USE = 0x0200,
PROC_DEADDROP = 0x0400,
PROC_OFFLINE = 0x0800,
PROC_UNREPAIRABLE = 0x1000,
PROC_DESTROYING = 0x2000,
PROC_NO_USER_TRASH = 0x4000,
PROC_BINDING = 0x8000,
PROC_CAN_WEBTRADE = 0x10000,
}
// Network / UI metadata (from S2C::cmd_own_ivtr_detail_info etc.)
// These did not exist in the original C++ class but are useful on the client.
public byte Package;
public int Slot;
public int State;
public ushort Crc;
public byte[] Content; // variable-length item-specific payload (can be null)
// Fields mirror the original C++ layout.
// NOTE: These are public so that legacy ported code can access them directly,
// matching the original C-style struct usage. Prefer using accessors where possible.
public int m_iCID; // Class ID
public int m_tid; // Template id
public int m_expire_date; // Expiration date
public int m_iCount; // Item count
public int m_iPileLimit; // Pile limit number
public int m_iPrice; // Item unit price
public int m_iShopPrice; // Shop price
public long m_i64EquipMask; // Equip mask
public bool m_bEmbeddable; // true, embeddable item
public bool m_bUseable; // true, item can be used
public bool m_bFrozen; // Frozen flag set by local reason
public bool m_bNetFrozen; // Frozen flag set by net reason
public uint m_dwUseFlags; // Use condition flags
public int m_iProcType; // proc-type flag
public int m_iScaleType; // Item price scale type
public float m_fPriceScale; // Price scale
public bool m_bNeedUpdate; // true, detail data needs to be updated
public bool m_bUpdating; // true, being updating detail data
public uint m_dwUptTime; // Time when updating request was sent (ms)
public string m_strDesc = ""; // Item description
public bool m_bIsInNPCPack; // true, this item is in NPC package
public bool m_bLocalDetailData; // true, data from GetDetailDataFromLocal
public EC_Inventory m_pDescIvtr; // Inventory only used to get item description
private ushort m_dwData;
private string m_strDataName;
#region Constructors
public EC_IvtrItem()
{
// Default constructor for object-initializer scenarios;
// mirrors the main constructor but with tid/expire_date = 0.
m_iCID = (int)InventoryClassId.ICID_ITEM;
m_tid = 0;
m_expire_date = 0;
m_iCount = 0;
m_iPileLimit = 1;
m_iPrice = 1;
m_iShopPrice = 1;
m_bNeedUpdate = true;
m_bUpdating = false;
m_dwUptTime = 0;
m_i64EquipMask = 0;
m_bEmbeddable = false;
m_bUseable = false;
m_bFrozen = false;
m_bNetFrozen = false;
m_dwUseFlags = 0;
m_iProcType = 0;
m_iScaleType = (int)ScaleType.SCALE_SELL;
m_fPriceScale = 1.0f; // PLAYER_PRICE_SCALE equivalent
m_strDesc = string.Empty;
m_bIsInNPCPack = false;
m_bLocalDetailData = false;
m_pDescIvtr = null;
}
public EC_IvtrItem(int tid, int expire_date)
{
m_iCID = (int)InventoryClassId.ICID_ITEM;
m_tid = tid;
m_expire_date = expire_date;
m_iCount = 0;
m_iPileLimit = 1;
m_iPrice = 1;
m_iShopPrice = 1;
m_bNeedUpdate = true;
m_bUpdating = false;
m_dwUptTime = 0;
m_i64EquipMask = 0;
m_bEmbeddable = false;
m_bUseable = false;
m_bFrozen = false;
m_bNetFrozen = false;
m_dwUseFlags = 0;
m_iProcType = 0;
m_iScaleType = (int)ScaleType.SCALE_SELL;
m_fPriceScale = 1.0f; // PLAYER_PRICE_SCALE equivalent
m_strDesc = string.Empty;
m_bIsInNPCPack = false;
m_bLocalDetailData = false;
m_pDescIvtr = null;
}
public EC_IvtrItem(EC_IvtrItem s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
m_iCID = s.m_iCID;
m_tid = s.m_tid;
m_expire_date = s.m_expire_date;
m_iCount = s.m_iCount;
m_iPileLimit = s.m_iPileLimit;
m_iPrice = s.m_iPrice;
m_iShopPrice = s.m_iShopPrice;
m_i64EquipMask = s.m_i64EquipMask;
m_bEmbeddable = s.m_bEmbeddable;
m_bUseable = s.m_bUseable;
m_bFrozen = false;
m_bNetFrozen = false;
m_dwUseFlags = s.m_dwUseFlags;
m_iProcType = s.m_iProcType;
m_iScaleType = s.m_iScaleType;
m_fPriceScale = s.m_fPriceScale;
m_bNeedUpdate = s.m_bNeedUpdate;
m_bUpdating = false;
m_dwUptTime = 0;
m_strDesc = s.m_strDesc;
m_bIsInNPCPack = s.m_bIsInNPCPack;
m_bLocalDetailData = s.m_bLocalDetailData;
m_pDescIvtr = s.m_pDescIvtr;
}
#endregion
#region Static helpers (mirror C++)
/// <summary>
/// Create an inventory item. For now this returns the base type.
/// Later this can be expanded to instantiate specific subclasses (weapon, armor, etc.)
/// based on element data type, mirroring the C++ switch in <c>CreateItem</c>.
/// </summary>
public static EC_IvtrItem CreateItem(int tid, int expire_date, int iCount, int idSpace = 0)
{
var pItem = new EC_IvtrItem(tid, expire_date);
DATA_TYPE DataType = DATA_TYPE.DT_INVALID;
object data = ElementDataManProvider.GetElementDataMan().get_data_ptr((uint)tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType);
//Debug.Log("Create item data: DataType: " + DataType);
switch (DataType)
{
case DATA_TYPE.DT_WEAPON_ESSENCE:
pItem = new CECIvtrWeapon(tid, expire_date);
break;
case DATA_TYPE.DT_PROJECTILE_ESSENCE:
pItem = new CECIvtrArrow(tid, expire_date);
break;
case DATA_TYPE.DT_ARMOR_ESSENCE:
pItem = new EC_IvtrArmor(tid, expire_date);
break;
case DATA_TYPE.DT_DECORATION_ESSENCE:
pItem = new EC_IvtrDecoration(tid, expire_date);
break;
case DATA_TYPE.DT_FASHION_ESSENCE:
pItem = new EC_IvtrFashion(tid, expire_date);
break;
case DATA_TYPE.DT_MEDICINE_ESSENCE:
pItem = new EC_IvtrMedicine(tid, expire_date);
break;
case DATA_TYPE.DT_MATERIAL_ESSENCE:
pItem = new EC_IvtrMaterial(tid, expire_date);
break;
case DATA_TYPE.DT_DAMAGERUNE_ESSENCE:
pItem = new EC_IvtrDamagerune(tid, expire_date);
break;
case DATA_TYPE.DT_ARMORRUNE_ESSENCE:
pItem = new EC_IvtrArmorrune(tid, expire_date);
break;
case DATA_TYPE.DT_SKILLTOME_ESSENCE:
pItem = new EC_IvtrSkilltome(tid, expire_date);
break;
case DATA_TYPE.DT_FLYSWORD_ESSENCE:
pItem = new EC_IvtrFlysword(tid, expire_date);
break;
case DATA_TYPE.DT_TOWNSCROLL_ESSENCE:
pItem = new EC_IvtrTownScroll(tid, expire_date);
break;
case DATA_TYPE.DT_UNIONSCROLL_ESSENCE:
pItem = new EC_IvtrUnionscroll(tid, expire_date);
break;
case DATA_TYPE.DT_REVIVESCROLL_ESSENCE:
pItem = new EC_IvtrRevScroll(tid, expire_date);
break;
case DATA_TYPE.DT_ELEMENT_ESSENCE:
pItem = new EC_IvtrElement(tid, expire_date);
break;
case DATA_TYPE.DT_TOSSMATTER_ESSENCE:
pItem = new EC_IvtrTossMat(tid, expire_date);
break;
case DATA_TYPE.DT_TASKMATTER_ESSENCE:
pItem = new EC_IvtrTaskItem(tid, expire_date);
break;
case DATA_TYPE.DT_STONE_ESSENCE:
pItem = new EC_IvtrStone(tid, expire_date);
break;
case DATA_TYPE.DT_WINGMANWING_ESSENCE:
pItem = new EC_IvtrWing(tid, expire_date);
break;
case DATA_TYPE.DT_TASKDICE_ESSENCE:
pItem = new EC_IvtrTaskDice(tid, expire_date);
break;
case DATA_TYPE.DT_TASKNORMALMATTER_ESSENCE:
pItem = new EC_IvtrTaskNmMatter(tid, expire_date);
break;
case DATA_TYPE.DT_FACETICKET_ESSENCE:
pItem = new EC_IvtrFaceTicket(tid, expire_date);
break;
case DATA_TYPE.DT_FACEPILL_ESSENCE:
pItem = new EC_IvtrFacePill(tid, expire_date);
break;
case DATA_TYPE.DT_GM_GENERATOR_ESSENCE:
pItem = new EC_IvtrGmGenerator(tid, expire_date);
break;
case DATA_TYPE.DT_RECIPE_ESSENCE:
pItem = new EC_IvtrRecipe(tid, expire_date);
break;
case DATA_TYPE.DT_PET_EGG_ESSENCE:
pItem = new EC_IvtrPetEgg(tid, expire_date);
break;
case DATA_TYPE.DT_PET_FOOD_ESSENCE:
pItem = new EC_IvtrPetFood(tid, expire_date);
break;
case DATA_TYPE.DT_PET_FACETICKET_ESSENCE:
pItem = new EC_IvtrPetFaceTicket(tid, expire_date);
break;
case DATA_TYPE.DT_FIREWORKS_ESSENCE:
pItem = new EC_IvtrFirework(tid, expire_date);
break;
case DATA_TYPE.DT_WAR_TANKCALLIN_ESSENCE:
pItem = new EC_IvtrWarTankCallin(tid, expire_date);
break;
case DATA_TYPE.DT_SKILLMATTER_ESSENCE:
pItem = new EC_IvtrSkillMat(tid, expire_date);
break;
case DATA_TYPE.DT_INC_SKILL_ABILITY_ESSENCE:
pItem = new EC_IvtrIncSkillAbility(tid, expire_date);
break;
case DATA_TYPE.DT_REFINE_TICKET_ESSENCE:
pItem = new EC_IvtrRefineTicket(tid, expire_date);
break;
case DATA_TYPE.DT_DESTROYING_ESSENCE:
pItem = new EC_IvtrDestroyingEssence(tid, expire_date);
break;
case DATA_TYPE.DT_BIBLE_ESSENCE:
pItem = new EC_IvtrBible(tid, expire_date);
break;
case DATA_TYPE.DT_SPEAKER_ESSENCE:
pItem = new EC_IvtrSpeaker(tid, expire_date);
break;
case DATA_TYPE.DT_AUTOHP_ESSENCE:
pItem = new EC_IvtrAutoHp(tid, expire_date);
break;
case DATA_TYPE.DT_AUTOMP_ESSENCE:
pItem = new EC_IvtrAutoMp(tid, expire_date);
break;
case DATA_TYPE.DT_DOUBLE_EXP_ESSENCE:
pItem = new EC_IvtrDoubleExp(tid, expire_date);
break;
case DATA_TYPE.DT_DYE_TICKET_ESSENCE:
pItem = new EC_IvtrDyeTicket(tid, expire_date);
break;
case DATA_TYPE.DT_TRANSMITSCROLL_ESSENCE:
pItem = new EC_IvtrTransmitScroll(tid, expire_date);
break;
case DATA_TYPE.DT_GOBLIN_ESSENCE:
pItem = new EC_IvtrGoblin(tid, expire_date);
break;
case DATA_TYPE.DT_GOBLIN_EQUIP_ESSENCE:
pItem = new EC_IvtrGoblinEquip(tid, expire_date);
break;
case DATA_TYPE.DT_GOBLIN_EXPPILL_ESSENCE:
pItem = new EC_IvtrGoblinExpPill(tid, expire_date);
break;
case DATA_TYPE.DT_SELL_CERTIFICATE_ESSENCE:
pItem = new EC_IvtrCertificate(tid, expire_date);
break;
case DATA_TYPE.DT_TARGET_ITEM_ESSENCE:
pItem = new EC_IvtrTargetItem(tid, expire_date);
break;
case DATA_TYPE.DT_LOOK_INFO_ESSENCE:
pItem = new EC_IvtrLookInfoItem(tid, expire_date);
break;
case DATA_TYPE.DT_WEDDING_BOOKCARD_ESSENCE:
pItem = new EC_IvtrWeddingBookCard(tid, expire_date);
break;
case DATA_TYPE.DT_WEDDING_INVITECARD_ESSENCE:
pItem = new EC_IvtrWeddingInviteCard(tid, expire_date);
break;
case DATA_TYPE.DT_SHARPENER_ESSENCE:
pItem = new EC_IvtrSharpener(tid, expire_date);
break;
case DATA_TYPE.DT_FACTION_MATERIAL_ESSENCE:
pItem = new EC_IvtrFactionMaterial(tid, expire_date);
break;
case DATA_TYPE.DT_CONGREGATE_ESSENCE:
pItem = new EC_IvtrCongregate(tid, expire_date);
break;
case DATA_TYPE.DT_FORCE_TOKEN_ESSENCE:
pItem = new EC_IvtrForceToken(tid, expire_date);
break;
case DATA_TYPE.DT_DYNSKILLEQUIP_ESSENCE:
pItem = new EC_IvtrDynSkillEquip(tid, expire_date);
break;
case DATA_TYPE.DT_MONEY_CONVERTIBLE_ESSENCE:
pItem = new EC_IvtrMoneyConvertible(tid, expire_date);
break;
case DATA_TYPE.DT_MONSTER_SPIRIT_ESSENCE:
pItem = new EC_IvtrMonsterSpirit(tid, expire_date);
break;
case DATA_TYPE.DT_POKER_ESSENCE:
pItem = new EC_IvtrGeneralCard(tid, expire_date);
break;
case DATA_TYPE.DT_POKER_DICE_ESSENCE:
pItem = new EC_IvtrGeneralCardDice(tid, expire_date);
break;
case DATA_TYPE.DT_SHOP_TOKEN_ESSENCE:
pItem = new EC_IvtrShopToken(tid, expire_date);
break;
case DATA_TYPE.DT_UNIVERSAL_TOKEN_ESSENCE:
pItem = new EC_IvtrUniversalToken(tid, expire_date);
break;
default:
pItem = new EC_IvtrUnknown(tid, expire_date);
break;
}
pItem.SetCount(iCount);
return pItem;
}
/// <summary>
/// Get pile limit for a given template id.
/// C++ implementation creates a temporary item and calls its GetPileLimit().
/// Here we read directly from element data, mirroring EC_IvtrItemUtils logic.
/// </summary>
public static int GetPileLimit(int tid)
{
if (tid <= 0) return 1;
// Reuse the same cache as EC_IvtrItemUtils to avoid duplicate lookups.
// We keep this small helper inside the item class so gameplay code
// does not depend on UI helpers.
var utils = EC_IvtrItemUtils.Instance;
return utils.GetPileLimit(tid);
}
// Check whether item2 is item1's candidate (only medicines have candidates).
// Porting the full logic requires medicine essence structures; for now keep the
// signature and return false so code can be wired up later.
public static bool IsCandidate(int tid1, int tid2)
{
return false;
}
public static bool IsCandidate(int tid1, EC_IvtrItem pItem2)
{
return false;
}
/// <summary>
/// Get scaled price of specified count of items.
/// Exact port of the C++ price scaling logic.
/// </summary>
public static int GetScaledPrice(int iUnitPrice, int iCount, int iScaleType, float fScale)
{
if (iCount == 0)
return 0;
int iPrice = 0;
switch ((ScaleType)iScaleType)
{
case ScaleType.SCALE_BUY:
iPrice = (int)(iUnitPrice * fScale + 0.5f);
if (iPrice >= 1000)
iPrice = ((iPrice + 99) / 100) * 100;
else if (iPrice >= 100)
iPrice = ((iPrice + 9) / 10) * 10;
iPrice *= iCount;
break;
case ScaleType.SCALE_SELL:
iPrice = (int)(iUnitPrice * iCount * fScale + 0.5f);
break;
case ScaleType.SCALE_BOOTH:
case ScaleType.SCALE_MAKE:
iPrice = iUnitPrice * iCount;
break;
default:
iPrice = iUnitPrice * iCount;
break;
}
return iPrice;
}
public static bool IsSharpenerProperty(byte propertyType)
{
return propertyType >= 100 && propertyType <= 115;
}
#endregion
#region Virtual operations (instance side)
/// <summary>
/// Set item detail information.
/// C++ default just clears update flags; detail parsing happens in derived types.
/// </summary>
public virtual bool SetItemInfo(byte[] pInfoData, int iDataLen)
{
if (pInfoData != null && iDataLen > 0)
{
this.Content = new byte[iDataLen];
Buffer.BlockCopy(pInfoData, 0, this.Content, 0, iDataLen);
}
else
{
this.Content = null;
}
m_bNeedUpdate = false;
m_bUpdating = false;
m_strDesc = string.Empty;
return true;
}
/// <summary>Get item default information from database (no-op base, like C++).</summary>
public virtual void DefaultInfo()
{
}
/// <summary>Get item icon file name (C++ default returns "Unknown.dds").</summary>
public virtual string GetIconFile()
{
return "Unknown.dds";
}
/// <summary>
/// Get item name. In C++ this uses string tables; here we go through
/// <see cref="EC_IvtrItemUtils.ResolveItemName(int)"/> which already mirrors that logic.
/// </summary>
public virtual string GetName()
{
return EC_IvtrItemUtils.Instance.ResolveItemName(m_tid);
}
/// <summary>
/// Get item name color. The original returns an <c>A3DCOLOR</c>;
/// here we return ARGB packed into a <see cref="uint"/>; default is white.
/// </summary>
public virtual uint GetNameColor()
{
return 0xFFFFFFFFu;
}
/// <summary>Use item. Base class just returns true.</summary>
public virtual bool Use()
{
return true;
}
/// <summary>Get scaled price of this item instance.</summary>
public virtual int GetScaledPrice()
{
int iPrice = m_iScaleType == (int)ScaleType.SCALE_BUY ? m_iShopPrice : m_iPrice;
return GetScaledPrice(iPrice, m_iCount, m_iScaleType, m_fPriceScale);
}
/// <summary>Clone item (shallow copy, same as C++ default).</summary>
public virtual EC_IvtrItem Clone()
{
return CreateItem(m_tid, m_expire_date, m_iCount, m_iCID);
}
/// <summary>Get item cool time in milliseconds (0 by default).</summary>
public virtual int GetCoolTime(out int piMax)
{
piMax = -1;
return 0;
}
public virtual bool CheckUseCondition()
{
return IsUseable();
}
/// <summary>Get drop model for showing in world.</summary>
public virtual string GetDropModel()
{
return "Models\\Error\\Error.ecm";
}
/// <summary>Get item quality level. Base returns -1 (unknown).</summary>
public virtual int GetItemLevel()
{
return -1;
}
/// <summary>
/// Get item description text (normal / booth-buy / reward / repair).
/// Mirrors the inline C++ <c>GetDesc</c>.
/// </summary>
public string GetDesc(DescType iDescType = DescType.DESC_NORMAL, EC_Inventory pInventory = null)
{
m_pDescIvtr = pInventory;
switch (iDescType)
{
case DescType.DESC_BOOTHBUY:
return GetBoothBuyDesc();
case DescType.DESC_REWARD:
return GetRewardDesc();
default:
return GetNormalDesc(iDescType == DescType.DESC_REPAIR);
}
}
/// <summary>
/// Merge item amount with another same kind item.
/// Returns the number of items actually merged.
/// </summary>
public int MergeItem(int tid, int iAmount)
{
if (tid != m_tid || m_iCount >= m_iPileLimit)
return 0;
int iNumAdd = iAmount;
if (m_iCount + iNumAdd > m_iPileLimit)
iNumAdd = m_iPileLimit - m_iCount;
m_iCount += iNumAdd;
return iNumAdd;
}
/// <summary>Add item amount. Returns new amount of item.</summary>
public int AddAmount(int iAmount)
{
Debug.Log($"[EC_IvtrItem] Old Amount: {m_iCount}");
m_iCount += iAmount;
if (m_iCount < 0) m_iCount = 0;
//if (m_iCount > m_iPileLimit) m_iCount = m_iPileLimit;
Debug.Log($"[EC_IvtrItem] New Amount: {m_iCount}");
return m_iCount;
}
public void SetAmount(int iAmount)
{
m_iCount = iAmount;
}
public void SetExpireDate(int iExpireDate)
{
m_expire_date = iExpireDate;
}
/// <summary>
/// Can this item be equipped to specified position?
/// Uses the same bitmask test as C++.
/// </summary>
public bool CanEquippedTo(int iSlot)
{
return (m_i64EquipMask & (1L << iSlot)) != 0;
}
/// <summary>
/// Get first slot index starting from <paramref name="iStartSlot"/> where this item can be equipped.
/// </summary>
public int GetEquippedSlot(int iStartSlot = 0)
{
if (!IsEquipment())
return -1;
// C++ uses SIZE_EQUIPIVTR constant; here caller should limit range if needed.
for (int i = iStartSlot; i < 64; ++i)
{
if (CanEquippedTo(i))
return i;
}
return -1;
}
/// <summary>
/// Can this item be put into account box?
/// Full C++ logic depends on config data; here we mirror the simple local flag check
/// and leave server-side blacklist for future porting.
/// </summary>
public bool CanPutIntoAccBox()
{
if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0)
return false;
return true;
}
#endregion
#region Simple property-style accessors (1:1 with C++)
public int GetClassID() => m_iCID;
public int GetTemplateID() => m_tid;
public int GetExpireDate() => m_expire_date;
public int GetCount() => m_iCount;
public void SetCount(int iCount) => m_iCount = iCount;
public int GetPileLimitInstance() => m_iPileLimit;
public int GetUnitPrice() => m_iPrice;
public void SetUnitPrice(int iPrice) => m_iPrice = iPrice;
public int GetShopPrice() => m_iShopPrice;
public long GetEquipMask() => m_i64EquipMask;
public void SetPriceScale(int iType, float fScale)
{
m_iScaleType = iType;
m_fPriceScale = fScale;
}
public bool IsInNPCPack() => m_bIsInNPCPack;
public void SetInNPCPack(bool bInNPCPack) => m_bIsInNPCPack = bInNPCPack;
public int GetProcType() => m_iProcType;
public void SetProcType(int iType) => m_iProcType = iType;
public bool IsEmbeddable() => m_bEmbeddable;
public bool IsUseable() => m_bUseable;
public bool IsEquipment() => m_i64EquipMask != 0;
public bool IsFrozen() => m_bFrozen || m_bNetFrozen;
public virtual bool IsTradeable()
{
bool tradeableFlag = (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0;
bool bindingFlag = (m_iProcType & (int)ProcType.PROC_BINDING) != 0;
return !(tradeableFlag || bindingFlag);
}
public virtual bool IsWebTradeable()
{
return IsTradeable() || (m_iProcType & (int)ProcType.PROC_CAN_WEBTRADE) != 0;
}
public bool IsBinding()
{
bool binding = (m_iProcType & (int)ProcType.PROC_BINDING) != 0;
bool canWebTrade = (m_iProcType & (int)ProcType.PROC_CAN_WEBTRADE) != 0;
return binding && !canWebTrade;
}
public bool IsSellable()
{
return (m_iProcType & (int)ProcType.PROC_SELLABLE) == 0;
}
public bool IsRepairable()
{
return (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) == 0;
}
public virtual bool IsRare()
{
return GetUnitPrice() >= 10000 || m_iCID == (int)InventoryClassId.ICID_MONEYCONVERTIBLE;
}
public bool NeedUpdate() => m_bNeedUpdate;
public void GetDetailDataFromSev(int iPack, int iSlot)
{
// Full network request logic will be wired when the session layer is ported.
// We keep the state flags to mirror C++ behavior.
if (!m_bNeedUpdate)
return;
if (m_bUpdating)
{
// In C++ this checks a timer and can resend; here we just early-out.
return;
}
m_bUpdating = true;
// m_dwUptTime could be set from a game time provider when available.
}
public virtual void GetDetailDataFromLocal()
{
//itemdataman* pItemDataMan = g_pGame->GetItemDataMan();
object pData_temp = itemdataman.get_item_for_sell((uint)m_tid);
if (pData_temp == null)
{
SetItemInfo(null, 0);
SetLocalProps();
m_bLocalDetailData = true;
return;
}
item_data pData = (item_data)pData_temp;
SetItemInfo(pData.item_content, pData.content_length);
SetLocalProps();
m_bLocalDetailData = true;
}
public bool IsDataFromLocal() => m_bLocalDetailData;
public void Freeze(bool bFreeze) => m_bFrozen = bFreeze;
public void NetFreeze(bool bFreeze) => m_bNetFrozen = bFreeze;
public uint GetUseFlags() => m_dwUseFlags;
public bool Use_AtkTarget() => (m_dwUseFlags & (uint)UseCondition.USE_ATKTARGET) != 0;
public bool Use_Persist() => (m_dwUseFlags & (uint)UseCondition.USE_PERSIST) != 0;
public bool Use_Target() => (m_dwUseFlags & (uint)UseCondition.USE_TARGET) != 0;
#endregion
#region Protected description helpers (simplified stubs)
/// <summary>
/// Base implementation: just returns current description string.
/// Derived item types can override and build rich description like in C++.
/// </summary>
protected virtual string GetNormalDesc(bool bRepair)
{
// Build a simple but centralized description:
// - Name
// - String-table description (if any)
// - Extended description (if any)
m_strDesc = string.Empty;
// Item name line
CECStringTab pDescTab = EC_Game.GetItemDesc();
AddDescText((int)DescriptipionMsg.ITEMDESC_COL_WHITE, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_ERRORITEM));
AddDescText((int)DescriptipionMsg.ITEMDESC_COL_WHITE, false, "({0})", m_tid);
m_strDesc += "\\r";
AddDescText((int)DescriptipionMsg.ITEMDESC_COL_RED, false, "This Item Type is not implemented yet. Type of {0}", this.GetType().Name);
TrimLastReturn();
return m_strDesc;
}
protected virtual string GetBoothBuyDesc()
{
return GetNormalDesc(false);
}
protected virtual string GetRewardDesc()
{
return GetNormalDesc(false);
}
protected virtual void AddPriceDesc(int col, bool bRepair)
{
// Basic price string using scaled price; color is ignored at this level.
if ((!IsEquipment() && bRepair) || m_iPrice == 0 || m_fPriceScale == 0.0f)
{
TrimLastReturn();
return;
}
// use specific color for the item price
if ((int)DescriptipionMsg.ITEMDESC_COL_WHITE == col)
{
if (m_iPrice >= 100000000) // 100 million
col = (int)DescriptipionMsg.ITEMDESC_COL_GREEN;
else if (m_iPrice >= 10000000) // 10 million
col = (int)DescriptipionMsg.ITEMDESC_COL_DARKGOLD;
else if (m_iPrice >= 1000000) // 1 million
col = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW;
}
CECStringTab pDescTab = EC_Game.GetItemDesc();
if (m_iScaleType == (int)ScaleType.SCALE_OFFLINESHOP)
{
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PRICE));
string s1, s2;
BuildPriceNumberStr(m_iPrice, out s1);
if (GetCount() > 1)
{
s2 = (m_iPrice * (long)GetCount()).ToString();
AddDescText(-1, false, " %s (%s)", s1, s2);
}
else
AddDescText(-1, false, " %s", s1);
}
else if (m_iScaleType == (int)ScaleType.SCALE_BOOTH || m_tid == 21652) // 21651: yinpiao
{
string s1;
BuildPriceNumberStr(m_iPrice, out s1);
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_UNITPRICE));
AddDescText(-1, false, " %s", s1);
}
else if (m_iScaleType == (int)ScaleType.SCALE_SELL && m_iCount > 1 && m_tid != 21652)
{
string s1, s2;
BuildPriceNumberStr(m_iPrice, out s1);
BuildPriceNumberStr(GetScaledPrice(), out s2);
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PRICE));
AddDescText(-1, false, " %s (%s)", s1, s2);
}
else
{
string s1;
BuildPriceNumberStr(GetScaledPrice(), out s1);
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PRICE));
AddDescText(-1, false, " %s", s1);
}
}
protected virtual void AddProfReqDesc(uint iProfReq)
{
if (EC_ProfConfigs.ContainsAllProfession(iProfReq))
{
return;// All profession permit equirement
}
CECStringTab pDescTab = EC_Game.GetItemDesc();
CECGameRun pGameRun = EC_Game.GetGameRun();
CECHostPlayer pHost = pGameRun.GetHostPlayer();
int col = (iProfReq & (1 << pHost.GetProfession())) != 0 ? (int)DescriptipionMsg.ITEMDESC_COL_WHITE : (int)DescriptipionMsg.ITEMDESC_COL_RED;
AddDescText(col, false, pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_PROFESSIONREQ));
for (int i = 0; i < (int)Profession.NUM_PROFESSION; i++)
{
if ((iProfReq & (1 << i)) != 0)
{
m_strDesc += " ";
string profName = pGameRun.GetProfName(i);
// Remove newline and carriage return characters that cause UI display issues
if (!string.IsNullOrEmpty(profName))
{
profName = profName.Replace("\r", "").Replace("\n", "").Trim();
}
AddDescText(col, false, profName);
}
}
AddDescText(col, true, " ");
}
protected virtual int DecideNameCol()
{
return -1;
}
protected virtual void SetLocalProps()
{
}
protected virtual void AddDescText(int iCol, bool bRet, string szText, params object[] args)
{
// Add color prefix if color is specified
if (iCol >= 0)
{
string colorStr = GetColorString((DescriptipionMsg)iCol);
m_strDesc += colorStr;
}
string line;
if (args != null && args.Length > 0)
{
// Accept both C#-style ("{0}") and printf-style ("%d", "%+d", "%.2f", "%%") formats
if (szText.IndexOf('%') >= 0 && szText.IndexOf('{') < 0)
{
line = FormatPrintfLike(szText, args);
}
else
{
line = string.Format(szText, args);
}
}
else
{
line = szText;
}
m_strDesc += line;
if (bRet)
m_strDesc += "\n";
}
/// <summary>
/// Get color string for color ID
/// Returns color codes in ^RRGGBB format (6 hex digits) for text formatting
/// </summary>
protected virtual string GetColorString(DescriptipionMsg colorId)
{
switch (colorId)
{
case DescriptipionMsg.ITEMDESC_COL_WHITE: return "^FFFFFF"; // White
case DescriptipionMsg.ITEMDESC_COL_GREEN: return "^00FF00"; // Green
case DescriptipionMsg.ITEMDESC_COL_YELLOW: return "^FFFF00"; // Yellow
case DescriptipionMsg.ITEMDESC_COL_DARKGOLD: return "^FF8C00"; // Dark Gold / Orange
case DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE: return "^5998FF"; // Light Blue
case DescriptipionMsg.ITEMDESC_COL_CYANINE: return "^00FFFF"; // Cyan
case DescriptipionMsg.ITEMDESC_COL_RED: return "^FF0000"; // Red
case DescriptipionMsg.ITEMDESC_COL_GRAY: return "^808080"; // Gray
default: return "^FFFFFF"; // Default to white
}
}
/// <summary>
/// Format string using printf-style format specifiers (%d, %+d, %.2f, %s, etc.)
/// Converts printf-style formats to C# string formatting
/// </summary>
private static string FormatPrintfLike(string fmt, object[] args)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
int argIndex = 0;
for (int i = 0; i < fmt.Length; i++)
{
char c = fmt[i];
if (c != '%')
{
sb.Append(c);
continue;
}
if (i + 1 < fmt.Length && fmt[i + 1] == '%')
{
sb.Append('%');
i += 1;
continue;
}
bool plus = false;
int precision = -1;
int j = i + 1;
if (j < fmt.Length && fmt[j] == '+') { plus = true; j++; }
if (j < fmt.Length && fmt[j] == '.')
{
// precision like %.2f
j++;
int start = j;
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
int.TryParse(fmt.Substring(start, j - start), out precision);
}
if (j < fmt.Length)
{
char spec = fmt[j];
if (spec == 'd')
{
object val = argIndex < args.Length ? args[argIndex++] : 0;
long iv = Convert.ToInt64(val);
string s = iv.ToString();
if (plus && iv >= 0) s = "+" + s;
sb.Append(s);
i = j;
continue;
}
else if (spec == 'f')
{
object val = argIndex < args.Length ? args[argIndex++] : 0.0;
double dv = Convert.ToDouble(val);
string s = precision >= 0 ? dv.ToString("F" + precision) : dv.ToString("F");
if (plus && dv >= 0) s = "+" + s;
sb.Append(s);
i = j;
continue;
}
else if (spec == 's')
{
object val = argIndex < args.Length ? args[argIndex++] : "";
string s = val != null ? val.ToString() : "";
sb.Append(s);
i = j;
continue;
}
}
// Fallback: treat '%' as literal if format not recognized
sb.Append('%');
}
return sb.ToString();
}
// Add extend description to description string / 添加扩展描述到描述字符串
protected void AddExtDescText()
{
// Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述
string szExtDesc = TryGetItemExtDesc();
// Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了
// if (!szExtDesc || !szExtDesc[0])
// return;
m_strDesc += "\\r";
bool bAddLine = true;
// Add special properties description / 添加特殊属性描述
var pDescTab = EC_Game.GetItemDesc();
// Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整
int green = (int)DescriptipionMsg.ITEMDESC_COL2_BRIGHTBLUE; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value
if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性
{
// Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
// 精确的C++逻辑:(PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0
|| (!((m_iProcType & (int)ProcType.PROC_BINDING) != 0)
&& ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0
|| (m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0
|| (m_iProcType & (int)ProcType.PROC_SELLABLE) != 0
|| (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0
|| (m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0
|| (m_iProcType & (int)ProcType.PROC_USE) != 0
|| (m_iProcType & (int)ProcType.PROC_DEADDROP) != 0
|| (m_iProcType & (int)ProcType.PROC_OFFLINE) != 0
|| (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)))
{
bAddLine = false;
if (pDescTab != null && pDescTab.IsInitialized())
{
string szCol = pDescTab.GetWideString(green);
if (!string.IsNullOrEmpty(szCol))
{
m_strDesc += szCol;
}
}
if ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DEAD_PROTECT);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_DROP);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_SELLABLE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_TRADE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_PLAYER_TRADE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_LEAVE_SCENE_DISAPEAR);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_USE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_USE_AFTER_PICK_UP);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_DEADDROP) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DROP_WHEN_DEAD);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_OFFLINE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_DROP_WHEN_OFFLINE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_UNREPAIRABLE);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0)
{
m_strDesc += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
string desc = pDescTab.GetWideString((int)DescriptipionMsg.ITEMDESC_NO_USER_TRASH);
if (!string.IsNullOrEmpty(desc))
m_strDesc += desc;
}
}
}
else
{
TrimLastReturn();
}
}
else
{
TrimLastReturn();
}
if (string.IsNullOrEmpty(szExtDesc))
return;
// Extend description is below special properties / 扩展描述在特殊属性下方
if (bAddLine)
m_strDesc += "\\r\\r";
else
m_strDesc += "\\r";
m_strDesc += szExtDesc;
}
protected void AddExpireTimeDesc()
{
// Placeholder: expiry description can be added here when time system is fully wired.
}
protected void AddExpireTimeDesc(int expire_date)
{
// Overwrite, call standard one; we don't change m_expire_date in this simplified port.
AddExpireTimeDesc();
}
protected void AddIDDescText()
{
// Optional: show internal id for debugging
#if UNITY_EDITOR
AddDescText(0, true, "ID: {0}", m_tid);
#endif
}
protected void AddBindDescText()
{
// Simple binding flags display based on ProcType
if ((m_iProcType & (int)ProcType.PROC_BINDING) != 0)
{
AddDescText(0, true, "[Bound]");
}
else if ((m_iProcType & (int)ProcType.PROC_BIND) != 0)
{
AddDescText(0, true, "[Bind on Use]");
}
}
protected void AddActionTypeDescText(int action_type)
{
// Action/weapon type formatting can be added later as needed.
}
protected void TrimLastReturn()
{
if (string.IsNullOrEmpty(m_strDesc))
return;
// Remove trailing "\r" or "\n" for consistency with C++ style
if (m_strDesc.EndsWith("\\r", StringComparison.Ordinal))
m_strDesc = m_strDesc.Substring(0, m_strDesc.Length - 2);
else if (m_strDesc.EndsWith("\n", StringComparison.Ordinal))
m_strDesc = m_strDesc.Substring(0, m_strDesc.Length - 1);
}
protected void BuildPriceNumberStr(int iPrice, out string str)
{
str = iPrice.ToString();
}
protected void BuildPriceNumberStr(uint iPrice, out string str)
{
str = iPrice.ToString();
}
public virtual int GetColorStrID(int templateId)
{
// Placeholder: color index lookup; return -1 (white) by default.
return -1;
}
protected int VisualizeFloatPercent(int p)
{
var bytes = BitConverter.GetBytes(p);
float f = BitConverter.ToSingle(bytes, 0);
return (int)(f * 100.0f + 0.5f);
}
/// <summary>
/// Try to get the main description line for this item from item_desc.txt via EC_Game.
/// This mirrors the behaviour used by Inventory UI and NPC shop, but centralised here.
/// </summary>
private string TryGetItemMainDesc()
{
try
{
if (EC_Game.TryGetItemMsg(m_tid, out int messageId, out int displayMode))
{
var tab = EC_Game.GetItemDesc();
if (tab != null && tab.IsInitialized())
{
var s = tab.GetWideString(messageId);
if (!string.IsNullOrEmpty(s)) return s;
}
}
// Fallback: direct template id lookup
{
var tab = EC_Game.GetItemDesc();
if (tab != null && tab.IsInitialized())
{
var s = tab.GetWideString(m_tid);
if (!string.IsNullOrEmpty(s)) return s;
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"[EC_IvtrItem] Error getting main description for tid={m_tid}: {ex.Message}");
}
return string.Empty;
}
/// <summary>
/// Try to get the extended description line from item_ext_desc.txt.
/// </summary>
private string TryGetItemExtDesc()
{
try
{
if (EC_Game.TryGetItemMsg(m_tid, out int messageId, out int displayMode))
{
var tab = EC_Game.GetItemExtDesc();
if (tab != null && tab.IsInitialized())
{
var s = tab.GetWideString(messageId);
if (!string.IsNullOrEmpty(s)) return s;
}
}
// Fallback: direct id
{
var tab = EC_Game.GetItemExtDesc();
if (tab != null && tab.IsInitialized())
{
var s = tab.GetWideString(m_tid);
if (!string.IsNullOrEmpty(s)) return s;
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"[EC_IvtrItem] Error getting extended description for tid={m_tid}: {ex.Message}");
}
return string.Empty;
}
/// <summary>
/// Get extended description text for UI display.
/// This method mirrors AddExtDescText() logic but returns a string instead of modifying m_strDesc.
/// 此方法镜像AddExtDescText()逻辑,但返回字符串而不是修改m_strDesc
/// </summary>
public string GetExtendedDescText()
{
string result = string.Empty;
// Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述
string szExtDesc = TryGetItemExtDesc();
// Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了
// if (!szExtDesc || !szExtDesc[0])
// return;
result += "\\r";
bool bAddLine = true;
// Add special properties description / 添加特殊属性描述
var pDescTab = EC_Game.GetItemDesc();
// Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整
int green = 1000; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value
if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性
{
// Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
// 精确的C++逻辑:(PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...))
if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0
|| (!((m_iProcType & (int)ProcType.PROC_BINDING) != 0)
&& ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0
|| (m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0
|| (m_iProcType & (int)ProcType.PROC_SELLABLE) != 0
|| (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0
|| (m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0
|| (m_iProcType & (int)ProcType.PROC_USE) != 0
|| (m_iProcType & (int)ProcType.PROC_DEADDROP) != 0
|| (m_iProcType & (int)ProcType.PROC_OFFLINE) != 0
|| (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)))
{
bAddLine = false;
if (pDescTab != null && pDescTab.IsInitialized())
{
string szCol = pDescTab.GetWideString(green);
if (!string.IsNullOrEmpty(szCol))
{
result += szCol;
}
}
// Note: These message IDs are placeholders - adjust based on actual string table / 注意:这些消息ID是占位符 - 根据实际字符串表调整
if ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_DEAD_PROTECT placeholder - adjust this value
string desc = pDescTab.GetWideString(2000); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_NO_DROP placeholder - adjust this value
string desc = pDescTab.GetWideString(2001); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_SELLABLE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_NO_TRADE placeholder - adjust this value
string desc = pDescTab.GetWideString(2002); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_NO_PLAYER_TRADE placeholder - adjust this value
string desc = pDescTab.GetWideString(2003); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_LEAVE_SCENE_DISAPEAR placeholder - adjust this value
string desc = pDescTab.GetWideString(2004); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_USE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_USE_AFTER_PICK_UP placeholder - adjust this value
string desc = pDescTab.GetWideString(2005); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_DEADDROP) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_DROP_WHEN_DEAD placeholder - adjust this value
string desc = pDescTab.GetWideString(2006); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_OFFLINE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_DROP_WHEN_OFFLINE placeholder - adjust this value
string desc = pDescTab.GetWideString(2007); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_UNREPAIRABLE placeholder - adjust this value
string desc = pDescTab.GetWideString(2008); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0)
{
result += "\\r";
if (pDescTab != null && pDescTab.IsInitialized())
{
// ITEMDESC_NO_USER_TRASH placeholder - adjust this value
string desc = pDescTab.GetWideString(2009); // Placeholder ID
if (!string.IsNullOrEmpty(desc))
result += desc;
}
}
}
}
if (string.IsNullOrEmpty(szExtDesc))
return result;
// Extend description is below special properties / 扩展描述在特殊属性下方
if (bAddLine)
result += "\\r\\r";
else
result += "\\r";
result += szExtDesc;
return result;
}
#endregion
// public ushort GetData(string strName = "")
// {
// // if (0 != m_dwData && strName != m_strDataName)
// // AUI_ReportError(__LINE__, 1, "AUIObject::GetData(), data name not match");
// return m_dwData;
// }
// public void SetData(ushort dwData, string strName)
// {
// m_strDataName = strName;
// m_dwData = dwData;
// }
}
/// <summary>
/// C# mirror of C++ <c>CECIvtrUnknown</c> (fallback item type).
/// </summary>
public class CECIvtrUnknown : EC_IvtrItem
{
public CECIvtrUnknown(int tid) : base(tid, 0)
{
m_iCID = (int)InventoryClassId.ICID_ERRORITEM;
m_bNeedUpdate = false;
m_bUpdating = false;
}
public CECIvtrUnknown(CECIvtrUnknown s) : base(s)
{
}
public override string GetIconFile()
{
return "Unknown.dds";
}
public override string GetName()
{
// In C++ this pulls from ITEMDESC_ERRORITEM string table.
return "Unknown Item";
}
public override EC_IvtrItem Clone()
{
return new CECIvtrUnknown(this);
}
protected override string GetNormalDesc(bool bRepair)
{
// Minimal mirror of C++: show an error item description with id.
m_strDesc = string.Empty;
AddDescText(0, false, "Error Item ({0})", m_tid);
return m_strDesc;
}
}
}