Skip to content

Commit

Permalink
Fix stuck soldiers when free fight gets aborted while waiting
Browse files Browse the repository at this point in the history
When one soldier involved in an upcoming free fight is waiting for the
other and that aborts the free fight for any reason, e.g. when the spot
becomes unreachable, the waiting soldier changes state but doesn't start
moving and as he isn't moving already there is no event queued that
would make it handle the new (moving) state.
This leads to it being stuck there forever and also crashes the draw
code which doesn't expect a supposedly walking soldier not having a walk
event.

Split the `AbortFreeFight` method in 2:
- virtual method to determine the next state
- base method to reset the free fight state for both soldiers and let
  them continue

For the aborting soldier effectively nothing is changed.
The other soldier gets a simulated walk event if he was waiting to
handle the new state.

As the 2 soldiers might get involved in another fight with each other
both their "fight states" are cleaned up first to establish a valid
state before the other soldier gets triggered.
  • Loading branch information
Flamefire committed Oct 13, 2024
1 parent 109147f commit 7d400f6
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 28 deletions.
42 changes: 26 additions & 16 deletions libs/s25main/figures/nofActiveSoldier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,8 @@ void nofActiveSoldier::IncreaseRank()

void nofActiveSoldier::MeetingEnemy()
{
// Enemy vanished?
if(!enemy)
{
AbortFreeFight();
Walked();
return;
}
// At this point we must still have an enemy which we were walking towards
RTTR_Assert(enemy);

// Reached the fighting place?
if(GetPos() == fightSpot_)
Expand All @@ -288,14 +283,15 @@ void nofActiveSoldier::MeetingEnemy()
FightingStarted();
} else
{
// Is fighting point still valid (there could e.g. be another fight already) and enemy on the way?
if(world->IsValidPointForFighting(pos, *this, false) && enemy->GetState() == SoldierState::MeetEnemy)
// Is fighting point still valid (there could e.g. be another fight already)
if(world->IsValidPointForFighting(pos, *this, false))
{
// Enemy should be on the way, so wait here
RTTR_Assert(enemy->enemy == this);
RTTR_Assert(enemy->GetState() == SoldierState::MeetEnemy);
state = SoldierState::WaitingForFight;
} else
{
enemy->AbortFreeFight();
AbortFreeFight();
Walked();
}
Expand All @@ -309,7 +305,6 @@ void nofActiveSoldier::MeetingEnemy()
else
{
// No way from current location to fighting spot -> cancel fight
enemy->AbortFreeFight();
AbortFreeFight();
Walked();
}
Expand All @@ -318,16 +313,31 @@ void nofActiveSoldier::MeetingEnemy()

void nofActiveSoldier::AbortFreeFight()
{
enemy = nullptr;
const bool enemyWasWaiting = enemy && enemy->state == SoldierState::WaitingForFight;
auto* old_enemy = enemy;
// First clean up as much as possible: Reset enemies and set new state for both
if(enemy)
{
RTTR_Assert(enemy->enemy == this);
enemy->enemy = nullptr;
enemy = nullptr;
}
state = FreeFightAborted();
if(old_enemy)
old_enemy->state = old_enemy->FreeFightAborted();
// Now the (possibly) 2 soldiers are in a state where they could even engage in another fight with each other
if(enemyWasWaiting)
{
// Act as-if the enemy just arrived at his position so he'll start moving again if required
RTTR_Assert(!old_enemy->IsMoving());
old_enemy->Walked();
}
}

void nofActiveSoldier::InformTargetsAboutCancelling()
{
if(enemy)
{
enemy->AbortFreeFight();
enemy = nullptr;
}
AbortFreeFight();
}

void nofActiveSoldier::TakeHit()
Expand Down
5 changes: 4 additions & 1 deletion libs/s25main/figures/nofActiveSoldier.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,17 @@ class nofActiveSoldier : public nofSoldier
/// increase rank if possible
void IncreaseRank();
/// Notify that a free fight (somewhere at the map, not the one at the building flag) cannot be started
virtual void AbortFreeFight();
/// Return state that is able to handle the next walk event
virtual SoldierState FreeFightAborted() = 0;

private:
/// Soldier reached his military building
void GoalReached() override;

/// Get how much of the map/FoW the soldier discovers
unsigned GetVisualRange() const override;
/// Abort a free fight (somewhere at the map, not the one at the building flag) that cannot be started
void AbortFreeFight();

public:
nofActiveSoldier(MapPoint pos, unsigned char player, nobBaseMilitary& home, unsigned char rank,
Expand Down
6 changes: 2 additions & 4 deletions libs/s25main/figures/nofAggressiveDefender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,8 @@ void nofAggressiveDefender::CancelAtAttacker()
}
}

void nofAggressiveDefender::AbortFreeFight()
nofActiveSoldier::SoldierState nofAggressiveDefender::FreeFightAborted()
{
nofActiveSoldier::AbortFreeFight();
RTTR_Assert(state != SoldierState::WaitingForFight);
// Continue with normal walking towards our goal
state = SoldierState::AggressivedefendingWalkingToAggressor;
return SoldierState::AggressivedefendingWalkingToAggressor;
}
4 changes: 2 additions & 2 deletions libs/s25main/figures/nofAggressiveDefender.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class nofAggressiveDefender : public nofActiveSoldier

void CancelAtAttacker();

void AbortFreeFight() override;

protected:
SoldierState FreeFightAborted() override;

[[noreturn]] void HandleDerivedEvent(unsigned) override { throw std::logic_error("No events expected"); }

public:
Expand Down
5 changes: 2 additions & 3 deletions libs/s25main/figures/nofAttacker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -929,11 +929,10 @@ void nofAttacker::CancelSeaAttack()
Abrogate();
}

void nofAttacker::AbortFreeFight()
nofActiveSoldier::SoldierState nofAttacker::FreeFightAborted()
{
nofActiveSoldier::AbortFreeFight();
// Continue with normal walking towards our goal
state = SoldierState::AttackingWalkingToGoal;
return SoldierState::AttackingWalkingToGoal;
}

bool nofAttacker::CanStartFarAwayCapturing(const nobMilitary& dest) const
Expand Down
3 changes: 2 additions & 1 deletion libs/s25main/figures/nofAttacker.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ class nofAttacker : public nofActiveSoldier
/// Handle walking back to ship
void HandleState_SeaAttack_ReturnToShip();

void AbortFreeFight() override;
protected:
SoldierState FreeFightAborted() override;

public:
/// Notify all targets (or those that target us) that we won't be coming anymore
Expand Down
5 changes: 4 additions & 1 deletion libs/s25main/figures/nofDefender.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ class nofDefender : public nofActiveSoldier
void Walked() override;

/// Hand back control to derived class after a fight of nofActiveSoldier, not possible for defenders
[[noreturn]] void AbortFreeFight() override { throw std::logic_error("Should not participate in free fights"); }
[[noreturn]] SoldierState FreeFightAborted() override
{
throw std::logic_error("Should not participate in free fights");
}

protected:
void HandleDerivedEvent [[noreturn]] (unsigned) override { throw std::logic_error("No events expected"); }
Expand Down

0 comments on commit 7d400f6

Please sign in to comment.