Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
68743ff
bugfix: Fix issue where builders could resume completed tasks after b…
Stubbjax Jun 14, 2026
8b4cd2e
bugfix: Only save the previous task for resumption when explicitly di…
Stubbjax Jun 14, 2026
0c66471
refactor: Move fix to the task resumption method
Stubbjax Jun 14, 2026
6cf851a
docs: Remove superfluous comments
Stubbjax Jun 14, 2026
dc11862
refactor: Check construction status bit instead of construction percent
Stubbjax Jun 14, 2026
122edf0
tweak: Still clear previous task if the resumed build fails
Stubbjax Jun 14, 2026
04e0119
refactor: Optimise target acquisition
Stubbjax Jun 14, 2026
cbb639a
fix: Reverse condition
Stubbjax Jun 14, 2026
2145c6a
bugfix: Apply correct version condition
Stubbjax Aug 23, 2026
cb68740
tweak: Support resumption of repair tasks
Stubbjax Aug 23, 2026
506f141
refactor: Streamline previous task assignment
Stubbjax Aug 23, 2026
5f41b3b
refactor: Push down onDisabledEdge behaviour to DozerAIUpdate and Wor…
Stubbjax Sep 1, 2026
155c6d4
refactor: Remember task as part of cancellation
Stubbjax Sep 1, 2026
3886b38
refactor: Consolidate previous task clearing logic
Stubbjax Sep 1, 2026
6bc5dac
bugfix: Also resume FORTIFY tasks
Stubbjax Sep 1, 2026
3c36a85
bugfix: Cancelling all tasks now clears the previous task
Stubbjax Sep 1, 2026
a87efc3
docs: Explain new method argument
Stubbjax Sep 3, 2026
47ccf55
chore: Move previous-task methods from the interface to the implementers
Stubbjax Sep 3, 2026
d85087d
chore: Clear previous task if we're not remembering
Stubbjax Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Generals/Code/GameEngine/Include/Common/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ class ObjectModule : public Module
// virtual destructor prototype defined by MemoryPoolObject

virtual void onCapture( Player *oldOwner, Player *newOwner ) { }
virtual void onDisabledEdge( Bool nowDisabled ) { }

protected:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,8 @@ class DozerAIInterface

// 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. Can remember the cancelled task for resumption.
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 resumePreviousTask() = 0; ///< resume the previous task if there was one

// internal methods to manage behavior from within the dozer state machine
virtual void internalTaskComplete( DozerTask task ) = 0; ///< set a dozer task as successfully completed
Expand Down Expand Up @@ -211,6 +210,7 @@ class DozerAIUpdate : public AIUpdateInterface, public DozerAIInterface
virtual const DozerAIInterface* getDozerAIInterface() const override {return this;}

virtual void onDelete() override;
virtual void onDisabledEdge(Bool nowDisabled) override;

//
// module data methods ... this is LAME, multiple inheritance off an interface with replicated
Expand Down Expand Up @@ -240,9 +240,8 @@ class DozerAIUpdate : public AIUpdateInterface, public DozerAIInterface

// 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
virtual void cancelAllTasks() override; ///< cancel all tasks from the queue, if it's the current task the dozer will stop working on it
virtual void resumePreviousTask() override; ///< resume the previous task if there was one

// internal methods to manage behavior from within the dozer state machine
virtual void internalTaskComplete( DozerTask task ) override; ///< set a dozer task as successfully completed
Expand Down Expand Up @@ -278,6 +277,10 @@ class DozerAIUpdate : public AIUpdateInterface, public DozerAIInterface
virtual void privateRepair( Object *obj, CommandSourceType cmdSource ) override; ///< repair the target
virtual void privateResumeConstruction( Object *obj, CommandSourceType cmdSource ) override; ///< resume construction on obj

virtual void setPreviousTask(DozerTask task); ///< set the previous task
virtual void resumePreviousTask(); ///< resume the previous task if there was one
virtual void clearPreviousTask(); ///< clear the previous task

