• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 24084873464

07 Apr 2026 01:48PM UTC coverage: 50.359% (+0.02%) from 50.337%
24084873464

Pull #1910

github

web-flow
Merge f20cbf041 into e4146df45
Pull Request #1910: Fix wrongly shown soldiers & Refactor HQ start wares and inventory handling

197 of 409 new or added lines in 22 files covered. (48.17%)

99 existing lines in 25 files now uncovered.

23062 of 45795 relevant lines covered (50.36%)

41987.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.26
/libs/s25main/figures/nofAttacker.cpp
1
// Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "nofAttacker.h"
6
#include "EventManager.h"
7
#include "GamePlayer.h"
8
#include "GlobalGameSettings.h"
9
#include "SerializedGameData.h"
10
#include "addons/const_addons.h"
11
#include "buildings/nobHarborBuilding.h"
12
#include "buildings/nobMilitary.h"
13
#include "helpers/containerUtils.h"
14
#include "nofAggressiveDefender.h"
15
#include "nofDefender.h"
16
#include "nofPassiveSoldier.h"
17
#include "postSystem/PostMsgWithBuilding.h"
18
#include "random/Random.h"
19
#include "world/GameWorld.h"
20
#include "nodeObjs/noFighting.h"
21
#include "nodeObjs/noFlag.h"
22
#include "nodeObjs/noShip.h"
23
#include "gameData/BuildingProperties.h"
24

25
/// The time the attacker stands at the buildings flag before it starts blocking the road
26
/// Only used for AttackingWaitingfordefender
27
constexpr unsigned BLOCK_OFFSET = 10;
28

29
nofAttacker::nofAttacker(const nofPassiveSoldier& other, nobBaseMilitary& attacked_goal,
90✔
30
                         const nobHarborBuilding* const harbor)
90✔
31
    : nofActiveSoldier(other, harbor ? SoldierState::SeaattackingGoToHarbor : SoldierState::AttackingWalkingToGoal),
32
      attacked_goal(&attacked_goal), mayBeHunted(true),
33
      canPlayerSendAggDefender(world->GetNumPlayers(), SendDefender::Undecided), huntingDefender(nullptr), radius(0),
90✔
34
      blocking_event(nullptr), harborPos(harbor ? harbor->GetPos() : MapPoint::Invalid()), shipPos(MapPoint::Invalid()),
90✔
35
      ship_obj_id(0)
180✔
36
{
37
    attacked_goal.LinkAggressor(*this);
90✔
38
}
90✔
39

40
nofAttacker::~nofAttacker() = default;
180✔
41

42
void nofAttacker::Destroy()
23✔
43
{
44
    RTTR_Assert(!attacked_goal);
23✔
45
    RTTR_Assert(!ship_obj_id);
23✔
46
    RTTR_Assert(!huntingDefender);
23✔
47
    nofActiveSoldier::Destroy();
23✔
48

49
    /*unsigned char oplayer = (player == 0) ? 1 : 0;
50
    RTTR_Assert(!world->GetPlayer(oplayer).GetFirstWH()->Test(this));*/
51
}
23✔
52

53
void nofAttacker::Serialize(SerializedGameData& sgd) const
×
54
{
55
    nofActiveSoldier::Serialize(sgd);
×
56

57
    if(state != SoldierState::WalkingHome && state != SoldierState::FigureWork)
×
58
    {
59
        sgd.PushObject(attacked_goal);
×
60
        sgd.PushBool(mayBeHunted);
×
61
        helpers::pushContainer(sgd, canPlayerSendAggDefender);
×
62
        sgd.PushObject(huntingDefender, true);
×
63
        sgd.PushUnsignedShort(radius);
×
64

65
        if(state == SoldierState::AttackingWaitingForDefender)
×
66
            sgd.PushEvent(blocking_event);
×
67

68
        helpers::pushPoint(sgd, harborPos);
×
69
        helpers::pushPoint(sgd, shipPos);
×
70
        sgd.PushUnsignedInt(ship_obj_id);
×
71
    } else
72
    {
73
        RTTR_Assert(!attacked_goal);
×
74
        RTTR_Assert(!huntingDefender);
×
75
        RTTR_Assert(!blocking_event);
×
76
    }
77
}
×
78

79
nofAttacker::nofAttacker(SerializedGameData& sgd, const unsigned obj_id) : nofActiveSoldier(sgd, obj_id)
×
80
{
81
    if(state != SoldierState::WalkingHome && state != SoldierState::FigureWork)
×
82
    {
83
        attacked_goal = sgd.PopObject<nobBaseMilitary>();
×
84
        mayBeHunted = sgd.PopBool();
×
85
        sgd.PopContainer(canPlayerSendAggDefender);
×
86
        RTTR_Assert(canPlayerSendAggDefender.size() == world->GetNumPlayers());
×
87
        huntingDefender = sgd.PopObject<nofAggressiveDefender>(GO_Type::NofAggressivedefender);
×
88

89
        radius = sgd.PopUnsignedShort();
×
90

91
        if(state == SoldierState::AttackingWaitingForDefender)
×
92
            blocking_event = sgd.PopEvent();
×
93
        else
94
            blocking_event = nullptr;
×
95

96
        harborPos = sgd.PopMapPoint();
×
97
        shipPos = sgd.PopMapPoint();
×
98
        ship_obj_id = sgd.PopUnsignedInt();
×
99
    } else
100
    {
101
        attacked_goal = nullptr;
×
102
        mayBeHunted = false;
×
103
        canPlayerSendAggDefender.resize(world->GetNumPlayers(), SendDefender::Undecided);
×
104
        huntingDefender = nullptr;
×
105
        radius = 0;
×
106
        blocking_event = nullptr;
×
107
        harborPos = MapPoint::Invalid();
×
108
        shipPos = MapPoint::Invalid(); //-V656
×
109
        ship_obj_id = 0;
×
110
    }
111
}
×
112

113
void nofAttacker::Walked()
640✔
114
{
115
    ExpelEnemies();
640✔
116

117
    switch(state)
640✔
118
    {
119
        default: nofActiveSoldier::Walked(); break;
22✔
120
        case SoldierState::AttackingWalkingToGoal: MissAttackingWalk(); break;
533✔
121
        case SoldierState::AttackingAttackingFlag:
1✔
122
        {
123
            // If target is destroyed go home
124
            if(!attacked_goal)
1✔
125
            {
126
                ReturnHomeMissionAttacking();
×
127
                return;
×
128
            }
129

130
            const MapPoint goalFlagPos = attacked_goal->GetFlagPos();
1✔
131

132
            nofDefender* defender = nullptr;
1✔
133
            // Look for defenders at this position
134
            for(auto& figure : world->GetFigures(goalFlagPos))
6✔
135
            {
136
                if(figure.GetGOT() == GO_Type::NofDefender)
2✔
137
                {
138
                    // Is the defender waiting at the flag?
139
                    // (could be wandering around or something)
140
                    if(static_cast<nofDefender&>(figure).IsWaitingAtFlag())
1✔
141
                    {
142
                        defender = &static_cast<nofDefender&>(figure);
1✔
143
                        break;
1✔
144
                    }
145
                }
146
            }
147

148
            // Are we at the flag?
149
            if(pos == goalFlagPos)
1✔
150
            {
151
                if(defender)
1✔
152
                {
153
                    // Start fight with the defender
154
                    world->AddFigure(pos, std::make_unique<noFighting>(*this, *defender));
1✔
155
                    state = SoldierState::AttackingFightingVsDefender;
1✔
156
                    defender->FightStarted();
1✔
157
                } else
158
                {
159
                    // No defender at the flag?
160
                    // -> Order new defenders or capture the building
161
                    ContinueAtFlag();
×
162
                }
163
            } else
164
            {
165
                const auto dir = world->FindHumanPath(pos, goalFlagPos, 5, true);
×
166
                if(dir)
×
167
                    StartWalking(*dir);
×
168
                else
169
                {
170
                    // If there is no path anymore walk in the direction of the goal (or go back home)
171
                    state = SoldierState::AttackingWalkingToGoal;
×
172
                    MissAttackingWalk();
×
173
                    // Notify defender if any
174
                    if(defender)
×
175
                        defender->AttackerArrested();
×
176
                }
177
            }
178
        }
179
        break;
1✔
180
        case SoldierState::AttackingCapturingFirst:
13✔
181
        {
182
            if(!attacked_goal)
13✔
183
            {
184
                // Goal destroyed -> Go gome
185
                ReturnHomeMissionAttacking();
×
186
                return;
×
187
            }
188

189
            // If there is a defender available then an enemy soldier went back in
190
            if(attacked_goal->DefendersAvailable())
13✔
191
            {
192
                // Go back out
193
                if(attacked_goal->GetGOT() == GO_Type::NobMilitary)
1✔
194
                    static_cast<nobMilitary*>(attacked_goal)->StopCapturing();
1✔
195

196
                state = SoldierState::AttackingWalkingToGoal;
1✔
197
                StartWalking(Direction::SouthEast);
1✔
198
            } else
199
            {
200
                // Did we just conquer a "regular" military building?
201
                if(BuildingProperties::IsMilitary(attacked_goal->GetBuildingType()))
12✔
202
                {
203
                    RTTR_Assert(dynamic_cast<nobMilitary*>(attacked_goal));
10✔
204
                    // We will now have this building as the new home, so inform old home, hunting soldier and ship
205
                    if(building)
10✔
206
                        building->SoldierLost(this);
10✔
207
                    CancelAtHuntingDefender();
10✔
208
                    if(ship_obj_id)
10✔
209
                        CancelAtShip();
×
210
                    // Store the goal in a temporary as attacked_goal will be reset
211
                    auto* goal = static_cast<nobMilitary*>(attacked_goal);
10✔
212
                    goal->Capture(player);
10✔
213
                    building = attacked_goal;
10✔
214
                    attacked_goal->AddActiveSoldier(world->RemoveFigure(pos, *this));
10✔
215
                    RemoveFromAttackedGoal();
10✔
216
                    // This might call other capturers
217
                    goal->CapturingSoldierArrived();
10✔
218
                } else // Something like HQ or harbor
219
                {
220
                    // Inform the owner of the building and destroy it
221
                    const std::string msg = (attacked_goal->GetGOT() == GO_Type::NobHq) ?
2✔
222
                                              _("Our headquarters was destroyed!") :
×
223
                                              _("This harbor building was destroyed");
6✔
224
                    SendPostMessage(attacked_goal->GetPlayer(),
2✔
225
                                    std::make_unique<PostMsgWithBuilding>(GetEvMgr().GetCurrentGF(), msg,
4✔
226
                                                                          PostCategory::Military, *attacked_goal));
2✔
227

228
                    nobBaseMilitary* tmp_goal = attacked_goal; // Store in a temporary in case it gets reset by Destroy
2✔
229
                    tmp_goal->Destroy();
2✔
230
                    delete tmp_goal;
2✔
231
                    attacked_goal = nullptr;
2✔
232
                    ReturnHomeMissionAttacking();
2✔
233
                }
234
            }
235
        }
236
        break;
13✔
237
        case SoldierState::AttackingCapturingNext: CapturingWalking(); break;
6✔
238

239
        case SoldierState::SeaattackingGoToHarbor:
60✔
240
        {
241
            // Abort if the harbor doesn't exist, belongs to another player or the attacked building is gone
242
            const noBase* hb = world->GetNO(harborPos);
60✔
243
            const bool valid_harbor = hb->GetGOT() == GO_Type::NobHarborbuilding
60✔
244
                                      && static_cast<const nobHarborBuilding*>(hb)->GetPlayer() == player;
60✔
245

246
            if(!valid_harbor || !attacked_goal)
60✔
247
            {
248
                ReturnHomeMissionAttacking();
×
249
                return;
×
250
            }
251

252
            if(pos == harborPos) // When we arrived add to harbor
60✔
253
            {
254
                state = SoldierState::SeaattackingWaitInHarbor;
10✔
255
                world->GetSpecObj<nobHarborBuilding>(pos)->AddSeaAttacker(world->RemoveFigure(pos, *this));
10✔
256
            } else // Go to flag then walk inside the building
257
            {
258
                const MapPoint harborFlagPos = world->GetNeighbour(harborPos, Direction::SouthEast);
50✔
259

260
                if(pos == harborFlagPos)
50✔
261
                    StartWalking(Direction::NorthWest);
10✔
262
                else
263
                {
264
                    const auto dir =
265
                      world->FindHumanPath(pos, harborFlagPos, MAX_ATTACKING_RUN_DISTANCE, false, nullptr);
40✔
266
                    if(dir)
40✔
267
                        StartWalking(*dir);
40✔
268
                    else
269
                        ReturnHomeMissionAttacking(); // No possible path -> Abort
×
270
                }
271
            }
272
        }
273
        break;
60✔
274
        case SoldierState::SeaattackingWaitInHarbor: break;
×
275
        case SoldierState::SeaattackingOnShip:
×
276
            // Can't walk while on the ship
277
            RTTR_Assert(false);
×
278
            break;
279
        case SoldierState::SeaattackingReturnToShip: HandleState_SeaAttack_ReturnToShip(); break;
5✔
280
    }
281
}
282

283
void nofAttacker::HomeDestroyed()
1✔
284
{
285
    // If we are waiting, no events will trigger -> Start wandering right now,
286
    // else continue current event (e.g. movement) and handle at the end of that
287
    if(state == SoldierState::AttackingWaitingAroundBuilding)
1✔
288
    {
289
        // We are lost now so no valid source or target of attacks
290
        nobBaseMilitary* curGoal = attacked_goal; // attacked_goal gets reset
×
291
        InformTargetsAboutCancelling();
×
292
        if(ship_obj_id)
×
293
            CancelAtShip();
×
294

295
        building = nullptr;
×
296
        state = SoldierState::FigureWork;
×
297
        StartWandering();
×
298
        Wander();
×
299

300
        // Someone else might take this place
301
        curGoal->SendSuccessor(pos, radius);
×
302
    } else
303
    {
304
        // If we were going back home, reset that goal
305
        if(goal_ == building)
1✔
306
            goal_ = nullptr;
×
307
        building = nullptr;
1✔
308
    }
309
}
1✔
310

311
void nofAttacker::HomeDestroyedAtBegin()
×
312
{
313
    building = nullptr;
×
314

315
    // We are lost now and hence not targetable
316
    InformTargetsAboutCancelling();
×
317

318
    state = SoldierState::FigureWork;
×
319

320
    StartWandering();
×
321
    StartWalking(RANDOM_ENUM(Direction));
×
322
}
×
323

324
void nofAttacker::WonFighting()
14✔
325
{
326
    if(world->GetGGS().isEnabled(AddonId::BATTLEFIELD_PROMOTION))
14✔
327
        IncreaseRank();
×
328
    // If our home was destroyed then we are lost
329
    // unless we are currently fighting at the flag so that building can become our new home
330
    if(!building && state != SoldierState::AttackingFightingVsDefender)
14✔
331
    {
332
        // Lost -> Tell all dependents
333
        InformTargetsAboutCancelling();
×
334
        if(ship_obj_id)
×
335
            CancelAtShip();
×
336

337
        state = SoldierState::FigureWork;
×
338
        StartWandering();
×
339
        Wander();
×
340
    } else if(!attacked_goal)
14✔
341
    {
342
        // Target is gone -> Go home
343
        ReturnHomeMissionAttacking();
×
344
    } else
345
        ContinueAtFlag();
14✔
346
}
14✔
347

