Fixing wrong logic check task and subtask

This commit is contained in:
HungDK
2025-12-16 16:37:03 +07:00
parent 5f04ba4fd3
commit c35e464a1d
3 changed files with 439 additions and 291 deletions
+129 -34
View File
@@ -449,40 +449,100 @@ namespace BrewMonster.Scripts.Task
m_uUsedCount += pTempl.m_uDepth;
}
}
void RealignTask(ActiveTaskEntry pEntry, byte uReserve)
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
/// Index-based variant used by deliver/award flows where the "slot" matters (C++ passes an entry pointer).
/// </summary>
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);
}
};
@@ -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<unsigned char>(pEntry - aEntries);
pEntry->m_ID = static_cast<unsigned short>(m_ID);
pEntry->m_ulTemplAddr = reinterpret_cast<unsigned long>(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<unsigned long>(GetTaskTemplMan()->GetTopTaskByID(ulCaptainTask));
if (pEntry->m_ulCapTemplAddr) pEntry->m_uCapTaskId = static_cast<unsigned short>(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);
// ΪPQPQҪʱ¼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<unsigned short>(m_ID);
pTask->NotifyServer(&notify, 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<unsigned char>(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
/*{
+54 -4
View File
@@ -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<TaskItemClickEvent>(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 )
{