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

Return-To-The-Roots / s25client / 22797874830

07 Mar 2026 11:05AM UTC coverage: 50.455% (+0.1%) from 50.327%
22797874830

Pull #1883

github

web-flow
Merge 1fb8f3eeb into d2a3730c9
Pull Request #1883: Destroy ships when player is defeated

15 of 17 new or added lines in 2 files covered. (88.24%)

5 existing lines in 3 files now uncovered.

23101 of 45785 relevant lines covered (50.46%)

42153.23 hits per line

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

70.92
/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)
23✔
50
    : noMovable(NodalObjectType::Ship, pos), ownerId_(player), state(State::Idle), seaId_(0), goal_harborId(0),
51
      goal_dir(0), name(RANDOM_ELEMENT(ship_names[world->GetPlayer(player).nation])), curRouteIdx(0), lost(false),
23✔
52
      remaining_sea_attackers(0), home_harbor(0), covered_distance(0)
46✔
53
{
54
    // Meer ermitteln, auf dem dieses Schiff fährt
55
    for(const auto dir : helpers::EnumRange<Direction>{})
368✔
56
    {
57
        unsigned short seaId = world->GetNeighbourNode(pos, dir).seaId;
138✔
58
        if(seaId)
138✔
59
            this->seaId_ = seaId;
126✔
60
    }
61

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

66
noShip::~noShip() = default;
46✔
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_);
×
75
    sgd.PushUnsignedInt(goal_harborId);
×
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(home_harbor);
×
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
      goal_harborId(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()), home_harbor(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()
2✔
102
{
103
    RTTR_Assert(figures.empty());
2✔
104
    RTTR_Assert(wares.empty());
2✔
105
    world->GetNotifications().publish(ShipNote(ShipNote::Destroyed, ownerId_, pos));
2✔
106
    // Schiff wieder abmelden
107
    world->GetPlayer(ownerId_).RemoveShip(*this);
2✔
108
}
2✔
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)
413✔
212
{
213
    RTTR_Assert(current_ev);
413✔
214
    RTTR_Assert(current_ev->id == id);
413✔
215
    current_ev = nullptr;
413✔
216

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

236
                // Spieler benachrichtigen
237
                SendPostMessage(ownerId_, std::make_unique<ShipPostMsg>(GetEvMgr().GetCurrentGF(),
2✔
238
                                                                        _("A ship is ready for an expedition."),
2✔
239
                                                                        PostCategory::Economy, *this));
2✔
240
                world->GetNotifications().publish(ExpeditionNote(ExpeditionNote::Waiting, ownerId_, pos));
2✔
241
                break;
2✔
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 = goal_harborId ? world->GetNO(world->GetHarborPoint(goal_harborId)) : nullptr;
1✔
251

252
                if(hb && hb->GetGOT() == GO_Type::NobHarborbuilding)
1✔
253
                {
254
                    Inventory 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)->AddGoods(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 = goal_harborId ? world->GetNO(world->GetHarborPoint(goal_harborId)) : 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
                    Inventory goods;
3✔
281
                    goods.people[Job::Scout] = world->GetGGS().GetNumScoutsExpedition();
3✔
282
                    static_cast<nobBaseWarehouse*>(hb)->AddGoods(goods, false);
3✔
283
                    // Wieder idlen und ggf. neuen Job suchen
284
                    StartIdling();
3✔
285
                    world->GetPlayer(ownerId_).GetJobForShip(*this);
3✔
286
                } else
287
                {
288
                    // target harbor for unloading doesnt exist anymore -> set state to driving and handle the new state
289
                    state = State::ExplorationexpeditionDriving;
×
290
                    HandleState_ExplorationExpeditionDriving();
×
291
                }
292

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

425
    state = State::Gotoharbor;
11✔
426

427
    goal_harborId = world->GetNode(hb.GetPos()).harborId;
11✔
428
    RTTR_Assert(goal_harborId);
11✔
429

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

434
    // losfahren
435
    StartDriving(route[0]);
11✔
436
}
11✔
437

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

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

