using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using BrewMonster; using BrewMonster.Common; using BrewMonster.Network; using BrewMonster.Scripts; using BrewMonster.Scripts.Managers; using ModelRenderer.Scripts.Common; using ModelRenderer.Scripts.GameData; using UnityEngine; using UnityEngine.AddressableAssets; namespace BrewMonster.Scripts { /// /// Equipment item class that handles all equipment-specific functionality /// Converted from C++ EC_IvtrEquip.cpp /// public class EC_IvtrEquip : EC_IvtrItem { #region Constants and Enums public enum EQUIP_CLASS_ID { 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 Made From Types public enum ITEM_MAKE_TAG { IMT_NULL, IMT_CREATE, // GM ���� IMT_DROP, // ������� IMT_SHOP, // �̳ǻ��̵���� IMT_PRODUCE, // ������ IMT_SIGN, // װ��ǩ�� }; // Property Effect Essence Flags public const uint PEE_PHYDAMAGE = 0x00000001; public const uint PEE_MAGICDAMAGE = 0x00000002; public const uint PEE_PHYDEF = 0x00000004; public const uint PEE_GOLDDEF = 0x00000008; public const uint PEE_WOODDEF = 0x00000010; public const uint PEE_WATERDEF = 0x00000020; public const uint PEE_FIREDEF = 0x00000040; public const uint PEE_EARTHDEF = 0x00000080; public const uint PEE_ATKSPEED = 0x00000100; public const uint PEE_ATKDIST = 0x00000200; public const uint PEE_HP = 0x00000400; public const uint PEE_MP = 0x00000800; public const uint PEE_DODGE = 0x00001000; public const uint PEE_ENDURANCE = 0x00002000; public const uint PEE_STRENGTHREQ = 0x00004000; public const uint PEE_AGILITYREQ = 0x00008000; public const uint PEE_ENERGYREQ = 0x00010000; public const uint PEE_VITALITYREQ = 0x00020000; // Property Effect Essence Indexes (must match C++ enum order) public const int PEEI_PHYDAMAGE = 0; public const int PEEI_PHYDEF = 1; public const int PEEI_MAGICDAMAGE = 2; public const int PEEI_GOLDDEF = 3; public const int PEEI_WOODDEF = 4; public const int PEEI_WATERDEF = 5; public const int PEEI_FIREDEF = 6; public const int PEEI_EARTHDEF = 7; public const int PEEI_HP = 8; public const int PEEI_MP = 9; public const int PEEI_ENDURANCE = 10; public const int PEEI_ATKDIST = 11; public const int PEEI_STRENGTHREQ = 12; public const int PEEI_AGILITYREQ = 13; public const int PEEI_ATKSPEED = 14; public const int PEEI_MAX_PHYDAMAGE = 15; public const int PEEI_MAX_MAGICDAMAGE = 16; public const int PEEI_DODGE = 17; public const int MAX_PEEINDEX = 18; // Refine Indexes public const int REFINE_PHYDAMAGE = 0; public const int REFINE_MAGICDAMAGE = 1; public const int REFINE_PHYDEF = 2; public const int REFINE_GOLDDEF = 3; public const int REFINE_WOODDEF = 4; public const int REFINE_WATERDEF = 5; public const int REFINE_FIREDEF = 6; public const int REFINE_EARTHDEF = 7; public const int REFINE_HP = 8; public const int REFINE_DODGE = 9; public const int MAX_REFINEINDEX = 10; // Item Description Colors and String IDs are now in DescriptipionMsg enum from EC_FixedMsg.cs // Scale Types public const int SCALE_SELL = 1; #endregion #region Public Fields // Basic Item Properties public int TemplateId { get; set; } public int ExpireDate { get; set; } /// /// class id /// public int CID { get; set; } public int Count { get; set; } //public int ScaleType { get; set; } // Equipment Requirements public int LevelReq { get; set; } public int StrengthReq { get; set; } public int AgilityReq { get; set; } public int ProfReq { get; set; } public int VitalityReq { get; set; } public int EnergyReq { get; set; } public int ReputationReq { get; set; } // Endurance public int CurEndurance { get; set; } public int MaxEndurance { get; set; } public int RepairFee { get; set; } // Maker Information public byte MadeFrom { get; set; } public string Maker { get; set; } // Equipment Properties public ushort StoneMask { get; set; } public int FixProps { get; set; } public int RefineLvl { get; set; } public byte PropNum { get; set; } public byte EmbedNum { get; set; } // Equipment Arrays public List Holes { get; set; } public List Props { get; set; } #endregion #region Base Stats (from Element Data) private static bool TryGetNumber(object data, string[] names, out T value) where T : struct { value = default; if (data == null) return false; var t = data.GetType(); foreach (var name in names) { var f = t.GetField(name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase); if (f != null) { try { object v = f.GetValue(data); if (v == null) continue; if (typeof(T) == typeof(float)) { float fv = Convert.ToSingle(v); value = (T)(object)fv; return true; } else { int iv = Convert.ToInt32(v); if (typeof(T) == typeof(int)) { value = (T)(object)iv; return true; } } } catch { } } var p = t.GetProperty(name, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase); if (p != null) { try { object v = p.GetValue(data, null); if (v == null) continue; if (typeof(T) == typeof(float)) { float fv = Convert.ToSingle(v); value = (T)(object)fv; return true; } else { int iv = Convert.ToInt32(v); if (typeof(T) == typeof(int)) { value = (T)(object)iv; return true; } } } catch { } } } return false; } public string GetBaseStatsForDisplay() { return GetBaseStatsDesc(); } private void AddBaseStatsDesc(int[] aPEEVals, int[] aRefines) { elementdataman edm = ElementDataManProvider.GetElementDataMan(); if (edm == null) return; try { DATA_TYPE dt = DATA_TYPE.DT_INVALID; object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); if (data == null) return; int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; // Try weapon-style fields first int dmgLow = 0, dmgHigh = 0; bool hasDmgLow = TryGetNumber(data, new[] { "damage_low" }, out dmgLow); bool hasDmgHigh = TryGetNumber(data, new[] { "damage_high_max", "damage_high" }, out dmgHigh); if (hasDmgLow && hasDmgHigh) { // Physical damage range - adjust by aPEEVals and aRefines (like C++: m_Essence.damage_low - aPEEVals[PEEI_PHYDAMAGE] + aRefines[REFINE_PHYDAMAGE]) int adjDmgLow = dmgLow - aPEEVals[PEEI_PHYDAMAGE] + aRefines[REFINE_PHYDAMAGE]; int adjDmgHigh = dmgHigh - aPEEVals[PEEI_PHYDAMAGE] - aPEEVals[PEEI_MAX_PHYDAMAGE] + aRefines[REFINE_PHYDAMAGE]; if (adjDmgLow > 0 || adjDmgHigh > 0 || aRefines[REFINE_PHYDAMAGE] != 0) { string dmgFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDAMAGE); AddDescText(white, false, dmgFmt); AddDescText(white, true, " {0}-{1}", adjDmgLow, adjDmgHigh); } // Magic damage if available if (TryGetNumber(data, new[] { "magic_damage_low" }, out int mdLow) && TryGetNumber(data, new[] { "magic_damage_high_max", "magic_damage_high" }, out int mdHigh)) { int adjMdLow = mdLow - aPEEVals[PEEI_MAGICDAMAGE] + aRefines[REFINE_MAGICDAMAGE]; int adjMdHigh = mdHigh - aPEEVals[PEEI_MAGICDAMAGE] - aPEEVals[PEEI_MAX_MAGICDAMAGE] + aRefines[REFINE_MAGICDAMAGE]; if (adjMdLow > 0 || adjMdHigh > 0 || aRefines[REFINE_MAGICDAMAGE] != 0) { string mdFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_MAGICDAMAGE); AddDescText(white, false, mdFmt); AddDescText(white, true, " {0}-{1}", adjMdLow, adjMdHigh); } } // Attack time/speed - adjust color based on PEE_ATKSPEED int atkCol = white; if ((PropEffectEssence() & (1 << PEEI_ATKSPEED)) != 0) atkCol = lblue; if (TryGetNumber(data, new[] { "attack_speed", "atk_speed" }, out float atkSpeed) && atkSpeed > 0) { // Show speed in attacks per second (1 / (attack_speed * 0.05)) float aps = 1.0f / (atkSpeed * 0.05f); string atkFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ATKSPEED); if (atkFmt.Contains("%.2f") || atkFmt.Contains("%f")) AddDescText(atkCol, true, atkFmt, aps); else AddDescText(atkCol, true, "{0} {1:F2}", atkFmt, aps); } // Range - adjust by aPEEVals[PEEI_ATKDIST] float range; if (TryGetNumber(data, new[] { "range", "attack_range", "atk_range" }, out range)) { float adjRange = range - BitConverter.ToSingle(BitConverter.GetBytes(aPEEVals[PEEI_ATKDIST]), 0); string rangeFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ATKDISTANCE); if (rangeFmt.Contains("%") && (rangeFmt.Contains("f") || rangeFmt.Contains("d"))) AddDescText(white, true, rangeFmt, adjRange); else AddDescText(white, true, "{0} {1:F2}", rangeFmt, adjRange); } } else { // Try armor-style fields if (TryGetNumber(data, new[] { "defence_high", "defense_high", "defence", "defense" }, out int defHigh)) { int adjDef = defHigh - aPEEVals[PEEI_PHYDEF] + aRefines[REFINE_PHYDEF]; if (adjDef > 0 || aRefines[REFINE_PHYDEF] != 0) { string defFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE); AddDescText(white, false, defFmt); AddDescText(white, true, " {0:+0;-#;+0}", adjDef); } } // Dodge if (TryGetNumber(data, new[] { "armor", "dodge" }, out int dodge)) { int adjDodge = dodge - aPEEVals[PEEI_DODGE] + aRefines[REFINE_DODGE]; if (adjDodge > 0 || aRefines[REFINE_DODGE] != 0) { string dodgeFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_DODGE); AddDescText(white, false, dodgeFmt); AddDescText(white, true, " {0:+0;-#;+0}", adjDodge); } } // HP if (TryGetNumber(data, new[] { "hp_enhance", "hp" }, out int hp)) { int adjHp = hp - aPEEVals[PEEI_HP] + aRefines[REFINE_HP]; if (adjHp > 0 || aRefines[REFINE_HP] != 0) { string hpFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP); AddDescText(white, false, hpFmt); AddDescText(white, true, " {0:+0;-#;+0}", adjHp); } } // MP if (TryGetNumber(data, new[] { "mp_enhance", "mp" }, out int mp)) { int adjMp = mp - aPEEVals[PEEI_MP]; if (adjMp > 0) { string mpFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMP); AddDescText(white, false, mpFmt); AddDescText(white, true, " {0:+0;-#;+0}", adjMp); } } // Elemental resistances - adjust by aPEEVals and aRefines // In C++: m_Essence.resistance[MAGICCLASS_GOLD] - aPEEVals[PEEI_GOLDDEF] + aRefines[REFINE_GOLDDEF] // In C# struct: resistance is stored in magic_defences array (DescriptipionMsg descId, int refineIdx, int peeIdx, int magicClassIdx)[] res = new (DescriptipionMsg, int, int, int)[] { (DescriptipionMsg.ITEMDESC_GOLDDEFENCE, REFINE_GOLDDEF, PEEI_GOLDDEF, 0), // MAGICCLASS_GOLD = 0 (DescriptipionMsg.ITEMDESC_WOODDEFENCE, REFINE_WOODDEF, PEEI_WOODDEF, 1), // MAGICCLASS_WOOD = 1 (DescriptipionMsg.ITEMDESC_WATERDEFENCE, REFINE_WATERDEF, PEEI_WATERDEF, 2), // MAGICCLASS_WATER = 2 (DescriptipionMsg.ITEMDESC_FIREDEFENCE, REFINE_FIREDEF, PEEI_FIREDEF, 3), // MAGICCLASS_FIRE = 3 (DescriptipionMsg.ITEMDESC_EARTHDEFENCE, REFINE_EARTHDEF, PEEI_EARTHDEF, 4) // MAGICCLASS_EARTH = 4 }; // Try to read from magic_defences array first (C# struct format) bool foundResistanceArray = false; try { var dataType = data.GetType(); var magicDefencesField = dataType.GetField("magic_defences"); if (magicDefencesField != null) { var magicDefences = magicDefencesField.GetValue(data); if (magicDefences != null && magicDefences is Array) { foundResistanceArray = true; Array arr = (Array)magicDefences; for (int i = 0; i < res.Length && i < arr.Length; i++) { var magicDef = arr.GetValue(i); if (magicDef != null) { var highField = magicDef.GetType().GetField("high"); if (highField != null) { int v = Convert.ToInt32(highField.GetValue(magicDef)); var descId = res[i].descId; var refineIdx = res[i].refineIdx; var peeIdx = res[i].peeIdx; int adjV = v - aPEEVals[peeIdx] + aRefines[refineIdx]; if (adjV > 0 || aRefines[refineIdx] != 0) { string resFmt = GetItemDescString(descId); AddDescText(white, false, resFmt); AddDescText(white, true, " {0:+0;-#;+0}", adjV); } } } } } } } catch { } // Fallback: try reading from individual fields if array method didn't work if (!foundResistanceArray) { for (int i = 0; i < res.Length; i++) { var descId = res[i].descId; var refineIdx = res[i].refineIdx; var peeIdx = res[i].peeIdx; var magicClassIdx = res[i].magicClassIdx; // Try multiple field name patterns string[] names = new[] { $"magic_defences[{magicClassIdx}].high", $"magic_defences_{magicClassIdx}_high", $"resistance_{magicClassIdx}", $"resist_{magicClassIdx}", "resist_metal", "resistance_metal", "gold_resist" // For index 0 }; if (TryGetNumber(data, names, out int v)) { int adjV = v - aPEEVals[peeIdx] + aRefines[refineIdx]; if (adjV > 0 || aRefines[refineIdx] != 0) { string resFmt = GetItemDescString(descId); AddDescText(white, false, resFmt); AddDescText(white, true, " {0:+0;-#;+0}", adjV); } } } } } } catch { } } // Keep old method for backward compatibility private string GetBaseStatsDesc() { string oldDesc = m_strDesc; m_strDesc = ""; int[] emptyPEE = new int[MAX_PEEINDEX]; int[] emptyRefines = new int[MAX_REFINEINDEX]; AddBaseStatsDesc(emptyPEE, emptyRefines); string result = m_strDesc; m_strDesc = oldDesc; return result; } public ushort GetStoneMask() { return StoneMask; } private static object TryFindElementByScanningArraysLocal(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; } #endregion #region Property Structure public class Property { public int Type { get; set; } public int NumParam { get; set; } public bool Embed { get; set; } public bool Suite { get; set; } public bool Engraved { get; set; } public bool Local { get; set; } public int[] Params { get; set; } public Property() { Params = new int[4]; } public Property(Property other) { Type = other.Type; NumParam = other.NumParam; Embed = other.Embed; Suite = other.Suite; Engraved = other.Engraved; Local = other.Local; Params = new int[4]; Array.Copy(other.Params, Params, Math.Min(4, other.Params.Length)); } } #endregion #region Constructor public EC_IvtrEquip(int tid, int expireDate) : base(tid, expireDate) { TemplateId = tid; ExpireDate = expireDate; CID = (int)InventoryClassId.ICID_EQUIP; m_iPrice = 0; Count = 1; m_fPriceScale = 1.0f; //ScaleType = 0; LevelReq = 0; StrengthReq = 0; AgilityReq = 0; ProfReq = 0; VitalityReq = 0; EnergyReq = 0; ReputationReq = 0; CurEndurance = 0; MaxEndurance = 0; RepairFee = 0; MadeFrom = 0; StoneMask = 0; FixProps = 0; RefineLvl = 0; PropNum = 0; EmbedNum = 0; Holes = new List(); Props = new List(); } public EC_IvtrEquip(EC_IvtrEquip other) { // Copy basic properties TemplateId = other.TemplateId; ExpireDate = other.ExpireDate; CID = other.CID; m_iPrice = other.m_iPrice; Count = other.Count; m_fPriceScale = other.m_fPriceScale; //ScaleType = other.ScaleType; // Copy equipment properties LevelReq = other.LevelReq; ProfReq = other.ProfReq; StrengthReq = other.StrengthReq; AgilityReq = other.AgilityReq; VitalityReq = other.VitalityReq; EnergyReq = other.EnergyReq; ReputationReq = other.ReputationReq; CurEndurance = other.CurEndurance; MaxEndurance = other.MaxEndurance; RepairFee = other.RepairFee; MadeFrom = other.MadeFrom; Maker = other.Maker; StoneMask = other.StoneMask; FixProps = other.FixProps; RefineLvl = other.RefineLvl; PropNum = other.PropNum; EmbedNum = other.EmbedNum; Holes = new List(other.Holes); Props = new List(other.Props.Select(p => new Property(p))); } #endregion #region Core Methods /// /// Set item detail information from binary data /// public override bool SetItemInfo(byte[] infoData, int dataLen) { base.SetItemInfo(infoData, dataLen); if (infoData == null || dataLen == 0) return true; // Try native order (as original client): // [6 x short requirements][2 x int endurance][short essenceSize][maker info][essence bytes][short numHole][WORD stoneMask][numHole x int holes][int numProp][props] if (TryParseEquipInfoNative(infoData, dataLen)) { ParseProperties(); return true; } // Fallback to legacy/custom order if server payload differs if (TryParseEquipInfoLegacy(infoData, dataLen)) { ParseProperties(); return true; } return false; } private bool TryParseEquipInfoNative(byte[] data, int len) { try { CECDataReader dr = new CECDataReader(data, len); LevelReq = dr.ReadShort(); ProfReq = dr.ReadShort(); StrengthReq= dr.ReadShort(); VitalityReq= dr.ReadShort(); AgilityReq = dr.ReadShort(); EnergyReq = dr.ReadShort(); CurEndurance = dr.ReadInt(); MaxEndurance = dr.ReadInt(); int essenceSize = dr.ReadShort(); ReadMakerInfo(dr); dr.Offset(essenceSize, CECDataReader.SEEK_CUR); if(essenceSize < 0 ) { throw new Exception("TYPE_DATAERR"); } int numHole = dr.ReadShort(); StoneMask = (ushort)dr.ReadShort(); if (numHole > 0) { Holes.Clear(); //Holes.Capacity = numHole; for (int i = 0; i < numHole; i++) { Holes.Add(dr.ReadInt()); } } else if (numHole == 0) { Holes.Clear(); } else { throw new Exception("TYPE_DATAERR"); } int numProp = dr.ReadInt(); if (numProp > 0) { Props.Clear(); //Props.Capacity = numProp; for (int i = 0; i < numProp; i++) { int type = dr.ReadInt(); Property prop = new Property(); prop.Type = type & 0x1fff; prop.NumParam = (type & 0x6000) >> 13; prop.Embed = (type & 0x8000) != 0; prop.Suite = (type & 0x10000) != 0; prop.Engraved = (type & 0x20000) != 0; prop.Local = false; for (int j = 0; j < prop.NumParam; j++) { prop.Params[j] = dr.ReadInt(); } Props.Add(prop); } } else if (numProp == 0) { Props.Clear(); } else { throw new Exception("TYPE_DATAERR"); } // Sanity check to catch misalignment if (LevelReq < 0 || LevelReq > 2000) return false; if (MaxEndurance < 0 || MaxEndurance > 1000000) return false; return true; } catch (System.Exception ex) { BMLogger.LogError("CECIvtrEquip::SetItemInfo, data read error (" + ex.GetType() + ")" + ex.StackTrace); return false; } } private bool TryParseEquipInfoLegacy(byte[] data, int len) { try { int offset = 0; if (len < 16 + 6 * 2 + 8 + 2) return false; // A legacy format we used earlier that prefixed price/scale before requirements m_iPrice = BitConverter.ToInt32(data, offset); offset += 4; Count = BitConverter.ToInt32(data, offset); offset += 4; m_fPriceScale = BitConverter.ToSingle(data, offset); offset += 4; //ScaleType = BitConverter.ToInt32(data, offset); offset += 4; LevelReq = BitConverter.ToInt16(data, offset); offset += 2; ProfReq = BitConverter.ToInt16(data, offset); offset += 2; StrengthReq= BitConverter.ToInt16(data, offset); offset += 2; VitalityReq= BitConverter.ToInt16(data, offset); offset += 2; AgilityReq = BitConverter.ToInt16(data, offset); offset += 2; EnergyReq = BitConverter.ToInt16(data, offset); offset += 2; CurEndurance = BitConverter.ToInt32(data, offset); offset += 4; MaxEndurance = BitConverter.ToInt32(data, offset); offset += 4; int essenceSize = BitConverter.ToInt16(data, offset); offset += 2; //ReadMakerInfo(data, ref offset); if (essenceSize < 0 || offset + essenceSize > len) return false; offset += essenceSize; int numHole = BitConverter.ToInt16(data, offset); offset += 2; StoneMask = BitConverter.ToUInt16(data, offset); offset += 2; Holes.Clear(); if (numHole > 0) { if (offset + 4 * numHole > len) return false; for (int i = 0; i < numHole; i++) { Holes.Add(BitConverter.ToInt32(data, offset)); offset += 4; } } else if (numHole < 0) return false; if (offset + 4 > len) return false; int numProp = BitConverter.ToInt32(data, offset); offset += 4; Props.Clear(); if (numProp > 0) { for (int i = 0; i < numProp; i++) { if (offset + 4 > len) return false; int type = BitConverter.ToInt32(data, offset); offset += 4; Property prop = new Property(); prop.Type = type & 0x1fff; prop.NumParam = (type & 0x6000) >> 13; prop.Embed = (type & 0x8000) != 0; prop.Suite = (type & 0x10000) != 0; prop.Engraved = (type & 0x20000) != 0; for (int j = 0; j < prop.NumParam; j++) { if (offset + 4 > len) return false; prop.Params[j] = BitConverter.ToInt32(data, offset); offset += 4; } Props.Add(prop); } } return true; } catch { return false; } } /// /// Read maker information from binary data /// protected void ReadMakerInfo(CECDataReader dr) { // Debug: Log the bytes at current position before reading // We need to check what bytes are actually at the reader position MadeFrom = dr.ReadByte(); int makerLen = dr.ReadByte(); if (makerLen > 0) { if (MadeFrom == (byte)ITEM_MAKE_TAG.IMT_SIGN) { ushort color = (ushort)dr.ReadShort(); makerLen -= sizeof(ushort); byte[] makerData = dr.ReadData(makerLen); // Find null terminator (0x00 0x00 for Unicode) and decode only up to that point int actualLength = makerLen; for (int i = 0; i < makerLen - 1; i += 2) { if (makerData[i] == 0 && makerData[i + 1] == 0) { actualLength = i; break; } } string maker = System.Text.Encoding.Unicode.GetString(makerData, 0, actualLength).TrimEnd('\0'); if (string.IsNullOrEmpty(maker)) { return; } //#define FASHION_WORDCOLOR_TO_A3DCOLOR(c) A3DCOLORRGB(((c) & (0x1f << 10)) >> 7, ((c) & (0x1f << 5)) >> 2, ((c) & 0x1f) << 3) //A3DCOLOR clr = FASHION_WORDCOLOR_TO_A3DCOLOR(color); Color clr = ColorFromWord(color); SetNewMark(maker, clr); } else { //m_strMaker = ACString((ACHAR*)dr.Read_Data(iMakerLen), iMakerLen / sizeof (ACHAR)); byte[] makerData = dr.ReadData(makerLen); // Find null terminator (0x00 0x00 for Unicode) and decode only up to that point int actualLength = makerLen; for (int i = 0; i < makerLen - 1; i += 2) { if (makerData[i] == 0 && makerData[i + 1] == 0) { actualLength = i; break; } } Maker = System.Text.Encoding.Unicode.GetString(makerData, 0, actualLength).TrimEnd('\0'); } } else { Maker = ""; } } /// /// Set new mark with color /// public void SetNewMark(string mark, Color color) { Maker = mark; if (!string.IsNullOrEmpty(Maker)) { // Convert hint string // Filter bad words // g_pGame->GetGameRun()->GetUIManager()->FilterBadWords(m_strMaker); // Add color using ^RRGGBB from Color32 bytes Color32 c32 = color; string colorStr = $"^{c32.r:X2}{c32.g:X2}{c32.b:X2}"; Maker = colorStr + Maker; // Add equipment mark display string Maker = string.Format(GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIPMARK), Maker); } MadeFrom = string.IsNullOrEmpty(mark) ? (byte)ITEM_MAKE_TAG.IMT_NULL : (byte)ITEM_MAKE_TAG.IMT_SIGN; } #endregion #region Utility Methods /// /// Get item name /// public virtual string GetName() { return EC_IvtrItemUtils.Instance.ResolveItemName(TemplateId); } /// /// Add current endurance /// public int AddCurEndurance(int value) { CurEndurance += value; CurEndurance = Mathf.Clamp(CurEndurance, 0, MaxEndurance); return CurEndurance; } /// /// Convert endurance real value to displaying value /// public static int VisualizeEndurance(int v) { return (v + GameConstants.ENDURANCE_SCALE - 1) / GameConstants.ENDURANCE_SCALE; } /// /// Get empty hole number /// public int GetEmptyHoleNum() { int count = 0; foreach (int hole in Holes) { if (hole == 0) count++; } return count; } /// /// Get repair cost /// public int GetRepairCost() { if (MaxEndurance == 0 || MaxEndurance == CurEndurance) return 0; int cost = (int)GetRawRepairCost(); if (cost < 1) cost = 1; return cost; } /// /// Get raw repair cost /// public float GetRawRepairCost() { float cost = 0.0f; if (!IsRepairable()) return cost; if (CurEndurance == 0) { cost = RepairFee; } else if (MaxEndurance > 0 && CurEndurance < MaxEndurance) { float factor = (MaxEndurance - CurEndurance) / (float)MaxEndurance; cost = RepairFee * factor; } return cost; } /// /// Get scaled item price /// public override int GetScaledPrice() { if (m_iScaleType != (int)EC_IvtrItem.ScaleType.SCALE_SELL) return base.GetScaledPrice(); int price = m_iPrice * Count; if (MaxEndurance == CurEndurance || MaxEndurance == 0) { return (int)(price * m_fPriceScale + 0.5f); } else { return (int)(price * m_fPriceScale * CurEndurance / (float)MaxEndurance + 0.5f); } } /// /// Check if item is repairable /// public bool IsRepairable() { return MaxEndurance > 0; } /// /// Check if item is destroying /// public bool IsDestroying() { return CurEndurance == 0 && MaxEndurance > 0; } /// /// Check if item is rare /// public virtual bool IsRare() { return RefineLvl >= 3; } #endregion #region Property System /// /// Get property effect essence flags /// public uint PropEffectEssence() { uint flags = 0; foreach (Property prop in Props) { flags = PropEffectMask(prop, flags); } return flags; } /// /// Get property effect mask for a specific property /// public uint PropEffectMask(Property prop, uint flags) { // Get property type from game's item ext prop table byte propType = GetPropertyType(prop.Type); switch (propType) { case 0: // Physical damage flags |= PEE_PHYDAMAGE; break; case 1: // Max physical damage flags |= PEE_PHYDAMAGE; break; case 2: // Physical damage (%) flags |= PEE_PHYDAMAGE; break; case 3: // Magic damage flags |= PEE_MAGICDAMAGE; break; case 4: // Max magic damage case 5: // Magic damage (%) flags |= PEE_MAGICDAMAGE; break; case 6: // +Def -Atk flags |= PEE_PHYDAMAGE; flags |= PEE_PHYDEF; break; case 7: // +Atk -Def flags |= PEE_PHYDAMAGE; flags |= PEE_PHYDEF; break; case 8: // +Magic -MagicDef flags |= PEE_MAGICDAMAGE; flags |= PEE_GOLDDEF; flags |= PEE_WOODDEF; flags |= PEE_WATERDEF; flags |= PEE_FIREDEF; flags |= PEE_EARTHDEF; break; case 9: // Attack speed flags |= PEE_ATKSPEED; break; case 10: // Attack distance flags |= PEE_ATKDIST; break; case 11: // Cast time break; case 12: // Physical defense flags |= PEE_PHYDEF; break; case 13: // Physical defense (%) flags |= PEE_PHYDEF; break; case 14: // All magic defense flags |= PEE_GOLDDEF; flags |= PEE_WOODDEF; flags |= PEE_WATERDEF; flags |= PEE_FIREDEF; flags |= PEE_EARTHDEF; break; case 15: // Gold defense flags |= PEE_GOLDDEF; break; case 16: // Gold defense (%) flags |= PEE_GOLDDEF; break; case 17: // Wood defense flags |= PEE_WOODDEF; break; case 18: // Wood defense (%) flags |= PEE_WOODDEF; break; case 19: // Water defense flags |= PEE_WATERDEF; break; case 20: // Water defense (%) flags |= PEE_WATERDEF; break; case 21: // Fire defense flags |= PEE_FIREDEF; break; case 22: // Fire defense (%) flags |= PEE_FIREDEF; break; case 23: // Earth defense flags |= PEE_EARTHDEF; break; case 24: // Earth defense (%) flags |= PEE_EARTHDEF; break; case 30: // +Gold -Fire flags |= PEE_GOLDDEF; flags |= PEE_FIREDEF; break; case 31: // +Wood -Gold flags |= PEE_GOLDDEF; flags |= PEE_WOODDEF; break; case 32: // +Water -Earth flags |= PEE_WATERDEF; flags |= PEE_EARTHDEF; break; case 33: // +Fire -Water flags |= PEE_WATERDEF; flags |= PEE_FIREDEF; break; case 34: // +Earth -Wood flags |= PEE_WOODDEF; flags |= PEE_EARTHDEF; break; case 35: // HP flags |= PEE_HP; break; case 36: // MP flags |= PEE_MP; break; case 37: // HP (%) flags |= PEE_HP; break; case 38: // MP (%) flags |= PEE_MP; break; case 50: // Dodge flags |= PEE_DODGE; break; case 53: // Endurance (%) flags |= PEE_ENDURANCE; break; case 56: // Equipment requirements flags |= PEE_STRENGTHREQ; flags |= PEE_AGILITYREQ; flags |= PEE_ENERGYREQ; flags |= PEE_VITALITYREQ; break; case 92: // +Physical +Magic flags |= PEE_PHYDEF; flags |= PEE_GOLDDEF; flags |= PEE_WOODDEF; flags |= PEE_WATERDEF; flags |= PEE_FIREDEF; flags |= PEE_EARTHDEF; break; case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: case 208: case 209: case 210: case 211: case 212: // refine props – do not change mask further break; } return flags; } /// /// Get property type from property ID /// private static Dictionary s_propIdToType; private static bool s_propMapLoaded; private static readonly object s_propLock = new(); private static void EnsurePropMapLoaded() { if (s_propMapLoaded) return; lock (s_propLock) { if (s_propMapLoaded) return; s_propIdToType = new Dictionary(); try { // Load item_ext_prop.txt from Addressables // Address must match the Addressables "Address" configured in Assets/AddressableAssetsData/... const string address = "Assets/Addressable/item_ext_prop.txt"; // Initialize Addressables if not already initialized Addressables.InitializeAsync().WaitForCompletion(); // Load the TextAsset synchronously (since GetPropertyType is called synchronously) var textAsset = Addressables.LoadAssetAsync(address).WaitForCompletion(); if (textAsset != null && !string.IsNullOrEmpty(textAsset.text)) { // Parse the text content int currentType = -1; bool inTypeBlock = false; bool inBlockComment = false; using (var reader = new StringReader(textAsset.text)) { string rawLine; while ((rawLine = reader.ReadLine()) != null) { string src = rawLine; if (string.IsNullOrEmpty(src)) continue; // strip block comments /* ... */ possibly spanning lines System.Text.StringBuilder sb = new System.Text.StringBuilder(); int i = 0; while (i < src.Length) { if (inBlockComment) { int end = src.IndexOf("*/", i, StringComparison.Ordinal); if (end < 0) { i = src.Length; break; } inBlockComment = false; i = end + 2; continue; } int start = src.IndexOf("/*", i, StringComparison.Ordinal); if (start < 0) { sb.Append(src, i, src.Length - i); break; } sb.Append(src, i, start - i); int end2 = src.IndexOf("*/", start + 2, StringComparison.Ordinal); if (end2 < 0) { inBlockComment = true; break; } i = end2 + 2; } string line = sb.ToString().Trim(); if (line.Length == 0) continue; if (line.StartsWith("//")) continue; // Detect a new type section: e.g., "type: 45" var typeMatch = Regex.Match(line, "^type:\\s*(?\\d+)"); if (typeMatch.Success) { if (int.TryParse(typeMatch.Groups["type"].Value, out int t)) currentType = t; inTypeBlock = line.Contains("{"); if (line.Contains("}")) { inTypeBlock = false; currentType = -1; } continue; } if (line.Contains("{")) inTypeBlock = true; if (line.Contains("}")) { inTypeBlock = false; currentType = -1; } if (inTypeBlock && currentType >= 0) { foreach (Match m in Regex.Matches(line, "\\b(?\\d{1,6})\\b")) { if (int.TryParse(m.Groups["id"].Value, out int id)) { if (!s_propIdToType.ContainsKey(id)) s_propIdToType[id] = (byte)Mathf.Clamp(currentType, 0, 255); } } } } } } else { BMLogger.LogWarning($"[EC_IvtrEquip] Failed to load item_ext_prop.txt from Addressables (address: {address})"); } } catch (Exception ex) { BMLogger.LogError($"[EC_IvtrEquip] Exception loading item_ext_prop.txt from Addressables: {ex.Message}"); // ignore parse errors; fallback below will handle unknown properties } s_propMapLoaded = true; } } private byte GetPropertyType(int propId) { // Load once from item_ext_prop.txt; if not enough info, fall back to heuristics EnsurePropMapLoaded(); if (s_propIdToType != null && s_propIdToType.TryGetValue(propId, out byte mapped)) return mapped; // Unknown return 0xff; } /// /// Set properties to local /// public override void SetLocalProps() { if (Props.Count == 0) return; foreach (Property prop in Props) { prop.Local = true; } } /// /// Get deadly strike rate provided by this equipment /// public int GetDeadlyStrikeRate(bool suiteGen) { int val = 0; if (suiteGen) { // Suite generation logic would go here // This is a simplified version } else { foreach (Property prop in Props) { byte propType = GetPropertyType(prop.Type); if (propType == 45 || propType == 110) val += prop.Params[0]; } } return val; } /// /// Decide equipment name color /// protected override int DecideNameCol() { int index = GetColorStrID(TemplateId); if (index >= 0) return index; int col = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; switch (FixProps) { case 1: col = (int)DescriptipionMsg.ITEMDESC_COL_GREEN; break; case 2: col = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; break; case 3: col = (int)DescriptipionMsg.ITEMDESC_COL_DARKGOLD; break; default: if (PropNum > 0) col = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; break; } return col; } /// /// Get color string ID for template /// public override int GetColorStrID(int templateId) { // This would normally query the game's color system int iIndex = EC_Game.GetItemNameColorIdx(templateId); if (iIndex <= 0) return -1; else if (iIndex < 7) return (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE + iIndex - 1; else return (int)DescriptipionMsg.ITEMDESC_COL2_START + iIndex - 7 + 1; } #endregion #region Description Methods (high level entry points) /// /// Get normal in-inventory description, mirroring C++ CECIvtrEquip::GetNormalDesc. /// This is a single formatted string using ^color codes and '\\r' as line separators. /// protected override string GetNormalDesc(bool bRepair) { // Build addon and refine properties and save it (like C++ does first) int[] aPEEVals = new int[MAX_PEEINDEX]; int[] aRefines = new int[MAX_REFINEINDEX]; for (int i = 0; i < MAX_PEEINDEX; i++) aPEEVals[i] = 0; for (int i = 0; i < MAX_REFINEINDEX; i++) aRefines[i] = 0; m_strDesc = ""; BuildAddOnPropDesc(aPEEVals, aRefines); string strAddon = m_strDesc; // Reset and build description from scratch m_strDesc = ""; int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; // 1) Item name AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_NAME), GetName()); // 1.5) Sub class name (loại trang bị) - like C++: AddDescText(white, true, pDescTab->GetWideString(ITEMDESC_CLASSNAME), m_pDBSubType->name); // In C++, this is always called (no null check), so we always call it too string subTypeName = GetSubTypeName(); AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_CLASSNAME), subTypeName ?? ""); // 1.6) Item level (cấp vũ khí/trang bị) - like C++: AddDescText(-1, true, pDescTab->GetWideString(ITEMDESC_LEVEL), m_Essence.weapon_level); int itemLevel = GetItemLevel(); if (itemLevel > 0) { AddDescText(-1, true, GetItemDescString(DescriptipionMsg.ITEMDESC_LEVEL), itemLevel); } // 2) Base stats from element data (damage/defence/speed/range/resists) // Adjust base stats by subtracting aPEEVals and adding aRefines AddBaseStatsDesc(aPEEVals, aRefines); // 3) Endurance (current / max) - adjust color based on PEE_ENDURANCE if (MaxEndurance > 0) { int col = white; if (CurEndurance == 0) col = red; else if ((PropEffectEssence() & (1 << PEEI_ENDURANCE)) != 0) col = lblue; int curVis = VisualizeEndurance(CurEndurance); int maxVis = VisualizeEndurance(MaxEndurance); AddDescText(col, true, "{0} {1}/{2}", GetItemDescString(DescriptipionMsg.ITEMDESC_ENDURANCE), curVis, maxVis); } // 4) Requirements (level / stats / reputation) AddRequirementDesc(aPEEVals); AddReputationReqDesc(); // 4.5) Profession restriction (phái hạn chế) - like C++: AddProfReqDesc(m_iProfReq); // In C++, this is always called regardless of value (the function checks internally) AddProfReqDesc(ProfReq); // 5) Add-on properties (non-embedded, non-suite, non-engraved) if (!string.IsNullOrEmpty(strAddon)) m_strDesc += strAddon; AddPriceDesc(white, false); // 6) Tessera / stones (socketed gems) BuildTesseraDesc(); // 7) Sharpener properties AddSharpenerDesc(); // 8) Engraved properties AddEngravedDesc(); // 9) Suite information AddSuiteDesc(); // 10) Maker & destroying info if any AddMakerDesc(); // Destroying description is added by caller when needed; keep it optional here. m_strDesc += "\\r"; AddExtDescText(); // 11) Price (sell price scaled) return m_strDesc; } /// /// Get item description for booth buying /// public string GetBoothBuyDesc() { m_strDesc = ""; int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; // Item name AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_NAME), GetName()); // Base stats from element data string baseStats = GetBaseStatsDesc(); if (!string.IsNullOrEmpty(baseStats)) { m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_WHITE); m_strDesc += baseStats; m_strDesc += "\\r"; } // Add-on properties BuildAddOnPropDesc(null, null); // Tessera / stones BuildTesseraDesc(); // Suite info (if any) AddSuiteDesc(); // Price AddPriceDesc(white, false); return m_strDesc; } /// /// Add concise requirement description (level / stats / profession). /// This is a simplified mirror of the original C++ text; colors are kept white for now. /// private void AddRequirementDesc(int[] aPEEVals) { int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; // Get host player for requirement checks (via GameRun if available) object hostPlayer = null; try { var gameRun = EC_Game.GetGameRun(); if (gameRun != null) { // Try to get host player - method name may vary hostPlayer = gameRun; // Placeholder - would need actual GetHostPlayer() method } } catch { } uint dwPEE = PropEffectEssence(); // Level requirement if (LevelReq > 0) { int col = white; if (hostPlayer != null) { // In C++: pHost->GetMaxLevelSofar() >= m_iLevelReq ? white : red // For now, use a simple check - would need GetMaxLevelSofar() equivalent col = white; // TODO: Check actual player level } string levelFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_LEVELREQ); if (levelFmt.Contains("%d")) AddDescText(col, true, levelFmt, LevelReq); else AddDescText(col, true, "{0} {1}", levelFmt, LevelReq); } // Stat requirements - adjust color based on PEE flags and player stats if (StrengthReq > 0) { int col = white; if (hostPlayer != null) { // In C++: pHost->GetExtendProps().bs.strength < m_iStrengthReq ? red : ((dwPEE & PEE_STRENGTHREQ) ? lblue : white) // For now, use simple check if ((dwPEE & (1 << PEEI_STRENGTHREQ)) != 0) col = lblue; // TODO: Check actual player strength } string strFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTHREQ); if (strFmt.Contains("%d")) AddDescText(col, true, strFmt, StrengthReq); else AddDescText(col, true, "{0} {1}", strFmt, StrengthReq); } if (AgilityReq > 0) { int col = white; if (hostPlayer != null) { if ((dwPEE & (1 << PEEI_AGILITYREQ)) != 0) col = lblue; // TODO: Check actual player agility } string agiFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITYREQ); if (agiFmt.Contains("%d")) AddDescText(col, true, agiFmt, AgilityReq); else AddDescText(col, true, "{0} {1}", agiFmt, AgilityReq); } if (VitalityReq > 0) { int col = white; if (hostPlayer != null) { // TODO: Check PEE_VITALITYREQ flag and player vitality } string vitFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITYREQ); if (vitFmt.Contains("%d")) AddDescText(col, true, vitFmt, VitalityReq); else AddDescText(col, true, "{0} {1}", vitFmt, VitalityReq); } if (EnergyReq > 0) { int col = white; if (hostPlayer != null) { // TODO: Check PEE_ENERGYREQ flag and player energy } string eneFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGYREQ); if (eneFmt.Contains("%d")) AddDescText(col, true, eneFmt, EnergyReq); else AddDescText(col, true, "{0} {1}", eneFmt, EnergyReq); } } /// /// Get sub type name (loại trang bị) from element data /// private string GetSubTypeName() { elementdataman edm = ElementDataManProvider.GetElementDataMan(); if (edm == null) return null; try { DATA_TYPE dt = DATA_TYPE.DT_INVALID; object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); if (data == null) return null; // Get id_sub_type from essence uint idSubType = 0; if (TryGetNumber(data, new[] { "id_sub_type" }, out idSubType) && idSubType > 0) { // Try to get sub type data - check if it's weapon or armor DATA_TYPE subTypeDt = DATA_TYPE.DT_INVALID; object subTypeData = null; // Try weapon sub type first subTypeData = edm.get_data_ptr(idSubType, ID_SPACE.ID_SPACE_ESSENCE, ref subTypeDt); if (subTypeDt == DATA_TYPE.DT_WEAPON_SUB_TYPE) { var nameField = subTypeData.GetType().GetField("name"); if (nameField != null) { var nameArray = nameField.GetValue(subTypeData); if (nameArray != null && nameArray is ushort[]) { return ByteToStringUtils.UshortArrayToUnicodeString((ushort[])nameArray); } } } // Try armor sub type subTypeDt = DATA_TYPE.DT_INVALID; subTypeData = edm.get_data_ptr(idSubType, ID_SPACE.ID_SPACE_ESSENCE, ref subTypeDt); if (subTypeDt == DATA_TYPE.DT_ARMOR_SUB_TYPE) { var nameField = subTypeData.GetType().GetField("name"); if (nameField != null) { var nameArray = nameField.GetValue(subTypeData); if (nameArray != null && nameArray is ushort[]) { return ByteToStringUtils.UshortArrayToUnicodeString((ushort[])nameArray); } } } } } catch { } return null; } /// /// Get item level (cấp vũ khí/trang bị) from element data /// private int GetItemLevel() { elementdataman edm = ElementDataManProvider.GetElementDataMan(); if (edm == null) return 0; try { DATA_TYPE dt = DATA_TYPE.DT_INVALID; object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); if (data == null) return 0; // Try weapon_level first (for weapons) if (TryGetNumber(data, new[] { "weapon_level" }, out int weaponLevel) && weaponLevel > 0) return weaponLevel; // Try level (for armor and other items) if (TryGetNumber(data, new[] { "level" }, out int level) && level > 0) return level; } catch { } return 0; } /// /// Get character combo ID (phái hạn chế) from element data /// private uint GetCharacterComboId() { elementdataman edm = ElementDataManProvider.GetElementDataMan(); if (edm == null) return 0; try { DATA_TYPE dt = DATA_TYPE.DT_INVALID; object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); if (data == null) return 0; if (TryGetNumber(data, new[] { "character_combo_id" }, out uint comboId)) return comboId; } catch { } return 0; } /// /// Add profession requirement description (phái hạn chế) /// Like C++: AddProfReqDesc(int iProfReq) - displays class restrictions /// private void AddProfReqDesc(int profReq) { // In C++: if (CECProfConfig::Instance().ContainsAllProfession(iProfReq)) return; // Check if all professions are allowed - if so, don't display restriction // If profReq == 0, it means no restriction (all professions allowed) // If all 12 bits are set (0xFFF = 4095), it also means all professions allowed if (profReq == 0) return; // No restriction const int NUM_PROFESSION = 12; const int allProfMask = (1 << NUM_PROFESSION) - 1; // 0xFFF = 4095 (all 12 bits set) if ((profReq & allProfMask) == allProfMask) return; // All professions allowed // Check if any profession bit is set - if none, don't display bool hasAnyProf = false; for (int i = 0; i < NUM_PROFESSION; i++) { if ((profReq & (1 << i)) != 0) { hasAnyProf = true; break; } } if (!hasAnyProf) return; // No profession restriction specified int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; // Get host player profession for color check int playerProf = -1; try { var gameRun = EC_Game.GetGameRun(); if (gameRun != null) { var hostPlayer = gameRun.GetHostPlayer(); if (hostPlayer != null) { playerProf = hostPlayer.GetProfession(); } } } catch { } int col = white; if (playerProf >= 0) { // In C++: col = (iProfReq & (1 << pHost->GetProfession())) ? ITEMDESC_COL_WHITE : ITEMDESC_COL_RED; if ((profReq & (1 << playerProf)) == 0) col = red; } string profFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_PROFESSIONREQ); AddDescText(col, false, profFmt); // List all allowed professions // In C++: for (int i=0; i < NUM_PROFESSION; i++) { if (iProfReq & (1 << i)) { m_strDesc += _AL(" "); AddDescText(col, false, pGameRun->GetProfName(i)); } } // Profession message IDs matching C++ GetProfName: FIXMSG_PROF_WARRIOR, FIXMSG_PROF_MAGE, etc. FixedMsg[] profMsgIds = new FixedMsg[] { FixedMsg.FIXMSG_PROF_WARRIOR, // 0 FixedMsg.FIXMSG_PROF_MAGE, // 1 FixedMsg.FIXMSG_PROF_MONK, // 2 FixedMsg.FIXMSG_PROF_HAG, // 3 FixedMsg.FIXMSG_PROF_ORC, // 4 FixedMsg.FIXMSG_PROF_GHOST, // 5 FixedMsg.FIXMSG_PROF_ARCHOR, // 6 FixedMsg.FIXMSG_PROF_ANGEL, // 7 FixedMsg.FIXMSG_PROF_JIANLING, // 8 FixedMsg.FIXMSG_PROF_MEILING, // 9 FixedMsg.FIXMSG_PROF_YEYING, // 10 FixedMsg.FIXMSG_PROF_YUEXIAN // 11 }; for (int i = 0; i < NUM_PROFESSION && i < profMsgIds.Length; i++) { if ((profReq & (1 << i)) != 0) { // In C++, space is always added before each profession name m_strDesc += " "; // Get profession name from fixed messages (like C++ GetProfName does) // C++ uses: pStrTab->GetWideString(s_ProfDesc[i]) where s_ProfDesc[i] = FIXMSG_PROF_WARRIOR, etc. string profName = null; try { var fixedMsgs = EC_Game.GetFixedMsgs(); if (fixedMsgs != null && fixedMsgs.IsInitialized()) { profName = fixedMsgs.GetWideString((int)profMsgIds[i]); // Trim whitespace if present if (!string.IsNullOrEmpty(profName)) profName = profName.Trim(); } } catch (Exception ex) { UnityEngine.Debug.LogWarning($"[EC_IvtrEquip] Failed to get profession name for index {i}: {ex.Message}"); } // If we got a valid name, use it; otherwise use fallback if (!string.IsNullOrEmpty(profName)) { AddDescText(col, false, profName); } else { // Fallback: show profession index (should not happen if fixed_msg.txt is loaded correctly) UnityEngine.Debug.LogWarning($"[EC_IvtrEquip] Profession name not found for index {i}, enum value {(int)profMsgIds[i]}, profReq={profReq}"); AddDescText(col, false, $"P{i}"); } } } m_strDesc += "\\r"; } /// /// Add price description /// protected override void AddPriceDesc(int col, bool repair) { if (repair) AddDescText(col, false, GetItemDescString(DescriptipionMsg.ITEMDESC_REPAIRCOST), GetRepairCost()); else AddDescText(col, true, "Price: {0}", GetScaledPrice()); } /// /// Format property description /// public string FormatPropDesc(Property prop) { string tmpDesc = m_strDesc; m_strDesc = ""; AddOneAddOnPropDesc(prop.Type, prop.Params, null, null, prop.Local); string result = m_strDesc; m_strDesc = tmpDesc; return result; } /// /// Format refine data into a string /// (hack function, do NOT use it in multi-thread environment) /// public string FormatRefineData(uint addonId) { DATA_TYPE dataType = DATA_TYPE.DT_INVALID; elementdataman edm = EC_Game.GetElementDataMan(); if (edm == null) return ""; object data = edm.get_data_ptr(addonId, ID_SPACE.ID_SPACE_ADDON, ref dataType); if (dataType != DATA_TYPE.DT_EQUIPMENT_ADDON || data == null) return ""; EQUIPMENT_ADDON pType = (EQUIPMENT_ADDON)data; string szTxt = ""; // save current member for backup string tmpDesc = m_strDesc; m_strDesc = ""; // get the original refine data int[] aRefines = new int[MAX_REFINEINDEX]; for (int i = 0; i < MAX_REFINEINDEX; i++) aRefines[i] = 0; int[] paramArray = new int[] { pType.param1, pType.param2, pType.param3 }; AddOneAddOnPropDesc((int)pType.id, paramArray, null, aRefines, true); // get splitter // In C++: g_pGame->GetGameRun()->GetUIManager()->GetInGameUIMan()->GetStringFromTable(8656) string szSplitter = " "; // Default splitter try { var gameRun = EC_Game.GetGameRun(); if (gameRun != null) { // Try to get from UI manager if available // Note: This may not be fully implemented in C# yet var fixedMsgs = EC_Game.GetFixedMsgs(); if (fixedMsgs != null) { string splitterStr = fixedMsgs.GetWideString(8656); if (!string.IsNullOrEmpty(splitterStr)) szSplitter = splitterStr; } } } catch { // Use default splitter if lookup fails } int findSplitter = 0; for (int pos = 0; pos < szSplitter.Length; pos++) { findSplitter = m_strDesc.IndexOf(szSplitter[pos]); if (findSplitter >= 0) break; } szTxt = (findSplitter <= 0) ? m_strDesc : m_strDesc.Substring(0, findSplitter); // restore member because previous method may modify it m_strDesc = tmpDesc; // replace all old splitter to new splitter const string OldSplitter = "\\r"; const string NewSplitter = " "; string result = ""; findSplitter = 0; while (true) { int curfind = szTxt.IndexOf(OldSplitter, findSplitter); if (curfind >= 0) { if (!string.IsNullOrEmpty(result)) result += NewSplitter; if (curfind > findSplitter) { result += szTxt.Substring(findSplitter, curfind - findSplitter); } findSplitter = curfind + OldSplitter.Length; if (findSplitter >= szTxt.Length) break; } else { if (!string.IsNullOrEmpty(result)) result += NewSplitter; result += szTxt.Substring(findSplitter); break; } } szTxt = result; // check the special refine property group int[] ALLMAGIC_REFINE = new int[] { REFINE_GOLDDEF, REFINE_WOODDEF, REFINE_WATERDEF, REFINE_FIREDEF, REFINE_EARTHDEF }; int[] ALLDAMAGE_REFINE = new int[] { REFINE_PHYDAMAGE, REFINE_MAGICDAMAGE }; int[] ALLDEFENCE_REFINE = new int[] { REFINE_PHYDEF, REFINE_GOLDDEF, REFINE_WOODDEF, REFINE_WATERDEF, REFINE_FIREDEF, REFINE_EARTHDEF }; if (CheckSpecialRefineType(aRefines, ALLMAGIC_REFINE, ALLMAGIC_REFINE.Length)) { int value = aRefines[ALLMAGIC_REFINE[0]]; // Format with %+d equivalent: always show sign string szRefine = string.Format("{0} {1}{2}", GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF), value >= 0 ? "+" : "", value); if (!string.IsNullOrEmpty(szTxt)) szTxt += NewSplitter; szTxt += szRefine; } else if (CheckSpecialRefineType(aRefines, ALLDAMAGE_REFINE, ALLDAMAGE_REFINE.Length)) { int value = aRefines[ALLDAMAGE_REFINE[0]]; // Format with %+d equivalent: always show sign string szRefine = string.Format("{0} {1}{2}", GetItemDescString(DescriptipionMsg.ITEMDESC_ADDDAMAGE), value >= 0 ? "+" : "", value); if (!string.IsNullOrEmpty(szTxt)) szTxt += NewSplitter; szTxt += szRefine; } else if (CheckSpecialRefineType(aRefines, ALLDEFENCE_REFINE, ALLDEFENCE_REFINE.Length)) { int value = aRefines[ALLDEFENCE_REFINE[0]]; // Format with %+d equivalent: always show sign string szRefine = string.Format("{0} {1}{2}", GetItemDescString(DescriptipionMsg.ITEMDESC_DEFENCE), value >= 0 ? "+" : "", value); if (!string.IsNullOrEmpty(szTxt)) szTxt += NewSplitter; szTxt += szRefine; } else { int descId = 0; for (int refineIndex = 0; refineIndex < MAX_REFINEINDEX; refineIndex++) { if (aRefines[refineIndex] == 0) continue; // do NOT use loop because the enum value may changed switch (refineIndex) { case REFINE_PHYDAMAGE: descId = (int)DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE; break; case REFINE_MAGICDAMAGE: descId = (int)DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE; break; case REFINE_PHYDEF: descId = (int)DescriptipionMsg.ITEMDESC_PHYDEFENCE; break; case REFINE_GOLDDEF: descId = (int)DescriptipionMsg.ITEMDESC_GOLDDEFENCE; break; case REFINE_WOODDEF: descId = (int)DescriptipionMsg.ITEMDESC_WOODDEFENCE; break; case REFINE_WATERDEF: descId = (int)DescriptipionMsg.ITEMDESC_WATERDEFENCE; break; case REFINE_FIREDEF: descId = (int)DescriptipionMsg.ITEMDESC_FIREDEFENCE; break; case REFINE_EARTHDEF: descId = (int)DescriptipionMsg.ITEMDESC_EARTHDEFENCE; break; case REFINE_HP: descId = (int)DescriptipionMsg.ITEMDESC_ADDHP; break; case REFINE_DODGE: descId = (int)DescriptipionMsg.ITEMDESC_DODGE; break; default: descId = -1; break; } if (descId >= 0) { int value = aRefines[refineIndex]; // Format with %+d equivalent: always show sign string szRefine = string.Format("{0} {1}{2} ", GetItemDescString((DescriptipionMsg)descId), value >= 0 ? "+" : "", value); if (!string.IsNullOrEmpty(szTxt)) szTxt += NewSplitter; szTxt += szRefine; } } } return szTxt; } /// /// Get refine addon ID (virtual method, override in derived classes) /// public virtual uint GetRefineAddOn() { return 0; } /// /// Check the special refine property /// private bool CheckSpecialRefineType(int[] aRefines, int[] types, int typeCount) { // check the special refine property int sameValue = 0; for (int refineIndex = 0; refineIndex < MAX_REFINEINDEX; refineIndex++) { bool checked_ = false; for (int i = 0; i < typeCount; i++) { if (types[i] == refineIndex) { if (sameValue == 0) { if (aRefines[refineIndex] == 0) { // specific property was not found return false; } sameValue = aRefines[refineIndex]; } else if (sameValue != aRefines[refineIndex]) { // property values were different return false; } checked_ = true; break; } } // check other refine property if (!checked_ && aRefines[refineIndex] != 0) { // has other refine property return false; } } return true; } /// /// Check property range value /// public bool CheckPropRangeValue(Property prop) { int idProp = prop.Type; byte propType = GetPropertyType(idProp); switch (propType) { case 25: // +Gold(%) -Fire(%) case 26: // +Wood(%) -Gold(%) case 27: // +Water(%) -Earth(%) case 28: // +Fire(%) -Water(%) case 29: // +Earth(%) -Wood(%) case 30: // +Gold -Fire case 31: // +Wood -Gold case 32: // +Water -Earth case 33: // +Fire -Water case 34: // +Earth -Wood case 55: // Add skill return false; } // Check for range values in equipment addon data // This would normally query the equipment addon data return false; } public int GetHoleNum() { return Holes != null ? Holes.Count : 0; } public int GetHoleItem(int index) { if(Holes == null || index < 0 || index >= Holes.Count) return 0; return Holes[index]; } #endregion #region Helper Methods /// /// Parse properties /// private void ParseProperties() { RefineLvl = 0; PropNum = 0; EmbedNum = 0; if (Props.Count == 0) return; foreach (Property prop in Props) { int level = 0; if (prop.Embed) { EmbedNum++; continue; } else if (prop.Suite) continue; else if (prop.Engraved) continue; byte propType = GetPropertyType(prop.Type); switch (propType) { case 200: // Refine physical damage case 201: // Refine magic damage case 202: // Refine physical defense case 203: // Refine gold defense case 204: // Refine wood defense case 205: // Refine water defense case 206: // Refine fire defense case 207: // Refine earth defense case 208: // Refine HP case 209: // Refine dodge case 210: // Refine all magic defense case 211: // Refine physical damage & magic damage case 212: // Refine physical defense & magic defense level = prop.Params[1]; break; } if (level > 0) RefineLvl = level; else PropNum++; } } /// /// Check if this equipment belongs to a suite /// public int GetSuiteID() { // This would normally query the game's suite equip table Dictionary suiteEquipTab = EC_Game.GetSuiteEquipTab(); if (suiteEquipTab.TryGetValue(GetTemplateID(), out int suiteId)) { return suiteId; } return 0; } /// /// Get item description string by ID /// Primary source: item_desc.txt table (loaded via EC_Game.GetItemDesc()) /// Fallback: Directly reads from item_desc.txt file when table lookup fails /// private string GetItemDescString(DescriptipionMsg id) { int enumValue = (int)id; // First try to get from the loaded string table (item_desc.txt) // The table contains strings indexed 0-416, matching enum values var tab = EC_Game.GetItemDesc(); if (tab != null) { string s = tab.GetWideString(enumValue); if (!string.IsNullOrEmpty(s)) return s; } // Fallback: Directly read f rom item_desc.txt file // This handles cases where table lookup fails but the string exists in the file try { string filePath = Path.Combine(Application.streamingAssetsPath, "configs", "item_desc.txt"); if (File.Exists(filePath)) { string content = File.ReadAllText(filePath, Encoding.UTF8); string[] lines = content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); bool foundBegin = false; int currentIndex = 0; foreach (string line in lines) { string trimmed = line.Trim(); if (trimmed.Length == 0) continue; if (trimmed.Equals("#_begin", StringComparison.OrdinalIgnoreCase)) { foundBegin = true; continue; } if (!foundBegin) continue; // Skip comments if (trimmed.StartsWith("#") || trimmed.StartsWith("//")) continue; // Remove quotes if present if (trimmed.StartsWith("\"") && trimmed.EndsWith("\"")) { trimmed = trimmed.Substring(1, trimmed.Length - 2); } if (currentIndex == enumValue) { return trimmed; } currentIndex++; } } } catch (Exception ex) { Debug.LogWarning($"[EC_IvtrEquip] Failed to read item_desc.txt fallback for ID {enumValue}: {ex.Message}"); } // Final fallback: Return enum value as string return enumValue.ToString(); } /// /// Add resistance property description with proper formatting /// private void AddResistDesc(int color, DescriptipionMsg resistId, int value, bool local) { string resistFmt = GetItemDescString(resistId); int displayValue = local ? VisualizeFloatPercent(value) : value; // Check if format string has placeholder, if not append value with + sign if (resistFmt.Contains("%d") || resistFmt.Contains("%+d") || resistFmt.Contains("{0}")) AddDescText(color, true, resistFmt, displayValue); else AddDescText(color, true, "{0} +{1}", resistFmt, displayValue); } /// /// Add description text /// protected override void AddDescText(int color, bool newLine, string format, params object[] args) { // Add color prefix if color is specified if (color >= 0) { string colorStr = GetColorString((DescriptipionMsg)color); m_strDesc += colorStr; } // Call base implementation for format conversion (handles both {0} and %d styles) base.AddDescText(-1, false, format, args); // Add newline if requested (base class uses "\n", but equip uses "\\r") if (newLine) m_strDesc += "\\r"; } /// /// Get color string for color ID /// Returns color codes in ^RRGGBB format (6 hex digits) for text formatting /// private 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 } } /// /// Convert word color to Unity Color /// private Color ColorFromWord(ushort wordColor) { // Convert word color to Unity Color // This is a simplified conversion return new Color( ((wordColor >> 10) & 0x1F) / 31.0f, ((wordColor >> 5) & 0x1F) / 31.0f, (wordColor & 0x1F) / 31.0f, 1.0f ); } /// /// Check if property is sharpener property /// private bool IsSharpenerProperty(byte propType) { return propType >= 100 && propType <= 115; } /// /// Visualize float percent /// private int VisualizeFloatPercent(int value) { return (int)(IntToFloat(value) * 100.0f + 0.5f); } /// /// Helper method to convert int to float (bit reinterpretation) /// private float IntToFloat(int value) { return BitConverter.ToSingle(BitConverter.GetBytes(value), 0); } /// /// Helper method to convert float to int (bit reinterpretation) /// private int FloatToInt(float value) { return BitConverter.ToInt32(BitConverter.GetBytes(value), 0); } /// /// Add range value description for normal integer values (replaces ADD_RANGE_VALUE_DESC_ID_NORMAL macro) /// private void AddRangeValueDescIdNormal(DescriptipionMsg idString, int p0, int p1, bool local, int color) { if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(idString), p0); AddDescText(color, true, "~~{0}", p1); } else { AddDescText(color, true, GetItemDescString(idString), p0); } } else { AddDescText(color, true, GetItemDescString(idString), p0); } } /// /// Add range value description for float values (replaces ADD_RANGE_VALUE_DESC_ID_FLOAT macro) /// private void AddRangeValueDescIdFloat(DescriptipionMsg idString, int p0, int p1, bool local, int color) { float f0 = IntToFloat(p0); float f1 = IntToFloat(p1); if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(idString), f0); AddDescText(color, true, "~~{0:F2}", f1); } else { AddDescText(color, true, GetItemDescString(idString), f0); } } else { AddDescText(color, true, GetItemDescString(idString), f0); } } /// /// Add range value description for percent values (replaces ADD_RANGE_VALUE_DESC_ID_PERCENT macro) /// private void AddRangeValueDescIdPercent(DescriptipionMsg idString, int p0, int p1, bool local, int color) { if (local) { int v0 = VisualizeFloatPercent(p0); int v1 = VisualizeFloatPercent(p1); if (v0 != v1) { AddDescText(color, false, GetItemDescString(idString), v0); AddDescText(color, true, "~~{0}%%", v1); } else { AddDescText(color, true, GetItemDescString(idString), v0); } } else { AddDescText(color, true, GetItemDescString(idString), p0); } } /// /// Add range value description for minus percent values variant 1 (replaces ADD_RANGE_VALUE_DESC_ID_MINUS_PERCENT_1 macro) /// private void AddRangeValueDescIdMinusPercent1(DescriptipionMsg idString, int p0, int p1, bool local, int color) { if (local) { int v0 = -VisualizeFloatPercent(p0); int v1 = VisualizeFloatPercent(p1); if (v0 != v1) { AddDescText(color, false, GetItemDescString(idString), v0); AddDescText(color, true, "~~{0}%%", v1); } else { AddDescText(color, true, GetItemDescString(idString), v0); } } else { AddDescText(color, true, GetItemDescString(idString), -p0); } } /// /// Add range value description for minus percent values variant 2 (replaces ADD_RANGE_VALUE_DESC_ID_MINUS_PERCENT_2 macro) /// private void AddRangeValueDescIdMinusPercent2(DescriptipionMsg idString, int p0, int p1, bool local, int color) { if (local) { int v0 = -VisualizeFloatPercent(p0); int v1 = VisualizeFloatPercent(p1); if (v0 != v1) { AddDescText(color, false, GetItemDescString(idString), v0); AddDescText(color, true, "~~{0}%%", v1); } else { AddDescText(color, true, GetItemDescString(idString), v0); } } else { AddDescText(color, true, GetItemDescString(idString), -VisualizeFloatPercent(p0)); } } /// /// Add range value description for half values (replaces ADD_RANGE_VALUE_DESC_ID_HALF macro) /// private void AddRangeValueDescIdHalf(DescriptipionMsg idString, int p0, int p1, bool local, int color) { int h0 = p0 / 2; int h1 = p1 / 2; if (local) { if (h0 != h1) { AddDescText(color, false, GetItemDescString(idString), h0); AddDescText(color, true, "~~{0}", h1); } else { AddDescText(color, true, GetItemDescString(idString), h0); } } else { AddDescText(color, true, GetItemDescString(idString), h0); } } /// /// Add range value description for string normal values (replaces ADD_RANGE_VALUE_DESC_STR_NORMAL macro) /// private void AddRangeValueDescStrNormal(DescriptipionMsg idString, int p0, int p1, bool local, int color) { if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(idString)); AddDescText(color, true, " %+d~~%d", p0, p1); } else { AddDescText(color, false, GetItemDescString(idString)); AddDescText(color, true, " %+d", p0); } } else { AddDescText(color, false, GetItemDescString(idString)); AddDescText(color, true, " %+d", p0); } } /// /// Add one add-on property description /// private void AddOneAddOnPropDesc(int idProp, int[] param, int[] aPEEVals, int[] aRefines, bool local) { // Extract parameters from array int p0 = param != null && param.Length > 0 ? param[0] : 0; int p1 = param != null && param.Length > 1 ? param[1] : 0; int p2 = param != null && param.Length > 2 ? param[2] : 0; byte propType = GetPropertyType(idProp); int color = -1; if (!IsSharpenerProperty(propType)) { switch (propType) { case 0: // ������ if (!local) { if (aPEEVals != null) aPEEVals[PEEI_PHYDAMAGE] += p0; } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE)); AddDescText(color, true, " %+d", p0); break; case 1: // ���������� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE), p0); } } else { if (aPEEVals != null) aPEEVals[PEEI_MAX_PHYDAMAGE] += p0; AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE), p0); } break; case 2: // ������(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDMGEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDMGEXTRA), p0); } break; case 3: // ħ������ if (!local) { if (aPEEVals != null) aPEEVals[PEEI_MAGICDAMAGE] += p0; } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE)); AddDescText(color, true, " %+d", p0); break; case 4: // ħ���������� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE), p0); } } else { if (aPEEVals != null) aPEEVals[PEEI_MAX_MAGICDAMAGE] += p0; AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE), p0); } break; case 5: // ħ������(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MAGICDMGEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MAGICDMGEXTRA), p0); } break; case 6: // +���-�﹥ if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_PHYDEF] += p0; aPEEVals[PEEI_PHYDAMAGE] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE)); AddDescText(color, true, " %+d", -p1); break; case 7: // +�﹥-��� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_PHYDAMAGE] += p0; aPEEVals[PEEI_PHYDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); AddDescText(color, true, " %+d", -p1); break; case 8: // +ħ��-ħ�� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_MAGICDAMAGE] += p0; aPEEVals[PEEI_GOLDDEF] -= p1; aPEEVals[PEEI_WOODDEF] -= p1; aPEEVals[PEEI_WATERDEF] -= p1; aPEEVals[PEEI_FIREDEF] -= p1; aPEEVals[PEEI_EARTHDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF)); AddDescText(color, true, " %+d", -p1); break; case 9: // �����ٶ� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKTIME), -IntToFloat(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKTIME), -p0 * 0.05f); } break; case 10: // �������� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDATKDIST), IntToFloat(p0)); } else { if (aPEEVals != null) { float fDist = IntToFloat(aPEEVals[PEEI_ATKDIST]) + IntToFloat(p0); aPEEVals[PEEI_ATKDIST] = FloatToInt(fDist); } AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDATKDIST), IntToFloat(p0)); } break; case 11: // ����ʱ�� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -p0); } break; case 12: // ������� if (!local) { if (aPEEVals != null) aPEEVals[PEEI_PHYDEF] += p0; } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 13: // �������(%) if (local) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFEXTRA), VisualizeFloatPercent(p0)); else AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFEXTRA), p0); break; case 14: // ���з��� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_GOLDDEF] += p0; aPEEVals[PEEI_WOODDEF] += p0; aPEEVals[PEEI_WATERDEF] += p0; aPEEVals[PEEI_FIREDEF] += p0; aPEEVals[PEEI_EARTHDEF] += p0; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF)); AddDescText(color, true, " %+d", p0); break; case 15: // ��� if (local) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFENCE)); if (p0 != p1) AddDescText(color, true, " %d~~%d", p0, p1); else AddDescText(color, true, " %d", p0); } else { if (aPEEVals != null) aPEEVals[PEEI_GOLDDEF] += p0; AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFENCE)); AddDescText(color, true, " %+d", p0); } break; case 16: // ���(%) if (local) { if ((p0) != (p1)) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFEXTRA), (p0)); AddDescText(color, true, "~~-%.2f%%", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFEXTRA), (p0)); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFEXTRA), (p0)); } break; case 17: // ľ�� if (local) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFENCE)); if (p0 != p1) AddDescText(color, true, " %d~~%d", p0, p1); else AddDescText(color, true, " %d", p0); } else { if (aPEEVals != null) aPEEVals[PEEI_WOODDEF] += p0; AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFENCE)); AddDescText(color, true, " %+d", p0); } break; case 18: // ľ��(%) if (local) { if ((p0) != (p1)) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFEXTRA), (p0)); AddDescText(color, true, "~~-%.2f%%", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFEXTRA), (p0)); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFEXTRA), (p0)); } break; case 19: // ˮ�� if (local) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFENCE)); if (p0 != p1) AddDescText(color, true, " %d~~%d", p0, p1); else AddDescText(color, true, " %d", p0); } else { if (aPEEVals != null) aPEEVals[PEEI_WATERDEF] += p0; AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFENCE)); AddDescText(color, true, " %+d", p0); } break; case 20: // ˮ��(%) if (local) { if ((p0) != (p1)) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFEXTRA), (p0)); AddDescText(color, true, "~~-%.2f%%", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFEXTRA), (p0)); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFEXTRA), (p0)); } break; case 21: // ��� if (local) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFENCE)); if (p0 != p1) AddDescText(color, true, " %d~~%d", p0, p1); else AddDescText(color, true, " %d", p0); } else { if (aPEEVals != null) aPEEVals[PEEI_FIREDEF] += p0; AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFENCE)); AddDescText(color, true, " %+d", p0); } break; case 22: // ���(%) if (local) { if ((p0) != (p1)) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFEXTRA), (p0)); AddDescText(color, true, "~~-%.2f%%", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFEXTRA), (p0)); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFEXTRA), (p0)); } break; case 23: // ���� if (local) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFENCE)); if (p0 != p1) AddDescText(color, true, " %d~~%d", p0, p1); else AddDescText(color, true, " %d", p0); } else { if (aPEEVals != null) aPEEVals[PEEI_EARTHDEF] += p0; AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFENCE)); AddDescText(color, true, " %+d", p0); } break; case 24: // ����(%) if (local) { if ((p0) != (p1)) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFEXTRA), (p0)); AddDescText(color, true, "~~-%.2f%%", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFEXTRA), (p0)); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFEXTRA), (p0)); } break; case 25: // +���(%)-���(%) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFEXTRA), VisualizeFloatPercent(p0)); AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFEXTRA), -VisualizeFloatPercent(p1)); break; case 26: // +ľ��(%)-���(%) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFEXTRA), VisualizeFloatPercent(p0)); AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFEXTRA), -VisualizeFloatPercent(p1)); break; case 27: // +ˮ��(%)-����(%) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFEXTRA), VisualizeFloatPercent(p0)); AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFEXTRA), -VisualizeFloatPercent(p1)); break; case 28: // +���(%)-ˮ��(%) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFEXTRA), VisualizeFloatPercent(p0)); AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFEXTRA), -VisualizeFloatPercent(p1)); break; case 29: // +����(%)-ľ��(%) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFEXTRA), VisualizeFloatPercent(p0)); AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFEXTRA), -VisualizeFloatPercent(p1)); break; case 30: // +���-��� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_GOLDDEF] += p0; aPEEVals[PEEI_FIREDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFENCE)); AddDescText(color, true, " %+d", -p1); break; case 31: // +ľ��-��� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_WOODDEF] += p0; aPEEVals[PEEI_GOLDDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFENCE)); AddDescText(color, true, " %+d", -p1); break; case 32: // +ˮ��-���� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_WATERDEF] += p0; aPEEVals[PEEI_EARTHDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFENCE)); AddDescText(color, true, " %+d", -p1); break; case 33: // +���-ˮ�� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_FIREDEF] += p0; aPEEVals[PEEI_WATERDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFENCE)); AddDescText(color, true, " %+d", -p1); break; case 34: // +����-ľ�� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_EARTHDEF] += p0; aPEEVals[PEEI_WOODDEF] -= p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFENCE)); AddDescText(color, true, " %+d", -p1); break; case 35: // HP if (!local) { if (aPEEVals != null) aPEEVals[PEEI_HP] += p0; } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP)); AddDescText(color, true, " %+d", p0); break; case 36: // MP if (!local) { if (aPEEVals != null) aPEEVals[PEEI_MP] += p0; } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMP)); AddDescText(color, true, " %+d", p0); break; case 37: // HP(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_HPEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_HPEXTRA), p0); } break; case 38: // MP(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MPEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MPEXTRA), p0); } break; case 39: // HP�ָ��ٶ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_HPRECOVER), p0 / 2); break; case 40: // MP�ָ��ٶ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_MPRECOVER), p0 / 2); break; case 41: // ���� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); } break; case 42: // ���� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); } break; case 43: // ���� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); } break; case 44: // ���� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); } break; case 45: // ����һ���� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), p0); } break; case 46: // ���� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0); } break; case 47: // ����(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATINGEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATINGEXTRA), p0); } break; case 48: // �ƶ��ٶ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_RUNSPEED), IntToFloat(p0)); break; case 49: // �ƶ��ٶ�(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_RUNSPEEDEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_RUNSPEEDEXTRA), p0); } break; case 50: // ���� if (!local) { if (aPEEVals != null) aPEEVals[PEEI_DODGE] += p0; } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DODGE)); AddDescText(color, true, " %+d", p0); break; case 51: // ����(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DODGEEXTRA), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DODGEEXTRA), p0); } break; case 52: // �;ö� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ENDURANCE)); AddDescText(color, true, " %+d", p0); break; case 53: // �;ö�(%) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ENDURANCEEXTRA), VisualizeFloatPercent(p0)); break; case 54: // �������� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYRESIST), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYRESIST), p0); } break; case 55: // ���Ӽ��� { // Get skill description - TODO: Implement skill description retrieval string skillDesc = $"Skill {p0}"; // Placeholder - needs proper implementation AddDescText(color, true, "{0}", skillDesc); break; } case 56: // װ������ if (local) { if (VisualizeFloatPercent(p0) != VisualizeFloatPercent(p1)) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_REQEXTRA), -VisualizeFloatPercent(p0)); AddDescText(color, true, "~~%d", VisualizeFloatPercent(p1)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_REQEXTRA), -VisualizeFloatPercent(p0)); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_REQEXTRA), -VisualizeFloatPercent(p0)); } break; case 57: // δ֪���� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_RANDOMPROP)); break; case 58: // ����ֵ�ӳ� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXP), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXP), p0); } break; case 59: // �����ȼ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); break; case 60: // �����ȼ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DEF_DEGREE), p0); break; case 61: // ���з�����%�� if (local) AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_TOTAL_DEFENCE_ADD), VisualizeFloatPercent(p0)); else AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_TOTAL_DEFENCE_ADD), (p0)); break; case 62: // ����֮�� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PROFVIEW)); break; case 63: // ���� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_SOULPOWER), p0); break; case 64: // ��ϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_GOLDRESIST, p0, local); break; case 65: // ľϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_WOODRESIST, p0, local); break; case 66: // ˮϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_WATERRESIST, p0, local); break; case 67: // ��ϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_FIRERESIST, p0, local); break; case 68: // ��ϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_EARTHRESIST, p0, local); break; case 69: // ���м���(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICRESIST), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICRESIST), p0); } break; case 70: // �����ȼ���Χ������ֵ������ã� // ADD_RANGE_VALUE_DESC_ID_NORMAL equivalent if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); AddDescText(color, true, "~~{0}", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); } break; case 71: // �����ȼ���Χ������ֵ������ã� AddRangeValueDescIdNormal(DescriptipionMsg.ITEMDESC_DEF_DEGREE, p0, p1, local, color); break; case 72: // ����һ����(%)��Χ������ֵ������ã� AddRangeValueDescIdPercent(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE, p0, p1, local, color); break; case 73: // HP��Χ������ֵ������ã� AddRangeValueDescStrNormal(DescriptipionMsg.ITEMDESC_ADDHP, p0, p1, local, color); break; case 74: // MP��Χ������ֵ������ã� AddRangeValueDescStrNormal(DescriptipionMsg.ITEMDESC_ADDMP, p0, p1, local, color); break; case 75: // ����(%)��Χ������ֵ������ã� AddRangeValueDescIdPercent(DescriptipionMsg.ITEMDESC_ATKRATINGEXTRA, p0, p1, local, color); break; case 76: // ���������Χ������ֵ������ã� AddRangeValueDescStrNormal(DescriptipionMsg.ITEMDESC_PHYDEFENCE, p0, p1, local, color); break; case 77: // ���з�����Χ������ֵ������ã� AddRangeValueDescStrNormal(DescriptipionMsg.ITEMDESC_ALLMAGICDEF, p0, p1, local, color); break; case 78: // �������(%)��Χ������ֵ������ã� AddRangeValueDescIdPercent(DescriptipionMsg.ITEMDESC_PHYRESIST, p0, p1, local, color); break; case 79: // ���м���(%)��Χ������ֵ������ã� AddRangeValueDescIdPercent(DescriptipionMsg.ITEMDESC_ALLMAGICRESIST, p0, p1, local, color); break; case 80: // ����ʱ��(%)��Χ������ֵ������ã� AddRangeValueDescIdMinusPercent1(DescriptipionMsg.ITEMDESC_CASTTIME, p0, p1, local, color); break; case 81: // �������뷶Χ������ֵ������ã� AddRangeValueDescIdFloat(DescriptipionMsg.ITEMDESC_ADDATKDIST, p0, p1, local, color); break; case 82: // MP�ָ��ٶȷ�Χ������ֵ������ã� AddRangeValueDescIdHalf(DescriptipionMsg.ITEMDESC_MPRECOVER, p0, p1, local, color); break; case 83: // �������(%)��Χ������ֵ������ã� AddRangeValueDescIdPercent(DescriptipionMsg.ITEMDESC_PHYDEFEXTRA, p0, p1, local, color); break; case 84: // ���з���(%)��Χ������ֵ������ã� AddRangeValueDescIdPercent(DescriptipionMsg.ITEMDESC_TOTAL_DEFENCE_ADD, p0, p1, local, color); break; case 85: // HP�ָ��ٶȷ�Χ������ֵ������ã� AddRangeValueDescIdHalf(DescriptipionMsg.ITEMDESC_HPRECOVER, p0, p1, local, color); break; case 86: // ������Χ������ֵ������ã� AddRangeValueDescStrNormal(DescriptipionMsg.ITEMDESC_DODGE, p0, p1, local, color); break; case 87: // ���������޷�Χ������ֵ������ã� AddRangeValueDescIdNormal(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE, p0, p1, local, color); break; case 88: // ħ���������޷�Χ������ֵ������ã� AddRangeValueDescIdNormal(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE, p0, p1, local, color); break; case 89: // װ������Χ������ֵ������ã� AddRangeValueDescIdMinusPercent2(DescriptipionMsg.ITEMDESC_REQEXTRA, p0, p1, local, color); break; case 90: // ��ħ�ȼ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PENETRATION), p0); break; case 91: // ��ħ�ȼ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_RESILIENCE), p0); break; case 92: // +���+ħ�� if (!local) { if (aPEEVals != null) { aPEEVals[PEEI_PHYDEF] += p0; aPEEVals[PEEI_GOLDDEF] += p1; aPEEVals[PEEI_WOODDEF] += p1; aPEEVals[PEEI_WATERDEF] += p1; aPEEVals[PEEI_FIREDEF] += p1; aPEEVals[PEEI_EARTHDEF] += p1; } } AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); AddDescText(color, true, " %+d", p0); AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF)); AddDescText(color, true, " %+d", p1); break; case 93: // ��ɫHP AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP)); AddDescText(color, true, " %+d", p0); break; case 94: // ��ɫHP��Χ AddRangeValueDescStrNormal(DescriptipionMsg.ITEMDESC_ADDHP, p0, p1, local, color); break; case 95: // ���� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); break; case 96: // ���� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); break; case 97: // ���� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); break; case 98: // ���� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); break; case 99: // ��ɫMP AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMP)); AddDescText(color, true, " %+d", p0); break; case 160: // �����̶�ֵ AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_VIGOUR)); AddDescText(color, true, " %+d", p0); break; case 200: // ���������� if (aRefines != null) aRefines[REFINE_PHYDAMAGE] += p0; break; case 201: // ����ħ������ if (aRefines != null) aRefines[REFINE_MAGICDAMAGE] += p0; break; case 202: // ����������� if (aRefines != null) aRefines[REFINE_PHYDEF] += p0; break; case 203: // ������� if (aRefines != null) aRefines[REFINE_GOLDDEF] += p0; break; case 204: // ����ľ�� if (aRefines != null) aRefines[REFINE_WOODDEF] += p0; break; case 205: // ����ˮ�� if (aRefines != null) aRefines[REFINE_WATERDEF] += p0; break; case 206: // ������� if (aRefines != null) aRefines[REFINE_FIREDEF] += p0; break; case 207: // �������� if (aRefines != null) aRefines[REFINE_EARTHDEF] += p0; break; case 208: // ����HP if (aRefines != null) aRefines[REFINE_HP] += p0; break; case 209: // �������� if (aRefines != null) aRefines[REFINE_DODGE] += p0; break; case 210: // �������з��� if (aRefines != null) { aRefines[REFINE_GOLDDEF] += p0; aRefines[REFINE_WOODDEF] += p0; aRefines[REFINE_WATERDEF] += p0; aRefines[REFINE_FIREDEF] += p0; aRefines[REFINE_EARTHDEF] += p0; } break; case 211: // ���������� & ħ������ if (aRefines != null) { aRefines[REFINE_PHYDAMAGE] += p0; aRefines[REFINE_MAGICDAMAGE] += p0; } break; case 212: // ����������� & ħ������ if (aRefines != null) { aRefines[REFINE_PHYDEF] += p0; aRefines[REFINE_GOLDDEF] += p0; aRefines[REFINE_WOODDEF] += p0; aRefines[REFINE_WATERDEF] += p0; aRefines[REFINE_FIREDEF] += p0; aRefines[REFINE_EARTHDEF] += p0; } break; // �Կ�������� case 120: // �Կ̽�� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_GOLDDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 121: // �Կ�ľ�� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WOODDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 122: // �Կ�ˮ�� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_WATERDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 123: // �Կ̻�� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_FIREDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 124: // �Կ����� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_EARTHDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 125: // �Կ����� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0); break; case 126: // �Կ̶��� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DODGE)); AddDescText(color, true, " %+d", p0); break; case 127: // �Կ�MP AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMP)); AddDescText(color, true, " %+d", p0); break; case 128: // �Կ����� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITY), p0); } break; case 129: // �Կ����� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); } break; case 130: // �Կ����� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); } break; case 131: // �Կ����� if (local) { if (p0 != p1) { AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); AddDescText(color, true, "~~%d", p1); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); } } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); } break; case 132: // �Կ�HP AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP)); AddDescText(color, true, " %+d", p0); break; case 133: // �Կ�������� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); AddDescText(color, true, " %+d", p0); break; case 134: // �Կ����з��� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF)); AddDescText(color, true, " %+d", p0); break; case 135: // �Կ������� AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE)); AddDescText(color, true, " %+d", p0); break; case 136: // �Կ�ħ������ AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE)); AddDescText(color, true, " %+d", p0); break; case 137: // �Կ̽�ϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_GOLDRESIST, p0, local); break; case 138: // �Կ�ľϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_WOODRESIST, p0, local); break; case 139: // �Կ�ˮϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_WATERRESIST, p0, local); break; case 140: // �Կ̻�ϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_FIRERESIST, p0, local); break; case 141: // �Կ���ϵ����(%) AddResistDesc(color, DescriptipionMsg.ITEMDESC_EARTHRESIST, p0, local); break; case 142: // �Կ����м���(%) if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICRESIST), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICRESIST), p0); } break; case 143: // �Կ�����һ���� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), p0); } break; case 144: // �Կ̹����ȼ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); break; case 145: // �Կ̷����ȼ� AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_DEF_DEGREE), p0); break; case 146: // �Կ��������� if (local) { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYRESIST), VisualizeFloatPercent(p0)); } else { AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYRESIST), p0); } break; default: AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ERRORPROP), idProp); break; } } } /// /// Build add-ons properties description /// protected void BuildAddOnPropDesc(int[] aPEEVals, int[] aRefines) { if (Props.Count == 0) return; // Change color m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE); foreach (Property prop in Props) { // Properties added by Embedded stone will be printed by BuildTesseraDesc() later // Ignore suite properties also if (prop.Embed || prop.Suite || prop.Engraved) continue; AddOneAddOnPropDesc(prop.Type, prop.Params, aPEEVals, aRefines, prop.Local); } } /// /// Get soul power added by this equipment /// public int GetSoulPowerAdded() { int added = 0; foreach (Property prop in Props) { if (prop.NumParam > 0) { byte propType = GetPropertyType(prop.Type); if (propType == 63) // Soul power property { added += prop.Params[0]; } } } return added; } /// /// Build tessera description (socketed gems/stones) /// protected void BuildTesseraDesc() { if (Holes.Count == 0) return; int cyanine = (int)DescriptipionMsg.ITEMDESC_COL_CYANINE; for (int i = 0; i < Holes.Count; i++) { if (Holes[i] == 0) continue; // Get item name - would normally use CECIvtrItem::CreateItem string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(Holes[i]); string descText = "null"; // Check if it's a stone and get its description // In C++, this checks if pItem->GetClassID() == ICID_STONE // and then gets weapon_desc or armor_desc based on equipment type // For now, use item name as fallback descText = itemName; AddDescText(cyanine, true, GetItemDescString(DescriptipionMsg.ITEMDESC_2STRINGS), itemName, descText); } } struct SUITEITEM { public bool bEnabled; public int tid; public char[] szName; public string Name => new string(szName); public SUITEITEM(bool bEnabled, int tid) { this.bEnabled = bEnabled; this.tid = tid; this.szName = new char[32]; } } /// /// Add suite description /// protected void AddSuiteDesc() { int idSuite = GetSuiteID(); if (idSuite == 0) return; // This equipment isn't one of any suite // Get suite info DATA_TYPE dataType = DATA_TYPE.DT_INVALID; elementdataman dataMan = EC_Game.GetElementDataMan(); object pData = dataMan.get_data_ptr((uint)idSuite, ID_SPACE.ID_SPACE_ESSENCE, ref dataType); if (dataType != DATA_TYPE.DT_SUITE_ESSENCE) { // ASSERT in C++ return; } SUITE_ESSENCE pSuiteEss = (SUITE_ESSENCE)pData; CECHostPlayer hostPlayer = EC_Game.GetGameRun().GetHostPlayer(); // Colors int iNameCol = DecideNameCol(); int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; int green = (int)DescriptipionMsg.ITEMDESC_COL_GREEN; int gray = (int)DescriptipionMsg.ITEMDESC_COL_GRAY; int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; int yellow = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; // Save current description string strCurDesc = m_strDesc; bool bShowDetail = true; if (hostPlayer.GetEquipment() != m_pDescIvtr) bShowDetail = false; else { for(int i = 0; i < m_pDescIvtr.GetSize(); i++) { EC_IvtrItem pItem = m_pDescIvtr.GetItem(i); if (pItem == null) { bShowDetail = false; continue; } if (pItem.m_tid == this.m_tid) { bShowDetail = true; break; } } } if (!bShowDetail) { m_strDesc = ""; AddDescText(iNameCol, false, "{0} {1}/{2}", pSuiteEss.Name, 0, pSuiteEss.max_equips); m_strDesc = strCurDesc + m_strDesc; return; } // Maximum number of suite items const int MAX_NUM = 12; SUITEITEM[] aSuiteItems = new SUITEITEM[MAX_NUM]; int maxEquips = (pSuiteEss.max_equips > MAX_NUM) ? MAX_NUM : (int)pSuiteEss.max_equips; for(int i = 0; i < maxEquips; i++) { aSuiteItems[i].bEnabled = false; aSuiteItems[i].tid = (int)pSuiteEss.equipments[i].id; aSuiteItems[i].szName = new char[32]; aSuiteItems[i].szName[0] = '\0'; EC_IvtrItem pEquipItem = CreateItem((int)pSuiteEss.equipments[i].id, 0,1); if (pEquipItem != null) { aSuiteItems[i].szName = pEquipItem.GetName().ToCharArray(); //delete pEquipItem; } else { aSuiteItems[i].tid = 0; } } int iItemCnt; int[] aEquipped = new int[MAX_NUM]; iItemCnt = hostPlayer.GetEquippedSuiteItem(idSuite,ref aEquipped); if(iItemCnt == 0) return; //m_strDesc += "\\r\\r"; // Build suite addon properties at first for (int i = 0; i < MAX_NUM; i++) { for(int j = 0; j < iItemCnt; j++) { if (aSuiteItems[i].tid == aEquipped[j]) { aSuiteItems[i].bEnabled = true; break; } } } if(iItemCnt > 1) { // Change color AddDescText(lblue, false, ""); for (int i=1; i < iItemCnt; i++) { int idProp = (int)pSuiteEss.addons[i-1].id; if (idProp == 0) continue; pData = dataMan.get_data_ptr((uint)idProp, ID_SPACE.ID_SPACE_ADDON, ref dataType); if (dataType != DATA_TYPE.DT_EQUIPMENT_ADDON) { continue; } EQUIPMENT_ADDON pAddOn = (EQUIPMENT_ADDON)pData; AddDescText(-1, false, "(%d) ", i+1); AddDescText(-1, true, "%s", pAddOn.Name); } } // Add suite name AddDescText(yellow/*iNameCol*/, true, "{0} ({1} / {2})", pSuiteEss.Name, iItemCnt, pSuiteEss.max_equips); for (int i=0; i < pSuiteEss.max_equips; i++) { SUITEITEM suiteItem = aSuiteItems[i]; if (suiteItem.tid == 0) continue; int col = suiteItem.bEnabled ? green : gray; bool bRet = (i == pSuiteEss.max_equips-1) ? false : true; // Add item name AddDescText(col, bRet, " %s", suiteItem.Name); } } /// /// Add destroying description /// public void AddDestroyingDesc(int dropItemID, int num) { if (!IsDestroying() || dropItemID == 0 || num == 0) return; // Get destroying info DATA_TYPE dataType = DATA_TYPE.DT_INVALID; elementdataman dataMan = EC_Game.GetElementDataMan(); object pData = dataMan.get_data_ptr((uint)dropItemID, ID_SPACE.ID_SPACE_ESSENCE, ref dataType); if (dataType != DATA_TYPE.DT_ELEMENT_ESSENCE) { // ASSERT(0) in C++ return; } // Cast to ELEMENT_ESSENCE to get name // In C#, we'd need to access the name field from the essence data string essenceName = "Repair Item"; // TODO: Get from ELEMENT_ESSENCE.name int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; m_strDesc += "\\r"; AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIP_DESTROYING)); AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIP_REPAIR_NEED_ITEM), essenceName); AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT), (int)Math.Ceiling(num * 1.2)); } /// /// Add reputation requirement description /// protected void AddReputationReqDesc() { if (ReputationReq == 0) return; // Get host player reputation // In C++: CECHostPlayer* pHost = g_pGame->GetGameRun()->GetHostPlayer(); // int col = pHost->GetReputation() >= m_iReputationReq ? ITEMDESC_COL_WHITE : ITEMDESC_COL_RED; int playerReputation = 0; // TODO: Get from host player int col = playerReputation >= ReputationReq ? (int)DescriptipionMsg.ITEMDESC_COL_WHITE : (int)DescriptipionMsg.ITEMDESC_COL_RED; AddDescText(col, true, GetItemDescString(DescriptipionMsg.ITEMDESC_REPUTATION_REQ), ReputationReq); } /// /// Get engraved property number /// public int GetEngravedPropertyNum() { int num = 0; foreach (Property prop in Props) { if (prop.Engraved) num++; } return num; } /// /// Add sharpener description (磨刀石 properties) /// protected void AddSharpenerDesc() { if (Props.Count == 0) return; int color = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; // Check if all sharpener properties have the same expire time bool sameExpireTime = true; bool findFirst = true; int lastExpireTime = 0; foreach (Property prop in Props) { if (prop.Embed || prop.Suite || prop.Engraved) continue; byte propType = GetPropertyType(prop.Type); if (!IsSharpenerProperty(propType)) continue; int p1 = prop.Params.Length > 1 ? prop.Params[1] : 0; if (findFirst) { // Found first sharpener property lastExpireTime = p1; findFirst = false; } else { // Found another sharpener property if (p1 != lastExpireTime) { // Expire times are different sameExpireTime = false; break; } } } if (findFirst) { // Didn't find any sharpener property return; } bool firstProp = true; foreach (Property prop in Props) { if (prop.Embed || prop.Suite || prop.Engraved) continue; byte propType = GetPropertyType(prop.Type); if (!IsSharpenerProperty(propType)) continue; if (firstProp) { m_strDesc += "\\r"; firstProp = false; } // New line m_strDesc += "\\r"; int p0 = prop.Params.Length > 0 ? prop.Params[0] : 0; // First parameter value int p1 = prop.Params.Length > 1 ? prop.Params[1] : 0; // Expire time // Add property description switch (propType) { case 100: // Sharpener physical damage AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE)); AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); break; case 101: // Sharpener max physical damage AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE), p0); break; case 102: // Sharpener magic damage AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE)); AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); break; case 103: // Sharpener max magic damage AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE), p0); break; case 104: // Sharpener physical defence AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); break; case 105: // Sharpener HP AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP)); AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); break; case 106: // Sharpener strength AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); break; case 107: // Sharpener agility AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); break; case 108: // Sharpener energy AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); break; case 109: // Sharpener attack rating AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0); break; case 110: // Sharpener deadly strike if (prop.Local) AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(p0)); else AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), p0); break; case 111: // Sharpener attack degree AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); break; case 112: // Sharpener defence degree AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEF_DEGREE), p0); break; case 113: // Sharpener cast time if (prop.Local) AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -VisualizeFloatPercent(p0)); else AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -p0); break; case 114: // Sharpener magic defence AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF)); AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); break; case 115: // Sharpener ride pet speed AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDRIDEONPETSPEED), IntToFloat(p0)); break; default: // ASSERT(false) in C++ continue; } // If expire times are different, add expire time after each property if (!sameExpireTime) { if (p1 != 0) { m_strDesc += " "; AddExpireTimeDesc(p1); TrimLastReturn(); } } } // If expire times are the same, add expire time at the end if (sameExpireTime) { if (lastExpireTime != 0) { m_strDesc += "\\r"; AddExpireTimeDesc(lastExpireTime); TrimLastReturn(); } } } /// /// Append engraved property descriptions to the current description buffer. /// Mirrors the behaviour of the original C++ AddEngravedDesc. /// protected void AddEngravedDesc() { if (Props.Count == 0) return; // Change color bool firstProp = true; foreach (Property prop in Props) { if (!prop.Engraved) continue; if (firstProp) { firstProp = false; m_strDesc += "\\r"; m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_YELLOW); } AddOneAddOnPropDesc(prop.Type, prop.Params, null, null, prop.Local); } if (!firstProp) { // Trim last return after engraved properties TrimLastReturn(); } } /// /// Append maker description (signature / crafted by) to the description buffer. /// protected void AddMakerDesc() { if (string.IsNullOrEmpty(Maker)) return; m_strDesc += "\\r"; // For signed marks (IMT_SIGN), Maker already contains color codes and formatted text. if (MadeFrom == (byte)ITEM_MAKE_TAG.IMT_SIGN) { m_strDesc += Maker; } else { // Normal "made by" line using item-desc string if available string fmt = GetItemDescString(DescriptipionMsg.ITEMDESC_MADEFROM); if (string.IsNullOrEmpty(fmt)) { fmt = "Made by {0}"; } AddDescText((int)DescriptipionMsg.ITEMDESC_COL_GREEN, false, fmt, Maker); } } /// /// Trim the last '\r' in description string /// private void TrimLastReturn() { int len = m_strDesc.Length; if (len >= 2 && m_strDesc[len - 2] == '\\' && m_strDesc[len - 1] == 'r') { m_strDesc = m_strDesc.Substring(0, len - 2); } } /// /// Add expire time description /// private void AddExpireTimeDesc() { if (ExpireDate == 0) return; int green = (int)DescriptipionMsg.ITEMDESC_COL_GREEN; int yellow = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; int gold = (int)DescriptipionMsg.ITEMDESC_COL_DARKGOLD; int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; // Get server GMT time (this would normally come from EC_Game) long serverTime = 0; // TODO: Get from EC_Game.GetServerGMTTime() long timeLeft = ExpireDate - serverTime; if (timeLeft < 0) timeLeft = 0; if (timeLeft > 24 * 3600) { AddDescText(green, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_DAY), (int)(timeLeft / (24 * 3600)), (int)((timeLeft % (24 * 3600)) / 3600)); } else if (timeLeft > 3600) { AddDescText(yellow, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_HOUR_MIN), (int)(timeLeft / 3600), (int)((timeLeft % 3600) / 60)); } else if (timeLeft > 60) { AddDescText(gold, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_MIN_SEC), (int)(timeLeft / 60), (int)(timeLeft % 60)); } else { AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_SECOND), (int)timeLeft); } } /// /// Add expire time description with specific expire date /// private void AddExpireTimeDesc(int expireDate) { int temp = ExpireDate; ExpireDate = expireDate; AddExpireTimeDesc(); ExpireDate = temp; } /// /// Get preview info /// public virtual string GetPreviewInfo() { m_strDesc = ""; BuildAddOnPropDesc(null, null); return m_strDesc; } /// /// Get add-on property description /// public string GetAddOnPropDesc(int num, int[] addons) { int[] aPEEVals = new int[MAX_PEEINDEX]; int[] aRefines = new int[MAX_REFINEINDEX]; m_strDesc = ""; if (Props.Count == 0) return m_strDesc; // Change color m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE); foreach (Property prop in Props) { if (prop.Embed || prop.Suite || prop.Engraved) continue; for (int j = 0; j < num; j++) { if ((addons[j] & 0x1fff) == prop.Type) { AddOneAddOnPropDesc(prop.Type, prop.Params, aPEEVals, aRefines, prop.Local); break; } } } return m_strDesc; } /// /// Get engrave description /// public string GetEngraveDesc() { int[] aPEEVals = new int[MAX_PEEINDEX]; int[] aRefines = new int[MAX_REFINEINDEX]; m_strDesc = ""; if (Props.Count == 0) return m_strDesc; // Change color m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_YELLOW); foreach (Property prop in Props) { if (prop.Engraved) AddOneAddOnPropDesc(prop.Type, prop.Params, aPEEVals, aRefines, prop.Local); } return m_strDesc; } public struct RefineEffect { int m_refineIndex; public int RefineIndex{ get { return m_refineIndex; } set { m_refineIndex = value; } } int m_incEffect; public int IncEffect{ get { return m_incEffect; } set { m_incEffect = value; } } int[] m_aPEEVals; public int[] APEEVals{ get { return m_aPEEVals; } set { m_aPEEVals = value; } } int[] m_aRefines; public int[] ARefines{ get { return m_aRefines; } set { m_aRefines = value; } } string m_clrAttribute; public string ClrAttribute{ get { return m_clrAttribute; } set { m_clrAttribute = value; } } string m_clrEffect; public string ClrEffect{ get { return m_clrEffect; } set { m_clrEffect = value; } } public RefineEffect(int[] aPEEVals, int[] aRefines, string clrAttribute, string clrEffect) { m_refineIndex = -1; m_incEffect = 0; m_aPEEVals = aPEEVals; m_aRefines = aRefines; m_clrAttribute = clrAttribute; m_clrEffect = clrEffect; } public void Set(int refineIndex, int incEffect){ m_refineIndex = refineIndex; m_incEffect = incEffect; } public int GetIncEffect(){ return m_incEffect; } public string GetClrAttribute(){ return m_clrAttribute; } public string GetClrEffect(){ return m_clrEffect; } }; public virtual bool GetRefineEffectFor(string strEffect, RefineEffect rhs){ return false; } public static int CalcRefineEffect(int refineLevel, int baseEffect) { const int MAX_REFINE_LEVEL = 12; float[] refine_factor = new float[MAX_REFINE_LEVEL + 1] { 0, 1.0f, 2.0f, 3.05f, 4.3f, 5.75f, 7.55f, 9.95f, 13f, 17.05f, 22.3f, 29f, 37.5f }; if (refineLevel >= 0 && refineLevel <= MAX_REFINE_LEVEL){ return (int)(baseEffect * refine_factor[refineLevel] + 0.1f); } return 0; } #endregion } /// /// Equipment addon description class /// public class EC_IvtrEquipAddonDesc { private object m_pAddon; private string m_strDesc = ""; /// /// Set addon /// public bool SetAddon(uint idEquipAddon) { if (idEquipAddon == 0) return false; // This would normally get the addon data from element data manager // For now, return false return false; } } }