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 ) {