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

Return-To-The-Roots / s25client / 21780228483

07 Feb 2026 12:38PM UTC coverage: 50.817% (+0.06%) from 50.754%
21780228483

push

github

Flow86
Also use references for non-NULL arguments in `GamePlayer`

44 of 59 new or added lines in 11 files covered. (74.58%)

520 existing lines in 14 files now uncovered.

22787 of 44841 relevant lines covered (50.82%)

42084.45 hits per line

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

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

5
#include "figures/noFigure.h"
6
#include "EventManager.h"
7
#include "FindWhConditions.h"
8
#include "GamePlayer.h"
9
#include "Loader.h"
10
#include "SerializedGameData.h"
11
#include "WineLoader.h"
12
#include "buildings/nobBaseWarehouse.h"
13
#include "buildings/nobHarborBuilding.h"
14
#include "helpers/containerUtils.h"
15
#include "network/GameClient.h"
16
#include "nofCarrier.h"
17
#include "ogl/glArchivItem_Bitmap.h"
18
#include "ogl/glArchivItem_Bitmap_Player.h"
19
#include "ogl/glArchivItem_Bob.h"
20
#include "ogl/glSmartBitmap.h"
21
#include "pathfinding/PathConditionHuman.h"
22
#include "random/Random.h"
23
#include "world/GameWorld.h"
24
#include "nodeObjs/noFlag.h"
25
#include "nodeObjs/noRoadNode.h"
26
#include "nodeObjs/noSkeleton.h"
27
#include "gameTypes/DirectionToImgDir.h"
28
#include "gameData/GameConsts.h"
29
#include "gameData/JobConsts.h"
30
#include "s25util/Log.h"
31
#include "s25util/colors.h"
32

33
const RoadSegment noFigure::emulated_wanderroad(RoadType::Normal, nullptr, nullptr,
34
                                                std::vector<Direction>(0, Direction::East));
35
/// Welche Strecke soll minimal und maximal zurückgelegt werden beim Rumirren, bevor eine Flagge gesucht wird
36
const unsigned short WANDER_WAY_MIN = 20;
37
const unsigned short WANDER_WAY_MAX = 40;
38
/// Versuche, eine Flagge zu finden, bis er stirbt beim Rumirren
39
const unsigned short WANDER_TRYINGS = 3;
40
// Größe des Rechtecks um den Punkt, wo er die Flaggen sucht beim Rumirren
41
const unsigned short WANDER_RADIUS = 10;
42
/// Dasselbe nochmal für Soldaten
43
const unsigned short WANDER_TRYINGS_SOLDIERS = 6;
44
const unsigned short WANDER_RADIUS_SOLDIERS = 15;
45

46
noFigure::noFigure(const Job job, const MapPoint pos, const unsigned char player, noRoadNode* const goal)
662✔
47
    : noMovable(NodalObjectType::Figure, pos), fs(FigureState::GotToGoal), job_(job), player(player), cur_rs(nullptr),
48
      rs_pos(0), rs_dir(false), on_ship(false), goal_(goal), waiting_for_free_node(false), wander_way(0),
49
      wander_tryings(0), flagPos_(MapPoint::Invalid()), flag_obj_id(0), burned_wh_id(0xFFFFFFFF), last_id(0xFFFFFFFF)
662✔
50
{
51
    // If the goal is a storehouse we won't work there but go to the new home
52
    if(goal && nobBaseWarehouse::isStorehouseGOT(goal->GetGOT()))
662✔
53
        fs = FigureState::GoHome;
69✔
54
}
662✔
55

56
noFigure::noFigure(const Job job, const MapPoint pos, const unsigned char player)
44✔
57
    : noMovable(NodalObjectType::Figure, pos), fs(FigureState::Job), job_(job), player(player), cur_rs(nullptr),
58
      rs_pos(0), rs_dir(false), on_ship(false), goal_(nullptr), waiting_for_free_node(false), wander_way(0),
59
      wander_tryings(0), flagPos_(MapPoint::Invalid()), flag_obj_id(0), burned_wh_id(0xFFFFFFFF), last_id(0xFFFFFFFF)
44✔
60
{}
44✔
61

62
void noFigure::Destroy()
190✔
63
{
64
    RTTR_Assert(HasNoGoal());
190✔
65
    RTTR_Assert(!cur_rs);
190✔
66
    noMovable::Destroy();
190✔
67

68
    RTTR_Assert(!world->GetPlayer(player).IsDependentFigure(*this));
190✔
69
}
190✔
70

71
void noFigure::Serialize(SerializedGameData& sgd) const
8✔
72
{
73
    noMovable::Serialize(sgd);
8✔
74

75
    sgd.PushEnum<uint8_t>(fs);
8✔
76
    sgd.PushEnum<uint8_t>(job_);
8✔
77
    sgd.PushUnsignedChar(player);
8✔
78
    sgd.PushObject(cur_rs, true);
8✔
79
    sgd.PushUnsignedShort(rs_pos);
8✔
80
    sgd.PushBool(rs_dir);
8✔
81
    sgd.PushBool(on_ship);
8✔
82

83
    if(fs == FigureState::GotToGoal || fs == FigureState::GoHome)
8✔
84
        sgd.PushObject(goal_);
6✔
85

86
    sgd.PushBool(waiting_for_free_node);
8✔
87

88
    if(fs == FigureState::Wander)
8✔
89
    {
UNCOV
90
        sgd.PushUnsignedShort(wander_way);
×
UNCOV
91
        sgd.PushUnsignedShort(wander_tryings);
×
UNCOV
92
        helpers::pushPoint(sgd, flagPos_);
×
UNCOV
93
        sgd.PushUnsignedInt(flag_obj_id);
×
94
        sgd.PushUnsignedInt(burned_wh_id);
×
95
    }
96
}
8✔
97

98
noFigure::noFigure(SerializedGameData& sgd, const unsigned obj_id)
4✔
99
    : noMovable(sgd, obj_id), fs(sgd.Pop<FigureState>()), job_(sgd.Pop<Job>()), player(sgd.PopUnsignedChar()),