struct DozerTaskInfo
{
DozerTaskInfo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class WorkerAIUpdate : public AIUpdateInterface, public DozerAIInterface, public

// Dozer side
virtual void onDelete() override;
virtual void onDisabledEdge(Bool nowDisabled) override;

virtual Real getRepairHealthPerSecond() const override; ///< get health to repair per second
virtual Real getBoredTime() const override; ///< how long till we're bored
Expand All @@ -154,9 +155,8 @@ class WorkerAIUpdate : public AIUpdateInterface, public DozerAIInterface, public

// 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.
virtual void cancelAllTasks() override; ///< cancel all tasks from the queue, if it's the current task the dozer will stop working on it
virtual void resumePreviousTask() override; ///< resume the previous task if there was one

// internal methods to manage behavior from within the dozer state machine
virtual void internalTaskComplete( DozerTask task ) override; ///< set a dozer task as successfully completed
Expand Down Expand Up @@ -264,6 +264,10 @@ class WorkerAIUpdate : public AIUpdateInterface, public DozerAIInterface, public
virtual void privateDock( Object *obj, CommandSourceType cmdSource ) override;
virtual void privateIdle(CommandSourceType cmdSource) override; ///< Enter idle state.

virtual void setPreviousTask(DozerTask task); ///< set the previous task
virtual void resumePreviousTask(); ///< resume the previous task if there was one
virtual void clearPreviousTask(); ///< clear the previous task

private:

void createMachines(); ///< create our behavior machines we need
Expand Down
4 changes: 4 additions & 0 deletions Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3415,6 +3415,10 @@ void Object::friend_adjustPowerForPlayer( Bool incoming )
//-------------------------------------------------------------------------------------------------
void Object::onDisabledEdge(Bool becomingDisabled)
{
// rip through the behavior modules and call the onDisabledEdge for any modules that care
for( BehaviorModule **module = m_behaviors; *module; ++module )
(*module)->onDisabledEdge( becomingDisabled );

Player* controller = getControllingPlayer();
// can be called during game teardown, thus controller can be null
if (controller)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2045,8 +2045,12 @@ void DozerAIUpdate::newTask( DozerTask task, Object *target )
* re-evaluate what it wants to do if it was working on the task being
* cancelled */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::cancelTask( DozerTask task )
void DozerAIUpdate::cancelTask( DozerTask task, Bool rememberTask )
{
if (rememberTask)
setPreviousTask(task);
Comment thread
xezon marked this conversation as resolved.
else
clearPreviousTask();

// clear the order
internalCancelTask( task );
Expand All @@ -2061,20 +2065,51 @@ void DozerAIUpdate::cancelAllTasks()
for (UnsignedInt task = DOZER_TASK_FIRST; task < DOZER_NUM_TASKS; ++task)
internalCancelTask((DozerTask)task);

clearPreviousTask();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Better move it up so it is consistent with the call order in DozerAIUpdate::cancelTask


m_dozerMachine->resetToDefaultState();
}

//-------------------------------------------------------------------------------------------------
/** Set the previous task so that we may return to it if we become temporarily incapacitated */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::setPreviousTask(DozerTask task)
{
if (task == DOZER_TASK_INVALID)
return;

m_previousTask = task;
m_previousTaskInfo = m_task[task];
}

//-------------------------------------------------------------------------------------------------
/** Attempt to resume the previous task */
//-------------------------------------------------------------------------------------------------
void DozerAIUpdate::resumePreviousTask()
{
if (m_previousTask != DOZER_TASK_INVALID)
if (m_previousTask == DOZER_TASK_INVALID)
return;

if (m_previousTask == DOZER_TASK_BUILD)
{
Object* target = TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID);
if (target && target->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION))
newTask(m_previousTask, target);
}
else if (m_previousTask == DOZER_TASK_REPAIR || m_previousTask == DOZER_TASK_FORTIFY)
{
newTask(m_previousTask, TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID));
m_previousTask = DOZER_TASK_INVALID;
m_previousTaskInfo = DozerTaskInfo();
Object* target = TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID);
if (target)
newTask(m_previousTask, target);
}

clearPreviousTask();
}

void DozerAIUpdate::clearPreviousTask()
{
m_previousTask = DOZER_TASK_INVALID;
m_previousTaskInfo = DozerTaskInfo();
}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -2133,8 +2168,7 @@ void DozerAIUpdate::internalTaskComplete( DozerTask task )
m_task[ task ].m_targetObjectID = INVALID_ID;
m_task[ task ].m_taskOrderFrame = 0;

m_previousTask = DOZER_TASK_INVALID;
m_previousTaskInfo = DozerTaskInfo();
clearPreviousTask();

// remove dock point info for this task
for( Int i = 0; i < DOZER_NUM_DOCK_POINTS; i++ )
Expand All @@ -2158,9 +2192,6 @@ void DozerAIUpdate::internalCancelTask( DozerTask task )
// call the single method that gets called for completing and canceling tasks
internalTaskCompleteOrCancelled( task );

m_previousTask = task;
m_previousTaskInfo = m_task[task];

// remove the info for this task
m_task[ task ].m_targetObjectID = INVALID_ID;
m_task[ task ].m_taskOrderFrame = 0;
Expand Down Expand Up @@ -2297,6 +2328,32 @@ void DozerAIUpdate::onDelete()
}
}

void DozerAIUpdate::onDisabledEdge(Bool nowDisabled)
{
if (nowDisabled)
{
// Have to say goodbye to the thing we might be building or repairing so someone else can do it.
if (getCurrentTask() != DOZER_TASK_INVALID)
{
// TheSuperHackers @info We want to explicitly define what types to resume from as some types
// are undesirable (e.g. DISABLED_HELD via entering/exiting a container).
Bool rememberTask = getObject()->isDisabledByType(DISABLED_EMP) ||
getObject()->isDisabledByType(DISABLED_HACKED) ||
getObject()->isDisabledByType(DISABLED_SUBDUED) ||
getObject()->isDisabledByType(DISABLED_UNDERPOWERED);

cancelTask(getCurrentTask(), rememberTask);
}
}
else
{
#if !RETAIL_COMPATIBLE_CRC
// TheSuperHackers @bugfix Stubbjax 17/11/2025 Resume previous task when re-enabled.
resumePreviousTask();
#endif
}
}

//-------------------------------------------------------------------------------------------------
/** Get the most recently issued task */
//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -2511,7 +2568,7 @@ void DozerAIUpdate::xfer( Xfer *xfer )
xfer->xferSnapshot(m_dozerMachine);
xfer->xferUser(&m_currentTask, sizeof(m_currentTask));

if (currentVersion >= 2)
if (version >= 2)
{
xfer->xferUser(&m_previousTask, sizeof(m_previousTask));
xfer->xferUser(&m_previousTaskInfo, sizeof(m_previousTaskInfo));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,12 @@ void WorkerAIUpdate::newTask( DozerTask task, Object* target )
* re-evaluate what it wants to do if it was working on the task being
* cancelled */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::cancelTask( DozerTask task )
void WorkerAIUpdate::cancelTask( DozerTask task, Bool rememberTask )
{
if (rememberTask)
setPreviousTask(task);
else
clearPreviousTask();

// clear the order
internalCancelTask( task );
Expand All @@ -701,20 +705,51 @@ void WorkerAIUpdate::cancelAllTasks()
for (UnsignedInt task = DOZER_TASK_FIRST; task < DOZER_NUM_TASKS; ++task)
internalCancelTask((DozerTask)task);

clearPreviousTask();

m_dozerMachine->resetToDefaultState();
}

//-------------------------------------------------------------------------------------------------
/** Set the previous task so that we may return to it if we become temporarily incapacitated */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::setPreviousTask(DozerTask task)
{
if (task == DOZER_TASK_INVALID)
return;

m_previousTask = task;
m_previousTaskInfo = m_task[task];
}

//-------------------------------------------------------------------------------------------------
/** Attempt to resume the previous task */
//-------------------------------------------------------------------------------------------------
void WorkerAIUpdate::resumePreviousTask()
{
if (m_previousTask != DOZER_TASK_INVALID)
if (m_previousTask == DOZER_TASK_INVALID)
return;

if (m_previousTask == DOZER_TASK_BUILD)
{
Object* target = TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID);
if (target && target->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION))
newTask(m_previousTask, target);
}
else if (m_previousTask == DOZER_TASK_REPAIR || m_previousTask == DOZER_TASK_FORTIFY)
{
newTask(m_previousTask, TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID));
m_previousTask = DOZER_TASK_INVALID;
m_previousTaskInfo = DozerTaskInfo();
Object* target = TheGameLogic->findObjectByID(m_previousTaskInfo.m_targetObjectID);
if (target)
newTask(m_previousTask, target);
}

clearPreviousTask();
}

void WorkerAIUpdate::clearPreviousTask()
{
m_previousTask = DOZER_TASK_INVALID;
m_previousTaskInfo = DozerTaskInfo();
}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -773,8 +808,7 @@ void WorkerAIUpdate::internalTaskComplete( DozerTask task )
m_task[ task ].m_targetObjectID = INVALID_ID;
m_task[ task ].m_taskOrderFrame = 0;

