Skip to content

Commit 4c7ed68

Browse files
FlamefireFlow86
authored andcommitted
Fix waiting attackers becoming stuck when target flag becomes unreachable.
When the defender kills the current attacker (at the flag) `FindAttackerNearBuilding` is used to get the next one for the defender to fight. This takes the closest attacker that is ready, i.e. waiting around the flag and calls `AttackDefenderAtFlag` which usually causes the attacker to start walking to the flag. However if there is no path to the flag (anymore, e.g. after destroying a road) the attacker won't do anything and the function returns `false`. As this isn't checked the defender will keep waiting for it. This can lead to use-after-free if the attacker is destroyed (defeated in free fight, went back home, ...) as the defender will then have a dangling pointer to it. Fixes #1863
1 parent e9947dc commit 4c7ed68

2 files changed

Lines changed: 47 additions & 43 deletions

File tree

libs/s25main/buildings/nobBaseMilitary.cpp

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -165,29 +165,28 @@ nofAttacker* nobBaseMilitary::FindAggressor(nofAggressiveDefender& defender)
165165

166166
MapPoint nobBaseMilitary::FindAnAttackerPlace(unsigned short& ret_radius, const nofAttacker& soldier)
167167
{
168-
const MapPoint flagPos = world->GetNeighbour(pos, Direction::SouthEast);
168+
const MapPoint flagPos = GetFlagPos();
169+
const MapPoint soldierPos = soldier.GetPos();
169170

170-
// Diesen Flaggenplatz nur nehmen, wenn es auch nich gerade eingenommen wird, sonst gibts Deserteure!
171-
// Eigenommen werden können natürlich nur richtige Militärgebäude
172-
bool capturing =
173-
(BuildingProperties::IsMilitary(bldType_)) ? (static_cast<nobMilitary*>(this)->IsBeingCaptured()) : false;
171+
// Only consider the flag position for fighting if the building isn't currently being captured.
172+
// Only "real" military buildings can be captured
173+
const bool capturing =
174+
BuildingProperties::IsMilitary(bldType_) && static_cast<nobMilitary*>(this)->IsBeingCaptured();
174175

175-
if(!capturing && world->IsValidPointForFighting(flagPos, soldier, false))
176+
// Also check if we can reach this.
177+
// If not, still consider the other points as the flag could become reachable by then.
178+
if(!capturing && world->IsValidPointForFighting(flagPos, soldier, false)
179+
&& (soldierPos == flagPos || world->FindHumanPath(soldierPos, flagPos) != boost::none))
176180
{
177181
ret_radius = 0;
178182
return flagPos;
179183
}
180184

181-
const MapPoint soldierPos = soldier.GetPos();
182-
// Get points AROUND the flag. Never AT the flag
183-
const auto nodes = world->GetPointsInRadius(flagPos, 3, ReturnMapPointWithRadius{});
184-
185-
// Weg zu allen möglichen Punkten berechnen und den mit den kürzesten Weg nehmen
186-
// Die bisher kürzeste gefundene Länge
185+
// Check all points around the flag and take shortest
187186
unsigned min_length = std::numeric_limits<unsigned>::max();
188187
MapPoint minPt = MapPoint::Invalid();
189188
ret_radius = 100;
190-
for(const auto& node : nodes)
189+
for(const auto& node : world->GetPointsInRadius(flagPos, 3, ReturnMapPointWithRadius{}))
191190
{
192191
// We found a point with a better radius
193192
if(node.second > ret_radius)
@@ -196,18 +195,18 @@ MapPoint nobBaseMilitary::FindAnAttackerPlace(unsigned short& ret_radius, const
196195
if(!world->ValidWaitingAroundBuildingPoint(node.first, pos))
197196
continue;
198197

199-
// Derselbe Punkt? Dann können wir gleich abbrechen, finden ja sowieso keinen kürzeren Weg mehr
198+
// If this is where the soldier currently is, it is already the shortest possible distance
200199
if(soldierPos == node.first)
201200
{
202201
ret_radius = node.second;
203202
return node.first;
204203
}
205204

206205
unsigned length = 0;
207-
// Gültiger Weg gefunden
206+
// Is there a path at all?
208207
if(world->FindHumanPath(soldierPos, node.first, 100, false, &length))
209208
{
210-
// Kürzer als bisher kürzester Weg? --> Dann nehmen wir diesen Punkt (vorerst)
209+
// Take if shorter
211210
if(length < min_length)
212211
{
213212
minPt = node.first;
@@ -252,30 +251,30 @@ bool nobBaseMilitary::CallDefender(nofAttacker& attacker)
252251

253252
nofAttacker* nobBaseMilitary::FindAttackerNearBuilding()
254253
{
255-
// Alle angreifenden Soldaten durchgehen
256-
// Den Soldaten, der am nächsten dran steht, nehmen
257254
nofAttacker* best_attacker = nullptr;
258-
unsigned best_radius = 0xFFFFFFFF;
259-
260-
for(auto* aggressor : aggressors)
255+
do
261256
{
262-
// Ist der Soldat überhaupt bereit zum Kämpfen (also wartet er um die Flagge herum oder rückt er nach)?
263-
if(aggressor->IsAttackerReady())
257+
// Call the closest attacker to fight the defender
258+
best_attacker = nullptr;
259+
unsigned best_radius = 0xFFFFFFFF;
260+
for(auto* aggressor : aggressors)
264261
{
265-
// Besser als bisher bester?
266-
if(aggressor->GetRadius() < best_radius || !best_attacker)
262+
// Only consider those waiting around the flag, otherwise the defender could go/stay inside
263+
if(aggressor->IsAttackerReady())
267264
{
268-
best_attacker = aggressor;
269-
best_radius = best_attacker->GetRadius();
265+
if(!best_attacker || aggressor->GetRadius() < best_radius)
266+
{
267+
best_attacker = aggressor;
268+
best_radius = aggressor->GetRadius();
269+
}
270270
}
271271
}
272-
}
273-
274-
if(best_attacker)
275-
best_attacker->AttackDefenderAtFlag();
276-
277-
// und ihn zurückgeben, wenns keine gibt, natürlich 0
278-
return best_attacker;
272+
// Return the attacker if found and he starts attacking, else try again
273+
// The current one will be removed from aggressors or not be ready again
274+
if(best_attacker && best_attacker->AttackDefenderAtFlag())
275+
return best_attacker;
276+
} while(best_attacker); // Stop if we didn't found any attacker at all
277+
return nullptr;
279278
}
280279

281280
void nobBaseMilitary::CheckArrestedAttackers()

libs/s25main/figures/nofAttacker.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -605,18 +605,23 @@ bool nofAttacker::AttackDefenderAtFlag()
605605
{
606606
// Walk to flag if possible
607607
const auto dir = world->FindHumanPath(pos, attacked_goal->GetFlagPos(), 3, true);
608-
if(!dir)
609-
return false;
610-
611-
const bool waiting_around_building = (state == SoldierState::AttackingWaitingAroundBuilding);
612-
state = SoldierState::AttackingAttackingFlag;
608+
if(dir)
609+
{
610+
const bool waiting_around_building = (state == SoldierState::AttackingWaitingAroundBuilding);
611+
state = SoldierState::AttackingAttackingFlag;
613612

614-
if(waiting_around_building)
613+
if(waiting_around_building)
614+
{
615+
StartWalking(*dir);
616+
attacked_goal->SendSuccessor(pos, radius);
617+
}
618+
return true;
619+
} else
615620
{
616-
StartWalking(*dir);
617-
attacked_goal->SendSuccessor(pos, radius);
621+
state = nofActiveSoldier::SoldierState::AttackingWalkingToGoal;
622+
MissAttackingWalk();
623+
return false;
618624
}
619-
return true;
620625
}
621626

622627
void nofAttacker::AttackFlag()

0 commit comments

Comments
 (0)