16✔
100
      cur_rs(sgd.PopObject<RoadSegment>(GO_Type::Roadsegment)), rs_pos(sgd.PopUnsignedShort()), rs_dir(sgd.PopBool()),
4✔
101
      on_ship(sgd.PopBool()), last_id(0xFFFFFFFF)
8✔
102
{
103
    if(fs == FigureState::GotToGoal || fs == FigureState::GoHome)
4✔
104
        goal_ = sgd.PopObject<noRoadNode>();
3✔
105
    else
106
        goal_ = nullptr;
1✔
107

108
    waiting_for_free_node = sgd.PopBool();
4✔
109

110
    if(fs == FigureState::Wander)
4✔
111
    {
UNCOV
112
        wander_way = sgd.PopUnsignedShort();
×
UNCOV
113
        wander_tryings = sgd.PopUnsignedShort();
×
UNCOV
114
        flagPos_ = sgd.PopMapPoint();
×
UNCOV
115
        flag_obj_id = sgd.PopUnsignedInt();
×
116
        burned_wh_id = sgd.PopUnsignedInt();
×
117
    }
118
}
4✔
119

120
bool noFigure::IsSoldier() const
341✔
121
{
122
    return isSoldierJob(job_);
341✔
123
}
124

125
void noFigure::ActAtFirst()
310✔
126
{
127
    // Je nach unserem Status bestimmte Dinge tun
128
    switch(fs)
310✔
129
    {
UNCOV
130
        default: break;
×
131
        case FigureState::GotToGoal: WalkToGoal(); break;
154✔
132
        case FigureState::Job:
99✔
133
            StartWalking(Direction::SouthEast);
99✔
134
            break; // erstmal rauslaufen, darum kümmern sich dann die abgeleiteten Klassen
99✔
135
        case FigureState::GoHome:
53✔
136
        {
137
            // Wenn ich gleich wieder nach Hause geschickt wurde und aus einem Lagerhaus rauskomme, gar nicht erst
138
            // rausgehen!
139
            if(goal_->GetPos() == pos)
53✔
140
            {
141
                // Reset goal before re-adding to wh
UNCOV
142
                auto* wh = checkedCast<nobBaseWarehouse*>(goal_);
×
143
                goal_ = nullptr;
×
UNCOV
144
                cur_rs = nullptr;
×
UNCOV
145
                wh->AddFigure(world->RemoveFigure(pos, *this));
×
146
            } else
147
                // ansonsten ganz normal rausgehen
148
                WalkToGoal();
53✔
149
        }
150
        break;
53✔
151
        case FigureState::Wander:
4✔
152
            StartWalking(Direction::SouthEast);
4✔
153
            break; // erstmal rauslaufen, darum kümmern sich dann die Wander-Funktionen
4✔
154
    }
155
}
310✔
156

157
/// Gibt den Sichtradius dieser Figur zurück (0, falls nicht-spähend)
158
unsigned noFigure::GetVisualRange() const
3,374✔
159
{
160
    return 0;
3,374✔
161
}
162

163
/// Legt die Anfangsdaten für das Laufen auf Wegen fest
164
void noFigure::InitializeRoadWalking(const RoadSegment* const road, const unsigned short rs_pos, const bool rs_dir)
285✔
165
{
166
    this->cur_rs = road;
285✔
167
    this->rs_pos = rs_pos;
285✔
168
    this->rs_dir = rs_dir;
285✔
169
}
285✔
170

UNCOV
171
DrawPoint noFigure::CalcFigurRelative() const
×
172
{
UNCOV
173
    MapPoint targetPt = world->GetNeighbour(pos, GetCurMoveDir());
×
UNCOV
174
    Position curPt = world->GetNodePos(pos);
×
UNCOV
175
    Position nextPt = world->GetNodePos(targetPt);
×
176

UNCOV
177
    Position offset(0, 0);
×
178

UNCOV
179
    if(GetCurMoveDir() == Direction::NorthWest
×
UNCOV
180
       && (world->GetNO(targetPt)->GetType() == NodalObjectType::Buildingsite
×
UNCOV
181
           || world->GetNO(targetPt)->GetType() == NodalObjectType::Building))
×
182
    {
UNCOV
183
        auto* const bld = world->GetSpecObj<noBaseBuilding>(targetPt);
×
184
        nextPt += bld->GetDoorPoint();
×
UNCOV
185
    } else if(GetCurMoveDir() == Direction::SouthEast
×
186
              && (world->GetNO(pos)->GetType() == NodalObjectType::Buildingsite
×
187
                  || world->GetNO(pos)->GetType() == NodalObjectType::Building))
×
188
    {
UNCOV
189
        auto* const bld = world->GetSpecObj<noBaseBuilding>(pos);
×
190
        curPt += bld->GetDoorPoint();
×
UNCOV
191
        offset = bld->GetDoorPoint();
×
192
    }
193

194
    return offset + CalcRelative(curPt, nextPt);
×
195
}
196

197
void noFigure::StartWalking(const Direction dir)
4,014✔
198
{
199
    RTTR_Assert(!(GetGOT() == GO_Type::NofPassivesoldier && fs == FigureState::Job));
4,014✔
200

201
    // Gehen wir in ein Gebäude?
202
    if(dir == Direction::NorthWest
8,028✔
203
       && world->GetNO(world->GetNeighbour(pos, Direction::NorthWest))->GetType() == NodalObjectType::Building)
4,014✔
204
        world->GetSpecObj<noBuilding>(world->GetNeighbour(pos, Direction::NorthWest))
205
          ->OpenDoor(); // Dann die Tür aufmachen
410✔
206
    // oder aus einem raus?
207
    else if(dir == Direction::SouthEast && world->GetNO(pos)->GetType() == NodalObjectType::Building)
3,604✔
208
        world->GetSpecObj<noBuilding>(pos)->OpenDoor(); // Dann die Tür aufmachen
520✔
209

210
    // Ist der Platz schon besetzt, wo wir hinlaufen wollen und laufen wir auf Straßen?
211
    if(!world->IsRoadNodeForFigures(world->GetNeighbour(pos, dir)) && cur_rs)
4,014✔
212
    {
213
        // Dann stehen bleiben!
UNCOV
214
        FaceDir(dir);
×
UNCOV
215
        waiting_for_free_node = true;
×
216
        // Andere Figuren stoppen
UNCOV
217
        world->StopOnRoads(pos, dir);
×
218
    } else
219
    {
220
        // Normal hinlaufen
221
        StartMoving(dir, 20);
4,014✔
222
    }
223
}
4,014✔
224