348
void nofAttacker::ContinueAtFlag()
14✔
349
{
350
    RTTR_Assert(attacked_goal);
14✔
351
    // If we were fighting at the flag of the goal then call a new defender
352
    // or capture the building if there isn't one anymore
353
    if(state == SoldierState::AttackingFightingVsDefender
28✔
354
       || (state == SoldierState::Fighting && attacked_goal->GetFlagPos() == pos))
14✔
355
    {
356
        if(attacked_goal->CallDefender(*this)) //-V522
13✔
357
            SwitchStateAttackingWaitingForDefender();
1✔
358
        else if(!TryFightingNearbyEnemy(attacked_goal->GetPlayer()))
12✔
359
        {
360
            // No defender or soldiers of other non-friendly non-owner players to fight
361
            // -> Enter building
362
            state = SoldierState::AttackingCapturingFirst;
12✔
363
            StartWalking(Direction::NorthWest);
12✔
364

365
            // Regular military buildings will be captured
366
            if(attacked_goal->GetGOT() == GO_Type::NobMilitary)
12✔
367
                static_cast<nobMilitary*>(attacked_goal)->PrepareCapturing();
11✔
368
        }
369
    } else
370
    {
371
        // Go (back) to goal
372
        state = SoldierState::AttackingWalkingToGoal;
1✔
373
        MissAttackingWalk();
1✔
374
    }
375
}
14✔
376

377
void nofAttacker::LostFighting()
2✔
378
{
379
    // Inform home building (so e.g. new troops can be ordered) and all dependents
380
    AbrogateWorkplace();
2✔
381
    InformTargetsAboutCancelling();
2✔
382
    if(ship_obj_id)
2✔
383
        this->CancelAtShip();
×
384
}
2✔
385

386
void nofAttacker::ReturnHomeMissionAttacking()
8✔
387
{
388
    InformTargetsAboutCancelling(); // We don't attack anymore
8✔
389
    if(ship_obj_id)
8✔
390
    {
391
        state = SoldierState::SeaattackingReturnToShip;
4✔
392
        HandleState_SeaAttack_ReturnToShip();
4✔
393
    } else
394
        ReturnHome();
4✔
395
}
8✔
396

397
void nofAttacker::MissAttackingWalk()
539✔
398
{
399
    // If our home is destroyed we are lost
400
    if(!building)
539✔
401
    {
402
        InformTargetsAboutCancelling();
1✔
403
        if(ship_obj_id)
1✔
404
            CancelAtShip();
×
405

406
        state = SoldierState::FigureWork;
1✔
407
        StartWandering();
1✔
408
        Wander();
1✔
409

410
        return;
3✔
411
    }
412

413
    // When target is gone go home
414
    if(!attacked_goal)
538✔
415
    {
416
        ReturnHomeMissionAttacking();
2✔
417
        return;
2✔
418
    }
419

420
    // Find a position next to the target
421
    const MapPoint goal = attacked_goal->FindAnAttackerPlace(radius, *this);
536✔
422

423
    if(!goal.isValid()) // Go home if there is no free spot
536✔
424
        ReturnHomeMissionAttacking();
3✔
425
    else if(pos == goal)
533✔
426
        ReachedDestination();
58✔
427
    else if(!TryFightingNearbyEnemy())
475✔
428
    {
429
        // If no Enemy (attackers, aggressive defenders..) was found (would be handled by nofActiveSoldier)
430
        // check if some building might send a defender and walk to goal
431

432
        TryToOrderAggressiveDefender();
472✔
433

434
        const auto dir = world->FindHumanPath(pos, goal, MAX_ATTACKING_RUN_DISTANCE, true);
472✔
435
        if(dir)
472✔
436
            StartWalking(*dir);
472✔
437
        else
438
            ReturnHomeMissionAttacking();
×
439
    }
440
}
441

