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

Return-To-The-Roots / s25client / 24151579386

08 Apr 2026 06:24PM UTC coverage: 50.371% (+0.03%) from 50.337%
24151579386

push

github

Flow86
Use `helpers::sort` consistently

14 of 18 new or added lines in 12 files covered. (77.78%)

840 existing lines in 36 files now uncovered.

23069 of 45798 relevant lines covered (50.37%)

45802.94 hits per line

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

69.3
/libs/s25main/nodeObjs/noShip.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 "noShip.h"
6
#include "EventManager.h"
7
#include "GameEvent.h"
8
#include "GamePlayer.h"
9
#include "GlobalGameSettings.h"
10
#include "Loader.h"
11
#include "SerializedGameData.h"
12
#include "Ware.h"
13
#include "addons/const_addons.h"
14
#include "buildings/nobHarborBuilding.h"
15
#include "figures/noFigure.h"
16
#include "figures/nofAttacker.h"
17
#include "helpers/EnumArray.h"
18
#include "helpers/containerUtils.h"
19
#include "helpers/pointerContainerUtils.h"
20
#include "network/GameClient.h"
21
#include "notifications/ExpeditionNote.h"
22
#include "notifications/ShipNote.h"
23
#include "ogl/glArchivItem_Bitmap.h"
24
#include "ogl/glArchivItem_Bitmap_Player.h"
25
#include "postSystem/ShipPostMsg.h"
26
#include "random/Random.h"
27
#include "world/GameWorld.h"
28
#include "gameData/BuildingConsts.h"
29
#include "gameData/ShipNames.h"
30
#include "s25util/Log.h"
31
#include <array>
32

33
/// Zeit zum Beladen des Schiffes
34
const unsigned LOADING_TIME = 200;
35
/// Zeit zum Entladen des Schiffes
36
const unsigned UNLOADING_TIME = 200;
37

38
/// Maximaler Weg, der zurückgelegt werden kann bei einem Erkundungsschiff
39
const unsigned MAX_EXPLORATION_EXPEDITION_DISTANCE = 100;
40
/// Zeit (in GF), die das Schiff bei der Erkundungs-Expedition jeweils an einem Punkt ankert
41
const unsigned EXPLORATION_EXPEDITION_WAITING_TIME = 300;
42

43
/// Positionen der Flaggen am Schiff für die 6 unterschiedlichen Richtungen jeweils
44
constexpr std::array<helpers::EnumArray<DrawPoint, Direction>, 2> SHIPS_FLAG_POS = {{
45
  {{{-3, -77}, {-6, -71}, {-3, -71}, {-1, -71}, {5, -63}, {-1, -70}}}, // Standing (sails down)
46
  {{{3, -70}, {0, -64}, {3, -64}, {-1, -70}, {5, -63}, {5, -63}}}      // Driving
47
}};
48

49
noShip::noShip(const MapPoint pos, const unsigned char player)
22✔
50
    : noMovable(NodalObjectType::Ship, pos), ownerId_(player), state(State::Idle), goal_dir(0),
51
      name(RANDOM_ELEMENT(ship_names[world->GetPlayer(player).nation])), curRouteIdx(0), lost(false),
22✔
52
      remaining_sea_attackers(0), covered_distance(0)
44✔
53
{
54
    // Meer ermitteln, auf dem dieses Schiff fährt
55
    for(const auto dir : helpers::EnumRange<Direction>{})
352✔
56
    {
57
        SeaId seaId = world->GetNeighbourNode(pos, dir).seaId;
132✔
58
        if(seaId)
132✔
59
            this->seaId_ = seaId;
124✔
60
    }
61

62
    // Auf irgendeinem Meer müssen wir ja sein
63
    RTTR_Assert(seaId_);
22✔
64
}
22✔
65

66
noShip::~noShip() = default;
44✔
67

68
void noShip::Serialize(SerializedGameData& sgd) const
×
69
{
70
    noMovable::Serialize(sgd);
×
71

72
    sgd.PushUnsignedChar(ownerId_);
×
73
    sgd.PushEnum<uint8_t>(state);
×
74
    sgd.PushUnsignedShort(seaId_.value());
×
75
    sgd.PushUnsignedInt(goalHarbor.value());
×
76
    sgd.PushUnsignedChar(goal_dir);
×
77
    sgd.PushString(name);
×
78
    sgd.PushUnsignedInt(curRouteIdx);
×
79
    sgd.PushBool(lost);
×
80
    sgd.PushUnsignedInt(remaining_sea_attackers);
×
81
    sgd.PushUnsignedInt(homeHarbor.value());
×
82
    sgd.PushUnsignedInt(covered_distance);
×
83
    helpers::pushContainer(sgd, route_);
×
84
    sgd.PushObjectContainer(figures);
×
85
    sgd.PushObjectContainer(wares, true);
×
86
}
×
87

88
noShip::noShip(SerializedGameData& sgd, const unsigned obj_id)
×
89
    : noMovable(sgd, obj_id), ownerId_(sgd.PopUnsignedChar()), state(sgd.Pop<State>()), seaId_(sgd.PopUnsignedShort()),
×
90
      goalHarbor(sgd.PopUnsignedInt()), goal_dir(sgd.PopUnsignedChar()),
×
91
      name(sgd.GetGameDataVersion() < 2 ? sgd.PopLongString() : sgd.PopString()), curRouteIdx(sgd.PopUnsignedInt()),
×
92
      route_(sgd.GetGameDataVersion() < 7 ? sgd.PopUnsignedInt() : 0), lost(sgd.PopBool()),
×
93
      remaining_sea_attackers(sgd.PopUnsignedInt()), homeHarbor(sgd.PopUnsignedInt()),
×
94
      covered_distance(sgd.PopUnsignedInt())
×
95
{
96
    helpers::popContainer(sgd, route_, sgd.GetGameDataVersion() < 7);
×
97
    sgd.PopObjectContainer(figures);
×
98
    sgd.PopObjectContainer(wares, GO_Type::Ware);
×
99
}
×
100

101
void noShip::Destroy()
×
102
{
103
    RTTR_Assert(figures.empty());
×
104
    RTTR_Assert(wares.empty());
×
105
    world->GetNotifications().publish(ShipNote(ShipNote::Destroyed, ownerId_, pos));
×
106
    // Schiff wieder abmelden
107
    world->GetPlayer(ownerId_).RemoveShip(this);
×
108
}
×
109