UNCOV
225
void noFigure::DrawShadow(DrawPoint drawPt, const unsigned char anistep, Direction dir)
×
226
{
227
    auto* bitmap = LOADER.GetMapTexture(calcWalkFrameIndex(900, dir, anistep));
×
228
    if(bitmap)
×
UNCOV
229
        bitmap->DrawFull(drawPt, COLOR_SHADOW);
×
230
}
×
231

232
void noFigure::WalkFigure()
3,913✔
233
{
234
    // Tür hinter sich zumachen, wenn wir aus einem Gebäude kommen
235
    if(GetCurMoveDir() == Direction::SouthEast && world->GetNO(pos)->GetType() == NodalObjectType::Building)
3,913✔
236
        world->GetSpecObj<noBuilding>(pos)->CloseDoor();
506✔
237

238
    Walk();
3,913✔
239

240
    if(cur_rs)
3,913✔
241
        ++rs_pos;
2,191✔
242

243
    // oder in eins reingegangen sind
244
    if(GetCurMoveDir() == Direction::NorthWest && world->GetNO(pos)->GetType() == NodalObjectType::Building)
3,913✔
245
        world->GetSpecObj<noBuilding>(pos)->CloseDoor();
403✔
246
}
3,913✔
247

248
void noFigure::WalkToGoal()
1,536✔
249
{
250
    // Kein Ziel mehr --> Rumirren
251
    if(!goal_)
1,536✔
252
    {
UNCOV
253
        StartWandering();
×
UNCOV
254
        Wander();
×
UNCOV
255
        return;
×
256
    }
257

258
    // Straße abgelaufen oder noch gar keine Straße vorhanden?
259
    if(!cur_rs || rs_pos == cur_rs->GetLength())
1,536✔
260
    {
261
        // Ziel erreicht?
262
        // Bei dem Träger können das beide Flaggen sein!
263
        bool reachedGoal;
264
        if(GetGOT() == GO_Type::NofCarrier && fs == FigureState::GotToGoal)
879✔
265
        {
266
            RTTR_Assert(dynamic_cast<nofCarrier*>(this));
87✔
267
            auto* carrier = static_cast<nofCarrier*>(this);
87✔
268
            noRoadNode* flag = carrier->GetFirstFlag();
87✔
269
            if(flag && flag->GetPos() == pos)
87✔
270
                reachedGoal = true;
24✔
271
            else
272
            {
273
                flag = carrier->GetSecondFlag();
63✔
274
                reachedGoal = flag && flag->GetPos() == pos;
63✔
275
            }
276
        } else
277
        {
278
            reachedGoal = goal_->GetPos() == pos;
792✔
279
        }
280

281
        if(reachedGoal)
879✔
282
        {
283
            noRoadNode* goal = goal_;
537✔
284
            // Zeug nullen
285
            cur_rs = nullptr;
537✔
286
            goal_ = nullptr;
537✔
287
            rs_dir = false;
537✔
288
            rs_pos = 0;
537✔
289
            if(fs == FigureState::GoHome)
537✔
290
            {
291
                // Mann im Lagerhaus angekommen
292
                static_cast<nobBaseWarehouse*>(goal)->AddFigure(world->RemoveFigure(pos, *this));
59✔
293
            } else
294
            {
295
                // abgeleiteter Klasse sagen, dass das Ziel erreicht wurde
296
                fs = FigureState::Job;
478✔
297
                GoalReached();
478✔
298
            }
299

300
        } else
301
        {
302
            MapPoint next_harbor;
342✔
303
            // Neuen Weg berechnen
304
            auto* const curRoadNode = world->GetSpecObj<noRoadNode>(pos);
342✔
305
            RoadPathDirection route = curRoadNode ?
342✔
306
                                        world->FindHumanPathOnRoads(*curRoadNode, *goal_, nullptr, &next_harbor) :
342✔
307
                                        RoadPathDirection::None;
342✔
308
            // Kein Weg zum Ziel... nächstes Lagerhaus suchen
309
            if(route == RoadPathDirection::None)
342✔
310
            {
311
                // Arbeisplatz oder Laghaus Bescheid sagen
312
                Abrogate();
1✔
313
                // Wir gehen jetzt nach Hause
314
                GoHome();
1✔
315
                // Evtl wurde kein Lagerhaus gefunden und wir sollen rumirren, dann tun wir das gleich
316
                if(fs == FigureState::Wander)
1✔
UNCOV
317
                    Wander();
×
318
                else
319
                    WalkToGoal(); // Nach Hause laufen...
1✔
320
            }
321
            // Oder müssen wir das Schiff nehmen?
322
            else if(route == RoadPathDirection::Ship)
341✔
323
            {
324
                // Uns in den Hafen einquartieren
UNCOV
325
                noBase* hb = world->GetNO(pos);
×
UNCOV
326
                if(hb->GetGOT() != GO_Type::NobHarborbuilding)
×
327
                {
328
                    // Es gibt keinen Hafen mehr -> nach Hause gehen
329

330
                    // Arbeitsplatz oder Lagerhaus Bescheid sagen
UNCOV
331
                    Abrogate();
×
332
                    // Wir gehen jetzt nach Hause
UNCOV
333
                    GoHome();
×
334
                    // Evtl wurde kein Lagerhaus gefunden und wir sollen rumirren, dann tun wir das gleich
UNCOV
335
                    if(fs == FigureState::Wander)
×
UNCOV
336
                        Wander();
×
337
                    else
338
                        WalkToGoal(); // Nach Hause laufen...
×
339
                } else
340
                {
341
                    // Uns in den Hafen einquartieren
UNCOV
342
                    cur_rs = nullptr; // wir laufen nicht mehr auf einer Straße
×
UNCOV
343
                    static_cast<nobHarborBuilding*>(hb)->AddFigureForShip(world->RemoveFigure(pos, *this), next_harbor);
×
344
                }
345
            } else
346
            {
347
                // Get next street we are walking on
348
                const Direction walkDir = toDirection(route);
341✔
349
                cur_rs = curRoadNode->GetRoute(walkDir);
341✔
350
                StartWalking(walkDir);
341✔
351
                rs_pos = 0;
341✔
352
                rs_dir = curRoadNode != cur_rs->GetF1();
341✔
353
            }
354
        }
355

356
    } else
357
    {
358
        StartWalking(cur_rs->GetDir(rs_dir, rs_pos));
657✔
359
    }
360
}
361