442
void nofAttacker::ReachedDestination()
58✔
443
{
444
    // Are we at the flag of the target?
445
    const MapPoint attFlagPos = attacked_goal->GetFlagPos();
58✔
446
    if(pos == attFlagPos)
58✔
447
    {
448
        // If building is already captured continue capturing
449
        // (This can only be a far away attacker)
450
        if(attacked_goal->GetPlayer() == player)
23✔
451
        {
452
            state = SoldierState::AttackingCapturingNext;
2✔
453
            RTTR_Assert(dynamic_cast<nobMilitary*>(attacked_goal));
2✔
454
            auto* goal = static_cast<nobMilitary*>(attacked_goal);
2✔
455
            RTTR_Assert(goal->IsFarAwayCapturer(*this));
2✔
456
            // Start walking first so the flag is free
457
            StartWalking(Direction::NorthWest);
2✔
458
            // Then tell the building
459
            goal->FarAwayCapturerReachedGoal(*this, true);
2✔
460
        } else
461
        {
462
            // Notify player
463
            // TODO: Possibly improve that not every attacker does that
464
            SendPostMessage(attacked_goal->GetPlayer(),
21✔
465
                            std::make_unique<PostMsgWithBuilding>(GetEvMgr().GetCurrentGF(), _("We are under attack!"),
42✔
466
                                                                  PostCategory::Military, *attacked_goal));
21✔
467

468
            // Get a defender or walk into the building
469
            if(attacked_goal->CallDefender(*this))
21✔
470
                SwitchStateAttackingWaitingForDefender();
20✔
471
            else
472
            {
473
                state = SoldierState::AttackingCapturingFirst;
1✔
474
                StartWalking(Direction::NorthWest);
1✔
475
                // Regular military buildings will be captured
476
                if(attacked_goal->GetGOT() == GO_Type::NobMilitary)
1✔
477
                    static_cast<nobMilitary*>(attacked_goal)->PrepareCapturing();
×
478
            }
479
        }
480
    } else
481
    {
482
        // Destination wasn't the flag, so just wait until we can fight and face the flag
483
        state = SoldierState::AttackingWaitingAroundBuilding;
35✔
484
        Direction dir;
485
        if(pos.y == attFlagPos.y)
35✔
486
            dir = (pos.x < attFlagPos.x) ? Direction::East : Direction::West;
12✔
487
        else if(pos.x == attFlagPos.x)
23✔
488
        {
489
            if(pos.y < attFlagPos.y)
7✔
490
            {
491
                if(absDiff(pos.y, attFlagPos.y) & 1)
×
492
                    dir = (pos.y & 1) ? Direction::SouthWest : Direction::SouthEast;
×
493
                else
494
                    dir = Direction::SouthEast;
×
495
            } else
496
            {
497
                if(absDiff(pos.y, attFlagPos.y) & 1)
7✔
498
                    dir = (pos.y & 1) ? Direction::NorthWest : Direction::NorthEast;
7✔
499
                else
500
                    dir = Direction::NorthEast;
×
501
            }
502
        } else if(pos.y < attFlagPos.y)
16✔
503
            dir = (pos.x < attFlagPos.x) ? Direction::SouthEast : Direction::SouthWest;
9✔
504
        else // pos.y > attFlagPos.y
505
            dir = (pos.x < attFlagPos.x) ? Direction::NorthEast : Direction::NorthWest;
7✔
506
        FaceDir(dir);
35✔
507
        if(attacked_goal->GetPlayer() == player)
35✔
508
        {
509
            // Building already captured? -> Then we might be a far-away-capturer
510
            // -> Tell the building, that we are here
UNCOV
511
            RTTR_Assert(dynamic_cast<nobMilitary*>(attacked_goal));
×
UNCOV
512
            auto* goal = static_cast<nobMilitary*>(attacked_goal);
×
UNCOV
513
            if(goal->IsFarAwayCapturer(*this))
×
514
                goal->FarAwayCapturerReachedGoal(*this, false);
×
515
        }
516
    }
517
}
58✔
518

519
void nofAttacker::TryToOrderAggressiveDefender()
472✔
520
{
521
    RTTR_Assert(state == SoldierState::AttackingWalkingToGoal);
472✔
522
    if(mayBeHunted)
472✔
523
    {
524
        // 20% chance to get one
525
        if(RANDOM_RAND(10) < 2)
452✔
526
            OrderAggressiveDefender();
94✔
527
    }
528
}
472✔
529

530
void nofAttacker::OrderAggressiveDefender()
94✔
531
{
532
    // Any military building in at most this distance could send a defender
533
    constexpr auto maxDistance = 14;
94✔
534
    sortedMilitaryBlds buildings = world->LookForMilitaryBuildings(pos, 2);
188✔
535
    for(nobBaseMilitary* bld : buildings)
1,100✔
536
    {
537
        // Exclude HQs unless the one being attacked
538
        if(bld->GetBuildingType() == BuildingType::Headquarters && bld != attacked_goal)
411✔
539
            continue;
190✔
540
        if(world->CalcDistance(pos, bld->GetPos()) > maxDistance)
221✔
541
            continue;
7✔
542
        const unsigned bldOwnerId = bld->GetPlayer();
214✔
543
        if(canPlayerSendAggDefender[bldOwnerId] == SendDefender::No)
214✔
544
            continue;
2✔
545
        // Only allies of the attacked player send defenders and only if we can attack them
546
        GamePlayer& bldOwner = world->GetPlayer(bldOwnerId);
212✔
547
        if(!bldOwner.IsAlly(attacked_goal->GetPlayer()))
212✔
548
            continue;
92✔
549
        if(!bldOwner.IsAttackable(player))
120✔
550
            continue;
×
551
        // If player did not decide on sending do it now.
552
        // Doing this as late as here reduces chances,
553
        // that the player changed the setting when the defender is asked for
554
        if(canPlayerSendAggDefender[bldOwnerId] == SendDefender::Undecided)
120✔
555
        {
556
            if(bldOwner.ShouldSendDefender())
46✔
557
                canPlayerSendAggDefender[bldOwnerId] = SendDefender::Yes;
45✔
558
            else
559
            {
560
                canPlayerSendAggDefender[bldOwnerId] = SendDefender::No;
1✔
561
                continue;
1✔
562
            }
563
        }
564
        huntingDefender = bld->SendAggressiveDefender(*this);
119✔
565
        if(huntingDefender)
119✔
566
        {
567
            // Cannot be hunted again
568
            mayBeHunted = false;
2✔
569
            break;
2✔
570
        }
571
    }
572
}
94✔
573