465
/// Fährt weiter zu einem Hafen
466
noShip::Result noShip::DriveToHarbour()
246✔
467
{
468
    if(!goal_harborId)
246✔
469
        return Result::HarborDoesntExist;
3✔
470

471
    MapPoint goal(world->GetHarborPoint(goal_harborId));
243✔
472
    RTTR_Assert(goal.isValid());
243✔
473

474
    // Existiert der Hafen überhaupt noch?
475
    if(world->GetGOT(goal) != GO_Type::NobHarborbuilding)
243✔
476
        return Result::HarborDoesntExist;
×
477

478
    return DriveToHarbourPlace();
243✔
479
}
480

481
/// Fährt weiter zu Hafenbauplatz
482
noShip::Result noShip::DriveToHarbourPlace()
400✔
483
{
484
    if(goal_harborId == 0)
400✔
485
        return Result::HarborDoesntExist;
×
486

487
    // Sind wir schon da?
488
    if(curRouteIdx == route_.size())
400✔
489
        return Result::GoalReached;
27✔
490

491
    MapPoint goalRoutePos;
373✔
492

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

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

516
            curRouteIdx = 0;
×
517
        }
518
    }
519

520
    StartDriving(route_[curRouteIdx++]);
373✔
521
    return Result::Driving;
373✔
522
}
523

524
unsigned noShip::GetCurrentHarbor() const
3✔
525
{
526
    RTTR_Assert(state == State::ExpeditionWaiting);
3✔
527
    return goal_harborId;
3✔
528
}
529

530
unsigned noShip::GetTargetHarbor() const
42✔
531
{
532
    return goal_harborId;
42✔
533
}
534

535
unsigned noShip::GetHomeHarbor() const
11✔
536
{
537
    return home_harbor;
11✔
538
}
539

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

546
    // Nächsten Hafenpunkt in dieser Richtung suchen
547
    unsigned new_goal = world->GetNextFreeHarborPoint(pos, goal_harborId, dir, ownerId_);
3✔
548

549
    // Auch ein Ziel gefunden?
550
    if(!new_goal)
3✔
551
        return;
1✔
552

553
    // Versuchen, Weg zu finden
554
    if(!world->FindShipPathToHarbor(pos, new_goal, seaId_, &route_, nullptr))
2✔
555
        return;
×
556

557
    // Dann fahren wir da mal hin
558
    curRouteIdx = 0;
2✔
559
    goal_harborId = new_goal;
2✔
560
    state = State::ExpeditionDriving;
2✔
561

562
    StartDriving(route_[curRouteIdx++]);
2✔
563
}
564

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

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

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

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

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

612
void noShip::HandleState_GoToHarbor()
42✔
613
{
614
    // Hafen schon zerstört?
615
    if(goal_harborId == 0)
42✔
616
    {
617
        StartIdling();
1✔
618
        return;
1✔
619
    }
620

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

650
void noShip::HandleState_ExpeditionDriving()
43✔
651
{
652
    Result res;
653
    // Zum Heimathafen fahren?
654
    if(home_harbor == goal_harborId)
43✔
655
        res = DriveToHarbour();
15✔
656
    else
657
        res = DriveToHarbourPlace();
28✔
658

659
    switch(res)
43✔
660
    {
661
        case Result::Driving: return;
40✔
662
        case Result::GoalReached:
3✔
663
        {
664
            // Haben wir unsere Expedition beendet?
665
            if(home_harbor == goal_harborId)
3✔
666
            {
667
                // Sachen wieder in den Hafen verladen
668
                state = State::ExpeditionUnloading;
1✔
669
                current_ev = GetEvMgr().AddEvent(this, UNLOADING_TIME, 1);
1✔
670
            } else
671
            {
672
                // Warten auf weitere Anweisungen
673
                state = State::ExpeditionWaiting;
2✔
674

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

700
void noShip::HandleState_ExplorationExpeditionDriving()
197✔
701
{
702
    Result res;
703
    // Zum Heimathafen fahren?
704
    if(home_harbor == goal_harborId)
197✔
705
        res = DriveToHarbour();
101✔
706
    else
707
        res = DriveToHarbourPlace();
96✔
708

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

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

772
            for(auto& ware : wares)
4✔
773
            {
774
                ware->NotifyGoalAboutLostWare();
2✔
775
            }
776

777
            FindUnloadGoal(State::TransportDriving);
2✔
778
        }
779
        break;
2✔
780
    }
781
}
782

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

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

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

832
    return false;
×
833
}
834

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

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

879
    this->figures = std::move(figures);
7✔
880
    this->wares = std::move(wares);
7✔
881

882
    state = State::TransportLoading;
7✔
883
    current_ev = GetEvMgr().AddEvent(this, LOADING_TIME, 1);
7✔
884
}
7✔
885

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

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

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

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

946
        if(state == State::SeaattackLoading)
×
947
        {
948
            // Abort loading
949
            RTTR_Assert(current_ev);
×
950
            GetEvMgr().RemoveEvent(current_ev);
×
951
        }
952

953
        // Das Schiff muss einen Notlandeplatz ansteuern
954
        FindUnloadGoal(State::SeaattackReturnDriving);
×
955
    }
956
}
×
957

958
/// Fängt an zu einem Hafen zu fahren (berechnet Route usw.)
959
void noShip::StartDrivingToHarborPlace()
21✔
960
{
961
    if(!goal_harborId)
21✔
962
    {
963
        route_.clear();
1✔
964
        curRouteIdx = 0;
1✔
965
        return;
1✔
966
    }
967

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

1001
/// Startet die eigentliche Transportaktion, nachdem das Schiff beladen wurde
1002
void noShip::StartTransport()
6✔
1003
{
1004
    state = State::TransportDriving;
6✔
1005

1006
    StartDrivingToHarborPlace();
6✔
1007
    // Einfach weiterfahren
1008
    HandleState_TransportDriving();
6✔
1009
}
6✔
1010

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

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

1050
    if(destroyedHarborId == home_harbor)
14✔
1051
        home_harbor = 0;
5✔
1052

1053
    // Ist unser Ziel betroffen?
1054
    if(destroyedHarborId != goal_harborId)
14✔
1055
    {
1056
        return;
6✔
1057
    }
1058

1059
    State oldState = state;
8✔
1060

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

1094
    // Are we currently getting the wares?
1095
    if(oldState == State::TransportLoading)
3✔
1096
    {
1097
        RTTR_Assert(current_ev);
1✔
1098
        if(home_harbor)
1✔
1099
        {
1100
            // Then save us some time and unload immediately
1101
            // goal is now the start harbor (if it still exists)
1102
            goal_harborId = home_harbor;
1✔
1103
            state = State::TransportUnloading;
1✔
1104
        } else
1105
        {
1106
            GetEvMgr().RemoveEvent(current_ev);
×
1107
            FindUnloadGoal(State::TransportDriving);
×
1108
        }
1109
    } else if(oldState == State::TransportUnloading || oldState == State::SeaattackUnloading)
2✔
1110
    {
1111
        // Remove current unload event
1112
        GetEvMgr().RemoveEvent(current_ev);
2✔
1113

1114
        if(oldState == State::SeaattackUnloading)
2✔
1115
            AbortSeaAttack();
×
1116
        else
1117
            FindUnloadGoal(State::TransportDriving);
2✔
1118
    }
1119
}
1120

1121
/// Fängt an mit idlen und setzt nötigen Sachen auf nullptr
1122
void noShip::StartIdling()
20✔
1123
{
1124
    // If those are not empty, then we are lost, not idling!
1125
    RTTR_Assert(figures.empty());
20✔
1126
    RTTR_Assert(wares.empty());
20✔
1127
    RTTR_Assert(remaining_sea_attackers == 0);
20✔
1128
    // Implicit contained wares/figures on expeditions
1129
    RTTR_Assert(!IsOnExplorationExpedition() || state == State::ExplorationexpeditionUnloading);
20✔
1130
    RTTR_Assert(!IsOnExpedition() || state == State::ExpeditionUnloading);
20✔
1131

1132
    home_harbor = 0;
20✔
1133
    goal_harborId = 0;
20✔
1134
    state = State::Idle;
20✔
1135
}
20✔
1136