110
void noShip::Draw(DrawPoint drawPt)
×
111
{
112
    unsigned flag_drawing_type = 1;
×
113

114
    // Sind wir verloren? Dann immer stehend zeichnen
115
    if(lost)
×
116
    {
117
        DrawFixed(drawPt, true);
×
118
        return;
×
119
    }
120

121
    switch(state)
×
122
    {
123
        default: break;
×
124
        case State::Idle:
×
125
        case State::SeaattackWaiting:
126
        {
127
            DrawFixed(drawPt, false);
×
128
            flag_drawing_type = 0;
×
129
        }
130
        break;
×
131

132
        case State::Gotoharbor:
×
133
        {
134
            DrawDriving(drawPt);
×
135
        }
136
        break;
×
137
        case State::ExpeditionLoading:
×
138
        case State::ExpeditionUnloading:
139
        case State::TransportLoading:
140
        case State::TransportUnloading:
141
        case State::SeaattackLoading:
142
        case State::SeaattackUnloading:
143
        case State::ExplorationexpeditionLoading:
144
        case State::ExplorationexpeditionUnloading:
145
        {
146
            DrawFixed(drawPt, false);
×
147
        }
148
        break;
×
149
        case State::ExplorationexpeditionWaiting:
×
150
        case State::ExpeditionWaiting:
151
        {
152
            DrawFixed(drawPt, true);
×
153
        }
154
        break;
×
155
        case State::ExpeditionDriving:
×
156
        case State::TransportDriving:
157
        case State::SeaattackDrivingToDestination:
158
        case State::ExplorationexpeditionDriving:
159
        {
160
            DrawDrivingWithWares(drawPt);
×
161
        }
162
        break;
×
163
        case State::SeaattackReturnDriving:
×
164
        {
165
            if(!figures.empty() || !wares.empty())
×
166
                DrawDrivingWithWares(drawPt);
×
167
            else
168
                DrawDriving(drawPt);
×
169
        }
170
        break;
×
171
    }
172

173
    LOADER.GetPlayerImage("boot_z", 40 + GAMECLIENT.GetGlobalAnimation(6, 1, 1, GetObjId()))
×
174
      ->DrawFull(drawPt + SHIPS_FLAG_POS[flag_drawing_type][GetCurMoveDir()], COLOR_WHITE,
×
175
                 world->GetPlayer(ownerId_).color);
×
176
    // Second, white flag, only when on expedition, always swinging in the opposite direction
177
    if(state >= State::ExpeditionLoading && state <= State::ExpeditionDriving)
×
178
        LOADER.GetPlayerImage("boot_z", 40 + GAMECLIENT.GetGlobalAnimation(6, 1, 1, GetObjId() + 4))
×
179
          ->DrawFull(drawPt + SHIPS_FLAG_POS[flag_drawing_type][GetCurMoveDir()]);
×
180
}
181

182
/// Zeichnet das Schiff stehend mit oder ohne Waren
183
void noShip::DrawFixed(DrawPoint drawPt, const bool draw_wares)
×
184
{
185
    LOADER.GetImageN("boot_z", rttr::enum_cast(GetCurMoveDir() + 3u) * 2 + 1)->DrawFull(drawPt, COLOR_SHADOW);
×
186
    LOADER.GetImageN("boot_z", rttr::enum_cast(GetCurMoveDir() + 3u) * 2)->DrawFull(drawPt);
×
187

188
    if(draw_wares)
×
189
        /// Waren zeichnen
190
        LOADER.GetImageN("boot_z", 30 + rttr::enum_cast(GetCurMoveDir() + 3u))->DrawFull(drawPt);
×
191
}
×
192

193
/// Zeichnet normales Fahren auf dem Meer ohne irgendwelche Güter
194
void noShip::DrawDriving(DrawPoint& drawPt)
×
195
{
196
    // Interpolieren zwischen beiden Knotenpunkten
197
    drawPt += CalcWalkingRelative();
×
198

199
    LOADER.GetImageN("boot_z", 13 + rttr::enum_cast(GetCurMoveDir() + 3u) * 2)->DrawFull(drawPt, COLOR_SHADOW);
×
200
    LOADER.GetImageN("boot_z", 12 + rttr::enum_cast(GetCurMoveDir() + 3u) * 2)->DrawFull(drawPt);
×
201
}
×
202

203
/// Zeichnet normales Fahren auf dem Meer mit Gütern
204
void noShip::DrawDrivingWithWares(DrawPoint& drawPt)
×
205
{
206
    DrawDriving(drawPt);
×
207
    /// Waren zeichnen
208
    LOADER.GetImageN("boot_z", 30 + rttr::enum_cast(GetCurMoveDir() + 3u))->DrawFull(drawPt);
×
209
}
×
210

211
void noShip::HandleEvent(const unsigned id)
428✔
212
{
213
    RTTR_Assert(current_ev);
428✔
214
    RTTR_Assert(current_ev->id == id);
428✔
215
    current_ev = nullptr;
428✔
216

217
    if(id == 0)
428✔
218
    {
219
        // Move event
220
        // neue Position einnehmen
221
        Walk();
396✔
222
        // entscheiden, was als nächstes zu tun ist
223
        Driven();
396✔
224
    } else
225
    {
226
        switch(state)
32✔
227
        {
228
            default:
×
229
                RTTR_Assert(false);
×
230
                LOG.write("Bug detected: Invalid state in ship event");
231
                break;
232
            case State::ExpeditionLoading:
3✔
233
                // Schiff ist nun bereit und Expedition kann beginnen
234
                state = State::ExpeditionWaiting;
3✔
235

236
                // Spieler benachrichtigen
237
                SendPostMessage(ownerId_, std::make_unique<ShipPostMsg>(GetEvMgr().GetCurrentGF(),
3✔
238
                                                                        _("A ship is ready for an expedition."),
3✔
239
                                                                        PostCategory::Economy, *this));
3✔
240
                world->GetNotifications().publish(ExpeditionNote(ExpeditionNote::Waiting, ownerId_, pos));
3✔
241
                break;
3✔
242
            case State::ExplorationexpeditionLoading:
8✔
243
            case State::ExplorationexpeditionWaiting:
244
                // Schiff ist nun bereit und Expedition kann beginnen
245
                ContinueExplorationExpedition();
8✔
246
                break;
8✔
247
            case State::ExpeditionUnloading:
1✔
248
            {
249
                // Hafen herausfinden
250
                noBase* hb = goalHarbor ? world->GetNO(world->GetHarborPoint(goalHarbor)) : nullptr;
1✔
251

252
                if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding)
1✔
253
                {
254
                    GoodsAndPeopleCounts goods;
1✔
255
                    goods.goods[GoodType::Boards] = BUILDING_COSTS[BuildingType::HarborBuilding].boards;
1✔
256
                    goods.goods[GoodType::Stones] = BUILDING_COSTS[BuildingType::HarborBuilding].stones;
1✔
257
                    goods.people[Job::Builder] = 1;
1✔
258
                    static_cast<nobBaseWarehouse*>(hb)->AddToInventory(goods, false);
1✔
259
                    // Wieder idlen und ggf. neuen Job suchen
260
                    StartIdling();
1✔
261
                    world->GetPlayer(ownerId_).GetJobForShip(*this);
1✔
262
                } else
263
                {
264
                    // target harbor for unloading doesnt exist anymore -> set state to driving and handle the new state
265
                    state = State::ExpeditionDriving;
×
266
                    HandleState_ExpeditionDriving();
×
267
                }
268
                break;
1✔
269
            }
270
            case State::ExplorationexpeditionUnloading:
3✔
271
            {
272
                // Hafen herausfinden
273
                noBase* hb = goalHarbor ? world->GetNO(world->GetHarborPoint(goalHarbor)) : nullptr;
3✔
274

275
                unsigned old_visual_range = GetVisualRange();
3✔
276

277
                if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding)
3✔
278
                {
279
                    // Späher wieder entladen
280
                    const auto people = PeopleCounts::make(Job::Scout, world->GetGGS().GetNumScoutsExpedition());
3✔
281
                    static_cast<nobBaseWarehouse*>(hb)->AddToInventory(people, false);
3✔
282
                    // Wieder idlen und ggf. neuen Job suchen
283
                    StartIdling();
3✔
284
                    world->GetPlayer(ownerId_).GetJobForShip(*this);
3✔
285
                } else
286
                {
287
                    // target harbor for unloading doesnt exist anymore -> set state to driving and handle the new state
UNCOV
288
                    state = State::ExplorationexpeditionDriving;
×
289
                    HandleState_ExplorationExpeditionDriving();
×
290
                }
291

292
                // Sichtbarkeiten neu berechnen
293
                world->RecalcVisibilitiesAroundPoint(pos, old_visual_range, ownerId_, nullptr);
3✔
294

295
                break;
3✔
296
            }
297
            case State::TransportLoading: StartTransport(); break;
4✔
298
            case State::TransportUnloading:
5✔
299
            case State::SeaattackUnloading:
300
            {
301
                // Hafen herausfinden
302
                RTTR_Assert(state == State::SeaattackUnloading || remaining_sea_attackers == 0);
5✔
303
                noBase* hb = goalHarbor ? world->GetNO(world->GetHarborPoint(goalHarbor)) : nullptr;
5✔
304
                if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding)
5✔
305
                {
306
                    static_cast<nobHarborBuilding*>(hb)->ReceiveGoodsFromShip(figures, wares);
5✔
307
                    figures.clear();
5✔
308
                    wares.clear();
5✔
309

310
                    state = State::TransportUnloading;
5✔
311
                    // Hafen bescheid sagen, dass er das Schiff nun nutzen kann
312
                    static_cast<nobHarborBuilding*>(hb)->ShipArrived(*this);
5✔
313

314
                    // Hafen hat keinen Job für uns?
315
                    if(state == State::TransportUnloading)
5✔
316
                    {
317
                        // Wieder idlen und ggf. neuen Job suchen
318
                        StartIdling();
5✔
319
                        world->GetPlayer(ownerId_).GetJobForShip(*this);
5✔
320
                    }
321
                } else
322
                {
323
                    // target harbor for unloading doesnt exist anymore -> set state to driving and handle the new state
UNCOV
324
                    if(state == State::TransportUnloading)
×
325
                        FindUnloadGoal(State::TransportDriving);
×
326
                    else
UNCOV
327
                        FindUnloadGoal(State::SeaattackReturnDriving);
×
328
                }
329
                break;
5✔
330
            }