362
void noFigure::HandleEvent(const unsigned id)
4,523✔
363
{
364
    // Bei ID = 0 ists ein Laufevent, bei allen anderen an abgeleitete Klassen weiterleiten
365
    if(id)
4,523✔
366
    {
367
        HandleDerivedEvent(id);
610✔
368
    } else
369
    {
370
        current_ev = nullptr;
3,913✔
371
        WalkFigure();
3,913✔
372

373
        // Alte Richtung und Position für die Berechnung der Sichtbarkeiten merken
374
        const Direction old_dir = GetCurMoveDir();
3,913✔
375

376
        const MapPoint old_pos(pos);
3,913✔
377

378
        switch(fs)
3,913✔
379
        {
380
            case FigureState::GoHome:
987✔
381
            case FigureState::GotToGoal: WalkToGoal(); break;
987✔
382

383
            case FigureState::Job: Walked(); break;
2,513✔
384
            case FigureState::Wander: Wander(); break;
413✔
385
        }
386

387
        // Ggf. Sichtbereich testen
388
        const unsigned visualRange = GetVisualRange();
3,913✔
389
        if(visualRange)
3,913✔
390
        {
391
            // Use old position (don't use this->pos because it might be different now
392
            // Figure could be in a ship etc.)
393
            world->RecalcMovingVisibilities(old_pos, player, visualRange, old_dir, nullptr);
539✔
394

395
            // Wenn Figur verschwunden ist, muss ihr ehemaliger gesamter Sichtbereich noch einmal
396
            // neue berechnet werden
397
            if(!world->HasFigureAt(old_pos, *this))
539✔
398
                CalcVisibilities(old_pos);
51✔
399
        }
400
    }
401
}
4,523✔
402

403
void noFigure::GoHome(noRoadNode* goal)
62✔
404
{
405
    if(on_ship)
62✔
406
    {
407
        // Wir befinden uns gerade an Deck, also einfach goal auf Null setzen und dann sehen wir, was so passiert
UNCOV
408
        this->goal_ = nullptr;
×
UNCOV
409
        return;
×
410
    }
411
    // Nächstes Lagerhaus suchen
412
    else if(!goal)
62✔
413
    {
414
        // Wenn wir cur_rs == 0, dann hängen wir wahrscheinlich noch im Lagerhaus in der Warteschlange
415
        if(cur_rs == nullptr)
58✔
416
        {
417
            RTTR_Assert(nobBaseWarehouse::isStorehouseGOT(world->GetNO(pos)->GetGOT()));
39✔
418
            goal_ = nullptr;
39✔
419
            world->GetSpecObj<nobBaseWarehouse>(pos)->CancelFigure(this);
39✔
420
            return;
39✔
421
        } else
422
            this->goal_ = world->GetPlayer(player).FindWarehouse((rs_dir) ? *cur_rs->GetF1() : *cur_rs->GetF2(),
38✔
423
                                                                 FW::AcceptsFigure(job_), true, false);
38✔
424
    } else
425
        this->goal_ = goal;
4✔
426

427
    if(this->goal_)
23✔
428
    {
429
        fs = FigureState::GoHome;
22✔
430
        // Lagerhaus Bescheid sagen
431
        static_cast<nobBaseWarehouse*>(this->goal_)->AddDependentFigure(*this);
22✔
432

433
        // Wenn wir stehen, zusätzlich noch loslaufen!
434
        if(waiting_for_free_node)
22✔
435
        {
UNCOV
436
            waiting_for_free_node = false;
×
UNCOV
437
            WalkToGoal();
×
438
            // anderen Leuten noch ggf Bescheid sagen
UNCOV
439
            world->RoadNodeAvailable(pos);
×
440
        }
441
    } else
442
    {
443
        // Kein Lagerhaus gefunden --> Rumirren
444
        StartWandering();
1✔
445
        cur_rs = nullptr;
1✔
446
    }
447
}
448

449
void noFigure::StartWandering(const unsigned burned_wh_id)
47✔
450
{
451
    RTTR_Assert(HasNoGoal());
47✔
452
    fs = FigureState::Wander;
47✔
453
    cur_rs = nullptr;
47✔
454
    rs_pos = 0;
47✔
455
    this->burned_wh_id = burned_wh_id;
47✔
456
    // eine bestimmte Strecke rumirren und dann eine Flagge suchen
457
    // 3x rumirren und eine Flagge suchen, wenn dann keine gefunden wurde, stirbt die Figur
458
    wander_way = WANDER_WAY_MIN + RANDOM_RAND(WANDER_WAY_MAX - WANDER_WAY_MIN);
47✔
459
    // Soldaten sind härter im Nehmen
460
    wander_tryings = IsSoldier() ? WANDER_TRYINGS_SOLDIERS : WANDER_TRYINGS;
47✔
461

462
    // Wenn wir stehen, zusätzlich noch loslaufen!
463
    if(waiting_for_free_node)
47✔
464
    {
UNCOV
465
        waiting_for_free_node = false;
×
466
        // We should be paused, so just continue moving now
UNCOV
467
        if(IsStoppedBetweenNodes())
×
UNCOV
468
            StartMoving(GetCurMoveDir(), GetPausedEvent().length);
×
469
        else
UNCOV
470
            Wander();
×
471
    }
472
}
47✔
473

