using BrewMonster; using CSNetwork.GPDataType; using Cysharp.Threading.Tasks; using ModelRenderer.Scripts.GameData; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Threading; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; namespace BrewMonster.Scripts.Task { /// /// contains and manages all task templates /// init in EC_Game /// [Serializable] public class ATaskTemplMan { public int TaskLoadedCount => m_TaskTemplMap.Count; public const ulong TASK_PACK_MAGIC = 0x93858361; public const ulong _task_templ_cur_version = 121; private const int DYN_TASK_CUR_VERSION = 10; private const int DYN_TASK_VERIFY_SVR = 8711; private const int DYN_TASK_DELIVER_SVR = 8889; private const int TASK_NPC_INFO_VERSION = 1; private const int SEEK_SET = 0; /* set file offset to offset */ private const int SEEK_CUR = 1; /* set file offset to current plus offset */ private const int SEEK_END = 2; /* set file offset to EOF plus offset */ private ulong g_ulNewCount = 0;// do we need this? // MH: I think not, it look like a debug counter private Dictionary m_TaskTemplMap = new Dictionary(); private Dictionary m_AllTemplMap = new Dictionary(); private Dictionary m_DynTaskMap = new Dictionary(); private Dictionary m_TitleTaskMap = new Dictionary(); private Dictionary m_ExlusiveAwardTaskMap = new Dictionary(); private Dictionary m_ProtectNPCMap = new Dictionary(); private Dictionary m_AutoDelvMap = new Dictionary(); private Dictionary m_DeathTrigMap = new Dictionary(); private Dictionary m_PQTemplMap = new Dictionary(); private Dictionary m_StorageEssenseMap = new Dictionary(); private Dictionary m_WeightEssenseMap = new Dictionary(); private Dictionary m_StorageTaskMap = new Dictionary(); private List m_SkillTaskLst = new List(); private List m_TmLmtChkLst = new List(); private List m_TasksCanSeekOut = new List(); private elementdataman m_pEleDataMan; // Dictionary m_DynTaskMap = new (); uint m_ulDynTasksTimeMark; byte[] m_pDynTasksData; uint m_ulDynTasksDataSize; byte[] m_pNPCInfoData; uint m_ulNPCInfoDataSize; uint m_ulNPCInfoTimeMark; private Dictionary m_NPCInfoMap = new(); private bool _wasLoaded = false; // Lookup NPC/task object coordinates info by template id (loaded from task_npc pack) // 通过模板ID查找NPC/任务对象坐标信息(从task_npc包加载) public bool TryGetTaskNPCInfo(uint id, out NPC_INFO info) { return m_NPCInfoMap.TryGetValue(id, out info); } private TaskTemplContainerSO _taskTemplContainerSO; #if _TASK_CLIENT // char m_szDynPackPath[512]; private string m_szDynPackPath; bool m_bDynTasksVerified; protected special_award m_SpecialAward; #endif public void Release() { } public void Init(elementdataman pMan) { m_pEleDataMan = pMan; } public async UniTask LoadTasksFromPack(string address, bool bLoadDescript, Action onProgress, CancellationToken token) { // If loaded Task Template from last play time, skip loading again if (_wasLoaded) { goto END_PROGRESS; } _wasLoaded = await LoadTaskTemplFromSO(); if (_wasLoaded) { goto END_PROGRESS; } var handle = await AddressableManager.Instance.LoadTextAssetAsync(address); byte[] data = handle.bytes; List loadedTasks; // thread-safe capture Action workerReport = p => { // workerProgress = p; }; try { // background thread loadedTasks = await UniTask.RunOnThreadPool(() => LoadTasksFromPack_Internal(data, onProgress, token), cancellationToken: token); } catch (OperationCanceledException) { // ❗ Đây là cancel bình thường → KHÔNG log error Debug.Log("LoadTasksFromPack canceled"); return false; } catch (Exception e) { Debug.LogException(e); return false; } Debug.Log($" Starting to load {loadedTasks.Count} task templates..."); // int batch = 0; // foreach (var templ in loadedTasks) // { // AddOneTaskTempl(templ); // g_ulNewCount++; // // // avoid frame spike // if (++batch >= 20) // { // batch = 0; // await UniTask.Yield(); // } // } int count = loadedTasks.Count; for (int i = 0; i < count; i++) { AddOneTaskTempl(loadedTasks[i]); g_ulNewCount++; onProgress?.Invoke(0.8f + (i + 1) / (float)count * 0.2f); if (i % 20 == 0) await UniTask.Yield(); } END_PROGRESS: _wasLoaded = true; onProgress?.Invoke(1f); Debug.Log($" Finished loading {m_TaskTemplMap.Count} task templates."); #if !_TASK_CLIENT UpdateTimeLimitCheckList(); #else SortTasksCanSeekOut(); #endif return true; } /* public bool LoadTasksFromPackNoAsyn(string szPackPath, bool bLoadDescript) { //TaskInterface::WriteLog(0, 0, 2, "LoadPack begin"); BMLogger.Log("[Dat]- szPackPath: " + szPackPath); if (!File.Exists(szPackPath)) { BMLogger.LogError("[Dat]- File not found: " + szPackPath); return false; } long readBytes = 0; FileStream fs = new FileStream(szPackPath, FileMode.Open, FileAccess.Read); TASK_PACK_HEADER tph = AAssit.ReadFromBinaryOf(fs, ref readBytes); if (tph.magic != TASK_PACK_MAGIC || tph.version != _task_templ_cur_version) return false; if (tph.item_count == 0) return true; uint[] pOffs = new uint[tph.item_count]; g_ulNewCount++; // fread(pOffs, sizeof(long), tph.item_count, fp); // read File and prepare offset array before loading tasks pOffs = AAssit.ReadArrayFromBinary(fs, (int)tph.item_count, ref readBytes); Debug.Log((int)tph.item_count); //BMLogger.Log($" [MH] Task File Lenght: {fs.Length}"); // for (int i = 2058; i < 2059; i++) //TODO: tph.item_count Debug.Log($" Starting to load {tph.item_count} task templates..."); for (int i = 0; i < tph.item_count; i++) { // mvoe file pointer to task offset fs.Seek(pOffs[i], SeekOrigin.Begin); // BMLogger.Log(" [MH] Loading Task Templ at offset: " + pOffs[i]); ATaskTempl pTempl = new ATaskTempl(); g_ulNewCount++; // Debug.Log($"Task Index {i}: Attempting to load task template..."); if (!pTempl.LoadFromBinFile(fs)) { CECTaskInterface.WriteLog(0, (int)pTempl.m_FixedData.m_ID, 0, "Cant Load Task"); // LOG_DELETE(pTempl); continue; } AddOneTaskTempl(pTempl); // TaskInterface::WriteLog(0, pTempl->m_ID, 2, "LoadTask"); } Debug.Log($" Finished loading {m_TaskTemplMap.Count} task templates."); // // char log[1024]; // // sprintf(log, "LoadTask, Count = %d", m_TaskTemplMap.size()); // // TaskInterface::WriteLog(0, 0, 2, log); // //todo: check // // LOG_DELETE_ARR(pOffs); fs.Close(); #if !_TASK_CLIENT UpdateTimeLimitCheckList(); #else SortTasksCanSeekOut(); #endif #if _ELEMENTCLIENT // TODO: implement task error logging if needed // _task_err.Release(); // _task_err.Init("Configs\\task_err.txt", true); #endif return true; }*/ private static List LoadTasksFromPack_Internal(byte[] data, Action onProgress, CancellationToken token) { long readBytes = 0; var tasks = new List(); using var fs = new MemoryStream(data); TASK_PACK_HEADER tph = AAssit.ReadFromBinaryOf(fs, ref readBytes); if (tph.magic != TASK_PACK_MAGIC || tph.version != _task_templ_cur_version) throw new Exception("Invalid task pack header"); if (tph.item_count == 0) return tasks; uint[] pOffs = AAssit.ReadArrayFromBinary(fs, (int)tph.item_count, ref readBytes); const float TASK_LOAD_WEIGHT = 0.8f; for (int i = 0; i < tph.item_count; i++) { if (token.IsCancellationRequested) return tasks; // percentCount += percent; float progress = ((i + 1) / (float)tph.item_count) * TASK_LOAD_WEIGHT; // LoadingSceneController.Instance.UpdateUI(percentCount); // Debug.LogError($"pc: {percentCount}"); fs.Seek(pOffs[i], SeekOrigin.Begin); ATaskTempl templ = new ATaskTempl(); if (!templ.LoadFromBinFile(fs)) { onProgress?.Invoke(progress); continue; } onProgress?.Invoke(progress); tasks.Add(templ); } return tasks; } public int GetStorageRefreshPerDay(uint storageId) { if (storageId == 0 || storageId > TaskInterfaceConstants.TASK_STORAGE_COUNT) return 0; if (!m_StorageEssenseMap.TryGetValue(storageId, out uint essenceId)) return 0; DATA_TYPE dt = DATA_TYPE.DT_INVALID; var serviceData = m_pEleDataMan.get_data_ptr( essenceId, ID_SPACE.ID_SPACE_ESSENCE, ref dt ); if (serviceData != null && dt == DATA_TYPE.DT_NPC_TASK_OUT_SERVICE) { var outService = (NPC_TASK_OUT_SERVICE)serviceData; return (int)outService.storage_refresh_per_day; } return 0; } // General method to read a struct from a FileStream private T ReadStruct(FileStream stream) where T : struct { int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)); byte[] buffer = new byte[size]; int bytesRead = stream.Read(buffer, 0, size); if (bytesRead != size) throw new EndOfStreamException("Could not read enough bytes for struct"); var handle = System.Runtime.InteropServices.GCHandle.Alloc(buffer, System.Runtime.InteropServices.GCHandleType.Pinned); try { return (T)System.Runtime.InteropServices.Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); } finally { handle.Free(); } } public async UniTask LoadNPCInfoFromPack(string address) { // // TODO: Implement NPC info loading if needed // // FILE* fp = fopen(szPath, "rb"); // FileStream fp = new FileStream(szPath, FileMode.Open, FileAccess.Read); // // if (fp == null) // // { // // TaskInterface::WriteLog(0, 0, 0, "LoadNPCInfoFromPack, no such file"); // // return false; // // } // // // fseek(fp, 0, SEEK_END); // // size_t sz = ftell(fp); // // fseek(fp, 0, SEEK_SET); // long sz = fp.Length; // // if (sz == 0) // { // BMLogger.LogError("[ATaskTemplMan] LoadNPCInfoFromPack, file size is 0"); // fp.Close(); // return false; // } // // long offset = 0; // byte[] buf = new Byte[sz]; // g_ulNewCount++; // // fread(buf, 1, sz, fp); // buf = AAssit.ReadArrayFromBinary( fp, (int)sz, ref offset); // // fclose(fp); // fp.Close(); // // if (!UnmarshalNPCInfo(buf, (int)sz, false)) // { // // LOG_DELETE_ARR(buf); // return false; // } // // #if _TASK_CLIENT // // LOG_DELETE_ARR(buf); // #else // m_pNPCInfoData = buf; // m_ulNPCInfoDataSize = (uint)sz; // #endif // // return true; var textAsset = await AddressableManager.Instance.LoadTextAssetAsync(address); byte[] data = textAsset.bytes; byte[] buf; try { // 1️⃣ background thread buf = await UniTask.RunOnThreadPool( () => LoadNPCInfoFromPack_Internal(data) ); } catch (Exception e) { Debug.LogException(e); return false; } // 2️⃣ main thread (Unity-safe) g_ulNewCount++; if (!UnmarshalNPCInfo(buf, buf.Length, false)) return false; #if _TASK_CLIENT // nothing to store #else m_pNPCInfoData = buf; m_ulNPCInfoDataSize = (uint)buf.Length; #endif return true; } private static byte[] LoadNPCInfoFromPack_Internal(byte[] data) { using var fs = new MemoryStream(data); long sz = fs.Length; if (sz == 0) throw new Exception("NPC info file size is 0"); long offset = 0; return AAssit.ReadArrayFromBinary( fs, (int)sz, ref offset ); } public async UniTask VerifyDynTasksPack(string szPath) { // // TODO: Implement dynamic task pack verification if needed // // strcpy(m_szDynPackPath, szPath); // m_szDynPackPath = szPath; // // // // FILE* fp = fopen(szPath, "rb"); // FileStream fp = new FileStream(szPath, FileMode.Open, FileAccess.Read); // // if (fp == NULL) return; // // // C++ // // fseek(fp, 0, SEEK_END); // // size_t sz = ftell(fp); // // fseek(fp, 0, SEEK_SET); // // // C# // long offset = 0; // long sz = fp.Length; // // int header_sz = Marshal.SizeOf(); // // if (sz < header_sz) // { // // fclose(fp); // fp.Close(); // return; // } // // // C++ // // char* buf = new char[header_sz]; // // g_ulNewCount++; // // fread(buf, 1, header_sz, fp); // // fclose(fp); // // byte[] buf = new byte[header_sz]; // g_ulNewCount++; // buf = AAssit.ReadArrayFromBinary(fp, header_sz, ref offset); // fp.Close(); // // UnmarshalDynTasks(buf, header_sz, true); // // LOG_DELETE_ARR(buf); // store path on main thread m_szDynPackPath = szPath; var textAsset = await AddressableManager.Instance.LoadTextAssetAsync(szPath); byte[] data = textAsset.bytes; byte[] headerBuf; try { // 1️⃣ background thread headerBuf = await UniTask.RunOnThreadPool( () => VerifyDynTasksPack_Internal(data) ); } catch (Exception e) { Debug.LogException(e); return false; } if (headerBuf == null) return false; // 2️⃣ main thread g_ulNewCount++; UnmarshalDynTasks(headerBuf, headerBuf.Length, true); return true; } private static byte[] VerifyDynTasksPack_Internal(byte[] data) { using var fs = new MemoryStream(data); long sz = fs.Length; int headerSize = Marshal.SizeOf(); if (sz < headerSize) return null; long offset = 0; return AAssit.ReadArrayFromBinary( fs, headerSize, ref offset ); } public ATaskTempl GetTopTaskByID(uint ulID) { if (m_TaskTemplMap.TryGetValue(ulID, out ATaskTempl task)) { return task; } return null; } public ATaskTempl GetTaskTemplByID(uint ulID) { if (m_AllTemplMap.TryGetValue((uint)ulID, out ATaskTempl task)) { return task; } return null; } public bool CanGiveUpTask(uint ulTaskId) { var pTempl = GetTaskTemplByID(ulTaskId); if (pTempl == null) return false; pTempl = pTempl.GetTopTask(); return pTempl.m_FixedData.m_bCanGiveUp; } private void AddOneTaskTempl(ATaskTempl pTask) { if (m_TaskTemplMap.ContainsKey(pTask.m_FixedData.m_ID)) { CECTaskInterface.WriteLog(0, (int)pTask.m_FixedData.m_ID, 0, "Dup Task Found"); // Optionally log duplicate task found, e.g.: BMLogger.LogError($"Duplicate Task Found: {pTask.m_ID}"); return; } m_TaskTemplMap[pTask.m_ID] = pTask; if (pTask.m_FixedData.m_bDeathTrig) m_DeathTrigMap[pTask.m_FixedData.m_ID] = pTask; else if (pTask.m_FixedData.m_bAutoDeliver) m_AutoDelvMap[pTask.m_FixedData.m_ID] = pTask; if (pTask.m_FixedData.m_bPQTask) m_PQTemplMap[pTask.m_FixedData.m_ID] = pTask; if (pTask.m_FixedData.m_bSkillTask) m_SkillTaskLst.Add(pTask); //todo: recheck m_DynTaskType type if (pTask.m_FixedData.m_DynTaskType != '\0') { if (m_DynTaskMap.TryGetValue(pTask.m_FixedData.m_ID, out ATaskTempl task)) { CECTaskInterface.WriteLog(0, (int)pTask.m_FixedData.m_ID, 0, "Dup Dyn Task Found"); BMLogger.LogError($"Duplicate Dyn Task Found: {pTask.m_ID}"); } m_DynTaskMap[pTask.m_FixedData.m_ID] = pTask; } if (pTask.m_FixedData.m_bDisplayInTitleTaskUI) m_TitleTaskMap[pTask.m_FixedData.m_ID] = pTask; if (pTask.m_FixedData.m_bAutoDeliver && pTask.m_FixedData.m_bDisplayInExclusiveUI) m_ExlusiveAwardTaskMap[pTask.m_FixedData.m_ID] = pTask; #if _TASK_CLIENT if (pTask.m_FixedData.m_ulDelvNPC != 0 && pTask.m_FixedData.m_bCanSeekOut) m_TasksCanSeekOut.Add(pTask); #endif AddTaskToMap(pTask); } private void AddTaskToMap(ATaskTempl pTempl) { // fill unserialize data when play game if (_taskTemplContainerSO != null && _taskTemplContainerSO.LoadAllTasksFromSO) { pTempl.FillUnserializableDataWhenPlayGame(_taskTemplContainerSO); } if (pTempl.m_FixedData.m_enumMethod == (ulong)TaskCompletionMethod.enumTMProtectNPC && pTempl.m_FixedData.m_ulNPCToProtect > 0) m_ProtectNPCMap[pTempl.m_FixedData.m_ulNPCToProtect] = pTempl; m_AllTemplMap[pTempl.m_FixedData.m_ID] = pTempl; ATaskTempl pChild = pTempl.m_pFirstChild; while (pChild != null) { AddTaskToMap(pChild); pChild = pChild.m_pNextSibling; } } #if !_TASK_CLIENT void UpdateTimeLimitCheckList() { m_TmLmtChkLst.Clear(); foreach (var entry in m_TaskTemplMap) { if (entry.Value.m_FixedData.m_ulMaxReceiver != 0) { m_TmLmtChkLst.Add(entry.Value); } } } #endif public bool InitStorageTask() { m_StorageEssenseMap.Clear(); m_WeightEssenseMap.Clear(); DATA_TYPE dt; //ID_SPACE.ID_SPACE_ESSENCE foreach (var pair in m_pEleDataMan.essence_id_data_type_map) { if (pair.Value == DATA_TYPE.DT_NPC_TASK_OUT_SERVICE) { dt = pair.Value; NPC_TASK_OUT_SERVICE pData = (NPC_TASK_OUT_SERVICE)m_pEleDataMan.get_data_ptr(pair.Key, ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (pData.storage_id == 0) continue; if (pData.storage_id > TaskTemplConstants.TASK_STORAGE_COUNT) return false; if (m_StorageEssenseMap.ContainsKey(pData.storage_id)) return false; m_StorageEssenseMap[pData.storage_id] = pData.id; for (var i = 0; i < pData.id_tasks.Length; i++) { if (pData.id_tasks[i] > 0) { m_StorageTaskMap[(int)pData.id_tasks[i]] = (int)pData.storage_id; } } } } // ID_SPACE_CONFIG foreach (var pair in m_pEleDataMan.config_id_data_type_map) { if (pair.Value == DATA_TYPE.DT_NPC_TASK_OUT_SERVICE) { dt = pair.Value; NPC_TASK_OUT_SERVICE pData = (NPC_TASK_OUT_SERVICE)m_pEleDataMan.get_data_ptr(pair.Key, ID_SPACE.ID_SPACE_ESSENCE, ref dt); if (pData.storage_id == 0) continue; if (pData.storage_id > TaskTemplConstants.TASK_STORAGE_COUNT) return false; if (m_StorageEssenseMap.ContainsKey(pData.storage_id)) return false; m_StorageEssenseMap[pData.storage_id] = pData.id; for (var i = 0; i < pData.id_tasks.Length; i++) { if (pData.id_tasks[i] > 0) { m_StorageTaskMap[(int)pData.id_tasks[i]] = (int)pData.storage_id; } } } } return true; } #if _TASK_CLIENT public bool IsTaskToPush(int id) { // TODO: Implement this method properly // int count = m_TasksToPush.size(); // for (size_t i = 0; i < count; ++i) { // ATaskTempl pTempl = m_TasksToPush[i].task; // if (pTempl && (int)pTempl->m_ID == id) return true; // } return false; } // 可接任务列表(全量一次扫完;分帧接口见 ProcessAvailableTasksChunk) // Available tasks list (full scan in one call; time-sliced API: ProcessAvailableTasksChunk) public void GetAvailableTasks(TaskInterface pPlayer, List lst) { if (lst == null) return; if (lst.Capacity < 256) lst.Capacity = 256; // 预留容量 // reserve capacity lst.Clear(); int cursor = 0; bool completed; do { ProcessAvailableTasksChunk(pPlayer, lst, ref cursor, int.MaxValue, out completed); } while (!completed); } /// /// Process up to maxSteps templates from m_TasksCanSeekOut; cursor is next index (0 = start of list). /// 每次最多处理 maxSteps 个 m_TasksCanSeekOut 项;cursor 为下一待处理下标。 /// public void ProcessAvailableTasksChunk(TaskInterface pPlayer, List lst, ref int cursor, int maxSteps, out bool completed) { int count = m_TasksCanSeekOut.Count; int processed = 0; while (cursor < count && processed < maxSteps) { ATaskTempl pTempl = m_TasksCanSeekOut[cursor]; cursor++; processed++; if (pTempl == null) continue; if (!pTempl.CheckReachLevel(pPlayer)) continue; if (pPlayer.CanDeliverTask(pTempl.m_FixedData.m_ID) == 0) lst.Add(pTempl); } completed = cursor >= count; } public uint GetTaskStorageId(uint id) { // id��1��ʼ // abase::hash_map::iterator it = m_StorageTaskMap.find(id); // return it == m_StorageTaskMap.end() ? 0 : it->second; return m_StorageTaskMap.ContainsKey((int)id) ? (uint)m_StorageTaskMap[(int)id] : 0; } public void RemoveActiveStorageTask(StorageTaskList pLst, uint id) { // unsigned int set_id = GetTaskTemplMan()->GetTaskStorageId(id); uint set_id = GetTaskStorageId(id); if (set_id > 0) { int start = (int)set_id - 1; ushort[] arr = pLst.m_Storages; for (int i = start; i < TaskTemplConstants.TASK_STORAGE_LEN; i++) { if (arr[i] == (ushort)id) { arr[i] = 0; break; } } } } // 占位:可接返回0,不可接返回非0 // Placeholder: return 0 if deliverable, non-zero otherwise // private int CanDeliverTask(TaskInterface pPlayer, uint templId) // { // // 后续可替换为正式逻辑 // Replace with real logic later // var impl = pPlayer; // return (impl != null && impl.IsDeliverLegal()) ? 0 : 1; // } public void OnSpecialAward(special_award p, TaskInterface pTask) { m_SpecialAward = p; CheckSpecialAwardMask(pTask); } public void CheckSpecialAwardMask(TaskInterface pTask) { ActiveTaskList pLst = pTask.GetActiveTaskList(); uint ulCurTime = pTask.GetCurTime(); for (int i = 0; i < TaskTemplConstants.NUM_SPECIAL_AWARD; i++) { if ((m_SpecialAward.special_mask & (1 << i)) != 0) { // const ATaskTempl* pTempl = GetTaskTemplByID(static_cast(TASK_SPECIAL_AWARD[i])); C++ var pTempl = GetTaskTemplByID((uint)TaskTemplConstants.TASK_SPECIAL_AWARD[i]); if (pTempl != null && pTempl.CheckPrerequisite(pTask, pLst, ulCurTime) == 0) { // _notify_svr(pTask, TASK_CLT_NOTIFY_SPECIAL_AWARD_MASK,static_cast(pTempl->m_ID)); C++ _notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_SPECIAL_AWARD_MASK, (ushort)(pTempl.m_FixedData.m_ID)); } } } } private void _notify_svr(TaskInterface pTask, byte uReason, ushort uTaskID) { ATaskTempl._notify_svr(pTask, uReason, uTaskID); } static bool compare_tasks_canseekout(ATaskTempl lhs, ATaskTempl rhs) { if (lhs.m_FixedData.m_ulPremItems != 0 && rhs.m_FixedData.m_ulPremItems == 0) return true; else if (lhs.m_FixedData.m_ulPremItems == 0 && rhs.m_FixedData.m_ulPremItems != 0) return false; else if (lhs.m_FixedData.m_ulPremItems != 0 && rhs.m_FixedData.m_ulPremItems != 0) return lhs.m_FixedData.m_ID > rhs.m_FixedData.m_ID; else return lhs.m_FixedData.m_ulPremise_Lev_Min > rhs.m_FixedData.m_ulPremise_Lev_Min; } /// /// Comparator tương đương C++ version /// private static int CompareTasksCanSeekOut(ATaskTempl lhs, ATaskTempl rhs) { // Rule 1: Ưu tiên task có PremItems bool lhsHasPrem = lhs.m_FixedData.m_ulPremItems != 0; bool rhsHasPrem = rhs.m_FixedData.m_ulPremItems != 0; if (lhsHasPrem && !rhsHasPrem) return -1; // lhs lên trước if (!lhsHasPrem && rhsHasPrem) return 1; // rhs lên trước // Rule 2: Nếu cả hai có PremItems → sắp theo ID giảm dần if (lhsHasPrem && rhsHasPrem) return rhs.m_FixedData.m_ID.CompareTo(lhs.m_FixedData.m_ID); // ID lớn đứng trước // Rule 3: Nếu không có PremItems → LevelMin giảm dần return rhs.m_FixedData.m_ulPremise_Lev_Min.CompareTo(lhs.m_FixedData.m_ulPremise_Lev_Min); } void SortTasksCanSeekOut() { // std::sort(m_TasksCanSeekOut.begin(), m_TasksCanSeekOut.end(), compare_tasks_canseekout); m_TasksCanSeekOut.Sort(CompareTasksCanSeekOut); } public void CheckAutoDelv(TaskInterface pTask) { ATaskTempl pTempl = null; uint ulCurTime = pTask.GetCurTime(); ActiveTaskList pLst = pTask.GetActiveTaskList(); #if _ELEMENTLOCALIZE #if _ELEMENTCLIENT // if (CECCommandLine::GetBriefConfig(_AL("noautodelivertask"))) return; #endif #endif // C++ // for (; it != m_AutoDelvMap.end(); ++it) // { // pTempl = it->second; // // if (!pTempl->IsValidState()) // continue; // // if (pTempl->CheckPrerequisite(pTask, pLst, ulCurTime) == 0) // { // pTempl->IncValidCount(); // _notify_svr(pTask, TASK_CLT_NOTIFY_AUTO_DELV, static_cast(pTempl->m_ID)); // } // } foreach (var key in m_AutoDelvMap.Keys) { pTempl = m_AutoDelvMap[key]; if (!pTempl.IsValidState()) continue; if (pTempl.CheckPrerequisite(pTask, pLst, ulCurTime) == 0) { pTempl.IncValidCount(); _notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_AUTO_DELV, (ushort)(pTempl.m_FixedData.m_ID)); } } } public void UpdateStatus(TaskInterface pTask) { // ��ΪCheckTitle������ƺ����ݣ�����ֱ����ȡ���ƺ�����֮ǰ�����ܵ��������� //TODO:Add -----bool CECHostPlayer::ProcessMessage(const ECMSG& Msg)---- feature to check if title data is ready. //If enable now it will not create autotask //if (!pTask.IsTitleDataReady()) return; CheckAutoDelv(pTask); // TODO: Implement other checks as needed // if (CECUIConfig::Instance().GetGameUI().bEnableTitle) // CheckTitleTask(pTask); // UpdateTasksSeekOutDiff(pTask); } // extarn from TaskServer #endif #if !_TASK_CLIENT private void OnTaskGiveUpOneTask(TaskInterface pTask, uint ulTaskId, bool bForce) { TaskServer.OnTaskGiveUpOneTask(pTask, ulTaskId, bForce); } #endif public void OnForgetLivingSkill(TaskInterface pTask) { // FinishedTaskList* pList = static_cast(pTask->GetFinishedTaskList()); // C++ FinishedTaskList pList = pTask.GetFinishedTaskList(); for (int i = 0; i < m_SkillTaskLst.Count; i++) { pList.RemoveTask(m_SkillTaskLst[i].GetID()); #if !_TASK_CLIENT OnTaskGiveUpOneTask(pTask, m_SkillTaskLst[i].GetID(), false); #endif } #if !_TASK_CLIENT task_notify_base notify = new task_notify_base(); notify.reason = TaskTemplConstants.TASK_SVR_NOTIFY_FORGET_SKILL; notify.task = 0; pTask.NotifyClient(notify, Marshal.SizeOf()); #endif } // process part #if _TASK_CLIENT // void GetTitleTasks(TaskInterface pTask, TaskTemplLst lst); // void GetAvailableTasks(TaskInterface* pPlayer, TaskTemplLst& lst); // void ManualTrigTask(TaskInterface* pTask, unsigned long ulTask); // void ForceGiveUpTask(TaskInterface* pTask, unsigned long ulTask); // void ForceRemoveFinishTask(TaskInterface* pTask, unsigned long ulTask); public bool IsDynTasksVerified() { return m_bDynTasksVerified; } void SetDynTasksVerified(bool b) { m_bDynTasksVerified = b; } public void OnDynTasksTimeMark(TaskInterface pTask, uint ulTimeMark, ushort version) { if (version != DYN_TASK_CUR_VERSION) return; if (m_ulDynTasksTimeMark == ulTimeMark && LoadDynTasksFromPack(m_szDynPackPath)) { SetDynTasksVerified(true); pTask.InitActiveTaskList(); UpdateDynDataNPCService(); } else _notify_svr(pTask, ClientNotificationConstants.TASK_CLT_NOTIFY_DYN_DATA, 0); } public void OnDynTasksData(TaskInterface pTask, byte[] data, int sz, bool ended) { if (m_bDynTasksVerified) { // assert(false); return; } uint new_sz = (uint)(sz + m_ulDynTasksDataSize); var buf = new byte[new_sz]; g_ulNewCount++; if (m_pDynTasksData != null && m_ulDynTasksDataSize > 0) { // memcpy(buf, m_pDynTasksData, m_ulDynTasksDataSize); Buffer.BlockCopy( m_pDynTasksData, // src 0, // src offset buf, // dst 0, // dst offset (int)m_ulDynTasksDataSize // bytes ); } // memcpy(buf + m_ulDynTasksDataSize, data, sz); Buffer.BlockCopy( data, // src 0, // src offset buf, // dst (int)m_ulDynTasksDataSize, // dst offset sz // bytes ); // LOG_DELETE_ARR(m_pDynTasksData); m_pDynTasksData = buf; m_ulDynTasksDataSize = new_sz; if (ended) { // a_LogOutput(1, "[Dat Task] OnDynTasksData"); if (UnmarshalDynTasks(m_pDynTasksData, (int)m_ulDynTasksDataSize, false)) { if (m_pDynTasksData != null && m_pDynTasksData.Length > 0) { try { using (FileStream fs = new FileStream(m_szDynPackPath, FileMode.Create, FileAccess.Write)) { fs.Write(m_pDynTasksData, 0, (int)m_ulDynTasksDataSize); } } catch (Exception ex) { BMLogger.LogError($"[ATaskTemplMan] Failed to write dyn tasks pack: {ex.Message}"); } } SetDynTasksVerified(true); pTask.InitActiveTaskList(); UpdateDynDataNPCService(); } // LOG_DELETE_ARR(m_pDynTasksData); m_pDynTasksData = null; m_ulDynTasksDataSize = 0; } } // 处理存储任务数据 // Handle storage task data public void OnStorageData(TaskInterface pTask, byte[] data) { // Copy data directly to the storage buffer (equivalent to C++ memcpy operations) // 直接将数据复制到存储缓冲区(等同于C++的memcpy操作) if (pTask is CECTaskInterface cecTask) { cecTask.SetStorageTaskListBuffer(data); } else { // Fallback: read into struct (but won't persist without writing back) // 后备方案:读取到结构体(但不写回则不会持久化) StorageTaskList pLst = pTask.GetStorageTaskList(); pLst.ReadByte(data); } } // void OnSpecialAward(const special_award* p,TaskInterface* pTask); // void VerifyDynTasksPack(const char* szPath); // const special_award* GetSpecialAward() const { return &m_SpecialAward; } public void ClearSpecailAward() { // memset(&m_SpecialAward, 0, sizeof(m_SpecialAward)); m_SpecialAward = new special_award(); } // void SortTasksCanSeekOut(); // void UpdateTasksSeekOutDiff(TaskInterface* pTask); // bool IsTaskToPush(int id); // void ClearTasksToPush() { m_TasksToPush.clear(); } // bool HasTaskToPush() { return !m_TasksToPush.empty(); } void UpdateDynDataNPCService() { // assert(m_pEleDataMan); DATA_TYPE dt = default; NPC_TASK_IN_SERVICE service = (NPC_TASK_IN_SERVICE)m_pEleDataMan.get_data_ptr( DYN_TASK_VERIFY_SVR, ID_SPACE.ID_SPACE_ESSENCE, ref dt ); if (dt != DATA_TYPE.DT_NPC_TASK_IN_SERVICE) { BMLogger.LogError($"UpdateDynDataNPCService, wrong service, dt = {dt}"); return; } NPC_TASK_OUT_SERVICE deliver = (NPC_TASK_OUT_SERVICE)m_pEleDataMan.get_data_ptr( DYN_TASK_DELIVER_SVR, ID_SPACE.ID_SPACE_ESSENCE, ref dt ); if (dt != DATA_TYPE.DT_NPC_TASK_OUT_SERVICE) { BMLogger.LogError($"UpdateDynDataNPCService, wrong service, dt = {dt}"); return; } deliver.id_tasks ??= new uint[256]; service.id_tasks ??= new uint[256]; deliver.id_tasks.AsSpan().Clear(); service.id_tasks.AsSpan().Clear(); foreach (var kv in m_DynTaskMap) { ATaskTempl p = kv.Value; if (p != null && p.m_FixedData.m_DynTaskType != (byte)DynTaskType.enumDTTGiftCard) { mount_task_out_service(p, ref deliver); mount_task_in_service(p, ref service); } } } #else void CheckDeathTrig(TaskInterface* pTask); void OnTaskCheckAllTimeLimits(unsigned long ulCurTime); void OnTaskGetDynTasksTimeMark(TaskInterface* pTask); void OnTaskGetDynTasksData(TaskInterface* pTask); void OnTaskGetSpecialAward(TaskInterface* pTask); void OnTaskRemoveFinishTask(TaskInterface* pTask, unsigned long ulTask); void OnTaskUpdateStorage(TaskInterface* pTask, unsigned long ulCurTime); bool UpdateStorage(TaskInterface* pTask, StorageTaskList* pLst, unsigned long ulCurTime, unsigned long idStorage); bool UpdateOneStorageDebug(TaskInterface* pTask, unsigned long ulCurTime, int idStorage, bool bUseDayAsSeed); #endif bool UnmarshalNPCInfo(byte[] data, int data_size, bool header_only) { if (data_size < Marshal.SizeOf()) { // TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong size"); BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong size: {data_size} < {Marshal.SizeOf()}"); return false; } // const char* p = data; // TASK_NPC_PACK_HEADER* header = (TASK_NPC_PACK_HEADER*)p; // p += sizeof(TASK_NPC_PACK_HEADER); TASK_NPC_PACK_HEADER header = GPDataTypeHelper.FromBytes(data); long p = Marshal.SizeOf(); if (header.version != TASK_NPC_INFO_VERSION) { // TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong version"); BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong version: {header.version}"); return false; } if (header.pack_size != data_size) { // TaskInterface::WriteLog(0, 0, 0, "UnmarshalNPCInfo, wrong header"); BMLogger.LogError($" [ATaskTemplMan] UnmarshalNPCInfo, wrong header: pack_size {header.pack_size} != data_size {data_size}"); return false; } m_ulNPCInfoTimeMark = (uint)header.time_mark; if (header_only) return true; // const NPC_INFO* pInfos = (const NPC_INFO*)p; for (int i = 0; i < header.npc_count; i++) { // const NPC_INFO& info = pInfos[i]; // m_NPCInfoMap[info.id] = info; NPC_INFO info = GPDataTypeHelper.FromBytes(data, ref p); m_NPCInfoMap[info.id] = info; } return true; } bool UnmarshalDynTasks(byte[] data, int data_size, bool header_only) { if (data_size < Marshal.SizeOf()) { // TaskInterface::WriteLog(0, 0, 0, "UnmarshalDynTasks, wrong size"); BMLogger.LogError(" [ATaskTemplMan] UnmarshalDynTasks, wrong size"); return false; } // C++ // const char* p = data; // DYN_TASK_PACK_HEADER* header = (DYN_TASK_PACK_HEADER*)p; // p += sizeof(DYN_TASK_PACK_HEADER); DYN_TASK_PACK_HEADER header = GPDataTypeHelper.FromBytes(data); long p = Marshal.SizeOf(); if (header.version != DYN_TASK_CUR_VERSION) { // TaskInterface::WriteLog(0, 0, 0, "UnmarshalDynTasks, wrong version"); BMLogger.LogError($" [ATaskTemplMan] UnmarshalDynTasks, wrong version: {header.version}"); return false; } // Only check pack_size when reading full data, not when header_only is true // When header_only is true, we only read the header, so pack_size will be larger than data_size if (!header_only && header.pack_size != data_size) { // TaskInterface::WriteLog(0, 0, 0, "UnmarshalDynTasks, wrong header"); BMLogger.LogError($" [ATaskTemplMan] UnmarshalDynTasks, wrong header: pack_size {header.pack_size} != data_size {data_size}"); return false; } m_ulDynTasksTimeMark = (uint)header.time_mark; if (header_only) return true; // BMLogger.Log($" [ATaskTemplMan] UnmarshalDynTasks, loading {header.task_count} tasks, data_size: {data_size}"); for (int i = 0; i < header.task_count; i++) { ATaskTempl pTempl = new ATaskTempl(); g_ulNewCount++; pTempl.UnmarshalDynTask(data, ref p); // BMLogger.Log($" [MH Task] after load UnmarshalDynTask[{i}] pointer at : {p - Marshal.SizeOf()}"); AddOneTaskTempl(pTempl); // TaskInterface::WriteLog(0, pTempl->GetID(), 2, "LoadDynTask"); } #if _TASK_CLIENT SortTasksCanSeekOut(); #endif // assert(p == data + data_size); return true; } void mount_task_out_service(ATaskTempl task, ref NPC_TASK_OUT_SERVICE svr) { if (task.IsAutoDeliver()) return; if (task.m_pParent != null && !task.m_pParent.m_FixedData.m_bChooseOne) return; uint[] tasks = svr.id_tasks; for (int i = 0; i < tasks.Length; i++) { if (tasks[i] != 0) continue; tasks[i] = task.GetID(); break; } ATaskTempl child = task.m_pFirstChild; while (child != null) { mount_task_out_service(child, ref svr); child = child.m_pNextSibling; } } void mount_task_in_service(ATaskTempl task, ref NPC_TASK_IN_SERVICE svr) { if ((TaskFinishType)task.m_FixedData.m_enumFinishType == TaskFinishType.enumTFTNPC) { uint[] tasks = svr.id_tasks; for (int i = 0; i < tasks.Length; i++) { if (tasks[i] != 0) continue; tasks[i] = task.GetID(); break; } } ATaskTempl child = task.m_pFirstChild; while (child != null) { mount_task_in_service(child, ref svr); child = child.m_pNextSibling; } } bool LoadDynTasksFromPack(string szPath) { // TaskInterface::WriteLog(0, 0, 2, "LoadDynPack begin"); // FILE* fp = fopen(szPath, "rb"); FileStream fp = new FileStream(szPath, FileMode.Open, FileAccess.Read); if (fp == null) { // TaskInterface::WriteLog(0, 0, 0, "LoadDynTasksFromPack, no such file"); BMLogger.LogError("[ATaskTemplMan] LoadDynTasksFromPack, no such file"); return false; } // fseek(fp, 0, SEEK_END); // size_t sz = ftell(fp); // fseek(fp, 0, SEEK_SET); long sz = fp.Length; if (sz == 0) { fp.Close(); return false; } // char* buf = new char[sz]; byte[] buf = new byte[sz]; g_ulNewCount++; // fread(buf, 1, sz, fp); long offset = 0; buf = AAssit.ReadArrayFromBinary(fp, (int)sz, ref offset); fp.Close(); if (!UnmarshalDynTasks(buf, (int)sz, false)) { // LOG_DELETE_ARR(buf); return false; } #if _TASK_CLIENT // LOG_DELETE_ARR(buf); #else m_pDynTasksData = buf; m_ulDynTasksDataSize = sz; UpdateDynDataNPCService(); #endif return true; } public async UniTask LoadTaskTemplFromSO() { const string addressKey = "Assets/PerfectWorld/SO/TaskTemplContainerSO.asset"; // set this to the real address AsyncOperationHandle handle = default; try { handle = Addressables.LoadAssetAsync(addressKey); await handle.Task; if (handle.Status != AsyncOperationStatus.Succeeded) { BMLogger.LogError($"[ATaskTemplMan] LoadTaskTemplFromSO failed: {handle.OperationException}"); return false; } var config = handle.Result; if (config == null) { BMLogger.LogError("[ATaskTemplMan] LoadTaskTemplFromSO returned null result"); return false; } if (!config.LoadAllTasksFromSO) return false; _taskTemplContainerSO = config; config.BuildTaskTemplateMap(); var loadedTasks = config.TopTaskTemplates; if (loadedTasks == null || loadedTasks.Count == 0) { BMLogger.LogError("[ATaskTemplMan] LoadTaskTemplFromSO, no task templates found in SO"); return false; } for (int i = 0; i < loadedTasks.Count; i++) { AddOneTaskTempl(loadedTasks[i]); g_ulNewCount++; } Debug.Log($"Finished loading {m_TaskTemplMap.Count} task templates."); return true; } catch (Exception e) { BMLogger.LogError($"[ATaskTemplMan] LoadTaskTemplFromSO exception: {e}"); return false; } finally { if (handle.IsValid()) Addressables.Release(handle); } } public TASK_DICE_BY_WEIGHT_CONFIG GetWeightTasksEssence(uint idStorage, out bool result) { result = false; if (!m_WeightEssenseMap.TryGetValue(idStorage, out uint essenceId)) return default; DATA_TYPE dt = DATA_TYPE.DT_INVALID; var temp = m_pEleDataMan.get_data_ptr((uint)essenceId, ID_SPACE.ID_SPACE_CONFIG, ref dt); if (temp == null && dt != DATA_TYPE.DT_TASK_DICE_BY_WEIGHT_CONFIG) return default; result = true; return (TASK_DICE_BY_WEIGHT_CONFIG)temp; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TASK_NPC_PACK_HEADER { public uint pack_size; public int time_mark; public short version; public short npc_count; }; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct DYN_TASK_PACK_HEADER { public uint pack_size; public int time_mark; public ushort version; public ushort task_count; }; }