331
            case State::SeaattackLoading: StartSeaAttack(); break;
2✔
332
            case State::SeaattackWaiting:
6✔
333
            {
334
                // Nächsten Soldaten nach draußen beordern
335
                if(figures.empty())
6✔
336
                    break;
2✔
337

338
                // Evtl. ist ein Angreifer schon fertig und wieder an Board gegangen
339
                // der darf dann natürlich nicht noch einmal raus, sonst kann die schöne Reise
340
                // böse enden
341
                if(static_cast<nofAttacker&>(*figures.front()).IsSeaAttackCompleted())
4✔
UNCOV
342
                    break;
×
343

344
                auto& attacker = world->AddFigure(pos, std::move(figures.front()));
4✔
345
                figures.pop_front();
4✔
346

347
                current_ev = GetEvMgr().AddEvent(this, 30, 1);
4✔
348
                static_cast<nofAttacker&>(attacker).StartAttackOnOtherIsland(pos, GetObjId());
4✔
349
                break;
4✔
350
            }
351
        }
352
    }
353
}
428✔
354

355
void noShip::StartDriving(const Direction dir)
401✔
356
{
357
    const std::array<unsigned, 5> SHIP_SPEEDS = {35, 25, 20, 10, 5};
401✔
358

359
    StartMoving(dir, SHIP_SPEEDS[world->GetGGS().getSelection(AddonId::SHIP_SPEED)]);
401✔
360
}
401✔
361

362
void noShip::Driven()
398✔
363
{
364
    MapPoint enemy_territory_discovered(MapPoint::Invalid());
398✔
365
    world->RecalcMovingVisibilities(pos, ownerId_, GetVisualRange(), GetCurMoveDir(), &enemy_territory_discovered);
398✔
366

367
    // Feindliches Territorium entdeckt?
368
    if(enemy_territory_discovered.isValid())
398✔
369
    {
370
        // Send message if necessary
371
        if(world->GetPlayer(ownerId_).ShipDiscoveredHostileTerritory(enemy_territory_discovered))
25✔
372
            SendPostMessage(ownerId_, std::make_unique<PostMsg>(GetEvMgr().GetCurrentGF(),
1✔
373
                                                                _("A ship disovered an enemy territory"),
1✔
374
                                                                PostCategory::Military, enemy_territory_discovered));
2✔
375
    }
376

377
    switch(state)
398✔
378
    {
379
        case State::Gotoharbor: HandleState_GoToHarbor(); break;
42✔
380
        case State::ExpeditionDriving: HandleState_ExpeditionDriving(); break;
58✔
381
        case State::ExplorationexpeditionDriving: HandleState_ExplorationExpeditionDriving(); break;
189✔
382
        case State::TransportDriving: HandleState_TransportDriving(); break;
64✔
383
        case State::SeaattackDrivingToDestination: HandleState_SeaAttackDriving(); break;
31✔
384
        case State::SeaattackReturnDriving: HandleState_SeaAttackReturn(); break;
14✔
UNCOV
385
        default: RTTR_Assert(false); break;
×
386
    }
387
}
398✔
388

389
bool noShip::IsLoading() const
10✔
390
{
391
    return state == State::ExpeditionLoading || state == State::ExplorationexpeditionLoading
10✔
392
           || state == State::TransportLoading || state == State::SeaattackLoading;
20✔
393
}
394

395
bool noShip::IsUnloading() const
100✔
396
{
397
    return state == State::ExpeditionUnloading || state == State::ExplorationexpeditionUnloading
100✔
398
           || state == State::TransportUnloading || state == State::SeaattackUnloading;
200✔
399
}
400

UNCOV
401
bool noShip::IsOnBoard(const noFigure& figure) const
×
402
{
UNCOV
403
    return helpers::containsPtr(figures, &figure);
×
404
}
405

406
/// Gibt Sichtradius dieses Schiffes zurück
407
unsigned noShip::GetVisualRange() const
4,905✔
408
{
409
    // Erkundungsschiffe haben einen größeren Sichtbereich
410
    if(state >= State::ExplorationexpeditionLoading && state <= State::ExplorationexpeditionDriving)
4,905✔
411
        return VISUALRANGE_EXPLORATION_SHIP;
3,974✔
412
    else
413
        return VISUALRANGE_SHIP;
931✔
414
}
415

416
/// Fährt zum Hafen, um dort eine Mission (Expedition) zu erledigen
417
void noShip::GoToHarbor(const nobHarborBuilding& hb, const std::vector<Direction>& route)
10✔
418
{
419
    RTTR_Assert(state == State::Idle); // otherwise we might carry wares etc
10✔
420
    RTTR_Assert(figures.empty());
10✔
421
    RTTR_Assert(wares.empty());
10✔
422
    RTTR_Assert(remaining_sea_attackers == 0);
10✔
423

424
    state = State::Gotoharbor;
10✔
425

426
    goalHarbor = world->GetNode(hb.GetPos()).harborId;
10✔
427
    RTTR_Assert(goalHarbor);
10✔
428

429
    // Route merken
430
    this->route_ = route;
10✔
431
    curRouteIdx = 1;
10✔
432

433
    // losfahren
434
    StartDriving(route[0]);
10✔
435
}
10✔
436

