Skip to content

bugfix: Fix issue where builders could resume completed tasks after being disabled - #2793

Open
Stubbjax wants to merge 8 commits into
TheSuperHackers:mainfrom
Stubbjax:fix-previous-dozer-task
Open

bugfix: Fix issue where builders could resume completed tasks after being disabled#2793
Stubbjax wants to merge 8 commits into
TheSuperHackers:mainfrom
Stubbjax:fix-previous-dozer-task

Conversation

@Stubbjax

Copy link
Copy Markdown

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

@Stubbjax Stubbjax self-assigned this Jun 14, 2026
@Stubbjax Stubbjax added Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour labels Jun 14, 2026
@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a regression from #1870 where a builder (Dozer/Worker) could incorrectly resume a completed build task after recovering from a temporary disable (EMP, underpowered, hacked). The fix moves previous-task capture into setDisabledUntil via a new setPreviousTask method and adds an OBJECT_STATUS_UNDER_CONSTRUCTION guard in resumePreviousTask so that a finished building is never re-targeted.

  • New setPreviousTask method is called in setDisabledUntil before the disable flag is set, correctly capturing task info while m_task[task] is still populated; a DOZER_TASK_INVALID guard prevents the out-of-bounds array access that was flagged in the prior review.
  • resumePreviousTask restructured to only call newTask for DOZER_TASK_BUILD when the target is still under construction, clearing m_previousTask unconditionally afterward; repair and fortify tasks still do not resume after re-enabling (previously flagged regression).
  • internalCancelTask cleaned up by removing the now-redundant previous-task save lines, since capture responsibility has moved to setDisabledUntil.

Confidence Score: 4/5

Safe to merge for the build-task fix specifically, but the repair and fortify task paths still do not resume after an EMP/underpowered disable, leaving builders silently idle — a pre-existing regression flagged in the prior review that this PR does not yet address.

The build-task completion guard is correct and the DOZER_TASK_INVALID out-of-bounds issue is properly resolved. The resumePreviousTask refactor, however, only handles DOZER_TASK_BUILD; a builder disabled while repairing or fortifying will silently idle after recovery instead of resuming work. This defect was noted in the previous review and remains unresolved here.

Files Needing Attention: DozerAIUpdate.cpp and WorkerAIUpdate.cpp (both Generals/ and GeneralsMD/) — specifically the resumePreviousTask function, which needs to handle DOZER_TASK_REPAIR and DOZER_TASK_FORTIFY alongside DOZER_TASK_BUILD.

Important Files Changed

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]
Loading

Reviews (7): Last reviewed commit: "fix: Reverse condition" | Re-trigger Greptile

@xezon xezon added the ThisProject The issue was introduced by this project, or this task is specific to this project label Jun 14, 2026
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this perhaps go into DozerAIUpdate::resumePreviousTask and here it should be an assert?

@Stubbjax Stubbjax Jun 14, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Stubbjax
Stubbjax force-pushed the fix-previous-dozer-task branch from 657688e to a0fbee6 Compare June 14, 2026 15:59
@Stubbjax
Stubbjax force-pushed the fix-previous-dozer-task branch from de2f3f1 to bdf1ab1 Compare June 14, 2026 16:17

DozerAIInterface* dozerAI = getAI() ? getAI()->getDozerAIInterface() : nullptr;
if (dozerAI)
dozerAI->setPreviousTask(dozerAI->getCurrentTask());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit strange. Wasn't cancelTask supposed to take of this? For example from Object::onDisabledEdge.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Stubbjax
Stubbjax force-pushed the fix-previous-dozer-task branch from 74beb35 to a4ffdb5 Compare June 14, 2026 16:52
@xezon

This comment was marked as outdated.

@xezon

xezon commented Jun 29, 2026

Copy link
Copy Markdown

@Stubbjax Is this now fixed up?

@Stubbjax

Stubbjax commented Jul 1, 2026

Copy link
Copy Markdown
Author

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& target->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@xezon

xezon commented Jul 21, 2026

Copy link
Copy Markdown

This needs polishing.

@Stubbjax
Stubbjax force-pushed the fix-previous-dozer-task branch from ede3cb1 to 2f69024 Compare July 28, 2026 15:02
if (m_previousTask == DOZER_TASK_INVALID)
return;

if (m_previousTask == DOZER_TASK_BUILD)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker ThisProject The issue was introduced by this project, or this task is specific to this project ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants