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

Return-To-The-Roots / s25client / 24041761791

06 Apr 2026 05:10PM UTC coverage: 50.37% (+0.03%) from 50.337%
24041761791

Pull #1910

github

web-flow
Merge 8d86aacd8 into e4146df45
Pull Request #1910: Fix wrongly shown soldiers & Refactor HQ start wares and inventory handling

197 of 409 new or added lines in 22 files covered. (48.17%)

96 existing lines in 25 files now uncovered.

23067 of 45795 relevant lines covered (50.37%)

43239.58 hits per line

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

82.47
/libs/s25main/RoadSegment.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 "RoadSegment.h"
6
#include "EventManager.h"
7
#include "GamePlayer.h"
8
#include "SerializedGameData.h"
9
#include "buildings/nobBaseWarehouse.h"
10
#include "figures/nofCarrier.h"
11
#include "random/Random.h"
12
#include "world/GameWorld.h"
13
#include "nodeObjs/noFlag.h"
14
#include "nodeObjs/noRoadNode.h"
15
#include "gameData/BuildingProperties.h"
16
#include "s25util/Log.h"
17
#include <utility>
18

19
RoadSegment::RoadSegment(const RoadType rt, noRoadNode* const f1, noRoadNode* const f2, std::vector<Direction> route)
853✔
20
    : rt(rt), f1(f1), f2(f2), route(std::move(route))
853✔
21
{
22
    carriers_[0] = carriers_[1] = nullptr;
853✔
23
}
853✔
24

25
RoadSegment::RoadSegment(SerializedGameData& sgd, const unsigned obj_id)
12✔
26
    : GameObject(sgd, obj_id), rt(sgd.Pop<RoadType>()), f1(sgd.PopObject<noRoadNode>()), f2(sgd.PopObject<noRoadNode>())
12✔
27
{
28
    if(sgd.GetGameDataVersion() < 7)
12✔
29
        route.resize(sgd.PopUnsignedShort());
×
30
    else
31
        helpers::popContainer(sgd, route);
12✔
32

33
    carriers_[0] = sgd.PopObject<nofCarrier>(GO_Type::NofCarrier);
12✔
34
    carriers_[1] = sgd.PopObject<nofCarrier>(GO_Type::NofCarrier);
12✔
35

36
    if(sgd.GetGameDataVersion() < 7)
12✔
37
        helpers::popContainer(sgd, route, true);
×
38

39
    // tell the noRoadNodes about our existance
40
    f1->SetRoute(route.front(), this);
12✔
41
    f2->SetRoute(route.back() + 3u, this);
12✔
42
}
12✔
43

44
bool RoadSegment::GetNodeID(const noRoadNode& rn) const
90✔
45
{
46
    RTTR_Assert(&rn == f1 || &rn == f2);
90✔
47
    // Return true if it is the 2nd node
48
    return (&rn == f2);
90✔
49
}
50

51
void RoadSegment::Destroy()
223✔
52
{
53
    // This can be the road segment from the flag to the building (always in this order!)
54
    // Those are not considered "roads" and therefore never registered
55
    RTTR_Assert(f1->GetGOT() == GO_Type::Flag);
223✔
56
    if(f2->GetGOT() == GO_Type::Flag)
223✔
57
        world->GetPlayer(f1->GetPlayer()).DeleteRoad(this);
103✔
58

59
    if(carriers_[0])
223✔
60
        carriers_[0]->LostWork();
3✔
61
    if(carriers_[1])
223✔
62
        carriers_[1]->LostWork();
×
63

64
    if(!route.empty())
223✔
65
    {
66
        // Tell all characters on this road they lost their jobs
67
        MapPoint pt = f1->GetPos();
223✔
68

69
        for(unsigned short i = 0; i < route.size() + 1; ++i)
900✔
70
        {
71
            // Figuren sammeln
72
            for(noBase& object : world->GetFigures(pt))
3,066✔
73
            {
74
                if(object.GetType() == NodalObjectType::Figure)
179✔
75
                {
76
                    auto& figure = static_cast<noFigure&>(object);
77✔
77
                    if(figure.GetCurrentRoad() == this)
77✔
78
                    {
79
                        figure.Abrogate();
1✔
80
                        figure.StartWandering();
1✔
81
                    }
82
                }
83
            }
84

85
            world->RoadNodeAvailable(pt);
677✔
86

87
            if(i != route.size())
677✔
88
            {
89
                pt = world->GetNeighbour(pt, route[i]);
454✔
90
            }
91
        }
92

93
        route.clear();
223✔
94
    }
95
}
223✔
96

97
void RoadSegment::Serialize(SerializedGameData& sgd) const
25✔
98
{
99
    sgd.PushEnum<uint8_t>(rt);
25✔
100
    sgd.PushObject(f1);
25✔
101
    sgd.PushObject(f2);
25✔
102
    helpers::pushContainer(sgd, route);
25✔
103
    sgd.PushObject(carriers_[0], true);
25✔
104
    sgd.PushObject(carriers_[1], true);
25✔
105
}
25✔
106

107
/**
108
 *  zerteilt die Straße in 2 Teile.
109
 */
110
void RoadSegment::SplitRoad(noFlag* splitflag)
21✔
111
{
112
    // Flag 1 _________ This flag _________ Flag 2
113
    //         |          broken road           |
114

115
    // Store the old route of the road so that we can tell everyone on it later
116
    std::vector<Direction> old_route(route);
42✔
117

118
    // find the place where the road is cut ( = length of the first section )
119
    unsigned length1, length2;
120
    MapPoint t = f1->GetPos();
21✔
121
    for(length1 = 0; length1 < route.size(); ++length1)
71✔
122
    {
123
        if(t == splitflag->GetPos())
71✔
124
            break;
21✔
125

126
        t = world->GetNeighbour(t, route[length1]);
50✔
127
    }
128

129
    length2 = this->route.size() - length1;
21✔
130

131
    std::vector<Direction> second_route(length2);
42✔
132
    for(unsigned i = 0; i < length2; ++i)
77✔
133
        second_route[i] = this->route[length1 + i];
56✔
134

135
    auto* second = new RoadSegment(rt, splitflag, f2, second_route);
21✔
136

137
    // donkey road? Then upgrade flag, since it will be between donkey roads
138
    if(rt == RoadType::Donkey)
21✔
139
        splitflag->Upgrade();
×
140

141
    // create 1st section from F1 to this F (1st section is this road!)
142
    route.resize(length1);
21✔
143
    // f1 = f1;
144
    f2 = splitflag;
21✔
145

146
    f1->SetRoute(route.front(), this);
21✔
147
    splitflag->SetRoute(route.back() + 3u, this);
21✔
148

149
    // 2nd section from this F to F2
150

151
    splitflag->SetRoute(second->route.front(), second);
21✔
152
    second->f2->SetRoute(second->route.back() + 3u, second);
21✔
153

154
    // Notify all characters on the road
155
    t = f1->GetPos();
21✔
156

157
    for(unsigned short i = 0; i < old_route.size() + 1; ++i)
148✔
158
    {
159
        for(noBase& object : world->GetFigures(t))
508✔
160
        {
UNCOV
161
            if(object.GetType() == NodalObjectType::Figure)
×
162
            {
UNCOV
163
                auto& figure = static_cast<noFigure&>(object);
×
UNCOV
164
                if(figure.GetCurrentRoad() == this)
×
165
                    figure.CorrectSplitData(second);
×
166
            }
167
        }
168

169
        if(i != old_route.size())
127✔
170
        {
171
            t = world->GetNeighbour(t, old_route[i]);
106✔
172
        }
173
    }
174

175
    world->GetPlayer(f1->GetPlayer()).AddRoad(second);
21✔
176

177
    for(unsigned char i = 0; i < 2; ++i)
63✔
178
    {
179
        if(carriers_[i])
42✔
180
            carriers_[i]->RoadSplitted(this, second);
2✔
181
        else if(i == 0)
40✔
182
            // If road was unoccupied before then add 2nd part to the unoccupied roads
183
            // (1st is already included)
184
            world->GetPlayer(f1->GetPlayer()).FindCarrierForRoad(*second);
19✔
185
    }
186
}
21✔
187

188
/**
189
 *  Überprüft ob es an den Flaggen noch Waren zu tragen gibt für den Träger.
190
 *  Nur bei Straßen mit 2 Flagge aufrufen, nicht bei Hauseingängen etc. !!
191
 */
192
bool RoadSegment::AreWareJobs(const bool flag, CarrierType ct, const bool take_ware_immediately) const
442✔
193
{
194
    unsigned jobs_count;
195

196
    // Anzahl der Waren, die getragen werden wollen, ermitteln
197
    if(flag)
442✔
198
        jobs_count = static_cast<noFlag*>(f2)->GetNumWaresForRoad((route.back() + 3u));
219✔
199
    else
200
        jobs_count = static_cast<noFlag*>(f1)->GetNumWaresForRoad(route.front());
223✔
201

202
    // Nur eine Ware da --> evtl läuft schon ein anderer Träger/Esel hin, nur wo Esel und Träger da sind
203
    // Wenn der Träger nun natürlich schon da ist, kann er die mitnehmen
204
    if(jobs_count == 1 && carriers_[0] && carriers_[1] && !take_ware_immediately)
442✔
205
    {
206
        // anderen Esel ermitteln
207
        unsigned otherCarrier = (ct == CarrierType::Donkey) ? 0 : 1;
×
208

209
        switch(carriers_[otherCarrier]->GetCarrierState())
×
210
        {
211
            default: break;
×
212
            case CarrierState::FetchWare:
×
213
            case CarrierState::CarryWare:
214
            case CarrierState::WaitForWareSpace:
215
            case CarrierState::GoBackFromFlag:
216
            {
217
                // Läuft der in die Richtung, holt eine Ware bzw. ist schon fast da, braucht der hier nicht hinlaufen
218
                if(carriers_[otherCarrier]->GetRoadDir() == !flag)
×
219
                    return false;
×
220
            }
221
            break;
×
222
            case CarrierState::CarryWareToBuilding:
×
223
            case CarrierState::LeaveBuilding:
224
            {
225
                // Wenn an die Flagge ein Gebäude angrenzt und der Träger da was reinträgt, kann der auch die Ware
226
                // gleich mitnehmen, der zweite muss hier also nicht kommen
227
                if((carriers_[otherCarrier]->GetCurrentRoad()->f1 == f1 && !flag)
×
228
                   || (carriers_[otherCarrier]->GetCurrentRoad()->f1 == f2 && flag))
×
229
                    return false;
×
230
            }
231
            break;
×
232
        }
233
    }
234

235
    return (jobs_count > 0);
442✔
236
}
237
/**
238
 *  Eine Ware sagt Bescheid, dass sie über dem Weg getragen werden will.
239
 *
240
 *  rn ist die Flagge, von der sie kommt
241
 */
242
void RoadSegment::AddWareJob(const noRoadNode* rn)
133✔
243
{
244
    // Wenn das eine Straße zu einer Gebäudetür ist, muss dem entsprechenden Gebäude Bescheid gesagt werden (momentan
245
    // nur Lagerhäuser!)
246
    if(route.size() == 1)
133✔
247
    {
248
        if(f2->GetType() == NodalObjectType::Building)
1✔
249
        {
250
            if(BuildingProperties::IsWareHouse(static_cast<noBuilding*>(f2)->GetBuildingType()))
1✔
251
                static_cast<nobBaseWarehouse*>(f2)->FetchWare();
1✔
252
            else
253
                LOG.write("RoadSegment::AddWareJob: WARNING: Ware in front of building at %i,%i (gf: %u)!\n")
×
254
                  % f2->GetPos().x % f2->GetPos().y % GetEvMgr().GetCurrentGF();
×
255
        } else
256
            LOG.write("RoadSegment::AddWareJob: WARNING: Ware in front of building site at %i,%i (gf: %u)!\n")
×
257
              % f2->GetPos().x % f2->GetPos().y % GetEvMgr().GetCurrentGF();
×
258
    }
259

260
    // Zufällig Esel oder Träger zuerst fragen, ob er Zeit hat
261
    unsigned char first = RANDOM_RAND(2);
133✔
262
    for(unsigned char i = 0; i < 2; ++i)
339✔
263
    {
264
        if(carriers_[(i + first) % 2])
246✔
265
        {
266
            if(carriers_[(i + first) % 2]->AddWareJob(rn))
127✔
267
                // Ja, hat Zeit, dann brauchen wir den anderen nicht zu fragen
268
                break;
40✔
269
        }
270
    }
271
}
133✔
272