437
/// Startet eine Expedition
438
void noShip::StartExpedition(HarborId homeHarborId)
3✔
439
{
440
    /// Schiff wird "beladen", also kurze Zeit am Hafen stehen, bevor wir bereit sind
441
    state = State::ExpeditionLoading;
3✔
442
    current_ev = GetEvMgr().AddEvent(this, LOADING_TIME, 1);
3✔
443
    RTTR_Assert(homeHarborId);
3✔
444
    RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_));
3✔
445
    homeHarbor = homeHarborId;
3✔
446
    goalHarbor = homeHarborId; // This is current goal (commands are relative to current goal)
3✔
447
}
3✔
448

449
/// Startet eine Erkundungs-Expedition
450
void noShip::StartExplorationExpedition(HarborId homeHarborId)
4✔
451
{
452
    /// Schiff wird "beladen", also kurze Zeit am Hafen stehen, bevor wir bereit sind
453
    state = State::ExplorationexpeditionLoading;
4✔
454
    current_ev = GetEvMgr().AddEvent(this, LOADING_TIME, 1);
4✔
455
    covered_distance = 0;
4✔
456
    RTTR_Assert(homeHarborId);
4✔
457
    RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_));
4✔
458
    homeHarbor = homeHarborId;
4✔
459
    goalHarbor = homeHarborId; // This is current goal (commands are relative to current goal)
4✔
460
    // Sichtbarkeiten neu berechnen
461
    world->MakeVisibleAroundPoint(pos, GetVisualRange(), ownerId_);
4✔
462
}
4✔
463

464
/// Fährt weiter zu einem Hafen
465
noShip::Result noShip::DriveToHarbour()
244✔
466
{
467
    if(!goalHarbor)
244✔
468
        return Result::HarborDoesntExist;
3✔
469
    const MapPoint goal = world->GetHarborPoint(goalHarbor);
241✔
470

471
    // Existiert der Hafen überhaupt noch?
472
    if(world->GetGOT(goal) != GO_Type::NobHarborbuilding)
241✔
UNCOV
473
        return Result::HarborDoesntExist;
×
474

475
    return DriveToHarbourPlace();
241✔
476
}
477

478
/// Fährt weiter zu Hafenbauplatz
479
noShip::Result noShip::DriveToHarbourPlace()
419✔
480
{
481
    if(!goalHarbor)
419✔
UNCOV
482
        return Result::HarborDoesntExist;
×
483

484
    // Sind wir schon da?
485
    if(curRouteIdx == route_.size())
419✔
486
        return Result::GoalReached;
28✔
487

488
    MapPoint goalRoutePos;
391✔
489

490
    // Route überprüfen
491
    if(!world->CheckShipRoute(pos, route_, curRouteIdx, &goalRoutePos))
391✔
492
    {
493
        // Route kann nicht mehr passiert werden --> neue Route suchen
UNCOV
494
        if(!world->FindShipPathToHarbor(pos, goalHarbor, seaId_, &route_, nullptr))
×
495
        {
496
            // Wieder keine gefunden -> raus
UNCOV
497
            return Result::NoRouteFound;
×
498
        }
499

500
        // Wir fangen bei der neuen Route wieder von vorne an
UNCOV
501
        curRouteIdx = 0;
×
502
    } else if(goalRoutePos != world->GetCoastalPoint(goalHarbor, seaId_))
391✔
503
    {
504
        // Our goal point of the current route has changed
505
        // If we are close to it, recalculate the route
UNCOV
506
        RTTR_Assert(route_.size() >= curRouteIdx);
×
507
        if(route_.size() - curRouteIdx < 10)
×
508
        {
UNCOV
509
            if(!world->FindShipPathToHarbor(pos, goalHarbor, seaId_, &route_, nullptr))
×
510
                // Keiner gefunden -> raus
UNCOV
511
                return Result::NoRouteFound;
×
512

UNCOV
513
            curRouteIdx = 0;
×
514
        }
515
    }
516

517
    RTTR_Assert(curRouteIdx < route_.size());
391✔
518
    StartDriving(route_[curRouteIdx++]);
391✔
519
    return Result::Driving;
391✔
520
}
521

522
HarborId noShip::GetCurrentHarbor() const
13✔
523
{
524
    RTTR_Assert(state == State::ExpeditionWaiting);
13✔
525
    return goalHarbor;
13✔
526
}
527

528
HarborId noShip::GetTargetHarbor() const
48✔
529
{
530
    return goalHarbor;
48✔
531
}
532

533
HarborId noShip::GetHomeHarbor() const
11✔
534
{
535
    return homeHarbor;
11✔
536
}
537

538
/// Weist das Schiff an, in einer bestimmten Richtung die Expedition fortzusetzen
539
void noShip::ContinueExpedition(const ShipDirection dir)
8✔
540
{
541
    if(state != State::ExpeditionWaiting)
8✔
542
        return;
3✔
543

544
    // Nächsten Hafenpunkt in dieser Richtung suchen
545
    HarborId new_goal = world->GetNextFreeHarborPoint(pos, goalHarbor, dir, ownerId_);
6✔
546

547
    // Auch ein Ziel gefunden?
548
    if(!new_goal)
6✔
549
        return;
1✔
550

551
    // Versuchen, Weg zu finden
552
    if(!world->FindShipPathToHarbor(pos, new_goal, seaId_, &route_, nullptr))
5✔
UNCOV
553
        return;
×
554

555
    // Dann fahren wir da mal hin
556
    curRouteIdx = 0;
5✔
557
    goalHarbor = new_goal;
5✔
558
    state = State::ExpeditionDriving;
5✔
559

560
    HandleState_ExpeditionDriving();
5✔
561
}
562

563
/// Weist das Schiff an, eine Expedition abzubrechen (nur wenn es steht) und zum
564
/// Hafen zurückzukehren
565
void noShip::CancelExpedition()
3✔
566
{
567
    // Protect against double execution
568
    if(state != State::ExpeditionWaiting)
3✔
569
        return;
2✔
570

571
    // We are waiting. There should be no event!
572
    RTTR_Assert(!current_ev);
1✔
573

574
    // Zum Heimathafen zurückkehren
575
    // Oder sind wir schon dort?
576
    if(goalHarbor == homeHarbor)
1✔
577
    {
UNCOV
578
        route_.clear();
×
579
        curRouteIdx = 0;
×
580
        state = State::ExpeditionDriving; // just in case the home harbor was destroyed
×
581
        HandleState_ExpeditionDriving();
×
582
    } else
583
    {
584
        state = State::ExpeditionDriving;
1✔
585
        goalHarbor = homeHarbor;
1✔
586
        StartDrivingToHarborPlace();
1✔
587
        HandleState_ExpeditionDriving();
1✔
588
    }
589
}
590

