Merge branch 'develop' into feature/skill-data

This commit is contained in:
VDH
2025-12-08 17:51:18 +07:00
52 changed files with 9820 additions and 3863 deletions
@@ -15,3 +15,5 @@ MonoBehaviour:
lstPrefabDialog:
- id: DialogNPC
prefab: {fileID: 8237288432181259026, guid: 7653e7e64393ec24c903f0606499b8c4, type: 3}
- id: DialogNPCShop
prefab: {fileID: 8237288432181259026, guid: eaeb778b6aab3d74299373b3a96b72c4, type: 3}
@@ -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<string, AsyncOperationHandle<GameObject>> _loadedAssets = new();
public event Action OnDispose;
protected override void Initialize()
{
@@ -63,10 +65,71 @@ namespace BrewMonster.Scripts
/// <summary>
/// When the asset is no longer needed, call this method to unload it.
/// </summary>
/// <param name="assetPath"></param>
public void UnloadAsset(string assetPath)
/// <param name="assetPath">The asset path used when loading the asset</param>
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}");
}
}
/// <summary>
/// Release a specific asset by its handle directly.
/// </summary>
/// <param name="handle">The async operation handle to release</param>
public void ReleaseAsset(AsyncOperationHandle<GameObject> handle)
{
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
/// <summary>
/// Release all loaded assets from the cache.
/// </summary>
public void ReleaseAllAssets()
{
foreach (var kvp in _loadedAssets)
{
if (kvp.Value.IsValid())
{
Addressables.Release(kvp.Value);
}
}
_loadedAssets.Clear();
BMLogger.Log("AddressableManager: Released all assets");
}
/// <summary>
/// Check if an asset is currently loaded in the cache.
/// </summary>
/// <param name="assetPath">The asset path to check</param>
/// <returns>True if the asset is loaded</returns>
public bool IsAssetLoaded(string assetPath)
{
return _loadedAssets.ContainsKey(assetPath) && _loadedAssets[assetPath].IsValid();
}
/// <summary>
/// Get the count of currently loaded assets.
/// </summary>
public int LoadedAssetCount => _loadedAssets.Count;
private void OnDestroy()
{
OnDispose?.Invoke();
ReleaseAllAssets();
}
}
}
@@ -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,
};
}
@@ -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);
}
}
}
@@ -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<RectTransform>();
// 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);
}
/// <summary>
/// Fill equipment-specific details
/// </summary>
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<string> reqs = new List<string>();
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<string> lines = new List<string>();
// 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");
}
}
}
/// <summary>
/// Clear equipment details
/// </summary>
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)
File diff suppressed because it is too large Load Diff
@@ -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
/// </summary>
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);
}
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// Try to get the extended description line from item_ext_desc.txt.
/// </summary>
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;
}
/// <summary>
/// Get extended description text for UI display.
/// This method mirrors AddExtDescText() logic but returns a string instead of modifying m_strDesc.
/// 此方法镜像AddExtDescText()逻辑,但返回字符串而不是修改m_strDesc
/// </summary>
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
}
@@ -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
@@ -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<NPCSevBuyCONTENT>();
uint itemSize = (uint)Marshal.SizeOf<CSNetwork.C2SCommand.npc_trade_item>();
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<NPCSevSellCONTENT>();
uint itemSize = (uint)Marshal.SizeOf<npc_sell_item>();
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)
@@ -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,
};
}
@@ -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<int> aRoleIDs)
{
if (iNumRole <= 0 || aRoleIDs == null || aRoleIDs.Count == 0) return;
@@ -46,7 +46,6 @@ namespace BrewMonster.Managers
private void OnDestroy()
{
EC_ManMessage.Dispose();
CECGameRun.Dispose();
}
private void Update()
@@ -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();
@@ -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<Model>
{
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<Model> where Model : class
{
private struct ObserverImpl
{
public CECObserver<Model> 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<Model> 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<Model> 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<ObserverImpl> m_observers = new List<ObserverImpl>();
protected Model AsModel()
{
return this as Model;
}
public bool IsObserverRegistered(CECObserver<Model> pObserver)
{
return m_observers.Any(impl => impl.Equals(pObserver));
}
public bool RegisterObserver(CECObserver<Model> 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<Model> 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;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1be878d234498514c8356a798b2e4515
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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<GShopItem>();
// 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<GShopLoader>()?.gameObject;
if (shopLoaderObj != null)
{
m_shopLoader = shopLoaderObj.GetComponent<GShopLoader>();
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<GShopItem> items = new List<GShopItem>(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<GShopItem> 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;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 01372a5632621c3438a20d995d97bdee
@@ -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<GShopItem>();
// 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<GShopLoader>()?.gameObject;
if (shopLoaderObj != null)
{
m_shopLoader = shopLoaderObj.GetComponent<GShopLoader>();
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<GShopItem> items = new List<GShopItem>(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<GShopItem> 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;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2d753dbecaf480345acbcb47feb6340f
@@ -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
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c8e321ee92225d04ab0eb82b965fe32d
@@ -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<GShopItem> m_pItems;
public CECShopArrayItems(List<GShopItem> 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<GShopItem> lhs, List<GShopItem> 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;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f70fb69e0e992a24c8e03b2558f3ee6f
@@ -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<CECShopBase>
{
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();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 16ba0a2da752c6a49ad053e4af8be921
@@ -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;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e995fc9c415a8a143a75c18021965074
@@ -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
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c8734b057cb3bec4d83b03b28b652b6a
@@ -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;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f4bc2fd669173534b9f94313c78af59e
@@ -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;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 28ee6f77dbdc1204eb2a2f933941e610
@@ -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<NPCShopUIManager>();
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<NPCShopUIManager>();
}
}
}
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<NPCShopUIManager>();
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<NPCShopUIManager>();
}
}
}
if (shopManager != null)
{
shopManager.OpenNPCShop(npcID);
}
}
}
else if (idFunction == (int)SERVICE_TYPE.NPC_INSTALL)
{
@@ -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
@@ -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);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8456b18cc099671499414348dc8b6833
@@ -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<Button>();
if (button == null)
button = GetComponentInChildren<Button>();
if (button != null)
{
button.onClick.AddListener(OnItemClicked);
}
else
{
// If no button exists, add one to the Image or create a clickable area
if (itemIconImage != null)
{
// Try to add Button component to the icon image
Button iconButton = itemIconImage.GetComponent<Button>();
if (iconButton == null)
iconButton = itemIconImage.gameObject.AddComponent<Button>();
if (iconButton != null)
{
iconButton.onClick.AddListener(OnItemClicked);
}
}
}
}
public void SetupItem(GShopItem item, NPCShopUIManager manager)
{
itemData = item;
shopManager = manager;
UpdateDisplay();
}
void OnItemClicked()
{
if (shopManager != null && itemData.id != 0)
{
shopManager.ShowItemDetail(itemData);
}
}
void UpdateDisplay()
{
if (itemData.id == 0)
return;
// Set item name
if (itemNameText != null)
itemNameText.text = itemData.name;
// Set price (use first available price)
uint price = 0;
if (itemData.buy != null && itemData.buy.Length > 0)
{
for (int i = 0; i < itemData.buy.Length; i++)
{
if (itemData.buy[i].price > 0)
{
price = itemData.buy[i].price;
break;
}
}
}
if (itemPriceText != null)
itemPriceText.text = price > 0 ? price.ToString() : "N/A";
// Load icon - use coroutine to ensure UI is ready
if (itemIconImage != null)
{
// Stop any existing icon load coroutine
if (iconLoadCoroutine != null)
{
StopCoroutine(iconLoadCoroutine);
}
// Start new icon load coroutine
iconLoadCoroutine = StartCoroutine(LoadItemIconCoroutine(itemIconImage, (int)itemData.id));
}
}
IEnumerator LoadItemIconCoroutine(Image iconImage, int itemId)
{
// Wait one frame to ensure UI is fully initialized
yield return null;
LoadItemIcon(iconImage, itemId);
}
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;
}
}
void OnDestroy()
{
if (iconLoadCoroutine != null)
{
StopCoroutine(iconLoadCoroutine);
}
// Clean up button listeners
Button button = GetComponent<Button>();
if (button == null)
button = GetComponentInChildren<Button>();
if (button != null)
{
button.onClick.RemoveListener(OnItemClicked);
}
if (itemIconImage != null)
{
Button iconButton = itemIconImage.GetComponent<Button>();
if (iconButton != null)
{
iconButton.onClick.RemoveListener(OnItemClicked);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 14c449229b5a12a42892e4fa263d6cbd
@@ -0,0 +1,464 @@
// Filename : NPCShopUIManager.cs
// Creator : Converted from C++ EC_Shop
// Date : 2024
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using PerfectWorld.Scripts.Shop;
using BrewMonster;
using static CSNetwork.Common.ExpTypes;
using ModelRenderer.Scripts.Common;
public class NPCShopUIManager : MonoBehaviour
{
[Header("UI Panels")]
public GameObject npcShopMainPanel;
public GameObject npcShopDetailPanel;
[Header("Tabs")]
public Transform tabButtonContainer;
public GameObject tabButtonPrefab;
public string tabButtonTextComponentName = "Text";
[Header("Item Display")]
public Transform itemContainer;
public GameObject itemPanelPrefab;
[Header("Main Panel Components")]
public Button closeShopButton;
private List<GameObject> currentItemPanels = new List<GameObject>();
private List<GameObject> currentTabButtons = new List<GameObject>();
private List<int> tabPageMapping = new List<int>(); // Maps tab index to page index
private int currentTabIndex = 0;
private uint currentNPCID = 0;
private NPC_SELL_SERVICE? cachedSellService = null;
private NPCShopDetailPanel detailPanelScript;
void Start()
{
if (closeShopButton != null)
closeShopButton.onClick.AddListener(CloseShop);
// Initialize detail panel reference
if (npcShopDetailPanel != null)
{
detailPanelScript = npcShopDetailPanel.GetComponent<NPCShopDetailPanel>();
if (detailPanelScript == null)
detailPanelScript = npcShopDetailPanel.GetComponentInChildren<NPCShopDetailPanel>();
// Initially hide the detail panel
if (npcShopDetailPanel.activeSelf)
npcShopDetailPanel.SetActive(false);
}
}
public void OpenNPCShop(uint npcID)
{
if (npcShopMainPanel == null)
{
Debug.LogError("NPCShopUIManager: npcShopMainPanel is null!");
return;
}
// Get NPC ID from CECUIManager
uint actualNPCID = npcID;
CECUIManager uiManager = CECUIManager.Instance;
if (uiManager != null)
{
int managerNPCID = uiManager.GetCurrentTargetNPCID();
if (managerNPCID > 0)
{
actualNPCID = (uint)managerNPCID;
}
}
currentNPCID = actualNPCID;
// Load sell service data
if (!LoadSellService(actualNPCID))
{
Debug.LogError($"NPCShopUIManager: Failed to load sell service for NPC {actualNPCID}");
return;
}
// Show panel
npcShopMainPanel.SetActive(true);
// Create tabs
CreateTabs();
// Select first tab
if (tabPageMapping.Count > 0)
{
OnTabSelected(0);
}
RefreshShopDisplay();
}
bool LoadSellService(uint npcID)
{
try
{
var elementDataMan = ElementDataManProvider.GetElementDataMan();
if (elementDataMan == null)
return false;
// Get NPC_ESSENCE
DATA_TYPE dataType = DATA_TYPE.DT_INVALID;
object npcData = elementDataMan.get_data_ptr(npcID, ID_SPACE.ID_SPACE_ESSENCE, ref dataType);
if (dataType != DATA_TYPE.DT_NPC_ESSENCE || npcData == null)
return false;
NPC_ESSENCE npcEssence = (NPC_ESSENCE)npcData;
uint sellServiceID = npcEssence.id_sell_service;
if (sellServiceID == 0)
return false;
// Get NPC_SELL_SERVICE
DATA_TYPE serviceDataType = DATA_TYPE.DT_INVALID;
object serviceData = elementDataMan.get_data_ptr(sellServiceID, ID_SPACE.ID_SPACE_ESSENCE, ref serviceDataType);
if (serviceDataType != DATA_TYPE.DT_NPC_SELL_SERVICE || serviceData == null)
return false;
cachedSellService = (NPC_SELL_SERVICE)serviceData;
return true;
}
catch (System.Exception ex)
{
Debug.LogError($"NPCShopUIManager: Exception loading sell service: {ex.Message}");
return false;
}
}
void CreateTabs()
{
ClearTabs();
if (currentNPCID == 0 || tabButtonContainer == null || tabButtonPrefab == null)
return;
if (!cachedSellService.HasValue || cachedSellService.Value.pages == null)
return;
var sellService = cachedSellService.Value;
tabPageMapping.Clear();
// Create tabs for each page that has items
for (int pageIndex = 0; pageIndex < sellService.pages.Length; pageIndex++)
{
var page = sellService.pages[pageIndex];
// Check if page has any items
bool hasItems = false;
if (page.goods != null)
{
foreach (var good in page.goods)
{
if (good.id != 0)
{
hasItems = true;
break;
}
}
}
if (!hasItems)
continue;
tabPageMapping.Add(pageIndex);
// Get page title
string pageTitle = ByteToStringUtils.UshortArrayToUnicodeString(page.page_title);
if (string.IsNullOrEmpty(pageTitle))
{
pageTitle = $"Page {pageIndex + 1}";
}
// Create tab button
GameObject tabButtonObj = Instantiate(tabButtonPrefab, tabButtonContainer);
tabButtonObj.name = $"Tab_Page_{pageIndex}";
tabButtonObj.SetActive(true);
// Get button component
Button tabButton = tabButtonObj.GetComponent<Button>();
if (tabButton == null)
tabButton = tabButtonObj.GetComponentInChildren<Button>();
// Get text component
TextMeshProUGUI tabText = tabButtonObj.GetComponent<TextMeshProUGUI>();
if (tabText == null)
{
Transform textTransform = tabButtonObj.transform.Find(tabButtonTextComponentName);
if (textTransform != null)
tabText = textTransform.GetComponent<TextMeshProUGUI>();
else
tabText = tabButtonObj.GetComponentInChildren<TextMeshProUGUI>();
}
if (tabText != null)
tabText.text = pageTitle;
// Setup click listener
if (tabButton != null)
{
int tabIndex = tabPageMapping.Count - 1; // Current tab index
tabButton.onClick.AddListener(() => OnTabSelected(tabIndex));
}
currentTabButtons.Add(tabButtonObj);
}
}
void ClearTabs()
{
foreach (GameObject tabButton in currentTabButtons)
{
if (tabButton != null)
Destroy(tabButton);
}
currentTabButtons.Clear();
tabPageMapping.Clear();
}
void OnTabSelected(int tabIndex)
{
if (tabIndex == currentTabIndex || tabIndex < 0 || tabIndex >= tabPageMapping.Count)
return;
currentTabIndex = tabIndex;
RefreshShopDisplay();
// Update tab button states
for (int i = 0; i < currentTabButtons.Count; i++)
{
if (currentTabButtons[i] != null)
{
Button tabButton = currentTabButtons[i].GetComponent<Button>();
if (tabButton == null)
tabButton = currentTabButtons[i].GetComponentInChildren<Button>();
if (tabButton != null)
tabButton.interactable = (i != tabIndex);
}
}
}
void RefreshShopDisplay()
{
// Clear existing items
ClearItems();
if (itemContainer == null || itemPanelPrefab == null)
return;
if (currentTabIndex < 0 || currentTabIndex >= tabPageMapping.Count)
return;
if (!cachedSellService.HasValue)
return;
int pageIndex = tabPageMapping[currentTabIndex];
var sellService = cachedSellService.Value;
if (pageIndex >= sellService.pages.Length)
return;
var page = sellService.pages[pageIndex];
// Create item panels
if (page.goods != null)
{
var elementDataMan = ElementDataManProvider.GetElementDataMan();
if (elementDataMan == null)
return;
foreach (var good in page.goods)
{
if (good.id == 0)
continue;
// Get item essence
DATA_TYPE itemDataType = DATA_TYPE.DT_INVALID;
object itemData = elementDataMan.get_data_ptr(good.id, ID_SPACE.ID_SPACE_ESSENCE, ref itemDataType);
if (itemData == null)
continue;
// Create GShopItem
GShopItem shopItem = CreateShopItemFromGood(good, itemData, itemDataType);
// Create panel
CreateItemPanel(shopItem);
}
}
}
GShopItem CreateShopItemFromGood(NPC_SELL_SERVICE.SellGood good, object itemData, DATA_TYPE itemDataType)
{
GShopItem shopItem = new GShopItem();
shopItem.id = good.id;
shopItem.num = 1;
// Initialize buy array with at least one option
shopItem.buy = new GShopBuyOption[4]; // GShopItem supports up to 4 buy options
// Get item name and price based on type
string itemName = "Unknown";
int shopPrice = 0;
switch (itemDataType)
{
case DATA_TYPE.DT_WEAPON_ESSENCE:
var weaponEssence = (WEAPON_ESSENCE)itemData;
itemName = weaponEssence.Name;
shopPrice = weaponEssence.shop_price;
break;
case DATA_TYPE.DT_ARMOR_ESSENCE:
var armorEssence = (ARMOR_ESSENCE)itemData;
itemName = armorEssence.Name;
shopPrice = armorEssence.shop_price;
break;
case DATA_TYPE.DT_MEDICINE_ESSENCE:
var medicineEssence = (MEDICINE_ESSENCE)itemData;
itemName = ByteToStringUtils.UshortArrayToUnicodeString(medicineEssence.name);
shopPrice = medicineEssence.shop_price;
break;
case DATA_TYPE.DT_DECORATION_ESSENCE:
var decorationEssence = (DECORATION_ESSENCE)itemData;
itemName = ByteToStringUtils.UshortArrayToUnicodeString(decorationEssence.name);
shopPrice = decorationEssence.shop_price;
break;
case DATA_TYPE.DT_STONE_ESSENCE:
var stoneEssence = (STONE_ESSENCE)itemData;
itemName = ByteToStringUtils.UshortArrayToUnicodeString(stoneEssence.name);
shopPrice = stoneEssence.shop_price;
break;
case DATA_TYPE.DT_MATERIAL_ESSENCE:
var materialEssence = (MATERIAL_ESSENCE)itemData;
itemName = ByteToStringUtils.UshortArrayToUnicodeString(materialEssence.name);
shopPrice = materialEssence.shop_price;
break;
default:
itemName = $"Item_{good.id}";
break;
}
shopItem.name = itemName;
// Set price from contribution cost or shop price
if (good.contrib_cost > 0)
{
shopItem.buy[0].price = (uint)good.contrib_cost;
shopItem.buy[0].type = 0; // 0 = permanent
}
else if (shopPrice > 0)
{
shopItem.buy[0].price = (uint)shopPrice;
shopItem.buy[0].type = 0; // 0 = permanent
}
else
{
// Initialize with invalid type if no price
shopItem.buy[0].type = -1; // -1 = invalid
shopItem.buy[0].price = 0;
}
return shopItem;
}
void CreateItemPanel(GShopItem item)
{
if (itemPanelPrefab == null || itemContainer == null)
return;
GameObject itemPanel = Instantiate(itemPanelPrefab, itemContainer);
itemPanel.SetActive(true);
NPCShopItemPanel itemPanelScript = itemPanel.GetComponent<NPCShopItemPanel>();
if (itemPanelScript == null)
itemPanelScript = itemPanel.GetComponentInChildren<NPCShopItemPanel>();
if (itemPanelScript != null)
{
itemPanelScript.SetupItem(item, this);
}
else
{
Debug.LogError($"[NPCShopUIManager] NPCShopItemPanel component not found on prefab! Prefab name: {itemPanelPrefab.name}");
}
currentItemPanels.Add(itemPanel);
}
void ClearItems()
{
foreach (GameObject panel in currentItemPanels)
{
if (panel != null)
Destroy(panel);
}
currentItemPanels.Clear();
}
public void ShowItemDetail(GShopItem item)
{
if (item.id == 0)
return;
if (npcShopDetailPanel == null)
{
Debug.LogWarning("[NPCShopUIManager] npcShopDetailPanel is not assigned!");
return;
}
// Ensure detail panel script is available
if (detailPanelScript == null)
{
detailPanelScript = npcShopDetailPanel.GetComponent<NPCShopDetailPanel>();
if (detailPanelScript == null)
detailPanelScript = npcShopDetailPanel.GetComponentInChildren<NPCShopDetailPanel>();
}
if (detailPanelScript != null)
{
npcShopDetailPanel.SetActive(true);
detailPanelScript.SetupDetailPanel(item, this);
}
else
{
Debug.LogError("[NPCShopUIManager] NPCShopDetailPanel component not found on npcShopDetailPanel GameObject!");
}
}
public void CloseDetailPanel()
{
if (npcShopDetailPanel != null)
npcShopDetailPanel.SetActive(false);
}
public void CloseShop()
{
if (npcShopMainPanel != null)
npcShopMainPanel.SetActive(false);
// Close detail panel when closing shop
CloseDetailPanel();
ClearTabs();
ClearItems();
cachedSellService = null;
currentNPCID = 0;
}
void OnDestroy()
{
ClearTabs();
ClearItems();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 009ffcf832a02b545a5cce71d5e32877
@@ -44,7 +44,7 @@ namespace BrewMonster.PerfectWorld.Scripts.Vfx
{
if(!string.IsNullOrEmpty(_vfxPath))
{
AddressableManager.Instance.UnloadAsset(_vfxPath);
AddressableManager.Instance.ReleaseAsset(_vfxPath);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: eaeb778b6aab3d74299373b3a96b72c4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
+259
View File
@@ -0,0 +1,259 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &532136160345846687
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7369895020851334411}
- component: {fileID: 214429650323778303}
- component: {fileID: 1383770310334783977}
- component: {fileID: 436672111126768001}
m_Layer: 0
m_Name: Tab_Button
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7369895020851334411
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 532136160345846687}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5379974387884416915}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: -10.5289, y: 0}
m_SizeDelta: {x: 78.9422, y: 40}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &214429650323778303
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 532136160345846687}
m_CullTransparentMesh: 1
--- !u!114 &1383770310334783977
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 532136160345846687}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 8f24853d9cfea43389e8fb3101ffaae1, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &436672111126768001
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 532136160345846687}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1383770310334783977}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &5614972339824250559
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5379974387884416915}
- component: {fileID: 8079830739419442855}
- component: {fileID: 7398759648826456359}
m_Layer: 0
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5379974387884416915
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5614972339824250559}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7369895020851334411}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 1.3087}
m_SizeDelta: {x: 0, y: -2.6175}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8079830739419442855
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5614972339824250559}
m_CullTransparentMesh: 1
--- !u!114 &7398759648826456359
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5614972339824250559}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Button
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4281348144
m_fontColor: {r: 0.18867922, g: 0.18867922, b: 0.18867922, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 22
m_fontSizeBase: 22
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 548ae6ac061bc9648b093c9f9d203615
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+174
View File
@@ -0,0 +1,174 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3478571236783653060
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7551350917878394023}
- component: {fileID: 1847471482995505170}
- component: {fileID: 2586856635111866746}
m_Layer: 0
m_Name: item
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7551350917878394023
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3478571236783653060}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5438773728966160586}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 42, y: -42}
m_SizeDelta: {x: 84, y: 84}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1847471482995505170
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3478571236783653060}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 14c449229b5a12a42892e4fa263d6cbd, type: 3}
m_Name:
m_EditorClassIdentifier:
itemNameText: {fileID: 0}
itemPriceText: {fileID: 0}
itemIconImage: {fileID: 7716303670266317094}
--- !u!114 &2586856635111866746
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3478571236783653060}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 7716303670266317094}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &5714852699199745934
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5438773728966160586}
- component: {fileID: 7941037763135429874}
- component: {fileID: 7716303670266317094}
m_Layer: 5
m_Name: icon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5438773728966160586
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5714852699199745934}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7551350917878394023}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: -42}
m_SizeDelta: {x: 84, y: 84}
m_Pivot: {x: 0, y: 0.5}
--- !u!222 &7941037763135429874
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5714852699199745934}
m_CullTransparentMesh: 1
--- !u!114 &7716303670266317094
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5714852699199745934}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 8b997e886fff00540b9c069a9dff12af, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3c44c4ffd9028b8439ed778ff0dd564b
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+6 -6
View File
@@ -35,20 +35,20 @@ public partial class CECGameRun
// _playerPrefab = _gameRunConfig.PlayerPrefab;
// _monsterPrefab = _gameRunConfig.MonsterPrefab;
// _npcServerPrefab = _gameRunConfig.NpcServerPrefab;
// _testVfxPrefab = _gameRunConfig.TestVfxPrefab;
LoadPrefabs();
// LoadPrefabs();
EC_ManMessage.RegisterHandler(this);
AddressableManager.Instance.OnDispose += Dispose;
}
public static void Dispose()
private static void Dispose()
{
instance = null;
AddressableManager.Instance.UnloadAsset(AddressResourceConfig.PlayerPrefab);
AddressableManager.Instance.UnloadAsset(AddressResourceConfig.MonsterPrefab);
AddressableManager.Instance.UnloadAsset(AddressResourceConfig.NpcServerPrefab);
AddressableManager.Instance.UnloadAsset(AddressResourceConfig.TestVfxPrefab);
AddressableManager.Instance.ReleaseAsset(AddressResourceConfig.PlayerPrefab);
AddressableManager.Instance.ReleaseAsset(AddressResourceConfig.MonsterPrefab);
AddressableManager.Instance.ReleaseAsset(AddressResourceConfig.NpcServerPrefab);
}
private async void LoadPrefabs()
+70 -19
View File
@@ -54,35 +54,74 @@ namespace BrewMonster
protected bool LoadANSIStrings(string resourceName)
{
TextAsset textAsset = Resources.Load<TextAsset>(resourceName);
if (textAsset == null)
try
{
Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
// If a real file path is provided (e.g. StreamingAssets), read directly from disk.
// 如果提供的是实际文件路径(例如 StreamingAssets),则直接从磁盘读取。
if (File.Exists(resourceName))
{
// ANSI tables are in CP936 in original PW; keep using CP936 decoder.
// 原版完美世界的ANSI表是CP936编码,这里保持一致。
byte[] bytes = File.ReadAllBytes(resourceName);
string content = ByteToStringUtils.ByteArrayToCP936String(bytes);
using var srFile = new StringReader(content);
return ParseIntoDict(srFile, isWide: false);
}
// Fallback to Resources (old behaviour).
// 回退到 Resources 加载(旧行为)。
TextAsset textAsset = Resources.Load<TextAsset>(resourceName);
if (textAsset == null)
{
Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
return false;
}
string resContent = ByteToStringUtils.ByteArrayToCP936String(textAsset.bytes);
using var srRes = new StringReader(resContent);
return ParseIntoDict(srRes, isWide: false);
}
catch (Exception e)
{
Debug.LogError($"[CECStringTab] LoadANSIStrings failed for '{resourceName}': {e}");
return false;
}
// Giải mã bytes -> string (ANSI: dùng Encoding.Default)
string content = ByteToStringUtils.ByteArrayToCP936String(textAsset.bytes);
using var sr = new StringReader(content);
return ParseIntoDict(sr, isWide: false);
}
protected bool LoadWideStrings(string resourceName)
{
TextAsset textAsset = Resources.Load<TextAsset>(resourceName);
if (textAsset == null)
try
{
Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
// Support absolute / relative filesystem paths (e.g. StreamingAssets/configs/*.txt)
// 支持文件系统路径(例如 StreamingAssets/configs/*.txt
if (File.Exists(resourceName))
{
// String tables we ship in StreamingAssets are saved as UTF-8.
// 我们放在 StreamingAssets 里的字符串表保存为 UTF-8。
string content = File.ReadAllText(resourceName, Encoding.UTF8);
using var srFile = new StringReader(content);
return ParseIntoDict(srFile, isWide: true);
}
// Fallback to Resources-based loading (old behaviour)
// 回退到基于 Resources 的加载(旧行为)
TextAsset textAsset = Resources.Load<TextAsset>(resourceName);
if (textAsset == null)
{
Debug.LogError($"[CECStringTab] Resource not found: {resourceName}");
return false;
}
// Unity TextAsset.text is already UTF-8 decoded.
string resContent = textAsset.text;
using var srRes = new StringReader(resContent);
return ParseIntoDict(srRes, isWide: true);
}
catch (Exception e)
{
Debug.LogError($"[CECStringTab] LoadWideStrings failed for '{resourceName}': {e}");
return false;
}
// Unity TextAsset mặc định đã decode text UTF8 -> textAsset.text
// nhưng để chắc chắn BOM/Unicode thì đọc từ bytes
string content;
content = textAsset.text;
using var sr = new StringReader(content);
return ParseIntoDict(sr, isWide: true);
}
private static Encoding DetectEncoding(byte[] bom)
@@ -183,6 +222,18 @@ namespace BrewMonster
private void PutString(int id, string value, bool isWide)
{
if (string.IsNullOrEmpty(value))
return;
// Many PW string tables wrap the payload in double quotes, e.g.:
// 12345 "^ffcb4aSome text\rMore text"
// Strip a single leading/trailing quote pair to avoid showing raw quotes in UI.
// 许多字符串表会用双引号包裹内容,这里去掉首尾各一个引号以避免在UI中显示多余的引号。
if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"')
{
value = value.Substring(1, value.Length - 2);
}
if (isWide) m_WStrTab[id] = value;
else m_AStrTab[id] = value;
}
+8
View File
@@ -130,4 +130,12 @@ public class CECUIManager : MonoSingleton<CECUIManager>
}
return gameUI;
}
/// <summary>
/// Get the current target NPC ID (same as stored from NPCINFO event)
/// </summary>
public int GetCurrentTargetNPCID()
{
return currentTargetNPCID;
}
}