474
namespace {
475
struct Point2Flag
476
{
477
    World& gwb;
478
    Point2Flag(World& gwb) : gwb(gwb) {}
8✔
479
    noFlag* operator()(const MapPoint pt, unsigned /*r*/) const { return gwb.GetSpecObj<noFlag>(pt); }
3,030✔
480
};
481

482
struct IsValidFlag
483
{
484
    const unsigned playerId_;
485
    IsValidFlag(const unsigned playerId) : playerId_(playerId) {}
8✔
486
    bool operator()(const noFlag* const flag) const { return flag && flag->GetPlayer() == playerId_; }
3,030✔
487
};
488
} // namespace
489

490
void noFigure::Wander()
415✔
491
{
492
    // Sind wir noch auf der Suche nach einer Flagge?
493
    if(wander_way == 0xFFFF)
415✔
494
    {
495
        // Wir laufen schon zur Flagge
496
        WanderToFlag();
21✔
497
        return;
21✔
498
    }
499

500
    // Ist es mal wieder an der Zeit, eine Flagge zu suchen?
501
    if(!wander_way)
394✔
502
    {
503
        // Soldaten sind härter im Nehmen
504
        const unsigned short wander_radius = IsSoldier() ? WANDER_RADIUS_SOLDIERS : WANDER_RADIUS;
8✔
505

506
        // Flaggen sammeln und dann zufällig eine auswählen
507
        const std::vector<noFlag*> flags =
508
          world->GetPointsInRadius(pos, wander_radius, Point2Flag(*world), IsValidFlag(player));
8✔
509

510
        unsigned best_way = 0xFFFFFFFF;
8✔
511
        const noFlag* best_flag = nullptr;
8✔
512

513
        for(auto* flag : flags)
30✔
514
        {
515
            // Ist das ein Flüchtling aus einem abgebrannten Lagerhaus?
516
            if(burned_wh_id != 0xFFFFFFFF)
22✔
517
            {
518
                // Dann evtl gucken, ob anderen Mitglieder schon gesagt haben, dass die Flagge nicht zugänglich ist
519
                if(flag->IsImpossibleForBWU(burned_wh_id))
21✔
520
                {
521
                    // Dann können wir die Flagge überspringen
UNCOV
522
                    continue;
×
523
                }
524
            }
525

526
            // würde die die bisher beste an Weg unterbieten?
527
            unsigned way = world->CalcDistance(pos, flag->GetPos());
22✔
528
            if(way < best_way)
22✔
529
            {
530
                // Are we at that flag or is there a path to it?
531
                if(way == 0 || world->FindHumanPath(pos, flag->GetPos(), wander_radius, false, &way))
8✔
532
                {
533
                    // gucken, ob ein Weg zu einem Warenhaus führt
534
                    if(world->GetPlayer(player).FindWarehouse(*flag, FW::AcceptsFigure(job_), true, false))
8✔
535
                    {
536
                        // dann nehmen wir die doch glatt
537
                        best_way = way;
7✔
538
                        best_flag = flag;
7✔
539
                        if(way == 0)
7✔
UNCOV
540
                            break; // Can't get better
×
541
                    }
UNCOV
542
                } else if(burned_wh_id != 0xFFFFFFFF)
×
543
                {
544
                    // Flagge nicht möglich zugänglich bei einem Flüchting aus einem abgebrannten Lagerhaus?
545
                    // --> der ganzen Gruppe Bescheid sagen, damit die nicht auch alle sinnlos einen Weg zu
546
                    // dieser Flagge suchen
547

548
                    // TODO: Actually it is possible! E.g. between us and the flag is a river, so we won't find a path
549
                    // within the radius but others (on the other side) could --> Remove ImpossibleForBWU?
UNCOV
550
                    flag->ImpossibleForBWU(burned_wh_id);
×
551
                }
552
            }
553
        }
554

555
        if(best_flag)
8✔
556
        {
557
            // bestmögliche schließlich nehmen
558
            wander_way = 0xFFFF;
7✔
559
            flagPos_ = best_flag->GetPos();
7✔
560
            flag_obj_id = best_flag->GetObjId();
7✔
561
            WanderToFlag();
7✔
562
            return;
7✔
563
        }
564

565
        // Wurde keine Flagge gefunden?
566

567
        // Haben wir noch Versuche?
568
        RTTR_Assert(wander_tryings > 0);
1✔
569
        if(--wander_tryings > 0)
1✔
570
        {
571
            // von vorne beginnen wieder mit Rumirren
572
            wander_way = WANDER_WAY_MIN + RANDOM_RAND(WANDER_WAY_MAX - WANDER_WAY_MIN);
1✔
573
        } else
574
        {
575
            // Genug rumgeirrt, wir finden halt einfach nichts --> Sterben
UNCOV
576
            Die();
×
UNCOV
577
            return;
×
578
        }
579
    }
580

581
    if(WalkInRandomDir())
387✔
582
    {
583
        RTTR_Assert(wander_way > 0);
387✔
584
        --wander_way;
387✔
585
    } else
586
    {
587
        // Wir sind eingesperrt! Kein Weg mehr gefunden --> Sterben
UNCOV
588
        Die();
×
589
    }
590
}
591

592
bool noFigure::WalkInRandomDir()
387✔
593
{
594
    PathConditionHuman pathChecker(*world);
387✔
595
    // Check all dirs starting with a random one and taking the first possible
596
    for(const Direction dir : helpers::enumRange(RANDOM_ENUM(Direction)))
2,071✔
597
    {
598
        if(pathChecker.IsNodeOk(world->GetNeighbour(pos, dir)) && pathChecker.IsEdgeOk(pos, dir))
1,303✔
599
        {
600
            StartWalking(dir);
387✔
601
            return true;
387✔
602
        }
603
    }
604
    return false;
×
605
}
606