591
/// Weist das Schiff an, an der aktuellen Position einen Hafen zu gründen
592
void noShip::FoundColony()
4✔
593
{
594
    if(state != State::ExpeditionWaiting)
4✔
595
        return;
2✔
596

597
    // Kolonie gründen
598
    if(world->FoundColony(goalHarbor, ownerId_, seaId_))
2✔
599
    {
600
        // For checks
601
        state = State::ExpeditionUnloading;
1✔
602
        // Dann idlen wir wieder
603
        StartIdling();
1✔
604
        // Neue Arbeit suchen
605
        world->GetPlayer(ownerId_).GetJobForShip(*this);
1✔
606
    } else // colony founding FAILED
607
        world->GetNotifications().publish(ExpeditionNote(ExpeditionNote::Waiting, ownerId_, pos));
1✔
608
}
609

610
void noShip::HandleState_GoToHarbor()
42✔
611
{
612
    // Hafen schon zerstört?
613
    if(!goalHarbor)
42✔
614
    {
615
        StartIdling();
1✔
616
        return;
1✔
617
    }
618

619
    Result res = DriveToHarbour();
41✔
620
    switch(res)
41✔
621
    {
622
        case Result::Driving: return; // Continue
33✔
623
        case Result::GoalReached:
8✔
624
        {
625
            const MapPoint goal = world->GetHarborPoint(goalHarbor);
8✔
626
            // Go idle here (if harbor does not need it)
627
            StartIdling();
8✔
628
            // Hafen Bescheid sagen, dass wir da sind (falls er überhaupt noch existiert)
629
            noBase* hb = goal.isValid() ? world->GetNO(goal) : nullptr;
8✔
630
            if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding)
8✔
631
                static_cast<nobHarborBuilding*>(hb)->ShipArrived(*this);
8✔
632
        }
633
        break;
8✔
UNCOV
634
        case Result::NoRouteFound:
×
635
        {
UNCOV
636
            MapPoint goal(world->GetHarborPoint(goalHarbor));
×
637
            RTTR_Assert(goal.isValid());
×
638
            // Dem Hafen Bescheid sagen
UNCOV
639
            world->GetSpecObj<nobHarborBuilding>(goal)->ShipLost(this);
×
640
            StartIdling();
×
641
        }
UNCOV
642
        break;
×
643
        case Result::HarborDoesntExist: StartIdling(); break;
×
644
    }
645
}
646

647
void noShip::HandleState_ExpeditionDriving()
64✔
648
{
649
    Result res;
650
    // Zum Heimathafen fahren?
651
    if(homeHarbor == goalHarbor)
64✔
652
        res = DriveToHarbour();
15✔
653
    else
654
        res = DriveToHarbourPlace();
49✔
655

656
    switch(res)
64✔
657
    {
658
        case Result::Driving: return;
59✔
659
        case Result::GoalReached:
5✔
660
        {
661
            // Haben wir unsere Expedition beendet?
662
            if(homeHarbor == goalHarbor)
5✔
663
            {
664
                // Sachen wieder in den Hafen verladen
665
                state = State::ExpeditionUnloading;
1✔
666
                current_ev = GetEvMgr().AddEvent(this, UNLOADING_TIME, 1);
1✔
667
            } else
668
            {
669
                // Warten auf weitere Anweisungen
670
                state = State::ExpeditionWaiting;
4✔
671

672
                // Spieler benachrichtigen
673
                SendPostMessage(
8✔
674
                  ownerId_, std::make_unique<ShipPostMsg>(GetEvMgr().GetCurrentGF(),
12✔
675
                                                          _("A ship has reached the destination of its expedition."),
4✔
676
                                                          PostCategory::Economy, *this));
4✔
677
                world->GetNotifications().publish(ExpeditionNote(ExpeditionNote::Waiting, ownerId_, pos));
4✔
678
            }
679
        }
680
        break;
5✔
UNCOV
681
        case Result::NoRouteFound:
×
682
        case Result::HarborDoesntExist: // should only happen when an expedition is cancelled and the home harbor no
683
                                        // longer exists
684
        {
UNCOV
685
            if(homeHarbor != goalHarbor && homeHarbor)
×
686
            {
687
                // Try to go back
UNCOV
688
                goalHarbor = homeHarbor;
×
689
                HandleState_ExpeditionDriving();
×
690
            } else
UNCOV
691
                FindUnloadGoal(State::ExpeditionDriving); // Unload anywhere!
×
692
        }
UNCOV
693
        break;
×
694
    }
695
}
696

697
void noShip::HandleState_ExplorationExpeditionDriving()
197✔
698
{
699
    Result res;
700
    // Zum Heimathafen fahren?
701
    if(homeHarbor == goalHarbor)
197✔
702
        res = DriveToHarbour();
101✔
703
    else
704
        res = DriveToHarbourPlace();
96✔
705

706
    switch(res)
197✔
707
    {
708
        case Result::Driving: return;
189✔
709
        case Result::GoalReached:
7✔
710
        {
711
            // Haben wir unsere Expedition beendet?
712
            if(homeHarbor == goalHarbor)
7✔
713
            {
714
                // Dann sind wir fertig -> wieder entladen
715
                state = State::ExplorationexpeditionUnloading;
3✔
716
                current_ev = GetEvMgr().AddEvent(this, UNLOADING_TIME, 1);
3✔
717
            } else
718
            {
719
                // Strecke, die wir gefahren sind, draufaddieren
720
                covered_distance += route_.size();
4✔
721
                // Erstmal kurz ausruhen an diesem Punkt und das Rohr ausfahren, um ein bisschen
722
                // auf der Insel zu gucken
723
                state = State::ExplorationexpeditionWaiting;
4✔
724
                current_ev = GetEvMgr().AddEvent(this, EXPLORATION_EXPEDITION_WAITING_TIME, 1);
4✔
725
            }
726
        }
727
        break;
7✔
728
        case Result::NoRouteFound:
1✔
729
        case Result::HarborDoesntExist:
730
            if(homeHarbor != goalHarbor && homeHarbor)
1✔
731
            {
732
                // Try to go back
UNCOV
733
                goalHarbor = homeHarbor;
×
734
                HandleState_ExplorationExpeditionDriving();
×
735
            } else
736
                FindUnloadGoal(State::ExplorationexpeditionDriving); // Unload anywhere!
1✔
737
            break;
1✔
738
    }
739
}
740

741
void noShip::HandleState_TransportDriving()
71✔
742
{
743
    Result res = DriveToHarbour();
71✔
744
    switch(res)
71✔
745
    {
746
        case Result::Driving: return;
64✔
747
        case Result::GoalReached:
5✔
748
        {
749
            // Waren abladen, dafür wieder kurze Zeit hier ankern
750
            state = State::TransportUnloading;
5✔
751
            current_ev = GetEvMgr().AddEvent(this, UNLOADING_TIME, 1);
5✔
752
        }
753
        break;
5✔
754
        case Result::NoRouteFound:
2✔
755
        case Result::HarborDoesntExist:
756
        {
757
            RTTR_Assert(!remaining_sea_attackers);
2✔
758
            // Kein Hafen mehr?
759
            // Dann müssen alle Leute ihren Heimatgebäuden Bescheid geben, dass sie
760
            // nun nicht mehr kommen
761
            // Das Schiff muss einen Notlandeplatz ansteuern
762
            // LOG.write(("transport goal harbor doesnt exist player %i state %i pos %u,%u \n",player,state,x,y);
763
            for(auto& figure : figures)
2✔
764
            {
UNCOV
765
                figure->Abrogate();
×
766
                figure->SetGoalTonullptr();
×
767
            }
768

769
            for(auto& ware : wares)
4✔
770
            {
771
                ware->NotifyGoalAboutLostWare();
2✔
772
            }
773

774
            FindUnloadGoal(State::TransportDriving);
2✔
775
        }
776
        break;
2✔
777
    }
778
}
779

