Skip to content

Prevent old update logic after state change in AIMoveToState::update() & AIFollowWaypointPathState::update() #2425

@diqezit

Description

@diqezit

Prerequisites

  • I have searched for similar issues and confirmed this is not a duplicate

Game Version

  • Command & Conquer Generals
  • Command & Conquer Generals: Zero Hour
  • Other (please specify below)

Bug Description

Unit inside AIMoveToState::update() or AIFollowWaypointPathState::update() could change its own state, for example, switch to AI_ATTACK_MOVE_TO, but after that, the old code still continued to execute in the same frame.

It turned out that the unit had already switched, but the old logic still had time to finish processing, so it would start moving toward the target again, and sometimes it would even cause an unnecessary route recalculation.

This resulted in a garbage cycle: the unit changed its state, then the old code intervened again, then the new state requested the path again, and so on, resulting in unnecessary path requests.

The fix does a simple thing: after a command that changes the state, the function immediately exits and does not allow the old logic to continue working in the same frame.


Edit file GeneralsMD\Code\GameEngine\Source\GameLogic\AI\AIStates.cpp

in StateReturnType AIMoveToState::update()

	UnsignedInt adjustment = ai->getMoodMatrixActionAdjustment(MM_Action_Move);
	if (m_isMoveTo && (adjustment & MAA_Action_To_AttackMove))
	{
		ai->aiAttackMoveToPosition(&m_goalPosition, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI);
		return STATE_CONTINUE;
	}

in StateReturnType AIFollowWaypointPathState::update()

	UnsignedInt adjustment = ai->getMoodMatrixActionAdjustment(MM_Action_Move);
	if (m_isFollowWaypointPathState && (adjustment & MAA_Action_To_AttackMove))	{
		if (m_moveAsGroup) {
			ai->aiAttackFollowWaypointPathAsTeam(m_currentWaypoint, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI);
		}	else {
			ai->aiAttackFollowWaypointPath(m_currentWaypoint, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI);
		}
		return STATE_CONTINUE;
	}

UPD.

In addition so that in the AIUpdateInterface::privateAttackMoveToPosition function in
File GeneralsGameCode\GeneralsMD\Code\GameEngine\Source\GameLogic\Object\Update\AIUpdate.cpp

This guard is needed so that the unit does not start the same attack again along the way.

If the unit is already moving in attack-move mode to the same point and receives the same command again, without the guard it will reset everything and start over.
returns if the coordinates are the same and the command

//----------------------------------------------------------------------------------------
/**
 * Attack move to the given location
 */
void AIUpdateInterface::privateAttackMoveToPosition( const Coord3D *pos, Int maxShotsToFire, CommandSourceType cmdSource )
{
	if (m_isAiDead || getObject()->isMobile() == FALSE)
		return;

	//Resetting the locomotor here was initially added for scripting purposes. It has been moved
	//to the responsibility of the script to reset the locomotor before moving. This is needed because
	//other systems (like the battle drone) change the locomotor based on what it's trying to do, and
	//doesn't want to get reset when ordered to move.
	//chooseLocomotorSet(LOCOMOTORSET_NORMAL);

	const AIStateMachine* sm = getStateMachine();
	if (sm && pos &&
			sm->getCurrentStateID() == AI_ATTACK_MOVE_TO &&
			m_lastCommandSource == cmdSource)
	{
		const Coord3D* goal = sm->getGoalPosition();
		if (goal && sqr(goal->x - pos->x) +
								sqr(goal->y - pos->y) +
								sqr(goal->z - pos->z) <= 1.0f)
		{
			return;
		}
	}

	getStateMachine()->clear();
	setGoalPositionClipped(pos, cmdSource);
	setLastCommandSource( cmdSource );
	getStateMachine()->setState( AI_ATTACK_MOVE_TO );

	// do this after setting it as the current state, as the max-shots-to-fire is reset in AttackState::onEnter()
	Weapon* weapon = getObject()->getCurrentWeapon();
	if (weapon)
		weapon->setMaxShotCount(maxShotsToFire);
}

It may not be entirely accurate, but I think those who need to understand will understand.

Reproduction Steps

Add debug information output to the necessary places

Ensure that units find enemies or another trigger that replaces the state - check the logs

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething is not working right, typically is user facing⚠️ TriageIssues requiring initial review and prioritization

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions