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 18 commits into
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp | Implements guarded task preservation and complete build, repair, and fortify resumption for Generals dozers. |
| Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp | Mirrors the corrected disable and task-resumption lifecycle for Generals workers. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp | Applies the corrected dozer task lifecycle to the Zero Hour variant. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp | Applies the corrected worker task lifecycle to the Zero Hour variant. |
| Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Dispatches disabled-state edges to behavior modules so builder-specific handling runs in the owning module. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Removes direct dozer handling after responsibility moves into builder behavior modules. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Builder disabled-state edge] --> B{Becoming disabled?}
B -- Yes --> C{Active task?}
C -- No --> D[No task cancellation]
C -- Yes --> E{Temporary resumable disable type?}
E -- Yes --> F[Snapshot task and target]
E -- No --> G[Cancel without remembering]
F --> H[Cancel active task]
G --> H
B -- No --> I{Remembered task type}
I -- Build --> J{Target still under construction?}
J -- Yes --> K[Resume build]
J -- No --> L[Do not resume completed build]
I -- Repair or Fortify --> M{Target still exists?}
M -- Yes --> N[Resume task]
M -- No --> O[Discard stale task]
K --> P[Clear remembered task]
L --> P
N --> P
O --> P
Reviews (13): Last reviewed commit: "chore: Move previous-task methods from t..." | 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.
There was a problem hiding this comment.
Has been a while I looked at this but it would be good to make enter and exit states symmetric and intuitive.
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. |
|
|
||
| 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.
2f69024 to
506f141
Compare
| Bool attemptToResumeTask = isDisabledByType(DISABLED_EMP) || | ||
| isDisabledByType(DISABLED_HACKED) || | ||
| isDisabledByType(DISABLED_SUBDUED) || | ||
| isDisabledByType(DISABLED_UNDERPOWERED); |
There was a problem hiding this comment.
Are these 4 complete?
Maybe do the checklist by exclusion instead?
Bool attemptToResumeTask = !isDisabledByType(DISABLED_HELD) && ... ;
There was a problem hiding this comment.
Yes, this mirrors the conditions in Object::setDisabledUntil and Object::clearDisabled.
There was a problem hiding this comment.
Ok this link is rather unintuitive. Can we perhaps consolidate the conditions across the 3 places so that they are unlikely to go out of sync?
There was a problem hiding this comment.
Would it make more sense to do such refactors to unrelated logic in a subsequent change?
There was a problem hiding this comment.
Yes if it is not forgotten. It's a side quest spawned from this.
| virtual void cancelAllTasks() = 0; ///< cancel all tasks from the queue, if it's the current task the dozer will stop working on it | ||
| virtual void setPreviousTask(DozerTask task) = 0; ///< set the previous task | ||
| virtual void resumePreviousTask() = 0; ///< resume the previous task if there was one | ||
| virtual void clearPreviousTask() = 0; ///< clear the previous task |
There was a problem hiding this comment.
Does it need to be virtual function? If it is only called internally in the implementation, then it can be implementation local. Same for the other Task functions here.
There was a problem hiding this comment.
Question still stands. Why are they virtual functions?
There was a problem hiding this comment.
They are virtual simply because it follows the existing pattern and allows WorkerAIUpdate to override them. It helps convey that it is shared functionality. I can make them local if that's preferred?
There was a problem hiding this comment.
So the question that needs to be asked where a third party is allowed to call these functions at any time. When part of DozerAIInterface, then they are public and meant to be to use for anybody. If the function are for internal use, then they need to move out of the interface and into interface implementations, either private or protected, and if they need to be overridable, then they can be virtual too.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3886b38525
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // task actions | ||
| virtual void newTask( DozerTask task, Object* target ) override; ///< set a desire to do the requrested task | ||
| virtual void cancelTask( DozerTask task ) override; ///< cancel this task from the queue, if it's the current task the dozer will stop working on it | ||
| virtual void cancelTask( DozerTask task, Bool rememberTask = false ) override; ///< cancel this task from the queue, if it's the current task the dozer will stop working on it. Can remember the cancelled task for resumption (e.g. after the unit is disabled). |
There was a problem hiding this comment.
The example in parentheses is ambiguous. It can be read as if the task is resumed after a unit is disabled. Better just omit the example.
There was a problem hiding this comment.
That's how it was supposed to be read, but okay.
There was a problem hiding this comment.
A unit will resume task after it is re-enabled
558b3cf to
a87efc3
Compare
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