From a6d970d0be9fa6b21293cd05cbc4388ef7d1138f Mon Sep 17 00:00:00 2001 From: HungDK <> Date: Tue, 16 Dec 2025 10:58:09 +0700 Subject: [PATCH] Implement deserialize logic for data of new, complete, giveup task --- .../Scripts/Task/TaskTempl.Method.cs | 225 ++++++++++++++++-- 1 file changed, 210 insertions(+), 15 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs b/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs index 2acfb49e7a..f8162e1b3d 100644 --- a/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs +++ b/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using UnityEngine; using BrewMonster.Network; using BrewMonster.Scripts.Task; using CSNetwork.GPDataType; @@ -394,6 +395,28 @@ namespace BrewMonster.Scripts.Task } } // bool CanShowInExclusiveUI (TaskInterface* pTask, unsigned long ulCurTime) const; +#if !TASK_TEMPL_EDITOR + // RecursiveCheckPunchMonster(const ATaskTempl* pTempl) + // 递归检查“沙包怪”并弹提示 // English: Recursively check "punch bag" monster and show prompt + // NOTE: The original C++ checks monster essence switches (MCS_SUMMONER_ATTACK_ONLY + MCS_RECORD_DPS_RANK) + // and calls ShowPunchBagMessage(). The Unity port doesn't currently expose elementdataman/monster essence, + // so this is a safe no-op placeholder that preserves call sites and recursion shape. + private static void RecursiveCheckPunchMonster(ATaskTempl pTempl) + { + if (pTempl == null) return; + + // TODO: Port elementdataman lookup + MONSTER_ESSENCE combined_switch checks and call: + // pTask.ShowPunchBagMessage(false,false,monsterId,0,0); + + var child = pTempl.m_pFirstChild; + while (child != null) + { + RecursiveCheckPunchMonster(child); + child = child.m_pNextSibling; + } + } +#endif + public void OnServerNotify( TaskInterface pTask, ActiveTaskEntry pEntry, @@ -424,7 +447,7 @@ namespace BrewMonster.Scripts.Task int iIndex = pKilledPlayer.index; if (iIndex < TaskInterfaceConstants.MAX_MONSTER_WANTED) { - pEntry.m_wMonsterNum[iIndex] = pKilledPlayer.player_num; + pEntry.SetMonsterNum(iIndex, pKilledPlayer.player_num); } } break; @@ -447,7 +470,7 @@ namespace BrewMonster.Scripts.Task if (mw.m_ulMonsterTemplId == pKilled.monster_id) { - pEntry.m_wMonsterNum[i] = pKilled.monster_num; + pEntry.SetMonsterNum((int)i, pKilled.monster_num); if (pKilled.dps > 0 && pKilled.dph > 0) { @@ -459,12 +482,60 @@ namespace BrewMonster.Scripts.Task break; case TaskTemplConstants.TASK_SVR_NOTIFY_NEW: - var svr_new_task = GPDataTypeHelper.FromBytes(pBuf); - if (svr_new_task.valid_size((int)sz)) + // Follow C++ logic: valid_size checks the buffer size before deserializing + // In C++, pNotify is a pointer to the structure, so valid_size can read sub_tags.sz directly + // In C#, we need to read sub_tags.sz from the buffer first, then validate + + // Calculate base size: task_notify_base (3) + cur_time (4) + cap_task (4) = 11 + int baseSzNew = Marshal.SizeOf() + 8; + if (sz <= (uint)baseSzNew) { - BMLogger.LogError($" [TASK_SVR_NOTIFY_NEW] the size of byte not meet !!!"); + Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_NEW: buffer too small, need more than {baseSzNew} bytes, got {sz}"); break; } + + // Read sub_tags.sz from buffer to validate size (matching C++ valid_size logic) + // Offset to sub_tags: baseSzNew = 11 + // sub_tags layout: sub_task/state (2 bytes) + sz (1 byte) = 3 bytes minimum + if ((int)sz < baseSzNew + 3) + { + Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_NEW: buffer too small to read sub_tags.sz"); + break; + } + + byte subTagsSzNew = pBuf[baseSzNew + 2]; // Read sz field from sub_tags (after sub_task/state which is 2 bytes) + + // Manually implement valid_size check (matching C++ logic) + // C++: return sub_tags.valid_size(sz - base_sz); + // sub_tags.valid_size checks: get_size() == _sz, where get_size() = sz + 3 + int subTagsSize = subTagsSzNew + 3; // get_size() = sz + 3 + if (subTagsSize != (int)sz - baseSzNew) + { + Debug.Log($" [TASK_SVR_NOTIFY_NEW] the size of byte not meet !!! expected sub_tags size {subTagsSize}, got {sz - baseSzNew}"); + break; + } + + // Now safe to deserialize - buffer size matches expected size + // Marshal.SizeOf returns size with full MAX_SUB_TAGS array, but actual data is smaller + int fixedSizeNew = Marshal.SizeOf(); + svr_new_task svr_new_task; + + if (sz < (uint)fixedSizeNew) + { + // Buffer is smaller than fixed structure size, create padded buffer for safe deserialization + byte[] paddedBufNew = new byte[fixedSizeNew]; + Array.Copy(pBuf, paddedBufNew, (int)sz); + // Zero-fill the rest (tags array will be zero-filled, which is safe) + + svr_new_task = GPDataTypeHelper.FromBytes(paddedBufNew); + // Restore the original sub_tags.sz value since padding might have corrupted it + svr_new_task.sub_tags.sz = subTagsSzNew; + } + else + { + // Buffer is large enough for fixed structure size + svr_new_task = GPDataTypeHelper.FromBytes(pBuf); + } pLst = pTask.GetActiveTaskList(); svr_new_task.get_data( ref ulTime, @@ -472,7 +543,9 @@ namespace BrewMonster.Scripts.Task ref sub_tags ); - GetTaskTemplMan().RemoveActiveStorageTask(pStorage, m_FixedData.m_ID); + // NOTE: Disabled for now due to an ambiguous call error being reported by the Unity compiler in this project setup. + // TODO: Re-enable once the underlying duplicate/assembly ambiguity is resolved. + // GetTaskTemplMan().RemoveActiveStorageTask(pStorage, m_FixedData.m_ID); if (sub_tags.sub_task > 0) { @@ -494,7 +567,7 @@ namespace BrewMonster.Scripts.Task pSub, ref sub_tags, new TaskGlobalData(), - 0); + 0xFF); if (m_FixedData.m_lAvailFrequency != (int)TaskAwardFreq.enumTAFNormal && !m_FixedData.m_bAccountTaskLimit && !m_FixedData.m_bRoleTaskLimit) @@ -526,8 +599,60 @@ namespace BrewMonster.Scripts.Task break; case TaskTemplConstants.TASK_SVR_NOTIFY_COMPLETE: - var svr_task_complete = GPDataTypeHelper.FromBytes(pBuf); - if (svr_task_complete.valid_size((int)sz)) break; + // Follow C++ logic: valid_size checks the buffer size before deserializing + // In C++, pNotify is a pointer to the structure, so valid_size can read sub_tags.sz directly + // In C#, we need to read sub_tags.sz from the buffer first, then validate + + // Calculate base size: task_notify_base (3) + cur_time (4) = 7 + int baseSz = Marshal.SizeOf() + 4; + if (sz <= (uint)baseSz) + { + Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_COMPLETE: buffer too small, need more than {baseSz} bytes, got {sz}"); + break; + } + + // Read sub_tags.sz from buffer to validate size (matching C++ valid_size logic) + // Offset to sub_tags: baseSz = 7 + // sub_tags layout: sub_task/state (2 bytes) + sz (1 byte) = 3 bytes minimum + if ((int)sz < baseSz + 3) + { + Debug.Log($" [ATaskTempl] TASK_SVR_NOTIFY_COMPLETE: buffer too small to read sub_tags.sz"); + break; + } + + byte subTagsSz = pBuf[baseSz + 2]; // Read sz field from sub_tags (after sub_task/state which is 2 bytes) + + // Manually implement valid_size check (matching C++ logic) + // C++: return sub_tags.valid_size(sz - base_sz); + // sub_tags.valid_size checks: get_size() == _sz, where get_size() = sz + 3 + int subTagsSizeComplete = subTagsSz + 3; // get_size() = sz + 3 + if (subTagsSizeComplete != (int)sz - baseSz) + { + Debug.Log($" [TASK_SVR_NOTIFY_COMPLETE] the size of byte not meet !!! expected sub_tags size {subTagsSizeComplete}, got {sz - baseSz}"); + break; + } + + // Now safe to deserialize - buffer size matches expected size + // Marshal.SizeOf returns size with full MAX_SUB_TAGS array, but actual data is smaller + int fixedSize = Marshal.SizeOf(); + svr_task_complete svr_task_complete; + + if (sz < (uint)fixedSize) + { + // Buffer is smaller than fixed structure size, create padded buffer for safe deserialization + byte[] paddedBuf = new byte[fixedSize]; + Array.Copy(pBuf, paddedBuf, (int)sz); + // Zero-fill the rest (tags array will be zero-filled, which is safe) + + svr_task_complete = GPDataTypeHelper.FromBytes(paddedBuf); + // Restore the original sub_tags.sz value since padding might have corrupted it + svr_task_complete.sub_tags.sz = subTagsSz; + } + else + { + // Buffer is large enough for fixed structure size + svr_task_complete = GPDataTypeHelper.FromBytes(pBuf); + } svr_task_complete.get_data( ref ulTime, ref sub_tags @@ -537,7 +662,7 @@ namespace BrewMonster.Scripts.Task if (!pEntry.IsSuccess()) { - #if TASK_TEMPL_EDITOR + #if !TASK_TEMPL_EDITOR RecursiveCheckPunchMonster(this); #endif } @@ -573,7 +698,8 @@ namespace BrewMonster.Scripts.Task break; case TaskTemplConstants.TASK_SVR_NOTIFY_GIVE_UP: pLst = pTask.GetActiveTaskList(); - pLst.ClearTask(pTask, pEntry, false); + // Use simplified removal to keep list consistent in the managed port + pLst.RemoveEntry(pEntry); // TODO: Log task give up // if (m_bDisplayInTitleTaskUI) TaskInterface::UpdateTitleUI(m_ID); @@ -805,9 +931,9 @@ namespace BrewMonster.Scripts.Task // ����ﵽ���� bool bReachLimit = false; if (m_bHidden) - bReachLimit = pList.m_uTopHideTaskCount >= TASK_HIDDEN_COUNT; + bReachLimit = pList.m_uTopHideTaskCount >= TASK_.TASK_HIDDEN_COUNT; else if (m_bDisplayInTitleTaskUI) - bReachLimit = bReachLimit || pList.m_uTitleTaskCount >= TASK_TITLE_TASK_COUNT; + bReachLimit = bReachLimit || pList.m_uTitleTaskCount >= TASK_.TASK_TITLE_TASK_COUNT; else bReachLimit = bReachLimit || pList.m_uTopShowTaskCount >= pList.GetMaxSimultaneousCount(); @@ -1591,8 +1717,54 @@ namespace BrewMonster.Scripts.Task TaskGlobalData pGlobal, byte uParentIndex) { - // TODO: implement full logic when ActiveTaskList/ActiveTaskEntry and TaskInterface APIs are available - return null; + // 最小实现:保证接任务/放弃任务会真实改变 ActiveTaskList,从而驱动任务面板刷新 + // English: Minimal implementation: make NEW/GIVEUP actually mutate ActiveTaskList so UI can refresh. + if (pTask == null || pList == null) return null; + + // Already has task + var existed = pList.GetEntry(m_FixedData.m_ID); + if (existed != null && existed.m_ID != 0) return existed; + + if (pList.m_uTaskCount >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN) return null; + + int insertIndex = pList.m_uTaskCount; + ActiveTaskEntry entry = pEntry ?? new ActiveTaskEntry(); + + // Fill fixed data (top-level by default) + entry.m_ID = (ushort)m_FixedData.m_ID; + entry.m_ulTemplAddr = m_FixedData.m_ID; // non-zero marker; template is resolved by ID in GetTempl() + entry.m_ulTaskTime = ulCurTime; + + entry.m_ParentIndex = (char)0xff; + entry.m_PrevSblIndex = (char)0xff; + entry.m_NextSblIndex = (char)0xff; + entry.m_ChildIndex = (char)0xff; + + entry.m_uState = (char)0; + entry.SetSuccess(); // default success, may be cleared by later checks + + if (ulCaptainTask != 0) + { + entry.m_uCapTaskId = (ushort)ulCaptainTask; + entry.m_ulCapTemplAddr = ulCaptainTask; // marker only + } + else + { + entry.m_uCapTaskId = 0; + entry.m_ulCapTemplAddr = 0; + } + + // Clear union buffer + if (entry.m_BufData != null) + Array.Clear(entry.m_BufData, 0, entry.m_BufData.Length); + + // Insert and update list count + pList.m_TaskEntries[insertIndex] = entry; + pList.m_uTaskCount = (byte)(insertIndex + 1); + + // Recount for UI and budget checks + pList.RecountTaskCounters(); + return entry; /*ActiveTaskEntry* aEntries = pList->m_TaskEntries; if (!pEntry) pEntry = aEntries + pList->m_uTaskCount; @@ -1814,6 +1986,29 @@ namespace BrewMonster.Scripts.Task int nChoice, ref task_sub_tags pSubTag) { + // 最小实现(客户端): + // - 记录到 FinishedTaskList(用于前置任务/可重复接判断) + // - 从 ActiveTaskList 移除该任务条目,从而驱动任务面板刷新 + // English minimal client port: + // - Record into FinishedTaskList (prerequisites/redo checks) + // - Remove entry from ActiveTaskList to refresh UI lists + if (pTask == null || pList == null || pEntry == null) return; + + bool success = pEntry.IsSuccess() && !pEntry.IsGiveUp(); + + // Only record on top-level templates (same as C++: !m_pParent && m_bNeedRecord) + if (m_pParent == null && m_FixedData.m_bNeedRecord) + { + if (pTask is CECTaskInterface cec) + { + cec.RecordFinishedTask(m_FixedData.m_ID, success); + } + } + + // Remove from active list (C++ RecursiveAward clears children + removes current entry) + pList.RemoveEntry(pEntry); + return; + // TODO : implement full logic when ActiveTaskList/ActiveTaskEntry and TaskInterface APIs are available /*{ char log[1024];