Skip to content

Commit 0805fe3

Browse files
committed
Add single-soldier coin training addon
1 parent 37e4166 commit 0805fe3

6 files changed

Lines changed: 108 additions & 20 deletions

File tree

libs/s25main/GlobalGameSettings.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ void GlobalGameSettings::registerAllAddons()
9191
AddonMoreAnimals,
9292
AddonNoAlliedPush,
9393
AddonNoCoinsDefault,
94+
AddonSingleSoldierCoinTraining,
9495
AddonNumScoutsExploration,
9596
AddonPeacefulMode,
9697
AddonRefundMaterials,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2+
//
3+
// SPDX-License-Identifier: GPL-2.0-or-later
4+
5+
#pragma once
6+
7+
#include "AddonBool.h"
8+
#include "mygettext/mygettext.h"
9+
10+
class AddonSingleSoldierCoinTraining : public AddonBool
11+
{
12+
public:
13+
AddonSingleSoldierCoinTraining()
14+
: AddonBool(AddonId::SINGLE_SOLDIER_COIN_TRAINING, AddonGroup::Military,
15+
_("Coins train only one soldier"),
16+
_("Gold coins promote only one lowest-rank soldier instead of all eligible lower-rank soldiers."))
17+
{}
18+
};

libs/s25main/addons/Addons.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "addons/AddonDefenderBehavior.h"
2929

3030
#include "addons/AddonNoCoinsDefault.h"
31+
#include "addons/AddonSingleSoldierCoinTraining.h"
3132

3233
#include "addons/AddonAdjustMilitaryStrength.h"
3334

libs/s25main/addons/const_addons.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x
7575
AUTOFLAGS = 0x00F00000,
7676

7777
WINE = 0x01000000, LEATHER = 0x01000001, NO_ARMOR_DEFAULT = 0x01000002,
78-
ARMOR_CAPTURED_BLD = 0x01000003)
78+
ARMOR_CAPTURED_BLD = 0x01000003, SINGLE_SOLDIER_COIN_TRAINING = 0x01000005)
7979
//-V:AddonId:801
8080

8181
enum class AddonGroup : unsigned

libs/s25main/buildings/nobMilitary.cpp

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "gameData/MilitaryConsts.h"
3737
#include "gameData/SettingTypeConv.h"
3838
#include "s25util/Log.h"
39+
#include <algorithm>
3940
#include <limits>
4041
#include <stdexcept>
4142

@@ -315,28 +316,40 @@ void nobMilitary::HandleEvent(const unsigned id)
315316
{
316317
upgrade_event = nullptr;
317318

318-
// Soldaten befördern
319-
// Von hinten durchgehen
320-
// Wenn der nachfolgende (schwächere) Soldat einen niedrigeren Rang hat,
321-
// wird dieser ebenfalls befördert usw.!
322319
std::vector<std::unique_ptr<nofPassiveSoldier>> soldiersToUpgrade;
323-
// Rang des letzten beförderten Soldaten, MaxRank am Anfang setzen, damit keiner über den maximalen Rang
324-
// befördert wird
325-
uint8_t last_rank = world->GetGGS().GetMaxMilitaryRank();
326-
for(auto it = troops.rbegin(); it != troops.rend();)
320+
const uint8_t maxRank = world->GetGGS().GetMaxMilitaryRank();
321+
322+
if(world->GetGGS().isEnabled(AddonId::SINGLE_SOLDIER_COIN_TRAINING))
327323
{
328-
auto& soldier = *it;
329-
// Es wurde schon einer befördert, dieser Soldat muss nun einen niedrigeren Rang
330-
// als der letzte haben, damit er auch noch befördert werden kann
331-
if(soldier->GetRank() < last_rank)
324+
auto it = std::find_if(troops.begin(), troops.end(),
325+
[maxRank](const auto& soldier) { return soldier->GetRank() < maxRank; });
326+
if(it != troops.end())
332327
{
333-
// Rang merken
334-
last_rank = soldier->GetRank();
335-
// Remove from sorted container as changing it breaks sorting
336-
soldiersToUpgrade.push_back(std::move(soldier));
337-
it = helpers::erase_reverse(troops, it);
338-
} else
339-
++it;
328+
soldiersToUpgrade.push_back(std::move(*it));
329+
troops.erase(it);
330+
}
331+
} else
332+
{
333+
// Soldaten befördern
334+
// Von hinten durchgehen
335+
// Wenn der nachfolgende (schwächere) Soldat einen niedrigeren Rang hat,
336+
// wird dieser ebenfalls befördert usw.!
337+
uint8_t last_rank = maxRank;
338+
for(auto it = troops.rbegin(); it != troops.rend();)
339+
{
340+
auto& soldier = *it;
341+
// Es wurde schon einer befördert, dieser Soldat muss nun einen niedrigeren Rang
342+
// als der letzte haben, damit er auch noch befördert werden kann
343+
if(soldier->GetRank() < last_rank)
344+
{
345+
// Rang merken
346+
last_rank = soldier->GetRank();
347+
// Remove from sorted container as changing it breaks sorting
348+
soldiersToUpgrade.push_back(std::move(soldier));
349+
it = helpers::erase_reverse(troops, it);
350+
} else
351+
++it;
352+
}
340353
}
341354

