Files
test/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem/EC_IvtrEquip.cs
T
2026-02-28 18:34:39 +07:00

4643 lines
192 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AddressableAssets;
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
{
/// <summary>
/// Equipment item class that handles all equipment-specific functionality
/// Converted from C++ EC_IvtrEquip.cpp
/// </summary>
public class EC_IvtrEquip : EC_IvtrItem
{
#region Constants and Enums
public enum EQUIP_CLASS_ID
{
ICID_ITEM = -100,
ICID_EQUIP = -101,
ICID_ARMOR = 0,
ICID_ARMORRUNE,
ICID_ARROW,
ICID_DECORATION,
ICID_DMGRUNE,
ICID_ELEMENT,
ICID_FASHION,
ICID_FLYSWORD,
ICID_MATERIAL,
ICID_MEDICINE,
ICID_REVSCROLL,
ICID_SKILLTOME,
ICID_TOSSMAT,
ICID_TOWNSCROLL,
ICID_UNIONSCROLL,
ICID_WEAPON,
ICID_TASKITEM,
ICID_STONE,
ICID_WING,
ICID_TASKDICE,
ICID_TASKNMMATTER,
ICID_ERRORITEM,
ICID_FACETICKET,
ICID_FACEPILL,
ICID_GM_GENERATOR,
ICID_RECIPE,
ICID_PETEGG,
ICID_PETFOOD,
ICID_PETFACETICKET,
ICID_FIREWORK,
ICID_TANKCALLIN,
ICID_SKILLMATTER,
ICID_REFINETICKET,
ICID_DESTROYINGESSENCE,
ICID_BIBLE,
ICID_SPEAKER,
ICID_AUTOHP,
ICID_AUTOMP,
ICID_DOUBLEEXP,
ICID_TRANSMITSCROLL,
ICID_DYETICKET,
ICID_GOBLIN,
ICID_GOBLIN_EQUIP,
ICID_GOBLIN_EXPPILL,
ICID_CERTIFICATE,
ICID_TARGETITEM,
ICID_LOOKINFOITEM,
ICID_INCSKILLABILITY,
ICID_WEDDINGBOOKCARD,
ICID_WEDDINGINVITECARD,
ICID_SHARPENER,
ICID_FACTIONMATERIAL,
ICID_CONGREGATE,
ICID_FORCETOKEN,
ICID_DYNSKILLEQUIP,
ICID_MONEYCONVERTIBLE,
ICID_MONSTERSPIRIT,
ICID_GENERALCARD,
ICID_GENERALCARD_DICE,
ICID_SHOPTOKEN,
ICID_UNIVERSAL_TOKEN,
}
// Item Made From Types
public enum ITEM_MAKE_TAG
{
IMT_NULL,
IMT_CREATE, // GM
IMT_DROP, //
IMT_SHOP, // ̳ǻ̵
IMT_PRODUCE, //
IMT_SIGN, // װǩ
};
// Property Effect Essence Flags
public const uint PEE_PHYDAMAGE = 0x00000001;
public const uint PEE_MAGICDAMAGE = 0x00000002;
public const uint PEE_PHYDEF = 0x00000004;
public const uint PEE_GOLDDEF = 0x00000008;
public const uint PEE_WOODDEF = 0x00000010;
public const uint PEE_WATERDEF = 0x00000020;
public const uint PEE_FIREDEF = 0x00000040;
public const uint PEE_EARTHDEF = 0x00000080;
public const uint PEE_ATKSPEED = 0x00000100;
public const uint PEE_ATKDIST = 0x00000200;
public const uint PEE_HP = 0x00000400;
public const uint PEE_MP = 0x00000800;
public const uint PEE_DODGE = 0x00001000;
public const uint PEE_ENDURANCE = 0x00002000;
public const uint PEE_STRENGTHREQ = 0x00004000;
public const uint PEE_AGILITYREQ = 0x00008000;
public const uint PEE_ENERGYREQ = 0x00010000;
public const uint PEE_VITALITYREQ = 0x00020000;
// Property Effect Essence Indexes (must match C++ enum order)
public const int PEEI_PHYDAMAGE = 0;
public const int PEEI_PHYDEF = 1;
public const int PEEI_MAGICDAMAGE = 2;
public const int PEEI_GOLDDEF = 3;
public const int PEEI_WOODDEF = 4;
public const int PEEI_WATERDEF = 5;
public const int PEEI_FIREDEF = 6;
public const int PEEI_EARTHDEF = 7;
public const int PEEI_HP = 8;
public const int PEEI_MP = 9;
public const int PEEI_ENDURANCE = 10;
public const int PEEI_ATKDIST = 11;
public const int PEEI_STRENGTHREQ = 12;
public const int PEEI_AGILITYREQ = 13;
public const int PEEI_ATKSPEED = 14;
public const int PEEI_MAX_PHYDAMAGE = 15;
public const int PEEI_MAX_MAGICDAMAGE = 16;
public const int PEEI_DODGE = 17;
public const int MAX_PEEINDEX = 18;
// Refine Indexes
public const int REFINE_PHYDAMAGE = 0;
public const int REFINE_MAGICDAMAGE = 1;
public const int REFINE_PHYDEF = 2;
public const int REFINE_GOLDDEF = 3;
public const int REFINE_WOODDEF = 4;
public const int REFINE_WATERDEF = 5;
public const int REFINE_FIREDEF = 6;
public const int REFINE_EARTHDEF = 7;
public const int REFINE_HP = 8;
public const int REFINE_DODGE = 9;
public const int MAX_REFINEINDEX = 10;
// Item Description Colors and String IDs are now in DescriptipionMsg enum from EC_FixedMsg.cs
// Scale Types
public const int SCALE_SELL = 1;
// Endurance Scale
public const int ENDURANCE_SCALE = 100;
#endregion
#region Public Fields
// Basic Item Properties
public int TemplateId { get; set; }
public int ExpireDate { get; set; }
/// <summary>
/// class id
/// </summary>
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<int> Holes { get; set; }
public List<Property> Props { get; set; }
#endregion
#region Base Stats (from Element Data)
private static bool TryGetNumber<T>(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<int>(data, new[] { "damage_low" }, out dmgLow);
bool hasDmgHigh = TryGetNumber<int>(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<int>(data, new[] { "magic_damage_low" }, out int mdLow) &&
TryGetNumber<int>(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<float>(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<float>(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<int>(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<int>(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<int>(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<int>(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<int>(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) : base(tid, expireDate)
{
TemplateId = tid;
ExpireDate = expireDate;
CID = (int)InventoryClassId.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<int>();
Props = new List<Property>();
}
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<int>(other.Holes);
Props = new List<Property>(other.Props.Select(p => new Property(p)));
}
#endregion
#region Core Methods
/// <summary>
/// Set item detail information from binary data
/// </summary>
public override bool SetItemInfo(byte[] infoData, int dataLen)
{
base.SetItemInfo(infoData, dataLen);
if (infoData == null || dataLen == 0)
return true;
// Try native order (as original client):
// [6 x short requirements][2 x int endurance][short essenceSize][maker info][essence bytes][short numHole][WORD stoneMask][numHole x int holes][int numProp][props]
if (TryParseEquipInfoNative(infoData, dataLen))
{
ParseProperties();
return true;
}
// Fallback to legacy/custom order if server payload differs
if (TryParseEquipInfoLegacy(infoData, dataLen))
{
ParseProperties();
return true;
}
return false;
}
private bool TryParseEquipInfoNative(byte[] data, int len)
{
try
{
CECDataReader dr = new CECDataReader(data, len);
LevelReq = dr.ReadShort();
ProfReq = dr.ReadShort();
StrengthReq= dr.ReadShort();
VitalityReq= dr.ReadShort();
AgilityReq = dr.ReadShort();
EnergyReq = dr.ReadShort();
CurEndurance = dr.ReadInt();
MaxEndurance = dr.ReadInt();
int essenceSize = dr.ReadShort();
ReadMakerInfo(dr);
dr.Offset(essenceSize, CECDataReader.SEEK_CUR);
if(essenceSize < 0 )
{
throw new Exception("TYPE_DATAERR");
}
int numHole = dr.ReadShort();
StoneMask = (ushort)dr.ReadShort();
if (numHole > 0)
{
Holes.Clear();
Holes.Capacity = numHole;
for (int i = 0; i < numHole; i++)
{
Holes.Add(dr.ReadInt());
}
}
else if (numHole == 0)
{
Holes.Clear();
}
else
{
throw new Exception("TYPE_DATAERR");
}
int numProp = dr.ReadInt();
if (numProp > 0)
{
Props.Clear();
Props.Capacity = numProp;
for (int i = 0; i < numProp; i++)
{
int type = dr.ReadInt();
Property prop = new Property();
prop.Type = type & 0x1fff;
prop.NumParam = (type & 0x6000) >> 13;
prop.Embed = (type & 0x8000) != 0;
prop.Suite = (type & 0x10000) != 0;
prop.Engraved = (type & 0x20000) != 0;
prop.Local = false;
for (int j = 0; j < prop.NumParam; j++)
{
prop.Params[j] = dr.ReadInt();
}
Props.Add(prop);
}
}
else if (numProp == 0)
{
Props.Clear();
}
else
{
throw new Exception("TYPE_DATAERR");
}
// Sanity check to catch misalignment
if (LevelReq < 0 || LevelReq > 2000) return false;
if (MaxEndurance < 0 || MaxEndurance > 1000000) return false;
return true;
}
catch (System.Exception ex) {
BMLogger.LogError("CECIvtrEquip::SetItemInfo, data read error (" + ex.GetType() + ")" + ex.StackTrace);
return false;
}
}
private bool TryParseEquipInfoLegacy(byte[] data, int len)
{
try
{
int offset = 0;
if (len < 16 + 6 * 2 + 8 + 2) return false;
// A legacy format we used earlier that prefixed price/scale before requirements
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; }
}
/// <summary>
/// Read maker information from binary data
/// </summary>
protected void ReadMakerInfo(CECDataReader dr)
{
// Debug: Log the bytes at current position before reading
// We need to check what bytes are actually at the reader position
MadeFrom = dr.ReadByte();
int makerLen = dr.ReadByte();
if (makerLen > 0)
{
if (MadeFrom == (byte)ITEM_MAKE_TAG.IMT_SIGN)
{
ushort color = (ushort)dr.ReadShort();
makerLen -= sizeof(ushort);
byte[] makerData = dr.ReadData(makerLen);
// Find null terminator (0x00 0x00 for Unicode) and decode only up to that point
int actualLength = makerLen;
for (int i = 0; i < makerLen - 1; i += 2)
{
if (makerData[i] == 0 && makerData[i + 1] == 0)
{
actualLength = i;
break;
}
}
string maker = System.Text.Encoding.Unicode.GetString(makerData, 0, actualLength).TrimEnd('\0');
if (string.IsNullOrEmpty(maker))
{
return;
}
//#define FASHION_WORDCOLOR_TO_A3DCOLOR(c) A3DCOLORRGB(((c) & (0x1f << 10)) >> 7, ((c) & (0x1f << 5)) >> 2, ((c) & 0x1f) << 3)
//A3DCOLOR clr = FASHION_WORDCOLOR_TO_A3DCOLOR(color);
Color clr = ColorFromWord(color);
SetNewMark(maker, clr);
}
else
{
//m_strMaker = ACString((ACHAR*)dr.Read_Data(iMakerLen), iMakerLen / sizeof (ACHAR));
byte[] makerData = dr.ReadData(makerLen);
// Find null terminator (0x00 0x00 for Unicode) and decode only up to that point
int actualLength = makerLen;
for (int i = 0; i < makerLen - 1; i += 2)
{
if (makerData[i] == 0 && makerData[i + 1] == 0)
{
actualLength = i;
break;
}
}
Maker = System.Text.Encoding.Unicode.GetString(makerData, 0, actualLength).TrimEnd('\0');
}
}
else
{
Maker = "";
}
}
/// <summary>
/// Set new mark with color
/// </summary>
public void SetNewMark(string mark, Color color)
{
Maker = mark;
if (!string.IsNullOrEmpty(Maker))
{
// Convert hint string
// Filter bad words
// g_pGame->GetGameRun()->GetUIManager()->FilterBadWords(m_strMaker);
// Add color using ^RRGGBB from Color32 bytes
Color32 c32 = color;
string colorStr = $"^{c32.r:X2}{c32.g:X2}{c32.b:X2}";
Maker = colorStr + Maker;
// Add equipment mark display string
Maker = string.Format(GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIPMARK), Maker);
}
MadeFrom = string.IsNullOrEmpty(mark) ? (byte)ITEM_MAKE_TAG.IMT_NULL : (byte)ITEM_MAKE_TAG.IMT_SIGN;
}
#endregion
#region Utility Methods
/// <summary>
/// Get item name
/// </summary>
public virtual string GetName()
{
return EC_IvtrItemUtils.Instance.ResolveItemName(TemplateId);
}
/// <summary>
/// Add current endurance
/// </summary>
public int AddCurEndurance(int value)
{
CurEndurance += value;
CurEndurance = Mathf.Clamp(CurEndurance, 0, MaxEndurance);
return CurEndurance;
}
/// <summary>
/// Convert endurance real value to displaying value
/// </summary>
public static int VisualizeEndurance(int v)
{
return (v + ENDURANCE_SCALE - 1) / ENDURANCE_SCALE;
}
/// <summary>
/// Get empty hole number
/// </summary>
public int GetEmptyHoleNum()
{
int count = 0;
foreach (int hole in Holes)
{
if (hole == 0)
count++;
}
return count;
}
/// <summary>
/// Get repair cost
/// </summary>
public int GetRepairCost()
{
if (MaxEndurance == 0 || MaxEndurance == CurEndurance)
return 0;
int cost = (int)GetRawRepairCost();
if (cost < 1)
cost = 1;
return cost;
}
/// <summary>
/// Get raw repair cost
/// </summary>
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;
}
/// <summary>
/// Get scaled item price
/// </summary>
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);
}
/// <summary>
/// Check if item is repairable
/// </summary>
public bool IsRepairable()
{
return MaxEndurance > 0;
}
/// <summary>
/// Check if item is destroying
/// </summary>
public bool IsDestroying()
{
return CurEndurance == 0 && MaxEndurance > 0;
}
/// <summary>
/// Check if item is rare
/// </summary>
public virtual bool IsRare()
{
return RefineLvl >= 3;
}
#endregion
#region Property System
/// <summary>
/// Get property effect essence flags
/// </summary>
public uint PropEffectEssence()
{
uint flags = 0;
foreach (Property prop in Props)
{
flags = PropEffectMask(prop, flags);
}
return flags;
}
/// <summary>
/// Get property effect mask for a specific property
/// </summary>
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;
}
/// <summary>
/// Get property type from property ID
/// </summary>
private static Dictionary<int, byte> 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<int, byte>();
try
{
// Load item_ext_prop.txt from Addressables
// Address must match the Addressables "Address" configured in Assets/AddressableAssetsData/...
const string address = "Assets/Addressable/item_ext_prop.txt";
// Initialize Addressables if not already initialized
Addressables.InitializeAsync().WaitForCompletion();
// Load the TextAsset synchronously (since GetPropertyType is called synchronously)
var textAsset = Addressables.LoadAssetAsync<TextAsset>(address).WaitForCompletion();
if (textAsset != null && !string.IsNullOrEmpty(textAsset.text))
{
// Parse the text content
int currentType = -1;
bool inTypeBlock = false;
bool inBlockComment = false;
using (var reader = new StringReader(textAsset.text))
{
string rawLine;
while ((rawLine = reader.ReadLine()) != null)
{
string src = rawLine;
if (string.IsNullOrEmpty(src)) continue;
// strip block comments /* ... */ possibly spanning lines
System.Text.StringBuilder sb = new System.Text.StringBuilder();
int i = 0;
while (i < src.Length)
{
if (inBlockComment)
{
int end = src.IndexOf("*/", i, StringComparison.Ordinal);
if (end < 0) { i = src.Length; break; }
inBlockComment = false; i = end + 2; continue;
}
int start = src.IndexOf("/*", i, StringComparison.Ordinal);
if (start < 0)
{
sb.Append(src, i, src.Length - i);
break;
}
sb.Append(src, i, start - i);
int end2 = src.IndexOf("*/", start + 2, StringComparison.Ordinal);
if (end2 < 0) { inBlockComment = true; break; }
i = end2 + 2;
}
string line = sb.ToString().Trim();
if (line.Length == 0) continue;
if (line.StartsWith("//")) continue;
// Detect a new type section: e.g., "type: 45"
var typeMatch = Regex.Match(line, "^type:\\s*(?<type>\\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(?<id>\\d{1,6})\\b"))
{
if (int.TryParse(m.Groups["id"].Value, out int id))
{
if (!s_propIdToType.ContainsKey(id)) s_propIdToType[id] = (byte)Mathf.Clamp(currentType, 0, 255);
}
}
}
}
}
}
else
{
Debug.LogWarning($"[EC_IvtrEquip] Failed to load item_ext_prop.txt from Addressables (address: {address})");
}
}
catch (Exception ex)
{
Debug.LogError($"[EC_IvtrEquip] Exception loading item_ext_prop.txt from Addressables: {ex.Message}");
// ignore parse errors; fallback below will handle unknown properties
}
s_propMapLoaded = true;
}
}
private byte GetPropertyType(int propId)
{
// Load once from item_ext_prop.txt; if not enough info, fall back to heuristics
EnsurePropMapLoaded();
if (s_propIdToType != null && s_propIdToType.TryGetValue(propId, out byte mapped))
return mapped;
// Unknown
return 0xff;
}
/// <summary>
/// Set properties to local
/// </summary>
public void SetLocalProps()
{
if (Props.Count == 0)
return;
foreach (Property prop in Props)
{
prop.Local = true;
}
}
/// <summary>
/// Get deadly strike rate provided by this equipment
/// </summary>
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;
}
/// <summary>
/// Decide equipment name color
/// </summary>
protected override int DecideNameCol()
{
int index = GetColorStrID(TemplateId);
if (index >= 0)
return index;
int col = (int)DescriptipionMsg.ITEMDESC_COL_WHITE;
switch (FixProps)
{
case 1: col = (int)DescriptipionMsg.ITEMDESC_COL_GREEN; break;
case 2: col = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; break;
case 3: col = (int)DescriptipionMsg.ITEMDESC_COL_DARKGOLD; break;
default:
if (PropNum > 0)
col = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE;
break;
}
return col;
}
/// <summary>
/// Get color string ID for template
/// </summary>
public override int GetColorStrID(int templateId)
{
// This would normally query the game's color system
int iIndex = EC_Game.GetItemNameColorIdx(templateId);
if (iIndex <= 0)
return -1;
else if (iIndex < 7)
return (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE + iIndex - 1;
else
return (int)DescriptipionMsg.ITEMDESC_COL2_START + iIndex - 7 + 1;
}
#endregion
#region Description Methods (high level entry points)
/// <summary>
/// Get normal in-inventory description, mirroring C++ CECIvtrEquip::GetNormalDesc.
/// This is a single formatted string using ^color codes and '\\r' as line separators.
/// </summary>
protected override string GetNormalDesc(bool bRepair)
{
// Build addon and refine properties and save it (like C++ does first)
int[] aPEEVals = new int[MAX_PEEINDEX];
int[] aRefines = new int[MAX_REFINEINDEX];
for (int i = 0; i < MAX_PEEINDEX; i++)
aPEEVals[i] = 0;
for (int i = 0; i < MAX_REFINEINDEX; i++)
aRefines[i] = 0;
m_strDesc = "";
BuildAddOnPropDesc(aPEEVals, aRefines);
string strAddon = m_strDesc;
// Reset and build description from scratch
m_strDesc = "";
int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE;
int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE;
int red = (int)DescriptipionMsg.ITEMDESC_COL_RED;
// 1) Item name
AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_NAME), GetName());
// 1.5) Sub class name (loại trang bị) - like C++: AddDescText(white, true, pDescTab->GetWideString(ITEMDESC_CLASSNAME), m_pDBSubType->name);
// In C++, this is always called (no null check), so we always call it too
string subTypeName = GetSubTypeName();
AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_CLASSNAME), subTypeName ?? "");
// 1.6) Item level (cấp vũ khí/trang bị) - like C++: AddDescText(-1, true, pDescTab->GetWideString(ITEMDESC_LEVEL), m_Essence.weapon_level);
int itemLevel = GetItemLevel();
if (itemLevel > 0)
{
AddDescText(-1, true, GetItemDescString(DescriptipionMsg.ITEMDESC_LEVEL), itemLevel);
}
// 2) Base stats from element data (damage/defence/speed/range/resists)
// Adjust base stats by subtracting aPEEVals and adding aRefines
AddBaseStatsDesc(aPEEVals, aRefines);
// 3) Endurance (current / max) - adjust color based on PEE_ENDURANCE
if (MaxEndurance > 0)
{
int col = white;
if (CurEndurance == 0)
col = red;
else if ((PropEffectEssence() & (1 << PEEI_ENDURANCE)) != 0)
col = lblue;
int curVis = VisualizeEndurance(CurEndurance);
int maxVis = VisualizeEndurance(MaxEndurance);
AddDescText(col, true, "{0} {1}/{2}",
GetItemDescString(DescriptipionMsg.ITEMDESC_ENDURANCE), curVis, maxVis);
}
// 4) Requirements (level / stats / reputation)
AddRequirementDesc(aPEEVals);
AddReputationReqDesc();
// 4.5) Profession restriction (phái hạn chế) - like C++: AddProfReqDesc(m_iProfReq);
// In C++, this is always called regardless of value (the function checks internally)
AddProfReqDesc(ProfReq);
// 5) Add-on properties (non-embedded, non-suite, non-engraved)
if (!string.IsNullOrEmpty(strAddon))
m_strDesc += strAddon;
AddPriceDesc(white, false);
// 6) Tessera / stones (socketed gems)
BuildTesseraDesc();
// 7) Sharpener properties
AddSharpenerDesc();
// 8) Engraved properties
AddEngravedDesc();
// 9) Suite information
AddSuiteDesc();
// 10) Maker & destroying info if any
AddMakerDesc();
// Destroying description is added by caller when needed; keep it optional here.
m_strDesc += "\\r";
AddExtDescText();
Debug.Log("m_strDesc add ext desc text: " + m_strDesc);
// 11) Price (sell price scaled)
return m_strDesc;
}
/// <summary>
/// Get item description for booth buying
/// </summary>
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;
}
/// <summary>
/// Add concise requirement description (level / stats / profession).
/// This is a simplified mirror of the original C++ text; colors are kept white for now.
/// </summary>
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);
}
}
/// <summary>
/// Get sub type name (loại trang bị) from element data
/// </summary>
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<uint>(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;
}
/// <summary>
/// Get item level (cấp vũ khí/trang bị) from element data
/// </summary>
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<int>(data, new[] { "weapon_level" }, out int weaponLevel) && weaponLevel > 0)
return weaponLevel;
// Try level (for armor and other items)
if (TryGetNumber<int>(data, new[] { "level" }, out int level) && level > 0)
return level;
}
catch { }
return 0;
}
/// <summary>
/// Get character combo ID (phái hạn chế) from element data
/// </summary>
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<uint>(data, new[] { "character_combo_id" }, out uint comboId))
return comboId;
}
catch { }
return 0;
}
/// <summary>
/// Add profession requirement description (phái hạn chế)
/// Like C++: AddProfReqDesc(int iProfReq) - displays class restrictions
/// </summary>
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";
}
/// <summary>
/// Add price description
/// </summary>
public void AddPriceDesc(int col, bool repair)
{
if (repair)
AddDescText(col, false, GetItemDescString(DescriptipionMsg.ITEMDESC_REPAIRCOST), GetRepairCost());
else
AddDescText(col, true, "Price: {0}", GetScaledPrice());
}
/// <summary>
/// Format property description
/// </summary>
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;
}
/// <summary>
/// Format refine data into a string
/// (hack function, do NOT use it in multi-thread environment)
/// </summary>
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;
}
/// <summary>
/// Get refine addon ID (virtual method, override in derived classes)
/// </summary>
public virtual uint GetRefineAddOn()
{
return 0;
}
/// <summary>
/// Check the special refine property
/// </summary>
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;
}
/// <summary>
/// Check property range value
/// </summary>
public bool CheckPropRangeValue(Property prop)
{
int idProp = prop.Type;
byte propType = GetPropertyType(idProp);
switch (propType)
{
case 25: // +Gold(%) -Fire(%)
case 26: // +Wood(%) -Gold(%)
case 27: // +Water(%) -Earth(%)
case 28: // +Fire(%) -Water(%)
case 29: // +Earth(%) -Wood(%)
case 30: // +Gold -Fire
case 31: // +Wood -Gold
case 32: // +Water -Earth
case 33: // +Fire -Water
case 34: // +Earth -Wood
case 55: // Add skill
return false;
}
// Check for range values in equipment addon data
// This would normally query the equipment addon data
return false;
}
public int GetHoleNum()
{
return Holes != null ? Holes.Count : 0;
}
public int GetHoleItem(int index)
{
if(Holes == null || index < 0 || index >= Holes.Count)
return 0;
return Holes[index];
}
#endregion
#region Helper Methods
/// <summary>
/// Parse properties
/// </summary>
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++;
}
}
/// <summary>
/// Check if this equipment belongs to a suite
/// </summary>
public int GetSuiteID()
{
// This would normally query the game's suite equip table
Dictionary<int, int> suiteEquipTab = EC_Game.GetSuiteEquipTab();
if (suiteEquipTab.TryGetValue(GetTemplateID(), out int suiteId))
{
return suiteId;
}
return 0;
}
/// <summary>
/// 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
/// </summary>
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();
}
/// <summary>
/// Add resistance property description with proper formatting
/// </summary>
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);
}
/// <summary>
/// Add description text
/// </summary>
protected override void AddDescText(int color, bool newLine, string format, params object[] args)
{
// Add color prefix if color is specified
if (color >= 0)
{
string colorStr = GetColorString((DescriptipionMsg)color);
m_strDesc += colorStr;
}
// Call base implementation for format conversion (handles both {0} and %d styles)
base.AddDescText(-1, false, format, args);
// Add newline if requested (base class uses "\n", but equip uses "\\r")
if (newLine)
m_strDesc += "\\r";
}
/// <summary>
/// Get color string for color ID
/// Returns color codes in ^RRGGBB format (6 hex digits) for text formatting
/// </summary>
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
}
}
/// <summary>
/// Convert word color to Unity Color
/// </summary>
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
);
}
/// <summary>
/// Check if property is sharpener property
/// </summary>
private bool IsSharpenerProperty(byte propType)
{
return propType >= 100 && propType <= 115;
}
/// <summary>
/// Visualize float percent
/// </summary>
private int VisualizeFloatPercent(int value)
{
return (int)(IntToFloat(value) * 100.0f + 0.5f);
}
/// <summary>
/// Helper method to convert int to float (bit reinterpretation)
/// </summary>
private float IntToFloat(int value)
{
return BitConverter.ToSingle(BitConverter.GetBytes(value), 0);
}
/// <summary>
/// Helper method to convert float to int (bit reinterpretation)
/// </summary>
private int FloatToInt(float value)
{
return BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
}
/// <summary>
/// Add range value description for normal integer values (replaces ADD_RANGE_VALUE_DESC_ID_NORMAL macro)
/// </summary>
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);
}
}
/// <summary>
/// Add range value description for float values (replaces ADD_RANGE_VALUE_DESC_ID_FLOAT macro)
/// </summary>
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);
}
}
/// <summary>
/// Add range value description for percent values (replaces ADD_RANGE_VALUE_DESC_ID_PERCENT macro)
/// </summary>
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);
}
}
/// <summary>
/// Add range value description for minus percent values variant 1 (replaces ADD_RANGE_VALUE_DESC_ID_MINUS_PERCENT_1 macro)
/// </summary>
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);
}
}
/// <summary>
/// Add range value description for minus percent values variant 2 (replaces ADD_RANGE_VALUE_DESC_ID_MINUS_PERCENT_2 macro)
/// </summary>
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));
}
}
/// <summary>
/// Add range value description for half values (replaces ADD_RANGE_VALUE_DESC_ID_HALF macro)
/// </summary>
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);
}
}
/// <summary>
/// Add range value description for string normal values (replaces ADD_RANGE_VALUE_DESC_STR_NORMAL macro)
/// </summary>
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);
}
}
/// <summary>
/// Add one add-on property description
/// </summary>
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;
}
}
}
/// <summary>
/// Build add-ons properties description
/// </summary>
protected void BuildAddOnPropDesc(int[] aPEEVals, int[] aRefines)
{
if (Props.Count == 0)
return;
// Change color
m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE);
foreach (Property prop in Props)
{
// Properties added by Embedded stone will be printed by BuildTesseraDesc() later
// Ignore suite properties also
if (prop.Embed || prop.Suite || prop.Engraved)
continue;
AddOneAddOnPropDesc(prop.Type, prop.Params, aPEEVals, aRefines, prop.Local);
}
}
/// <summary>
/// Get soul power added by this equipment
/// </summary>
public int GetSoulPowerAdded()
{
int added = 0;
foreach (Property prop in Props)
{
if (prop.NumParam > 0)
{
byte propType = GetPropertyType(prop.Type);
if (propType == 63) // Soul power property
{
added += prop.Params[0];
}
}
}
return added;
}
/// <summary>
/// Build tessera description (socketed gems/stones)
/// </summary>
protected void BuildTesseraDesc()
{
if (Holes.Count == 0)
return;
int cyanine = (int)DescriptipionMsg.ITEMDESC_COL_CYANINE;
for (int i = 0; i < Holes.Count; i++)
{
if (Holes[i] == 0)
continue;
// Get item name - would normally use CECIvtrItem::CreateItem
string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(Holes[i]);
string descText = "null";
// Check if it's a stone and get its description
// In C++, this checks if pItem->GetClassID() == ICID_STONE
// and then gets weapon_desc or armor_desc based on equipment type
// For now, use item name as fallback
descText = itemName;
AddDescText(cyanine, true, GetItemDescString(DescriptipionMsg.ITEMDESC_2STRINGS), itemName, descText);
}
}
struct SUITEITEM
{
public bool bEnabled;
public int tid;
public char[] szName;
public string Name => new string(szName);
public SUITEITEM(bool bEnabled, int tid)
{
this.bEnabled = bEnabled;
this.tid = tid;
this.szName = new char[32];
}
}
/// <summary>
/// Add suite description
/// </summary>
protected void AddSuiteDesc()
{
int idSuite = GetSuiteID();
if (idSuite == 0)
return; // This equipment isn't one of any suite
// Get suite info
DATA_TYPE dataType = DATA_TYPE.DT_INVALID;
elementdataman dataMan = EC_Game.GetElementDataMan();
object pData = dataMan.get_data_ptr((uint)idSuite, ID_SPACE.ID_SPACE_ESSENCE, ref dataType);
if (dataType != DATA_TYPE.DT_SUITE_ESSENCE)
{
// ASSERT in C++
return;
}
SUITE_ESSENCE pSuiteEss = (SUITE_ESSENCE)pData;
CECHostPlayer hostPlayer = EC_Game.GetGameRun().GetHostPlayer();
// Colors
int iNameCol = DecideNameCol();
int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE;
int green = (int)DescriptipionMsg.ITEMDESC_COL_GREEN;
int gray = (int)DescriptipionMsg.ITEMDESC_COL_GRAY;
int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE;
int yellow = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW;
// Save current description
string strCurDesc = m_strDesc;
bool bShowDetail = true;
if (hostPlayer.GetEquipment() != m_pDescIvtr)
bShowDetail = false;
else
{
for(int i = 0; i < m_pDescIvtr.GetSize(); i++)
{
EC_IvtrItem pItem = m_pDescIvtr.GetItem(i);
if (pItem == null)
{
bShowDetail = false;
continue;
}
if (pItem.m_tid == this.m_tid)
{
bShowDetail = true;
break;
}
}
}
if (!bShowDetail)
{
m_strDesc = "";
AddDescText(iNameCol, false, "{0} {1}/{2}", pSuiteEss.Name, 0, pSuiteEss.max_equips);
m_strDesc = strCurDesc + m_strDesc;
return;
}
// Maximum number of suite items
const int MAX_NUM = 12;
SUITEITEM[] aSuiteItems = new SUITEITEM[MAX_NUM];
int maxEquips = (pSuiteEss.max_equips > MAX_NUM) ? MAX_NUM : (int)pSuiteEss.max_equips;
for(int i = 0; i < maxEquips; i++)
{
aSuiteItems[i].bEnabled = false;
aSuiteItems[i].tid = (int)pSuiteEss.equipments[i].id;
aSuiteItems[i].szName = new char[32];
aSuiteItems[i].szName[0] = '\0';
EC_IvtrItem pEquipItem = CreateItem((int)pSuiteEss.equipments[i].id, 0,1);
if (pEquipItem != null)
{
aSuiteItems[i].szName = pEquipItem.GetName().ToCharArray();
//delete pEquipItem;
}
else
{
aSuiteItems[i].tid = 0;
}
}
int iItemCnt;
int[] aEquipped = new int[MAX_NUM];
iItemCnt = hostPlayer.GetEquippedSuiteItem(idSuite,ref aEquipped);
if(iItemCnt == 0) return;
//m_strDesc += "\\r\\r";
// Build suite addon properties at first
for (int i = 0; i < MAX_NUM; i++)
{
for(int j = 0; j < iItemCnt; j++)
{
if (aSuiteItems[i].tid == aEquipped[j])
{
aSuiteItems[i].bEnabled = true;
break;
}
}
}
if(iItemCnt > 1)
{
// Change color
AddDescText(lblue, false, "");
for (int i=1; i < iItemCnt; i++)
{
int idProp = (int)pSuiteEss.addons[i-1].id;
if (idProp == 0)
continue;
pData = dataMan.get_data_ptr((uint)idProp, ID_SPACE.ID_SPACE_ADDON, ref dataType);
if (dataType != DATA_TYPE.DT_EQUIPMENT_ADDON)
{
continue;
}
EQUIPMENT_ADDON pAddOn = (EQUIPMENT_ADDON)pData;
AddDescText(-1, false, "(%d) ", i+1);
AddDescText(-1, true, "%s", pAddOn.Name);
}
}
// Add suite name
AddDescText(yellow/*iNameCol*/, true, "{0} ({1} / {2})", pSuiteEss.Name, iItemCnt, pSuiteEss.max_equips);
for (int i=0; i < pSuiteEss.max_equips; i++)
{
SUITEITEM suiteItem = aSuiteItems[i];
if (suiteItem.tid == 0)
continue;
int col = suiteItem.bEnabled ? green : gray;
bool bRet = (i == pSuiteEss.max_equips-1) ? false : true;
// Add item name
AddDescText(col, bRet, " %s", suiteItem.Name);
}
}
/// <summary>
/// Add destroying description
/// </summary>
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));
}
/// <summary>
/// Add reputation requirement description
/// </summary>
protected void AddReputationReqDesc()
{
if (ReputationReq == 0)
return;
// Get host player reputation
// In C++: CECHostPlayer* pHost = g_pGame->GetGameRun()->GetHostPlayer();
// int col = pHost->GetReputation() >= m_iReputationReq ? ITEMDESC_COL_WHITE : ITEMDESC_COL_RED;
int playerReputation = 0; // TODO: Get from host player
int col = playerReputation >= ReputationReq ?
(int)DescriptipionMsg.ITEMDESC_COL_WHITE :
(int)DescriptipionMsg.ITEMDESC_COL_RED;
AddDescText(col, true, GetItemDescString(DescriptipionMsg.ITEMDESC_REPUTATION_REQ), ReputationReq);
}
/// <summary>
/// Get engraved property number
/// </summary>
public int GetEngravedPropertyNum()
{
int num = 0;
foreach (Property prop in Props)
{
if (prop.Engraved)
num++;
}
return num;
}
/// <summary>
/// Add sharpener description (磨刀石 properties)
/// </summary>
protected void AddSharpenerDesc()
{
if (Props.Count == 0)
return;
int color = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE;
// Check if all sharpener properties have the same expire time
bool sameExpireTime = true;
bool findFirst = true;
int lastExpireTime = 0;
foreach (Property prop in Props)
{
if (prop.Embed || prop.Suite || prop.Engraved)
continue;
byte propType = GetPropertyType(prop.Type);
if (!IsSharpenerProperty(propType))
continue;
int p1 = prop.Params.Length > 1 ? prop.Params[1] : 0;
if (findFirst)
{
// Found first sharpener property
lastExpireTime = p1;
findFirst = false;
}
else
{
// Found another sharpener property
if (p1 != lastExpireTime)
{
// Expire times are different
sameExpireTime = false;
break;
}
}
}
if (findFirst)
{
// Didn't find any sharpener property
return;
}
bool firstProp = true;
foreach (Property prop in Props)
{
if (prop.Embed || prop.Suite || prop.Engraved)
continue;
byte propType = GetPropertyType(prop.Type);
if (!IsSharpenerProperty(propType))
continue;
if (firstProp)
{
m_strDesc += "\\r";
firstProp = false;
}
// New line
m_strDesc += "\\r";
int p0 = prop.Params.Length > 0 ? prop.Params[0] : 0; // First parameter value
int p1 = prop.Params.Length > 1 ? prop.Params[1] : 0; // Expire time
// Add property description
switch (propType)
{
case 100: // Sharpener physical damage
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE));
AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0);
break;
case 101: // Sharpener max physical damage
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE), p0);
break;
case 102: // Sharpener magic damage
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE));
AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0);
break;
case 103: // Sharpener max magic damage
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE), p0);
break;
case 104: // Sharpener physical defence
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE));
AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0);
break;
case 105: // Sharpener HP
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP));
AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0);
break;
case 106: // Sharpener strength
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0);
break;
case 107: // Sharpener agility
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0);
break;
case 108: // Sharpener energy
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0);
break;
case 109: // Sharpener attack rating
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0);
break;
case 110: // Sharpener deadly strike
if (prop.Local)
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(p0));
else
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), p0);
break;
case 111: // Sharpener attack degree
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0);
break;
case 112: // Sharpener defence degree
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEF_DEGREE), p0);
break;
case 113: // Sharpener cast time
if (prop.Local)
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -VisualizeFloatPercent(p0));
else
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -p0);
break;
case 114: // Sharpener magic defence
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF));
AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0);
break;
case 115: // Sharpener ride pet speed
AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDRIDEONPETSPEED), IntToFloat(p0));
break;
default:
// ASSERT(false) in C++
continue;
}
// If expire times are different, add expire time after each property
if (!sameExpireTime)
{
if (p1 != 0)
{
m_strDesc += " ";
AddExpireTimeDesc(p1);
TrimLastReturn();
}
}
}
// If expire times are the same, add expire time at the end
if (sameExpireTime)
{
if (lastExpireTime != 0)
{
m_strDesc += "\\r";
AddExpireTimeDesc(lastExpireTime);
TrimLastReturn();
}
}
}
/// <summary>
/// Append engraved property descriptions to the current description buffer.
/// Mirrors the behaviour of the original C++ AddEngravedDesc.
/// </summary>
protected void AddEngravedDesc()
{
if (Props.Count == 0)
return;
// Change color
bool firstProp = true;
foreach (Property prop in Props)
{
if (!prop.Engraved)
continue;
if (firstProp)
{
firstProp = false;
m_strDesc += "\\r";
m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_YELLOW);
}
AddOneAddOnPropDesc(prop.Type, prop.Params, null, null, prop.Local);
}
if (!firstProp)
{
// Trim last return after engraved properties
TrimLastReturn();
}
}
/// <summary>
/// Append maker description (signature / crafted by) to the description buffer.
/// </summary>
protected void AddMakerDesc()
{
if (string.IsNullOrEmpty(Maker))
return;
m_strDesc += "\\r";
// For signed marks (IMT_SIGN), Maker already contains color codes and formatted text.
if (MadeFrom == (byte)ITEM_MAKE_TAG.IMT_SIGN)
{
m_strDesc += Maker;
}
else
{
// Normal "made by" line using item-desc string if available
string fmt = GetItemDescString(DescriptipionMsg.ITEMDESC_MADEFROM);
if (string.IsNullOrEmpty(fmt))
{
fmt = "Made by {0}";
}
AddDescText((int)DescriptipionMsg.ITEMDESC_COL_GREEN, false, fmt, Maker);
}
}
/// <summary>
/// Trim the last '\r' in description string
/// </summary>
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);
}
}
/// <summary>
/// Add expire time description
/// </summary>
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);
}
}
/// <summary>
/// Add expire time description with specific expire date
/// </summary>
private void AddExpireTimeDesc(int expireDate)
{
int temp = ExpireDate;
ExpireDate = expireDate;
AddExpireTimeDesc();
ExpireDate = temp;
}
/// <summary>
/// Get preview info
/// </summary>
public virtual string GetPreviewInfo()
{
m_strDesc = "";
BuildAddOnPropDesc(null, null);
return m_strDesc;
}
/// <summary>
/// Get add-on property description
/// </summary>
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;
}
/// <summary>
/// Get engrave description
/// </summary>
public string GetEngraveDesc()
{
int[] aPEEVals = new int[MAX_PEEINDEX];
int[] aRefines = new int[MAX_REFINEINDEX];
m_strDesc = "";
if (Props.Count == 0)
return m_strDesc;
// Change color
m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_YELLOW);
foreach (Property prop in Props)
{
if (prop.Engraved)
AddOneAddOnPropDesc(prop.Type, prop.Params, aPEEVals, aRefines, prop.Local);
}
return m_strDesc;
}
public struct RefineEffect
{
int m_refineIndex;
public int RefineIndex{ get { return m_refineIndex; } set { m_refineIndex = value; } }
int m_incEffect;
public int IncEffect{ get { return m_incEffect; } set { m_incEffect = value; } }
int[] m_aPEEVals;
public int[] APEEVals{ get { return m_aPEEVals; } set { m_aPEEVals = value; } }
int[] m_aRefines;
public int[] ARefines{ get { return m_aRefines; } set { m_aRefines = value; } }
string m_clrAttribute;
public string ClrAttribute{ get { return m_clrAttribute; } set { m_clrAttribute = value; } }
string m_clrEffect;
public string ClrEffect{ get { return m_clrEffect; } set { m_clrEffect = value; } }
public RefineEffect(int[] aPEEVals, int[] aRefines, string clrAttribute, string clrEffect)
{
m_refineIndex = -1;
m_incEffect = 0;
m_aPEEVals = aPEEVals;
m_aRefines = aRefines;
m_clrAttribute = clrAttribute;
m_clrEffect = clrEffect;
}
public void Set(int refineIndex, int incEffect){
m_refineIndex = refineIndex;
m_incEffect = incEffect;
}
public int GetIncEffect(){
return m_incEffect;
}
public string GetClrAttribute(){
return m_clrAttribute;
}
public string GetClrEffect(){
return m_clrEffect;
}
};
public virtual bool GetRefineEffectFor(string strEffect, RefineEffect rhs){ return false; }
public static int CalcRefineEffect(int refineLevel, int baseEffect)
{
const int MAX_REFINE_LEVEL = 12;
float[] refine_factor = new float[MAX_REFINE_LEVEL + 1]
{ 0, 1.0f, 2.0f, 3.05f, 4.3f, 5.75f, 7.55f, 9.95f, 13f, 17.05f, 22.3f, 29f, 37.5f };
if (refineLevel >= 0 && refineLevel <= MAX_REFINE_LEVEL){
return (int)(baseEffect * refine_factor[refineLevel] + 0.1f);
}
return 0;
}
#endregion
}
/// <summary>
/// Equipment addon description class
/// </summary>
public class EC_IvtrEquipAddonDesc
{
private object m_pAddon;
private string m_strDesc = "";
/// <summary>
/// Set addon
/// </summary>
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;
}
}
}