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

Return-To-The-Roots / s25client / 22044569181

15 Feb 2026 10:50PM UTC coverage: 50.34% (-0.5%) from 50.826%
22044569181

Pull #1720

github

web-flow
Merge 4dbe54b70 into 6db06730b
Pull Request #1720: Add leather addon

274 of 1055 new or added lines in 65 files covered. (25.97%)

286 existing lines in 28 files now uncovered.

23017 of 45723 relevant lines covered (50.34%)

43559.46 hits per line

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

73.93
/libs/s25main/GameCommands.cpp
1
// Copyright (C) 2005 - 2025 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "GameCommands.h"
6
#include "AddonHelperFunctions.h"
7
#include "GamePlayer.h"
8
#include "LeatherLoader.h"
9
#include "WineLoader.h"
10
#include "buildings/nobBaseWarehouse.h"
11
#include "buildings/nobHarborBuilding.h"
12
#include "buildings/nobMilitary.h"
13
#include "buildings/nobShipYard.h"
14
#include "buildings/nobTemple.h"
15
#include "enum_cast.hpp"
16
#include "helpers/MaxEnumValue.h"
17
#include "helpers/format.hpp"
18
#include "world/GameWorld.h"
19
#include "nodeObjs/noFlag.h"
20
#include "nodeObjs/noShip.h"
21
#include "gameData/SettingTypeConv.h"
22
#include <algorithm>
23
#include <stdexcept>
24