780
void noShip::HandleState_SeaAttackDriving()
33✔
781
{
782
    Result res = DriveToHarbourPlace();
33✔
783
    switch(res)
33✔
784
    {
785
        case Result::Driving: return; // OK
31✔
786
        case Result::GoalReached:
2✔
787
            // Ziel erreicht, dann stellen wir das Schiff hier hin und die Soldaten laufen nacheinander raus zum Ziel
788
            state = State::SeaattackWaiting;
2✔
789
            current_ev = GetEvMgr().AddEvent(this, 15, 1);
2✔
790
            remaining_sea_attackers = figures.size();
2✔
791
            break;
2✔
UNCOV
792
        case Result::NoRouteFound:
×
793
        case Result::HarborDoesntExist:
UNCOV
794
            RTTR_Assert(goalHarbor != homeHarbor || !homeHarbor);
×
795
            AbortSeaAttack();
×
796
            break;
×
797
    }
798
}
799

800
void noShip::HandleState_SeaAttackReturn()
16✔
801
{
802
    Result res = DriveToHarbour();
16✔
803
    switch(res)
16✔
804
    {
805
        case Result::Driving: return;
15✔
806
        case Result::GoalReached:
1✔
807
            // Entladen
808
            state = State::SeaattackUnloading;
1✔
809
            this->current_ev = GetEvMgr().AddEvent(this, UNLOADING_TIME, 1);
1✔
810
            break;
1✔
UNCOV
811
        case Result::HarborDoesntExist:
×
812
        case Result::NoRouteFound: AbortSeaAttack(); break;
×
813
    }
814
}
815

816
/// Gibt zurück, ob das Schiff jetzt in der Lage wäre, eine Kolonie zu gründen
UNCOV
817
bool noShip::IsAbleToFoundColony() const
×
818
{
819
    // Warten wir gerade?
UNCOV
820
    if(state == State::ExpeditionWaiting)
×
821
    {
822
        // We must always have a goal harbor
UNCOV
823
        RTTR_Assert(goalHarbor);
×
824
        // Ist der Punkt, an dem wir gerade ankern, noch frei?
UNCOV
825
        if(world->IsHarborPointFree(goalHarbor, ownerId_))
×
826
            return true;
×
827
    }
828

UNCOV
829
    return false;
×
830
}
831

832
/// Gibt zurück, ob das Schiff einen bestimmten Hafen ansteuert
833
bool noShip::IsGoingToHarbor(const nobHarborBuilding& hb) const
82✔
834
{
835
    if(goalHarbor != hb.GetHarborPosID())
82✔
836
        return false;
71✔
837
    // Explicit switch to check all states
838
    switch(state)
11✔
839
    {
840
        case State::Idle:
1✔
841
        case State::ExpeditionLoading:
842
        case State::ExpeditionUnloading:
843
        case State::ExpeditionWaiting:
844
        case State::ExpeditionDriving:
845
        case State::ExplorationexpeditionLoading:
846
        case State::ExplorationexpeditionUnloading:
847
        case State::ExplorationexpeditionWaiting:
848
        case State::ExplorationexpeditionDriving:
849
        case State::SeaattackLoading:
850
        case State::SeaattackDrivingToDestination:
851
        case State::SeaattackWaiting: return false;
1✔
852
        case State::Gotoharbor:
10✔
853
        case State::TransportDriving:       // Driving to this harbor
854
        case State::TransportLoading:       // Loading at home harbor and going to goal
855
        case State::TransportUnloading:     // Unloading at this harbor
856
        case State::SeaattackUnloading:     // Unloading attackers at this harbor
857
        case State::SeaattackReturnDriving: // Returning attackers to this harbor
858
            return true;
10✔
859
    }
UNCOV
860
    RTTR_Assert(false);
×
861
    return false;
862
}
863

864
/// Belädt das Schiff mit Waren und Figuren, um eine Transportfahrt zu starten
865
void noShip::PrepareTransport(HarborId homeHarborId, MapPoint goal, std::list<std::unique_ptr<noFigure>> figures,
5✔
866
                              std::list<std::unique_ptr<Ware>> wares)
867
{
868
    RTTR_Assert(homeHarborId);
5✔
869
    RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_));
5✔
870
    this->homeHarbor = homeHarborId;
5✔
871
    // ID von Zielhafen herausfinden
872
    noBase* nb = world->GetNO(goal);
5✔
873
    RTTR_Assert(nb->GetGOT() == GO_Type::NobHarborbuilding);
5✔
874
    this->goalHarbor = static_cast<nobHarborBuilding*>(nb)->GetHarborPosID();
5✔
875

876
    this->figures = std::move(figures);
5✔
877
    this->wares = std::move(wares);
5✔
878

879
    state = State::TransportLoading;
5✔
880
    current_ev = GetEvMgr().AddEvent(this, LOADING_TIME, 1);
5✔
881
}
5✔
882

883
/// Belädt das Schiff mit Schiffs-Angreifern
884
void noShip::PrepareSeaAttack(HarborId homeHarborId, MapPoint goal, std::vector<std::unique_ptr<nofAttacker>> attackers)
2✔
885
{
886
    // Heimathafen merken
887
    RTTR_Assert(homeHarborId);
2✔
888
    RTTR_Assert(pos == world->GetCoastalPoint(homeHarborId, seaId_));
2✔
889
    homeHarbor = homeHarborId;
2✔
890
    goalHarbor = world->GetHarborPointID(goal);
2✔
891
    RTTR_Assert(goalHarbor);
2✔
892
    figures.clear();
2✔
893
    for(auto& attacker : attackers)
6✔
894
    {
895
        attacker->StartShipJourney();
4✔
896
        attacker->SeaAttackStarted();
4✔
897
        figures.push_back(std::move(attacker));
4✔
898
    }
899
    state = State::SeaattackLoading;
2✔
900
    current_ev = GetEvMgr().AddEvent(this, LOADING_TIME, 1);
2✔
901
}
2✔
902

903
/// Startet Schiffs-Angreiff
904
void noShip::StartSeaAttack()
2✔
905
{
906
    state = State::SeaattackDrivingToDestination;
2✔
907
    StartDrivingToHarborPlace();
2✔
908
    HandleState_SeaAttackDriving();
2✔
909
}
2✔
910