m_previousTask = DOZER_TASK_INVALID;
m_previousTaskInfo = DozerTaskInfo();
clearPreviousTask();

// remove dock point info for this task
for( Int i = 0; i < DOZER_NUM_DOCK_POINTS; i++ )
Expand All @@ -798,9 +832,6 @@ void WorkerAIUpdate::internalCancelTask( DozerTask task )
// call the single method that gets called for completing and canceling tasks
internalTaskCompleteOrCancelled( task );

m_previousTask = task;
m_previousTaskInfo = m_task[task];

// remove the info for this task
m_task[ task ].m_targetObjectID = INVALID_ID;
m_task[ task ].m_taskOrderFrame = 0;
Expand Down Expand Up @@ -925,6 +956,32 @@ void WorkerAIUpdate::onDelete()
}
}

void WorkerAIUpdate::onDisabledEdge(Bool nowDisabled)
{
if (nowDisabled)
{
// Have to say goodbye to the thing we might be building or repairing so someone else can do it.
if (getCurrentTask() != DOZER_TASK_INVALID)
{
// TheSuperHackers @info We want to explicitly define what types to resume from as some types
// are undesirable (e.g. DISABLED_HELD via entering/exiting a container).
Bool rememberTask = getObject()->isDisabledByType(DISABLED_EMP) ||
getObject()->isDisabledByType(DISABLED_HACKED) ||
getObject()->isDisabledByType(DISABLED_SUBDUED) ||
getObject()->isDisabledByType(DISABLED_UNDERPOWERED);

cancelTask(getCurrentTask(), rememberTask);
}
}
else
{
#if !RETAIL_COMPATIBLE_CRC
// TheSuperHackers @bugfix Stubbjax 17/11/2025 Resume previous task when re-enabled.
resumePreviousTask();
#endif
}
}

//-------------------------------------------------------------------------------------------------
/** Get the most recently issued task */
//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1463,7 +1520,7 @@ void WorkerAIUpdate::xfer( Xfer *xfer )
xfer->xferSnapshot(m_dozerMachine);
xfer->xferUser(&m_currentTask, sizeof(m_currentTask));

if (currentVersion >= 2)
if (version >= 2)
{
xfer->xferUser(&m_previousTask, sizeof(m_previousTask));
xfer->xferUser(&m_previousTaskInfo, sizeof(m_previousTaskInfo));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,8 @@ class DozerAIInterface

// task actions
virtual void newTask( DozerTask task, Object *target ) = 0; ///< set a desire to do the requested 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. Can remember the cancelled task for resumption.
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 resumePreviousTask() = 0; ///< resume the previous task if there was one

// internal methods to manage behavior from within the dozer state machine
virtual void internalTaskComplete( DozerTask task ) = 0; ///< set a dozer task as successfully completed
Expand Down Expand Up @@ -211,6 +210,7 @@ class DozerAIUpdate : public AIUpdateInterface, public DozerAIInterface
virtual const DozerAIInterface* getDozerAIInterface() const override {return this;}

virtual void onDelete() override;
virtual void onDisabledEdge(Bool nowDisabled) override;

//
// module data methods ... this is LAME, multiple inheritance off an interface with replicated
Expand Down Expand Up @@ -240,9 +240,8 @@ class DozerAIUpdate : public AIUpdateInterface, public DozerAIInterface

// task actions
virtual void newTask( DozerTask task, Object *target ) override; ///< set a desire to do the requested 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
virtual void cancelAllTasks() override; ///< cancel all tasks from the queue, if it's the current task the dozer will stop working on it
virtual void resumePreviousTask() override; ///< resume the previous task if there was one

// internal methods to manage behavior from within the dozer state machine
virtual void internalTaskComplete( DozerTask task ) override; ///< set a dozer task as successfully completed
Expand Down Expand Up @@ -278,6 +277,10 @@ class DozerAIUpdate : public AIUpdateInterface, public DozerAIInterface
virtual void privateRepair( Object *obj, CommandSourceType cmdSource ) override; ///< repair the target
virtual void privateResumeConstruction( Object *obj, CommandSourceType cmdSource ) override; ///< resume construction on obj

virtual void setPreviousTask(DozerTask task); ///< set the previous task
virtual void resumePreviousTask(); ///< resume the previous task if there was one
virtual void clearPreviousTask(); ///< clear the previous task

struct DozerTaskInfo
{
DozerTaskInfo()
Expand Down
Loading
Loading