574
void nofAttacker::AttackedGoalDestroyed()
10✔
575
{
576
    attacked_goal = nullptr;
10✔
577

578
    if(state == SoldierState::SeaattackingWaitInHarbor)
10✔
579
    {
580
        // We don't need to wait anymore, target was destroyed
581
        auto* harbor = world->GetSpecObj<nobHarborBuilding>(harborPos);
6✔
582
        RTTR_Assert(harbor);
6✔
583
        // go home
584
        goal_ = building;
6✔
585
        state = SoldierState::FigureWork;
6✔
586
        fs = FigureState::GotToGoal;
6✔
587
        harbor->CancelSeaAttacker(this);
6✔
588
    } else
589
    {
590
        const bool was_waiting_for_defender = (state == SoldierState::AttackingWaitingForDefender);
4✔
591

592
        // If waiting start moving
593
        if(state == SoldierState::AttackingWaitingForDefender || state == SoldierState::AttackingWaitingAroundBuilding
4✔
594
           || state == SoldierState::WaitingForFight)
3✔
595
            ReturnHomeMissionAttacking();
1✔
596
        if(was_waiting_for_defender)
4✔
597
        {
598
            GetEvMgr().RemoveEvent(blocking_event);
×
599
            world->RoadNodeAvailable(pos);
×
600
        }
601
    }
602
}
10✔
603

604
bool nofAttacker::AttackDefenderAtFlag()
2✔
605
{
606
    // Walk to flag if possible
607
    const auto dir = world->FindHumanPath(pos, attacked_goal->GetFlagPos(), 3, true);
2✔
608
    if(dir)
2✔
609
    {
610
        const bool waiting_around_building = (state == SoldierState::AttackingWaitingAroundBuilding);
1✔
611
        state = SoldierState::AttackingAttackingFlag;
1✔
612

613
        if(waiting_around_building)
1✔
614
        {
615
            StartWalking(*dir);
1✔
616
            attacked_goal->SendSuccessor(pos, radius);
1✔
617
        }
618
        return true;
1✔
619
    } else
620
    {
621
        state = nofActiveSoldier::SoldierState::AttackingWalkingToGoal;
1✔
622
        MissAttackingWalk();
1✔
623
        return false;
1✔
624
    }
625
}
626

627
void nofAttacker::AttackFlag()
×
628
{
629
    state = SoldierState::AttackingWalkingToGoal;
×
630
    MissAttackingWalk();
×
631
}
×
632

633
void nofAttacker::CaptureBuilding()
9✔
634
{
635
    state = SoldierState::AttackingCapturingNext;
9✔
636
    CapturingWalking();
9✔
637
}
9✔
638

639
void nofAttacker::CapturingWalking()
15✔
640
{
641
    // If building is gone go home
642
    if(!attacked_goal)
15✔
643
    {
644
        ReturnHomeMissionAttacking();
×
645
        return;
×
646
    }
647
    RTTR_Assert(dynamic_cast<nobMilitary*>(attacked_goal));
15✔
648

649
    const MapPoint attFlagPos = attacked_goal->GetFlagPos();
15✔
650

651
    // 3 cases:
652
    // 1. We arrived in the building -> Capture
653
    // 2. We arrived at flag -> Go into the building
654
    // 3. Otherwise walk to the flag if our home building still exists, else wander around
655
    if(pos == attacked_goal->GetPos())
15✔
656
    {
657
        // We switch buildings
658
        if(building)
4✔
659
            building->SoldierLost(this);
4✔
660
        CancelAtHuntingDefender();
4✔
661
        if(ship_obj_id)
4✔
662
            CancelAtShip();
×
663
        building = attacked_goal;
4✔
664
        attacked_goal->AddActiveSoldier(world->RemoveFigure(pos, *this));
4✔
665

666
        // No longer attacking
667
        if(BuildingProperties::IsMilitary(attacked_goal->GetBuildingType()))
4✔
668
        {
669
            RTTR_Assert(dynamic_cast<nobMilitary*>(attacked_goal));
4✔
670
            auto* goal = static_cast<nobMilitary*>(attacked_goal);
4✔
671
            // If we are still a far-away-capturer at this point,
672
            // then the building belongs to us and capturing was already finished
673
            if(goal->IsFarAwayCapturer(*this))
4✔
674
            {
675
                RTTR_Assert(goal->GetPlayer() == player);
2✔
676
                RemoveFromAttackedGoal();
2✔
677
            } else
678
            {
679
                RemoveFromAttackedGoal();
2✔
680
                goal->CapturingSoldierArrived();
2✔
681
            }
682
        } else
683
            RemoveFromAttackedGoal();
×
684
    } else if(pos == attFlagPos)
11✔
685
    {
686
        // Go into the building and call next one for reinforcement
687
        StartWalking(Direction::NorthWest);
2✔
688
        RTTR_Assert(attacked_goal->GetPlayer() == player); // Assumed by the call below
2✔
689
        static_cast<nobMilitary*>(attacked_goal)->NeedOccupyingTroops();
2✔
690
    } else if(!building)
9✔
691
    {
692
        // If our home is destroyed we are lost and don't walk to the target (our new home if we were at least at the
693
        // flag already) Notify it, if it still exists (could be destroyed in the meantime too)
694
        if(attacked_goal)
×
695
        {
696
            auto* attackedBld = static_cast<nobMilitary*>(attacked_goal);
×
697
            RemoveFromAttackedGoal();
×
698
            // Reinforce building (should be already ours)
699
            RTTR_Assert(attackedBld->GetPlayer() == player);
×
700
            attackedBld->NeedOccupyingTroops();
×
701
        }
702

703
        if(ship_obj_id)
×
704
            CancelAtShip();
×
705

706
        state = SoldierState::FigureWork;
×
707
        StartWandering();
×
708
        Wander();
×
709
    } else
710
    {
711
        // Our home still exists so walk to the flag of the building if possible
712
        const auto dir = world->FindHumanPath(pos, attFlagPos, 10, true);
9✔
713
        if(dir)
9✔
714
            StartWalking(*dir);
9✔
715
        else
716
        {
717
            // Call other troops to occupy the building instead and go home
718
            RTTR_Assert(attacked_goal->GetPlayer() == player); // Assumed by the call below
×
719
            static_cast<nobMilitary*>(attacked_goal)->NeedOccupyingTroops();
×
720
            ReturnHomeMissionAttacking();
×
721
        }
722
    }
723
}
724

