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

Return-To-The-Roots / s25client / 22108417915

17 Feb 2026 05:19PM UTC coverage: 50.347% (-0.5%) from 50.809%
22108417915

Pull #1720

github

web-flow
Merge cdf86094c into 6e122731f
Pull Request #1720: Add leather addon

281 of 1064 new or added lines in 65 files covered. (26.41%)

42 existing lines in 25 files now uncovered.

23023 of 45729 relevant lines covered (50.35%)

43595.28 hits per line

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

65.32
/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()
12✔
40
{
41
    RTTR_Assert(!goal);
12✔
42
    RTTR_Assert(!location);
12✔
43
#if RTTR_ENABLE_ASSERTS
44
    for(unsigned p = 0; p < world->GetNumPlayers(); p++)
26✔
45
    {
46
        RTTR_Assert(!world->GetPlayer(p).IsWareRegistred(*this));
14✔
47
        RTTR_Assert(!world->GetPlayer(p).IsWareDependent(*this));
14✔
48
    }
49
#endif
50
}
12✔
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([&]() {
2✔
81
          // GoodType::Nothing is moved because of adding new wares due addons
82
          auto ware = sgd.Pop<GoodType>();
2✔
83
          if(sgd.GetGameDataVersion() < 11)
2✔
84
          {
85
              // The old GoodType::Nothing is now GoodType::Grapes
NEW
86
              if(ware == GoodType::Grapes)
×
NEW
87
                  ware = GoodType::Nothing;
×
88
          } else if(sgd.GetGameDataVersion() < 12)
2✔
89
          {
90
              // The old GoodType::Nothing is now GoodType::Skins
NEW
91
              if(ware == GoodType::Skins)
×
NEW
92
                  ware = GoodType::Nothing;
×
93
          }
94
          return ware;
2✔
NEW
95
      }()),
×
96
      goal(sgd.PopObject<noBaseBuilding>()), next_harbor(sgd.PopMapPoint())
4✔
97
{}
2✔
98

99
void Ware::SetGoal(noBaseBuilding* newGoal)
16✔
100
{
101
    goal = newGoal;
16✔
102
    if(goal)
16✔
103
        goal->TakeWare(this);
12✔
104
}
16✔
105

106
void Ware::RecalcRoute()
317✔
107
{
108
    // Nächste Richtung nehmen
109
    if(location && goal)
317✔
110
        next_dir = world->FindPathForWareOnRoads(*location, *goal, nullptr, &next_harbor);
317✔
111
    else
112
        next_dir = RoadPathDirection::None;
×
113

114
    // Evtl gibts keinen Weg mehr? Dann wieder zurück ins Lagerhaus (wenns vorher überhaupt zu nem Ziel ging)
115
    if(next_dir == RoadPathDirection::None && goal)
317✔
116
    {
117
        RTTR_Assert(location);
4✔
118
        // Tell goal about this
119
        NotifyGoalAboutLostWare();
4✔
120
        if(state == State::WaitForShip)
4✔
121
        {
122
            // Ware was waiting for a ship so send the ware into the harbor
123
            RTTR_Assert(location->GetGOT() == GO_Type::NobHarborbuilding);
1✔
124
            state = State::WaitInWarehouse;
1✔
125
            SetGoal(static_cast<nobHarborBuilding*>(location));
1✔
126
            // but not going by ship
127
            static_cast<nobHarborBuilding*>(goal)->WareDontWantToTravelByShip(this);
1✔
128
        } else
129
        {
130
            // TODO(Replay) This should calculate the next dir even when carried
131
            FindRouteToWarehouse();
3✔
132
        }
133
    } else
134
    {
135
        // If we waited in the harbor for the ship before and don't want to travel now
136
        // -> inform the harbor so that it can remove us from its list
137
        if(state == State::WaitForShip && next_dir != RoadPathDirection::Ship)
313✔
138
        {
139
            RTTR_Assert(location);
×
140
            RTTR_Assert(location->GetGOT() == GO_Type::NobHarborbuilding);
×
141
            state = State::WaitInWarehouse;
×
142
            static_cast<nobHarborBuilding*>(location)->WareDontWantToTravelByShip(this);
×
143
        }
144
    }
145
}
317✔
146

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

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

243
void Ware::WaitAtFlag(noFlag* flag)
170✔
244
{
245
    RTTR_Assert(flag);
170✔
246
    state = State::WaitAtFlag;
170✔
247
    location = flag;
170✔
248
}
170✔
249

250
void Ware::WaitInWarehouse(nobBaseWarehouse* wh)
148✔
251
{
252
    RTTR_Assert(wh);
148✔
253
    state = State::WaitInWarehouse;
148✔
254
    location = wh;
148✔
255
}
148✔
256

257
void Ware::Carry(noRoadNode* nextGoal)
416✔
258
{
259
    RTTR_Assert(nextGoal);
416✔
260
    state = State::Carried;
416✔
261
    location = nextGoal;
416✔
262
}
416✔
263

264
/// Gibt dem Ziel der Ware bekannt, dass diese nicht mehr kommen kann
265
void Ware::NotifyGoalAboutLostWare()
12✔
266
{
267
    // Meinem Ziel Bescheid sagen, dass ich weg vom Fenster bin (falls ich ein Ziel habe!)
268
    if(goal)
12✔
269
    {
270
        goal->WareLost(*this);
10✔
271
        goal = nullptr;
10✔
272
        next_dir = RoadPathDirection::None;
10✔
273
    }
274
}
12✔
275

276
/// Wenn die Ware vernichtet werden muss
277
void Ware::WareLost(const unsigned char player)
6✔
278
{
279
    location = nullptr;
6✔
280
    // Inventur verringern
281
    world->GetPlayer(player).DecreaseInventoryWare(type, 1);
6✔
282
    // Ziel der Ware Bescheid sagen
283
    NotifyGoalAboutLostWare();
6✔
284
    // Zentrale Registrierung der Ware löschen
285
    world->GetPlayer(player).RemoveWare(*this);
6✔
286
}
6✔
287

288
void Ware::RemoveWareJobForDir(const RoadPathDirection last_next_dir)
1✔
289
{
290
    // last_next_dir war die letzte Richtung, in die die Ware eigentlich wollte,
291
    // aber nun nicht mehr will, deshalb muss dem Träger Bescheid gesagt werden
292

293
    // War's überhaupt ne richtige Richtung?
294
    if(last_next_dir == RoadPathDirection::None || last_next_dir == RoadPathDirection::Ship)
1✔
295
        return;
×
296
    Direction lastDir = toDirection(last_next_dir);
1✔
297
    // Existiert da noch ne Straße?
298
    if(!location->GetRoute(lastDir))
1✔
299
        return;
×
300
    // Den Trägern Bescheid sagen
301
    location->GetRoute(lastDir)->WareJobRemoved(nullptr);
1✔
302
    // Wenn nicht, könntes ja sein, dass die Straße in ein Lagerhaus führt, dann muss dort Bescheid gesagt werden
303
    if(location->GetRoute(lastDir)->GetF2()->GetType() == NodalObjectType::Building)
1✔
304
    {
305
        auto* bld = static_cast<noBuilding*>(location->GetRoute(Direction::NorthWest)->GetF2());
×
306
        if(BuildingProperties::IsWareHouse(bld->GetBuildingType()))
×
307
            static_cast<nobBaseWarehouse*>(bld)->DontFetchNextWare();
×
308
    }
309
}
310

311
void Ware::CallCarrier()
1✔
312
{
313
    RTTR_Assert(IsWaitingAtFlag());
1✔
314
    RTTR_Assert(next_dir != RoadPathDirection::None && next_dir != RoadPathDirection::Ship);
1✔
315
    RTTR_Assert(location);
1✔
316
    location->GetRoute(toDirection(next_dir))->AddWareJob(location);
1✔
317
}
1✔
318

319
bool Ware::FindRouteToWarehouse()
4✔
320
{
321
    RTTR_Assert(location);
4✔
322
    RTTR_Assert(!goal); // Goal should have been notified and therefore reset
4✔
323
    SetGoal(world->GetPlayer(location->GetPlayer()).FindWarehouseForWare(*this));
4✔
324

325
    if(goal)
4✔
326
    {
327
        // Find path if not already carried (will be called after arrival in that case)
328
        if(state != State::Carried)
3✔
329
        {
330
            if(location == goal)
1✔
331
                next_dir = RoadPathDirection::None; // Warehouse will detect this
×
332
            else
333
            {
334
                next_dir = world->FindPathForWareOnRoads(*location, *goal, nullptr, &next_harbor);
1✔
335
                RTTR_Assert(next_dir != RoadPathDirection::None);
1✔
336
            }
337
        }
338
    } else
339
        next_dir = RoadPathDirection::None; // Make sure we are not going anywhere
1✔
340
    return goal != nullptr;
4✔
341
}
342

343
/// a lost ware got ordered
344
unsigned Ware::CheckNewGoalForLostWare(const noBaseBuilding& newgoal) const
×
345
{
346
    if(!IsWaitingAtFlag()) // todo: check all special cases for wares being carried right now and where possible allow
×
347
                           // them to be ordered
348
        return 0xFFFFFFFF;
×
349
    return CalcPathToGoal(newgoal).length;
×
350
}
351

352
Ware::RouteParams Ware::CalcPathToGoal(const noBaseBuilding& newgoal) const
×
353
{
354
    RTTR_Assert(location);
×
355
    unsigned length;
356
    RoadPathDirection possibledir = world->FindPathForWareOnRoads(*location, newgoal, &length);
×
357
    if(possibledir != RoadPathDirection::None) // there is a valid path to the goal? -> ordered!
×
358
    {
359
        // in case the ware is right in front of the goal building the ware has to be moved away 1 flag and then back
360
        // because non-warehouses cannot just carry in new wares they need a helper to do this
361
        if(possibledir == RoadPathDirection::NorthWest && newgoal.GetFlagPos() == location->GetPos())
×
362
        {
363
            // Not executed for road from flag to the warehouse as that is handled directly by the warehouse
364
            RTTR_Assert(!BuildingProperties::IsWareHouse(newgoal.GetBuildingType()));
×
365
            for(const auto dir : helpers::EnumRange<Direction>{})
×
366
            {
367
                // Bounce of in this direction
368
                if(dir != Direction::NorthWest && location->GetRoute(dir))
×
369
                    return {1, toRoadPathDirection(dir)};
×
370
            }
371
            // got no other route from the flag -> impossible
372
            return {0xFFFFFFFF, RoadPathDirection::None};
×
373
        }
374
        return {length, possibledir};
×
375
    }
376
    return {0xFFFFFFFF, RoadPathDirection::None};
×
377
}
378

379
/// this assumes that the ware is at a flag (todo: handle carried wares) and that there is a valid path to the goal
380
void Ware::SetNewGoalForLostWare(noBaseBuilding& newgoal)
×
381
{
382
    const auto newDir = CalcPathToGoal(newgoal).dir;
×
383
    if(newDir != RoadPathDirection::None) // there is a valid path to the goal? -> ordered!
×
384
    {
385
        next_dir = newDir;
×
386
        SetGoal(&newgoal);
×
387
        CallCarrier();
×
388
    }
389
}
×
390

391
bool Ware::IsRouteToGoal()
×
392
{
393
    RTTR_Assert(location);
×
394
    if(!goal)
×
395
        return false;
×
396
    if(location == goal)
×
397
        return true; // We are at our goal. All ok
×
398
    return world->FindPathForWareOnRoads(*location, *goal) != RoadPathDirection::None;
×
399
}
400

401
/// Informiert Ware, dass eine Schiffsreise beginnt
402
void Ware::StartShipJourney()
5✔
403
{
404
    state = State::OnShip;
5✔
405
    location = nullptr;
5✔
406
}
5✔
407

408
/// Informiert Ware, dass Schiffsreise beendet ist und die Ware nun in einem Hafengebäude liegt
409
void Ware::ShipJorneyEnded(nobHarborBuilding* hb)
4✔
410
{
411
    RTTR_Assert(hb);
4✔
412
    state = State::WaitInWarehouse;
4✔
413
    location = hb;
4✔
414
}
4✔
415

416
/// Beginnt damit auf ein Schiff im Hafen zu warten
417
void Ware::WaitForShip(nobHarborBuilding* hb)
17✔
418
{
419
    RTTR_Assert(hb);
17✔
420
    state = State::WaitForShip;
17✔
421
    location = hb;
17✔
422
}
17✔
423

424
std::string Ware::ToString() const
×
425
{
426
    std::stringstream s;
×
427
    s << "Ware(" << GetObjId() << "): type=" << GoodType2String(type) << ", location=" << location->GetX() << ","
×
428
      << location->GetY();
×
429
    return s.str();
×
430
}
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