607
void noFigure::WanderFailedTrade()
6✔
608
{
609
    DieFailedTrade();
6✔
610
}
6✔
611

612
void noFigure::WanderToFlag()
28✔
613
{
614
    // Existiert die Flagge überhaupt noch?
615
    noBase* no = world->GetNO(flagPos_);
28✔
616
    if(no->GetObjId() != flag_obj_id)
28✔
617
    {
618
        // Wenn nicht, wieder normal weiter rumirren
UNCOV
619
        StartWandering();
×
620
        Wander();
×
621
        return;
5✔
622
    }
623

624
    // Sind wir schon da?
625
    if(pos == flagPos_)
28✔
626
    {
627
        // Gibts noch nen Weg zu einem Lagerhaus?
628
        RTTR_Assert(world->GetSpecObj<noRoadNode>(pos));
5✔
629
        if(nobBaseWarehouse* wh = world->GetPlayer(player).FindWarehouse(*world->GetSpecObj<noRoadNode>(pos),
10✔
630
                                                                         FW::AcceptsFigure(job_), true, false))
10✔
631
        {
632
            // ja, dann können wir ja hingehen
633
            goal_ = wh;
5✔
634
            cur_rs = nullptr;
5✔
635
            rs_pos = 0;
5✔
636
            fs = FigureState::GoHome;
5✔
637
            wh->AddDependentFigure(*this);
5✔
638
            WalkToGoal();
5✔
639
            return;
5✔
640
        } else
641
        {
642
            // Wenn nicht, wieder normal weiter rumirren
UNCOV
643
            StartWandering();
×
UNCOV
644
            Wander();
×
UNCOV
645
            return;
×
646
        }
647
    }
648

649
    // Weiter zur Flagge gehen
650
    // Gibts noch nen Weg dahin bzw. existiert die Flagge noch?
651
    const auto dir = world->FindHumanPath(pos, flagPos_, 60, false);
23✔
652
    if(dir)
23✔
653
    {
654
        // weiter hinlaufen
655
        StartWalking(*dir);
23✔
656
    } else
657
    {
658
        // Wenn nicht, wieder normal weiter rumirren
659
        StartWandering();
×
660
        Wander();
×
661
    }
662
}
663

UNCOV
664
void noFigure::CorrectSplitData(const RoadSegment* const rs2)
×
665
{
666
    // cur_rs entspricht Teilstück 1 !
667

668
    // Wenn man sich auf den ersten Teilstück befindet...
UNCOV
669
    if((rs_pos < cur_rs->GetLength() && !rs_dir) || (rs_pos > rs2->GetLength() && rs_dir))
×
670
    {
671
        // Nur Position berichtigen
UNCOV
672
        if(rs_dir)
×
UNCOV
673
            rs_pos -= rs2->GetLength();
×
674
    }
675

676
    // Wenn man auf dem 2. steht, ...
UNCOV
677
    else if((rs_pos > cur_rs->GetLength() && !rs_dir) || (rs_pos < rs2->GetLength() && rs_dir))
×
678
    {
679
        // Position berichtigen (wenn man in umgekehrter Richtung läuft, beibehalten!)
680
        if(!rs_dir)
×
UNCOV
681
            rs_pos -= cur_rs->GetLength();
×
682

683
        // wir laufen auf dem 2. Teilstück
UNCOV
684
        cur_rs = rs2;
×
685
    } else if((rs_pos == cur_rs->GetLength() && !rs_dir) || (rs_pos == rs2->GetLength() && rs_dir))
×
686
    {
687
        // wir stehen genau in der Mitte
688
        // abhängig von der Richtung machen, in die man gerade läuft
689
        if(GetCurMoveDir() == rs2->GetRoute(0))
×
690
        {
691
            // wir laufen auf dem 2. Teilstück
UNCOV
692
            cur_rs = rs2;
×
693
            // und wir sind da noch am Anfang
UNCOV
694
            rs_pos = 0;
×
UNCOV
695
        } else if(GetCurMoveDir() == cur_rs->GetRoute(cur_rs->GetLength() - 1) + 3u)
×
696
        {
697
            // wir laufen auf dem 1. Teilstück
698

699
            // und wir sind da noch am Anfang
700
            rs_pos = 0;
×
701
        } else
702
        {
703
            // Wahrscheinlich stehen wir
704
            // dann einfach auf das 2. gehen
705
            cur_rs = rs2;
×
UNCOV
706
            rs_pos = 0;
×
UNCOV
707
            rs_dir = false;
×
708
        }
709
    }
710

711
    CorrectSplitData_Derived();
×
UNCOV
712
}
×
713

714
/// Wird aufgerufen, wenn die Straße unter der Figur geteilt wurde (für abgeleitete Klassen)
UNCOV
715
void noFigure::CorrectSplitData_Derived() {}
×
716

UNCOV
717
unsigned noFigure::CalcWalkAnimationFrame() const
×
718
{
719
    // If we are waiting for a free node use the 2nd frame, else interpolate
UNCOV
720
    return waiting_for_free_node ? 2 : GAMECLIENT.Interpolate(ASCENT_ANIMATION_STEPS[GetAscent()], current_ev) % 8;
×
721
}
722

723
/// Calculate the index of the current frame for a figure walking in the given direction
724
/// The sprites start at `imgSetIndex` and contain 8 images per direction, starting at EAST going clockwise.
725
/// Note that some of the 48 indices might be empty entries in the archive if only specific directions can ever be drawn
UNCOV
726
unsigned noFigure::calcWalkFrameIndex(const unsigned imgSetIndex, const Direction dir, const unsigned animationStep)
×
727
{
728
    RTTR_Assert(animationStep < 8);
×
UNCOV
729
    return imgSetIndex + rttr::enum_cast(toImgDir(dir)) * 8 + animationStep;
×
730
}
731

