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

Return-To-The-Roots / s25client / 21587362885

02 Feb 2026 10:57AM UTC coverage: 50.944% (+0.2%) from 50.754%
21587362885

Pull #1883

github

web-flow
Merge c2f69f8a5 into dc0ce04f6
Pull Request #1883: Destroy ships when player is defeated

104 of 149 new or added lines in 18 files covered. (69.8%)

6 existing lines in 4 files now uncovered.

22851 of 44855 relevant lines covered (50.94%)

42148.04 hits per line

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

66.2
/libs/s25main/Ware.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 "Ware.h"
6
#include "EventManager.h"
7
#include "GamePlayer.h"
8
#include "RoadSegment.h"
9
#include "SerializedGameData.h"
10
#include "buildings/noBaseBuilding.h"
11
#include "buildings/noBuilding.h"
12
#include "buildings/nobBaseWarehouse.h"
13
#include "buildings/nobHarborBuilding.h"
14
#include "world/GameWorld.h"
15
#include "nodeObjs/noFlag.h"
16
#include "nodeObjs/noRoadNode.h"
17
#include "gameData/BuildingProperties.h"
18
#include "gameData/GameConsts.h"
19
#include "gameData/GoodConsts.h"
20
#include "gameData/ShieldConsts.h"
21
#include "s25util/Log.h"
22
#include <sstream>
23

24
Ware::Ware(const GoodType type, noBaseBuilding* goal, noRoadNode* location)
182✔
25
    : next_dir(RoadPathDirection::None), state(State::WaitInWarehouse), location(location),
26
      type(convertShieldToNation(type,
182✔
27
                                 world->GetPlayer(location->GetPlayer()).nation)), // Use nation specific shield
182✔
28
      goal(goal), next_harbor(MapPoint::Invalid())
364✔
29
{
30
    RTTR_Assert(location);
182✔
31
    // Ware in den Index mit eintragen
32
    world->GetPlayer(location->GetPlayer()).RegisterWare(*this);
182✔
33
    if(goal)
182✔
34
        goal->TakeWare(this);
170✔
35
}
182✔
36

37
Ware::~Ware() = default;
364✔
38

39
void Ware::Destroy()
15✔
40
{
41
    RTTR_Assert(!goal);
15✔
42
    RTTR_Assert(!location);
15✔
43
#if RTTR_ENABLE_ASSERTS
44
    for(unsigned p = 0; p < world->GetNumPlayers(); p++)
33✔
45
    {
46
        RTTR_Assert(!world->GetPlayer(p).IsWareRegistred(*this));
18✔
47
        RTTR_Assert(!world->GetPlayer(p).IsWareDependent(*this));
18✔
48
    }
49
#endif
50
}
15✔
51

52
void Ware::Serialize(SerializedGameData& sgd) const
4✔
53
{
54
    sgd.PushEnum<uint8_t>(next_dir);
4✔
55
    sgd.PushEnum<uint8_t>(state);
4✔
56
    sgd.PushObject(location);
4✔
57
    sgd.PushEnum<uint8_t>(type);
4✔
58
    sgd.PushObject(goal);
4✔
59
    helpers::pushPoint(sgd, next_harbor);
4✔
60
}
4✔
61

62
static RoadPathDirection PopRoadPathDirection(SerializedGameData& sgd)
2✔
63
{
64
    if(sgd.GetGameDataVersion() < 5)
2✔
65
    {
66
        const auto iDir = sgd.PopUnsignedChar();
×
67
        if(iDir == 100)
×
68
            return RoadPathDirection::Ship;
×
69
        if(iDir == 0xFF)
×
70
            return RoadPathDirection::None;
×
71
        if(iDir > helpers::MaxEnumValue_v<Direction>)
×
72
            throw SerializedGameData::Error("Invalid RoadPathDirection");
×
73
        return RoadPathDirection(iDir);
×
74
    } else
75
        return sgd.Pop<RoadPathDirection>();
2✔
76
}
77

78
Ware::Ware(SerializedGameData& sgd, const unsigned obj_id)
2✔
79
    : GameObject(sgd, obj_id), next_dir(PopRoadPathDirection(sgd)), state(sgd.Pop<State>()),
6✔
80
      location(sgd.PopObject<noRoadNode>()), type(sgd.Pop<GoodType>()), goal(sgd.PopObject<noBaseBuilding>()),
4✔
81
      next_harbor(sgd.PopMapPoint())
6✔
82
{}
2✔
83

84
void Ware::SetGoal(noBaseBuilding* newGoal)
16✔
85
{
86
    goal = newGoal;
16✔
87
    if(goal)
16✔
88
        goal->TakeWare(this);
12✔
89
}
16✔
90

91
void Ware::RecalcRoute()
314✔
92
{
93
    // Nächste Richtung nehmen
94
    if(location && goal)
314✔
95
        next_dir = world->FindPathForWareOnRoads(*location, *goal, nullptr, &next_harbor);
314✔
96
    else
97
        next_dir = RoadPathDirection::None;
×
98

99
    // Evtl gibts keinen Weg mehr? Dann wieder zurück ins Lagerhaus (wenns vorher überhaupt zu nem Ziel ging)
100
    if(next_dir == RoadPathDirection::None && goal)
314✔
101
    {
102
        RTTR_Assert(location);
4✔
103
        // Tell goal about this
104
        NotifyGoalAboutLostWare();
4✔
105
        if(state == State::WaitForShip)
4✔
106
        {
107
            // Ware was waiting for a ship so send the ware into the harbor
108
            RTTR_Assert(location->GetGOT() == GO_Type::NobHarborbuilding);
1✔
109
            state = State::WaitInWarehouse;
1✔
110
            SetGoal(static_cast<nobHarborBuilding*>(location));
1✔
111
            // but not going by ship
112
            static_cast<nobHarborBuilding*>(goal)->WareDontWantToTravelByShip(this);
1✔
113
        } else
114
        {
115
            // TODO(Replay) This should calculate the next dir even when carried
116
            FindRouteToWarehouse();
3✔
117
        }
118
    } else
119
    {
120
        // If we waited in the harbor for the ship before and don't want to travel now
121
        // -> inform the harbor so that it can remove us from its list
122
        if(state == State::WaitForShip && next_dir != RoadPathDirection::Ship)
310✔
123
        {
124
            RTTR_Assert(location);
×
125
            RTTR_Assert(location->GetGOT() == GO_Type::NobHarborbuilding);
×
126
            state = State::WaitInWarehouse;
×
127
            static_cast<nobHarborBuilding*>(location)->WareDontWantToTravelByShip(this);
×
128
        }
129
    }
130
}
314✔
131

132
void Ware::GoalDestroyed()
11✔
133
{
134
    if(state == State::WaitInWarehouse)
11✔
135
    {
136
        // Ware ist noch im Lagerhaus auf der Warteliste
137
        RTTR_Assert(false); // Should not happen. noBaseBuilding::WareNotNeeded handles this case!
×
138
        goal = nullptr; // just in case: avoid corruption although the ware itself might be lost (won't ever be carried
139
                        // again)
140
    }
141
    // Ist sie evtl. gerade mit dem Schiff unterwegs?
142
    else if(state == State::OnShip)
11✔
143
    {
144
        // Ziel zunächst auf nullptr setzen, was dann vom Zielhafen erkannt wird,
145
        // woraufhin dieser die Ware gleich in sein Inventar mit übernimmt
146
        goal = nullptr;
4✔
147
    }
148
    // Oder wartet sie im Hafen noch auf ein Schiff
149
    else if(state == State::WaitForShip)
7✔
150
    {
151
        // Dann dem Hafen Bescheid sagen
152
        RTTR_Assert(location);
6✔
153
        RTTR_Assert(location->GetGOT() == GO_Type::NobHarborbuilding);
6✔
154
        // This also adds the ware to the harbors inventory
155
        auto ownedWare = static_cast<nobHarborBuilding*>(location)->CancelWareForShip(this);
6✔
156
        // Kill the ware
157
        world->GetPlayer(location->GetPlayer()).RemoveWare(*this);
6✔
158
        goal = nullptr;
6✔
159
        location = nullptr;
6✔
160
        GetEvMgr().AddToKillList(std::move(ownedWare));
6✔
161
    } else
162
    {
163
        // Ware ist unterwegs, Lagerhaus finden und Ware dort einliefern
164
        RTTR_Assert(location);
1✔
165
        RTTR_Assert(location->GetPlayer() < MAX_PLAYERS);
1✔
166

167
        // Currently carried out of a warehouse?
168
        if(nobBaseWarehouse::isStorehouseGOT(location->GetGOT()))
1✔
169
        {
170
            if(location != goal)
×
171
            {
172
                SetGoal(static_cast<noBaseBuilding*>(location));
×
173
            } else // at the goal (which was just destroyed) and get carried out right now? -> we are about to get
174
                   // destroyed...
175
            {
176
                goal = nullptr;
×
177
                next_dir = RoadPathDirection::None;
×
178
            }
179
        }
180
        // Wenn sie an einer Flagge liegt, muss der Weg neu berechnet werden und dem Träger Bescheid gesagt werden
181
        else if(state == State::WaitAtFlag)
1✔
182
        {
183
            goal = nullptr;
1✔
184
            const auto oldNextDir = next_dir;
1✔
185
            FindRouteToWarehouse();
1✔
186
            if(oldNextDir != next_dir)
1✔
187
            {
188
                RemoveWareJobForDir(oldNextDir);
1✔
189
                if(next_dir != RoadPathDirection::None)
1✔
190
                {
191
                    RTTR_Assert(goal); // Having a nextDir implies having a goal
1✔
192
                    CallCarrier();
1✔
193
                } else
194
                    RTTR_Assert(!goal); // Can only have a goal with a valid path
×
195
            }
196
        } else if(state == State::Carried)
×
197
        {
198
            if(goal != location)
×
199
            {
200
                // find a warehouse for us (if we are entering a warehouse already set this as new goal (should only
201
                // happen if its a harbor for shipping as the building wasn't our goal))
NEW
202
                if(nobBaseWarehouse::isStorehouseGOT(
×
NEW
203
                     location->GetGOT())) // currently carried into a warehouse? -> add ware (pathfinding
×
204
                                          // will not return this wh because of path lengths 0)
205
                {
206
                    if(location->GetGOT() != GO_Type::NobHarborbuilding)
×
207
                        LOG.write("WARNING: Ware::GoalDestroyed() -- ware is currently being carried into warehouse or "
×
208
                                  "hq that was not "
209
                                  "it's goal! ware id %i, type %s, player %i, wareloc %i,%i, goal loc %i,%i \n")
210
                          % GetObjId() % WARE_NAMES[type] % location->GetPlayer() % GetLocation()->GetX()
×
211
                          % GetLocation()->GetY() % goal->GetX() % goal->GetY();
×
212
                    SetGoal(static_cast<noBaseBuilding*>(location));
×
213
                } else
214
                {
215
                    goal = nullptr;
×
216
                    FindRouteToWarehouse();
×
217
                }
218
            } else
219
            {
220
                // too late to do anything our road will be removed and ware destroyed when the carrier starts walking
221
                // about
222
                goal = nullptr;
×
223
            }
224
        }
225
    }
226
}
11✔
227

228
void Ware::WaitAtFlag(noFlag* flag)
167✔
229
{
230
    RTTR_Assert(flag);
167✔
231
    state = State::WaitAtFlag;
167✔
232
    location = flag;
167✔
233
}
167✔
234

235
void Ware::WaitInWarehouse(nobBaseWarehouse* wh)
145✔
236
{
237
    RTTR_Assert(wh);
145✔
238
    state = State::WaitInWarehouse;
145✔
239
    location = wh;
145✔
240
}
145✔
241

242
void Ware::Carry(noRoadNode* nextGoal)
407✔
243
{
244
    RTTR_Assert(nextGoal);
407✔
245
    state = State::Carried;
407✔
246
    location = nextGoal;
407✔
247
}
407✔
248

249
/// Gibt dem Ziel der Ware bekannt, dass diese nicht mehr kommen kann
250
void Ware::NotifyGoalAboutLostWare()
15✔
251
{
252
    // Meinem Ziel Bescheid sagen, dass ich weg vom Fenster bin (falls ich ein Ziel habe!)
253
    if(goal)
15✔
254
    {
255
        goal->WareLost(*this);
13✔
256
        goal = nullptr;
13✔
257
        next_dir = RoadPathDirection::None;
13✔
258
    }
259
}
15✔
260

261
/// Wenn die Ware vernichtet werden muss
262
void Ware::WareLost(const unsigned char player)
9✔
263
{
264
    location = nullptr;
9✔
265
    // Inventur verringern
266
    world->GetPlayer(player).DecreaseInventoryWare(type, 1);
9✔
267
    // Ziel der Ware Bescheid sagen
268
    NotifyGoalAboutLostWare();
9✔
269
    // Zentrale Registrierung der Ware löschen
270
    world->GetPlayer(player).RemoveWare(*this);
9✔
271
}
9✔
272

273
void Ware::RemoveWareJobForDir(const RoadPathDirection last_next_dir)
1✔
274
{
275
    // last_next_dir war die letzte Richtung, in die die Ware eigentlich wollte,
276
    // aber nun nicht mehr will, deshalb muss dem Träger Bescheid gesagt werden
277

278
    // War's überhaupt ne richtige Richtung?
279
    if(last_next_dir == RoadPathDirection::None || last_next_dir == RoadPathDirection::Ship)
1✔
280
        return;
×
281
    Direction lastDir = toDirection(last_next_dir);
1✔
282
    // Existiert da noch ne Straße?
283
    if(!location->GetRoute(lastDir))
1✔
284
        return;
×
285
    // Den Trägern Bescheid sagen
286
    location->GetRoute(lastDir)->WareJobRemoved(nullptr);
1✔
287
    // Wenn nicht, könntes ja sein, dass die Straße in ein Lagerhaus führt, dann muss dort Bescheid gesagt werden
288
    if(location->GetRoute(lastDir)->GetF2()->GetType() == NodalObjectType::Building)
1✔
289
    {
290
        auto* bld = static_cast<noBuilding*>(location->GetRoute(Direction::NorthWest)->GetF2());
×
291
        if(BuildingProperties::IsWareHouse(bld->GetBuildingType()))
×
292
            static_cast<nobBaseWarehouse*>(bld)->DontFetchNextWare();
×
293
    }
294
}
295

296
void Ware::CallCarrier()
1✔
297
{
298
    RTTR_Assert(IsWaitingAtFlag());
1✔
299
    RTTR_Assert(next_dir != RoadPathDirection::None && next_dir != RoadPathDirection::Ship);
1✔
300
    RTTR_Assert(location);
1✔
301
    location->GetRoute(toDirection(next_dir))->AddWareJob(location);
1✔
302
}
1✔
303

304
bool Ware::FindRouteToWarehouse()
4✔
305
{
306
    RTTR_Assert(location);
4✔
307
    RTTR_Assert(!goal); // Goal should have been notified and therefore reset
4✔
308
    SetGoal(world->GetPlayer(location->GetPlayer()).FindWarehouseForWare(*this));
4✔
309

310
    if(goal)
4✔
311
    {
312
        // Find path if not already carried (will be called after arrival in that case)
313
        if(state != State::Carried)
3✔
314
        {
315
            if(location == goal)
1✔
316
                next_dir = RoadPathDirection::None; // Warehouse will detect this
×
317
            else
318
            {
319
                next_dir = world->FindPathForWareOnRoads(*location, *goal, nullptr, &next_harbor);
1✔
320
                RTTR_Assert(next_dir != RoadPathDirection::None);
1✔
321
            }
322
        }
323
    } else
324
        next_dir = RoadPathDirection::None; // Make sure we are not going anywhere
1✔
325
    return goal != nullptr;
4✔
326
}
327

328
/// a lost ware got ordered
329
unsigned Ware::CheckNewGoalForLostWare(const noBaseBuilding& newgoal) const
×
330
{
331
    if(!IsWaitingAtFlag()) // todo: check all special cases for wares being carried right now and where possible allow
×
332
                           // them to be ordered
333
        return 0xFFFFFFFF;
×
334
    return CalcPathToGoal(newgoal).length;
×
335
}
336

337
Ware::RouteParams Ware::CalcPathToGoal(const noBaseBuilding& newgoal) const
×
338
{
339
    RTTR_Assert(location);
×
340
    unsigned length;
341
    RoadPathDirection possibledir = world->FindPathForWareOnRoads(*location, newgoal, &length);
×
342
    if(possibledir != RoadPathDirection::None) // there is a valid path to the goal? -> ordered!
×
343
    {
344
        // in case the ware is right in front of the goal building the ware has to be moved away 1 flag and then back
345
        // because non-warehouses cannot just carry in new wares they need a helper to do this
346
        if(possibledir == RoadPathDirection::NorthWest && newgoal.GetFlagPos() == location->GetPos())
×
347
        {
348
            // Not executed for road from flag to the warehouse as that is handled directly by the warehouse
349
            RTTR_Assert(!BuildingProperties::IsWareHouse(newgoal.GetBuildingType()));
×
350
            for(const auto dir : helpers::EnumRange<Direction>{})
×
351
            {
352
                // Bounce of in this direction
353
                if(dir != Direction::NorthWest && location->GetRoute(dir))
×
354
                    return {1, toRoadPathDirection(dir)};
×
355
            }
356
            // got no other route from the flag -> impossible
357
            return {0xFFFFFFFF, RoadPathDirection::None};
×
358
        }
359
        return {length, possibledir};
×
360
    }
361
    return {0xFFFFFFFF, RoadPathDirection::None};
×
362
}
363

364
/// this assumes that the ware is at a flag (todo: handle carried wares) and that there is a valid path to the goal
NEW
365
void Ware::SetNewGoalForLostWare(noBaseBuilding& newgoal)
×
366
{
NEW
367
    const auto newDir = CalcPathToGoal(newgoal).dir;
×
368
    if(newDir != RoadPathDirection::None) // there is a valid path to the goal? -> ordered!
×
369
    {
370
        next_dir = newDir;
×
NEW
371
        SetGoal(&newgoal);
×
372
        CallCarrier();
×
373
    }
374
}
×
375

376
bool Ware::IsRouteToGoal()
×
377
{
378
    RTTR_Assert(location);
×
379
    if(!goal)
×
380
        return false;
×
381
    if(location == goal)
×
382
        return true; // We are at our goal. All ok
×
383
    return world->FindPathForWareOnRoads(*location, *goal) != RoadPathDirection::None;
×
384
}
385

386
/// Informiert Ware, dass eine Schiffsreise beginnt
387
void Ware::StartShipJourney()
8✔
388
{
389
    state = State::OnShip;
8✔
390
    location = nullptr;
8✔
391
}
8✔
392

393
/// Informiert Ware, dass Schiffsreise beendet ist und die Ware nun in einem Hafengebäude liegt
394
void Ware::ShipJorneyEnded(nobHarborBuilding* hb)
4✔
395
{
396
    RTTR_Assert(hb);
4✔
397
    state = State::WaitInWarehouse;
4✔
398
    location = hb;
4✔
399
}
4✔
400

401
/// Beginnt damit auf ein Schiff im Hafen zu warten
402
void Ware::WaitForShip(nobHarborBuilding* hb)
20✔
403
{
404
    RTTR_Assert(hb);
20✔
405
    state = State::WaitForShip;
20✔
406
    location = hb;
20✔
407
}
20✔
408

409
std::string Ware::ToString() const
×
410
{
411
    std::stringstream s;
×
412
    s << "Ware(" << GetObjId() << "): type=" << GoodType2String(type) << ", location=" << location->GetX() << ","
×
413
      << location->GetY();
×
414
    return s.str();
×
415
}
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