342355
// Wurde jemand befördert?

tests/s25Main/integration/testAttacking.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <boost/test/unit_test.hpp>
3333
#include <array>
3434
#include <iostream>
35+
#include <memory>
3536
#include <numeric>
3637

3738
using SoldierState = nofActiveSoldier::SoldierState;
@@ -61,6 +62,24 @@ auto calcSum(const T& collection)
6162
return std::accumulate(std::begin(collection), std::end(collection), 0u);
6263
}
6364

65+
std::array<unsigned, NUM_SOLDIER_RANKS> CountTroopsByRank(const nobMilitary& bld)
66+
{
67+
std::array<unsigned, NUM_SOLDIER_RANKS> counts{};
68+
for(const auto& soldier : bld.GetTroops())
69+
++counts[soldier.GetRank()];
70+
return counts;
71+
}
72+
73+
void DeliverCoin(nobMilitary& bld, GameWorld& world)
74+
{
75+
auto coin = std::make_unique<Ware>(GoodType::Coins, &bld, &bld);
76+
world.GetPlayer(bld.GetPlayer()).RegisterWare(*coin);
77+
bld.TakeWare(coin.get());
78+
bld.AddWare(std::move(coin));
79+
80+
BOOST_TEST_REQUIRE(bld.GetNumCoins() == 1u);
81+
}
82+
6483
/// Reschedule the walk event of the obj to be executed in numGFs GFs
6584
void rescheduleWalkEvent(TestEventManager& em, noMovable& obj, unsigned numGFs)
6685
{
@@ -543,6 +562,42 @@ BOOST_FIXTURE_TEST_CASE(ArmoredSoldierLosesArmorInFight, AttackFixture<>)
543562
BOOST_TEST(milBld1->GetDefender()->GetHitpoints() == HITPOINTS[milBld1->GetDefender()->GetRank()]);
544563
}
545564

565+
BOOST_FIXTURE_TEST_CASE(CoinTrainingUpgradesRankChainByDefault, AttackFixture<>)
566+
{
567+
AddSoldiers(milBld0Pos, 1, Job::Private);
568+
AddSoldiers(milBld0Pos, 1, Job::PrivateFirstClass);
569+
AddSoldiers(milBld0Pos, 1, Job::Sergeant);
570+
571+
DeliverCoin(*milBld0, world);
572+
573+
RTTR_EXEC_TILL(5000, milBld0->GetNumCoins() == 0u);
574+
575+
const auto counts = CountTroopsByRank(*milBld0);
576+
BOOST_TEST_REQUIRE(counts[0] == 0u);
577+
BOOST_TEST_REQUIRE(counts[1] == 1u);
578+
BOOST_TEST_REQUIRE(counts[2] == 1u);
579+
BOOST_TEST_REQUIRE(counts[3] == 1u);
580+
}
581+
582+
BOOST_FIXTURE_TEST_CASE(SingleSoldierCoinTrainingUpgradesOnlyLowestRankSoldier, AttackFixture<>)
583+
{
584+
this->ggs.setSelection(AddonId::SINGLE_SOLDIER_COIN_TRAINING, 1);
585+
586+
AddSoldiers(milBld0Pos, 1, Job::Private);
587+
AddSoldiers(milBld0Pos, 1, Job::PrivateFirstClass);
588+
AddSoldiers(milBld0Pos, 1, Job::Sergeant);
589+
590+
DeliverCoin(*milBld0, world);
591+
592+
RTTR_EXEC_TILL(5000, milBld0->GetNumCoins() == 0u);
593+
594+
const auto counts = CountTroopsByRank(*milBld0);
595+
BOOST_TEST_REQUIRE(counts[0] == 0u);
596+
BOOST_TEST_REQUIRE(counts[1] == 2u);
597+
BOOST_TEST_REQUIRE(counts[2] == 1u);
598+
BOOST_TEST_REQUIRE(counts[3] == 0u);
599+
}
600+
546601
BOOST_FIXTURE_TEST_CASE(ConquerBldCoinAddonEnable, AttackFixture<>)
547602
{
548603
this->ggs.setSelection(AddonId::COINS_CAPTURED_BLD, 1); // addon is active on second run

0 commit comments

Comments
 (0)