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 16 commits into
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp | Implements setPreviousTask, clearPreviousTask, onDisabledEdge; refactors resumePreviousTask to skip resumed builds whose target has already completed; fixes currentVersion → version in xfer. |
| Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp | Parallel implementation to DozerAIUpdate.cpp for the Worker unit, including the xfer variable name fix. |
| GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DozerAIUpdate.h | Interface updated to match Generals version; clearPreviousTask declaration uses 2-space indentation while surrounding declarations use tabs. |
| GeneralsMD/Code/GameEngine/Include/GameLogic/Module/WorkerAIUpdate.h | Same as DozerAIUpdate.h — clearPreviousTask override declaration uses 2-space indentation instead of tabs. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Removes the now-redundant dozerAI-specific block from onDisabledEdge; the logic has been moved into the modules themselves and is invoked via the existing behavior-module loop. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp | Matches Generals implementation; clearPreviousTask function body uses 2-space indentation instead of tabs, inconsistent with surrounding code. xfer variable name fix applied correctly. |
Sequence Diagram
sequenceDiagram
participant Obj as Object
participant Mod as BehaviorModule loop
participant DAI as DozerAIUpdate / WorkerAIUpdate
Note over Obj: Builder gets EMP'd / hacked / underpowered
Obj->>Mod: onDisabledEdge(true)
Mod->>DAI: onDisabledEdge(true)
DAI->>DAI: "isDisabledByType(EMP|HACKED|SUBDUED|UNDERPOWERED)?"
alt "rememberTask = true"
DAI->>DAI: "setPreviousTask(currentTask)<br/>copies m_task[task] → m_previousTaskInfo"
DAI->>DAI: "internalCancelTask(task)<br/>clears m_task[task]"
else "rememberTask = false (e.g. DISABLED_HELD)"
DAI->>DAI: "internalCancelTask(task)<br/>no memory saved"
end
Note over Obj: Disable expires (re-enabled)
Obj->>Mod: onDisabledEdge(false)
Mod->>DAI: onDisabledEdge(false)
DAI->>DAI: resumePreviousTask()
alt "previousTask == BUILD && target still UNDER_CONSTRUCTION"
DAI->>DAI: newTask(BUILD, target) — resume
else "previousTask == BUILD && target complete"
DAI->>DAI: clearPreviousTask() — stay idle (bug fix)
else "previousTask == REPAIR or FORTIFY && target exists"
DAI->>DAI: "newTask(REPAIR|FORTIFY, target) — resume"
else target gone or no previousTask
DAI->>DAI: clearPreviousTask() — stay idle
end
Reviews (10): Last reviewed commit: "bugfix: Cancelling all tasks now clears ..." | 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.
💡 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 ) = 0; ///< set a desire to do the requrested task | ||
| virtual void cancelTask( DozerTask task ) = 0; ///< 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 ) = 0; ///< cancel this task from the queue, if it's the current task the dozer will stop working on it |
There was a problem hiding this comment.
The EA comment can be expanded that if rememberTask is set that it will resume the task when enabled again.
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