bugfix: Fix issue where builders could resume completed tasks after being disabled - #2793
bugfix: Fix issue where builders could resume completed tasks after being disabled#2793Stubbjax wants to merge 8 commits into
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Adds setPreviousTask call during disable to capture current task before cancellation; correctly placed before the actual task cancellation, and guarded by the not-already-disabled check. |
| Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp | Adds setPreviousTask with DOZER_TASK_INVALID guard (fixing prior OOB concern), restructures resumePreviousTask to check OBJECT_STATUS_UNDER_CONSTRUCTION for BUILD tasks, but silently drops REPAIR and FORTIFY tasks on resume. |
| Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp | Mirror of DozerAIUpdate changes for WorkerAI; same fix and same repair/fortify task-drop regression present. |
| Generals/Code/GameEngine/Include/GameLogic/Module/DozerAIUpdate.h | Adds setPreviousTask pure virtual and override declarations to the interface and concrete class; correct additions. |
| Generals/Code/GameEngine/Include/GameLogic/Module/WorkerAIUpdate.h | Adds setPreviousTask override declaration; mirrors DozerAIUpdate.h changes correctly. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp | Zero Hour mirror of Generals DozerAIUpdate changes; identical logic and identical repair/fortify regression. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp | Zero Hour mirror of Generals WorkerAIUpdate changes; identical logic and identical repair/fortify regression. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Zero Hour mirror of Generals Object.cpp disable-path change; correctly applies the same fix. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[setDisabledUntil called\nEMP / Underpowered / Hacked] --> B{Already disabled\nby same type?}
B -- Yes --> Z[No action]
B -- No --> C[setPreviousTask getCurrentTask]
C --> D{getCurrentTask ==\nDOZER_TASK_INVALID?}
D -- Yes --> E[Guard: return early\nNo save]
D -- No --> F[m_previousTask = task\nm_previousTaskInfo = m_task task]
F --> G[Disable flag set\nm_disabledTillFrame = frame]
G --> H[Next update tick:\ninternalCancelTask called]
H --> I[m_task task cleared\nbut m_previousTaskInfo already saved]
I --> J[... time passes ...\nDisable lifts]
J --> K[resumePreviousTask called]
K --> L{m_previousTask ==\nDOZER_TASK_INVALID?}
L -- Yes --> M[return early]
L -- No --> N{m_previousTask ==\nDOZER_TASK_BUILD?}
N -- No --> O[Clear m_previousTask\nBuilder stays idle\n⚠ REPAIR/FORTIFY dropped]
N -- Yes --> P[findObjectByID target]
P --> Q{target exists AND\nstill UNDER_CONSTRUCTION?}
Q -- No --> R[Clear m_previousTask\nBuilder stays idle\nFix: skip completed builds]
Q -- Yes --> S[newTask resume build]
S --> T[Clear m_previousTask]
Reviews (7): Last reviewed commit: "fix: Reverse condition" | Re-trigger Greptile
| if( task == DOZER_TASK_BUILD ) | ||
| { | ||
| // TheSuperHackers @bugfix Stubbjax 15/06/2026 Ignore the build task if the building is already complete. | ||
| if (target->getConstructionPercent() == CONSTRUCTION_COMPLETE) |
There was a problem hiding this comment.
Should this perhaps go into DozerAIUpdate::resumePreviousTask and here it should be an assert?
There was a problem hiding this comment.
Good point. I suppose it could seeing as this is the only way it can happen. Having to resolve the target from the task info does make the code a bit more complex though.
657688e to
a0fbee6
Compare
de2f3f1 to
bdf1ab1
Compare
|
|
||
| DozerAIInterface* dozerAI = getAI() ? getAI()->getDozerAIInterface() : nullptr; | ||
| if (dozerAI) | ||
| dozerAI->setPreviousTask(dozerAI->getCurrentTask()); |
There was a problem hiding this comment.
This looks a bit strange. Wasn't cancelTask supposed to take of this? For example from Object::onDisabledEdge.
There was a problem hiding this comment.
Yes, but there was no distinction on how the disabling happened here. A unit entering a transport causes it to become disabled, and thus it would attempt to resume the task after exiting the transport.
There was a problem hiding this comment.
setPreviousTask is confusing and can be removed again and
m_previousTask = task;
m_previousTaskInfo = m_task[task];
readded to internalCancelTask.
I tested that with the EMP test case shown in the video and it worked.
There was a problem hiding this comment.
The problem with this approach is that commanding a currently-constructing Dozer to enter a Chinook will have the Dozer attempt to resume the construction after exiting the Chinook.
There was a problem hiding this comment.
Yeah true. Maybe capture should not return to internalCancelTask. Could we move the capture into Object::onDisabledEdge(TRUE) before cancelTask, gated on DISABLED_UNDERPOWERED, DISABLED_EMP, DISABLED_SUBDUED, or DISABLED_HACKED? The disabled mask is set by then, the capture becomes symmetric with resumption, and it is no longer coupled to the audio-deduplication block. Could also use the !RETAIL_COMPATIBLE_CRC guard as the resume path.
74beb35 to
a4ffdb5
Compare
This comment was marked as outdated.
This comment was marked as outdated.
|
@Stubbjax Is this now fixed up? |
It should be. |
| m_previousTask = DOZER_TASK_INVALID; | ||
| m_previousTaskInfo = DozerTaskInfo(); | ||
| Object* target = TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID); | ||
| if (target && !target->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) |
There was a problem hiding this comment.
&& target->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)
There was a problem hiding this comment.
Yes this seems backwards, OBJECT_STATUS_UNDER_CONSTRUCTION is set while construction is unfinished and cleared when complete, so the current condition skips interrupted builds and resumes completed ones.
|
|
||
| DozerAIInterface* dozerAI = getAI() ? getAI()->getDozerAIInterface() : nullptr; | ||
| if (dozerAI) | ||
| dozerAI->setPreviousTask(dozerAI->getCurrentTask()); |
There was a problem hiding this comment.
setPreviousTask is confusing and can be removed again and
m_previousTask = task;
m_previousTaskInfo = m_task[task];
readded to internalCancelTask.
I tested that with the EMP test case shown in the video and it worked.
|
This needs polishing. |
ede3cb1 to
2f69024
Compare
| if (m_previousTask == DOZER_TASK_INVALID) | ||
| return; | ||
|
|
||
| if (m_previousTask == DOZER_TASK_BUILD) |
There was a problem hiding this comment.
This changes behavior for DOZER_TASK_REPAIR. In non-retail-compatible builds, main attempts to reissue any saved task, but this implementation only reissues DOZER_TASK_BUILD and then clears the saved repair task. Is this what we want?
If not, could have the OBJECT_STATUS_UNDER_CONSTRUCTION check remain specific to BUILD while preserving existing non-build resumption behavior. DOZER_TASK_FORTIFY follows the same code path, but does anything use it? This applies to the mirrored implementations too.
This change fixes an issue introduced by #1870 that allows builders to resume an already completed task after being disabled.
When assigning a new build task to a builder, if the target building is an already-completed building, then the new task is ignored.
Before
BEFORE.mp4
After
AFTER.mp4