Implement deserialize logic for data of new, complete, giveup task

This commit is contained in:
HungDK
2025-12-16 10:58:09 +07:00
parent f647e578ad
commit a6d970d0be
@@ -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<svr_new_task>(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<task_notify_base>() + 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 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<svr_new_task>(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<svr_new_task>(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<svr_task_complete>(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<task_notify_base>() + 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 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<svr_task_complete>(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<svr_task_complete>(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];