UNCOV
911
void noShip::AbortSeaAttack()
×
912
{
UNCOV
913
    RTTR_Assert(state != State::SeaattackWaiting); // figures are not aboard if this fails!
×
914
    RTTR_Assert(remaining_sea_attackers == 0);     // Some soldiers are still not aboard
×
915

UNCOV
916
    if((state == State::SeaattackLoading || state == State::SeaattackDrivingToDestination) && goalHarbor != homeHarbor
×
917
       && homeHarbor)
×
918
    {
919
        // We did not start the attack yet and we can (possibly) go back to our home harbor
920
        // -> tell the soldiers we go back (like after an attack)
UNCOV
921
        goalHarbor = homeHarbor;
×
922
        for(auto& figure : figures)
×
923
            checkedCast<nofAttacker*>(figure.get())->StartReturnViaShip(*this);
×
924
        if(state == State::SeaattackLoading)
×
925
        {
926
            // We are still loading (loading event must be active)
927
            // -> Use it to unload
UNCOV
928
            RTTR_Assert(current_ev);
×
929
            state = State::SeaattackUnloading;
×
930
        } else
931
        {
932
            // Else start driving back
UNCOV
933
            state = State::SeaattackReturnDriving;
×
934
            HandleState_SeaAttackReturn();
×
935
        }
936
    } else
937
    {
938
        // attack failed and we cannot go back to our home harbor
939
        // -> Tell figures that they won't go to their planned destination
UNCOV
940
        for(auto& figure : figures)
×
941
            checkedCast<nofAttacker*>(figure.get())->CancelSeaAttack();
×
942

UNCOV
943
        if(state == State::SeaattackLoading)
×
944
        {
945
            // Abort loading
UNCOV
946
            RTTR_Assert(current_ev);
×
947
            GetEvMgr().RemoveEvent(current_ev);
×
948
        }
949

950
        // Das Schiff muss einen Notlandeplatz ansteuern
UNCOV
951
        FindUnloadGoal(State::SeaattackReturnDriving);
×
952
    }
UNCOV
953
}
×
954

955
/// Fängt an zu einem Hafen zu fahren (berechnet Route usw.)
956
void noShip::StartDrivingToHarborPlace()
19✔
957
{
958
    if(!goalHarbor)
19✔
959
    {
960
        route_.clear();
1✔
961
        curRouteIdx = 0;
1✔
962
        return;
1✔
963
    }
964

965
    MapPoint coastalPos = world->GetCoastalPoint(goalHarbor, seaId_);
18✔
966
    if(pos == coastalPos)
18✔
967
        route_.clear();
1✔
968
    else
969
    {
970
        bool routeFound;
971
        // Use upper bound to distance by checking the distance between the harbors if we still have and are at the home
972
        // harbor
973
        if(homeHarbor && pos == world->GetCoastalPoint(homeHarbor, seaId_))
17✔
974
        {
975
            // Use the maximum distance between the harbors plus 6 fields
976
            unsigned maxDistance = world->CalcHarborDistance(homeHarbor, goalHarbor) + 6;
8✔
977
            routeFound = world->FindShipPath(pos, coastalPos, maxDistance, &route_, nullptr);
8✔
978
        } else
979
            routeFound = world->FindShipPathToHarbor(pos, goalHarbor, seaId_, &route_, nullptr);
9✔
980
        if(!routeFound)
17✔
981
        {
982
            // todo
UNCOV
983
            RTTR_Assert(false);
×
984
            LOG.write("WARNING: Bug detected (GF: %u). Please report this with the savegame and "
985
                      "replay.\nnoShip::StartDrivingToHarborPlace: Schiff hat keinen Weg gefunden!\nplayer %i state %i "
986
                      "pos %u,%u goal "
987
                      "coastal %u,%u goal-id %i goalpos %u,%u \n")
988
              % GetEvMgr().GetCurrentGF() % unsigned(ownerId_) % unsigned(state) % pos.x % pos.y % coastalPos.x
989
              % coastalPos.y % goalHarbor % world->GetHarborPoint(goalHarbor).x % world->GetHarborPoint(goalHarbor).y;
990
            goalHarbor.reset();
991
            return;
992
        }
993
    }
994
    curRouteIdx = 0;
18✔
995
}
996

997
/// Startet die eigentliche Transportaktion, nachdem das Schiff beladen wurde
998
void noShip::StartTransport()
4✔
999
{
1000
    state = State::TransportDriving;
4✔
1001

1002
    StartDrivingToHarborPlace();
4✔
1003
    // Einfach weiterfahren
1004
    HandleState_TransportDriving();
4✔
1005
}
4✔
1006

1007
void noShip::FindUnloadGoal(State newState)
5✔
1008
{
1009
    state = newState;
5✔
1010
    // Das Schiff muss einen Notlandeplatz ansteuern
1011
    // Neuen Hafen suchen
1012
    if(world->GetPlayer(ownerId_).FindHarborForUnloading(this, pos, &goalHarbor, &route_, nullptr))
5✔
1013
    {
1014
        curRouteIdx = 0;
3✔
1015
        homeHarbor = goalHarbor; // To allow unloading here
3✔
1016
        if(state == State::ExpeditionDriving)
3✔
UNCOV
1017
            HandleState_ExpeditionDriving();
×
1018
        else if(state == State::ExplorationexpeditionDriving)
3✔
UNCOV
1019
            HandleState_ExplorationExpeditionDriving();
×
1020
        else if(state == State::TransportDriving)
3✔
1021
            HandleState_TransportDriving();
3✔
UNCOV
1022
        else if(state == State::SeaattackReturnDriving)
×
1023
            HandleState_SeaAttackReturn();
×
1024
        else
1025
        {
UNCOV
1026
            RTTR_Assert(false);
×
1027
            LOG.write("Bug detected: Invalid state for FindUnloadGoal");
1028
            FindUnloadGoal(State::TransportDriving);
1029
        }
1030
    } else
1031
    {
1032
        // Ansonsten als verloren markieren, damit uns später Bescheid gesagt wird
1033
        // wenn es einen neuen Hafen gibt
1034
        homeHarbor.reset();
2✔
1035
        goalHarbor.reset();
2✔
1036
        lost = true;
2✔
1037
    }
1038
}
5✔
1039

