diff --git a/Assets/PerfectWorld/Scripts/Task/TaskProcess.cs b/Assets/PerfectWorld/Scripts/Task/TaskProcess.cs
index a12c9dbf0c..bf2a66e203 100644
--- a/Assets/PerfectWorld/Scripts/Task/TaskProcess.cs
+++ b/Assets/PerfectWorld/Scripts/Task/TaskProcess.cs
@@ -449,40 +449,100 @@ namespace BrewMonster.Scripts.Task
m_uUsedCount += pTempl.m_uDepth;
}
}
- void RealignTask(ActiveTaskEntry pEntry, byte uReserve)
+ ///
+ /// Shift-based realign that matches original C++ ActiveTaskList::RealignTask.
+ /// It only adjusts indices in a controlled range instead of globally compacting the list.
+ /// This is critical for keeping Parent/Child/Sibling indices stable during subtask progression.
+ ///
+ public void RealignTask(ActiveTaskEntry pEntry, byte uReserve)
{
- // 简化实现:压缩数组,移除空洞(m_ID==0 或 null)
- // English: Simplified implementation: compact array, remove holes (m_ID==0 or null).
- //
- // NOTE: This does not preserve complex parent/child/sibling linkage yet. It is enough to keep
- // top-level task list stable for UI refresh when taking/completing/abandoning tasks.
- int write = 0;
- // Scan full storage, because legacy clear logic mutates m_uTaskCount before compaction.
- // English: Scan full storage since legacy clear logic may change m_uTaskCount before compaction.
- int count = m_TaskEntries.Length;
- for (int read = 0; read < count; read++)
+ if (pEntry == null) return;
+ int uCurIndex = Array.IndexOf(m_TaskEntries, pEntry);
+ if (uCurIndex < 0) return;
+ RealignTaskAtIndex(uCurIndex, uReserve);
+ }
+
+ ///
+ /// Index-based variant used by deliver/award flows where the "slot" matters (C++ passes an entry pointer).
+ ///
+ public void RealignTaskAtIndex(int uCurIndex, byte uReserve)
+ {
+ if (uCurIndex < 0 || uCurIndex >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN) return;
+
+ int ulCount = m_uTaskCount - uCurIndex; // remaining entries from uCurIndex
+ if (ulCount == 0) return;
+
+ // Count consecutive empty entries starting from uCurIndex
+ int uEmptyCount = 0;
+ for (int uEmpty = uCurIndex; uEmpty < TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN; uEmpty++)
{
- ActiveTaskEntry e = m_TaskEntries[read];
- if (e == null || e.m_ID == 0) continue;
- if (write != read) m_TaskEntries[write] = e;
- write++;
+ var e = m_TaskEntries[uEmpty];
+ if (e == null || e.m_ID == 0) uEmptyCount++;
+ else break;
}
-
- for (int i = write; i < m_TaskEntries.Length; i++)
+
+ if (uReserve == uEmptyCount) return;
+
+ int srcIndex = uCurIndex + uEmptyCount;
+ int insertIndex = uCurIndex + uReserve;
+ int uGap = insertIndex - srcIndex;
+ if (uGap == 0) return;
+
+ // Move the block [srcIndex, srcIndex + ulCount) -> [insertIndex, insertIndex + ulCount)
+ if (uGap > 0)
+ {
+ for (int i = ulCount - 1; i >= 0; i--)
+ m_TaskEntries[insertIndex + i] = m_TaskEntries[srcIndex + i];
+ }
+ else
+ {
+ for (int i = 0; i < ulCount; i++)
+ m_TaskEntries[insertIndex + i] = m_TaskEntries[srcIndex + i];
+ }
+
+ // Clear vacated slots
+ int clearStart, clearEnd;
+ if (insertIndex > srcIndex)
+ {
+ clearStart = srcIndex;
+ clearEnd = insertIndex;
+ }
+ else
+ {
+ clearStart = insertIndex + ulCount;
+ clearEnd = srcIndex + ulCount;
+ }
+ for (int i = clearStart; i < clearEnd; i++)
m_TaskEntries[i] = null;
-
- m_uTaskCount = (byte)write;
-
- // Reset linkage to "top-level only" defaults
- for (int i = 0; i < m_uTaskCount; i++)
+
+ // Adjust indices for entries before uCurIndex (child + next sibling that point into >= uCurIndex)
+ for (int i = 0; i < uCurIndex; i++)
{
- var e = m_TaskEntries[i];
- e.m_ParentIndex = (char)0xff;
- e.m_PrevSblIndex = (char)0xff;
- e.m_NextSblIndex = (char)0xff;
- e.m_ChildIndex = (char)0xff;
+ var cur = m_TaskEntries[i];
+ if (cur == null || cur.m_ID == 0) continue;
+
+ if (cur.m_ChildIndex != 0xff && cur.m_ChildIndex >= uCurIndex)
+ cur.m_ChildIndex = (char)(cur.m_ChildIndex + uGap);
+ if (cur.m_NextSblIndex != 0xff && cur.m_NextSblIndex >= uCurIndex)
+ cur.m_NextSblIndex = (char)(cur.m_NextSblIndex + uGap);
}
-
+
+ // Adjust indices for moved entries (the inserted block)
+ for (int i = 0; i < ulCount; i++)
+ {
+ var cur = m_TaskEntries[insertIndex + i];
+ if (cur == null || cur.m_ID == 0) continue;
+
+ if (cur.m_ParentIndex != 0xff && cur.m_ParentIndex >= uCurIndex)
+ cur.m_ParentIndex = (char)(cur.m_ParentIndex + uGap);
+ if (cur.m_PrevSblIndex != 0xff && cur.m_PrevSblIndex >= uCurIndex)
+ cur.m_PrevSblIndex = (char)(cur.m_PrevSblIndex + uGap);
+ if (cur.m_ChildIndex != 0xff)
+ cur.m_ChildIndex = (char)(cur.m_ChildIndex + uGap);
+ if (cur.m_NextSblIndex != 0xff)
+ cur.m_NextSblIndex = (char)(cur.m_NextSblIndex + uGap);
+ }
+
RecountTaskCounters();
}
@@ -516,6 +576,30 @@ namespace BrewMonster.Scripts.Task
RecursiveClearTask(pTask, pEntry, bRemoveItem, true, true);
RealignTask(pEntry, 0);
}
+
+ // void ClearChildrenOf(TaskInterface* pTask, ActiveTaskEntry* pParent, bool bRemoveItem = true);
+ // 清除指定父节点的所有子任务(不清除父节点自身) // English: Clear all children of a parent entry (but keep the parent entry)
+ public void ClearChildrenOf(TaskInterface pTask, ActiveTaskEntry pParent, bool bRemoveItem = true)
+ {
+ if (pParent == null) return;
+
+ // Mirror C++: while parent has a first child, recursively clear that child subtree.
+ while (pParent.m_ChildIndex != 0xff)
+ {
+ int childIndex = (byte)pParent.m_ChildIndex;
+ if (childIndex < 0 || childIndex >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN) break;
+
+ var child = m_TaskEntries[childIndex];
+ if (child == null || child.m_ID == 0)
+ {
+ // Broken link: stop to avoid infinite loop
+ pParent.m_ChildIndex = (char)0xff;
+ break;
+ }
+
+ RecursiveClearTask(pTask, child, bRemoveItem, true, true);
+ }
+ }
void RecursiveClearTask(
TaskInterface pTask,
@@ -602,7 +686,6 @@ namespace BrewMonster.Scripts.Task
return null;
}
- // void ClearChildrenOf(TaskInterface* pTask, ActiveTaskEntry* pParent, bool bRemoveItem = true);
// ActiveTaskEntry* GetEntry(unsigned long ulId)
// {
// for (unsigned char i = 0; i < m_uTaskCount; i++)
@@ -637,15 +720,27 @@ namespace BrewMonster.Scripts.Task
m_uMaxSimultaneousCount = true;
}
- // 从列表中移除指定条目(并压缩列表) // English: Remove an entry from the list (and compact)
+ // 从列表中移除指定条目(并重新对齐列表) // English: Remove an entry from the list (and realign)
public void RemoveEntry(ActiveTaskEntry entry)
{
- if (entry == null) return;
- // Mark as empty
+ if (entry == null || entry.m_ID == 0) return;
+
+ // Best-effort unlink from sibling chain
+ if (entry.m_ParentIndex != 0xff)
+ {
+ if (entry.m_PrevSblIndex != 0xff && m_TaskEntries[entry.m_PrevSblIndex] != null)
+ m_TaskEntries[entry.m_PrevSblIndex].m_NextSblIndex = entry.m_NextSblIndex;
+ else if (m_TaskEntries[entry.m_ParentIndex] != null)
+ m_TaskEntries[entry.m_ParentIndex].m_ChildIndex = entry.m_NextSblIndex;
+
+ if (entry.m_NextSblIndex != 0xff && m_TaskEntries[entry.m_NextSblIndex] != null)
+ m_TaskEntries[entry.m_NextSblIndex].m_PrevSblIndex = entry.m_PrevSblIndex;
+ }
+
+ // Mark empty + decrement count, then realign from this slot.
entry.m_ulTemplAddr = 0;
entry.m_ID = 0;
-
- // Compact list (RealignTask is our compaction implementation)
+ if (m_uTaskCount > 0) m_uTaskCount--;
RealignTask(entry, 0);
}
};
diff --git a/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs b/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs
index f8162e1b3d..338fbd68e8 100644
--- a/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs
+++ b/Assets/PerfectWorld/Scripts/Task/TaskTempl.Method.cs
@@ -698,8 +698,8 @@ namespace BrewMonster.Scripts.Task
break;
case TaskTemplConstants.TASK_SVR_NOTIFY_GIVE_UP:
pLst = pTask.GetActiveTaskList();
- // Use simplified removal to keep list consistent in the managed port
- pLst.RemoveEntry(pEntry);
+ // Match C++ behavior: ClearTask (and realign), do NOT globally compact or just zero the entry.
+ pLst.ClearTask(pTask, pEntry, false);
// TODO: Log task give up
// if (m_bDisplayInTitleTaskUI) TaskInterface::UpdateTitleUI(m_ID);
@@ -1704,6 +1704,20 @@ namespace BrewMonster.Scripts.Task
return null;
}
+
+ // 通过子任务序号获取子任务模板(0基) // English: Get child template by index (0-based)
+ public ATaskTempl GetSubByIndex(byte index)
+ {
+ ATaskTempl child = m_pFirstChild;
+ byte i = 0;
+ while (child != null)
+ {
+ if (i == index) return child;
+ i++;
+ child = child.m_pNextSibling;
+ }
+ return null;
+ }
public ActiveTaskEntry DeliverTask(
TaskInterface pTask,
@@ -1716,266 +1730,149 @@ namespace BrewMonster.Scripts.Task
ref task_sub_tags pSubTag,
TaskGlobalData pGlobal,
byte uParentIndex)
+ {
+ if (pTask == null || pList == null) return null;
+
+ // Avoid duplicates (server shouldn't send duplicates, but protects managed UI)
+ var existed = pList.GetEntry(m_FixedData.m_ID);
+ if (existed != null && existed.m_ID != 0) return existed;
+
+ int startIndex = (pEntry == null)
+ ? pList.m_uTaskCount
+ : Array.IndexOf(pList.m_TaskEntries, pEntry);
+ if (startIndex < 0) startIndex = pList.m_uTaskCount;
+
+ uint maskRef = ulMask;
+ DeliverTask_Internal(
+ pTask, pList, startIndex, ulCaptainTask, ref maskRef, ulCurTime,
+ pSubTempl, ref pSubTag, pGlobal, uParentIndex);
+
+ return (startIndex >= 0 && startIndex < TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
+ ? pList.m_TaskEntries[startIndex]
+ : null;
+ }
+
+ // C++ parity helper: returns the next free slot index (like returning `pEntry` pointer).
+ private int DeliverTask_Internal(
+ TaskInterface pTask,
+ ActiveTaskList pList,
+ int entryIndex,
+ uint ulCaptainTask,
+ ref uint ulMask,
+ uint ulCurTime,
+ ATaskTempl pSubTempl,
+ ref task_sub_tags pSubTag,
+ TaskGlobalData pGlobal,
+ byte uParentIndex)
+ {
+ if (entryIndex < 0 || entryIndex >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
+ return entryIndex;
+
+ // If the slot is occupied, reserve 1 empty slot here (C++: RealignTask(pEntry, 1))
+ if (pList.m_TaskEntries[entryIndex] != null && pList.m_TaskEntries[entryIndex].m_ID != 0)
+ pList.RealignTaskAtIndex(entryIndex, 1);
+
+ var entry = pList.m_TaskEntries[entryIndex] ?? new ActiveTaskEntry();
+ pList.m_TaskEntries[entryIndex] = entry;
+ byte uIndex = (byte)entryIndex;
+
+ entry.m_ID = (ushort)m_FixedData.m_ID;
+ entry.m_ulTemplAddr = m_FixedData.m_ID; // managed marker (resolve by id)
+ entry.m_ParentIndex = (char)uParentIndex;
+ entry.m_PrevSblIndex = (char)0xff;
+ entry.m_NextSblIndex = (char)0xff;
+ entry.m_ChildIndex = (char)0xff;
+ entry.m_uState = (char)0;
+ entry.m_ulTaskTime = ulCurTime;
+
+ if (ulCaptainTask != 0)
{
- // 最小实现:保证接任务/放弃任务会真实改变 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;
- else if (pEntry->m_ID != 0) // entry��ռ�ã���Ҫ���Ų��һ����λ;
- pList->RealignTask(pEntry, 1);
-
- unsigned char uIndex = static_cast(pEntry - aEntries);
-
- pEntry->m_ID = static_cast(m_ID);
- pEntry->m_ulTemplAddr = reinterpret_cast(this);
- pEntry->m_ParentIndex = uParentIndex;
- pEntry->m_PrevSblIndex = 0xff;
- pEntry->m_NextSblIndex = 0xff;
- pEntry->m_ChildIndex = 0xff;
- pEntry->m_uState = 0;
- pEntry->m_ulTaskTime = ulCurTime;
- #ifndef _TASK_CLIENT
- // ��ΪPQ������Ҫ��ʱ�����¼��m_ulTaskTime��
- if (m_bPQTask)
- {
- pEntry->m_ulTaskTime = PublicQuestInterface::GetCurTaskStamp(m_ID);
- }
- #endif
- if (ulCaptainTask)
- {
- pEntry->m_ulCapTemplAddr = reinterpret_cast(GetTaskTemplMan()->GetTopTaskByID(ulCaptainTask));
- if (pEntry->m_ulCapTemplAddr) pEntry->m_uCapTaskId = static_cast(ulCaptainTask);
- else
- {
- pEntry->m_uCapTaskId = 0;
-
- char log[1024];
- sprintf(log, "DeliverTask, Cant Find CapTask = %d", ulCaptainTask);
- TaskInterface::WriteLog(pTask->GetPlayerId(), m_ID, 0, log);
- }
+ entry.m_uCapTaskId = (ushort)ulCaptainTask;
+ entry.m_ulCapTemplAddr = ulCaptainTask; // managed marker
}
else
{
- pEntry->m_uCapTaskId = 0;
- pEntry->m_ulCapTemplAddr = 0;
+ entry.m_uCapTaskId = 0;
+ entry.m_ulCapTemplAddr = 0;
}
- pEntry->SetSuccess(); // Later will check whether truly succeed
- memset(pEntry->m_BufData, 0, sizeof(pEntry->m_BufData));
- #ifndef _TASK_CLIENT
+ entry.SetSuccess(); // Later will check whether truly succeed
+ if (entry.m_BufData != null) Array.Clear(entry.m_BufData, 0, entry.m_BufData.Length);
- // ��ΪPQ�����PQ��������Ҫ��ʱ�����¼��m_ulTaskTime��
- if (m_bPQTask)
+ // C++ always increments here; deletion paths decrement before reusing slots.
+ if (pList.m_uTaskCount < byte.MaxValue) pList.m_uTaskCount++;
+
+ // Root-node counters (client-side only)
+ if (m_pParent == null)
{
- ulCurTime = PublicQuestInterface::GetCurTaskStamp(m_ID);
- }
-
- else if (m_enumMethod == enumTMReachTreasureZone)
- {
- // ����ر�λ�úͲر�ͼ���½�λ�õ�index
- unsigned short uZonesSum = m_ucZonesNumX * m_ucZonesNumZ;
- unsigned short uTreasureLocIndex = pTask->RandNormal(1,uZonesSum);
- char sTreasureLocMinX = (uTreasureLocIndex % m_ucZonesNumX - 1);
- char sTreasureLocMinZ = (uTreasureLocIndex / m_ucZonesNumX);
- // ������������IJر�ͼλ�ã�ʹ�ر�λ�ò��ܴ��ڲر�ͼ�ı�Ե
- int sMapMinX = pTask->RandNormal(sTreasureLocMinX,sTreasureLocMinX + TASK_TREASURE_MAP_SIDE_MULTIPLE - 3) - TASK_TREASURE_MAP_SIDE_MULTIPLE + 2;
- int sMapMinZ = pTask->RandNormal(sTreasureLocMinZ,sTreasureLocMinZ + TASK_TREASURE_MAP_SIDE_MULTIPLE - 3) - TASK_TREASURE_MAP_SIDE_MULTIPLE + 2;
- // �洢���ѽ������б���
- pEntry->m_iUsefulData1 = uTreasureLocIndex;
- pEntry->m_iUsefulData1 |= (sMapMinX << 16) & 0x00FF0000;
- pEntry->m_iUsefulData1 |= (sMapMinZ << 24) & 0xFF000000;
- }
-
- ulMask |= m_ulMask;
-
- #else
- if (m_enumMethod == enumTMReachTreasureZone)
- {
- task_notify_base notify;
- notify.reason = TASK_CLT_NOTIFY_REQUEST_TREASURE_INDEX;
- notify.task = static_cast(m_ID);
- pTask->NotifyServer(¬ify, sizeof(notify));
- }
-
- #endif
-
-
- pList->m_uTaskCount++;
-
- if (!m_pParent)
- {
- if (m_bHidden) pList->m_uTopHideTaskCount++;
- else if (m_bDisplayInTitleTaskUI) pList->m_uTitleTaskCount++;
- else pList->m_uTopShowTaskCount++;
- pList->m_uUsedCount += m_uDepth;
-
- #ifndef _TASK_CLIENT
-
- if (pGlobal)
- {
- pGlobal->AddRevNum();
- pGlobal->m_ulRcvUpdateTime = ulCurTime;
- TaskUpdateGlobalData(m_ID, pGlobal->buf);
- }
-
- #endif
-
+ if (m_FixedData.m_bHidden) pList.m_uTopHideTaskCount++;
+ else if (m_FixedData.m_bDisplayInTitleTaskUI) pList.m_uTitleTaskCount++;
+ else pList.m_uTopShowTaskCount++;
+
+ int used = pList.m_uUsedCount + m_uDepth;
+ pList.m_uUsedCount = (byte)Math.Clamp(used, 0, byte.MaxValue);
}
+ // Attach to parent (append to end of child sibling chain)
if (uParentIndex != 0xff)
{
- ActiveTaskEntry& ParentEntry = aEntries[uParentIndex];
- if (ParentEntry.m_ChildIndex == 0xff) ParentEntry.m_ChildIndex = uIndex;
- else
+ var parent = pList.m_TaskEntries[uParentIndex];
+ if (parent != null)
{
- unsigned char uChildEntry = ParentEntry.m_ChildIndex;
- while (aEntries[uChildEntry].m_NextSblIndex != 0xff)
- uChildEntry = aEntries[uChildEntry].m_NextSblIndex;
- aEntries[uChildEntry].m_NextSblIndex = uIndex;
- pEntry->m_PrevSblIndex = uChildEntry;
- }
- }
-
- #ifndef _TASK_CLIENT
-
- if (!m_pParent)
- DeliverGivenItems(pTask);
-
- #endif
-
- pEntry++;
-
- if (pSubTempl)
- {
- return pSubTempl->DeliverTask(
- pTask,
- pList,
- pEntry,
- 0,
- ulMask,
- ulCurTime,
- NULL,
- pSubTag,
- NULL,
- uIndex);
- }
- else if (m_bRandOne)
- {
- #ifdef _TASK_CLIENT
- if (pSubTag->cur_index < pSubTag->sz)
- {
- pSubTempl = GetSubByIndex(pSubTag->tags[pSubTag->cur_index]);
- pSubTag->cur_index++;
-
- if (pSubTempl)
+ if (parent.m_ChildIndex == (char)0xff)
+ parent.m_ChildIndex = (char)uIndex;
+ else
{
- return pSubTempl->DeliverTask(
- pTask,
- pList,
- pEntry,
- 0,
- ulMask,
- ulCurTime,
- NULL,
- pSubTag,
- NULL,
- uIndex);
+ int uChildEntry = (byte)parent.m_ChildIndex;
+ while (pList.m_TaskEntries[uChildEntry] != null && pList.m_TaskEntries[uChildEntry].m_NextSblIndex != 0xff)
+ uChildEntry = (byte)pList.m_TaskEntries[uChildEntry].m_NextSblIndex;
+
+ if (pList.m_TaskEntries[uChildEntry] != null)
+ {
+ pList.m_TaskEntries[uChildEntry].m_NextSblIndex = (char)uIndex;
+ entry.m_PrevSblIndex = (char)uChildEntry;
+ }
}
}
- #else
- int nSel;
- pSubTempl = RandOneChild(pTask, nSel);
-
- if (pSubTempl)
- {
- if (pSubTag->sz < MAX_SUB_TAGS)
- pSubTag->tags[pSubTag->sz++] = static_cast(nSel);
-
- return pSubTempl->DeliverTask(
- pTask,
- pList,
- pEntry,
- 0,
- ulMask,
- ulCurTime,
- NULL,
- pSubTag,
- NULL,
- uIndex);
- }
- #endif
}
+
+ int nextIndex = entryIndex + 1;
+
+ // Explicit server-specified sub-task comes first (C++ pSubTempl branch)
+ if (pSubTempl != null)
+ {
+ return pSubTempl.DeliverTask_Internal(
+ pTask, pList, nextIndex, 0, ref ulMask, ulCurTime, null, ref pSubTag, pGlobal, uIndex);
+ }
+ // Rand-one: on client, follow preselected tag path one step at a time
+ else if (m_FixedData.m_bRandOne)
+ {
+ if (pSubTag.cur_index < pSubTag.sz)
+ {
+ byte sel = pSubTag.tags[pSubTag.cur_index++];
+ var chosen = GetSubByIndex(sel);
+ if (chosen != null)
+ {
+ return chosen.DeliverTask_Internal(
+ pTask, pList, nextIndex, 0, ref ulMask, ulCurTime, null, ref pSubTag, pGlobal, uIndex);
+ }
+ }
+ return nextIndex;
+ }
+ // Normal: deliver children (all, or first if execute-in-order)
else
{
- const ATaskTempl* pChild = m_pFirstChild;
-
- while (pChild)
+ var child = m_pFirstChild;
+ while (child != null)
{
- pEntry = pChild->DeliverTask(
- pTask,
- pList,
- pEntry,
- 0,
- ulMask,
- ulCurTime,
- NULL,
- pSubTag,
- NULL,
- uIndex);
+ nextIndex = child.DeliverTask_Internal(
+ pTask, pList, nextIndex, 0, ref ulMask, ulCurTime, null, ref pSubTag, pGlobal, uIndex);
- if (m_bExeChildInOrder) return pEntry;
- pChild = pChild->m_pNextSibling;
+ if (m_FixedData.m_bExeChildInOrder) return nextIndex;
+ child = child.m_pNextSibling;
}
+ return nextIndex;
}
-
- return pEntry;*/
}
void RecursiveAward(
@@ -1986,28 +1883,134 @@ 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
+ // 客户端侧尽量对齐 C++ RecursiveAward:
+ // - 清除子任务
+ // - 维护父/子/兄弟索引关系
+ // - ExecuteChildInOrder 时复用 slot 投递下一个兄弟任务
+ // English: Client-side parity with C++ RecursiveAward:
+ // - Clear children
+ // - Maintain parent/child/sibling indices
+ // - If execute-children-in-order, reuse the freed slot to deliver the next sibling task
if (pTask == null || pList == null || pEntry == null) return;
-
+
+ int entryIndex = Array.IndexOf(pList.m_TaskEntries, pEntry);
+ if (entryIndex < 0) return;
+
+ // 失败时不收取物品的特殊逻辑(仅影响清子任务时是否移除物品)
+ // English: If failed and flagged, don't remove items when clearing children.
+ bool bFailedTaskDoNotTakeItem = !pEntry.IsSuccess() && m_FixedData.m_bNotClearItemWhenFailed && m_FixedData.m_bClearAcquired;
+
+ // Clear children first (C++: pList->ClearChildrenOf(pTask, pEntry, !bFailedTaskDoNotTakeItem))
+ pList.ClearChildrenOf(pTask, pEntry, !bFailedTaskDoNotTakeItem);
+ if (pEntry.m_ulTemplAddr == 0) return; // must check it (matches C++)
+
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;
+
+ // Mark empty + decrement count (C++ does this before realign / reuse)
+ pEntry.m_ulTemplAddr = 0;
+ pEntry.m_ID = 0;
+ if (pList.m_uTaskCount > 0) pList.m_uTaskCount--;
+
+ // If has parent, unlink from sibling chain and handle parent propagation
+ if (pEntry.m_ParentIndex != 0xff)
+ {
+ int parentIndex = (byte)pEntry.m_ParentIndex;
+ if (parentIndex >= 0 && parentIndex < TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN)
+ {
+ var parentEntry = pList.m_TaskEntries[parentIndex];
+ if (parentEntry != null)
+ {
+ // unlink siblings (matches C++)
+ if (pEntry.m_PrevSblIndex != 0xff)
+ {
+ var prev = pList.m_TaskEntries[(byte)pEntry.m_PrevSblIndex];
+ if (prev != null) prev.m_NextSblIndex = pEntry.m_NextSblIndex;
+ }
+ else
+ {
+ parentEntry.m_ChildIndex = pEntry.m_NextSblIndex;
+ }
+
+ if (pEntry.m_NextSblIndex != 0xff)
+ {
+ var next = pList.m_TaskEntries[(byte)pEntry.m_NextSblIndex];
+ if (next != null) next.m_PrevSblIndex = pEntry.m_PrevSblIndex;
+ }
+
+ // ParentAlsoFail
+ if (!pEntry.IsSuccess() && m_FixedData.m_bParentAlsoFail && m_pParent != null)
+ {
+ pList.RealignTaskAtIndex(entryIndex, 0);
+ parentEntry.ClearSuccess();
+ parentEntry.SetFinished();
+ m_pParent.RecursiveAward(pTask, pList, parentEntry, ulCurtime, -1, ref pSubTag);
+ return;
+ }
+ // ParentAlsoSuccess
+ else if (pEntry.IsSuccess() && m_FixedData.m_bParentAlsoSucc && m_pParent != null)
+ {
+ pList.RealignTaskAtIndex(entryIndex, 0);
+ parentEntry.SetFinished();
+ pList.ClearChildrenOf(pTask, parentEntry, true);
+ if (m_pParent.m_FixedData.m_enumFinishType == (uint)TaskFinishType.enumTFTDirect)
+ m_pParent.RecursiveAward(pTask, pList, parentEntry, ulCurtime, -1, ref pSubTag);
+ return;
+ }
+ // Execute children in order: deliver next sibling into the freed slot (C++ reuse pEntry)
+ else if (m_pParent != null && m_pParent.m_FixedData.m_bExeChildInOrder && m_pNextSibling != null)
+ {
+ // if parent still has children OR next sibling already exists, just realign
+ if (parentEntry.m_ChildIndex != 0xff || pList.GetEntry(m_pNextSibling.m_FixedData.m_ID) != null)
+ {
+ pList.RealignTaskAtIndex(entryIndex, 0);
+ }
+ else
+ {
+ // reuse current slot object; keep it in array at entryIndex
+ m_pNextSibling.DeliverTask(
+ pTask,
+ pList,
+ pEntry,
+ 0,
+ pTask.GetTaskMask(),
+ ulCurtime,
+ null,
+ ref pSubTag,
+ new TaskGlobalData(),
+ (byte)pEntry.m_ParentIndex);
+ }
+ return;
+ }
+ // If this was the last child, mark parent finished
+ else if (parentEntry.m_ChildIndex == 0xff && m_pParent != null)
+ {
+ pList.RealignTaskAtIndex(entryIndex, 0);
+ parentEntry.SetFinished();
+ if (m_pParent.m_FixedData.m_enumFinishType == (uint)TaskFinishType.enumTFTDirect)
+ m_pParent.RecursiveAward(pTask, pList, parentEntry, ulCurtime, -1, ref pSubTag);
+ return;
+ }
+ }
+ }
+
+ // Default: just realign this freed slot away
+ pList.RealignTaskAtIndex(entryIndex, 0);
+ return;
+ }
+ else
+ {
+ // Root node: realign and adjust counts similar to C++ (counters will also be recomputed by UI paths)
+ pList.RealignTaskAtIndex(entryIndex, 0);
+ pList.RecountTaskCounters();
+ return;
+ }
// TODO : implement full logic when ActiveTaskList/ActiveTaskEntry and TaskInterface APIs are available
/*{
diff --git a/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs b/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs
index 9312be4a80..7b04488f18 100644
--- a/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs
+++ b/Assets/PerfectWorld/Scripts/Task/UI/DlgTask.cs
@@ -137,12 +137,12 @@ namespace BrewMonster.Scripts.Task.UI
#region Unity METHODS
- private void OnEnable()
+ private new void OnEnable()
{
OnShowDialog();
}
- private void Awake()
+ private new void Awake()
{
EventBus.Subscribe(evt =>
{
@@ -770,7 +770,7 @@ namespace BrewMonster.Scripts.Task.UI
return default(T);
}
- public CECHostPlayer GetHostPlayer()
+ public new CECHostPlayer GetHostPlayer()
{
if(EC_Game.GetGameRun() == null)
{
@@ -828,6 +828,52 @@ namespace BrewMonster.Scripts.Task.UI
child = child.m_pNextSibling;
}
}
+
+ // [中文] 仅插入“已接任务(Active)”中的子任务(基于 ActiveTaskList 的 Child/NextSbl 索引)
+ // [English] Insert only active subtasks (based on ActiveTaskList Child/NextSbl indices)
+ private void InsertActiveTaskChildren(TaskTreeViewItem pRoot, uint idTask)
+ {
+ var pTreeTask = m_pTv_Quest;
+ var pMan = EC_Game.GetTaskTemplateMan();
+ var pTask = GetHostPlayer()?.GetTaskInterface();
+ if (pTreeTask == null || pMan == null || pTask == null) return;
+
+ ActiveTaskList pList = pTask.GetActiveTaskList();
+ if (pList == null) return;
+
+ ActiveTaskEntry parentEntry = pList.GetEntry(idTask);
+ if (parentEntry == null) return;
+
+ char idx = parentEntry.m_ChildIndex;
+ while (idx != (char)0xff)
+ {
+ int childIndex = (byte)idx;
+ if (childIndex < 0 || childIndex >= TaskInterfaceConstants.TASK_ACTIVE_LIST_MAX_LEN) break;
+
+ ActiveTaskEntry childEntry = pList.m_TaskEntries[childIndex];
+ if (childEntry == null || childEntry.m_ID == 0) break;
+
+ uint childId = childEntry.m_ID;
+ ATaskTempl childTempl = pMan.GetTaskTemplByID(childId);
+ string text = childTempl != null ? GetTaskNameWithColor(childTempl) : $"Task {childId}";
+
+ var pItem = pTreeTask.InsertItem(text, pRoot, null);
+ if (pItem != null)
+ {
+ pTreeTask.SetItemData(pItem, childId);
+ if ((int)childId == m_idSelTask)
+ {
+ if (m_pBtn_Abandon != null) m_pBtn_Abandon.interactable = true;
+ UpdateTask((int)childId);
+ }
+
+ // recurse into active children
+ InsertActiveTaskChildren(pItem, childId);
+ }
+
+ idx = childEntry.m_NextSblIndex;
+ }
+ }
private void SetTextItemText(string strNewTextItem, bool keepScrollPos, string strNewHintItem)
{
@@ -1469,7 +1515,11 @@ namespace BrewMonster.Scripts.Task.UI
pItem.SetItemTextColor(GetTaskColor((int)ENUM_TASK_TYPE.enumTTLevel2));
// pTreeTask.SetItemHint(pItem, pTemp->GetSignature()); // TODO
pTreeTask.SetItemData(pItem, (uint)id);
- InsertTaskChildren(pItem, (uint)id, true, pTemp.IsKeyTask());
+ // HaveQuest view: children should reflect ActiveTaskList, not template tree (otherwise they never disappear on completion)
+ if (m_iType == 0)
+ InsertActiveTaskChildren(pItem, (uint)id);
+ else
+ InsertTaskChildren(pItem, (uint)id, true, pTemp.IsKeyTask());
if( (int)id == m_idSelTask )
{