UNCOV
732
DrawPoint noFigure::InterpolateWalkDrawPos(DrawPoint drawPt) const
×
733
{
734
    // Add an offset relative to the starting point calculated by how far we already walked
735
    // Don't if we are waiting on our starting node
736
    if(!waiting_for_free_node || IsStoppedBetweenNodes())
×
UNCOV
737
        drawPt += CalcFigurRelative();
×
UNCOV
738
    return drawPt;
×
739
}
740

UNCOV
741
void noFigure::DrawWalkingCarrier(DrawPoint drawPt, helpers::OptionalEnum<GoodType> ware, bool fat)
×
742
{
UNCOV
743
    const unsigned ani_step = CalcWalkAnimationFrame();
×
744
    const GamePlayer& owner = world->GetPlayer(player);
×
745
    auto& sprite = ware ? LOADER.getCarrierSprite(*ware, fat, GetCurMoveDir(), ani_step) :
×
UNCOV
746
                          LOADER.getCarrierBobSprite(owner.nation, fat, GetCurMoveDir(), ani_step);
×
UNCOV
747
    sprite.drawForPlayer(InterpolateWalkDrawPos(drawPt), owner.color);
×
748
}
×
749

UNCOV
750
void noFigure::DrawWalkingBobJobs(DrawPoint drawPt, Job job)
×
751
{
752
    const unsigned ani_step = CalcWalkAnimationFrame();
×
753
    const GamePlayer& owner = world->GetPlayer(player);
×
754
    LOADER.getBobSprite(owner.nation, job, GetCurMoveDir(), ani_step)
×
UNCOV
755
      .drawForPlayer(InterpolateWalkDrawPos(drawPt), owner.color);
×
UNCOV
756
}
×
757

UNCOV
758
void noFigure::DrawWalking(DrawPoint drawPt, glArchivItem_Bob* file, unsigned id, bool fat)
×
759
{
760
    const unsigned ani_step = CalcWalkAnimationFrame();
×
761
    drawPt = InterpolateWalkDrawPos(drawPt);
×
762

763
    if(file)
×
764
        file->Draw(id, toImgDir(GetCurMoveDir()), fat, ani_step, drawPt, world->GetPlayer(player).color);
×
UNCOV
765
    DrawShadow(drawPt, ani_step, GetCurMoveDir());
×
766
}
×
767

768
void noFigure::DrawWalking(DrawPoint drawPt, const ResourceId& file, unsigned id)
×
769
{
770
    const unsigned ani_step = CalcWalkAnimationFrame();
×
771
    drawPt = InterpolateWalkDrawPos(drawPt);
×
772

UNCOV
773
    LOADER.GetPlayerImage(file, calcWalkFrameIndex(id, GetCurMoveDir(), ani_step))
×
774
      ->DrawFull(drawPt, COLOR_WHITE, world->GetPlayer(player).color);
×
UNCOV
775
    DrawShadow(drawPt, ani_step, GetCurMoveDir());
×
776
}
×
777

UNCOV
778
void noFigure::DrawWalking(DrawPoint drawPt)
×
779
{
780
    switch(job_)
×
781
    {
782
        case Job::PackDonkey:
×
783
        {
784
            const unsigned ani_step = CalcWalkAnimationFrame();
×
UNCOV
785
            drawPt = InterpolateWalkDrawPos(drawPt);
×
786

787
            LOADER.GetMapTexture(calcWalkFrameIndex(2000, GetCurMoveDir(), ani_step))->DrawFull(drawPt);
×
788
            // donkey shadow
789
            LOADER.GetMapTexture(2048 + rttr::enum_cast(GetCurMoveDir()) % 3)->DrawFull(drawPt, COLOR_SHADOW);
×
790
        }
791
        break;
×
792
        case Job::CharBurner: DrawWalking(drawPt, "charburner_bobs", 53); break;
×
UNCOV
793
        case Job::Vintner:
×
794
            DrawWalking(drawPt, "wine_bobs", wineaddon::bobIndex[wineaddon::BobTypes::VINTNER_WALKING]);
×
UNCOV
795
            break;
×
796
        case Job::Winegrower:
×
UNCOV
797
            DrawWalking(drawPt, "wine_bobs", wineaddon::bobIndex[wineaddon::BobTypes::WINEGROWER_WALKING_WITH_SHOVEL]);
×
798
            break;
×
UNCOV
799
        case Job::TempleServant:
×
800
            DrawWalking(drawPt, "wine_bobs", wineaddon::bobIndex[wineaddon::BobTypes::TEMPLESERVANT_WALKING]);
×
801
            break;
×
UNCOV
802
        default: DrawWalkingBobJobs(drawPt, job_); break;
×
803
    }
UNCOV
804
}
×
805

UNCOV
806
void noFigure::Die()
×
807
{
808
    // Weg mit mir
809
    GetEvMgr().AddToKillList(world->RemoveFigure(pos, *this));
×
810
    // ggf. Leiche hinlegen, falls da nix ist
811
    if(!world->GetSpecObj<noBase>(pos))
×
812
        world->SetNO(pos, new noSkeleton(pos));
×
813

814
    RemoveFromInventory();
×
815

816
    // Sichtbarkeiten neu berechnen für Erkunder und Soldaten
817
    CalcVisibilities(pos);
×
818
}
×
819

820
void noFigure::RemoveFromInventory()
×
821
{
822
    // Wars ein Bootmann? Dann Boot und Träger abziehen
UNCOV
823
    if(job_ == Job::BoatCarrier)
×
824
    {
825
        world->GetPlayer(player).DecreaseInventoryJob(Job::Helper, 1);
×
UNCOV
826
        world->GetPlayer(player).DecreaseInventoryWare(GoodType::Boat, 1);
×
827
    } else
828
        world->GetPlayer(player).DecreaseInventoryJob(job_, 1);
×
UNCOV
829
}
×
830