725
void nofAttacker::CapturedBuildingFull()
1✔
726
{
727
    // Reset goal, no longer our business
728
    attacked_goal = nullptr;
1✔
729

730
    // We only need to do something when we are currently waiting,
731
    // otherwise the next steps are handled when our current action (walk, fight) is finished
732
    if(state == SoldierState::AttackingWaitingAroundBuilding)
1✔
733
        ReturnHomeMissionAttacking();
×
734
}
1✔
735

736
void nofAttacker::StartSucceeding(const MapPoint /*pt*/, unsigned short /*new_radius*/)
×
737
{
738
    state = SoldierState::AttackingWalkingToGoal;
×
739

740
    const auto oldPos = pos;
×
741
    const auto oldRadius = radius;
×
742

743
    MissAttackingWalk();
×
744

745
    if(IsMoving())
×
746
        attacked_goal->SendSuccessor(oldPos, oldRadius);
×
747
}
×
748

749
void nofAttacker::LetsFight(nofAggressiveDefender& other)
11✔
750
{
751
    RTTR_Assert(!huntingDefender);
11✔
752
    // We only get hunted once
753
    mayBeHunted = false;
11✔
754
    huntingDefender = &other;
11✔
755
}
11✔
756

757
void nofAttacker::AggressiveDefenderLost()
11✔
758
{
759
    RTTR_Assert(huntingDefender);
11✔
760
    huntingDefender = nullptr;
11✔
761
}
11✔
762

763
void nofAttacker::SwitchStateAttackingWaitingForDefender()
21✔
764
{
765
    state = SoldierState::AttackingWaitingForDefender;
21✔
766
    blocking_event = GetEvMgr().AddEvent(this, BLOCK_OFFSET, 5);
21✔
767
}
21✔
768

769
void nofAttacker::HandleDerivedEvent(const unsigned /*id*/)
21✔
770
{
771
    RTTR_Assert(blocking_event);
21✔
772
    // If we are still waiting (we could be walking again if building was destroyed)
773
    // stop figures walking to this position
774
    if(state == SoldierState::AttackingWaitingForDefender)
21✔
775
    {
776
        world->StopOnRoads(pos);
21✔
777
        blocking_event = nullptr;
21✔
778
    }
779
}
21✔
780

781
bool nofAttacker::IsBlockingRoads() const
488✔
782
{
783
    if(state != SoldierState::AttackingWaitingForDefender)
488✔
784
        return false;
455✔
785

786
    // If the event isn't expired yet people can still move through
787
    return blocking_event == nullptr;
33✔
788
}
789

790
void nofAttacker::InformTargetsAboutCancelling()
11✔
791
{
792
    nofActiveSoldier::InformTargetsAboutCancelling();
11✔
793
    CancelAtHuntingDefender();
11✔
794

795
    if(attacked_goal)
11✔
796
        RemoveFromAttackedGoal();
6✔
797
    RTTR_Assert(attacked_goal == nullptr);
11✔
798
}
11✔
799

800
void nofAttacker::RemoveFromAttackedGoal()
20✔
801
{
802
    // If state == AttackingFightingVsDefender then we probably just lost the fight against the defender,
803
    // otherwise there must either be no defender or he is not waiting for us
804
    RTTR_Assert(
20✔
805
      state == SoldierState::AttackingFightingVsDefender || !attacked_goal->GetDefender()
806
      || (attacked_goal->GetDefender()->GetAttacker() != this && attacked_goal->GetDefender()->GetEnemy() != this));
807
    // No defender should be chasing us at this point
808
    for(auto* it : attacked_goal->GetAggresiveDefenders())
22✔
809
        RTTR_Assert(it->GetAttacker() != this);
2✔
810
    attacked_goal->UnlinkAggressor(*this);
20✔
811
    attacked_goal = nullptr;
20✔
812
}
20✔
813

