using BrewMonster; using BrewMonster.Scripts.Task; using CSNetwork; using Cysharp.Threading.Tasks; using ModelRenderer.Scripts.GameData; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using CSNetwork.GPDataType; namespace BrewMonster.Network { public partial class EC_Game { #region Fields private static CECFactionMan m_pFactionMan; // Faction manager public static bool g_bEnableFortressDeclareWar = false; private static ATaskTemplMan m_pTaskMan; // Task template manager private static elementdataman m_pElementDataMan; // global element templates manager private static CECGameRun m_pGameRun => CECGameRun.Instance; // Game running object private static CECGFXCaster m_pGFXCaster; // GFX caster private static BrewMonster.CECStringTab m_FixedMsgs; // Fixed message table private static BrewMonster.CECStringTab m_ItemDesc; // Item desciption string table private static BrewMonster.CECStringTab m_ItemExtDesc; // Item extend description string table private static BrewMonster.CECStringTab m_ItemExtProp; // Item extend prop string table private static BrewMonster.CECStringTab ItemColTab; // Item color string table private static Dictionary m_SuiteEquipTab; // Item suite string table private static BrewMonster.CECStringTab m_SkillDesc = new CECStringTab(); // Skill description string table private static BrewMonster.CECStringTab m_BuffDesc; // Buff description string table private static Dictionary m_ItemMsgMap; // TemplateId -> (MessageId, DisplayMode) private static CECConfigs m_pConfigs; private static int m_iCurCursor; // Current cursor private static List m_PetAutoSkills = new List(); #endregion #region Properties public static ATaskTemplMan GetTaskTemplateMan() { return m_pTaskMan; } public static elementdataman GetElementDataMan() { return m_pElementDataMan; } // String table getters public static CECFactionMan GetFactionMan() { return m_pFactionMan; } public static BrewMonster.CECStringTab GetFixedMsgs() { return m_FixedMsgs; } public static BrewMonster.CECStringTab GetItemDesc() { return m_ItemDesc; } public static BrewMonster.CECStringTab GetItemExtDesc() { return m_ItemExtDesc; } public static BrewMonster.CECStringTab GetSkillDesc() { return m_SkillDesc; } public static BrewMonster.CECStringTab GetBuffDesc() { return m_BuffDesc; } public static BrewMonster.CECStringTab GetItemExtProp() { return m_ItemExtProp; } public static BrewMonster.CECStringTab GetItemColTab() { return ItemColTab; } public static Dictionary GetSuiteEquipTab() { return m_SuiteEquipTab; } public static bool TryGetItemMsg(int templateId, out int messageId, out int displayMode) { messageId = 0; displayMode = 0; if (m_ItemMsgMap != null && m_ItemMsgMap.TryGetValue(templateId, out var entry)) { messageId = entry.MessageId; displayMode = entry.DisplayMode; return true; } return false; } #endregion #region Public Methods public static bool Init() { m_pElementDataMan = ElementDataManProvider.GetElementDataMan(); // Load task templates // if (m_pTaskMan == null) m_pTaskMan = new ATaskTemplMan(); m_pTaskMan = new ATaskTemplMan(); m_pTaskMan.Init(m_pElementDataMan); m_pConfigs = new CECConfigs(); /*ElementClient.g_GameCfgs*/; if (!m_pTaskMan.InitStorageTask()) { BMLogger.LogError("[Dat]- CECGame::Init, Storage task Init Failed!"); // return false; } // Create GFX caster if (m_pGFXCaster == null) { m_pGFXCaster = new CECGFXCaster(); // return false; } GetGameRun().Init(); InitializeStringTables(); // Load coord_data.txt (C++: Configs/Coord_data.txt) for clickable task links auto-move. // 加载 coord_data.txt(C++:Configs/Coord_data.txt)用于任务可点击链接的自动移动。 LoadObjectCoord(); LoadPetAutoSkill(); GlobalDataManager.globaldata_load(); return true; } public static CECConfigs GetConfigs() { return m_pConfigs; } //todo release? /// /// Initialize all string tables with their respective data files /// private static void InitializeStringTables() { // Initialize string table instances m_FixedMsgs = new BrewMonster.CECStringTab(); m_ItemDesc = new BrewMonster.CECStringTab(); m_ItemExtDesc = new BrewMonster.CECStringTab(); m_SkillDesc = new BrewMonster.CECStringTab(); m_BuffDesc = new BrewMonster.CECStringTab(); m_ItemExtProp = new BrewMonster.CECStringTab(); ItemColTab = new BrewMonster.CECStringTab(); m_SuiteEquipTab = new Dictionary(); try { // Addressables-only loading (no StreamingAssets/configs file IO). // These must match the Addressables "Address" values configured in `Assets/AddressableAssetsData/...`. Addressables.InitializeAsync().WaitForCompletion(); var fixedMsgTa = Addressables.LoadAssetAsync("Assets/Addressable/fixed_msg.txt").WaitForCompletion(); if (!m_FixedMsgs.InitFromTextAsset(fixedMsgTa, true)) { Debug.LogWarning("[EC_Game] Failed to load fixed_msg.txt"); } var itemDescTa = Addressables.LoadAssetAsync("Assets/Addressable/item_desc.txt").WaitForCompletion(); if (!m_ItemDesc.InitFromTextAsset(itemDescTa, true)) { Debug.LogWarning("[EC_Game] Failed to load item_desc.txt"); } var itemExtDescTa = Addressables.LoadAssetAsync("Assets/Addressable/item_ext_desc.txt").WaitForCompletion(); if (!m_ItemExtDesc.InitFromTextAsset(itemExtDescTa, true)) { Debug.LogWarning("[EC_Game] Failed to load item_ext_desc.txt"); } var skillStrTa = Addressables.LoadAssetAsync("Assets/Addressable/skillstr.txt").WaitForCompletion(); if (!m_SkillDesc.InitFromTextAsset(skillStrTa, true)) { Debug.LogWarning("[EC_Game] Failed to load skillstr.txt"); } var itemExtPropTa = Addressables.LoadAssetAsync("Assets/Addressable/item_ext_prop.txt").WaitForCompletion(); if (!m_ItemExtProp.InitFromTextAsset(itemExtPropTa, true)) { Debug.LogWarning("[EC_Game] Failed to load item_ext_prop.txt"); } var itemColTa = Addressables.LoadAssetAsync("Assets/Addressable/item_col.txt").WaitForCompletion(); if (!ItemColTab.InitFromTextAsset(itemColTa, true)) { Debug.LogWarning("[EC_Game] Failed to load item_col.txt"); } // Note: There's no buff_desc.txt file in the configs folder // You may need to create this file or use a different source for buff descriptions // (If you add it to Addressables later, load it here.) // BuildSuiteEquipTab() is now called from ElementDataManProvider after data is loaded // BuildSuiteEquipTab() 现在在 ElementDataManProvider 数据加载完成后调用 // Load item message map (template -> message id) LoadItemMsgMap(); Debug.Log("[EC_Game] String tables initialized successfully"); } catch (System.Exception ex) { Debug.LogError($"[EC_Game] Error initializing string tables: {ex.Message}"); } } private struct ItemMsgMapEntry { public int MessageId; public int DisplayMode; } private static void LoadItemMsgMap() { m_ItemMsgMap = new Dictionary(); try { Addressables.InitializeAsync().WaitForCompletion(); var mapTa = Addressables.LoadAssetAsync("Assets/Addressable/item_msg_map.txt").WaitForCompletion(); if (mapTa == null || string.IsNullOrEmpty(mapTa.text)) { Debug.LogWarning( "[EC_Game] item_msg_map.txt not found; descriptions will fall back to template IDs"); return; } using var sr = new StringReader(mapTa.text); string raw; while ((raw = sr.ReadLine()) != null) { var line = raw.Trim(); if (line.Length == 0) continue; if (line.StartsWith("//")) continue; // Expect: templateId messageId displayMode var parts = line.Split(new char[] { '\t', ' ' }, System.StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 2) continue; if (!int.TryParse(parts[0], out int templateId)) continue; if (!int.TryParse(parts[1], out int messageId)) continue; int displayMode = 0; if (parts.Length >= 3) int.TryParse(parts[2], out displayMode); m_ItemMsgMap[templateId] = new ItemMsgMapEntry { MessageId = messageId, DisplayMode = displayMode }; } } catch (System.Exception ex) { Debug.LogWarning($"[EC_Game] Failed to load item_msg_map: {ex.Message}"); } } public static CECGameRun GetGameRun() { return m_pGameRun; } public static CECGFXCaster GetGFXCaster() { if (m_pGFXCaster == null) m_pGFXCaster = new CECGFXCaster(); return m_pGFXCaster; } // Change current cursor public static int ChangeCursor(int iCursor) { if (iCursor == m_iCurCursor) return iCursor; // if (m_aCursors[iCursor]) // m_pA3DDevice->SetCursor(m_aCursors[iCursor]); // // // force show this cursor // ShowCursor(g_pGame->GetA3DDevice()->GetShowCursor()); // // if( l_idMainThread != GetCurrentThreadId() ) // { // // ::SetCursor must be called from main thread to take effects, so here we should post a WM_SETCURSOR message // // to ensure the main thread receive WM_SETCURSOR and update the cursor again // PostMessage(m_GameInit.hWnd, WM_SETCURSOR, (WPARAM)m_GameInit.hWnd, MAKELPARAM(HTCLIENT, WM_MOUSEMOVE)); // } int iOldCursor = m_iCurCursor; m_iCurCursor = iCursor; return iOldCursor; } // Get server GMT(UTC) time public static int GetServerGMTTime() { long unixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); return (int)unixTime + m_iTimeError; } #region Dummy Methods for itemdataman public static int addon_generate_arg(DATA_TYPE type, addon_data data, int arg_num/*��ʼ�IJ�������*/) { return arg_num; } public static void get_item_guid(uint id, out int g1, out int g2) { g1 = 0; g2 = 1; } public static int addon_update_ess_data(addon_data data, object essence,int ess_size, prerequisition require) { return 0; } public static void update_require_data(ref prerequisition require) { require.durability *= GameConstants.ENDURANCE_SCALE; require.max_durability *= GameConstants.ENDURANCE_SCALE; } public static void set_to_classid(DATA_TYPE type, byte[] data, int major_type) { } #endregion //////////////////////////////////////////////////////////////////////////////// // // Coord_data.txt support (C++: Configs/Coord_data.txt, CECGame::LoadObjectCoord/GetObjectCoord) // //////////////////////////////////////////////////////////////////////////////// private static readonly Dictionary> m_CoordTab = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static bool m_bCoordLoaded = false; public static bool LoadObjectCoord() { if (m_bCoordLoaded) { return true; } try { Addressables.InitializeAsync().WaitForCompletion(); var ta = Addressables.LoadAssetAsync("Assets/Addressable/coord_data.txt").WaitForCompletion(); if (ta == null) { Debug.LogError("[EC_Game] LoadObjectCoord: failed to load Addressable 'Assets/Addressable/coord_data.txt'"); return false; } ParseCoordDataText(ta.text); m_bCoordLoaded = true; Debug.Log($"[EC_Game] LoadObjectCoord: loaded {m_CoordTab.Count} coord keys from coord_data.txt"); return true; } catch (Exception ex) { Debug.LogError($"[EC_Game] LoadObjectCoord exception: {ex}"); return false; } } private static void ParseCoordDataText(string text) { m_CoordTab.Clear(); if (string.IsNullOrEmpty(text)) { return; } using var sr = new StringReader(text); string line; int lineNo = 0; while ((line = sr.ReadLine()) != null) { lineNo++; if (string.IsNullOrWhiteSpace(line)) { continue; } line = line.Trim(); if (line.StartsWith("#", StringComparison.Ordinal) || line.StartsWith("//", StringComparison.Ordinal)) { continue; } if (lineNo == 1 && line.StartsWith("ID", StringComparison.OrdinalIgnoreCase)) { continue; } string[] parts = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 5) { continue; } string key = parts[0]; string map = parts[1]; if (!float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out float x) || !float.TryParse(parts[3], NumberStyles.Float, CultureInfo.InvariantCulture, out float y) || !float.TryParse(parts[4], NumberStyles.Float, CultureInfo.InvariantCulture, out float z)) { continue; } var coord = new OBJECT_COORD { strMap = map, vPos = new A3DVECTOR3(x, y, z), }; if (!m_CoordTab.TryGetValue(key, out var list)) { list = new List(1); m_CoordTab[key] = list; } list.Add(coord); } } public static bool TryGetFirstObjectCoord(string targetId, out Vector3 pos, out string map) { pos = default; map = null; if (string.IsNullOrWhiteSpace(targetId)) { return false; } if (!m_bCoordLoaded) { LoadObjectCoord(); } if (!m_CoordTab.TryGetValue(targetId, out var list) || list == null || list.Count == 0) { return false; } pos = new Vector3(list[0].vPos.x, list[0].vPos.y, list[0].vPos.z); map = list[0].strMap; return true; } /// /// Build suite equip tab mapping. Must be called after m_pElementDataMan is initialized and data is loaded. /// 构建套装装备表映射。必须在 m_pElementDataMan 初始化且数据加载完成后调用。 /// public static void BuildSuiteEquipTab() { if (m_pElementDataMan == null) { return; } // Ensure m_SuiteEquipTab is initialized // 确保 m_SuiteEquipTab 已初始化 if (m_SuiteEquipTab == null) { m_SuiteEquipTab = new Dictionary(); } DATA_TYPE DataType = DATA_TYPE.DT_INVALID; elementdataman _edm = ElementDataManProvider.GetElementDataMan(); for (int i = 0; i < _edm.essence_id_data_type_map.Count; i++) { uint tid = _edm.get_data_id(ID_SPACE.ID_SPACE_ESSENCE, i, ref DataType); object pData = _edm.get_data_ptr(tid, ID_SPACE.ID_SPACE_ESSENCE, ref DataType); switch (DataType) { case DATA_TYPE.DT_SUITE_ESSENCE: SUITE_ESSENCE pSuiteEss = (SUITE_ESSENCE)pData; pSuiteEss.max_equips = 0; for (int j=0; j<12; j++) { if( pSuiteEss.equipments[j].id != 0 ) { pSuiteEss.max_equips ++; int index = (int)pSuiteEss.equipments[j].id; m_SuiteEquipTab[index] = (int)tid; } } break; case DATA_TYPE.DT_POKER_SUITE_ESSENCE: POKER_SUITE_ESSENCE pPokerSuiteEss = (POKER_SUITE_ESSENCE)pData; for (int j=0; j<6; j++) { if( pPokerSuiteEss.list[j] != 0 ) { int index = (int)pPokerSuiteEss.list[j]; m_SuiteEquipTab[index] = (int)tid; } } break; case DATA_TYPE.DT_FASHION_SUITE_ESSENCE: FASHION_SUITE_ESSENCE pFashionSuiteEss = (FASHION_SUITE_ESSENCE)pData; for (int j=0; j<6; j++) { if( pFashionSuiteEss.list[j] != 0 ) { int index = (int)pFashionSuiteEss.list[j]; m_SuiteEquipTab[index] = (int)tid; } } break; } } } public static int GetItemNameColorIdx(int tid, int iDefIndex = 0) { int iIndex = iDefIndex; string color = ItemColTab.GetWideString(tid); if (!string.IsNullOrEmpty(color)) iIndex = color.GetHashCode(); if (iIndex < 0 || iIndex >= 10) { iIndex = 0; } return iIndex; } public static int GetObjectCoord(string strTargetID, out List TargetCoord) { TargetCoord = new List(); if (string.IsNullOrWhiteSpace(strTargetID)) return 0; if (!m_bCoordLoaded) LoadObjectCoord(); if (!m_CoordTab.TryGetValue(strTargetID, out var list) || list == null) return 0; TargetCoord = list; return list.Count; } public static bool IsPetAutoSkill(int skill_id) { return m_PetAutoSkills.Contains(skill_id); } // Load the pet auto skill table public static async UniTask LoadPetAutoSkill() { string pszFilename = "Assets/Addressable/petautoskill.txt"; var ta = await LoadStringTableTextAssetByAddressables(pszFilename); if (ta == null || string.IsNullOrEmpty(ta.text)) { BMLogger.LogError($"[AUIManager] ImportStringTable failed: cannot load Addressables TextAsset for key='{pszFilename}'"); return false; } using (var sr = new StringReader(ta.text)) { string line; while ((line = sr.ReadLine()) != null) { if (string.IsNullOrWhiteSpace(line)) continue; if (int.TryParse(line, out int key)) { m_PetAutoSkills.Add(key); } } } return true; } private static async UniTask LoadStringTableTextAssetByAddressables(string key) { try { // Initialize Addressables if not already initialized (Unity-safe) await Addressables.InitializeAsync().ToUniTask(); // Load using Addressables directly with WaitForCompletion (Unity-safe, won't deadlock) // This matches the pattern used in EC_Game.cs var handle = Addressables.LoadAssetAsync(key); var textAsset = await handle.ToUniTask(); if (handle.Status == AsyncOperationStatus.Succeeded && textAsset != null) { // Keep the handle valid; string tables are used for the whole session // Note: We don't release the handle here to keep the asset loaded return textAsset; } if (handle.IsValid()) { Addressables.Release(handle); } BMLogger.LogError($"[AUIManager] Failed to load TextAsset for key='{key}'"); return null; } catch (Exception e) { BMLogger.LogError($"[AUIManager] LoadStringTableTextAssetByAddressables exception for key='{key}': {e}"); return null; } } #endregion } }