831
void noFigure::DieFailedTrade()
6✔
832
{
833
    // Weg mit mir
834
    GetEvMgr().AddToKillList(world->RemoveFigure(pos, *this));
6✔
835
    // ggf. Leiche hinlegen, falls da nix ist
836
    if(!world->GetSpecObj<noBase>(pos))
6✔
837
        world->SetNO(pos, new noSkeleton(pos));
6✔
838
}
6✔
839

840
void noFigure::NodeFreed(const MapPoint pt)
162✔
841
{
842
    // Stehen wir gerade aus diesem Grund?
843
    if(!waiting_for_free_node || pt != world->GetNeighbour(this->pos, GetCurMoveDir()))
162✔
844
        return;
161✔
845

846
    // Gehen wir in ein Gebäude? Dann wieder ausgleichen, weil wir die Türen sonst doppelt aufmachen!
847
    if(GetCurMoveDir() == Direction::NorthWest
1✔
848
       && world->GetNO(world->GetNeighbour(this->pos, Direction::NorthWest))->GetType() == NodalObjectType::Building)
1✔
UNCOV
849
        world->GetSpecObj<noBuilding>(world->GetNeighbour(this->pos, Direction::NorthWest))->CloseDoor();
×
850
    // oder aus einem raus?
851
    if(GetCurMoveDir() == Direction::SouthEast && world->GetNO(this->pos)->GetType() == NodalObjectType::Building)
1✔
UNCOV
852
        world->GetSpecObj<noBuilding>(this->pos)->CloseDoor();
×
853

854
    // Wir stehen nun nicht mehr
855
    waiting_for_free_node = false;
1✔
856

857
    // Dann loslaufen
858
    StartWalking(GetCurMoveDir());
1✔
859

860
    // anderen Leuten noch ggf Bescheid sagen
861
    world->RoadNodeAvailable(this->pos);
1✔
862
}
863

864
void noFigure::Abrogate()
7✔
865
{
866
    // Arbeisplatz oder Laghaus Bescheid sagen
867
    if(fs == FigureState::GoHome)
7✔
868
    {
869
        // goal might by nullptr if goal was a harbor that got destroyed during sea travel
870
        if(goal_)
1✔
871
        {
872
            checkedCast<nobBaseWarehouse*>(goal_)->RemoveDependentFigure(*this);
1✔
873
            goal_ = nullptr;
1✔
874
        } else
875
        {
UNCOV
876
            if(!on_ship) // no goal but going home - should not happen
×
877
            {
UNCOV
878
                LOG.write("noFigure::Abrogate - GOHOME figure has no goal and is not on a ship - player %i state %i "
×
879
                          "pos %u,%u \n")
UNCOV
880
                  % player % rttr::enum_cast(fs) % pos.x % pos.y;
×
881
                // RTTR_Assert(false);
882
            }
883
        }
884
    } else
885
    {
886
        goal_ = nullptr;
6✔
887
        AbrogateWorkplace();
6✔
888
    }
889
}
7✔
890

891
void noFigure::StopIfNecessary(const MapPoint pt)
39✔
892
{
893
    // Lauf ich auf Wegen --> wenn man zum Ziel oder Weg läuft oder die Träger, die natürlich auch auf Wegen arbeiten
894
    if(fs == FigureState::GoHome || fs == FigureState::GotToGoal
39✔
895
       || (fs == FigureState::Job && GetGOT() == GO_Type::NofCarrier))
78✔
896
    {
897
        // Laufe ich zu diesem Punkt?
898
        if(current_ev && !waiting_for_free_node && world->GetNeighbour(this->pos, GetCurMoveDir()) == pt)
3✔
899
        {
900
            // Dann stehenbleiben
901
            PauseWalking();
1✔
902
            waiting_for_free_node = true;
1✔
903
            world->StopOnRoads(this->pos, GetCurMoveDir());
1✔
904
        }
905
    }
906
}
39✔
907

908
/// Sichtbarkeiten berechnen für Figuren mit Sichtradius (Soldaten, Erkunder) vor dem Laufen
909
void noFigure::CalcVisibilities(const MapPoint pt)
51✔
910
{
911
    const unsigned visualRange = GetVisualRange();
51✔
912
    if(visualRange)
51✔
913
        // An alter Position neu berechnen
914
        world->RecalcVisibilitiesAroundPoint(pt, visualRange, player, nullptr);
51✔
915
}
51✔
916

917
/// Informiert die Figur, dass für sie eine Schiffsreise beginnt
918
void noFigure::StartShipJourney()
4✔
919
{
920
    // We should not be in the world, as we start the journey from a harbor -> We are in that harbor
921
    RTTR_Assert(!world->HasFigureAt(pos, *this));
4✔
922
    RTTR_Assert(!on_ship);
4✔
923

924
    pos = MapPoint::Invalid();
4✔
925
    on_ship = true;
4✔
926
}
4✔
927

928
void noFigure::ArrivedByShip(const MapPoint harborPos)
2✔
929
{
930
    RTTR_Assert(on_ship);
2✔
931
    pos = harborPos;
2✔
932
    on_ship = false;
2✔
933
}
2✔
934

935
/// Examines the route (maybe harbor, road destroyed?) before start shipping
936
MapPoint noFigure::ExamineRouteBeforeShipping(RoadPathDirection& newDir)
2✔
937
{
938
    MapPoint next_harbor;
2✔
939
    // Calc new route
940
    const noRoadNode* roadNode = world->GetSpecObj<noRoadNode>(pos);
2✔
941
    if(!roadNode || !goal_)
2✔
UNCOV
942
        newDir = RoadPathDirection::None;
×
943
    else
944
        newDir = world->FindHumanPathOnRoads(*roadNode, *goal_, nullptr, &next_harbor);
2✔
945

946
    if(newDir == RoadPathDirection::None)
2✔
UNCOV
947
        Abrogate();
×
948

949
    // Going by ship?
950
    if(newDir == RoadPathDirection::Ship)
2✔
951
        // All ok, return next harbor (could be another one!)
UNCOV
952
        return next_harbor;
×
953
    else
954
        return MapPoint(0, 0);
2✔
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