25
namespace gc {
26

27
void SetFlag::Execute(GameWorld& world, uint8_t playerId)
256✔
28
{
29
    world.SetFlag(pt_, playerId);
256✔
30
}
256✔
31

32
void DestroyFlag::Execute(GameWorld& world, uint8_t playerId)
209✔
33
{
34
    world.DestroyFlag(pt_, playerId);
209✔
35
}
209✔
36

37
BuildRoad::BuildRoad(Serializer& ser)
61✔
38
    : Coords(GCType::BuildRoad, ser), boat_road(ser.PopBool()), route(ser.PopUnsignedInt())
61✔
39
{
40
    for(Direction& i : route)
339✔
41
        i = helpers::popEnum<Direction>(ser);
278✔
42
}
61✔
43

44
void BuildRoad::Serialize(Serializer& ser) const
122✔
45
{
46
    Coords::Serialize(ser);
122✔
47

48
    ser.PushBool(boat_road);
122✔
49
    ser.PushUnsignedInt(route.size());
122✔
50
    for(auto i : route)
678✔
51
        helpers::pushEnum<uint8_t>(ser, i);
556✔
52
}
122✔
53

54
void BuildRoad::Execute(GameWorld& world, uint8_t playerId)
72✔
55
{
56
    world.BuildRoad(playerId, boat_road, pt_, route);
72✔
57
}
72✔
58

59
DestroyRoad::DestroyRoad(Serializer& ser)
12✔
60
    : Coords(GCType::DestroyRoad, ser), start_dir(helpers::popEnum<Direction>(ser))
12✔
61
{}
12✔
62

63
void DestroyRoad::Serialize(Serializer& ser) const
24✔
64
{
65
    Coords::Serialize(ser);
24✔
66

67
    helpers::pushEnum<uint8_t>(ser, start_dir);
24✔
68
}
24✔
69

70
void DestroyRoad::Execute(GameWorld& world, uint8_t playerId)
12✔
71
{
72
    auto* flag = world.GetSpecObj<noFlag>(pt_);
12✔
73
    if(flag && flag->GetPlayer() == playerId)
12✔
74
        flag->DestroyRoad(start_dir);
10✔
75
}
12✔
76

77
UpgradeRoad::UpgradeRoad(Serializer& ser)
5✔
78
    : Coords(GCType::UpgradeRoad, ser), start_dir(helpers::popEnum<Direction>(ser))
5✔
79
{}
5✔
80

81
void UpgradeRoad::Serialize(Serializer& ser) const
10✔
82
{
83
    Coords::Serialize(ser);
10✔
84
    helpers::pushEnum<uint8_t>(ser, start_dir);
10✔
85
}
10✔
86

87
void UpgradeRoad::Execute(GameWorld& world, uint8_t playerId)
5✔
88
{
89
    auto* flag = world.GetSpecObj<noFlag>(pt_);
5✔
90
    if(flag && flag->GetPlayer() == playerId)
5✔
91
        flag->UpgradeRoad(start_dir);
4✔
92
}
5✔
93

94
ChangeDistribution::ChangeDistribution(Deserializer& ser) : GameCommand(GCType::ChangeDistribution)
2✔
95
{
96
    if(ser.getDataVersion() >= 2)
2✔
97
        helpers::popContainer(ser, data);
2✔
98
    else
99
    {
NEW
100
        const unsigned wineAddonAdditionalDistributions = 3;
×
NEW
101
        const unsigned leatherAddonAdditionalDistributions = 3;
×
102

103
        auto const numNotSavedDistributions =
NEW
104
          leatherAddonAdditionalDistributions + (ser.getDataVersion() < 1 ? wineAddonAdditionalDistributions : 0);
×
105

NEW
106
        std::vector<Distributions::value_type> tmpData(std::tuple_size_v<Distributions> - numNotSavedDistributions);
×
107

NEW
108
        auto getSkipBuildingAndDefault = [&](DistributionMapping const& mapping) {
×
109
            // Skipped and standard distribution in skipped case
NEW
110
            std::tuple<bool, unsigned int> result = {false, 0};
×
NEW
111
            if(ser.getDataVersion() < 1)
×
112
            {
113
                // skip over wine buildings
NEW
114
                std::get<0>(result) |= wineaddon::isWineAddonBuildingType(std::get<BuildingType>(mapping));
×
115
            }
116

117
            // skip over leather addon buildings and leather addon wares only
NEW
118
            std::get<0>(result) |= leatheraddon::isLeatherAddonBuildingType(std::get<BuildingType>(mapping));
×
119

NEW
120
            if(std::get<BuildingType>(mapping) == BuildingType::Slaughterhouse
×
NEW
121
               && (std::get<GoodType>(mapping) == GoodType::Ham))
×
NEW
122
                result = {true, 8};
×
NEW
123
            return result;
×
NEW
124
        };
×
125

NEW
126
        helpers::popContainer(ser, tmpData, true);
×
127
        size_t srcIdx = 0, tgtIdx = 0;
×
128
        for(const auto& mapping : distributionMap)
×
129
        {
130
            // skip over not stored buildings in tmpData
NEW
131
            const auto [skipped, defaultValue] = getSkipBuildingAndDefault(mapping);
×
NEW
132
            const auto setting = skipped ? defaultValue : tmpData[srcIdx++];
×
UNCOV
133
            data[tgtIdx++] = setting;
×
134
        }
135
    }
136
}
2✔
137

138
void ChangeDistribution::Execute(GameWorld& world, uint8_t playerId)
6✔
139
{
140
    world.GetPlayer(playerId).ChangeDistribution(data);
6✔
141
}
6✔
142

143
ChangeBuildOrder::ChangeBuildOrder(Deserializer& ser)
2✔
144
    : GameCommand(GCType::ChangeBuildOrder), useCustomBuildOrder(ser.PopBool())
2✔
145
{
146
    if(ser.getDataVersion() >= 2)
2✔
147
    {
148
        for(BuildingType& i : data)
78✔
149
            i = helpers::popEnum<BuildingType>(ser);
76✔
150
    } else
151
    {
152
        auto countOfNotAvailableBuildingsInSaveGame =
NEW
153
          ser.getDataVersion() < 1 ? numWineAndLeatherAddonBuildings : numLeatherAddonBuildings;
×
NEW
154
        std::vector<BuildingType> buildOrder(data.size() - countOfNotAvailableBuildingsInSaveGame);
×
155

NEW
156
        if(ser.getDataVersion() < 1)
×
NEW
157
            buildOrder.insert(buildOrder.end(), {BuildingType::Vineyard, BuildingType::Winery, BuildingType::Temple});
×
158

NEW
159
        if(ser.getDataVersion() < 2)
×
NEW
160
            buildOrder.insert(buildOrder.end(),
×
NEW
161
                              {BuildingType::Skinner, BuildingType::Tannery, BuildingType::LeatherWorks});
×
162

NEW
163
        std::generate(buildOrder.begin(), buildOrder.end() - countOfNotAvailableBuildingsInSaveGame,
×
NEW
164
                      [&]() { return helpers::popEnum<BuildingType>(ser); });
×
165

NEW
166
        std::copy(buildOrder.begin(), buildOrder.end(), data.begin());
×
167
    }
168
}
2✔
169

170
void ChangeBuildOrder::Execute(GameWorld& world, uint8_t playerId)
2✔
171
{
172
    world.GetPlayer(playerId).ChangeBuildOrder(useCustomBuildOrder, data);
2✔
173
}
2✔
174

175
void SetBuildingSite::Execute(GameWorld& world, uint8_t playerId)
24✔
176
{
177
    world.SetBuildingSite(bt, pt_, playerId);
24✔
178
}
24✔
179

180
void DestroyBuilding::Execute(GameWorld& world, uint8_t playerId)
3✔
181
{
182
    world.DestroyBuilding(pt_, playerId);
3✔
183
}
3✔
184

185
ChangeTransport::ChangeTransport(Deserializer& ser) : GameCommand(GCType::ChangeTransport)
2✔
186
{
187
    if(ser.getDataVersion() >= 2)
2✔
188
        helpers::popContainer(ser, data);
2✔
189
    else
190
    {
NEW
191
        const unsigned leatherAddonAdditionalTransportOrders = 1;
×
192
        std::vector<TransportOrders::value_type> tmpData(std::tuple_size<TransportOrders>::value
NEW
193
                                                         - leatherAddonAdditionalTransportOrders);
×
194

NEW
195
        helpers::popContainer(ser, tmpData, true);
×
NEW
196
        std::copy(tmpData.begin(), tmpData.end(), data.begin());
×
197
        // all transport prios greater equal transportPrioOfLeatherworks are increased by one because the new
198
        // leatherwork uses prio transportPrioOfLeatherworks
NEW
199
        std::transform(data.begin(), data.end() - leatherAddonAdditionalTransportOrders, data.begin(),
×
NEW
200
                       [](uint8_t& prio) { return prio < transportPrioOfLeatherworks ? prio : prio + 1; });
×
NEW
201
        data[std::tuple_size<TransportOrders>::value - leatherAddonAdditionalTransportOrders] =
×
NEW
202
          STD_TRANSPORT_PRIO[GoodType::Leather];
×
203
    }
204
}
2✔
205

206
void ChangeTransport::Execute(GameWorld& world, uint8_t playerId)
2✔
207
{
208
    world.GetPlayer(playerId).ConvertTransportData(data);
2✔
209
}
2✔
210

211
void ChangeMilitary::Execute(GameWorld& world, uint8_t playerId)
65✔
212
{
213
    world.GetPlayer(playerId).ChangeMilitarySettings(data);
65✔
214
}
65✔
215

216
ChangeTools::ChangeTools(const ToolSettings& data, const int8_t* order_delta)
6✔
217
    : GameCommand(GCType::ChangeTools), data(data), orders()
6✔
218
{
219
    if(order_delta != nullptr)
6✔
220
        std::copy_n(order_delta, orders.size(), orders.begin());
3✔
221
}
6✔
222

223
void ChangeTools::Execute(GameWorld& world, uint8_t playerId)
6✔
224
{
225
    world.GetPlayer(playerId).ChangeToolsSettings(data, orders);
6✔
226
}
6✔
227

228
void CallSpecialist::Execute(GameWorld& world, uint8_t playerId)
32✔
229
{
230
    world.GetPlayer(playerId).CallFlagWorker(pt_, job);
32✔
231
}
32✔
232

233
void Attack::Execute(GameWorld& world, uint8_t playerId)
29✔
234
{
235
    world.Attack(playerId, pt_, soldiers_count, strong_soldiers);
29✔
236
}
29✔
237

238
void SeaAttack::Execute(GameWorld& world, uint8_t playerId)
35✔
239
{
240
    world.AttackViaSea(playerId, pt_, soldiers_count, strong_soldiers);
35✔
241
}
35✔
242

243
void SetCoinsAllowed::Execute(GameWorld& world, uint8_t playerId)
5✔
244
{
245
    auto* const bld = world.GetSpecObj<nobMilitary>(pt_);
5✔
246
    if(bld && bld->GetPlayer() == playerId)
5✔
247
        bld->SetCoinsAllowed(enabled);
3✔
248
}
5✔
249

250
void SetArmorAllowed::Execute(GameWorld& world, uint8_t playerId)
5✔
251
{
252
    auto* const bld = world.GetSpecObj<nobMilitary>(pt_);
5✔
253
    if(bld && bld->GetPlayer() == playerId)
5✔
254
        bld->SetArmorAllowed(enabled);
3✔
255
}
5✔
256

257
void SetTroopLimit::Execute(GameWorld& world, uint8_t playerId)
13✔
258
{
259
    auto* const bld = world.GetSpecObj<nobMilitary>(pt_);
13✔
260
    if(bld && bld->GetPlayer() == playerId)
13✔
261
        bld->SetTroopLimit(rank, count);
13✔
262
}
13✔
263

264
void SetProductionEnabled::Execute(GameWorld& world, uint8_t playerId)
10✔
265
{
266
    auto* const bld = world.GetSpecObj<nobUsual>(pt_);
10✔
267
    if(bld && bld->GetPlayer() == playerId)
10✔
268
        bld->SetProductionEnabled(enabled);
6✔
269
}
10✔
270

271
void SetInventorySetting::Execute(GameWorld& world, uint8_t playerId)
122✔
272
{
273
    auto* const bld = world.GetSpecObj<nobBaseWarehouse>(pt_);
122✔
274
    if(bld && bld->GetPlayer() == playerId)
122✔
275
        bld->SetInventorySetting(what, state);
122✔
276
}
122✔
277

278
SetAllInventorySettings::SetAllInventorySettings(Deserializer& ser)
4✔
279
    : Coords(GCType::SetAllInventorySettings, ser), isJob(ser.PopBool())
4✔
280
{
281
    const uint32_t numStates = (isJob ? helpers::NumEnumValues_v<Job> : helpers::NumEnumValues_v<GoodType>);
4✔
282
    if(ser.getDataVersion() >= 2)
4✔
283
    {
284
        for(unsigned i = 0; i < numStates; i++)
160✔
285
            states.push_back(InventorySetting(ser.PopUnsignedChar()));
156✔
286
    } else
287
    {
NEW
288
        states.resize(numStates);
×
NEW
289
        if(isJob)
×
290
        {
NEW
291
            auto isJobSkipped = [&](Job const& job) {
×
NEW
292
                return (ser.getDataVersion() < 1 && wineaddon::isWineAddonJobType(job))
×
NEW
293
                       || leatheraddon::isLeatherAddonJobType(job);
×
NEW
294
            };
×
295

NEW
296
            size_t tgtIdx = 0;
×
NEW
297
            for(const auto i : helpers::enumRange<Job>())
×
298
            {
299
                // skip over not stored jobs
NEW
300
                if(!isJobSkipped(i))
×
NEW
301
                    states[tgtIdx] = InventorySetting(ser.PopUnsignedChar());
×
NEW
302
                tgtIdx++;
×
303
            }
304
        } else
305
        {
NEW
306
            auto isWareSkipped = [&](GoodType const& ware) {
×
NEW
307
                return (ser.getDataVersion() < 1 && wineaddon::isWineAddonGoodType(ware))
×
NEW
308
                       || leatheraddon::isLeatherAddonGoodType(ware);
×
NEW
309
            };
×
310

NEW
311
            size_t tgtIdx = 0;
×
NEW
312
            for(const auto i : helpers::enumRange<GoodType>())
×
313
            {
314
                // skip over not stored wares
NEW
315
                if(!isWareSkipped(i))
×
NEW
316
                    states[tgtIdx] = InventorySetting(ser.PopUnsignedChar());
×
NEW
317
                tgtIdx++;
×
318
            }
319
        }
320
    }
321
}
4✔
322

323
void SetAllInventorySettings::Execute(GameWorld& world, uint8_t playerId)
4✔
324
{
325
    auto* const bld = world.GetSpecObj<nobBaseWarehouse>(pt_);
4✔
326
    if(bld && bld->GetPlayer() == playerId)
4✔
327
        bld->SetAllInventorySettings(isJob, states);
4✔
328
}
4✔
329

330
void ChangeReserve::Execute(GameWorld& world, uint8_t playerId)
15✔
331
{
332
    auto* const bld = world.GetSpecObj<nobBaseWarehouse>(pt_);
15✔
333
    if(bld && bld->GetPlayer() == playerId)
15✔
334
        bld->SetRealReserve(rank, count);
15✔
335
}
15✔
336

337
void Surrender::Execute(GameWorld& world, uint8_t playerId)
1✔
338
{
339
    world.GetPlayer(playerId).Surrender();
1✔
340
}
1✔
341

342
void CheatArmageddon::Execute(GameWorld& world, unsigned char /*playerId*/)
1✔
343
{
344
    world.Armageddon();
1✔
345
}
1✔
346

347
void DestroyAll::Execute(GameWorld& world, uint8_t playerId)
1✔
348
{
349
    world.Armageddon(playerId);
1✔
350
}
1✔
351

352
void SuggestPact::Execute(GameWorld& world, uint8_t playerId)
8✔
353
{
354
    world.GetPlayer(playerId).SuggestPact(targetPlayer, pt, duration);
8✔
355
}
8✔
356

357
void AcceptPact::Execute(GameWorld& world, uint8_t playerId)
21✔
358
{
359
    world.GetPlayer(fromPlayer).AcceptPact(id, pt, playerId);
21✔
360
}
21✔
361

362
void CancelPact::Execute(GameWorld& world, uint8_t playerId)
6✔
363
{
364
    world.GetPlayer(playerId).CancelPact(pt, otherPlayer);
6✔
365
}
6✔
366

367
void NotifyAlliesOfLocation::Execute(GameWorld& world, uint8_t playerId)
6✔
368
{
369
    world.GetPlayer(playerId).NotifyAlliesOfLocation(pt_);
6✔
370
}
6✔
371

372
void SetShipYardMode::Execute(GameWorld& world, uint8_t playerId)
5✔
373
{
374
    auto* const bld = world.GetSpecObj<nobShipYard>(pt_);
5✔
375
    if(bld && bld->GetPlayer() == playerId)
5✔
376
        bld->SetMode(buildShips ? nobShipYard::Mode::Ships : nobShipYard::Mode::Boats);
5✔
377
}
5✔
378

379
void SetTempleProductionMode::Execute(GameWorld& world, uint8_t playerId)
×
380
{
381
    auto* const bld = world.GetSpecObj<nobTemple>(pt_);
×
382
    if(bld && bld->GetPlayer() == playerId)
×
383
        bld->SetProductionMode(productionMode);
×
384
}
×
385

386
void StartStopExpedition::Execute(GameWorld& world, uint8_t playerId)
6✔
387
{
388
    auto* const bld = world.GetSpecObj<nobHarborBuilding>(pt_);
6✔
389
    if(bld && bld->GetPlayer() == playerId)
6✔
390
    {
391
        if(start)
6✔
392
            bld->StartExpedition();
4✔
393
        else
394
            bld->StopExpedition();
2✔
395
    }
396
}
6✔
397

398
void StartStopExplorationExpedition::Execute(GameWorld& world, uint8_t playerId)
8✔
399
{
400
    auto* const bld = world.GetSpecObj<nobHarborBuilding>(pt_);
8✔
401
    if(bld && bld->GetPlayer() == playerId)
8✔
402
    {
403
        if(start)
8✔
404
            bld->StartExplorationExpedition();
6✔
405
        else
406
            bld->StopExplorationExpedition();
2✔
407
    }
408
}
8✔
409

410
void ExpeditionCommand::Execute(GameWorld& world, uint8_t playerId)
12✔
411
{
412
    noShip* ship = world.GetPlayer(playerId).GetShipByID(this->ship_id);
12✔
413
    if(!ship)
12✔
414
        return;
×
415

416
    switch(action)
12✔
417
    {
418
        case Action::FoundColony: ship->FoundColony(); break;
4✔
419
        case Action::CancelExpedition: ship->CancelExpedition(); break;
3✔
420
        case Action::North: ship->ContinueExpedition(ShipDirection::North); break;
2✔
421
        case Action::NorthEast: ship->ContinueExpedition(ShipDirection::NorthEast); break;
×
422
        case Action::SouthEast: ship->ContinueExpedition(ShipDirection::SouthEast); break;
1✔
423
        case Action::South: ship->ContinueExpedition(ShipDirection::South); break;
2✔
424
        case Action::SouthWest: ship->ContinueExpedition(ShipDirection::SouthWest); break;
×
425
        case Action::NorthWest: ship->ContinueExpedition(ShipDirection::NorthWest); break;
×
426
    }
427
}
428

429
/// Fuehrt das GameCommand aus
430
void TradeOverLand::Execute(GameWorld& world, uint8_t playerId)
23✔
431
{
432
    auto* const bld = world.GetSpecObj<nobBaseWarehouse>(pt_);
23✔
433
    if(bld)
23✔
434
        world.GetPlayer(playerId).Trade(bld, what, count);
23✔
435
}
23✔
436

437
} // namespace gc
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