Files
test/Assets/PerfectWorld/Scripts/MainFiles/EC_Game.cs
T
2026-04-30 16:29:22 +07:00

620 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using 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<int, int> 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<int, ItemMsgMapEntry> m_ItemMsgMap; // TemplateId -> (MessageId, DisplayMode)
private static CECConfigs m_pConfigs;
private static int m_iCurCursor; // Current cursor
private static List<int> m_PetAutoSkills = new List<int>();
#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<int, int> 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.txtC++Configs/Coord_data.txt)用于任务可点击链接的自动移动。
LoadObjectCoord();
LoadPetAutoSkill();
GlobalDataManager.globaldata_load();
return true;
}
public static CECConfigs GetConfigs() { return m_pConfigs; }
//todo release?
/// <summary>
/// Initialize all string tables with their respective data files
/// </summary>
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<int, int>();
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<TextAsset>("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<TextAsset>("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<TextAsset>("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<TextAsset>("Assets/Addressable/skillstr.txt").WaitForCompletion();
if (!m_SkillDesc.InitFromTextAsset(skillStrTa, true))
{
Debug.LogWarning("[EC_Game] Failed to load skillstr.txt");
}
var itemExtPropTa = Addressables.LoadAssetAsync<TextAsset>("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<TextAsset>("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<int, ItemMsgMapEntry>();
try
{
Addressables.InitializeAsync().WaitForCompletion();
var mapTa = Addressables.LoadAssetAsync<TextAsset>("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 <ws> messageId <ws> 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<string, List<OBJECT_COORD>> m_CoordTab =
new Dictionary<string, List<OBJECT_COORD>>(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<TextAsset>("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<OBJECT_COORD>(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;
}
/// <summary>
/// Build suite equip tab mapping. Must be called after m_pElementDataMan is initialized and data is loaded.
/// 构建套装装备表映射。必须在 m_pElementDataMan 初始化且数据加载完成后调用。
/// </summary>
public static void BuildSuiteEquipTab()
{
if (m_pElementDataMan == null)
{
return;
}
// Ensure m_SuiteEquipTab is initialized
// 确保 m_SuiteEquipTab 已初始化
if (m_SuiteEquipTab == null)
{
m_SuiteEquipTab = new Dictionary<int, int>();
}
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<OBJECT_COORD> TargetCoord)
{
TargetCoord = new List<OBJECT_COORD>();
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<bool> 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<TextAsset> 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<TextAsset>(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
}
}