1040
/// Sagt dem Schiff, das ein bestimmter Hafen zerstört wurde
1041
void noShip::HarborDestroyed(nobHarborBuilding* hb)
12✔
1042
{
1043
    const HarborId destroyedHarborId = hb->GetHarborPosID();
12✔
1044
    // Almost every case of a destroyed harbor is handled when the ships event fires (the handler detects the destroyed
1045
    // harbor) So mostly we just reset the corresponding id
1046

1047
    if(destroyedHarborId == homeHarbor)
12✔
1048
        homeHarbor.reset();
4✔
1049

1050
    // Ist unser Ziel betroffen?
1051
    if(destroyedHarborId != goalHarbor)
12✔
1052
        return;
9✔
1053

1054
    State oldState = state;
7✔
1055

1056
    switch(state)
7✔
1057
    {
1058
        default:
4✔
1059
            // Just reset goal, but not for expeditions
1060
            if(!IsOnExpedition() && !IsOnExplorationExpedition())
4✔
1061
                goalHarbor.reset();
3✔
1062
            return; // Skip the rest
4✔
1063
        case State::TransportLoading:
3✔
1064
        case State::TransportUnloading:
1065
            // Tell wares and figures that they won't reach their goal
1066
            for(auto& figure : figures)
3✔
1067
            {
UNCOV
1068
                figure->Abrogate();
×
1069
                figure->SetGoalTonullptr();
×
1070
            }
1071
            for(auto& ware : wares)
6✔
1072
            {
1073
                // Notify goal only, if it is not the destroyed harbor. It already knows about that ;)
1074
                if(ware->GetGoal() != hb)
3✔
UNCOV
1075
                    ware->NotifyGoalAboutLostWare();
×
1076
                else
1077
                    ware->SetGoal(nullptr);
3✔
1078
            }
1079
            break;
3✔
UNCOV
1080
        case State::SeaattackLoading:
×
1081
            // We could also just set the goal harbor id to 0 but this can reuse the event
UNCOV
1082
            AbortSeaAttack();
×
1083
            break;
×
1084
        case State::SeaattackUnloading: break;
×
1085
    }
1086

1087
    // Are we currently getting the wares?
1088
    if(oldState == State::TransportLoading)
3✔
1089
    {
1090
        RTTR_Assert(current_ev);
1✔
1091
        if(homeHarbor)
1✔
1092
        {
1093
            // Then save us some time and unload immediately
1094
            // goal is now the start harbor (if it still exists)
1095
            goalHarbor = homeHarbor;
1✔
1096
            state = State::TransportUnloading;
1✔
1097
        } else
1098
        {
UNCOV
1099
            GetEvMgr().RemoveEvent(current_ev);
×
1100
            FindUnloadGoal(State::TransportDriving);
×
1101
        }
1102
    } else if(oldState == State::TransportUnloading || oldState == State::SeaattackUnloading)
2✔
1103
    {
1104
        // Remove current unload event
1105
        GetEvMgr().RemoveEvent(current_ev);
2✔
1106

1107
        if(oldState == State::SeaattackUnloading)
2✔
UNCOV
1108
            AbortSeaAttack();
×
1109
        else
1110
            FindUnloadGoal(State::TransportDriving);
2✔
1111
    }
1112
}
1113

1114
/// Fängt an mit idlen und setzt nötigen Sachen auf nullptr
1115
void noShip::StartIdling()
19✔
1116
{
1117
    // If those are not empty, then we are lost, not idling!
1118
    RTTR_Assert(figures.empty());
19✔
1119
    RTTR_Assert(wares.empty());
19✔
1120
    RTTR_Assert(remaining_sea_attackers == 0);
19✔
1121
    // Implicit contained wares/figures on expeditions
1122
    RTTR_Assert(!IsOnExplorationExpedition() || state == State::ExplorationexpeditionUnloading);
19✔
1123
    RTTR_Assert(!IsOnExpedition() || state == State::ExpeditionUnloading);
19✔
1124

1125
    homeHarbor.reset();
19✔
1126
    goalHarbor.reset();
19✔
1127
    state = State::Idle;
19✔
1128
}
19✔
1129

1130
/// Sagt Bescheid, dass ein Schiffsangreifer nicht mehr mit nach Hause fahren will
1131
void noShip::SeaAttackerWishesNoReturn()
4✔
1132
{
1133
    RTTR_Assert(remaining_sea_attackers);
4✔
1134
    RTTR_Assert(state == State::SeaattackWaiting);
4✔
1135

1136
    --remaining_sea_attackers;
4✔
1137
    // Alle Soldaten an Bord
1138
    if(remaining_sea_attackers == 0)
4✔
1139
    {
1140
        // Andere Events ggf. erstmal abmelden
1141
        GetEvMgr().RemoveEvent(current_ev);
2✔
1142
        if(!figures.empty())
2✔
1143
        {
1144
            // Go back home. Note: home_harbor can be 0 if it was destroyed, allow this and let the state handlers
1145
            // handle that case later
1146
            goalHarbor = homeHarbor;
2✔
1147
            state = State::SeaattackReturnDriving;
2✔
1148
            StartDrivingToHarborPlace();
2✔
1149
            HandleState_SeaAttackReturn();
2✔
1150
        } else
1151
        {
1152
            // Wenn keine Soldaten mehr da sind können wir auch erstmal idlen
UNCOV
1153
            StartIdling();
×
1154
            world->GetPlayer(ownerId_).GetJobForShip(*this);
×
1155
        }
1156
    }
1157
}
4✔
1158

1159
/// Schiffs-Angreifer sind nach dem Angriff wieder zurückgekehrt
1160
void noShip::AddReturnedAttacker(std::unique_ptr<nofAttacker> attacker)
4✔
1161
{
1162
    RTTR_Assert(!helpers::containsPtr(figures, attacker.get()));
4✔
1163

1164
    figures.push_back(std::move(attacker));
4✔
1165
    // Nun brauchen wir quasi einen Angreifer weniger
1166
    SeaAttackerWishesNoReturn();
4✔
1167
}
4✔
1168

1169
/// Weist das Schiff an, seine Erkundungs-Expedition fortzusetzen
1170
void noShip::ContinueExplorationExpedition()
8✔
1171
{
1172
    // Sind wir schon über unserem Limit, also zu weit gefahren
1173
    if(covered_distance >= MAX_EXPLORATION_EXPEDITION_DISTANCE)
8✔
1174
    {
1175
        // Dann steuern wir unseren Heimathafen an!
UNCOV
1176
        goalHarbor = homeHarbor;
×
1177
    } else
1178
    {
1179
        // Find the next harbor spot to explore
1180
        std::vector<HarborId> hps;
16✔
1181
        if(goalHarbor)
8✔
1182
            hps = world->GetUnexploredHarborPoints(goalHarbor, seaId_, GetPlayerId());
8✔
1183

1184
        // No possible spots? -> Go home
1185
        if(hps.empty())
8✔
1186
            goalHarbor = homeHarbor;
4✔
1187
        else
1188
        {
1189
            // Choose one randomly
1190
            goalHarbor = RANDOM_ELEMENT(hps);
4✔
1191
        }
1192
    }
1193

1194
    StartDrivingToHarborPlace();
8✔
1195
    state = State::ExplorationexpeditionDriving;
8✔
1196
    HandleState_ExplorationExpeditionDriving();
8✔
1197
}
8✔
1198

1199
/// Sagt dem Schiff, dass ein neuer Hafen erbaut wurde
1200
void noShip::NewHarborBuilt(nobHarborBuilding* hb)
11✔
1201
{
1202
    if(!lost)
11✔
1203
        return;
9✔
1204
    // Liegt der Hafen auch am Meer von diesem Schiff?
1205
    if(!world->IsHarborAtSea(hb->GetHarborPosID(), seaId_))
2✔
UNCOV
1206
        return;
×
1207

1208
    // LOG.write(("lost ship has new goal harbor player %i state %i pos %u,%u \n",player,state,x,y);
1209
    homeHarbor = goalHarbor = hb->GetHarborPosID();
2✔
1210
    lost = false;
2✔
1211

1212
    StartDrivingToHarborPlace();
2✔
1213

1214
    switch(state)
2✔
1215
    {
1216
        case State::ExplorationexpeditionDriving:
2✔
1217
        case State::ExpeditionDriving:
1218
        case State::TransportDriving:
1219
        case State::SeaattackReturnDriving: Driven(); break;
2✔
UNCOV
1220
        default:
×
1221
            RTTR_Assert(false); // Das darf eigentlich nicht passieren
×
1222
            LOG.write("Bug detected: Invalid state in NewHarborBuilt");
1223
            break;
1224
    }
1225
}
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