diff --git a/Assets/PerfectWorld/Resources/UI/DialogScriptTableObject.asset b/Assets/PerfectWorld/Resources/UI/DialogScriptTableObject.asset index a9fbcb99ec..24be82aded 100644 --- a/Assets/PerfectWorld/Resources/UI/DialogScriptTableObject.asset +++ b/Assets/PerfectWorld/Resources/UI/DialogScriptTableObject.asset @@ -15,3 +15,5 @@ MonoBehaviour: lstPrefabDialog: - id: DialogNPC prefab: {fileID: 8237288432181259026, guid: 7653e7e64393ec24c903f0606499b8c4, type: 3} + - id: DialogNPCShop + prefab: {fileID: 8237288432181259026, guid: eaeb778b6aab3d74299373b3a96b72c4, type: 3} diff --git a/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs b/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs index b3bea36612..1dda787681 100644 --- a/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs +++ b/Assets/PerfectWorld/Scripts/Addressable/AddressableManager.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; @@ -13,6 +14,7 @@ namespace BrewMonster.Scripts private bool _isInitialized = false; private Dictionary> _loadedAssets = new(); + public event Action OnDispose; protected override void Initialize() { @@ -63,10 +65,71 @@ namespace BrewMonster.Scripts /// /// When the asset is no longer needed, call this method to unload it. /// - /// - public void UnloadAsset(string assetPath) + /// The asset path used when loading the asset + public void ReleaseAsset(string assetPath) { - Addressables.Release(assetPath); + if (_loadedAssets.TryGetValue(assetPath, out var handle)) + { + if (handle.IsValid()) + { + Addressables.Release(handle); + } + _loadedAssets.Remove(assetPath); + BMLogger.Log($"AddressableManager: Released asset: {assetPath}"); + } + else + { + BMLogger.LogWarning($"AddressableManager: Asset not found in cache: {assetPath}"); + } + } + + /// + /// Release a specific asset by its handle directly. + /// + /// The async operation handle to release + public void ReleaseAsset(AsyncOperationHandle handle) + { + if (handle.IsValid()) + { + Addressables.Release(handle); + } + } + + /// + /// Release all loaded assets from the cache. + /// + public void ReleaseAllAssets() + { + foreach (var kvp in _loadedAssets) + { + if (kvp.Value.IsValid()) + { + Addressables.Release(kvp.Value); + } + } + _loadedAssets.Clear(); + BMLogger.Log("AddressableManager: Released all assets"); + } + + /// + /// Check if an asset is currently loaded in the cache. + /// + /// The asset path to check + /// True if the asset is loaded + public bool IsAssetLoaded(string assetPath) + { + return _loadedAssets.ContainsKey(assetPath) && _loadedAssets[assetPath].IsValid(); + } + + /// + /// Get the count of currently loaded assets. + /// + public int LoadedAssetCount => _loadedAssets.Count; + + private void OnDestroy() + { + OnDispose?.Invoke(); + ReleaseAllAssets(); } } } diff --git a/Assets/PerfectWorld/Scripts/GameData/EC_FixedMsg.cs b/Assets/PerfectWorld/Scripts/GameData/EC_FixedMsg.cs index d9df3a44c0..c6f8177c7a 100644 --- a/Assets/PerfectWorld/Scripts/GameData/EC_FixedMsg.cs +++ b/Assets/PerfectWorld/Scripts/GameData/EC_FixedMsg.cs @@ -348,4 +348,421 @@ namespace BrewMonster.Scripts FIXMSG_INVALID_NAME_LEN, FIXMSG_FC_RENAME_FACTION, } + public enum DescriptipionMsg +{ + // Item description ... + ITEMDESC_ATKSPD_VERYSLOW = 0, // 0, Attack speed + ITEMDESC_ATKSPD_SLOW, + ITEMDESC_ATKSPD_NORMAL, + ITEMDESC_ATKSPD_FAST, + ITEMDESC_ATKSPD_VERYFAST, + + ITEMDESC_COL_LIGHTBLUE, + ITEMDESC_COL_YELLOW, + ITEMDESC_COL_PURPLE, + ITEMDESC_COL_DARKGOLD, + ITEMDESC_COL_WHITE, + + ITEMDESC_COL_GRAY, + ITEMDESC_COL_BLUE, + ITEMDESC_COL_GREEN, + ITEMDESC_COL_RED, + ITEMDESC_COL_CYANINE, + + ITEMDESC_NAMESOCKET, // 15, name (socket num) + ITEMDESC_CLASSNAME, // Item class name + ITEMDESC_ATKSPEED, // Attack speed + ITEMDESC_PHYDAMAGE, + ITEMDESC_MAGICDAMAGE, + + ITEMDESC_ENDURANCE, // 20 + ITEMDESC_LEVELREQ, + ITEMDESC_STRENGTHREQ, + ITEMDESC_AGILITYREQ, + ITEMDESC_PRICE, + + ITEMDESC_PHYDEFENCE, + ITEMDESC_GOLDDEFENCE, + ITEMDESC_WOODDEFENCE, + ITEMDESC_WATERDEFENCE, + ITEMDESC_FIREDEFENCE, + + ITEMDESC_EARTHDEFENCE, + ITEMDESC_NAMENUMBER, // name (number) + ITEMDESC_REMAINTIME, // Remained time + ITEMDESC_NAME, + ITEMDESC_ADDPHYDAMAGE, + + ITEMDESC_ADDMAGICDAMAGE, + ITEMDESC_DODGE, + ITEMDESC_ADDHP, + ITEMDESC_ADDMP, + ITEMDESC_NOTDEAL, + + ITEMDESC_WEAPONREQ, // 40 + ITEMDESC_TASKITEM, + ITEMDESC_ATKDISTANCE, // Attack distance + ITEMDESC_PHYDEFEXTRA, + ITEMDESC_RUNSPEEDEXTRA, + + ITEMDESC_DODGEEXTRA, + ITEMDESC_PHYRESIST, + ITEMDESC_HPRECOVER, + ITEMDESC_MPRECOVER, + ITEMDESC_ATKTIME, + + ITEMDESC_CASTTIME, + ITEMDESC_DEADLYSTRIKE, + ITEMDESC_FIREWORKTIME, + ITEMDESC_PETLEVEL, + ITEMDESC_ATKRATINGEXTRA, + + ITEMDESC_EXP, + ITEMDESC_RANDOMPROP, + ITEMDESC_PHYDMGEXTRA, + ITEMDESC_MAGICDMGEXTRA, + ITEMDESC_CASTSKILL, + + ITEMDESC_MAXPHYDAMAGE, // 60 + ITEMDESC_MAXMAGICDAMAGE, + ITEMDESC_ENDURANCEEXTRA, + ITEMDESC_REQEXTRA, + ITEMDESC_ALLMAGICDEF, + + ITEMDESC_GOLDDEFEXTRA, + ITEMDESC_WOODDEFEXTRA, + ITEMDESC_WATERDEFEXTRA, + ITEMDESC_FIREDEFEXTRA, + ITEMDESC_EARTHDEFEXTRA, + + ITEMDESC_HPEXTRA, + ITEMDESC_MPEXTRA, + ITEMDESC_ADDATKDIST, + ITEMDESC_PROJECTILE, + ITEMDESC_ATKRATING, + + ITEMDESC_2STRINGS, + ITEMDESC_ADDHPINTIME, + ITEMDESC_ADDMPINTIME, + ITEMDESC_DECHALFPOISON, + ITEMDESC_ANTIDOTE, + + ITEMDESC_ALLMAGICRESIST, // 80 + ITEMDESC_USEEFFECT, + ITEMDESC_RECRUITHP, + ITEMDESC_RECRUITMP, + ITEMDESC_RECRUITHPMP, + + ITEMDESC_LEVEL, + ITEMDESC_USEREQUIREMENT, + ITEMDESC_MAXLEVELREQ, + ITEMDESC_WEAPONLVLREQ, + ITEMDESC_ELEMENTTIME, + + ITEMDESC_ADDFLYSPEED, + ITEMDESC_MPCONSUME, + ITEMDESC_PROFESSIONREQ, + ITEMDESC_RUNSPEED, + ITEMDESC_GENDERREQ, + + ITEMDESC_ERRORITEM, + ITEMDESC_COLOR, + ITEMDESC_PENTAGON, + ITEMDESC_COLORRECT, + ITEMDESC_UNITPRICE, + + ITEMDESC_REPAIRCOST, // 100 + ITEMDESC_VALIDTIME, + ITEMDESC_VITALITYREQ, + ITEMDESC_ENERGYREQ, + ITEMDESC_ERRORPROP, + + ITEMDESC_STRENGTH, + ITEMDESC_AGILITY, + ITEMDESC_VITALITY, + ITEMDESC_ENERGY, + ITEMDESC_CASTSKILL2, + + ITEMDESC_ADDFLYSPEED2, + ITEMDESC_MADEFROM, + ITEMDESC_WEAKDIST, + ITEMDESC_FOODTYPE, + ITEMDESC_FOOD_GRASS, + + ITEMDESC_FOOD_MEAT, + ITEMDESC_FOOD_VEGETABLE, + ITEMDESC_FOOD_FRUIT, + ITEMDESC_FOOD_WATER, + ITEMDESC_MOVESPEED, + + ITEMDESC_DAMAGE, // 120 + ITEMDESC_ATTACKRATE, + ITEMDESC_PETSKILL, + ITEMDESC_NULL02, + ITEMDESC_NULL03, + + // Command description ... + CMDDESC_SITDOWN, + CMDDESC_WALKRUN, + CMDDESC_NORMALATTACK, + CMDDESC_FINDTARGET, + CMDDESC_ASSISTATTACK, + + CMDDESC_INVITETOTEAM, + CMDDESC_LEAVETEAM, + CMDDESC_KICKTEAMMEM, + CMDDESC_FINDTEAM, + CMDDESC_STARTTRADE, + + CMDDESC_SELLBOOTH, + CMDDESC_BUYBOOTH, + CMDDESC_INVITETOFACTION, + CMDDESC_FLY, + CMDDESC_PICKUP, + + CMDDESC_GATHER, // 140 + CMDDESC_RUSHFLY, + CMDDESC_BINDBUDDY, + CMDDESC_SKILLGROUP, + CMDDESC_NULL3, + + // Emotion description ... + FACEDESC_WAVEHAND, + FACEDESC_NOD, + FACEDESC_SHADEHEAD, + FACEDESC_SHRUG, + FACEDESC_LAUGH, + + FACEDESC_ANGRY, + FACEDESC_FAINT, + FACEDESC_SAD, + FACEDESC_KISSHAND, + FACEDESC_SHY, + + FACEDESC_SALUTE, + FACEDESC_SITDOWN, + FACEDESC_CHARGE, + FACEDESC_THINK, + FACEDESC_CHALLENGE, + + FACEDESC_WIN, // 160 + FACEDESC_GAPE, + FACEDESC_KISS, + FACEDESC_FIGHT, + FACEDESC_ATTACK1, + + FACEDESC_ATTACK2, + FACEDESC_ATTACK3, + FACEDESC_ATTACK4, + FACEDESC_DEFENCE, + FACEDESC_FALL, + + FACEDESC_FALLONGROUND, // 170 + FACEDESC_LOOKAROUND, + FACEDESC_DANCE, + FACEDESC_FASHIONWEAPON, + FACEDESC_NULL01, + + ITEMDESC_EXPIRETIME_DAY, + ITEMDESC_EXPIRETIME_HOUR_MIN, + ITEMDESC_EXPIRETIME_MIN_SEC, + ITEMDESC_EXPIRETIME_SECOND, + ITEMDESC_REFINETICKET1, + + ITEMDESC_REFINETICKET2, // 180 + ITEMDESC_DESTROYINGNAME, + ITEMDESC_EQUIP_BIND, + ITEMDESC_AUTOHP1, + ITEMDESC_AUTOHP2, + + ITEMDESC_AUTOMP1, + ITEMDESC_AUTOMP2, + ITEMDESC_COOLTIME, + ITEMDESC_DOUBLEEXP_TIME, + ITEMDESC_EQUIP_BINDAFTERUSE, + + ITEMDESC_COL2_START, // 190 + ITEMDESC_COL2_BLUEGREEN, + ITEMDESC_COL2_RED, + ITEMDESC_COL2_BRIGHTBLUE, + ITEMDESC_COL2_NULL02, + + ITEMDESC_COL2_NULL03, + ITEMDESC_COL2_NULL04, + ITEMDESC_COL2_NULL05, + ITEMDESC_COL2_NULL06, + ITEMDESC_COL2_NULL07, + + ITEMDESC_SPOUSE_MALE, // 200 + ITEMDESC_SPOUSE_FEMALE, + ITEMDESC_ATK_DEGREE, + ITEMDESC_DEF_DEGREE, + ITEMDESC_GOBLIN_REFINE_LEVEL, + + ITEMDESC_GOBLIN_LEVEL, + ITEMDESC_GOBLIN_RANDPT, + ITEMDESC_GOBLIN_ENERGY, + ITEMDESC_GOBLIN_ENERGY_RESTORE, + ITEMDESC_GOBLIN_STAMINA, + + ITEMDESC_GOBLIN_REFINE_EFFECT, // 210 + ITEMDESC_GOBLIN_TRADE_PROTECT, + ITEMDESC_GOBLIN_TRADE_UNPROTECT, + ITEMDESC_GOBLIN_CANTRADE, + ITEMDESC_EXPPILL_EXP, + + ITEMDESC_GOBLINEQUIP_POS, + ITEMDESC_GOBLINEQUIP_LEVELREQ, + ITEMDESC_GOBLINEQUIP_GOLD, + ITEMDESC_GOBLINEQUIP_WOOD, + ITEMDESC_GOBLINEQUIP_WATER, + + ITEMDESC_GOBLINEQUIP_FIRE, // 220 + ITEMDESC_GOBLINEQUIP_EARTH, + ITEMDESC_GOBLINEQUIP_POS_1, + ITEMDESC_GOBLINEQUIP_POS_2, + ITEMDESC_GOBLINEQUIP_POS_3, + + ITEMDESC_GOBLINEQUIP_POS_4, + ITEMDESC_HASRANDOM_PROP, + ITEMDESC_GOBLIN_GROW_DEGREE, + ITEMDESC_SPECIAL_PROP, + ITEMDESC_DEAD_PROTECT, + + ITEMDESC_NO_DROP, // 230 + ITEMDESC_NO_TRADE, + ITEMDESC_NO_PLAYER_TRADE, + ITEMDESC_LEAVE_SCENE_DISAPEAR, + ITEMDESC_USE_AFTER_PICK_UP, + + ITEMDESC_DROP_WHEN_DEAD, + ITEMDESC_DROP_WHEN_OFFLINE, + ITEMDESC_SELL_COL_NUM, + ITEMDESC_BUY_COL_NUM, + ITEMDESC_MAX_BOOTH_NAME, + + ITEMDESC_UNREPAIRABLE, // 240 + ITEMDESC_TYPE, + ITEMDESC_ATTACK_ITEM, + ITEMDESC_DEFENCE_ITEM, + ITEMDESC_BATTLE_ITEM, + + ITEMDESC_USE_REGION, + ITEMDESC_CONSUME_COUNT, + ITEMDESC_SKILL_EFFECT, + ITEMDESC_SPECIAL_DESC, + ITEMDESC_CONSUME_ANYWAY, + + ITEMDESC_USE_EFFECT, // 250 + ITEMDESC_CANNOT_USE_IN_BATTLE, + ITEMDESC_EQUIP_DESTROYING, + ITEMDESC_EQUIP_REPAIR_NEED_ITEM, + ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT, + + ITEMDESC_TOTAL_DEFENCE_ADD, + ITEMDESC_NO_USER_TRASH, + ITEMDESC_INC_SKILL_ABILITY_REQUIRE_LEVEL, + ITEMDESC_INC_SKILL_ABILITY_RATIO, + ITEMDESC_REPUTATION_REQ, + + ITEMDESC_ADDDAMAGE, // 260 + ITEMDESC_PROFVIEW, + ITEMDESC_WEDDING_BOOKCARD, + ITEMDESC_WEDDING_INVITECARD, + ITEMDESC_SOULPOWER, + + ITEMDESC_LASTTIME_DAY, + ITEMDESC_LASTTIME_HOUR_MIN, + ITEMDESC_LASTTIME_MIN_SEC, + ITEMDESC_LASTTIME_SECOND, + ITEMDESC_GOLDRESIST, + + ITEMDESC_WOODRESIST, // 270 + ITEMDESC_WATERRESIST, + ITEMDESC_FIRERESIST, + ITEMDESC_EARTHRESIST, + ITEMDESC_CAN_WEBTRADE, + + ITEMDESC_PRICE_SEPERATOR, // 271 + ITEMDESC_PENETRATION, + ITEMDESC_RESILIENCE, + ITEMDESC_FORCE_REQUIRE, + ITEMDESC_FORCE_REPU_TOTAL, + + ITEMDESC_FORCE_REPU_INC_RATIO, // 280 + ITEMDESC_EQUIP_BIND_ONLY, + ITEMDESC_REQUIRE_MAX_LEVEL, + ITEMDESC_DEFENCE, + ITEMDESC_ONE_HANDED_WEAPON, + + ITEMDESC_TWO_HANDED_WEAPON, + ITEMDESC_TWO_HANDED_POLEARM, + ITEMDESC_TWO_HANDED_HEAVY, + ITEMDESC_TWO_HANDED_POLEHEAVY, + ITEMDESC_WHIP, + + ITEMDESC_BOW, // 290 + ITEMDESC_CROSSBOW, + ITEMDESC_BOXING_GLOVES, + ITEMDESC_PIKE, + ITEMDESC_EMPTY_HANDED, + + ITEMDESC_DAGGER, // 295 + ITEMDESC_TALISMAN, + ITEMDESC_EQUIPMARK, + ITEMDESC_PET_EVO_NAME, + ITEMDESC_PET_ATK_INHERIT, + + ITEMDESC_PET_DEF_INHERIT, // 300 + ITEMDESC_PET_HP_INHERIT, + ITEMDESC_PET_ATKLVL_INHERIT, + ITEMDESC_PET_DEFLVL_INHERIT, + ITEMDESC_PET_NATURE, + + ITEMDESC_CROSSSERVER_ERROR, // 305 + ITEMDESC_USE_IN_SANCTUARY_ONLY, + ITEMDESC_SYSMODULE_FM_GT, // GT + ITEMDESC_SYSMODULE_FM_TOUCH, + ITEMDESC_SYSMODULE_FM_ROBOT, + + ITEMDESC_SYSMODULE_FM_WIKI, // 310 + ITEMDESC_SYSMODULE_FM_OFFLINESHOP, + ITEMDESC_SYSMODULE_FM_BORADCAST, // �������� + ITEMDESC_SYSMODULE_FM_MATCHSYS, + ITEMDESC_SYSMODULE_FM_ADDEXP, + + + CMDDESC_TWO_KISS, // �������� 315 + ITEMDESC_GENERALCARD_RANK, + ITEMDESC_LEADERSHIP, + ITEMDESC_GENERALCARD_MAXLEVEL, + ITEMDESC_GENERALCARD_LEVEL, + + ITEMDESC_GENERALCARD_REINCARNATION_COUNT, + ITEMDESC_VIGOUR, + ITEMDESC_GENERALCARD_EXP_CANGIVE, + ITEMDESC_GENERALCARD_SHOWORDER, + ITEMDESC_GENERALCARD_SUITE_PROPRADIO, + + CMDDESC_JUMP_TRICK, + CMDDESC_RUN_TRICK, + ITEMDESC_GENERALCARD_EXP, + ITEMDESC_GENERALCARD_TYPE1, + ITEMDESC_GENERALCARD_TYPE2, + + ITEMDESC_GENERALCARD_TYPE3, + ITEMDESC_GENERALCARD_TYPE4, + ITEMDESC_GENERALCARD_TYPE5, + ITEMDESC_GENERALCARD_TYPE6, + ITEMDESC_CURRENT_PROFESSION, + + ITEMDESC_SYSMODULE_FM_AUTOHPMP, // 335 + ITEMDESC_FLYSWORD_NOIMPROVED, + ITEMDESC_FLYSWORD_IMPROVE, + ITEMDESC_VISIT_HTTP_WITH_TOKEN, + ITEMDESC_OBOROKNIFE_WEAPON, + + ITEMDESC_SICKLE_WEAPON, // 340 + ITEMDESC_ADDRIDEONPETSPEED, +}; } \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs index c3ce061b45..1345bde3a7 100644 --- a/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs +++ b/Assets/PerfectWorld/Scripts/Managers/CECAttacksMan.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; +using ModelRenderer.Scripts.Common; using UnityEngine; namespace BrewMonster @@ -745,6 +747,15 @@ public class CECAttackEvent // TODO: Implement AddSkillGfxEvent // CECGameRun.Instance.GetWorld().GetSkillGfxMan().AddSkillGfxEvent(m_idHost, data.idTarget, // pszFlyGFX, pszHitGFX, m_timeToDoDamage, false, GfxMoveMode.enumLinearMove, 1, 0, null, vFlyScale, vHitScale, data.dwModifier); + var target = EC_ManMessageMono.Instance?.GetObject(data.idTarget, 0)?.gameObject.transform; + if (target == null) + { + BMLogger.LogError("Target is null!"); + return false; + } + //todo: not set default like this + var fullGfx = "程序联入/击中/拳套击中"; + CECGameRun.Instance.ShowVfx(fullGfx, target.position, null, 1f); } } else @@ -762,7 +773,8 @@ public class CECAttackEvent { TARGET_DATA data = m_targets[i]; - // string fullGfx = pWeaponType.GetFileHitGfx(); + var fullGfx = ByteToStringUtils.ByteArrayToCP936String(pWeaponType.file_hitgfx); + fullGfx = fullGfx.Substring(4); // szGFX = fullGfx.Length > 4 ? fullGfx.Substring(4) : string.Empty; // skip gfx/ // szGFX = pWeaponType.file_hitgfx // if ((data.dwModifier & (uint)MOD.MOD_NULLITY) != 0) @@ -780,9 +792,10 @@ public class CECAttackEvent BMLogger.LogError("Target is null!"); return false; } - - szGFX = "程序联入/击中/拳套击中"; - CECGameRun.Instance.ShowVfx(szGFX, target.position, null, 1f); + + //todo: not set default like this + fullGfx = "程序联入/击中/拳套击中"; + CECGameRun.Instance.ShowVfx(fullGfx, target.position, null, 1f); } } } diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs index 1cfd3a63fc..70d4966f54 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_InventoryUI.cs @@ -28,24 +28,9 @@ namespace BrewMonster.Scripts.Managers [SerializeField] private TextOutlet nameText; [SerializeField] private TextOutlet descriptionText; [SerializeField] private TextOutlet extendedDescText; - [SerializeField] private TextOutlet countText; - [SerializeField] private TextOutlet stateText; - [SerializeField] private TextOutlet expireText; [SerializeField] private Button equipButton; [SerializeField] private Button dropButton; - [Header("Equipment Details (assign in Inspector)")] - [SerializeField] private TextOutlet levelReqText; - [SerializeField] private TextOutlet statsReqText; - [SerializeField] private TextOutlet enduranceText; - [SerializeField] private TextOutlet repairCostText; - [SerializeField] private TextOutlet propertiesText; - [SerializeField] private TextOutlet refineText; - [SerializeField] private TextOutlet makerText; - [SerializeField] private TextOutlet priceText; - [SerializeField] private TextOutlet holesText; - [SerializeField] private TextOutlet suiteText; - [Header("Inventory Settings")] [SerializeField] private bool autoRefresh = true; [SerializeField] private float refreshInterval = 1.0f; @@ -68,6 +53,12 @@ namespace BrewMonster.Scripts.Managers private static bool s_hasPendingCash; private static int s_pendingCashAmount; + // Flags to prevent log spam for extended description warnings + // 防止扩展描述警告日志刷屏的标志 + private static bool m_HasLoggedExtDescNull = false; + private static bool m_HasLoggedExtDescNotInit = false; + private static bool m_HasLoggedExtDescError = false; + private InventoryModel model; private InventoryView view; @@ -681,11 +672,20 @@ namespace BrewMonster.Scripts.Managers } } + public void RefreshLayout(GameObject gameObject) + { + var parent = gameObject.GetComponent(); + + // Force Unity to rebuild layout immediately + parent.ForceUpdateRectTransforms(); + LayoutRebuilder.ForceRebuildLayoutImmediate(parent); + } private void ShowDetailPanel(bool show) { if (detailPanelRoot != null) { detailPanelRoot.SetActive(show); + RefreshLayout(detailPanelRoot); } } @@ -807,268 +807,108 @@ namespace BrewMonster.Scripts.Managers return; } - // Get user-friendly text from string tables + // Get user-friendly name string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(item.m_tid); - string itemDescription = GetItemDescription(item.m_tid); - string itemExtendedDesc = GetItemExtendedDescription(item.m_tid); - - // Display basic content nameText?.Set(string.IsNullOrEmpty(itemName) ? $"Item {item.m_tid}" : itemName); - descriptionText?.Set(itemDescription ?? "No description available"); - extendedDescText?.Set(itemExtendedDesc ?? ""); - countText?.Set($"Count: {item.m_iCount}"); - - // Format state properly - string stateTextValue = GetItemStateText(item.State); - stateText?.Set(stateTextValue); - - // Format expiration date properly - if (item.m_expire_date > 0) - { - DateTime expireDate = DateTimeOffset.FromUnixTimeSeconds(item.m_expire_date).DateTime; - expireText?.Set($"Expires: {expireDate:yyyy-MM-dd HH:mm}"); - } - else - { - expireText?.Set("No expiration"); - } - // Display equipment-specific information + // Centralised description: + // - For equipment, prefer EC_IvtrEquip description (includes stats, addons, sockets, etc.) + // - For other items, use EC_IvtrItem.GetDesc which reads from string tables. + string fullDesc = null; if (showEquipmentDetails && currentSelectedEquipment != null) { - FillEquipmentDetails(); + fullDesc = currentSelectedEquipment.GetNormalDesc(); } else { - ClearEquipmentDetails(); + fullDesc = item.GetDesc(); + } + + if (!string.IsNullOrEmpty(fullDesc)) + { + // Replace C++ style "\r" line separators with real newlines for TMP + fullDesc = fullDesc.Replace("\\r", "\n"); + descriptionText?.Set(fullDesc); + } + else + { + // Fallback to legacy string-table description if centralised pipeline returns empty + string itemDescription = GetItemDescription(item.m_tid); + descriptionText?.Set(itemDescription ?? "No description available"); + } + + // Get extended description exactly like C++ code: g_pGame->GetItemExtDesc(m_tid) + // C++ code doesn't check IsInitialized() - it just calls GetWideString() directly + // 完全按照C++代码获取扩展描述:g_pGame->GetItemExtDesc(m_tid) + // C++代码不检查IsInitialized() - 它直接调用GetWideString() + string szExtDesc = null; + try + { + var itemExtDescTab = EC_Game.GetItemExtDesc(); + if (itemExtDescTab != null) + { + // First try to get mapped message ID (like TryGetItemExtDesc does) + // 首先尝试获取映射的消息ID(如TryGetItemExtDesc所做) + if (EC_Game.TryGetItemMsg(item.m_tid, out int messageId, out int displayMode)) + { + szExtDesc = itemExtDescTab.GetWideString(messageId); + } + + // Fallback: direct lookup using tid (exactly like C++: m_ItemExtDesc.GetWideString(tid)) + // 回退:直接使用tid查找(完全像C++:m_ItemExtDesc.GetWideString(tid)) + if (string.IsNullOrEmpty(szExtDesc)) + { + szExtDesc = itemExtDescTab.GetWideString(item.m_tid); + } + } + } + catch (System.Exception ex) + { + // Only log once to avoid spam + // 仅记录一次以避免垃圾日志 + if (!m_HasLoggedExtDescError) + { + Debug.LogWarning($"[InventoryUI] Error getting extended description: {ex.Message}"); + m_HasLoggedExtDescError = true; + } + } + + // Display extended description if found (exactly like C++ checks: if (!szExtDesc || !szExtDesc[0])) + // 如果找到扩展描述则显示(完全像C++检查:if (!szExtDesc || !szExtDesc[0])) + string displayText = !string.IsNullOrEmpty(szExtDesc) + ? szExtDesc.Replace("\\r", "\n") + : ""; + + // Debug logging to diagnose issues + // 调试日志以诊断问题 + if (string.IsNullOrEmpty(displayText)) + { + Debug.Log($"[InventoryUI] Extended description is empty for tid={item.m_tid}. szExtDesc was null/empty."); + } + else + { + Debug.Log($"[InventoryUI] Found extended description for tid={item.m_tid}, length={displayText.Length}"); } // Setup equip and drop buttons SetupEquipButton(package, item); SetupDropButton(package, item); + // Show panel first + // 先显示面板 ShowDetailPanel(true); - } - /// - /// Fill equipment-specific details - /// - private void FillEquipmentDetails() - { - if (currentSelectedEquipment == null) + // Set text directly - if this causes rebuild issues, we'll use coroutine + // 直接设置文本 - 如果这导致重建问题,我们将使用协程 + if (extendedDescText != null) { - ClearEquipmentDetails(); - return; + extendedDescText.Set(displayText); + Debug.Log($"[InventoryUI] Set extended description text, extendedDescText is {(extendedDescText == null ? "null" : "not null")}"); } - - // Level Requirements - if (levelReqText != null) + else { - string levelReq = ""; - if (currentSelectedEquipment.LevelReq > 0) - { - levelReq = $"Level: {currentSelectedEquipment.LevelReq}"; - } - if (currentSelectedEquipment.ProfReq > 0) - { - if (!string.IsNullOrEmpty(levelReq)) levelReq += "\\r"; - levelReq += $"Profession: {currentSelectedEquipment.ProfReq}"; - } - levelReqText.Set(levelReq); + Debug.LogWarning("[InventoryUI] extendedDescText is null! Check Inspector assignment."); } - - // Stats Requirements - if (statsReqText != null) - { - List reqs = new List(); - if (currentSelectedEquipment.StrengthReq > 0) reqs.Add($"Str: {currentSelectedEquipment.StrengthReq}"); - if (currentSelectedEquipment.AgilityReq > 0) reqs.Add($"Agi: {currentSelectedEquipment.AgilityReq}"); - if (currentSelectedEquipment.VitalityReq > 0) reqs.Add($"Vit: {currentSelectedEquipment.VitalityReq}"); - if (currentSelectedEquipment.EnergyReq > 0) reqs.Add($"Ene: {currentSelectedEquipment.EnergyReq}"); - if (currentSelectedEquipment.ReputationReq > 0) reqs.Add($"Rep: {currentSelectedEquipment.ReputationReq}"); - - statsReqText.Set(string.Join("\\r", reqs)); - } - - // Endurance - if (enduranceText != null) - { - if (currentSelectedEquipment.MaxEndurance > 0) - { - int curEndurance = EC_IvtrEquip.VisualizeEndurance(currentSelectedEquipment.CurEndurance); - int maxEndurance = EC_IvtrEquip.VisualizeEndurance(currentSelectedEquipment.MaxEndurance); - string endurance = $"Endurance: {curEndurance}/{maxEndurance}"; - - if (currentSelectedEquipment.IsDestroying()) - { - endurance += " (DESTROYED)"; - } - else if (currentSelectedEquipment.CurEndurance < currentSelectedEquipment.MaxEndurance) - { - endurance += " (Damaged)"; - } - - enduranceText.Set(endurance); - } - else - { - enduranceText.Set("Endurance: N/A"); - } - } - - // Repair Cost - if (repairCostText != null) - { - if (currentSelectedEquipment.IsRepairable()) - { - int repairCost = currentSelectedEquipment.GetRepairCost(); - repairCostText.Set($"Repair Cost: {repairCost}"); - } - else - { - repairCostText.Set("Repair Cost: N/A"); - } - } - - // Properties + Base Stats + Engraved + Stones (more closely matching original) - if (propertiesText != null) - { - List lines = new List(); - - // Base stats decoded from element essence (damage/defense/speed/range/resists) - string baseStats = currentSelectedEquipment.GetBaseStatsForDisplay(); - if (!string.IsNullOrEmpty(baseStats)) - { - lines.Add(baseStats); - } - - // Add-on properties from detail payload (non-embedded, non-suite, non-engraved) - if (currentSelectedEquipment.Props.Count > 0) - { - foreach (var prop in currentSelectedEquipment.Props) - { - if (!prop.Embed && !prop.Suite && !prop.Engraved) - { - string propDesc = currentSelectedEquipment.FormatPropDesc(prop); - if (!string.IsNullOrEmpty(propDesc)) - { - lines.Add(propDesc); - } - } - } - - // Engraved properties (displayed after normal add-ons) - foreach (var prop in currentSelectedEquipment.Props) - { - if (prop.Engraved) - { - string propDesc = currentSelectedEquipment.FormatPropDesc(prop); - if (!string.IsNullOrEmpty(propDesc)) - { - lines.Add(propDesc); - } - } - } - } - - // Socketed stones (show name and a basic description) - if (currentSelectedEquipment.Holes != null && currentSelectedEquipment.Holes.Count > 0) - { - foreach (int holeTid in currentSelectedEquipment.Holes) - { - if (holeTid == 0) continue; - string stoneName = EC_IvtrItemUtils.Instance.ResolveItemName(holeTid); - // Try to fetch a description from string tables; fallback to name if unavailable - string stoneDesc = GetItemDescription(holeTid) ?? stoneName; - if (!string.IsNullOrEmpty(stoneName)) - { - lines.Add($"{stoneName}: {stoneDesc}"); - } - } - } - - string combined = string.Join("\\r", lines); - propertiesText.Set(combined); - } - - // Refinement - if (refineText != null) - { - if (currentSelectedEquipment.RefineLvl > 0) - { - refineText.Set($"Refinement Level: +{currentSelectedEquipment.RefineLvl}"); - } - else - { - refineText.Set("Refinement: None"); - } - } - - // Maker Information - if (makerText != null) - { - if (!string.IsNullOrEmpty(currentSelectedEquipment.Maker)) - { - makerText.Set($"Maker: {currentSelectedEquipment.Maker}"); - } - else - { - makerText.Set("Maker: Unknown"); - } - } - - // Price - if (priceText != null) - { - int scaledPrice = currentSelectedEquipment.GetScaledPrice(); - priceText.Set($"Price: {scaledPrice}"); - } - - // Holes - if (holesText != null) - { - if (currentSelectedEquipment.Holes.Count > 0) - { - int emptyHoles = currentSelectedEquipment.GetEmptyHoleNum(); - int totalHoles = currentSelectedEquipment.Holes.Count; - holesText.Set($"Holes: {totalHoles - emptyHoles}/{totalHoles} filled"); - } - else - { - holesText.Set("Holes: None"); - } - } - - // Suite Information - if (suiteText != null) - { - int suiteId = currentSelectedEquipment.GetSuiteID(); - if (suiteId > 0) - { - suiteText.Set($"Suite Equipment: {suiteId}"); - } - else - { - suiteText.Set("Suite: None"); - } - } - } - - /// - /// Clear equipment details - /// - private void ClearEquipmentDetails() - { - levelReqText?.Set(""); - statsReqText?.Set(""); - enduranceText?.Set(""); - repairCostText?.Set(""); - propertiesText?.Set(""); - refineText?.Set(""); - makerText?.Set(""); - priceText?.Set(""); - holesText?.Set(""); - suiteText?.Set(""); } private void SetupEquipButton(byte package, EC_IvtrItem item) diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs index 46a44dff91..b834389bf0 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrEquip.cs @@ -3,13 +3,16 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; using ModelRenderer.Scripts.GameData; +using ModelRenderer.Scripts.Common; using BrewMonster; using BrewMonster.Common; using BrewMonster.Network; using System.IO; +using System.Text; using System.Text.RegularExpressions; using System.Reflection; using BrewMonster.Scripts.Managers; +using BrewMonster.Scripts; namespace PerfectWorld.Scripts.Managers { @@ -49,22 +52,26 @@ namespace PerfectWorld.Scripts.Managers public const uint PEE_ENERGYREQ = 0x00010000; public const uint PEE_VITALITYREQ = 0x00020000; - // Property Effect Essence Indexes + // Property Effect Essence Indexes (must match C++ enum order) public const int PEEI_PHYDAMAGE = 0; - public const int PEEI_MAX_PHYDAMAGE = 1; + public const int PEEI_PHYDEF = 1; public const int PEEI_MAGICDAMAGE = 2; - public const int PEEI_MAX_MAGICDAMAGE = 3; - public const int PEEI_PHYDEF = 4; - public const int PEEI_GOLDDEF = 5; - public const int PEEI_WOODDEF = 6; - public const int PEEI_WATERDEF = 7; - public const int PEEI_FIREDEF = 8; - public const int PEEI_EARTHDEF = 9; - public const int PEEI_ATKDIST = 10; - public const int PEEI_HP = 11; - public const int PEEI_MP = 12; - public const int PEEI_DODGE = 13; - public const int MAX_PEEINDEX = 14; + 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; @@ -79,74 +86,7 @@ namespace PerfectWorld.Scripts.Managers public const int REFINE_DODGE = 9; public const int MAX_REFINEINDEX = 10; - // Item Description Colors - public const int ITEMDESC_COL_WHITE = 0; - public const int ITEMDESC_COL_GREEN = 1; - public const int ITEMDESC_COL_YELLOW = 2; - public const int ITEMDESC_COL_DARKGOLD = 3; - public const int ITEMDESC_COL_LIGHTBLUE = 4; - public const int ITEMDESC_COL_CYANINE = 5; - public const int ITEMDESC_COL_RED = 6; - public const int ITEMDESC_COL_GRAY = 7; - - // Item Description String IDs - public const int ITEMDESC_NAME = 1000; - public const int ITEMDESC_ADDPHYDAMAGE = 1001; - public const int ITEMDESC_MAXPHYDAMAGE = 1002; - public const int ITEMDESC_ADDMAGICDAMAGE = 1003; - public const int ITEMDESC_MAXMAGICDAMAGE = 1004; - public const int ITEMDESC_PHYDEFENCE = 1005; - public const int ITEMDESC_ALLMAGICDEF = 1006; - public const int ITEMDESC_GOLDDEFENCE = 1007; - public const int ITEMDESC_WOODDEFENCE = 1008; - public const int ITEMDESC_WATERDEFENCE = 1009; - public const int ITEMDESC_FIREDEFENCE = 1010; - public const int ITEMDESC_EARTHDEFENCE = 1011; - public const int ITEMDESC_ADDHP = 1012; - public const int ITEMDESC_ADDMP = 1013; - public const int ITEMDESC_STRENGTH = 1014; - public const int ITEMDESC_AGILITY = 1015; - public const int ITEMDESC_ENERGY = 1016; - public const int ITEMDESC_VITALITY = 1017; - public const int ITEMDESC_DODGE = 1018; - public const int ITEMDESC_DEADLYSTRIKE = 1019; - public const int ITEMDESC_ATKRATING = 1020; - public const int ITEMDESC_ATKTIME = 1021; - public const int ITEMDESC_ADDATKDIST = 1022; - public const int ITEMDESC_CASTTIME = 1023; - public const int ITEMDESC_ENDURANCE = 1024; - public const int ITEMDESC_REPAIRCOST = 1025; - public const int ITEMDESC_EQUIPMARK = 1026; - public const int ITEMDESC_MADEFROM = 1027; - public const int ITEMDESC_2STRINGS = 1028; - public const int ITEMDESC_REPUTATION_REQ = 1029; - public const int ITEMDESC_EQUIP_DESTROYING = 1030; - public const int ITEMDESC_EQUIP_REPAIR_NEED_ITEM = 1031; - public const int ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT = 1032; - public const int ITEMDESC_ERRORPROP = 1033; - public const int ITEMDESC_RANDOMPROP = 1034; - public const int ITEMDESC_ATK_DEGREE = 1035; - public const int ITEMDESC_DEF_DEGREE = 1036; - public const int ITEMDESC_SOULPOWER = 1037; - public const int ITEMDESC_VIGOUR = 1038; - public const int ITEMDESC_ADDRIDEONPETSPEED = 1039; - public const int ITEMDESC_PENETRATION = 1040; - public const int ITEMDESC_RESILIENCE = 1041; - public const int ITEMDESC_PROFVIEW = 1042; - public const int ITEMDESC_REQEXTRA = 1043; - public const int ITEMDESC_HPRECOVER = 1044; - public const int ITEMDESC_MPRECOVER = 1045; - public const int ITEMDESC_RUNSPEED = 1046; - public const int ITEMDESC_RUNSPEEDEXTRA = 1047; - public const int ITEMDESC_PHYRESIST = 1048; - public const int ITEMDESC_TOTAL_DEFENCE_ADD = 1049; - public const int ITEMDESC_GOLDRESIST = 1050; - public const int ITEMDESC_WOODRESIST = 1051; - public const int ITEMDESC_WATERRESIST = 1052; - public const int ITEMDESC_FIRERESIST = 1053; - public const int ITEMDESC_EARTHRESIST = 1054; - public const int ITEMDESC_ALLMAGICRESIST = 1055; - public const int ITEMDESC_EXP = 1056; + // Item Description Colors and String IDs are now in DescriptipionMsg enum from EC_FixedMsg.cs // Scale Types public const int SCALE_SELL = 1; @@ -261,55 +201,80 @@ namespace PerfectWorld.Scripts.Managers return GetBaseStatsDesc(); } - private string GetBaseStatsDesc() + private void AddBaseStatsDesc(int[] aPEEVals, int[] aRefines) { elementdataman edm = ElementDataManProvider.GetElementDataMan(); - if (edm == null) return string.Empty; + 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 string.Empty; + 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, dmgHighMax = 0; + int dmgLow = 0, dmgHigh = 0; bool hasDmgLow = TryGetNumber(data, new[] { "damage_low" }, out dmgLow); - bool hasDmgHighMax = TryGetNumber(data, new[] { "damage_high_max", "damage_high" }, out dmgHighMax); + bool hasDmgHigh = TryGetNumber(data, new[] { "damage_high_max", "damage_high" }, out dmgHigh); - List lines = new List(); - - if (hasDmgLow && hasDmgHighMax) + if (hasDmgLow && hasDmgHigh) { - // Physical damage range - lines.Add(string.Format("{0} {1}-{2}", GetItemDescString(ITEMDESC_ADDPHYDAMAGE), dmgLow, dmgHighMax)); + // Physical damage range - adjust by aPEEVals and aRefines (like C++: m_Essence.damage_low - aPEEVals[PEEI_PHYDAMAGE] + aRefines[REFINE_PHYDAMAGE]) + int adjDmgLow = dmgLow - aPEEVals[PEEI_PHYDAMAGE] + aRefines[REFINE_PHYDAMAGE]; + int adjDmgHigh = dmgHigh - aPEEVals[PEEI_PHYDAMAGE] - aPEEVals[PEEI_MAX_PHYDAMAGE] + aRefines[REFINE_PHYDAMAGE]; + + if (adjDmgLow > 0 || adjDmgHigh > 0 || aRefines[REFINE_PHYDAMAGE] != 0) + { + string dmgFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDAMAGE); + AddDescText(white, false, dmgFmt); + AddDescText(white, true, " {0}-{1}", adjDmgLow, adjDmgHigh); + } // Magic damage if available if (TryGetNumber(data, new[] { "magic_damage_low" }, out int mdLow) && TryGetNumber(data, new[] { "magic_damage_high_max", "magic_damage_high" }, out int mdHigh)) { - lines.Add(string.Format("{0} {1}-{2}", GetItemDescString(ITEMDESC_ADDMAGICDAMAGE), mdLow, 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 - float atkTime; - if (TryGetNumber(data, new[] { "attack_time", "atk_time" }, out atkTime) && atkTime > 0.0001f) + // Attack time/speed - adjust color based on PEE_ATKSPEED + int atkCol = white; + if ((PropEffectEssence() & (1 << PEEI_ATKSPEED)) != 0) + atkCol = lblue; + + if (TryGetNumber(data, new[] { "attack_speed", "atk_speed" }, out float atkSpeed) && atkSpeed > 0) { - // Show speed in attacks per second (1 / time) - float aps = 1.0f / atkTime; - lines.Add(string.Format("{0} {1:F2}", GetItemDescString(ITEMDESC_ATKTIME), aps)); - } - else if (TryGetNumber(data, new[] { "attack_speed", "atk_speed" }, out float atkSpeed) && atkSpeed > 0) - { - lines.Add(string.Format("{0} {1:F2}", GetItemDescString(ITEMDESC_ATKTIME), atkSpeed)); + // 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 + // Range - adjust by aPEEVals[PEEI_ATKDIST] float range; if (TryGetNumber(data, new[] { "range", "attack_range", "atk_range" }, out range)) { - lines.Add(string.Format("{0} {1:F2}", GetItemDescString(ITEMDESC_ADDATKDIST), 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 @@ -317,32 +282,152 @@ namespace PerfectWorld.Scripts.Managers // Try armor-style fields if (TryGetNumber(data, new[] { "defence_high", "defense_high", "defence", "defense" }, out int defHigh)) { - lines.Add(string.Format("{0} +{1}", GetItemDescString(ITEMDESC_PHYDEFENCE), defHigh)); + 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); + } } - // Elemental resistances (look for *_resist or specific names) - (string label, string[] names)[] res = new (string, string[])[] + // Dodge + if (TryGetNumber(data, new[] { "armor", "dodge" }, out int dodge)) { - (GetItemDescString(ITEMDESC_GOLDDEFENCE), new[]{"resist_metal","resistance_metal","gold_resist"}), - (GetItemDescString(ITEMDESC_WOODDEFENCE), new[]{"resist_wood","resistance_wood","wood_resist"}), - (GetItemDescString(ITEMDESC_WATERDEFENCE), new[]{"resist_water","resistance_water","water_resist"}), - (GetItemDescString(ITEMDESC_FIREDEFENCE), new[]{"resist_fire","resistance_fire","fire_resist"}), - (GetItemDescString(ITEMDESC_EARTHDEFENCE), new[]{"resist_earth","resistance_earth","earth_resist"}) + int adjDodge = dodge - aPEEVals[PEEI_DODGE] + aRefines[REFINE_DODGE]; + if (adjDodge > 0 || aRefines[REFINE_DODGE] != 0) + { + string dodgeFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_DODGE); + AddDescText(white, false, dodgeFmt); + AddDescText(white, true, " {0:+0;-#;+0}", adjDodge); + } + } + + // HP + if (TryGetNumber(data, new[] { "hp_enhance", "hp" }, out int hp)) + { + int adjHp = hp - aPEEVals[PEEI_HP] + aRefines[REFINE_HP]; + if (adjHp > 0 || aRefines[REFINE_HP] != 0) + { + string hpFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP); + AddDescText(white, false, hpFmt); + AddDescText(white, true, " {0:+0;-#;+0}", adjHp); + } + } + + // MP + if (TryGetNumber(data, new[] { "mp_enhance", "mp" }, out int mp)) + { + int adjMp = mp - aPEEVals[PEEI_MP]; + if (adjMp > 0) + { + string mpFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMP); + AddDescText(white, false, mpFmt); + AddDescText(white, true, " {0:+0;-#;+0}", adjMp); + } + } + + // Elemental resistances - adjust by aPEEVals and aRefines + // In C++: m_Essence.resistance[MAGICCLASS_GOLD] - aPEEVals[PEEI_GOLDDEF] + aRefines[REFINE_GOLDDEF] + // In C# struct: resistance is stored in magic_defences array + (DescriptipionMsg descId, int refineIdx, int peeIdx, int magicClassIdx)[] res = new (DescriptipionMsg, int, int, int)[] + { + (DescriptipionMsg.ITEMDESC_GOLDDEFENCE, REFINE_GOLDDEF, PEEI_GOLDDEF, 0), // MAGICCLASS_GOLD = 0 + (DescriptipionMsg.ITEMDESC_WOODDEFENCE, REFINE_WOODDEF, PEEI_WOODDEF, 1), // MAGICCLASS_WOOD = 1 + (DescriptipionMsg.ITEMDESC_WATERDEFENCE, REFINE_WATERDEF, PEEI_WATERDEF, 2), // MAGICCLASS_WATER = 2 + (DescriptipionMsg.ITEMDESC_FIREDEFENCE, REFINE_FIREDEF, PEEI_FIREDEF, 3), // MAGICCLASS_FIRE = 3 + (DescriptipionMsg.ITEMDESC_EARTHDEFENCE, REFINE_EARTHDEF, PEEI_EARTHDEF, 4) // MAGICCLASS_EARTH = 4 }; + + // Try to read from magic_defences array first (C# struct format) + bool foundResistanceArray = false; + try + { + var dataType = data.GetType(); + var magicDefencesField = dataType.GetField("magic_defences"); + if (magicDefencesField != null) + { + var magicDefences = magicDefencesField.GetValue(data); + if (magicDefences != null && magicDefences is Array) + { + foundResistanceArray = true; + Array arr = (Array)magicDefences; + for (int i = 0; i < res.Length && i < arr.Length; i++) + { + var magicDef = arr.GetValue(i); + if (magicDef != null) + { + var highField = magicDef.GetType().GetField("high"); + if (highField != null) + { + int v = Convert.ToInt32(highField.GetValue(magicDef)); + var descId = res[i].descId; + var refineIdx = res[i].refineIdx; + var peeIdx = res[i].peeIdx; + + int adjV = v - aPEEVals[peeIdx] + aRefines[refineIdx]; + if (adjV > 0 || aRefines[refineIdx] != 0) + { + string resFmt = GetItemDescString(descId); + AddDescText(white, false, resFmt); + AddDescText(white, true, " {0:+0;-#;+0}", adjV); + } + } + } + } + } + } + } + catch { } + + // Fallback: try reading from individual fields if array method didn't work + if (!foundResistanceArray) + { for (int i = 0; i < res.Length; i++) { - var label = res[i].label; - var names = res[i].names; + var descId = res[i].descId; + var refineIdx = res[i].refineIdx; + var peeIdx = res[i].peeIdx; + var magicClassIdx = res[i].magicClassIdx; + + // Try multiple field name patterns + string[] names = new[] + { + $"magic_defences[{magicClassIdx}].high", + $"magic_defences_{magicClassIdx}_high", + $"resistance_{magicClassIdx}", + $"resist_{magicClassIdx}", + "resist_metal", "resistance_metal", "gold_resist" // For index 0 + }; + if (TryGetNumber(data, names, out int v)) { - lines.Add(string.Format("{0} +{1}", label, 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); } } } - - return string.Join("\\r", lines); + } + } } - catch { return string.Empty; } + 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) @@ -721,7 +806,7 @@ namespace PerfectWorld.Scripts.Managers Maker = colorStr + Maker; // Add equipment mark display string - Maker = string.Format(GetItemDescString(ITEMDESC_EQUIPMARK), Maker); + Maker = string.Format(GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIPMARK), Maker); } MadeFrom = string.IsNullOrEmpty(mark) ? IMT_NULL : IMT_SIGN; } @@ -1177,16 +1262,16 @@ namespace PerfectWorld.Scripts.Managers if (index >= 0) return index; - int col = ITEMDESC_COL_WHITE; + int col = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; switch (FixProps) { - case 1: col = ITEMDESC_COL_GREEN; break; - case 2: col = ITEMDESC_COL_YELLOW; break; - case 3: col = ITEMDESC_COL_DARKGOLD; break; + 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 = ITEMDESC_COL_LIGHTBLUE; + col = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; break; } @@ -1204,7 +1289,100 @@ namespace PerfectWorld.Scripts.Managers #endregion - #region Description Methods + #region Description Methods (high level entry points) + + /// + /// Get normal in-inventory description, mirroring C++ CECIvtrEquip::GetNormalDesc. + /// This is a single formatted string using ^color codes and '\\r' as line separators. + /// + public string GetNormalDesc() + { + // Build addon and refine properties and save it (like C++ does first) + int[] aPEEVals = new int[MAX_PEEINDEX]; + int[] aRefines = new int[MAX_REFINEINDEX]; + for (int i = 0; i < MAX_PEEINDEX; i++) + aPEEVals[i] = 0; + for (int i = 0; i < MAX_REFINEINDEX; i++) + aRefines[i] = 0; + + m_strDesc = ""; + BuildAddOnPropDesc(aPEEVals, aRefines); + string strAddon = m_strDesc; + + // Reset and build description from scratch + m_strDesc = ""; + + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; + int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; + int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; + + // 1) Item name + AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_NAME), GetName()); + + // 1.5) Sub class name (loại trang bị) - like C++: AddDescText(white, true, pDescTab->GetWideString(ITEMDESC_CLASSNAME), m_pDBSubType->name); + // In C++, this is always called (no null check), so we always call it too + string subTypeName = GetSubTypeName(); + AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_CLASSNAME), subTypeName ?? ""); + + // 1.6) Item level (cấp vũ khí/trang bị) - like C++: AddDescText(-1, true, pDescTab->GetWideString(ITEMDESC_LEVEL), m_Essence.weapon_level); + int itemLevel = GetItemLevel(); + if (itemLevel > 0) + { + AddDescText(-1, true, GetItemDescString(DescriptipionMsg.ITEMDESC_LEVEL), itemLevel); + } + + // 2) Base stats from element data (damage/defence/speed/range/resists) + // Adjust base stats by subtracting aPEEVals and adding aRefines + AddBaseStatsDesc(aPEEVals, aRefines); + + // 3) Endurance (current / max) - adjust color based on PEE_ENDURANCE + if (MaxEndurance > 0) + { + int col = white; + if (CurEndurance == 0) + col = red; + else if ((PropEffectEssence() & (1 << PEEI_ENDURANCE)) != 0) + col = lblue; + + int curVis = VisualizeEndurance(CurEndurance); + int maxVis = VisualizeEndurance(MaxEndurance); + AddDescText(col, true, "{0} {1}/{2}", + GetItemDescString(DescriptipionMsg.ITEMDESC_ENDURANCE), curVis, maxVis); + } + + // 4) Requirements (level / stats / reputation) + AddRequirementDesc(aPEEVals); + AddReputationReqDesc(); + + // 4.5) Profession restriction (phái hạn chế) - like C++: AddProfReqDesc(m_iProfReq); + // In C++, this is always called regardless of value (the function checks internally) + AddProfReqDesc(ProfReq); + + // 5) Add-on properties (non-embedded, non-suite, non-engraved) + if (!string.IsNullOrEmpty(strAddon)) + m_strDesc += strAddon; + + // 6) Tessera / stones (socketed gems) + BuildTesseraDesc(); + + // 7) Sharpener properties + AddSharpenerDesc(); + + // 8) Engraved properties + AddEngravedDesc(); + + // 9) Suite information + AddSuiteDesc(); + + // 10) Maker & destroying info if any + AddMakerDesc(); + // Destroying description is added by caller when needed; keep it optional here. + + // 11) Price (sell price scaled) + AddPriceDesc(white, false); + + return m_strDesc; + } /// /// Get item description for booth buying @@ -1213,16 +1391,16 @@ namespace PerfectWorld.Scripts.Managers { m_strDesc = ""; - int white = ITEMDESC_COL_WHITE; + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; // Item name - AddDescText(white, true, GetItemDescString(ITEMDESC_NAME), GetName()); + AddDescText(white, true, GetItemDescString(DescriptipionMsg.ITEMDESC_NAME), GetName()); // Base stats from element data string baseStats = GetBaseStatsDesc(); if (!string.IsNullOrEmpty(baseStats)) { - m_strDesc += GetColorString(ITEMDESC_COL_WHITE); + m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_WHITE); m_strDesc += baseStats; m_strDesc += "\\r"; } @@ -1242,13 +1420,353 @@ namespace PerfectWorld.Scripts.Managers return m_strDesc; } + /// + /// Add concise requirement description (level / stats / profession). + /// This is a simplified mirror of the original C++ text; colors are kept white for now. + /// + private void AddRequirementDesc(int[] aPEEVals) + { + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; + int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; + + // Get host player for requirement checks (via GameRun if available) + object hostPlayer = null; + try + { + var gameRun = EC_Game.GetGameRun(); + if (gameRun != null) + { + // Try to get host player - method name may vary + hostPlayer = gameRun; // Placeholder - would need actual GetHostPlayer() method + } + } + catch { } + + uint dwPEE = PropEffectEssence(); + + // Level requirement + if (LevelReq > 0) + { + int col = white; + if (hostPlayer != null) + { + // In C++: pHost->GetMaxLevelSofar() >= m_iLevelReq ? white : red + // For now, use a simple check - would need GetMaxLevelSofar() equivalent + col = white; // TODO: Check actual player level + } + + string levelFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_LEVELREQ); + if (levelFmt.Contains("%d")) + AddDescText(col, true, levelFmt, LevelReq); + else + AddDescText(col, true, "{0} {1}", levelFmt, LevelReq); + } + + // Stat requirements - adjust color based on PEE flags and player stats + if (StrengthReq > 0) + { + int col = white; + if (hostPlayer != null) + { + // In C++: pHost->GetExtendProps().bs.strength < m_iStrengthReq ? red : ((dwPEE & PEE_STRENGTHREQ) ? lblue : white) + // For now, use simple check + if ((dwPEE & (1 << PEEI_STRENGTHREQ)) != 0) + col = lblue; + // TODO: Check actual player strength + } + + string strFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTHREQ); + if (strFmt.Contains("%d")) + AddDescText(col, true, strFmt, StrengthReq); + else + AddDescText(col, true, "{0} {1}", strFmt, StrengthReq); + } + if (AgilityReq > 0) + { + int col = white; + if (hostPlayer != null) + { + if ((dwPEE & (1 << PEEI_AGILITYREQ)) != 0) + col = lblue; + // TODO: Check actual player agility + } + + string agiFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITYREQ); + if (agiFmt.Contains("%d")) + AddDescText(col, true, agiFmt, AgilityReq); + else + AddDescText(col, true, "{0} {1}", agiFmt, AgilityReq); + } + if (VitalityReq > 0) + { + int col = white; + if (hostPlayer != null) + { + // TODO: Check PEE_VITALITYREQ flag and player vitality + } + + string vitFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_VITALITYREQ); + if (vitFmt.Contains("%d")) + AddDescText(col, true, vitFmt, VitalityReq); + else + AddDescText(col, true, "{0} {1}", vitFmt, VitalityReq); + } + if (EnergyReq > 0) + { + int col = white; + if (hostPlayer != null) + { + // TODO: Check PEE_ENERGYREQ flag and player energy + } + + string eneFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGYREQ); + if (eneFmt.Contains("%d")) + AddDescText(col, true, eneFmt, EnergyReq); + else + AddDescText(col, true, "{0} {1}", eneFmt, EnergyReq); + } + } + + /// + /// Get sub type name (loại trang bị) from element data + /// + private string GetSubTypeName() + { + elementdataman edm = ElementDataManProvider.GetElementDataMan(); + if (edm == null) return null; + + try + { + DATA_TYPE dt = DATA_TYPE.DT_INVALID; + object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); + if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); + if (data == null) return null; + + // Get id_sub_type from essence + uint idSubType = 0; + if (TryGetNumber(data, new[] { "id_sub_type" }, out idSubType) && idSubType > 0) + { + // Try to get sub type data - check if it's weapon or armor + DATA_TYPE subTypeDt = DATA_TYPE.DT_INVALID; + object subTypeData = null; + + // Try weapon sub type first + subTypeData = edm.get_data_ptr(idSubType, ID_SPACE.ID_SPACE_ESSENCE, ref subTypeDt); + if (subTypeDt == DATA_TYPE.DT_WEAPON_SUB_TYPE) + { + var nameField = subTypeData.GetType().GetField("name"); + if (nameField != null) + { + var nameArray = nameField.GetValue(subTypeData); + if (nameArray != null && nameArray is ushort[]) + { + return ByteToStringUtils.UshortArrayToUnicodeString((ushort[])nameArray); + } + } + } + + // Try armor sub type + subTypeDt = DATA_TYPE.DT_INVALID; + subTypeData = edm.get_data_ptr(idSubType, ID_SPACE.ID_SPACE_ESSENCE, ref subTypeDt); + if (subTypeDt == DATA_TYPE.DT_ARMOR_SUB_TYPE) + { + var nameField = subTypeData.GetType().GetField("name"); + if (nameField != null) + { + var nameArray = nameField.GetValue(subTypeData); + if (nameArray != null && nameArray is ushort[]) + { + return ByteToStringUtils.UshortArrayToUnicodeString((ushort[])nameArray); + } + } + } + } + } + catch { } + + return null; + } + + /// + /// Get item level (cấp vũ khí/trang bị) from element data + /// + private int GetItemLevel() + { + elementdataman edm = ElementDataManProvider.GetElementDataMan(); + if (edm == null) return 0; + + try + { + DATA_TYPE dt = DATA_TYPE.DT_INVALID; + object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); + if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); + if (data == null) return 0; + + // Try weapon_level first (for weapons) + if (TryGetNumber(data, new[] { "weapon_level" }, out int weaponLevel) && weaponLevel > 0) + return weaponLevel; + + // Try level (for armor and other items) + if (TryGetNumber(data, new[] { "level" }, out int level) && level > 0) + return level; + } + catch { } + + return 0; + } + + /// + /// Get character combo ID (phái hạn chế) from element data + /// + private uint GetCharacterComboId() + { + elementdataman edm = ElementDataManProvider.GetElementDataMan(); + if (edm == null) return 0; + + try + { + DATA_TYPE dt = DATA_TYPE.DT_INVALID; + object data = edm.get_data_ptr(unchecked((uint)TemplateId), ID_SPACE.ID_SPACE_ESSENCE, ref dt); + if (data == null) data = TryFindElementByScanningArraysLocal(edm, unchecked((uint)TemplateId)); + if (data == null) return 0; + + if (TryGetNumber(data, new[] { "character_combo_id" }, out uint comboId)) + return comboId; + } + catch { } + + return 0; + } + + /// + /// Add profession requirement description (phái hạn chế) + /// Like C++: AddProfReqDesc(int iProfReq) - displays class restrictions + /// + private void AddProfReqDesc(int profReq) + { + // In C++: if (CECProfConfig::Instance().ContainsAllProfession(iProfReq)) return; + // Check if all professions are allowed - if so, don't display restriction + // If profReq == 0, it means no restriction (all professions allowed) + // If all 12 bits are set (0xFFF = 4095), it also means all professions allowed + if (profReq == 0) + return; // No restriction + + const int NUM_PROFESSION = 12; + const int allProfMask = (1 << NUM_PROFESSION) - 1; // 0xFFF = 4095 (all 12 bits set) + if ((profReq & allProfMask) == allProfMask) + return; // All professions allowed + + // Check if any profession bit is set - if none, don't display + bool hasAnyProf = false; + for (int i = 0; i < NUM_PROFESSION; i++) + { + if ((profReq & (1 << i)) != 0) + { + hasAnyProf = true; + break; + } + } + if (!hasAnyProf) + return; // No profession restriction specified + + int white = (int)DescriptipionMsg.ITEMDESC_COL_WHITE; + int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; + + // Get host player profession for color check + int playerProf = -1; + try + { + var gameRun = EC_Game.GetGameRun(); + if (gameRun != null) + { + var hostPlayer = gameRun.GetHostPlayer(); + if (hostPlayer != null) + { + playerProf = hostPlayer.GetProfession(); + } + } + } + catch { } + + int col = white; + if (playerProf >= 0) + { + // In C++: col = (iProfReq & (1 << pHost->GetProfession())) ? ITEMDESC_COL_WHITE : ITEMDESC_COL_RED; + if ((profReq & (1 << playerProf)) == 0) + col = red; + } + + string profFmt = GetItemDescString(DescriptipionMsg.ITEMDESC_PROFESSIONREQ); + AddDescText(col, false, profFmt); + + // List all allowed professions + // In C++: for (int i=0; i < NUM_PROFESSION; i++) { if (iProfReq & (1 << i)) { m_strDesc += _AL(" "); AddDescText(col, false, pGameRun->GetProfName(i)); } } + // Profession message IDs matching C++ GetProfName: FIXMSG_PROF_WARRIOR, FIXMSG_PROF_MAGE, etc. + FixedMsg[] profMsgIds = new FixedMsg[] + { + FixedMsg.FIXMSG_PROF_WARRIOR, // 0 + FixedMsg.FIXMSG_PROF_MAGE, // 1 + FixedMsg.FIXMSG_PROF_MONK, // 2 + FixedMsg.FIXMSG_PROF_HAG, // 3 + FixedMsg.FIXMSG_PROF_ORC, // 4 + FixedMsg.FIXMSG_PROF_GHOST, // 5 + FixedMsg.FIXMSG_PROF_ARCHOR, // 6 + FixedMsg.FIXMSG_PROF_ANGEL, // 7 + FixedMsg.FIXMSG_PROF_JIANLING, // 8 + FixedMsg.FIXMSG_PROF_MEILING, // 9 + FixedMsg.FIXMSG_PROF_YEYING, // 10 + FixedMsg.FIXMSG_PROF_YUEXIAN // 11 + }; + + for (int i = 0; i < NUM_PROFESSION && i < profMsgIds.Length; i++) + { + if ((profReq & (1 << i)) != 0) + { + // In C++, space is always added before each profession name + m_strDesc += " "; + // Get profession name from fixed messages (like C++ GetProfName does) + // C++ uses: pStrTab->GetWideString(s_ProfDesc[i]) where s_ProfDesc[i] = FIXMSG_PROF_WARRIOR, etc. + string profName = null; + try + { + var fixedMsgs = EC_Game.GetFixedMsgs(); + if (fixedMsgs != null && fixedMsgs.IsInitialized()) + { + profName = fixedMsgs.GetWideString((int)profMsgIds[i]); + // Trim whitespace if present + if (!string.IsNullOrEmpty(profName)) + profName = profName.Trim(); + } + } + catch (Exception ex) + { + UnityEngine.Debug.LogWarning($"[EC_IvtrEquip] Failed to get profession name for index {i}: {ex.Message}"); + } + + // If we got a valid name, use it; otherwise use fallback + if (!string.IsNullOrEmpty(profName)) + { + AddDescText(col, false, profName); + } + else + { + // Fallback: show profession index (should not happen if fixed_msg.txt is loaded correctly) + UnityEngine.Debug.LogWarning($"[EC_IvtrEquip] Profession name not found for index {i}, enum value {(int)profMsgIds[i]}, profReq={profReq}"); + AddDescText(col, false, $"P{i}"); + } + } + } + + m_strDesc += "\\r"; + } + /// /// Add price description /// public void AddPriceDesc(int col, bool repair) { if (repair) - AddDescText(col, false, GetItemDescString(ITEMDESC_REPAIRCOST), GetRepairCost()); + AddDescText(col, false, GetItemDescString(DescriptipionMsg.ITEMDESC_REPAIRCOST), GetRepairCost()); else AddDescText(col, true, "Price: {0}", GetScaledPrice()); } @@ -1270,12 +1788,254 @@ namespace PerfectWorld.Scripts.Managers /// /// Format refine data into a string + /// (hack function, do NOT use it in multi-thread environment) /// public string FormatRefineData(uint addonId) { - // This would normally query the equipment addon data - // For now, return empty string + DATA_TYPE dataType = DATA_TYPE.DT_INVALID; + elementdataman edm = EC_Game.GetElementDataMan(); + if (edm == null) return ""; + + object data = edm.get_data_ptr(addonId, ID_SPACE.ID_SPACE_ADDON, ref dataType); + if (dataType != DATA_TYPE.DT_EQUIPMENT_ADDON || data == null) + return ""; + + EQUIPMENT_ADDON pType = (EQUIPMENT_ADDON)data; + string szTxt = ""; + + // save current member for backup + string tmpDesc = m_strDesc; + m_strDesc = ""; + + // get the original refine data + int[] aRefines = new int[MAX_REFINEINDEX]; + for (int i = 0; i < MAX_REFINEINDEX; i++) + aRefines[i] = 0; + + int[] paramArray = new int[] { pType.param1, pType.param2, pType.param3 }; + AddOneAddOnPropDesc((int)pType.id, paramArray, null, aRefines, true); + + // get splitter + // In C++: g_pGame->GetGameRun()->GetUIManager()->GetInGameUIMan()->GetStringFromTable(8656) + string szSplitter = " "; // Default splitter + try + { + var gameRun = EC_Game.GetGameRun(); + if (gameRun != null) + { + // Try to get from UI manager if available + // Note: This may not be fully implemented in C# yet + var fixedMsgs = EC_Game.GetFixedMsgs(); + if (fixedMsgs != null) + { + string splitterStr = fixedMsgs.GetWideString(8656); + if (!string.IsNullOrEmpty(splitterStr)) + szSplitter = splitterStr; + } + } + } + catch + { + // Use default splitter if lookup fails + } + + int findSplitter = 0; + for (int pos = 0; pos < szSplitter.Length; pos++) + { + findSplitter = m_strDesc.IndexOf(szSplitter[pos]); + if (findSplitter >= 0) + break; + } + + szTxt = (findSplitter <= 0) ? m_strDesc : m_strDesc.Substring(0, findSplitter); + + // restore member because previous method may modify it + m_strDesc = tmpDesc; + + // replace all old splitter to new splitter + const string OldSplitter = "\\r"; + const string NewSplitter = " "; + string result = ""; + findSplitter = 0; + while (true) + { + int curfind = szTxt.IndexOf(OldSplitter, findSplitter); + if (curfind >= 0) + { + if (!string.IsNullOrEmpty(result)) + result += NewSplitter; + if (curfind > findSplitter) + { + result += szTxt.Substring(findSplitter, curfind - findSplitter); + } + findSplitter = curfind + OldSplitter.Length; + if (findSplitter >= szTxt.Length) + break; + } + else + { + if (!string.IsNullOrEmpty(result)) + result += NewSplitter; + result += szTxt.Substring(findSplitter); + break; + } + } + szTxt = result; + + // check the special refine property group + int[] ALLMAGIC_REFINE = new int[] { REFINE_GOLDDEF, REFINE_WOODDEF, REFINE_WATERDEF, REFINE_FIREDEF, REFINE_EARTHDEF }; + int[] ALLDAMAGE_REFINE = new int[] { REFINE_PHYDAMAGE, REFINE_MAGICDAMAGE }; + int[] ALLDEFENCE_REFINE = new int[] { REFINE_PHYDEF, REFINE_GOLDDEF, REFINE_WOODDEF, REFINE_WATERDEF, REFINE_FIREDEF, REFINE_EARTHDEF }; + + if (CheckSpecialRefineType(aRefines, ALLMAGIC_REFINE, ALLMAGIC_REFINE.Length)) + { + int value = aRefines[ALLMAGIC_REFINE[0]]; + // Format with %+d equivalent: always show sign + string szRefine = string.Format("{0} {1}{2}", + GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF), + value >= 0 ? "+" : "", value); + if (!string.IsNullOrEmpty(szTxt)) + szTxt += NewSplitter; + szTxt += szRefine; + } + else if (CheckSpecialRefineType(aRefines, ALLDAMAGE_REFINE, ALLDAMAGE_REFINE.Length)) + { + int value = aRefines[ALLDAMAGE_REFINE[0]]; + // Format with %+d equivalent: always show sign + string szRefine = string.Format("{0} {1}{2}", + GetItemDescString(DescriptipionMsg.ITEMDESC_ADDDAMAGE), + value >= 0 ? "+" : "", value); + if (!string.IsNullOrEmpty(szTxt)) + szTxt += NewSplitter; + szTxt += szRefine; + } + else if (CheckSpecialRefineType(aRefines, ALLDEFENCE_REFINE, ALLDEFENCE_REFINE.Length)) + { + int value = aRefines[ALLDEFENCE_REFINE[0]]; + // Format with %+d equivalent: always show sign + string szRefine = string.Format("{0} {1}{2}", + GetItemDescString(DescriptipionMsg.ITEMDESC_DEFENCE), + value >= 0 ? "+" : "", value); + if (!string.IsNullOrEmpty(szTxt)) + szTxt += NewSplitter; + szTxt += szRefine; + } + else + { + int descId = 0; + for (int refineIndex = 0; refineIndex < MAX_REFINEINDEX; refineIndex++) + { + if (aRefines[refineIndex] == 0) + continue; + + // do NOT use loop because the enum value may changed + switch (refineIndex) + { + case REFINE_PHYDAMAGE: + descId = (int)DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE; + break; + case REFINE_MAGICDAMAGE: + descId = (int)DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE; + break; + case REFINE_PHYDEF: + descId = (int)DescriptipionMsg.ITEMDESC_PHYDEFENCE; + break; + case REFINE_GOLDDEF: + descId = (int)DescriptipionMsg.ITEMDESC_GOLDDEFENCE; + break; + case REFINE_WOODDEF: + descId = (int)DescriptipionMsg.ITEMDESC_WOODDEFENCE; + break; + case REFINE_WATERDEF: + descId = (int)DescriptipionMsg.ITEMDESC_WATERDEFENCE; + break; + case REFINE_FIREDEF: + descId = (int)DescriptipionMsg.ITEMDESC_FIREDEFENCE; + break; + case REFINE_EARTHDEF: + descId = (int)DescriptipionMsg.ITEMDESC_EARTHDEFENCE; + break; + case REFINE_HP: + descId = (int)DescriptipionMsg.ITEMDESC_ADDHP; + break; + case REFINE_DODGE: + descId = (int)DescriptipionMsg.ITEMDESC_DODGE; + break; + default: + descId = -1; + break; + } + + if (descId >= 0) + { + int value = aRefines[refineIndex]; + // Format with %+d equivalent: always show sign + string szRefine = string.Format("{0} {1}{2} ", + GetItemDescString((DescriptipionMsg)descId), + value >= 0 ? "+" : "", value); + if (!string.IsNullOrEmpty(szTxt)) + szTxt += NewSplitter; + szTxt += szRefine; + } + } + } + + return szTxt; + } + + /// + /// Get refine addon ID (virtual method, override in derived classes) + /// + public virtual uint GetRefineAddOn() + { + return 0; + } + + /// + /// Check the special refine property + /// + private bool CheckSpecialRefineType(int[] aRefines, int[] types, int typeCount) + { + // check the special refine property + int sameValue = 0; + + for (int refineIndex = 0; refineIndex < MAX_REFINEINDEX; refineIndex++) + { + bool checked_ = false; + for (int i = 0; i < typeCount; i++) + { + if (types[i] == refineIndex) + { + if (sameValue == 0) + { + if (aRefines[refineIndex] == 0) + { + // specific property was not found + return false; + } + sameValue = aRefines[refineIndex]; + } + else if (sameValue != aRefines[refineIndex]) + { + // property values were different + return false; + } + + checked_ = true; + break; + } + } + + // check other refine property + if (!checked_ && aRefines[refineIndex] != 0) + { + // has other refine property + return false; + } + } + + return true; } /// @@ -1375,50 +2135,87 @@ namespace PerfectWorld.Scripts.Managers /// /// Get item description string by ID + /// Primary source: item_desc.txt table (loaded via EC_Game.GetItemDesc()) + /// Fallback: Directly reads from item_desc.txt file when table lookup fails /// - private string GetItemDescString(int id) + 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 && tab.IsInitialized()) + if (tab != null) { - string s = tab.GetWideString(id); + string s = tab.GetWideString(enumValue); if (!string.IsNullOrEmpty(s)) return s; } - // Fallback labels for common IDs when the table lacks #_index entries - switch (id) + + // Fallback: Directly read f rom item_desc.txt file + // This handles cases where table lookup fails but the string exists in the file + try { - case ITEMDESC_ADDPHYDAMAGE: return "Công vật lý"; - case ITEMDESC_ADDMAGICDAMAGE: return "TC phép thuật"; - case ITEMDESC_PHYDEFENCE: return "Thủ vật lý"; - case ITEMDESC_GOLDDEFENCE: return "Kháng Kim"; - case ITEMDESC_WOODDEFENCE: return "Kháng Mộc"; - case ITEMDESC_WATERDEFENCE: return "Kháng Thủy"; - case ITEMDESC_FIREDEFENCE: return "Kháng Hỏa"; - case ITEMDESC_EARTHDEFENCE: return "Kháng Thổ"; - case ITEMDESC_ATKTIME: return "Tốc độ TC(lần/giây)"; - case ITEMDESC_ADDATKDIST: return "Phạm vi tấn công"; - case ITEMDESC_NAME: return "Tên"; - case ITEMDESC_ERRORPROP: return "Thuộc tính không xác định {0}"; - case ITEMDESC_ADDHP: return "HP"; - case ITEMDESC_ADDMP: return "MP"; - case ITEMDESC_DODGE: return "Né tránh"; - case ITEMDESC_DEADLYSTRIKE: return "Chí mạng"; - case ITEMDESC_HPRECOVER: return "Hồi phục HP"; - case ITEMDESC_MPRECOVER: return "Hồi phục MP"; - case ITEMDESC_RUNSPEED: return "Tốc độ chạy"; - case ITEMDESC_RUNSPEEDEXTRA: return "Tốc độ chạy (%)"; - case ITEMDESC_PHYRESIST: return "Kháng vật lý (%)"; - case ITEMDESC_TOTAL_DEFENCE_ADD: return "Tổng kháng ngũ hành (%)"; - case ITEMDESC_GOLDRESIST: return "Kháng Kim (%)"; - case ITEMDESC_WOODRESIST: return "Kháng Mộc (%)"; - case ITEMDESC_WATERRESIST: return "Kháng Thủy (%)"; - case ITEMDESC_FIRERESIST: return "Kháng Hỏa (%)"; - case ITEMDESC_EARTHRESIST: return "Kháng Thổ (%)"; - case ITEMDESC_ALLMAGICRESIST: return "Tổng kháng phép (%)"; - case ITEMDESC_EXP: return "Kinh nghiệm (%)"; - default: - return $"String_{id}"; + string filePath = Path.Combine(Application.streamingAssetsPath, "configs", "item_desc.txt"); + if (File.Exists(filePath)) + { + string content = File.ReadAllText(filePath, Encoding.UTF8); + string[] lines = content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + bool foundBegin = false; + int currentIndex = 0; + + foreach (string line in lines) + { + string trimmed = line.Trim(); + if (trimmed.Length == 0) continue; + + if (trimmed.Equals("#_begin", StringComparison.OrdinalIgnoreCase)) + { + foundBegin = true; + continue; + } + + if (!foundBegin) continue; + + // Skip comments + if (trimmed.StartsWith("#") || trimmed.StartsWith("//")) continue; + + // Remove quotes if present + if (trimmed.StartsWith("\"") && trimmed.EndsWith("\"")) + { + trimmed = trimmed.Substring(1, trimmed.Length - 2); + } + + if (currentIndex == enumValue) + { + return trimmed; + } + + currentIndex++; + } + } } + catch (Exception ex) + { + Debug.LogWarning($"[EC_IvtrEquip] Failed to read item_desc.txt fallback for ID {enumValue}: {ex.Message}"); + } + + // Final fallback: Return enum value as string + return enumValue.ToString(); + } + + /// + /// Add resistance property description with proper formatting + /// + private void AddResistDesc(int color, DescriptipionMsg resistId, int value, bool local) + { + string resistFmt = GetItemDescString(resistId); + int displayValue = local ? VisualizeFloatPercent(value) : value; + // Check if format string has placeholder, if not append value with + sign + if (resistFmt.Contains("%d") || resistFmt.Contains("%+d") || resistFmt.Contains("{0}")) + AddDescText(color, true, resistFmt, displayValue); + else + AddDescText(color, true, "{0} +{1}", resistFmt, displayValue); } /// @@ -1428,7 +2225,7 @@ namespace PerfectWorld.Scripts.Managers { if (color >= 0) { - string colorStr = GetColorString(color); + string colorStr = GetColorString((DescriptipionMsg)color); m_strDesc += colorStr; } @@ -1511,6 +2308,14 @@ namespace PerfectWorld.Scripts.Managers i = j; continue; } + else if (spec == 's') + { + object val = argIndex < args.Length ? args[argIndex++] : ""; + string s = val != null ? val.ToString() : ""; + sb.Append(s); + i = j; + continue; + } } // Fallback: treat '%' as literal if format not recognized @@ -1521,20 +2326,21 @@ namespace PerfectWorld.Scripts.Managers /// /// Get color string for color ID + /// Returns color codes in ^RRGGBB format (6 hex digits) for text formatting /// - private string GetColorString(int colorId) + private string GetColorString(DescriptipionMsg colorId) { switch (colorId) { - case ITEMDESC_COL_WHITE: return "^FF"; - case ITEMDESC_COL_GREEN: return "^GF"; - case ITEMDESC_COL_YELLOW: return "^YF"; - case ITEMDESC_COL_DARKGOLD: return "^DF"; - case ITEMDESC_COL_LIGHTBLUE: return "^BF"; - case ITEMDESC_COL_CYANINE: return "^CF"; - case ITEMDESC_COL_RED: return "^RF"; - case ITEMDESC_COL_GRAY: return "^gF"; - default: return "^FF"; + 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 } } @@ -1569,762 +2375,1647 @@ namespace PerfectWorld.Scripts.Managers return value / 100; } + /// + /// Helper method to convert int to float (bit reinterpretation) + /// + private float IntToFloat(int value) + { + return BitConverter.ToSingle(BitConverter.GetBytes(value), 0); + } + + /// + /// Helper method to convert float to int (bit reinterpretation) + /// + private int FloatToInt(float value) + { + return BitConverter.ToInt32(BitConverter.GetBytes(value), 0); + } + + /// + /// Add range value description for normal integer values (replaces ADD_RANGE_VALUE_DESC_ID_NORMAL macro) + /// + private void AddRangeValueDescIdNormal(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + if (local) + { + if (p0 != p1) + { + AddDescText(color, false, GetItemDescString(idString), p0); + AddDescText(color, true, "~{0}", p1); + } + else + { + AddDescText(color, true, GetItemDescString(idString), p0); + } + } + else + { + AddDescText(color, true, GetItemDescString(idString), p0); + } + } + + /// + /// Add range value description for float values (replaces ADD_RANGE_VALUE_DESC_ID_FLOAT macro) + /// + private void AddRangeValueDescIdFloat(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + float f0 = IntToFloat(p0); + float f1 = IntToFloat(p1); + if (local) + { + if (p0 != p1) + { + AddDescText(color, false, GetItemDescString(idString), f0); + AddDescText(color, true, "~{0:F2}", f1); + } + else + { + AddDescText(color, true, GetItemDescString(idString), f0); + } + } + else + { + AddDescText(color, true, GetItemDescString(idString), f0); + } + } + + /// + /// Add range value description for percent values (replaces ADD_RANGE_VALUE_DESC_ID_PERCENT macro) + /// + private void AddRangeValueDescIdPercent(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + if (local) + { + int v0 = VisualizeFloatPercent(p0); + int v1 = VisualizeFloatPercent(p1); + if (v0 != v1) + { + AddDescText(color, false, GetItemDescString(idString), v0); + AddDescText(color, true, "~{0}%%", v1); + } + else + { + AddDescText(color, true, GetItemDescString(idString), v0); + } + } + else + { + AddDescText(color, true, GetItemDescString(idString), p0); + } + } + + /// + /// Add range value description for minus percent values variant 1 (replaces ADD_RANGE_VALUE_DESC_ID_MINUS_PERCENT_1 macro) + /// + private void AddRangeValueDescIdMinusPercent1(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + if (local) + { + int v0 = -VisualizeFloatPercent(p0); + int v1 = VisualizeFloatPercent(p1); + if (v0 != v1) + { + AddDescText(color, false, GetItemDescString(idString), v0); + AddDescText(color, true, "~{0}%%", v1); + } + else + { + AddDescText(color, true, GetItemDescString(idString), v0); + } + } + else + { + AddDescText(color, true, GetItemDescString(idString), -p0); + } + } + + /// + /// Add range value description for minus percent values variant 2 (replaces ADD_RANGE_VALUE_DESC_ID_MINUS_PERCENT_2 macro) + /// + private void AddRangeValueDescIdMinusPercent2(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + if (local) + { + int v0 = -VisualizeFloatPercent(p0); + int v1 = VisualizeFloatPercent(p1); + if (v0 != v1) + { + AddDescText(color, false, GetItemDescString(idString), v0); + AddDescText(color, true, "~{0}%%", v1); + } + else + { + AddDescText(color, true, GetItemDescString(idString), v0); + } + } + else + { + AddDescText(color, true, GetItemDescString(idString), -VisualizeFloatPercent(p0)); + } + } + + /// + /// Add range value description for half values (replaces ADD_RANGE_VALUE_DESC_ID_HALF macro) + /// + private void AddRangeValueDescIdHalf(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + int h0 = p0 / 2; + int h1 = p1 / 2; + if (local) + { + if (h0 != h1) + { + AddDescText(color, false, GetItemDescString(idString), h0); + AddDescText(color, true, "~{0}", h1); + } + else + { + AddDescText(color, true, GetItemDescString(idString), h0); + } + } + else + { + AddDescText(color, true, GetItemDescString(idString), h0); + } + } + + /// + /// Add range value description for string normal values (replaces ADD_RANGE_VALUE_DESC_STR_NORMAL macro) + /// + private void AddRangeValueDescStrNormal(DescriptipionMsg idString, int p0, int p1, bool local, int color) + { + if (local) + { + if (p0 != p1) + { + AddDescText(color, false, GetItemDescString(idString)); + AddDescText(color, true, " %+d~%d", p0, p1); + } + else + { + AddDescText(color, false, GetItemDescString(idString)); + AddDescText(color, true, " %+d", p0); + } + } + else + { + AddDescText(color, false, GetItemDescString(idString)); + AddDescText(color, true, " %+d", p0); + } + } + /// /// Add one add-on property description /// private void AddOneAddOnPropDesc(int idProp, int[] param, int[] aPEEVals, int[] aRefines, bool local) { + // Extract parameters from array + int p0 = param != null && param.Length > 0 ? param[0] : 0; + int p1 = param != null && param.Length > 1 ? param[1] : 0; + int p2 = param != null && param.Length > 2 ? param[2] : 0; + byte propType = GetPropertyType(idProp); + int color = -1; if (!IsSharpenerProperty(propType)) { switch (propType) - { - case 0: - if (!local && aPEEVals != null) aPEEVals[PEEI_PHYDAMAGE] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDPHYDAMAGE)); - AddDescText(-1, true, " %+d", param[0]); - break; - case 1: + { + 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 (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_MAXPHYDAMAGE), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXPHYDAMAGE), param[0]); } + 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 { - if (aPEEVals != null) aPEEVals[PEEI_MAX_PHYDAMAGE] += param[0]; - AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXPHYDAMAGE), param[0]); + AddDescText(color, true, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); } - break; - case 2: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDPHYDAMAGE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 3: - if (!local && aPEEVals != null) aPEEVals[PEEI_MAGICDAMAGE] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMAGICDAMAGE)); - AddDescText(-1, true, " %+d", param[0]); - break; - case 4: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_MAXMAGICDAMAGE), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXMAGICDAMAGE), param[0]); } - } - else - { - if (aPEEVals != null) aPEEVals[PEEI_MAX_MAGICDAMAGE] += param[0]; - AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXMAGICDAMAGE), param[0]); - } - break; - case 5: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDMAGICDAMAGE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; + 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; + } + } - case 6: - if (!local && aPEEVals != null) { aPEEVals[PEEI_PHYDEF] += param[0]; aPEEVals[PEEI_PHYDAMAGE] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDPHYDAMAGE)); - AddDescText(-1, true, " %+d", -param[1]); - break; - case 7: - if (!local && aPEEVals != null) { aPEEVals[PEEI_PHYDAMAGE] += param[0]; aPEEVals[PEEI_PHYDEF] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDPHYDAMAGE)); - AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); - AddDescText(-1, true, " %+d", -param[1]); - break; - case 8: - if (!local && aPEEVals != null) - { - aPEEVals[PEEI_MAGICDAMAGE] += param[0]; - aPEEVals[PEEI_GOLDDEF] -= param[1]; aPEEVals[PEEI_WOODDEF] -= param[1]; aPEEVals[PEEI_WATERDEF] -= param[1]; aPEEVals[PEEI_FIREDEF] -= param[1]; aPEEVals[PEEI_EARTHDEF] -= param[1]; - } - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMAGICDAMAGE)); - AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICDEF)); - AddDescText(-1, true, " %+d", -param[1]); - break; - - case 9: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKTIME), -BitConverter.ToSingle(BitConverter.GetBytes(param[0]), 0)); - else AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKTIME), -param[0] * 0.05f); - break; - case 10: - float dist = BitConverter.ToSingle(BitConverter.GetBytes(param[0]), 0); - if (aPEEVals != null) - { - float cur = BitConverter.ToSingle(BitConverter.GetBytes(aPEEVals[PEEI_ATKDIST]), 0); - cur += dist; - aPEEVals[PEEI_ATKDIST] = BitConverter.ToInt32(BitConverter.GetBytes(cur), 0); - } - AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDATKDIST), dist); - break; - case 11: - AddDescText(-1, true, GetItemDescString(ITEMDESC_CASTTIME), -(local ? VisualizeFloatPercent(param[0]) : param[0])); - break; - - case 12: - if (!local && aPEEVals != null) aPEEVals[PEEI_PHYDEF] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - break; - case 13: - AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYDEFENCE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 14: - if (!local && aPEEVals != null) - { - aPEEVals[PEEI_GOLDDEF] += param[0]; aPEEVals[PEEI_WOODDEF] += param[0]; aPEEVals[PEEI_WATERDEF] += param[0]; aPEEVals[PEEI_FIREDEF] += param[0]; aPEEVals[PEEI_EARTHDEF] += param[0]; - } - AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICDEF)); - AddDescText(-1, true, " %+d", param[0]); - break; - case 15: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_GOLDDEFENCE)); - if (param[0] != param[1]) AddDescText(-1, true, " %d~%d", param[0], param[1]); else AddDescText(-1, true, " %d", param[0]); - } - else - { - if (aPEEVals != null) aPEEVals[PEEI_GOLDDEF] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_GOLDDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - } - break; - case 16: - AddDescText(-1, true, GetItemDescString(ITEMDESC_GOLDDEFENCE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 17: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_WOODDEFENCE)); - if (param[0] != param[1]) AddDescText(-1, true, " %d~%d", param[0], param[1]); else AddDescText(-1, true, " %d", param[0]); - } - else - { - if (aPEEVals != null) aPEEVals[PEEI_WOODDEF] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_WOODDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - } - break; - case 18: - AddDescText(-1, true, GetItemDescString(ITEMDESC_WOODDEFENCE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 19: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_WATERDEFENCE)); - if (param[0] != param[1]) AddDescText(-1, true, " %d~%d", param[0], param[1]); else AddDescText(-1, true, " %d", param[0]); - } - else - { - if (aPEEVals != null) aPEEVals[PEEI_WATERDEF] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_WATERDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - } - break; - case 20: - AddDescText(-1, true, GetItemDescString(ITEMDESC_WATERDEFENCE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 21: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_FIREDEFENCE)); - if (param[0] != param[1]) AddDescText(-1, true, " %d~%d", param[0], param[1]); else AddDescText(-1, true, " %d", param[0]); - } - else - { - if (aPEEVals != null) aPEEVals[PEEI_FIREDEF] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_FIREDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - } - break; - case 22: - AddDescText(-1, true, GetItemDescString(ITEMDESC_FIREDEFENCE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 23: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_EARTHDEFENCE)); - if (param[0] != param[1]) AddDescText(-1, true, " %d~%d", param[0], param[1]); else AddDescText(-1, true, " %d", param[0]); - } - else - { - if (aPEEVals != null) aPEEVals[PEEI_EARTHDEF] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_EARTHDEFENCE)); - AddDescText(-1, true, " %+d", param[0]); - } - break; - case 24: - AddDescText(-1, true, GetItemDescString(ITEMDESC_EARTHDEFENCE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - - case 25: - AddDescText(-1, true, GetItemDescString(ITEMDESC_GOLDDEFENCE) + " %", VisualizeFloatPercent(param[0])); - AddDescText(-1, true, GetItemDescString(ITEMDESC_FIREDEFENCE) + " %", -VisualizeFloatPercent(param[1])); - break; - case 26: - AddDescText(-1, true, GetItemDescString(ITEMDESC_WOODDEFENCE) + " %", VisualizeFloatPercent(param[0])); - AddDescText(-1, true, GetItemDescString(ITEMDESC_GOLDDEFENCE) + " %", -VisualizeFloatPercent(param[1])); - break; - case 27: - AddDescText(-1, true, GetItemDescString(ITEMDESC_WATERDEFENCE) + " %", VisualizeFloatPercent(param[0])); - AddDescText(-1, true, GetItemDescString(ITEMDESC_EARTHDEFENCE) + " %", -VisualizeFloatPercent(param[1])); - break; - case 28: - AddDescText(-1, true, GetItemDescString(ITEMDESC_FIREDEFENCE) + " %", VisualizeFloatPercent(param[0])); - AddDescText(-1, true, GetItemDescString(ITEMDESC_WATERDEFENCE) + " %", -VisualizeFloatPercent(param[1])); - break; - case 29: - AddDescText(-1, true, GetItemDescString(ITEMDESC_EARTHDEFENCE) + " %", VisualizeFloatPercent(param[0])); - AddDescText(-1, true, GetItemDescString(ITEMDESC_WOODDEFENCE) + " %", -VisualizeFloatPercent(param[1])); - break; - - case 30: - if (!local && aPEEVals != null) { aPEEVals[PEEI_GOLDDEF] += param[0]; aPEEVals[PEEI_FIREDEF] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_GOLDDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_FIREDEFENCE)); AddDescText(-1, true, " %+d", -param[1]); - break; - case 31: - if (!local && aPEEVals != null) { aPEEVals[PEEI_WOODDEF] += param[0]; aPEEVals[PEEI_GOLDDEF] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_WOODDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_GOLDDEFENCE)); AddDescText(-1, true, " %+d", -param[1]); - break; - case 32: - if (!local && aPEEVals != null) { aPEEVals[PEEI_WATERDEF] += param[0]; aPEEVals[PEEI_EARTHDEF] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_WATERDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_EARTHDEFENCE)); AddDescText(-1, true, " %+d", -param[1]); - break; - case 33: - if (!local && aPEEVals != null) { aPEEVals[PEEI_FIREDEF] += param[0]; aPEEVals[PEEI_WATERDEF] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_FIREDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_WATERDEFENCE)); AddDescText(-1, true, " %+d", -param[1]); - break; - case 34: - if (!local && aPEEVals != null) { aPEEVals[PEEI_EARTHDEF] += param[0]; aPEEVals[PEEI_WOODDEF] -= param[1]; } - AddDescText(-1, false, GetItemDescString(ITEMDESC_EARTHDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_WOODDEFENCE)); AddDescText(-1, true, " %+d", -param[1]); - break; - - case 35: - if (!local && aPEEVals != null) aPEEVals[PEEI_HP] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP)); AddDescText(-1, true, " %+d", param[0]); - break; - case 36: - if (!local && aPEEVals != null) aPEEVals[PEEI_MP] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMP)); AddDescText(-1, true, " %+d", param[0]); - break; - case 37: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDHP) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 38: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDMP) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 39: - AddDescText(-1, true, GetItemDescString(ITEMDESC_HPRECOVER), param[0] / 2); - break; - case 40: - AddDescText(-1, true, GetItemDescString(ITEMDESC_MPRECOVER), param[0] / 2); - break; - - case 41: - if (local) { if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_STRENGTH), param[0]); AddDescText(-1, true, "~%d", param[1]); } else { AddDescText(-1, true, GetItemDescString(ITEMDESC_STRENGTH), param[0]); } } - else AddDescText(-1, true, GetItemDescString(ITEMDESC_STRENGTH), param[0]); - break; - case 42: - if (local) { if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_AGILITY), param[0]); AddDescText(-1, true, "~%d", param[1]); } else { AddDescText(-1, true, GetItemDescString(ITEMDESC_AGILITY), param[0]); } } - else AddDescText(-1, true, GetItemDescString(ITEMDESC_AGILITY), param[0]); - break; - case 43: - if (local) { if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ENERGY), param[0]); AddDescText(-1, true, "~%d", param[1]); } else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ENERGY), param[0]); } } - else AddDescText(-1, true, GetItemDescString(ITEMDESC_ENERGY), param[0]); - break; - case 44: - if (local) { if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_VITALITY), param[0]); AddDescText(-1, true, "~%d", param[1]); } else { AddDescText(-1, true, GetItemDescString(ITEMDESC_VITALITY), param[0]); } } - else AddDescText(-1, true, GetItemDescString(ITEMDESC_VITALITY), param[0]); - break; - case 45: - AddDescText(-1, true, GetItemDescString(ITEMDESC_DEADLYSTRIKE), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 46: - if (local) { if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ATKRATING), param[0]); AddDescText(-1, true, "~%d", param[1]); } else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKRATING), param[0]); } } - else AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKRATING), param[0]); - break; - case 47: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKRATING) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 48: - AddDescText(-1, true, GetItemDescString(ITEMDESC_RUNSPEED), BitConverter.ToSingle(BitConverter.GetBytes(param[0]), 0)); - break; - case 49: - AddDescText(-1, true, GetItemDescString(ITEMDESC_RUNSPEEDEXTRA), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - - case 50: - if (!local && aPEEVals != null) aPEEVals[PEEI_DODGE] += param[0]; - AddDescText(-1, false, GetItemDescString(ITEMDESC_DODGE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 51: - AddDescText(-1, true, GetItemDescString(ITEMDESC_DODGE) + " %", local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 52: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ENDURANCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 53: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ENDURANCE) + " %", VisualizeFloatPercent(param[0])); - break; - case 54: - AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYRESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 55: - AddDescText(-1, true, GetItemDescString(ITEMDESC_RANDOMPROP)); - break; - case 56: - AddDescText(-1, true, GetItemDescString(ITEMDESC_REQEXTRA), -VisualizeFloatPercent(param[0])); - break; - case 57: - AddDescText(-1, true, GetItemDescString(ITEMDESC_RANDOMPROP)); - break; - case 58: - AddDescText(-1, true, GetItemDescString(ITEMDESC_EXP), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 59: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ATK_DEGREE), param[0]); - break; - case 60: - AddDescText(-1, true, GetItemDescString(ITEMDESC_DEF_DEGREE), param[0]); - break; - case 61: - AddDescText(-1, true, GetItemDescString(ITEMDESC_TOTAL_DEFENCE_ADD), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 62: - AddDescText(-1, true, GetItemDescString(ITEMDESC_PROFVIEW)); - break; - case 63: - AddDescText(-1, true, GetItemDescString(ITEMDESC_SOULPOWER), param[0]); - break; - case 64: - AddDescText(-1, true, GetItemDescString(ITEMDESC_GOLDRESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 65: - AddDescText(-1, true, GetItemDescString(ITEMDESC_WOODRESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 66: - AddDescText(-1, true, GetItemDescString(ITEMDESC_WATERRESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 67: - AddDescText(-1, true, GetItemDescString(ITEMDESC_FIRERESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 68: - AddDescText(-1, true, GetItemDescString(ITEMDESC_EARTHRESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - case 69: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ALLMAGICRESIST), local ? VisualizeFloatPercent(param[0]) : param[0]); - break; - - case 70: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ATK_DEGREE), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ATK_DEGREE), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_ATK_DEGREE), param[0]); - } - break; - case 71: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_DEF_DEGREE), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_DEF_DEGREE), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_DEF_DEGREE), param[0]); - } - break; - case 72: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d%%", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_DEADLYSTRIKE), param[0]); - } - break; - case 73: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP)); - if (param[0] != param[1]) AddDescText(-1, true, " %+d~%d", param[0], param[1]); else AddDescText(-1, true, " %+d", param[0]); - } - else - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP)); AddDescText(-1, true, " %+d", param[0]); - } - break; - case 74: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMP)); - if (param[0] != param[1]) AddDescText(-1, true, " %+d~%d", param[0], param[1]); else AddDescText(-1, true, " %+d", param[0]); - } - else - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMP)); AddDescText(-1, true, " %+d", param[0]); - } - break; - case 75: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ATKRATING) + " %", VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKRATING) + " %", VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKRATING) + " %", param[0]); - } - break; - case 76: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); - if (param[0] != param[1]) AddDescText(-1, true, " %+d~%d", param[0], param[1]); else AddDescText(-1, true, " %+d", param[0]); - } - else - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - } - break; - case 77: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICDEF)); - if (param[0] != param[1]) AddDescText(-1, true, " %+d~%d", param[0], param[1]); else AddDescText(-1, true, " %+d", param[0]); - } - else - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICDEF)); AddDescText(-1, true, " %+d", param[0]); - } - break; - case 78: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYRESIST), VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYRESIST), VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYRESIST), param[0]); - } - break; - case 79: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICRESIST), VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ALLMAGICRESIST), VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_ALLMAGICRESIST), param[0]); - } - break; - case 80: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_CASTTIME), -VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d%%", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_CASTTIME), -VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_CASTTIME), -param[0]); - } - break; - case 81: - if (local) - { - float f0 = BitConverter.ToSingle(BitConverter.GetBytes(param[0]), 0); - float f1 = BitConverter.ToSingle(BitConverter.GetBytes(param[1]), 0); - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDATKDIST), f0); AddDescText(-1, true, "~%.2f", f1); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDATKDIST), f0); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_ADDATKDIST), BitConverter.ToSingle(BitConverter.GetBytes(param[0]), 0)); - } - break; - case 82: - if (local) - { - int h0 = param[0] / 2; int h1 = param[1] / 2; - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_MPRECOVER), h0); AddDescText(-1, true, "~%d", h1); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_MPRECOVER), h0); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_MPRECOVER), param[0] / 2); - } - break; - case 83: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE) + " %", VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYDEFENCE) + " %", VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYDEFENCE) + " %", param[0]); - } - break; - case 84: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_TOTAL_DEFENCE_ADD), VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_TOTAL_DEFENCE_ADD), VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_TOTAL_DEFENCE_ADD), param[0]); - } - break; - case 85: - if (local) - { - int hhp0 = param[0] / 2; int hhp1 = param[1] / 2; - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_HPRECOVER), hhp0); AddDescText(-1, true, "~%d", hhp1); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_HPRECOVER), hhp0); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_HPRECOVER), param[0] / 2); - } - break; - case 86: - if (local) - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_DODGE)); - if (param[0] != param[1]) AddDescText(-1, true, " %+d~%d", param[0], param[1]); else AddDescText(-1, true, " %+d", param[0]); - } - else - { - AddDescText(-1, false, GetItemDescString(ITEMDESC_DODGE)); AddDescText(-1, true, " %+d", param[0]); - } - break; - case 87: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_MAXPHYDAMAGE), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXPHYDAMAGE), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXPHYDAMAGE), param[0]); - } - break; - case 88: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_MAXMAGICDAMAGE), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXMAGICDAMAGE), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_MAXMAGICDAMAGE), param[0]); - } - break; - case 89: - if (local) - { - if (VisualizeFloatPercent(param[0]) != VisualizeFloatPercent(param[1])) { AddDescText(-1, false, GetItemDescString(ITEMDESC_REQEXTRA), -VisualizeFloatPercent(param[0])); AddDescText(-1, true, "~%d", VisualizeFloatPercent(param[1])); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_REQEXTRA), -VisualizeFloatPercent(param[0])); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_REQEXTRA), -VisualizeFloatPercent(param[0])); - } - break; - - case 90: - AddDescText(-1, true, GetItemDescString(ITEMDESC_PENETRATION), param[0]); - break; - case 91: - AddDescText(-1, true, GetItemDescString(ITEMDESC_RESILIENCE), param[0]); - break; - case 92: - if (!local && aPEEVals != null) - { - aPEEVals[PEEI_PHYDEF] += param[0]; - aPEEVals[PEEI_GOLDDEF] += param[1]; aPEEVals[PEEI_WOODDEF] += param[1]; aPEEVals[PEEI_WATERDEF] += param[1]; aPEEVals[PEEI_FIREDEF] += param[1]; aPEEVals[PEEI_EARTHDEF] += param[1]; - } - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICDEF)); AddDescText(-1, true, " %+d", param[1]); - break; - case 93: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP)); AddDescText(-1, true, " %+d", param[0]); - break; - case 94: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP)); if (param[0] != param[1]) AddDescText(-1, true, " %+d~%d", param[0], param[1]); else AddDescText(-1, true, " %+d", param[0]); - break; - case 95: - AddDescText(-1, true, GetItemDescString(ITEMDESC_STRENGTH), param[0]); - break; - case 96: - AddDescText(-1, true, GetItemDescString(ITEMDESC_AGILITY), param[0]); - break; - case 97: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ENERGY), param[0]); - break; - case 98: - AddDescText(-1, true, GetItemDescString(ITEMDESC_VITALITY), param[0]); - break; - case 99: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMP)); AddDescText(-1, true, " %+d", param[0]); - break; - case 120: - AddDescText(-1, false, GetItemDescString(ITEMDESC_GOLDDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 121: - AddDescText(-1, false, GetItemDescString(ITEMDESC_WOODDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 122: - AddDescText(-1, false, GetItemDescString(ITEMDESC_WATERDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 123: - AddDescText(-1, false, GetItemDescString(ITEMDESC_FIREDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 124: - AddDescText(-1, false, GetItemDescString(ITEMDESC_EARTHDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 125: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ATKRATING), param[0]); - break; - case 126: - AddDescText(-1, false, GetItemDescString(ITEMDESC_DODGE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 127: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMP)); AddDescText(-1, true, " %+d", param[0]); - break; - case 128: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_VITALITY), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_VITALITY), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_VITALITY), param[0]); - } - break; - case 129: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_STRENGTH), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_STRENGTH), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_STRENGTH), param[0]); - } - break; - case 130: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_AGILITY), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_AGILITY), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_AGILITY), param[0]); - } - break; - case 131: - if (local) - { - if (param[0] != param[1]) { AddDescText(-1, false, GetItemDescString(ITEMDESC_ENERGY), param[0]); AddDescText(-1, true, "~%d", param[1]); } - else { AddDescText(-1, true, GetItemDescString(ITEMDESC_ENERGY), param[0]); } - } - else - { - AddDescText(-1, true, GetItemDescString(ITEMDESC_ENERGY), param[0]); - } - break; - case 132: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDHP)); AddDescText(-1, true, " %+d", param[0]); - break; - case 133: - AddDescText(-1, false, GetItemDescString(ITEMDESC_PHYDEFENCE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 134: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ALLMAGICDEF)); AddDescText(-1, true, " %+d", param[0]); - break; - case 135: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDPHYDAMAGE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 136: - AddDescText(-1, false, GetItemDescString(ITEMDESC_ADDMAGICDAMAGE)); AddDescText(-1, true, " %+d", param[0]); - break; - case 137: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_GOLDRESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_GOLDRESIST), param[0]); - break; - case 138: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_WOODRESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_WOODRESIST), param[0]); - break; - case 139: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_WATERRESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_WATERRESIST), param[0]); - break; - case 140: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_FIRERESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_FIRERESIST), param[0]); - break; - case 141: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_EARTHRESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_EARTHRESIST), param[0]); - break; - case 142: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_ALLMAGICRESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_ALLMAGICRESIST), param[0]); - break; - case 143: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_DEADLYSTRIKE), param[0]); - break; - case 144: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ATK_DEGREE), param[0]); - break; - case 145: - AddDescText(-1, true, GetItemDescString(ITEMDESC_DEF_DEGREE), param[0]); - break; - case 146: - if (local) AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYRESIST), VisualizeFloatPercent(param[0])); else AddDescText(-1, true, GetItemDescString(ITEMDESC_PHYRESIST), param[0]); - break; - - case 160: - AddDescText(-1, false, GetItemDescString(ITEMDESC_VIGOUR)); AddDescText(-1, true, " %+d", param[0]); - break; - - case 200: if (aRefines != null) aRefines[REFINE_PHYDAMAGE] += param[0]; break; - case 201: if (aRefines != null) aRefines[REFINE_MAGICDAMAGE] += param[0]; break; - case 202: if (aRefines != null) aRefines[REFINE_PHYDEF] += param[0]; break; - case 203: if (aRefines != null) aRefines[REFINE_GOLDDEF] += param[0]; break; - case 204: if (aRefines != null) aRefines[REFINE_WOODDEF] += param[0]; break; - case 205: if (aRefines != null) aRefines[REFINE_WATERDEF] += param[0]; break; - case 206: if (aRefines != null) aRefines[REFINE_FIREDEF] += param[0]; break; - case 207: if (aRefines != null) aRefines[REFINE_EARTHDEF] += param[0]; break; - case 208: if (aRefines != null) aRefines[REFINE_HP] += param[0]; break; - case 209: if (aRefines != null) aRefines[REFINE_DODGE] += param[0]; break; - case 210: if (aRefines != null) { aRefines[REFINE_GOLDDEF] += param[0]; aRefines[REFINE_WOODDEF] += param[0]; aRefines[REFINE_WATERDEF] += param[0]; aRefines[REFINE_FIREDEF] += param[0]; aRefines[REFINE_EARTHDEF] += param[0]; } break; - case 211: if (aRefines != null) { aRefines[REFINE_PHYDAMAGE] += param[0]; aRefines[REFINE_MAGICDAMAGE] += param[0]; } break; - case 212: if (aRefines != null) { aRefines[REFINE_PHYDEF] += param[0]; aRefines[REFINE_GOLDDEF] += param[0]; aRefines[REFINE_WOODDEF] += param[0]; aRefines[REFINE_WATERDEF] += param[0]; aRefines[REFINE_FIREDEF] += param[0]; aRefines[REFINE_EARTHDEF] += param[0]; } break; - - default: - AddDescText(-1, true, GetItemDescString(ITEMDESC_ERRORPROP), idProp); - break; - } } - } + /// /// Build add-ons properties description /// @@ -2334,7 +4025,7 @@ namespace PerfectWorld.Scripts.Managers return; // Change color - m_strDesc += GetColorString(ITEMDESC_COL_LIGHTBLUE); + m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE); foreach (Property prop in Props) { @@ -2371,45 +4062,107 @@ namespace PerfectWorld.Scripts.Managers } /// - /// Build tessera description + /// Build tessera description (socketed gems/stones) /// private void BuildTesseraDesc() { if (Holes.Count == 0) return; - int cyanine = ITEMDESC_COL_CYANINE; + int cyanine = (int)DescriptipionMsg.ITEMDESC_COL_CYANINE; - foreach (int hole in Holes) + for (int i = 0; i < Holes.Count; i++) { - if (hole == 0) + if (Holes[i] == 0) continue; - // Get item name - string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(hole); + // Get item name - would normally use CECIvtrItem::CreateItem + string itemName = EC_IvtrItemUtils.Instance.ResolveItemName(Holes[i]); string descText = "null"; - // This would normally check if it's a stone and get its description - // For now, use the item name + // 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(ITEMDESC_2STRINGS), itemName, descText); + AddDescText(cyanine, true, GetItemDescString(DescriptipionMsg.ITEMDESC_2STRINGS), itemName, descText); } } /// /// Add suite description /// - public void AddSuiteDesc() + private void AddSuiteDesc() { int idSuite = GetSuiteID(); if (idSuite == 0) return; // This equipment isn't one of any suite - // This would normally build the suite description - // For now, just add a placeholder - m_strDesc += "\\r\\r"; - AddDescText(ITEMDESC_COL_YELLOW, true, "Suite Equipment"); + // Get suite info + DATA_TYPE dataType = DATA_TYPE.DT_INVALID; + elementdataman dataMan = EC_Game.GetElementDataMan(); + object pData = dataMan.get_data_ptr((uint)idSuite, ID_SPACE.ID_SPACE_ESSENCE, ref dataType); + if (dataType != DATA_TYPE.DT_SUITE_ESSENCE) + { + // ASSERT in C++ + return; + } + + // In C#, we'd need to cast to SUITE_ESSENCE struct + // For now, use placeholder values + string suiteName = "Suite"; // TODO: Get from SUITE_ESSENCE.name + int maxEquips = 12; // TODO: Get from SUITE_ESSENCE.max_equips + + // Get host player (would normally come from EC_Game.GetGameRun().GetHostPlayer()) + // Check if this equipment is in host's equipment pack + bool showDetail = false; // TODO: Check if m_pDescIvtr == pHostPlayer->GetEquipment() + + // Colors + int nameCol = DecideNameCol(); + int lblue = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; + int yellow = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; + + // Save current description + string strCurDesc = m_strDesc; + + if (!showDetail) + { + // Isn't equipment inventory, only add total suite number info. + m_strDesc = "\\r\\r"; + AddDescText(nameCol, false, "{0} ({1})", suiteName, maxEquips); + m_strDesc = strCurDesc + m_strDesc; + return; + } + + // Maximum number of suite items + const int MAX_NUM = 12; + + // Get equipped suite item list + int[] aEquipped = new int[MAX_NUM]; + int itemCnt = 0; // TODO: Get from pHostPlayer->GetEquippedSuiteItem(idSuite, aEquipped) + if (itemCnt == 0) + return; + + m_strDesc = "\\r\\r"; + + // Build suite addon properties at first + if (itemCnt > 1) + { + // Change color + AddDescText(lblue, false, ""); + + // In C++, this loops through suite addons and displays them + // For now, skip detailed addon display + } + + // Add suite name + AddDescText(yellow, true, "{0} ({1} / {2})", suiteName, itemCnt, maxEquips); + + // List suite item names would go here + // In C++, this creates SUITEITEM array and lists enabled/disabled items + // green, gray, white colors would be used here for enabled/disabled items + // For now, simplified version } /// @@ -2420,28 +4173,47 @@ namespace PerfectWorld.Scripts.Managers if (!IsDestroying() || dropItemID == 0 || num == 0) return; - // This would normally get the destroying info from element data - int red = ITEMDESC_COL_RED; + // 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(ITEMDESC_EQUIP_DESTROYING)); + AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIP_DESTROYING)); - // This would normally get the item name from element data - AddDescText(red, true, GetItemDescString(ITEMDESC_EQUIP_REPAIR_NEED_ITEM), "Repair Item"); - AddDescText(red, true, GetItemDescString(ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT), (int)(Math.Ceiling(num * 1.2))); + AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIP_REPAIR_NEED_ITEM), essenceName); + AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EQUIP_REPAIR_NEED_ITEMCNT), (int)(Math.Ceiling(num * 1.2))); } /// /// Add reputation requirement description /// - public void AddReputationReqDesc() + private void AddReputationReqDesc() { - if (ReputationReq > 0) - { - // This would normally check the host player's reputation - int col = ITEMDESC_COL_WHITE; // Default to white, would check against player reputation - AddDescText(col, true, GetItemDescString(ITEMDESC_REPUTATION_REQ), ReputationReq); - } + 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); } /// @@ -2457,6 +4229,305 @@ namespace PerfectWorld.Scripts.Managers } return num; } + + /// + /// Add sharpener description (磨刀石 properties) + /// + private void AddSharpenerDesc() + { + if (Props.Count == 0) + return; + + int color = (int)DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE; + + // Check if all sharpener properties have the same expire time + bool sameExpireTime = true; + bool findFirst = true; + int lastExpireTime = 0; + + foreach (Property prop in Props) + { + if (prop.Embed || prop.Suite || prop.Engraved) + continue; + + byte propType = GetPropertyType(prop.Type); + if (!IsSharpenerProperty(propType)) + continue; + + int p1 = prop.Params.Length > 1 ? prop.Params[1] : 0; + if (findFirst) + { + // Found first sharpener property + lastExpireTime = p1; + findFirst = false; + } + else + { + // Found another sharpener property + if (p1 != lastExpireTime) + { + // Expire times are different + sameExpireTime = false; + break; + } + } + } + + if (findFirst) + { + // Didn't find any sharpener property + return; + } + + bool firstProp = true; + foreach (Property prop in Props) + { + if (prop.Embed || prop.Suite || prop.Engraved) + continue; + + byte propType = GetPropertyType(prop.Type); + if (!IsSharpenerProperty(propType)) + continue; + + if (firstProp) + { + m_strDesc += "\\r"; + firstProp = false; + } + + // New line + m_strDesc += "\\r"; + + int p0 = prop.Params.Length > 0 ? prop.Params[0] : 0; // First parameter value + int p1 = prop.Params.Length > 1 ? prop.Params[1] : 0; // Expire time + + // Add property description + switch (propType) + { + case 100: // Sharpener physical damage + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDPHYDAMAGE)); + AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); + break; + + case 101: // Sharpener max physical damage + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXPHYDAMAGE), p0); + break; + + case 102: // Sharpener magic damage + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDMAGICDAMAGE)); + AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); + break; + + case 103: // Sharpener max magic damage + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_MAXMAGICDAMAGE), p0); + break; + + case 104: // Sharpener physical defence + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_PHYDEFENCE)); + AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); + break; + + case 105: // Sharpener HP + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDHP)); + AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); + break; + + case 106: // Sharpener strength + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_STRENGTH), p0); + break; + + case 107: // Sharpener agility + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_AGILITY), p0); + break; + + case 108: // Sharpener energy + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ENERGY), p0); + break; + + case 109: // Sharpener attack rating + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATKRATING), p0); + break; + + case 110: // Sharpener deadly strike + if (prop.Local) + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), VisualizeFloatPercent(p0)); + else + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEADLYSTRIKE), p0); + break; + + case 111: // Sharpener attack degree + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ATK_DEGREE), p0); + break; + + case 112: // Sharpener defence degree + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_DEF_DEGREE), p0); + break; + + case 113: // Sharpener cast time + if (prop.Local) + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -VisualizeFloatPercent(p0)); + else + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_CASTTIME), -p0); + break; + + case 114: // Sharpener magic defence + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ALLMAGICDEF)); + AddDescText(color, false, p0 >= 0 ? " +{0}" : " {0}", p0); + break; + + case 115: // Sharpener ride pet speed + AddDescText(color, false, GetItemDescString(DescriptipionMsg.ITEMDESC_ADDRIDEONPETSPEED), IntToFloat(p0)); + break; + + default: + // ASSERT(false) in C++ + continue; + } + + // If expire times are different, add expire time after each property + if (!sameExpireTime) + { + if (p1 != 0) + { + m_strDesc += " "; + AddExpireTimeDesc(p1); + TrimLastReturn(); + } + } + } + + // If expire times are the same, add expire time at the end + if (sameExpireTime) + { + if (lastExpireTime != 0) + { + m_strDesc += "\\r"; + AddExpireTimeDesc(lastExpireTime); + TrimLastReturn(); + } + } + } + + /// + /// Append engraved property descriptions to the current description buffer. + /// Mirrors the behaviour of the original C++ AddEngravedDesc. + /// + private void AddEngravedDesc() + { + if (Props.Count == 0) + return; + + // Change color + bool firstProp = true; + + foreach (Property prop in Props) + { + if (!prop.Engraved) + continue; + + if (firstProp) + { + firstProp = false; + m_strDesc += "\\r"; + m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_YELLOW); + } + AddOneAddOnPropDesc(prop.Type, prop.Params, null, null, prop.Local); + } + + if (!firstProp) + { + // Trim last return after engraved properties + TrimLastReturn(); + } + } + + /// + /// Append maker description (signature / crafted by) to the description buffer. + /// + private void AddMakerDesc() + { + if (string.IsNullOrEmpty(Maker)) + return; + + m_strDesc += "\\r"; + // For signed marks (IMT_SIGN), Maker already contains color codes and formatted text. + if (MadeFrom == IMT_SIGN) + { + m_strDesc += Maker; + } + else + { + // Normal "made by" line using item-desc string if available + string fmt = GetItemDescString(DescriptipionMsg.ITEMDESC_MADEFROM); + if (string.IsNullOrEmpty(fmt)) + { + fmt = "Made by {0}"; + } + AddDescText((int)DescriptipionMsg.ITEMDESC_COL_GREEN, false, fmt, Maker); + } + } + + /// + /// Trim the last '\r' in description string + /// + private void TrimLastReturn() + { + int len = m_strDesc.Length; + if (len >= 2 && m_strDesc[len - 2] == '\\' && m_strDesc[len - 1] == 'r') + { + m_strDesc = m_strDesc.Substring(0, len - 2); + } + } + + /// + /// Add expire time description + /// + private void AddExpireTimeDesc() + { + if (ExpireDate == 0) + return; + + int green = (int)DescriptipionMsg.ITEMDESC_COL_GREEN; + int yellow = (int)DescriptipionMsg.ITEMDESC_COL_YELLOW; + int gold = (int)DescriptipionMsg.ITEMDESC_COL_DARKGOLD; + int red = (int)DescriptipionMsg.ITEMDESC_COL_RED; + + // Get server GMT time (this would normally come from EC_Game) + long serverTime = 0; // TODO: Get from EC_Game.GetServerGMTTime() + long timeLeft = ExpireDate - serverTime; + if (timeLeft < 0) timeLeft = 0; + + if (timeLeft > 24 * 3600) + { + AddDescText(green, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_DAY), + (int)(timeLeft / (24 * 3600)), (int)((timeLeft % (24 * 3600)) / 3600)); + } + else if (timeLeft > 3600) + { + AddDescText(yellow, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_HOUR_MIN), + (int)(timeLeft / 3600), (int)((timeLeft % 3600) / 60)); + } + else if (timeLeft > 60) + { + AddDescText(gold, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_MIN_SEC), + (int)(timeLeft / 60), (int)(timeLeft % 60)); + } + else + { + AddDescText(red, true, GetItemDescString(DescriptipionMsg.ITEMDESC_EXPIRETIME_SECOND), (int)timeLeft); + } + } + + /// + /// Add expire time description with specific expire date + /// + private void AddExpireTimeDesc(int expireDate) + { + int temp = ExpireDate; + ExpireDate = expireDate; + AddExpireTimeDesc(); + ExpireDate = temp; + } /// /// Get preview info @@ -2481,7 +4552,7 @@ namespace PerfectWorld.Scripts.Managers return m_strDesc; // Change color - m_strDesc += GetColorString(ITEMDESC_COL_LIGHTBLUE); + m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_LIGHTBLUE); foreach (Property prop in Props) { @@ -2514,7 +4585,7 @@ namespace PerfectWorld.Scripts.Managers return m_strDesc; // Change color - m_strDesc += GetColorString(ITEMDESC_COL_YELLOW); + m_strDesc += GetColorString(DescriptipionMsg.ITEMDESC_COL_YELLOW); foreach (Property prop in Props) { @@ -2549,4 +4620,5 @@ namespace PerfectWorld.Scripts.Managers return false; } } -} \ No newline at end of file +} + diff --git a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs index dcf525698d..8c2734eada 100644 --- a/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs +++ b/Assets/PerfectWorld/Scripts/Managers/EC_IvtrItem.cs @@ -7,6 +7,8 @@ using BrewMonster; using ModelRenderer.Scripts.Common; using ModelRenderer.Scripts.GameData; using UnityEngine; +using PerfectWorld.Scripts.Managers; +using BrewMonster.Network; namespace BrewMonster.Scripts.Managers { @@ -1178,7 +1180,35 @@ namespace BrewMonster.Scripts.Managers /// protected virtual string GetNormalDesc(bool bRepair) { - return string.IsNullOrEmpty(m_strDesc) ? null : m_strDesc; + // Build a simple but centralized description: + // - Name + // - String-table description (if any) + // - Extended description (if any) + m_strDesc = string.Empty; + + // Item name line + string name = GetName(); + if (!string.IsNullOrEmpty(name)) + { + AddDescText(0, true, name); + } + + // Core description from item_desc.txt (via EC_Game / TryGetItemMsg) + string mainDesc = TryGetItemMainDesc(); + if (!string.IsNullOrEmpty(mainDesc)) + { + AddDescText(0, true, mainDesc); + } + + // Extended description from item_ext_desc.txt + string extDesc = TryGetItemExtDesc(); + if (!string.IsNullOrEmpty(extDesc)) + { + AddDescText(0, true, extDesc); + } + + TrimLastReturn(); + return m_strDesc; } protected virtual string GetBoothBuyDesc() @@ -1193,7 +1223,10 @@ namespace BrewMonster.Scripts.Managers protected virtual void AddPriceDesc(int col, bool bRepair) { - // Full text/color building uses string tables; keep a minimal stub for now. + // Basic price string using scaled price; color is ignored at this level. + int price = GetScaledPrice(); + if (price <= 0) return; + AddDescText(col, true, "Price: {0}", price); } protected virtual void AddProfReqDesc(int iProfReq) @@ -1217,29 +1250,215 @@ namespace BrewMonster.Scripts.Managers m_strDesc += "\n"; } + // Add extend description to description string / 添加扩展描述到描述字符串 protected void AddExtDescText() { - // Extension description comes from game configs; keep stubbed for now. + // Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述 + string szExtDesc = TryGetItemExtDesc(); + // Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了 + // if (!szExtDesc || !szExtDesc[0]) + // return; + + m_strDesc += "\\r"; + + bool bAddLine = true; + // Add special properties description / 添加特殊属性描述 + var pDescTab = EC_Game.GetItemDesc(); + // Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整 + int green = 1000; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value + + if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性 + { + // Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...)) + // 精确的C++逻辑:(PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...)) + if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0 + || (!((m_iProcType & (int)ProcType.PROC_BINDING) != 0) + && ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0 + || (m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0 + || (m_iProcType & (int)ProcType.PROC_SELLABLE) != 0 + || (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0 + || (m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0 + || (m_iProcType & (int)ProcType.PROC_USE) != 0 + || (m_iProcType & (int)ProcType.PROC_DEADDROP) != 0 + || (m_iProcType & (int)ProcType.PROC_OFFLINE) != 0 + || (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0))) + { + bAddLine = false; + if (pDescTab != null && pDescTab.IsInitialized()) + { + string szCol = pDescTab.GetWideString(green); + if (!string.IsNullOrEmpty(szCol)) + { + m_strDesc += szCol; + } + } + + // Note: These message IDs are placeholders - adjust based on actual string table / 注意:这些消息ID是占位符 - 根据实际字符串表调整 + if ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_DEAD_PROTECT placeholder - adjust this value + string desc = pDescTab.GetWideString(2000); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_DROP placeholder - adjust this value + string desc = pDescTab.GetWideString(2001); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_SELLABLE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_TRADE placeholder - adjust this value + string desc = pDescTab.GetWideString(2002); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_PLAYER_TRADE placeholder - adjust this value + string desc = pDescTab.GetWideString(2003); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_LEAVE_SCENE_DISAPEAR placeholder - adjust this value + string desc = pDescTab.GetWideString(2004); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_USE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_USE_AFTER_PICK_UP placeholder - adjust this value + string desc = pDescTab.GetWideString(2005); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_DEADDROP) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_DROP_WHEN_DEAD placeholder - adjust this value + string desc = pDescTab.GetWideString(2006); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_OFFLINE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_DROP_WHEN_OFFLINE placeholder - adjust this value + string desc = pDescTab.GetWideString(2007); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_UNREPAIRABLE placeholder - adjust this value + string desc = pDescTab.GetWideString(2008); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0) + { + m_strDesc += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_USER_TRASH placeholder - adjust this value + string desc = pDescTab.GetWideString(2009); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + m_strDesc += desc; + } + } + } + else + { + TrimLastReturn(); + } + } + else + { + TrimLastReturn(); + } + + if (string.IsNullOrEmpty(szExtDesc)) + return; + + // Extend description is below special properties / 扩展描述在特殊属性下方 + if (bAddLine) + m_strDesc += "\\r\\r"; + else + m_strDesc += "\\r"; + m_strDesc += szExtDesc; } protected void AddExpireTimeDesc() { + // Placeholder: expiry description can be added here when time system is fully wired. } protected void AddExpireTimeDesc(int expire_date) { + // Overwrite, call standard one; we don't change m_expire_date in this simplified port. + AddExpireTimeDesc(); } protected void AddIDDescText() { + // Optional: show internal id for debugging + AddDescText(0, true, "ID: {0}", m_tid); } protected void AddBindDescText() { + // Simple binding flags display based on ProcType + if ((m_iProcType & (int)ProcType.PROC_BINDING) != 0) + { + AddDescText(0, true, "[Bound]"); + } + else if ((m_iProcType & (int)ProcType.PROC_BIND) != 0) + { + AddDescText(0, true, "[Bind on Use]"); + } } protected void AddActionTypeDescText(int action_type) { + // Action/weapon type formatting can be added later as needed. } protected void TrimLastReturn() @@ -1247,7 +1466,10 @@ namespace BrewMonster.Scripts.Managers if (string.IsNullOrEmpty(m_strDesc)) return; - if (m_strDesc.EndsWith("\n", StringComparison.Ordinal)) + // Remove trailing "\r" or "\n" for consistency with C++ style + if (m_strDesc.EndsWith("\\r", StringComparison.Ordinal)) + m_strDesc = m_strDesc.Substring(0, m_strDesc.Length - 2); + else if (m_strDesc.EndsWith("\n", StringComparison.Ordinal)) m_strDesc = m_strDesc.Substring(0, m_strDesc.Length - 1); } @@ -1263,6 +1485,7 @@ namespace BrewMonster.Scripts.Managers protected int GetColorStrID(int tid) { + // Placeholder: color index lookup; return -1 (white) by default. return -1; } @@ -1273,6 +1496,253 @@ namespace BrewMonster.Scripts.Managers return (int)(f * 100.0f + 0.5f); } + /// + /// Try to get the main description line for this item from item_desc.txt via EC_Game. + /// This mirrors the behaviour used by Inventory UI and NPC shop, but centralised here. + /// + private string TryGetItemMainDesc() + { + try + { + if (EC_Game.TryGetItemMsg(m_tid, out int messageId, out int displayMode)) + { + var tab = EC_Game.GetItemDesc(); + if (tab != null && tab.IsInitialized()) + { + var s = tab.GetWideString(messageId); + if (!string.IsNullOrEmpty(s)) return s; + } + } + + // Fallback: direct template id lookup + { + var tab = EC_Game.GetItemDesc(); + if (tab != null && tab.IsInitialized()) + { + var s = tab.GetWideString(m_tid); + if (!string.IsNullOrEmpty(s)) return s; + } + } + } + catch (Exception ex) + { + Debug.LogWarning($"[EC_IvtrItem] Error getting main description for tid={m_tid}: {ex.Message}"); + } + + return string.Empty; + } + + /// + /// Try to get the extended description line from item_ext_desc.txt. + /// + private string TryGetItemExtDesc() + { + try + { + if (EC_Game.TryGetItemMsg(m_tid, out int messageId, out int displayMode)) + { + var tab = EC_Game.GetItemExtDesc(); + if (tab != null && tab.IsInitialized()) + { + var s = tab.GetWideString(messageId); + if (!string.IsNullOrEmpty(s)) return s; + } + } + + // Fallback: direct id + { + var tab = EC_Game.GetItemExtDesc(); + if (tab != null && tab.IsInitialized()) + { + var s = tab.GetWideString(m_tid); + if (!string.IsNullOrEmpty(s)) return s; + } + } + } + catch (Exception ex) + { + Debug.LogWarning($"[EC_IvtrItem] Error getting extended description for tid={m_tid}: {ex.Message}"); + } + + return string.Empty; + } + + /// + /// Get extended description text for UI display. + /// This method mirrors AddExtDescText() logic but returns a string instead of modifying m_strDesc. + /// 此方法镜像AddExtDescText()逻辑,但返回字符串而不是修改m_strDesc + /// + public string GetExtendedDescText() + { + string result = string.Empty; + + // Get extended description from item_ext_desc.txt using tid / 使用tid从item_ext_desc.txt获取扩展描述 + string szExtDesc = TryGetItemExtDesc(); + // Note: Original C++ had early return commented out / 注意:原始C++代码的早期返回被注释掉了 + // if (!szExtDesc || !szExtDesc[0]) + // return; + + result += "\\r"; + + bool bAddLine = true; + // Add special properties description / 添加特殊属性描述 + var pDescTab = EC_Game.GetItemDesc(); + // Note: ITEMDESC_COL2_BRIGHTBLUE constant - adjust based on actual string table / 注意:ITEMDESC_COL2_BRIGHTBLUE常量 - 根据实际字符串表调整 + int green = 1000; // ITEMDESC_COL2_BRIGHTBLUE placeholder - adjust this value + + if (m_iCID != (int)InventoryClassId.ICID_GOBLIN) // goblin does not need to display these special properties / 地精不需要显示这些特殊属性 + { + // Exact C++ logic: (PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...)) + // 精确的C++逻辑:(PROC_NO_USER_TRASH) || (!PROC_BINDING && (PROC_DROPWHENDIE || ...)) + if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0 + || (!((m_iProcType & (int)ProcType.PROC_BINDING) != 0) + && ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0 + || (m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0 + || (m_iProcType & (int)ProcType.PROC_SELLABLE) != 0 + || (m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0 + || (m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0 + || (m_iProcType & (int)ProcType.PROC_USE) != 0 + || (m_iProcType & (int)ProcType.PROC_DEADDROP) != 0 + || (m_iProcType & (int)ProcType.PROC_OFFLINE) != 0 + || (m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0))) + { + bAddLine = false; + if (pDescTab != null && pDescTab.IsInitialized()) + { + string szCol = pDescTab.GetWideString(green); + if (!string.IsNullOrEmpty(szCol)) + { + result += szCol; + } + } + + // Note: These message IDs are placeholders - adjust based on actual string table / 注意:这些消息ID是占位符 - 根据实际字符串表调整 + if ((m_iProcType & (int)ProcType.PROC_DROPWHENDIE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_DEAD_PROTECT placeholder - adjust this value + string desc = pDescTab.GetWideString(2000); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_DROPPABLE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_DROP placeholder - adjust this value + string desc = pDescTab.GetWideString(2001); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_SELLABLE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_TRADE placeholder - adjust this value + string desc = pDescTab.GetWideString(2002); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_TRADEABLE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_PLAYER_TRADE placeholder - adjust this value + string desc = pDescTab.GetWideString(2003); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_DISAPEAR) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_LEAVE_SCENE_DISAPEAR placeholder - adjust this value + string desc = pDescTab.GetWideString(2004); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_USE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_USE_AFTER_PICK_UP placeholder - adjust this value + string desc = pDescTab.GetWideString(2005); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_DEADDROP) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_DROP_WHEN_DEAD placeholder - adjust this value + string desc = pDescTab.GetWideString(2006); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_OFFLINE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_DROP_WHEN_OFFLINE placeholder - adjust this value + string desc = pDescTab.GetWideString(2007); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_UNREPAIRABLE) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_UNREPAIRABLE placeholder - adjust this value + string desc = pDescTab.GetWideString(2008); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + if ((m_iProcType & (int)ProcType.PROC_NO_USER_TRASH) != 0) + { + result += "\\r"; + if (pDescTab != null && pDescTab.IsInitialized()) + { + // ITEMDESC_NO_USER_TRASH placeholder - adjust this value + string desc = pDescTab.GetWideString(2009); // Placeholder ID + if (!string.IsNullOrEmpty(desc)) + result += desc; + } + } + } + } + + if (string.IsNullOrEmpty(szExtDesc)) + return result; + + // Extend description is below special properties / 扩展描述在特殊属性下方 + if (bAddLine) + result += "\\r\\r"; + else + result += "\\r"; + result += szExtDesc; + + return result; + } + #endregion } diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs index 58e1c17cc9..8137d2935b 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommand.cs @@ -704,6 +704,41 @@ namespace CSNetwork.C2SCommand { public bool agree; } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct npc_trade_item + { + public int tid; + public uint index; + public uint count; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct npc_sell_item + { + public int tid; + public uint index; + public uint count; + public int price; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct NPCSevBuyCONTENT + { + public uint money; // Not use now + public int consume_contrib; + public int cumulate_contrib; + public int force_id; + public int force_repu; + public int force_contrib; + public uint item_count; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct NPCSevSellCONTENT + { + public uint item_count; + } } namespace CSNetwork.S2CCommand diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs index ccc88c5488..63151f6636 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/C2SCommand/C2SCommandFactory.cs @@ -616,6 +616,76 @@ namespace CSNetwork.C2SCommand }; return SerializeCommand(CommandID.SEVNPC_SERVE, cmd, content); } + + public static Octets CreateNPCSevBuyCmd(int itemNum, CSNetwork.C2SCommand.npc_trade_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + throw new ArgumentException("Invalid itemNum or items array"); + + uint contentSize = (uint)Marshal.SizeOf(); + uint itemSize = (uint)Marshal.SizeOf(); + uint totalLen = contentSize + (uint)itemNum * itemSize; + + var cmd = new cmd_sevnpc_serve + { + service_type = NPC_service_type.GP_NPCSEV_SELL, // NPC sells to player = player buys + len = totalLen + }; + + NPCSevBuyCONTENT content = new NPCSevBuyCONTENT() + { + money = 0, // Not use now + consume_contrib = 0, + cumulate_contrib = 0, + force_id = 0, + force_repu = 0, + force_contrib = 0, + item_count = (uint)itemNum + }; + + // Serialize command + content + var octets = SerializeCommand(CommandID.SEVNPC_SERVE, cmd, content); + + // Append items array + for (int i = 0; i < itemNum; i++) + { + WriteStruct(octets, items[i]); + } + + return octets; + } + + public static Octets CreateNPCSevSellCmd(int itemNum, npc_sell_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + throw new ArgumentException("Invalid itemNum or items array"); + + uint contentSize = (uint)Marshal.SizeOf(); + uint itemSize = (uint)Marshal.SizeOf(); + uint totalLen = contentSize + (uint)itemNum * itemSize; + + var cmd = new cmd_sevnpc_serve + { + service_type = NPC_service_type.GP_NPCSEV_BUY, // NPC buys from player = player sells + len = totalLen + }; + + NPCSevSellCONTENT content = new NPCSevSellCONTENT() + { + item_count = (uint)itemNum + }; + + // Serialize command + content + var octets = SerializeCommand(CommandID.SEVNPC_SERVE, cmd, content); + + // Append items array + for (int i = 0; i < itemNum; i++) + { + WriteStruct(octets, items[i]); + } + + return octets; + } // TODO: Check orginal C++ implementation public static Octets CreateTaskNotifyCmd(byte[] pData, uint dwDataSize) diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Common/ExpTypes.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Common/ExpTypes.cs index f9487dfb33..7312c371d3 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/Common/ExpTypes.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/Common/ExpTypes.cs @@ -5,7 +5,7 @@ using System.Text; namespace CSNetwork.Common { - internal class ExpTypes + public class ExpTypes { // ts [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)] @@ -110,73 +110,74 @@ namespace CSNetwork.Common public int life; } } - - public enum SERVICE_TYPE : int - { - // ½»Ì¸·þÎñ - NPC_TALK = int.MinValue, - // ³öÊÛÉÌÆ·µÄ·þÎñ - NPC_SELL, - // ÊÕ¹ºÉÌÆ·µÄ·þÎñ - NPC_BUY, - // ÐÞÀíÉÌÆ·µÄ·þÎñ - NPC_REPAIR, - // ÏâǶ·þÎñ - NPC_INSTALL, - // ²ð³ý·þÎñ - NPC_UNINSTALL, - // ÈÎÎñÏà¹Ø·þÎñ,·Ö·¢ÈÎÎñºÍÍê³ÉÈÎÎñÒÔ¼°·¢·ÅÈÎÎñÎïÆ· - NPC_GIVE_TASK, - NPC_COMPLETE_TASK, - NPC_GIVE_TASK_MATTER, - // ½ÌÊÚÏà¹Ø·þÎñ - NPC_SKILL, - // ÖÎÁÆ·þÎñ - NPC_HEAL, - // ´«ËÍ·þÎñ - NPC_TRANSMIT, - // ÔËÊä·þÎñ - NPC_TRANSPORT, - // ´úÊÛ·þÎñ - NPC_PROXY, - // ´æ´¢ÎïÆ·¡¢½ðÇ® - NPC_STORAGE, - // Éú²ú·þÎñ - NPC_MAKE, - // ·Ö½â·þÎñ - NPC_DECOMPOSE, - // TALK·µ»Ø - TALK_RETURN, - // ½áÊø¶Ô»° - TALK_EXIT, - // ²Ö¿âÃÜÂë - NPC_STORAGE_PASSWORD, - // ¼ø¶¨·þÎñ - NPC_IDENTIFY, - // ·ÅÆúÈÎÎñ - TALK_GIVEUP_TASK, - // ³ÇÕ½ÅÚËþ½¨Ôì·þÎñ - NPC_WAR_TOWERBUILD, - // Ï´µã·þÎñ - NPC_RESETPROP, - // ³èÎï¸ÄÃû·þÎñ - NPC_PETNAME, - // ³èÎïѧϰ¼¼ÄÜ·þÎñ - NPC_PETLEARNSKILL, - // ³èÎïÒÅÍü¼¼ÄÜ·þÎñ - NPC_PETFORGETSKILL, - // ×°±¸°ó¶¨·þÎñ - NPC_EQUIPBIND, - // ×°±¸Ïú»Ù·þÎñ - NPC_EQUIPDESTROY, - // ×°±¸½â³ýÏú»Ù·þÎñ - NPC_EQUIPUNDESTROY, - // ÕʺŲֿâ - NPC_ACCOUNT_STORAGE, - // ïÔ¿Ì·þÎñ - NPC_ENGRAVE, - // ×°±¸ÖØÖý£¨Ëæ»úÊôÐÔ£© - NPC_RANDPROP, - }; } + + public enum SERVICE_TYPE : int + { + // ½»Ì¸·þÎñ + NPC_TALK = int.MinValue, + // ³öÊÛÉÌÆ·µÄ·þÎñ + NPC_SELL, + // ÊÕ¹ºÉÌÆ·µÄ·þÎñ + NPC_BUY, + // ÐÞÀíÉÌÆ·µÄ·þÎñ + NPC_REPAIR, + // ÏâǶ·þÎñ + NPC_INSTALL, + // ²ð³ý·þÎñ + NPC_UNINSTALL, + // ÈÎÎñÏà¹Ø·þÎñ,·Ö·¢ÈÎÎñºÍÍê³ÉÈÎÎñÒÔ¼°·¢·ÅÈÎÎñÎïÆ· + NPC_GIVE_TASK, + NPC_COMPLETE_TASK, + NPC_GIVE_TASK_MATTER, + // ½ÌÊÚÏà¹Ø·þÎñ + NPC_SKILL, + // ÖÎÁÆ·þÎñ + NPC_HEAL, + // ´«ËÍ·þÎñ + NPC_TRANSMIT, + // ÔËÊä·þÎñ + NPC_TRANSPORT, + // ´úÊÛ·þÎñ + NPC_PROXY, + // ´æ´¢ÎïÆ·¡¢½ðÇ® + NPC_STORAGE, + // Éú²ú·þÎñ + NPC_MAKE, + // ·Ö½â·þÎñ + NPC_DECOMPOSE, + // TALK·µ»Ø + TALK_RETURN, + // ½áÊø¶Ô»° + TALK_EXIT, + // ²Ö¿âÃÜÂë + NPC_STORAGE_PASSWORD, + // ¼ø¶¨·þÎñ + NPC_IDENTIFY, + // ·ÅÆúÈÎÎñ + TALK_GIVEUP_TASK, + // ³ÇÕ½ÅÚËþ½¨Ôì·þÎñ + NPC_WAR_TOWERBUILD, + // Ï´µã·þÎñ + NPC_RESETPROP, + // ³èÎï¸ÄÃû·þÎñ + NPC_PETNAME, + // ³èÎïѧϰ¼¼ÄÜ·þÎñ + NPC_PETLEARNSKILL, + // ³èÎïÒÅÍü¼¼ÄÜ·þÎñ + NPC_PETFORGETSKILL, + // ×°±¸°ó¶¨·þÎñ + NPC_EQUIPBIND, + // ×°±¸Ïú»Ù·þÎñ + NPC_EQUIPDESTROY, + // ×°±¸½â³ýÏú»Ù·þÎñ + NPC_EQUIPUNDESTROY, + // ÕʺŲֿâ + NPC_ACCOUNT_STORAGE, + // ïÔ¿Ì·þÎñ + NPC_ENGRAVE, + // ×°±¸ÖØÖý£¨Ëæ»úÊôÐÔ£© + NPC_RANDPROP, + }; } + diff --git a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs index ab02d44f69..38528e7480 100644 --- a/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs @@ -1266,6 +1266,26 @@ namespace CSNetwork SendProtocol(gamedatasend); } + public void c2s_SendCmdNPCSevBuy(int itemNum, C2SCommand.npc_trade_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + return; + + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevBuyCmd(itemNum, items); + SendProtocol(gamedatasend); + } + + public void c2s_SendCmdNPCSevSell(int itemNum, C2SCommand.npc_sell_item[] items) + { + if (itemNum <= 0 || items == null || items.Length < itemNum) + return; + + gamedatasend gamedatasend = new gamedatasend(); + gamedatasend.Data = C2SCommandFactory.CreateNPCSevSellCmd(itemNum, items); + SendProtocol(gamedatasend); + } + public void GetRoleCustomizeData(int iNumRole, List aRoleIDs) { if (iNumRole <= 0 || aRoleIDs == null || aRoleIDs.Count == 0) return; diff --git a/Assets/PerfectWorld/Scripts/Network/EC_ManMessageMono.cs b/Assets/PerfectWorld/Scripts/Network/EC_ManMessageMono.cs index 82903588cb..95a4620315 100644 --- a/Assets/PerfectWorld/Scripts/Network/EC_ManMessageMono.cs +++ b/Assets/PerfectWorld/Scripts/Network/EC_ManMessageMono.cs @@ -46,7 +46,6 @@ namespace BrewMonster.Managers private void OnDestroy() { EC_ManMessage.Dispose(); - CECGameRun.Dispose(); } private void Update() diff --git a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs index 1f9e559c07..d7f1797b90 100644 --- a/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs +++ b/Assets/PerfectWorld/Scripts/Network/UnityGameSession.cs @@ -1,4 +1,4 @@ -using BrewMonster; +using BrewMonster; using BrewMonster.Common; using CSNetwork; using CSNetwork.C2SCommand; @@ -319,6 +319,19 @@ namespace BrewMonster.Network Instance._gameSession.c2s_SendCmdNPCSevTaskMatter(idTask); } + public static void c2s_CmdNPCSevBuy(int itemNum, npc_trade_item[] items) + { + if (items == null || itemNum <= 0) + return; + Instance._gameSession.c2s_SendCmdNPCSevBuy(itemNum, items); + } + + public static void c2s_CmdNPCSevSell(int itemNum, npc_sell_item[] items) + { + if (items == null || itemNum <= 0) + return; + Instance._gameSession.c2s_SendCmdNPCSevSell(itemNum, items); + } public static void c2s_CmdStandUp() { Instance._gameSession.c2s_SendCmdStandUp(); diff --git a/Assets/PerfectWorld/Scripts/Pattern/CECObserver.cs b/Assets/PerfectWorld/Scripts/Pattern/CECObserver.cs index d6879c5377..c118b7a182 100644 --- a/Assets/PerfectWorld/Scripts/Pattern/CECObserver.cs +++ b/Assets/PerfectWorld/Scripts/Pattern/CECObserver.cs @@ -1,18 +1,149 @@ +// File : CECObserver.cs +// Creator : Xu Wenbin +// Date : 2014/4/10 +// Converted to C#: 2024 + using System; +using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace BrewMonster { - public class CECObservableChange : IDisposable + // class CECObservableChange + // 封装 CECObservable 的变化通知数据 / Encapsulates change notification data for CECObservable + public class CECObservableChange : IDisposable { public virtual void Dispose() { // Cleanup logic here } - }; + } - public class CECObserver + // class CECObserver + // 观察者基类:观察的 Model / Observer base class: observes Model + public class CECObserver { - + public virtual void OnRegistered(Model model) { } + public virtual void OnModelChange(Model model, CECObservableChange change) { } + public virtual void OnUnregister(Model model) { } + } + + // class CECObservable + // 可观察者基类:被观察的 Model / Observable base class: the Model being observed + // 提供观察者注册及通知功能 / Provides observer registration and notification functionality + public class CECObservable where Model : class + { + private struct ObserverImpl + { + public CECObserver observer; // 观察者指针 / Observer pointer + public int observeTimes; // 观察次数:为正数时,收到指定次数的 OnModelChange 消息后自动取消注册,为负数时为永久,为0值时无效 / Observe times: when positive, auto-unregister after receiving specified OnModelChange messages; when negative, permanent; when 0, invalid + public bool autoDeleteOnUnregister; // 取消注册时是否自动 delete / Whether to auto-delete on unregister + + public ObserverImpl(CECObserver observer, int observeTimes, bool autoDeleteOnUnregister) + { + this.observer = observer; + this.observeTimes = observeTimes; + this.autoDeleteOnUnregister = autoDeleteOnUnregister; + + if (this.observeTimes == 0) + { + Debug.Assert(false, "Observer times cannot be 0"); + this.observeTimes = 1; // 默认观察次数为1 / Default observe times to 1 + } + } + + public bool Equals(ObserverImpl rhs) + { + return this.observer == rhs.observer; + } + + public bool Equals(CECObserver pObserver) + { + return this.observer == pObserver; + } + + public bool WillNotObserve() + { + // 是否不再观察目标 / Whether no longer observing target + return observeTimes == 0; + } + + public bool ObserveOnce() + { + // 收到一次观察消息时调用,返回是否不再观察 / Called when receiving one observe message, returns whether no longer observing + if (observeTimes > 0) + { + --observeTimes; + } + return WillNotObserve(); + } + } + + private List m_observers = new List(); + + protected Model AsModel() + { + return this as Model; + } + + public bool IsObserverRegistered(CECObserver pObserver) + { + return m_observers.Any(impl => impl.Equals(pObserver)); + } + + public bool RegisterObserver(CECObserver pObserver, int observeTimes = -1, bool autoDeleteOnUnregister = false) + { + bool bRegistered = false; + if (!IsObserverRegistered(pObserver)) + { + m_observers.Add(new ObserverImpl(pObserver, observeTimes, autoDeleteOnUnregister)); + pObserver.OnRegistered(AsModel()); + bRegistered = true; + } + return bRegistered; + } + + public bool UnregisterObserver(CECObserver pObserver) + { + int index = m_observers.FindIndex(impl => impl.Equals(pObserver)); + if (index >= 0) + { + return UnregisterObserverImpl(index); + } + return false; + } + + public void NotifyObservers(CECObservableChange pChange) + { + Model pModel = AsModel(); + for (int i = m_observers.Count - 1; i >= 0; i--) + { + ObserverImpl observerImpl = m_observers[i]; + observerImpl.observer.OnModelChange(pModel, pChange); + if (observerImpl.ObserveOnce()) + { + UnregisterObserverImpl(i); + } + } + } + + private bool UnregisterObserverImpl(int index) + { + bool bUnRegistered = false; + if (index >= 0 && index < m_observers.Count) + { + ObserverImpl observerImpl = m_observers[index]; + observerImpl.observer.OnUnregister(AsModel()); + if (observerImpl.autoDeleteOnUnregister) + { + // In C#, we don't manually delete objects, but we can set to null + // The GC will handle cleanup + } + m_observers.RemoveAt(index); + bUnRegistered = true; + } + return bUnRegistered; + } } } diff --git a/Assets/PerfectWorld/Scripts/Shop.meta b/Assets/PerfectWorld/Scripts/Shop.meta new file mode 100644 index 0000000000..1e53564a68 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1be878d234498514c8356a798b2e4515 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PerfectWorld/Scripts/Shop/CECBackShop.cs b/Assets/PerfectWorld/Scripts/Shop/CECBackShop.cs new file mode 100644 index 0000000000..260548a8b1 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECBackShop.cs @@ -0,0 +1,230 @@ +// Filename : CECBackShop.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System; +using System.Collections.Generic; +using UnityEngine; +using BrewMonster.Network; + +namespace PerfectWorld.Scripts.Shop +{ + // CECBackShop + public class CECBackShop : CECShopArrayItems + { + private CECShopItemCategory m_fashionShopCategory = new CECShopItemCategory(); + private static CECBackShop s_instance; + private GShopLoader m_shopLoader; + + private CECBackShop() : base(null) + { + // Initialize with empty list first + m_pItems = new List(); + + // Try to get shop loader data (may not be available immediately) + RefreshShopData(); + + if (IsFashionShopEnabled()) + { + m_fashionShopCategory.InitFromQShopConfig(CECQShopConfig.CID_BACKSHOP_FASHION); + } + } + + public void RefreshShopData() + { + // Initialize with shop loader data + GameObject shopLoaderObj = GameObject.FindFirstObjectByType()?.gameObject; + if (shopLoaderObj != null) + { + m_shopLoader = shopLoaderObj.GetComponent(); + if (m_shopLoader != null && m_shopLoader.secondaryShop != null && m_shopLoader.secondaryShop.items != null) + { + m_pItems = m_shopLoader.secondaryShop.items; + } + } + } + + public static CECBackShop Instance() + { + if (s_instance == null) + { + s_instance = new CECBackShop(); + } + return s_instance; + } + + public override bool GetFromServer(int beginIndex, int endIndex) + { + bool result = false; + // TODO: Implement cooldown check (GP_CT_GET_DIVIDEND_MALL_PRICE) + // For now, always allow if indices are provided + if (beginIndex == 0 && endIndex == 0) + { + // Request all prices + // TODO: Implement UnityGameSession.RequestGetDividendMallItemPrice(0, 0) + result = true; + } + else if (beginIndex != 0 || endIndex != 0) + { + // Request specific range + // TODO: Implement UnityGameSession.RequestGetDividendMallItemPrice(beginIndex, endIndex) + result = true; + } + return result; + } + + public override uint GetLocalTimeStamp() + { + if (m_shopLoader != null && m_shopLoader.secondaryShop != null) + { + return m_shopLoader.secondaryShop.timestamp; + } + return 0; + } + + public override uint GetServerTimeStamp() + { + // TODO: Get from game run state + // return g_pGame->GetGameRun()->GetGShopTimeStamp2(); + return GetLocalTimeStamp(); // Stub for now + } + + public bool UpdateFromServer() // TODO: Add S2C::cmd_dividend_mall_item_price parameter when available + { + // TODO: Implement server update logic when S2C command structure is available + // For now, this is a stub + /* + List items = new List(m_pItems); + if (!ApplyChangesFromServer(items, pCmd)) + { + return false; + } + if (IsSame(items, m_pItems)) + { + return true; + } + m_pItems = items; + OnItemChange(); + */ + return true; + } + + private bool ApplyChangesFromServer(List pItems) // TODO: Add S2C::cmd_dividend_mall_item_price parameter when available + { + // TODO: Implement when S2C command structure is available + /* + int i = 0; + for (i = pCmd->start_index; i < pCmd->end_index; i++) + { + if (i < pItems.Count) + { + GShopItem data = pItems[i]; + for (int j = 0; j < 4; j++) + { + if (data.buy != null && j < data.buy.Length && data.buy[j].type != -1) + { + GShopBuyOption buyOption = data.buy[j]; + buyOption.type = -1; + buyOption.price = 0; + data.buy[j] = buyOption; + } + } + pItems[i] = data; + } + } + for (i = 0; i < pCmd->count; i++) + { + const S2C::cmd_dividend_mall_item_price::good_info& tempList = pCmd->list[i]; + if (tempList.good_index < pItems.Count) + { + GShopItem data = pItems[tempList.good_index]; + if (data.id == (uint)tempList.good_id) + { + if (data.buy != null && tempList.good_slot < data.buy.Length) + { + GShopBuyOption buyOption = data.buy[tempList.good_slot]; + buyOption.price = tempList.good_price; + buyOption.status = tempList.good_status; + buyOption.time = tempList.expire_time; + buyOption.type = 3; + data.buy[tempList.good_slot] = buyOption; + } + pItems[tempList.good_index] = data; + } + else + { + Debug.Assert(data.id == (uint)tempList.good_id); + return false; + } + } + } + */ + return true; + } + + public override int GetCash() + { + // TODO: Integrate with CECHostPlayer.GetDividend() + // For now, return 0 as stub + // return CECHostPlayer.Instance().GetDividend(); + return 0; + } + + public override bool Buy(int itemIndex, int buyIndex) + { + bool bOK = false; + + if (ReadyToBuy(itemIndex, buyIndex)) + { + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue) + { + GShopItem item = pItem.Value; + if (!CECShopItemOwnerNPC.HasOwnerNPC(item)) + { + // Regular dividend mall shopping + // TODO: Implement UnityGameSession.RequestDividendMallShopping(1, itemIndex, item.id, buyIndex) + // For now, use regular mall shopping as fallback + UnityGameSession.Instance.RequestMallShopping(1, (int)item.id, itemIndex, buyIndex); + } + else + { + // NPC server dividend mall shopping + // TODO: Implement UnityGameSession.RequestNPCSevDividendMallShopping(1, itemIndex, item.id, buyIndex) + UnityGameSession.Instance.RequestMallShopping(1, (int)item.id, itemIndex, buyIndex); + } + CECQShopConfig.Instance().OnItemBuyed(item.id); + // TODO: CECShoppingItemsMover::Instance().OnItemBuyed(this, itemIndex, buyIndex); + bOK = true; + } + } + else + { + Debug.Assert(false, "Item not ready to buy"); + } + return bOK; + } + + public override bool IsFashionShopEnabled() + { + return CECUIConfig.Instance().GetGameUI().bEnableBackShopFashionShop; + } + + public override bool IsFashionShopFlashSaleEnabled() + { + return CECUIConfig.Instance().GetGameUI().bEnableBackShopFashionShopFlashSale; + } + + public override string GetFashionShopFlashSaleTitle() + { + return CECUIConfig.Instance().GetGameUI().strBackShopFashionShopFlashSaleTitle; + } + + public override CECShopItemCategory GetFashionShopCategory() + { + return m_fashionShopCategory; + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECBackShop.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECBackShop.cs.meta new file mode 100644 index 0000000000..e302ac0894 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECBackShop.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 01372a5632621c3438a20d995d97bdee \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECQShop.cs b/Assets/PerfectWorld/Scripts/Shop/CECQShop.cs new file mode 100644 index 0000000000..5351701d01 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECQShop.cs @@ -0,0 +1,228 @@ +// Filename : CECQShop.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System; +using System.Collections.Generic; +using UnityEngine; +using BrewMonster.Network; + +namespace PerfectWorld.Scripts.Shop +{ + // CECQShop + public class CECQShop : CECShopArrayItems + { + private CECShopItemCategory m_fashionShopCategory = new CECShopItemCategory(); + private static CECQShop s_instance; + private GShopLoader m_shopLoader; + + private CECQShop() : base(null) + { + // Initialize with empty list first + m_pItems = new List(); + + // Try to get shop loader data (may not be available immediately) + RefreshShopData(); + + if (IsFashionShopEnabled()) + { + m_fashionShopCategory.InitFromQShopConfig(CECQShopConfig.CID_QSHOP_FASHION); + } + } + + public void RefreshShopData() + { + // Initialize with shop loader data + GameObject shopLoaderObj = GameObject.FindFirstObjectByType()?.gameObject; + if (shopLoaderObj != null) + { + m_shopLoader = shopLoaderObj.GetComponent(); + if (m_shopLoader != null && m_shopLoader.primaryShop != null && m_shopLoader.primaryShop.items != null) + { + m_pItems = m_shopLoader.primaryShop.items; + } + } + } + + public static CECQShop Instance() + { + if (s_instance == null) + { + s_instance = new CECQShop(); + } + return s_instance; + } + + public override bool GetFromServer(int beginIndex, int endIndex) + { + bool result = false; + // TODO: Implement cooldown check (GP_CT_GET_MALL_PRICE) + // For now, always allow if indices are provided + if (beginIndex == 0 && endIndex == 0) + { + // Request all prices + // TODO: Implement UnityGameSession.RequestGetMallItemPrice(0, 0) + result = true; + } + else if (beginIndex != 0 || endIndex != 0) + { + // Request specific range + // TODO: Implement UnityGameSession.RequestGetMallItemPrice(beginIndex, endIndex) + result = true; + } + return result; + } + + public override uint GetLocalTimeStamp() + { + if (m_shopLoader != null && m_shopLoader.primaryShop != null) + { + return m_shopLoader.primaryShop.timestamp; + } + return 0; + } + + public override uint GetServerTimeStamp() + { + // TODO: Get from game run state + // return g_pGame->GetGameRun()->GetGShopTimeStamp(); + return GetLocalTimeStamp(); // Stub for now + } + + public bool UpdateFromServer() // TODO: Add S2C::cmd_mall_item_price parameter when available + { + // TODO: Implement server update logic when S2C command structure is available + // For now, this is a stub + /* + List items = new List(m_pItems); + if (!ApplyChangesFromServer(items, pCmd)) + { + return false; + } + if (IsSame(items, m_pItems)) + { + return true; + } + m_pItems = items; + OnItemChange(); + */ + return true; + } + + private bool ApplyChangesFromServer(List pItems) // TODO: Add S2C::cmd_mall_item_price parameter when available + { + // TODO: Implement when S2C command structure is available + /* + int i = 0; + for (i = pCmd->start_index; i < pCmd->end_index; i++) + { + if (i < pItems.Count) + { + GShopItem data = pItems[i]; + for (int j = 0; j < 4; j++) + { + if (data.buy != null && j < data.buy.Length && data.buy[j].type != -1) + { + GShopBuyOption buyOption = data.buy[j]; + buyOption.type = -1; + buyOption.price = 0; + data.buy[j] = buyOption; + } + } + pItems[i] = data; + } + } + for (i = 0; i < pCmd->count; i++) + { + const S2C::cmd_mall_item_price::good_item& tempList = pCmd->list[i]; + if (tempList.good_index < pItems.Count) + { + GShopItem data = pItems[tempList.good_index]; + if (data.id == (uint)tempList.good_id) + { + if (data.buy != null && tempList.good_slot < data.buy.Length) + { + GShopBuyOption buyOption = data.buy[tempList.good_slot]; + buyOption.price = tempList.goods_price; + buyOption.status = tempList.good_status; + buyOption.time = tempList.expire_time; + buyOption.type = 3; + data.buy[tempList.good_slot] = buyOption; + } + pItems[tempList.good_index] = data; + } + else + { + Debug.Assert(data.id == (uint)tempList.good_id); + return false; + } + } + } + */ + return true; + } + + public override int GetCash() + { + // TODO: Integrate with CECHostPlayer.GetCash() + // For now, return 0 as stub + // return CECHostPlayer.Instance().GetCash(); + return 0; + } + + public override bool Buy(int itemIndex, int buyIndex) + { + bool bOK = false; + + if (ReadyToBuy(itemIndex, buyIndex)) + { + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue) + { + GShopItem item = pItem.Value; + if (!CECShopItemOwnerNPC.HasOwnerNPC(item)) + { + // Regular mall shopping + UnityGameSession.Instance.RequestMallShopping(1, (int)item.id, itemIndex, buyIndex); + } + else + { + // NPC server mall shopping + // TODO: Implement UnityGameSession.RequestNPCSevMallShopping(1, itemIndex, item.id, buyIndex) + UnityGameSession.Instance.RequestMallShopping(1, (int)item.id, itemIndex, buyIndex); + } + CECQShopConfig.Instance().OnItemBuyed(item.id); + // TODO: CECShoppingItemsMover::Instance().OnItemBuyed(this, itemIndex, buyIndex); + bOK = true; + } + } + else + { + Debug.Assert(false, "Item not ready to buy"); + } + return bOK; + } + + public override bool IsFashionShopEnabled() + { + return CECUIConfig.Instance().GetGameUI().bEnableQShopFashionShop; + } + + public override bool IsFashionShopFlashSaleEnabled() + { + return CECUIConfig.Instance().GetGameUI().bEnableQShopFashionShopFlashSale; + } + + public override string GetFashionShopFlashSaleTitle() + { + return CECUIConfig.Instance().GetGameUI().strQShopFashionShopFlashSaleTitle; + } + + public override CECShopItemCategory GetFashionShopCategory() + { + return m_fashionShopCategory; + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECQShop.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECQShop.cs.meta new file mode 100644 index 0000000000..6bf0337ee2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECQShop.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2d753dbecaf480345acbcb47feb6340f \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECQShopConfig.cs b/Assets/PerfectWorld/Scripts/Shop/CECQShopConfig.cs new file mode 100644 index 0000000000..ae7e953c30 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECQShopConfig.cs @@ -0,0 +1,40 @@ +// Filename : CECQShopConfig.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System; + +namespace PerfectWorld.Scripts.Shop +{ + // Stub implementation - to be properly implemented later + public class CECQShopConfig + { + public const int CID_QSHOP_FASHION = 0; // Stub value + public const int CID_BACKSHOP_FASHION = 1; // Stub value + + private static CECQShopConfig s_instance; + + public static CECQShopConfig Instance() + { + if (s_instance == null) + { + s_instance = new CECQShopConfig(); + } + return s_instance; + } + + public void FindCategory(int idCategory, out int mainType, out int subType) + { + // Stub implementation - to be properly implemented later + mainType = -1; + subType = -1; + } + + public void OnItemBuyed(uint goodsId) + { + // Stub implementation - to be properly implemented later + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECQShopConfig.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECQShopConfig.cs.meta new file mode 100644 index 0000000000..e77f9bfc42 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECQShopConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8e321ee92225d04ab0eb82b965fe32d \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopArrayItems.cs b/Assets/PerfectWorld/Scripts/Shop/CECShopArrayItems.cs new file mode 100644 index 0000000000..0a875f179d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopArrayItems.cs @@ -0,0 +1,60 @@ +// Filename : CECShopArrayItems.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System.Collections.Generic; +using System.Linq; + +namespace PerfectWorld.Scripts.Shop +{ + public abstract class CECShopArrayItems : CECShopBase + { + protected List m_pItems; + + public CECShopArrayItems(List pItems) + { + m_pItems = pItems; + } + + public override int GetCount() + { + return m_pItems != null ? m_pItems.Count : 0; + } + + public override GShopItem? GetItem(int index) + { + if (m_pItems != null && index >= 0 && index < m_pItems.Count) + { + return m_pItems[index]; + } + return null; + } + + public static bool IsSame(List lhs, List rhs) + { + bool result = false; + if (lhs == rhs) + { + result = true; + } + else if (lhs != null && rhs != null) + { + if (lhs.Count == rhs.Count) + { + result = true; + for (int u = 0; u < lhs.Count; ++u) + { + if (!CECShopBase.IsSame(lhs[u], rhs[u])) + { + result = false; + break; + } + } + } + } + return result; + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopArrayItems.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECShopArrayItems.cs.meta new file mode 100644 index 0000000000..089217a5d3 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopArrayItems.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f70fb69e0e992a24c8e03b2558f3ee6f \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopBase.cs b/Assets/PerfectWorld/Scripts/Shop/CECShopBase.cs new file mode 100644 index 0000000000..e93d9cc1a5 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopBase.cs @@ -0,0 +1,356 @@ +// Filename : CECShopBase.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System; +using System.Linq; +using BrewMonster; + +namespace PerfectWorld.Scripts.Shop +{ + // 封装 gshop 和 backshop 等的数据访问 / Encapsulates data access for gshop and backshop etc. + public static class CECShopConstants + { + public const int CECSHOP_INVALID_PRICE = -1; + public const int BUY_COUNT = 4; + } + + public abstract class CECShopBase : CECObservable + { + private CECShopItemOwnerNPC m_ownerNPC = new CECShopItemOwnerNPC(0); + + // CECShopBase + + public void OnItemChange() + { + CECShopBaseChange change = new CECShopBaseChange((uint)CECShopBaseChange.ChangeMask.ITEM_CHANGED); + NotifyObservers(change); + } + + public bool ValidateTimeStamp() + { + return GetLocalTimeStamp() == GetServerTimeStamp(); + } + + public void SetOwnerNPCID(uint ownerNPCID) + { + if (ownerNPCID == m_ownerNPC.GetOwnerNPCID()) + { + return; + } + m_ownerNPC.SetOwnerNPCID(ownerNPCID); + OnItemChange(); + } + + public uint GetOwnerNPCID() + { + return m_ownerNPC.GetOwnerNPCID(); + } + + public bool HasOwnerNPC() + { + return !m_ownerNPC.IsEmpty(); + } + + public CECShopItemOwnerNPC GetOwnerNPC() + { + return m_ownerNPC; + } + + public bool MatchOwnerNPC(GShopItem rhs) + { + return m_ownerNPC.MatchItem(rhs); + } + + public bool IsValid(int itemIndex, int buyIndex) + { + bool bValid = false; + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue) + { + if (buyIndex >= 0 && buyIndex < CECShopConstants.BUY_COUNT) + { + bValid = true; + } + } + return bValid; + } + + public bool ReadyToBuy(int itemIndex, int buyIndex) + { + bool result = false; + int[] buyType = new int[CECShopConstants.BUY_COUNT]; + if (IsValid(itemIndex, buyIndex) && CalcBuyType(itemIndex, buyType)) + { + for (int i = 0; i < CECShopConstants.BUY_COUNT; ++i) + { + if (buyType[i] == buyIndex) + { + result = true; + break; + } + } + } + return result; + } + + public bool CalcBuyType(int itemIndex, int[] buyTypes) + { + bool bOK = false; + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue) + { + GShopItem item = pItem.Value; + int[] typeDefault = new int[CECShopConstants.BUY_COUNT]; + int[] typeNew = new int[CECShopConstants.BUY_COUNT]; + int index1 = 0; + int index2 = 0; + for (int i = 0; i < CECShopConstants.BUY_COUNT; i++) + { + typeDefault[i] = -1; + typeNew[i] = -1; + if (item.buy != null && i < item.buy.Length) + { + if (item.buy[i].type == 3 && item.buy[i].price > 0) + { + typeNew[index1] = i; + index1++; + } + else if (item.buy[i].type == -1 && item.buy[i].price > 0) + { + typeDefault[index2] = i; + index2++; + } + } + } + if (index1 > 0) + { + Array.Copy(typeNew, buyTypes, CECShopConstants.BUY_COUNT); + bOK = true; + } + else if (index2 > 0) + { + Array.Copy(typeDefault, buyTypes, CECShopConstants.BUY_COUNT); + bOK = true; + } + } + return bOK; + } + + public bool HasSameBuyType(int itemIndexA, int itemIndexB) + { + int[] typeA = new int[CECShopConstants.BUY_COUNT]; + int[] typeB = new int[CECShopConstants.BUY_COUNT]; + if (CalcBuyType(itemIndexA, typeA) && + CalcBuyType(itemIndexB, typeB) && + typeA.SequenceEqual(typeB)) + { + GShopItem? pItemA = GetItem(itemIndexA); + GShopItem? pItemB = GetItem(itemIndexB); + if (pItemA.HasValue && pItemB.HasValue) + { + GShopItem itemA = pItemA.Value; + GShopItem itemB = pItemB.Value; + for (int i = 0; i < CECShopConstants.BUY_COUNT; ++i) + { + int buyIndex = typeA[i]; + if (buyIndex != -1) + { + bool priceAValid = (itemA.buy != null && buyIndex < itemA.buy.Length && itemA.buy[buyIndex].price != 0); + bool priceBValid = (itemB.buy != null && buyIndex < itemB.buy.Length && itemB.buy[buyIndex].price != 0); + if (priceAValid != priceBValid) + { + return false; + } + if (!priceAValid) + { + continue; + } + if (itemA.buy[buyIndex].time != itemB.buy[buyIndex].time) + { + return false; + } + } + } + return true; + } + } + return false; + } + + public bool CalcBuyIndex(int itemIndex, out int buyIndex, int cash = -1) + { + buyIndex = -1; + bool bOK = false; + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue) + { + GShopItem item = pItem.Value; + // 参考实现 CDlgQShopItem::SetItem / Reference implementation CDlgQShopItem::SetItem + + // 判断要显示的购买方式 / Determine which purchase method to display + int[] m_TypeDefault = new int[CECShopConstants.BUY_COUNT]; + int[] m_TypeNew = new int[CECShopConstants.BUY_COUNT]; + int index1 = 0; + int index2 = 0; + for (int i = 0; i < CECShopConstants.BUY_COUNT; i++) + { + m_TypeDefault[i] = -1; + m_TypeNew[i] = -1; + if (item.buy != null && i < item.buy.Length) + { + if (item.buy[i].type == 3 && item.buy[i].price > 0) + { + m_TypeNew[index1] = i; + index1++; + } + else if (item.buy[i].type == -1 && item.buy[i].price > 0) + { + m_TypeDefault[index2] = i; + index2++; + } + } + } + int m_BuyType = -1; + if (index1 > 0) + { + m_BuyType = 0; + } + else + { + m_BuyType = 1; + } + + if (cash == -1) + { + cash = GetCash(); + } + for (int i = 0; i < CECShopConstants.BUY_COUNT; i++) + { + int BuyIndex = 0; + if (m_BuyType == 0) + { + BuyIndex = m_TypeNew[i]; + } + else + { + BuyIndex = m_TypeDefault[i]; + } + if (BuyIndex != -1 && item.buy != null && BuyIndex < item.buy.Length) + { + uint price = item.buy[BuyIndex].price; + if (price != 0 && price <= cash) + { + bOK = true; + buyIndex = BuyIndex; + break; + } + } + } + } + return bOK; + } + + public int GetPrice(int itemIndex, int buyIndex) + { + int price = CECShopConstants.CECSHOP_INVALID_PRICE; + if (IsValid(itemIndex, buyIndex)) + { + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue && pItem.Value.buy != null && buyIndex < pItem.Value.buy.Length) + { + price = (int)pItem.Value.buy[buyIndex].price; + } + } + else + { + UnityEngine.Debug.Assert(false); + } + return price; + } + + public uint GetStatus(int itemIndex, int buyIndex) + { + uint status = 0xFFFFFFFF; // -1 as uint + if (IsValid(itemIndex, buyIndex)) + { + GShopItem? pItem = GetItem(itemIndex); + if (pItem.HasValue && pItem.Value.buy != null && buyIndex < pItem.Value.buy.Length) + { + status = pItem.Value.buy[buyIndex].status; + } + } + else + { + UnityEngine.Debug.Assert(false); + } + return status; + } + + public static bool IsSame(GShopItem lhs, GShopItem rhs) + { + bool result = false; + if (lhs.mainType != rhs.mainType || + lhs.subType != rhs.subType || + lhs.id != rhs.id || + lhs.num != rhs.num || + lhs.idGift != rhs.idGift || + lhs.giftNum != rhs.giftNum) + { + return false; + } + result = true; + if (lhs.buy != null && rhs.buy != null) + { + int buyCount = Math.Min(lhs.buy.Length, rhs.buy.Length); + for (int i = 0; i < buyCount && i < CECShopConstants.BUY_COUNT; ++i) + { + if (lhs.buy[i].type != rhs.buy[i].type || + lhs.buy[i].price != rhs.buy[i].price || + lhs.buy[i].time != rhs.buy[i].time || + lhs.buy[i].status != rhs.buy[i].status) + { + result = false; + break; + } + } + } + return result; + } + + public static int GetOriginalPrice(int finalPrice, uint discountStatus) + { + int result = finalPrice; + if (discountStatus >= 4 && discountStatus <= 12) + { + float originalPrice = finalPrice * (10.0f / (discountStatus - 3)); + if (CECUIConfig.Instance().GetGameUI().bEnableCeilPriceBeforeDiscountToGold) + { + result = (int)Math.Ceiling(originalPrice) + 99; + result /= 100; + result *= 100; + } + else + { + result = (int)Math.Ceiling(originalPrice); + } + } + return result; + } + + // Abstract methods + public abstract bool GetFromServer(int beginIndex, int endIndex); + public abstract uint GetLocalTimeStamp(); + public abstract uint GetServerTimeStamp(); + public abstract int GetCount(); + public abstract GShopItem? GetItem(int index); + public abstract int GetCash(); + public abstract bool Buy(int itemIndex, int buyIndex); + public abstract bool IsFashionShopEnabled(); + public abstract CECShopItemCategory GetFashionShopCategory(); + public abstract bool IsFashionShopFlashSaleEnabled(); + public abstract string GetFashionShopFlashSaleTitle(); + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopBase.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECShopBase.cs.meta new file mode 100644 index 0000000000..da10aac5bb --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopBase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 16ba0a2da752c6a49ad053e4af8be921 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopBaseChange.cs b/Assets/PerfectWorld/Scripts/Shop/CECShopBaseChange.cs new file mode 100644 index 0000000000..f147caae25 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopBaseChange.cs @@ -0,0 +1,36 @@ +// Filename : CECShopBaseChange.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using BrewMonster; + +namespace PerfectWorld.Scripts.Shop +{ + // 封装 CECShopBase 的变化 / Encapsulates changes to CECShopBase + public class CECShopBaseChange : CECObservableChange + { + public enum ChangeMask + { + ITEM_CHANGED = 0x01, + } + + private uint m_changeMask; + + public CECShopBaseChange(uint changeMask) + { + m_changeMask = changeMask; + } + + public uint GetChangeMask() + { + return m_changeMask; + } + + public bool ItemChanged() + { + return (GetChangeMask() & (uint)ChangeMask.ITEM_CHANGED) != 0; + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopBaseChange.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECShopBaseChange.cs.meta new file mode 100644 index 0000000000..6101ea3b3c --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopBaseChange.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e995fc9c415a8a143a75c18021965074 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopItemCategory.cs b/Assets/PerfectWorld/Scripts/Shop/CECShopItemCategory.cs new file mode 100644 index 0000000000..914f239c2f --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopItemCategory.cs @@ -0,0 +1,59 @@ +// Filename : CECShopItemCategory.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System; + +namespace PerfectWorld.Scripts.Shop +{ + // 封装 GSHOP_ITEM 的分类信息 / Encapsulates category information for GSHOP_ITEM + public class CECShopItemCategory + { + private int m_mainType; + private int m_subType; + + public CECShopItemCategory(int mainType = -1, int subType = -1) + { + m_mainType = mainType; + m_subType = subType; + } + + public void InitFromQShopConfig(int idCategory) + { + int mainType = -1; + int subType = -1; + CECQShopConfig.Instance().FindCategory(idCategory, out mainType, out subType); + SetCategory(mainType, subType); + } + + public void SetCategory(int mainType, int subType) + { + m_mainType = mainType; + m_subType = subType; + } + + public int GetMainType() + { + return m_mainType; + } + + public int GetSubType() + { + return m_subType; + } + + public bool MatchMainType(int mainType) + { + return m_mainType >= 0 && m_mainType == mainType; + } + + public bool MatchItem(GShopItem item) + { + // 匹配主类型有效时检查 / Check when main type matching is valid + return MatchMainType(item.mainType) + && (m_subType < 0 || item.subType == m_subType); // 子类型小于0时表示不限制子类型 / When sub type < 0, means no sub type restriction + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopItemCategory.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECShopItemCategory.cs.meta new file mode 100644 index 0000000000..77400befcc --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopItemCategory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8734b057cb3bec4d83b03b28b652b6a \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopItemOwnerNPC.cs b/Assets/PerfectWorld/Scripts/Shop/CECShopItemOwnerNPC.cs new file mode 100644 index 0000000000..8deb4cabdc --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopItemOwnerNPC.cs @@ -0,0 +1,79 @@ +// Filename : CECShopItemOwnerNPC.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +using System; + +namespace PerfectWorld.Scripts.Shop +{ + // 封装 GSHOP_ITEM 的挂靠 NPC 查询 / Encapsulates NPC ownership query for GSHOP_ITEM + public class CECShopItemOwnerNPC + { + private const int TREASURE_ITEM_OWNER_NPC_COUNT = 8; + + private uint m_ownerNPCID; + + public CECShopItemOwnerNPC(uint ownerNPCID = 0) + { + m_ownerNPCID = ownerNPCID; + } + + public void SetOwnerNPCID(uint ownerNPCID) + { + m_ownerNPCID = ownerNPCID; + } + + public uint GetOwnerNPCID() + { + return m_ownerNPCID; + } + + public bool IsEmpty() + { + return GetOwnerNPCID() == 0; + } + + public bool Equals(CECShopItemOwnerNPC rhs) + { + return GetOwnerNPCID() == rhs.GetOwnerNPCID(); + } + + public bool MatchID(uint id) + { + return id == m_ownerNPCID; + } + + public bool MatchItem(GShopItem item) + { + if (IsEmpty()) + { + return !HasOwnerNPC(item); + } + bool result = false; + if (item.ownerNpcs != null) + { + for (int i = 0; i < TREASURE_ITEM_OWNER_NPC_COUNT && i < item.ownerNpcs.Length; ++i) + { + if (MatchID(item.ownerNpcs[i])) + { + result = true; // 找到了 / Found + break; + } + if (item.ownerNpcs[i] == 0) + { + break; // 已经到最后一个了,后面都是0 / Already reached the last one, rest are 0 + } + } + } + return result; + } + + public static bool HasOwnerNPC(GShopItem item) + { + // 编辑器保证数组中,第一个是非零的在前 / Editor ensures that in the array, the first non-zero is at the front + return item.ownerNpcs != null && item.ownerNpcs.Length > 0 && item.ownerNpcs[0] != 0; + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECShopItemOwnerNPC.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECShopItemOwnerNPC.cs.meta new file mode 100644 index 0000000000..6fbe8b414f --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECShopItemOwnerNPC.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f4bc2fd669173534b9f94313c78af59e \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/Shop/CECUIConfig.cs b/Assets/PerfectWorld/Scripts/Shop/CECUIConfig.cs new file mode 100644 index 0000000000..3806c34c70 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECUIConfig.cs @@ -0,0 +1,39 @@ +// Filename : CECUIConfig.cs +// Creator : Xu Wenbin +// Date : 2013/12/11 +// Converted to C#: 2024 + +namespace PerfectWorld.Scripts.Shop +{ + // Stub implementation - to be properly implemented later + public class CECUIConfig + { + private static CECUIConfig s_instance; + + public static CECUIConfig Instance() + { + if (s_instance == null) + { + s_instance = new CECUIConfig(); + } + return s_instance; + } + + public GameUIConfig GetGameUI() + { + return new GameUIConfig(); + } + } + + public class GameUIConfig + { + public bool bEnableQShopFashionShop = false; + public bool bEnableQShopFashionShopFlashSale = false; + public string strQShopFashionShopFlashSaleTitle = ""; + public bool bEnableBackShopFashionShop = false; + public bool bEnableBackShopFashionShopFlashSale = false; + public string strBackShopFashionShopFlashSaleTitle = ""; + public bool bEnableCeilPriceBeforeDiscountToGold = false; + } +} + diff --git a/Assets/PerfectWorld/Scripts/Shop/CECUIConfig.cs.meta b/Assets/PerfectWorld/Scripts/Shop/CECUIConfig.cs.meta new file mode 100644 index 0000000000..e1be10b43d --- /dev/null +++ b/Assets/PerfectWorld/Scripts/Shop/CECUIConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 28ee6f77dbdc1204eb2a2f933941e610 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgNPC.cs b/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgNPC.cs index fd388ec7a0..dd91d8a214 100644 --- a/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgNPC.cs +++ b/Assets/PerfectWorld/Scripts/UI/Dialogs/DlgNPC.cs @@ -1460,6 +1460,32 @@ namespace BrewMonster.UI //pButton = (Button)pShow2.GetDlgItem("Btn_QuestItem"); //pButton.SetPushed(false); //GetGameUIMan().m_pDlgInventory.SetShowItem(CDlgInventory::INVENTORY_ITEM_NORMAL); + if (pCurNPCEssence.HasValue) + { + uint npcID = pCurNPCEssence.Value.id; + NPCShopUIManager shopManager = FindFirstObjectByType(); + if (shopManager == null) + { + // Instantiate NPCShopUIManager if not found, similar to how DlgNPC is instantiated + CECGameUIMan gameUIMan = GetGameUIMan(); + DialogScriptTableObject dialogResource = gameUIMan.GetDialogResource(); + Canvas canvas = gameUIMan.GetCanvas(); + + if (dialogResource != null && canvas != null) + { + GameObject ob = dialogResource.GetPrefabDialog("DialogNPCShop"); + if (ob != null) + { + shopManager = GameObject.Instantiate(ob, canvas.transform).GetComponent(); + } + } + } + + if (shopManager != null) + { + shopManager.OpenNPCShop(npcID); + } + } } else if (idFunction == (int)SERVICE_TYPE.NPC_INSTALL) { @@ -3072,6 +3098,7 @@ namespace BrewMonster.UI SetData(NPC_DIALOG.NPC_DIALOG_TALK, ""); } } + //Show Mua item id = 0 else { object pData1 = m_pLst_Main.GetItemDataPtr(nCurSel, 0, ""); @@ -3111,6 +3138,32 @@ namespace BrewMonster.UI //pButton = (PAUISTILLIMAGEBUTTON)pShow2.GetDlgItem("Btn_QuestItem"); //pButton.SetPushed(false); //GetGameUIMan().m_pDlgInventory.SetShowItem(CDlgInventory::INVENTORY_ITEM_NORMAL); + if (pCurNPCEssence.HasValue) + { + uint npcID = pCurNPCEssence.Value.id; + NPCShopUIManager shopManager = FindFirstObjectByType(); + if (shopManager == null) + { + // Instantiate NPCShopUIManager if not found, similar to how DlgNPC is instantiated + CECGameUIMan gameUIMan = GetGameUIMan(); + DialogScriptTableObject dialogResource = gameUIMan.GetDialogResource(); + Canvas canvas = gameUIMan.GetCanvas(); + + if (dialogResource != null && canvas != null) + { + GameObject ob = dialogResource.GetPrefabDialog("DialogNPCShop"); + if (ob != null) + { + shopManager = GameObject.Instantiate(ob, canvas.transform).GetComponent(); + } + } + } + + if (shopManager != null) + { + shopManager.OpenNPCShop(npcID); + } + } } else if (idFunction == (int)SERVICE_TYPE.NPC_INSTALL) { diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs index b43ace4a56..3763178897 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs @@ -103,6 +103,15 @@ namespace BrewMonster.UI return m_pDlgTask.UpdateQuestView(); } } + public DialogScriptTableObject GetDialogResource() + { + return m_dialogResouce; + } + + public Canvas GetCanvas() + { + return m_canvas; + } } public enum EC_GAMEUI_ICONS diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs new file mode 100644 index 0000000000..1af4021df2 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs @@ -0,0 +1,238 @@ +// Filename : NPCShopDetailPanel.cs +// Creator : Converted from C++ EC_Shop +// Date : 2024 + +using UnityEngine; +using UnityEngine.UI; +using TMPro; +using PerfectWorld.Scripts.Shop; +using PerfectWorld.Scripts.Managers; +using BrewMonster.Scripts.Managers; +using BrewMonster.Network; +using CSNetwork.C2SCommand; + +public class NPCShopDetailPanel : MonoBehaviour +{ + [Header("UI Components")] + public TextOutlet itemDescriptionText; + public Image itemIconImage; + //public TextMeshProUGUI itemPriceText; + + [Header("Action Buttons")] + public Button closeButton; + public Button buyButton; + + private GShopItem currentItem; + private NPCShopUIManager shopManager; + + void Start() + { + SetupEventListeners(); + } + + void SetupEventListeners() + { + if (closeButton != null) + closeButton.onClick.AddListener(OnCloseClicked); + + if (buyButton != null) + buyButton.onClick.AddListener(OnBuyButtonClicked); + } + + public void SetupDetailPanel(GShopItem item, NPCShopUIManager manager) + { + currentItem = item; + shopManager = manager; + + UpdateDisplay(); + } + + void UpdateDisplay() + { + if (currentItem.id == 0) + { + Debug.LogWarning("[NPCShopDetailPanel] Current item ID is 0, skipping display update"); + return; + } + + // Set item description + if (itemDescriptionText != null) + { + string description = GetItemDescription((int)currentItem.id); + itemDescriptionText.Set(description); + } + + // Set price (use first available price) + uint price = 0; + if (currentItem.buy != null && currentItem.buy.Length > 0) + { + for (int i = 0; i < currentItem.buy.Length; i++) + { + if (currentItem.buy[i].price > 0) + { + price = currentItem.buy[i].price; + break; + } + } + } + + // if (itemPriceText != null) + // itemPriceText.text = price > 0 ? $"Price: {price}" : "Price: N/A"; + + // Load icon + if (itemIconImage != null) + { + LoadItemIcon(itemIconImage, (int)currentItem.id); + } + } + + void LoadItemIcon(Image iconImage, int itemId) + { + if (itemId <= 0 || iconImage == null) + return; + + // Ensure the Image component is enabled and properly configured + iconImage.enabled = true; + iconImage.preserveAspect = true; + iconImage.type = Image.Type.Simple; + + // Use the existing icon loading system from EC_IvtrItemUtils + Sprite iconSprite = EC_IvtrItemUtils.Instance.ResolveItemIconSprite(itemId); + + if (iconSprite != null) + { + iconImage.sprite = iconSprite; + iconImage.color = Color.white; + } + else + { + iconImage.sprite = null; + } + } + + string GetItemDescription(int itemId) + { + // First check if description is already in the item data + if (!string.IsNullOrEmpty(currentItem.desc)) + { + return currentItem.desc; + } + + // Try to use EC_IvtrEquip description for equipment items (like EC_InventoryUI does) + try + { + // Create EC_IvtrEquip for equipment items - this will work for weapons and armor + EC_IvtrEquip equipment = new EC_IvtrEquip(itemId, 0); + + // For NPC shop items, we typically have static data only + // Try to get description using GetNormalDesc() which works with base stats + string equipDesc = equipment.GetNormalDesc(); + if (!string.IsNullOrEmpty(equipDesc)) + { + // Replace C++ style "\r" line separators with real newlines for TMP + return equipDesc.Replace("\\r", "\n"); + } + } + catch (System.Exception ex) + { + // If EC_IvtrEquip fails, fall through to EC_IvtrItem method + Debug.LogWarning($"[NPCShopDetailPanel] Failed to get equipment description for item {itemId}: {ex.Message}"); + } + + // Fallback: build description using EC_IvtrItem (for non-equipment items) + try + { + EC_IvtrItem item = EC_IvtrItem.CreateItem(itemId, 0, 1); + if (item != null) + { + // For NPC shop, we typically have static data only, so load from local DB + item.GetDetailDataFromLocal(); + string description = item.GetDesc(); + if (!string.IsNullOrEmpty(description)) + { + return description.Replace("\\r", "\n"); + } + } + } + catch (System.Exception ex) + { + Debug.LogWarning($"[NPCShopDetailPanel] Failed to get description for item {itemId}: {ex.Message}"); + } + + return "No description available."; + } + + void OnCloseClicked() + { + gameObject.SetActive(false); + } + + void OnBuyButtonClicked() + { + if (currentItem.id == 0) + { + Debug.LogWarning("[NPCShopDetailPanel] Cannot buy item with ID 0"); + return; + } + + // Get price from item + uint price = 0; + if (currentItem.buy != null && currentItem.buy.Length > 0) + { + for (int i = 0; i < currentItem.buy.Length; i++) + { + if (currentItem.buy[i].price > 0) + { + price = currentItem.buy[i].price; + break; + } + } + } + + // Create npc_trade_item array for buying from NPC + // The tid is the item template ID, index is shop item index (0 for now), count is quantity to buy + npc_trade_item[] items = new npc_trade_item[1]; + items[0] = new npc_trade_item + { + tid = (int)currentItem.id, + index = 0, // Shop item index - may need to be determined from shop item position + count = 1 // Quantity to buy + }; + + // Send the buy command + UnityGameSession.c2s_CmdNPCSevBuy(1, items); + + Debug.Log($"[NPCShopDetailPanel] Sent buy command for item {currentItem.id}, price {price}"); + } + + void OnDestroy() + { + // Clean up event listeners + if (closeButton != null) + closeButton.onClick.RemoveListener(OnCloseClicked); + + if (buyButton != null) + buyButton.onClick.RemoveListener(OnBuyButtonClicked); + } + + // === Text Outlet Helper === + [System.Serializable] + public class TextOutlet + { + [SerializeField] public Text legacy; + [SerializeField] public TMPro.TextMeshProUGUI tmp; + + public void Set(string value) + { + if (legacy != null) + { + legacy.text = EC_Utility.FormatForLegacyText(value ?? string.Empty); + } + if (tmp != null) + { + tmp.text = EC_Utility.FormatForTextMeshPro(value ?? string.Empty); + } + } + } +} + diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs.meta b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs.meta new file mode 100644 index 0000000000..8e7e351332 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/NPCShopDetailPanel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8456b18cc099671499414348dc8b6833 \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs b/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs new file mode 100644 index 0000000000..5ccd57d557 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/NPCShopItemPanel.cs @@ -0,0 +1,171 @@ +// Filename : NPCShopItemPanel.cs +// Creator : Converted from C++ EC_Shop +// Date : 2024 + +using UnityEngine; +using UnityEngine.UI; +using TMPro; +using System.Collections; +using PerfectWorld.Scripts.Shop; +using BrewMonster.Scripts.Managers; + +public class NPCShopItemPanel : MonoBehaviour +{ + [Header("UI Components")] + public TextMeshProUGUI itemNameText; + public TextMeshProUGUI itemPriceText; + public Image itemIconImage; + + private GShopItem itemData; + private Coroutine iconLoadCoroutine; + private NPCShopUIManager shopManager; + + void Start() + { + SetupClickHandler(); + } + + void SetupClickHandler() + { + // Check for existing Button component + Button button = GetComponent