814
void nofAttacker::StartAttackOnOtherIsland(const MapPoint shipPos, const unsigned ship_id)
4✔
815
{
816
    pos = this->shipPos = shipPos;
4✔
817
    this->ship_obj_id = ship_id;
4✔
818

819
    state = SoldierState::AttackingWalkingToGoal;
4✔
820
    on_ship = false;
4✔
821
    MissAttackingWalk();
4✔
822
}
4✔
823

824
void nofAttacker::SeaAttackFailedBeforeLaunch()
×
825
{
826
    InformTargetsAboutCancelling();
×
827
    RTTR_Assert(!huntingDefender);
×
828
    AbrogateWorkplace();
×
829
    goal_ = nullptr;
×
830
    state = SoldierState::FigureWork;
×
831
}
×
832

833
void nofAttacker::StartReturnViaShip(noShip& ship)
4✔
834
{
835
    if(pos.isValid())
4✔
836
    {
837
        ship.AddReturnedAttacker(world->RemoveFigure(pos, *this));
4✔
838
        pos = MapPoint::Invalid(); // Similar to start ship journey
4✔
839
    } else
840
    {
841
        // If pos is not valid, then we are still on the ship!
842
        // This can happen, if the ship cannot reach its target
843
        RTTR_Assert(state == SoldierState::SeaattackingOnShip);
×
844
        RTTR_Assert(ship.IsOnBoard(*this));
×
845
        InformTargetsAboutCancelling();
×
846
    }
847

848
    goal_ = building;
4✔
849
    state = SoldierState::FigureWork;
4✔
850
    fs = FigureState::GotToGoal;
4✔
851
    on_ship = true;
4✔
852
    ship_obj_id = 0;
4✔
853
}
4✔
854

855
void nofAttacker::HomeHarborLost()
×
856
{
857
    // this in combination with telling the home building that the soldier is lost should work just fine
858
    goal_ = nullptr;
×
859
}
×
860

861
void nofAttacker::CancelAtShip()
×
862
{
863
    for(noBase& figure : world->GetFigures(shipPos))
×
864
    {
865
        if(figure.GetObjId() == ship_obj_id)
×
866
        {
867
            static_cast<noShip&>(figure).SeaAttackerWishesNoReturn();
×
868
            break;
×
869
        }
870
    }
871
    ship_obj_id = 0;
×
872
}
×
873

874
void nofAttacker::CancelAtHuntingDefender()
25✔
875
{
876
    if(huntingDefender)
25✔
877
    {
878
        RTTR_Assert(huntingDefender->GetAttacker() == this);
2✔
879
        huntingDefender->AttackerLost();
2✔
880
        huntingDefender = nullptr;
2✔
881
    }
882
}
25✔
883

884
void nofAttacker::HandleState_SeaAttack_ReturnToShip()
9✔
885
{
886
    if(!building)
9✔
887
    {
888
        // Home destroyed -> start wandering
889
        state = SoldierState::FigureWork;
×
890
        StartWandering();
×
891
        Wander();
×
892

893
        CancelAtShip();
×
894
    } else if(pos == shipPos) // Arrived at ship
9✔
895
    {
896
        for(noBase& figure : world->GetFigures(pos))
16✔
897
        {
898
            if(figure.GetObjId() == ship_obj_id)
4✔
899
            {
900
                StartReturnViaShip(static_cast<noShip&>(figure));
4✔
901
                return;
4✔
902
            }
903
        }
904

905
        // Ship is gone, shouldn't happen!
906
        RTTR_Assert(false);
×
907
        ship_obj_id = 0;
908
        StartWandering();
909
        state = SoldierState::FigureWork;
910
        Wander();
911
    } else
912
    {
913
        const auto dir = world->FindHumanPath(pos, shipPos, MAX_ATTACKING_RUN_DISTANCE);
5✔
914
        if(dir)
5✔
915
            StartWalking(*dir);
5✔
916
        else
917
        {
918
            // No path -> wander around
919
            StartWandering();
×
920
            state = SoldierState::FigureWork;
×
921
            Wander();
×
922

923
            // Notify home and ship
924
            building->SoldierLost(this);
×
925
            CancelAtShip();
×
926
        }
927
    }
928
}
929

930
void nofAttacker::CancelSeaAttack()
×
931
{
932
    InformTargetsAboutCancelling();
×
933
    RTTR_Assert(!huntingDefender);
×
934
    Abrogate();
×
935
}
×
936

937
nofActiveSoldier::SoldierState nofAttacker::FreeFightAborted()
2✔
938
{
939
    // Continue with normal walking towards our goal
940
    return SoldierState::AttackingWalkingToGoal;
2✔
941
}
942

943
bool nofAttacker::CanStartFarAwayCapturing(const nobMilitary& dest) const
3✔
944
{
945
    // Are we already walking to the destination?
946
    if(state == SoldierState::AttackingWalkingToGoal || state == SoldierState::MeetEnemy
3✔
947
       || state == SoldierState::WaitingForFight || state == SoldierState::Fighting)
2✔
948
    {
949
        // Not too far away?
950
        if(world->CalcDistance(pos, dest.GetPos()) < MAX_FAR_AWAY_CAPTURING_DISTANCE)
2✔
951
            return true;
2✔
952
    }
953

954
    return false;
1✔
955
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc