diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs
new file mode 100644
index 0000000000..05415cde77
--- /dev/null
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs
@@ -0,0 +1,1653 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using ModelRenderer.Scripts.GameData;
+using BrewMonster;
+using BrewMonster.Common;
+using BrewMonster.Network;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Reflection;
+
+namespace PerfectWorld.Scripts.Managers
+{
+ ///
+ /// Equipment item class that handles all equipment-specific functionality
+ /// Converted from C++ EC_IvtrEquip.cpp
+ ///
+ public class EC_IvtrEquip
+ {
+ #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
+ public const int PEEI_PHYDAMAGE = 0;
+ public const int PEEI_MAX_PHYDAMAGE = 1;
+ public const int PEEI_MAGICDAMAGE = 2;
+ public const int PEEI_MAX_MAGICDAMAGE = 3;
+ public const int PEEI_PHYDEF = 4;
+ public const int PEEI_GOLDDEF = 5;
+ public const int PEEI_WOODDEF = 6;
+ public const int PEEI_WATERDEF = 7;
+ public const int PEEI_FIREDEF = 8;
+ public const int PEEI_EARTHDEF = 9;
+ public const int PEEI_ATKDIST = 10;
+ public const int PEEI_HP = 11;
+ public const int PEEI_MP = 12;
+ public const int PEEI_DODGE = 13;
+ public const int MAX_PEEINDEX = 14;
+
+ // 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
+ public const int ITEMDESC_COL_WHITE = 0;
+ public const int ITEMDESC_COL_GREEN = 1;
+ public const int ITEMDESC_COL_YELLOW = 2;
+ public const int ITEMDESC_COL_DARKGOLD = 3;
+ public const int ITEMDESC_COL_LIGHTBLUE = 4;
+ public const int ITEMDESC_COL_CYANINE = 5;
+ public const int ITEMDESC_COL_RED = 6;
+ public const int ITEMDESC_COL_GRAY = 7;
+
+ // Item Description String IDs
+ public const int ITEMDESC_NAME = 1000;
+ public const int ITEMDESC_ADDPHYDAMAGE = 1001;
+ public const int ITEMDESC_MAXPHYDAMAGE = 1002;
+ public const int ITEMDESC_ADDMAGICDAMAGE = 1003;
+ public const int ITEMDESC_MAXMAGICDAMAGE = 1004;
+ public const int ITEMDESC_PHYDEFENCE = 1005;
+ public const int ITEMDESC_ALLMAGICDEF = 1006;
+ public const int ITEMDESC_GOLDDEFENCE = 1007;
+ public const int ITEMDESC_WOODDEFENCE = 1008;
+ public const int ITEMDESC_WATERDEFENCE = 1009;
+ public const int ITEMDESC_FIREDEFENCE = 1010;
+ public const int ITEMDESC_EARTHDEFENCE = 1011;
+ public const int ITEMDESC_ADDHP = 1012;
+ public const int ITEMDESC_ADDMP = 1013;
+ public const int ITEMDESC_STRENGTH = 1014;
+ public const int ITEMDESC_AGILITY = 1015;
+ public const int ITEMDESC_ENERGY = 1016;
+ public const int ITEMDESC_VITALITY = 1017;
+ public const int ITEMDESC_DODGE = 1018;
+ public const int ITEMDESC_DEADLYSTRIKE = 1019;
+ public const int ITEMDESC_ATKRATING = 1020;
+ public const int ITEMDESC_ATKTIME = 1021;
+ public const int ITEMDESC_ADDATKDIST = 1022;
+ public const int ITEMDESC_CASTTIME = 1023;
+ public const int ITEMDESC_ENDURANCE = 1024;
+ public const int ITEMDESC_REPAIRCOST = 1025;
+ public const int ITEMDESC_EQUIPMARK = 1026;
+ public const int ITEMDESC_MADEFROM = 1027;
+ public const int ITEMDESC_2STRINGS = 1028;
+ public const int ITEMDESC_REPUTATION_REQ = 1029;
+ public const int ITEMDESC_EQUIP_DESTROYING = 1030;
+ public const int ITEMDESC_EQUIP_REPAIR_NEED_ITEM = 1031;
+ public const int ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT = 1032;
+ public const int ITEMDESC_ERRORPROP = 1033;
+ public const int ITEMDESC_RANDOMPROP = 1034;
+ public const int ITEMDESC_ATK_DEGREE = 1035;
+ public const int ITEMDESC_DEF_DEGREE = 1036;
+ public const int ITEMDESC_SOULPOWER = 1037;
+ public const int ITEMDESC_VIGOUR = 1038;
+ public const int ITEMDESC_ADDRIDEONPETSPEED = 1039;
+ public const int ITEMDESC_PENETRATION = 1040;
+ public const int ITEMDESC_RESILIENCE = 1041;
+ public const int ITEMDESC_PROFVIEW = 1042;
+ public const int ITEMDESC_REQEXTRA = 1043;
+
+ // 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 string GetBaseStatsDesc()
+ {
+ elementdataman edm = ElementDataManProvider.GetElementDataMan();
+ if (edm == null) return string.Empty;
+
+ 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 string.Empty;
+
+ // Try weapon-style fields first
+ int dmgLow = 0, dmgHighMax = 0;
+ bool hasDmgLow = TryGetNumber(data, new[] { "damage_low" }, out dmgLow);
+ bool hasDmgHighMax = TryGetNumber(data, new[] { "damage_high_max", "damage_high" }, out dmgHighMax);
+
+ List lines = new List();
+
+ if (hasDmgLow && hasDmgHighMax)
+ {
+ // Physical damage range
+ lines.Add(string.Format("{0} {1}-{2}", GetItemDescString(ITEMDESC_ADDPHYDAMAGE), dmgLow, dmgHighMax));
+
+ // 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))
+ {
+ lines.Add(string.Format("{0} {1}-{2}", GetItemDescString(ITEMDESC_ADDMAGICDAMAGE), mdLow, mdHigh));
+ }
+
+ // Attack time/speed
+ float atkTime;
+ if (TryGetNumber(data, new[] { "attack_time", "atk_time" }, out atkTime) && atkTime > 0.0001f)
+ {
+ // Show speed in attacks per second (1 / time)
+ float aps = 1.0f / atkTime;
+ lines.Add(string.Format("{0} {1:F2}", GetItemDescString(ITEMDESC_ATKTIME), aps));
+ }
+ else if (TryGetNumber(data, new[] { "attack_speed", "atk_speed" }, out float atkSpeed) && atkSpeed > 0)
+ {
+ lines.Add(string.Format("{0} {1:F2}", GetItemDescString(ITEMDESC_ATKTIME), atkSpeed));
+ }
+
+ // Range
+ float range;
+ if (TryGetNumber(data, new[] { "range", "attack_range", "atk_range" }, out range))
+ {
+ lines.Add(string.Format("{0} {1:F2}", GetItemDescString(ITEMDESC_ADDATKDIST), range));
+ }
+ }
+ else
+ {
+ // Try armor-style fields
+ if (TryGetNumber(data, new[] { "defence_high", "defense_high", "defence", "defense" }, out int defHigh))
+ {
+ lines.Add(string.Format("{0} +{1}", GetItemDescString(ITEMDESC_PHYDEFENCE), defHigh));
+ }
+
+ // Elemental resistances (look for *_resist or specific names)
+ (string label, string[] names)[] res = new (string, string[])[]
+ {
+ (GetItemDescString(ITEMDESC_GOLDDEFENCE), new[]{"resist_metal","resistance_metal","gold_resist"}),
+ (GetItemDescString(ITEMDESC_WOODDEFENCE), new[]{"resist_wood","resistance_wood","wood_resist"}),
+ (GetItemDescString(ITEMDESC_WATERDEFENCE), new[]{"resist_water","resistance_water","water_resist"}),
+ (GetItemDescString(ITEMDESC_FIREDEFENCE), new[]{"resist_fire","resistance_fire","fire_resist"}),
+ (GetItemDescString(ITEMDESC_EARTHDEFENCE), new[]{"resist_earth","resistance_earth","earth_resist"})
+ };
+ for (int i = 0; i < res.Length; i++)
+ {
+ var label = res[i].label;
+ var names = res[i].names;
+ if (TryGetNumber(data, names, out int v))
+ {
+ lines.Add(string.Format("{0} +{1}", label, v));
+ }
+ }
+ }
+
+ return string.Join("\\r", lines);
+ }
+ catch { return string.Empty; }
+ }
+
+ 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
+ Maker = EC_GameUIMan.AUI_ConvertHintString(Maker);
+
+ // Filter bad words
+ // g_pGame->GetGameRun()->GetUIManager()->FilterBadWords(m_strMaker);
+
+ // Add color
+ string colorStr = $"^FF{color.r:X2}{color.g:X2}{color.b:X2}";
+ Maker = colorStr + Maker;
+
+ // Add equipment mark display string
+ Maker = string.Format(GetItemDescString(ITEMDESC_EQUIPMARK), Maker);
+ }
+ MadeFrom = string.IsNullOrEmpty(mark) ? IMT_NULL : IMT_SIGN;
+ }
+
+ #endregion
+
+ #region Utility Methods
+
+ ///
+ /// Get item name
+ ///
+ public string GetName()
+ {
+ return EC_IvtrItem.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 3: // 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 12: // 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 17: // Wood defense
+ flags |= PEE_WOODDEF;
+ break;
+ case 19: // Water defense
+ flags |= PEE_WATERDEF;
+ break;
+ case 21: // Fire defense
+ flags |= PEE_FIREDEF;
+ break;
+ case 23: // 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 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;
+ }
+
+ 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))
+ {
+ foreach (var rawLine in File.ReadLines(path))
+ {
+ string line = rawLine.Trim();
+ if (line.Length == 0) continue;
+ if (line.StartsWith("//") || line.StartsWith("#")) continue;
+
+ // Match forms like: 12: or 12\t
+ var m = Regex.Match(line, "^(?\\d{1,4})\\s*[:\\t\\s]");
+ if (m.Success)
+ {
+ if (int.TryParse(m.Groups["id"].Value, out int id) && id >= 0 && id <= 255)
+ {
+ // Treat the id as both propId and normalized type for lack of a separate map.
+ if (!s_propIdToType.ContainsKey(id))
+ s_propIdToType[id] = (byte)id;
+ }
+ }
+ }
+ }
+ }
+ 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 (propId >= 0 && propId <= 255)
+ {
+ if (s_propIdToType != null && s_propIdToType.TryGetValue(propId, out byte t))
+ return t;
+ return (byte)propId; // identity mapping for common types listed 0..255
+ }
+
+ // Refine codes and other extended ranges from original client
+ if (propId >= 200 && propId <= 212) return (byte)propId; // refine buckets
+ if (propId >= 100 && propId <= 115) return (byte)propId; // sharpener/stone buckets
+
+ // 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 = ITEMDESC_COL_WHITE;
+
+ switch (FixProps)
+ {
+ case 1: col = ITEMDESC_COL_GREEN; break;
+ case 2: col = ITEMDESC_COL_YELLOW; break;
+ case 3: col = ITEMDESC_COL_DARKGOLD; break;
+ default:
+ if (PropNum > 0)
+ col = 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
+
+ ///
+ /// Get item description for booth buying
+ ///
+ public string GetBoothBuyDesc()
+ {
+ m_strDesc = "";
+
+ int white = ITEMDESC_COL_WHITE;
+
+ // Item name
+ AddDescText(white, true, GetItemDescString(ITEMDESC_NAME), GetName());
+
+ // Base stats from element data
+ string baseStats = GetBaseStatsDesc();
+ if (!string.IsNullOrEmpty(baseStats))
+ {
+ m_strDesc += GetColorString(ITEMDESC_COL_WHITE);
+ m_strDesc += baseStats;
+ m_strDesc += "\\r";
+ }
+
+ // Price
+ AddPriceDesc(white, false);
+
+ return m_strDesc;
+ }
+
+ ///
+ /// Add price description
+ ///
+ public void AddPriceDesc(int col, bool repair)
+ {
+ if (repair)
+ AddDescText(col, false, GetItemDescString(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
+ ///
+ public string FormatRefineData(uint addonId)
+ {
+ // This would normally query the equipment addon data
+ // For now, return empty string
+ return "";
+ }
+
+ ///
+ /// 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;
+
+ int level = 0;
+
+ foreach (Property prop in Props)
+ {
+ 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
+ ///
+ private string GetItemDescString(int id)
+ {
+ var tab = EC_Game.GetItemDesc();
+ if (tab != null && tab.IsInitialized())
+ {
+ string s = tab.GetString(id);
+ if (!string.IsNullOrEmpty(s)) return s;
+ }
+ // Fallback labels for common IDs when the table lacks #_index entries
+ switch (id)
+ {
+ case ITEMDESC_ADDPHYDAMAGE: return "Công vật lý";
+ case ITEMDESC_ADDMAGICDAMAGE: return "TC phép thuật";
+ case ITEMDESC_PHYDEFENCE: return "Thủ vật lý";
+ case ITEMDESC_GOLDDEFENCE: return "Kháng Kim";
+ case ITEMDESC_WOODDEFENCE: return "Kháng Mộc";
+ case ITEMDESC_WATERDEFENCE: return "Kháng Thủy";
+ case ITEMDESC_FIREDEFENCE: return "Kháng Hỏa";
+ case ITEMDESC_EARTHDEFENCE: return "Kháng Thổ";
+ case ITEMDESC_ATKTIME: return "Tốc độ TC(lần/giây)";
+ case ITEMDESC_ADDATKDIST: return "Phạm vi tấn công";
+ case ITEMDESC_NAME: return "Tên";
+ default:
+ return $"String_{id}";
+ }
+ }
+
+ ///
+ /// Add description text
+ ///
+ private void AddDescText(int color, bool newLine, string format, params object[] args)
+ {
+ if (color >= 0)
+ {
+ // Add color formatting
+ string colorStr = GetColorString(color);
+ m_strDesc += colorStr;
+ }
+
+ if (args.Length > 0)
+ m_strDesc += string.Format(format, args);
+ else
+ m_strDesc += format;
+
+ if (newLine)
+ m_strDesc += "\\r";
+ }
+
+ ///
+ /// Get color string for color ID
+ ///
+ private string GetColorString(int colorId)
+ {
+ switch (colorId)
+ {
+ case ITEMDESC_COL_WHITE: return "^FF";
+ case ITEMDESC_COL_GREEN: return "^GF";
+ case ITEMDESC_COL_YELLOW: return "^YF";
+ case ITEMDESC_COL_DARKGOLD: return "^DF";
+ case ITEMDESC_COL_LIGHTBLUE: return "^BF";
+ case ITEMDESC_COL_CYANINE: return "^CF";
+ case ITEMDESC_COL_RED: return "^RF";
+ case ITEMDESC_COL_GRAY: return "^GF";
+ default: return "^FF";
+ }
+ }
+
+ ///
+ /// 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;
+ }
+
+ ///
+ /// Add one add-on property description
+ ///
+ private void AddOneAddOnPropDesc(int idProp, int[] param, int[] aPEEVals, int[] aRefines, bool local)
+ {
+ // This is a simplified version - the full implementation would handle all property types
+ byte propType = GetPropertyType(idProp);
+
+ if (!IsSharpenerProperty(propType))
+ {
+ switch (propType)
+ {
+ case 0: // Physical damage
+ if (!local && aPEEVals != null)
+ aPEEVals[PEEI_PHYDAMAGE] += param[0];
+ AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDPHYDAMAGE));
+ AddDescText(-1, true, " %+d", param[0]);
+ break;
+ case 3: // Magic damage
+ if (!local && aPEEVals != null)
+ aPEEVals[PEEI_MAGICDAMAGE] += param[0];
+ AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMAGICDAMAGE));
+ AddDescText(-1, true, " %+d", param[0]);
+ break;
+ case 12: // Physical defense
+ if (!local && aPEEVals != null)
+ aPEEVals[PEEI_PHYDEF] += param[0];
+ AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE));
+ AddDescText(-1, true, " %+d", param[0]);
+ break;
+ case 35: // HP
+ if (!local && aPEEVals != null)
+ aPEEVals[PEEI_HP] += param[0];
+ AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP));
+ AddDescText(-1, true, " %+d", param[0]);
+ break;
+ case 36: // MP
+ if (!local && aPEEVals != null)
+ aPEEVals[PEEI_MP] += param[0];
+ AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMP));
+ AddDescText(-1, true, " %+d", param[0]);
+ break;
+ case 50: // Dodge
+ if (!local && aPEEVals != null)
+ aPEEVals[PEEI_DODGE] += param[0];
+ AddDescText(-1, false, GetItemDescString(ITEMDESC_DODGE));
+ AddDescText(-1, true, " %+d", param[0]);
+ break;
+ default:
+ AddDescText(-1, true, GetItemDescString(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(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
+ ///
+ private void BuildTesseraDesc()
+ {
+ if (Holes.Count == 0)
+ return;
+
+ int cyanine = ITEMDESC_COL_CYANINE;
+
+ foreach (int hole in Holes)
+ {
+ if (hole == 0)
+ continue;
+
+ // Get item name
+ string itemName = EC_IvtrItem.ResolveItemName(hole);
+ string descText = "null";
+
+ // This would normally check if it's a stone and get its description
+ // For now, use the item name
+ descText = itemName;
+
+ AddDescText(cyanine, true, GetItemDescString(ITEMDESC_2STRINGS), itemName, descText);
+ }
+ }
+
+ ///
+ /// Add suite description
+ ///
+ public void AddSuiteDesc()
+ {
+ int idSuite = GetSuiteID();
+ if (idSuite == 0)
+ return; // This equipment isn't one of any suite
+
+ // This would normally build the suite description
+ // For now, just add a placeholder
+ m_strDesc += "\\r\\r";
+ AddDescText(ITEMDESC_COL_YELLOW, true, "Suite Equipment");
+ }
+
+ ///
+ /// Add destroying description
+ ///
+ public void AddDestroyingDesc(int dropItemID, int num)
+ {
+ if (!IsDestroying() || dropItemID == 0 || num == 0)
+ return;
+
+ // This would normally get the destroying info from element data
+ int red = ITEMDESC_COL_RED;
+
+ m_strDesc += "\\r";
+ AddDescText(red, true, GetItemDescString(ITEMDESC_EQUIP_DESTROYING));
+
+ // This would normally get the item name from element data
+ AddDescText(red, true, GetItemDescString(ITEMDESC_EQUIP_REPAIR_NEED_ITEM), "Repair Item");
+ AddDescText(red, true, GetItemDescString(ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT), (int)(Math.Ceiling(num * 1.2)));
+ }
+
+ ///
+ /// Add reputation requirement description
+ ///
+ public void AddReputationReqDesc()
+ {
+ if (ReputationReq > 0)
+ {
+ // This would normally check the host player's reputation
+ int col = ITEMDESC_COL_WHITE; // Default to white, would check against player reputation
+ AddDescText(col, true, GetItemDescString(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;
+ }
+
+ ///
+ /// 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(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(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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs.meta b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs.meta
new file mode 100644
index 0000000000..71ac740d78
--- /dev/null
+++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 50210c0839c503b42843db0237a9c3a8
\ No newline at end of file