using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using ModelRenderer.Scripts.GameData; using ModelRenderer.Scripts.Common; using BrewMonster; using BrewMonster.Common; using BrewMonster.Network; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Reflection; using BrewMonster.Scripts.Managers; using BrewMonster.Scripts; namespace PerfectWorld.Scripts.Managers { /// /// 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 // Item Class IDs public const int ICID_EQUIP = 1; public const int ICID_WEAPON = 2; // Item Made From Types public const byte IMT_NULL = 0; public const byte IMT_SIGN = 1; // 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; // Endurance Scale public const int ENDURANCE_SCALE = 100; #endregion #region Fields // Basic Item Properties public int TemplateId { get; set; } public int ExpireDate { get; set; } public int CID { get; set; } public int Price { get; set; } public int Count { get; set; } public float PriceScale { 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; } // Description protected string m_strDesc = ""; #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; } 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) { TemplateId = tid; ExpireDate = expireDate; CID = ICID_EQUIP; Price = 0; Count = 1; PriceScale = 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; Price = other.Price; Count = other.Count; PriceScale = other.PriceScale; 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 bool SetItemInfo(byte[] infoData, int 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; } Debug.LogError("EC_IvtrEquip::SetItemInfo: could not parse detail payload"); return false; } private bool TryParseEquipInfoNative(byte[] data, int len) { try { int offset = 0; if (len < 6 * 2 + 2 * 4 + 2) return false; 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; // Maker info (type + length + payload) if (offset + 2 <= len) { ReadMakerInfo(data, ref offset); } if (essenceSize < 0 || offset + essenceSize > len) return false; offset += essenceSize; // skip essence for now if (offset + 2 + 2 > len) return false; 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; Holes.Capacity = numHole; 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) { Props.Capacity = numProp; 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; prop.Local = false; 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); } } else if (numProp < 0) { return false; } // Sanity check to catch misalignment if (LevelReq < 0 || LevelReq > 2000) return false; if (MaxEndurance < 0 || MaxEndurance > 1000000) return false; return true; } catch { 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 Price = BitConverter.ToInt32(data, offset); offset += 4; Count = BitConverter.ToInt32(data, offset); offset += 4; PriceScale = 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 /// private void ReadMakerInfo(byte[] data, ref int offset) { MadeFrom = data[offset++]; int makerLen = data[offset++]; if (makerLen > 0) { if (MadeFrom == IMT_SIGN) { ushort color = BitConverter.ToUInt16(data, offset); offset += 2; makerLen -= 2; string maker = System.Text.Encoding.Unicode.GetString(data, offset, makerLen); offset += makerLen; if (string.IsNullOrEmpty(maker)) { Debug.LogWarning($"EC_IvtrEquip::ReadMakerInfo: Invalid maker info with makerLen={makerLen + 2}"); return; } SetNewMark(maker, ColorFromWord(color)); } else { Maker = System.Text.Encoding.Unicode.GetString(data, offset, makerLen); offset += makerLen; } } 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) ? IMT_NULL : IMT_SIGN; } #endregion #region Utility Methods /// /// Get item name /// public 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 + ENDURANCE_SCALE - 1) / 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 int GetScaledPrice() { if (ScaleType != SCALE_SELL) return (int)(Price * Count * PriceScale + 0.5f); int price = Price * Count; if (MaxEndurance == CurEndurance || MaxEndurance == 0) return (int)(price * PriceScale + 0.5f); else return (int)(price * PriceScale * 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 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 object(); private static void EnsurePropMapLoaded() { if (s_propMapLoaded) return; lock (s_propLock) { if (s_propMapLoaded) return; s_propIdToType = new Dictionary(); try { // Parse configs/item_ext_prop.txt to learn valid property type ids string path = Path.Combine(UnityEngine.Application.streamingAssetsPath, "configs/item_ext_prop.txt"); if (File.Exists(path)) { int currentType = -1; bool inTypeBlock = false; bool inBlockComment = false; foreach (var rawLine in File.ReadLines(path)) { 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); } } } } } } catch { /* ignore parse errors; fallback below will handle */ } 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 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 /// public 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 /// private int GetColorStrID(int templateId) { // This would normally query the game's color system return -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. /// public string GetNormalDesc() { // 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; // 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. // 11) Price (sell price scaled) AddPriceDesc(white, false); 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 /// public 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; } #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 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 /// private void AddDescText(int color, bool newLine, string format, params object[] args) { if (color >= 0) { string colorStr = GetColorString((DescriptipionMsg)color); m_strDesc += colorStr; } if (args.Length > 0) { // Accept both C#-style ("{0}") and printf-style ("%d", "%+d", "%.2f", "%%") formats string output; if (format.IndexOf('%') >= 0 && format.IndexOf('{') < 0) { output = FormatPrintfLike(format, args); } else { output = string.Format(format, args); } m_strDesc += output; } else { m_strDesc += format; } if (newLine) m_strDesc += "\\r"; } private static string FormatPrintfLike(string fmt, object[] args) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); int argIndex = 0; for (int i = 0; i < fmt.Length; i++) { char c = fmt[i]; if (c != '%') { sb.Append(c); continue; } if (i + 1 < fmt.Length && fmt[i + 1] == '%') { sb.Append('%'); i += 1; continue; } bool plus = false; int precision = -1; int j = i + 1; if (j < fmt.Length && fmt[j] == '+') { plus = true; j++; } if (j < fmt.Length && fmt[j] == '.') { // precision like %.2f j++; int start = j; while (j < fmt.Length && char.IsDigit(fmt[j])) j++; int.TryParse(fmt.Substring(start, j - start), out precision); } if (j < fmt.Length) { char spec = fmt[j]; if (spec == 'd') { object val = argIndex < args.Length ? args[argIndex++] : 0; long iv = Convert.ToInt64(val); string s = iv.ToString(); if (plus && iv >= 0) s = "+" + s; sb.Append(s); i = j; continue; } else if (spec == 'f') { object val = argIndex < args.Length ? args[argIndex++] : 0.0; double dv = Convert.ToDouble(val); string s = precision >= 0 ? dv.ToString("F" + precision) : dv.ToString("F"); if (plus && dv >= 0) s = "+" + s; sb.Append(s); i = j; continue; } else if (spec == 's') { object val = argIndex < args.Length ? args[argIndex++] : ""; string s = val != null ? val.ToString() : ""; sb.Append(s); i = j; continue; } } // Fallback: treat '%' as literal if format not recognized sb.Append('%'); } return sb.ToString(); } /// /// 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 value / 100; } /// /// 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 /// private 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; int propertyCount = Props.Count; 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) /// private 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); } } /// /// Add suite description /// private 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; } // In C#, we'd need to cast to SUITE_ESSENCE struct // For now, use placeholder values string suiteName = "Suite"; // TODO: Get from SUITE_ESSENCE.name int maxEquips = 12; // TODO: Get from SUITE_ESSENCE.max_equips // Get host player (would normally come from EC_Game.GetGameRun().GetHostPlayer()) // Check if this equipment is in host's equipment pack bool showDetail = false; // TODO: Check if m_pDescIvtr == pHostPlayer->GetEquipment() // Colors int nameCol = DecideNameCol(); int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; int yellow = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; // Save current description string strCurDesc = m_strDesc; if (!showDetail) { // Isn't equipment inventory, only add total suite number info. m_strDesc = "\\r\\r"; AddDescText(nameCol, false, "{0} ({1})", suiteName, maxEquips); m_strDesc = strCurDesc + m_strDesc; return; } // Maximum number of suite items const int MAX_NUM = 12; // Get equipped suite item list int[] aEquipped = new int[MAX_NUM]; int itemCnt = 0; // TODO: Get from pHostPlayer->GetEquippedSuiteItem(idSuite, aEquipped) if (itemCnt == 0) return; m_strDesc = "\\r\\r"; // Build suite addon properties at first if (itemCnt > 1) { // Change color AddDescText(lblue, false, ""); // In C++, this loops through suite addons and displays them // For now, skip detailed addon display } // Add suite name AddDescText(yellow, true, "{0} ({1} / {2})", suiteName, itemCnt, maxEquips); // List suite item names would go here // In C++, this creates SUITEITEM array and lists enabled/disabled items // green, gray, white colors would be used here for enabled/disabled items // For now, simplified version } /// /// 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 /// private 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) /// private 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. /// private 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. /// private 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 == 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 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; } #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; } } }