1137
/// Sagt Bescheid, dass ein Schiffsangreifer nicht mehr mit nach Hause fahren will
1138
void noShip::SeaAttackerWishesNoReturn()
4✔
1139
{
1140
    RTTR_Assert(remaining_sea_attackers);
4✔
1141
    RTTR_Assert(state == State::SeaattackWaiting);
4✔
1142

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

1166
/// Schiffs-Angreifer sind nach dem Angriff wieder zurückgekehrt
1167
void noShip::AddReturnedAttacker(std::unique_ptr<nofAttacker> attacker)
4✔
1168
{
1169
    RTTR_Assert(!helpers::containsPtr(figures, attacker.get()));
4✔
1170

1171
    figures.push_back(std::move(attacker));
4✔
1172
    // Nun brauchen wir quasi einen Angreifer weniger
1173
    SeaAttackerWishesNoReturn();
4✔
1174
}
4✔
1175

1176
/// Weist das Schiff an, seine Erkundungs-Expedition fortzusetzen
1177
void noShip::ContinueExplorationExpedition()
8✔
1178
{
1179
    // Sind wir schon über unserem Limit, also zu weit gefahren
1180
    if(covered_distance >= MAX_EXPLORATION_EXPEDITION_DISTANCE)
8✔
1181
    {
1182
        // Dann steuern wir unseren Heimathafen an!
1183
        goal_harborId = home_harbor;
×
1184
    } else
1185
    {
1186
        // Find the next harbor spot to explore
1187
        std::vector<unsigned> hps;
16✔
1188
        if(goal_harborId)
8✔
1189
            hps = world->GetUnexploredHarborPoints(goal_harborId, seaId_, GetPlayerId());
8✔
1190

1191
        // No possible spots? -> Go home
1192
        if(hps.empty())
8✔
1193
            goal_harborId = home_harbor;
4✔
1194
        else
1195
        {
1196
            // Choose one randomly
1197
            goal_harborId = RANDOM_ELEMENT(hps);
4✔
1198
        }
1199
    }
1200

1201
    StartDrivingToHarborPlace();
8✔
1202
    state = State::ExplorationexpeditionDriving;
8✔
1203
    HandleState_ExplorationExpeditionDriving();
8✔
1204
}
8✔
1205

1206
/// Sagt dem Schiff, dass ein neuer Hafen erbaut wurde
1207
void noShip::NewHarborBuilt(nobHarborBuilding* hb)
11✔
1208
{
1209
    if(!lost)
11✔
1210
        return;
9✔
1211
    // Liegt der Hafen auch am Meer von diesem Schiff?
1212
    if(!world->IsHarborAtSea(hb->GetHarborPosID(), seaId_))
2✔
1213
        return;
×
1214

1215
    // LOG.write(("lost ship has new goal harbor player %i state %i pos %u,%u \n",player,state,x,y);
1216
    home_harbor = goal_harborId = hb->GetHarborPosID();
2✔
1217
    lost = false;
2✔
1218

1219
    StartDrivingToHarborPlace();
2✔
1220

1221
    switch(state)
2✔
1222
    {
1223
        case State::ExplorationexpeditionDriving:
2✔
1224
        case State::ExpeditionDriving:
1225
        case State::TransportDriving:
1226
        case State::SeaattackReturnDriving: Driven(); break;
2✔
NEW
1227
        default:
×
NEW
1228
            RTTR_Assert(false); // Das darf eigentlich nicht passieren
×
1229
            LOG.write("Bug detected: Invalid state in NewHarborBuilt");
1230
            break;
1231
    }
1232
}
1233

1234
void noShip::Sink()
2✔
1235
{
1236
    for(auto& figure : figures)
4✔
1237
    {
1238
        figure->Abrogate();
2✔
1239
        figure->SetGoalTonullptr();
2✔
1240
        figure->RemoveFromInventory();
2✔
1241
    }
1242
    figures.clear();
2✔
1243

1244
    for(auto& ware : wares)
5✔
1245
    {
1246
        ware->WareLost(ownerId_);
3✔
1247
        ware->Destroy();
3✔
1248
    }
1249
    wares.clear();
2✔
1250

1251
    GetEvMgr().RemoveEvent(current_ev);
2✔
1252
    GetEvMgr().AddToKillList(world->RemoveFigure(pos, *this));
2✔
1253
    world->RecalcVisibilitiesAroundPoint(pos, GetVisualRange(), ownerId_, nullptr);
2✔
1254
}
2✔
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