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

Return-To-The-Roots / s25client / 22149850233

18 Feb 2026 05:13PM UTC coverage: 50.318% (-0.5%) from 50.787%
22149850233

Pull #1720

github

web-flow
Merge d8835065e into 7a79bd216
Pull Request #1720: Add leather addon

282 of 1064 new or added lines in 65 files covered. (26.5%)

46 existing lines in 27 files now uncovered.

23027 of 45763 relevant lines covered (50.32%)

43151.19 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)
×
160
        {
NEW
161
            buildOrder.insert(buildOrder.end(),
×
NEW
162
                              {BuildingType::Skinner, BuildingType::Tannery, BuildingType::LeatherWorks});
×
163
        }
164

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

439
} // 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