273
/**
274
 *  Eine Ware will nicht mehr befördert werden.
275
 */
276
void RoadSegment::WareJobRemoved(const noFigure* const exception)
125✔
277
{
278
    // Allen Trägern Bescheid sagen
279
    for(unsigned char i = 0; i < 2; ++i)
375✔
280
    {
281
        if(carriers_[i] && carriers_[i] != exception)
250✔
282
            carriers_[i]->RemoveWareJob();
3✔
283
    }
284
}
125✔
285

286
/**
287
 *  Baut die Straße zu einer Eselstraße aus.
288
 */
289
void RoadSegment::UpgradeDonkeyRoad()
3✔
290
{
291
    // Nur normale Straße können Eselstraßen werden
292
    if(rt != RoadType::Normal)
3✔
293
        return;
1✔
294

295
    rt = RoadType::Donkey;
2✔
296

297
    // Eselstraßen setzen
298
    MapPoint pt = f1->GetPos();
2✔
299
    for(auto i : route)
6✔
300
    {
301
        world->SetPointRoad(pt, i, PointRoad::Donkey);
4✔
302
        pt = world->GetNeighbour(pt, i);
4✔
303
    }
304

305
    // Flaggen auf beiden Seiten upgraden
306
    RTTR_Assert(f1->GetGOT() == GO_Type::Flag);
2✔
307
    RTTR_Assert(f2->GetGOT() == GO_Type::Flag);
2✔
308

309
    static_cast<noFlag*>(f1)->Upgrade();
2✔
310
    static_cast<noFlag*>(f2)->Upgrade();
2✔
311

312
    // Esel rufen (falls es einen gibt)
313
    TryGetDonkey();
2✔
314
}
315

316
/**
317
 *  Soll versuchen einen Esel zu bekommen.
318
 */
319
void RoadSegment::TryGetDonkey()
186✔
320
{
321
    // Nur rufen, falls es eine Eselstraße ist, noch kein Esel da ist, aber schon ein Träger da ist
322
    if(NeedDonkey())
186✔
UNCOV
323
        carriers_[1] = world->GetPlayer(f1->GetPlayer()).OrderDonkey(*this);
×
324
}
186✔
325

326
/**
327
 *  Ein Träger muss kündigen, aus welchen Gründen auch immer.
328
 */
329
void RoadSegment::CarrierAbrogated(nofCarrier* carrier)
5✔
330
{
331
    // Gucken, ob Träger und Esel gekündigt hat
332
    if(carrier->GetCarrierType() == CarrierType::Normal || carrier->GetCarrierType() == CarrierType::Boat)
5✔
333
    {
334
        // Straße wieder unbesetzt, bzw. nur noch Esel
335
        this->carriers_[0] = nullptr;
4✔
336
        world->GetPlayer(f1->GetPlayer()).FindCarrierForRoad(*this);
4✔
337
    } else
338
    {
339
        // Kein Esel mehr da, versuchen, neuen zu bestellen
340
        this->carriers_[1] = world->GetPlayer(f1->GetPlayer()).OrderDonkey(*this);
1✔
341
    }
342
}
5✔
343
/**
344
 * Return flag at the other end of the road
345
 */
346
const noFlag& RoadSegment::GetOtherFlag(const noFlag& flag) const
6✔
347
{
348
    if(GetNodeID(flag))
6✔
349
        return dynamic_cast<noFlag&>(*f1);
×
350
    else
351
        return dynamic_cast<noFlag&>(*f2);
6✔
352
}
353
/**
354
 * Return last road direction to flag at the other end of the road
355
 */
356
Direction RoadSegment::GetOtherFlagDir(const noFlag& flag) const
5✔
357
{
358
    if(GetNodeID(flag))
5✔
359
        return (route.front() + 3u);
×
360
    else
361
        return route.back();
5✔
362
}
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