Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ea2ee5f
Fix inexhaustible granite mine production
DevOpsOfChaos May 1, 2026
b887aaf
Simplify granite mine resource handling
DevOpsOfChaos May 1, 2026
4e4b9d1
Tighten granite mine production tests
DevOpsOfChaos May 1, 2026
b636b5d
Clarify inexhaustible granite mines addon description
DevOpsOfChaos May 1, 2026
2a4936e
Avoid runtime addon toggle in granite mine test
DevOpsOfChaos May 1, 2026
fb40fc9
Split granite mine work-everywhere addon
DevOpsOfChaos May 1, 2026
44bbab0
Implement configurable mine resource behavior
DevOpsOfChaos May 14, 2026
64a8b71
Implement mine no-output fallback behavior
DevOpsOfChaos May 15, 2026
621d52e
Teach AI mine planning configurable resource behavior
DevOpsOfChaos May 15, 2026
d1df485
Adjust mine productivity display for S4-like behavior
DevOpsOfChaos May 15, 2026
1733b37
Merge branch 'master' into sidequest/configurable-mine-resource-behavior
DevOpsOfChaos May 21, 2026
b9be39f
Merge branch 'master' into sidequest/configurable-mine-resource-behavior
DevOpsOfChaos May 26, 2026
bf24106
Rework configurable mine resource behavior
DevOpsOfChaos May 26, 2026
86587eb
Clarify S4-like mine productivity reference
DevOpsOfChaos May 26, 2026
3c27b7e
Merge remote-tracking branch 'upstream/master' into sidequest/configu…
DevOpsOfChaos Jun 23, 2026
9535c6c
Address mine resource behavior review feedback
DevOpsOfChaos Jun 23, 2026
1bade00
Fix mine resource behavior review regression
DevOpsOfChaos Jun 23, 2026
ea0a195
Merge remote-tracking branch 'upstream/master' into sidequest/configu…
DevOpsOfChaos Jun 23, 2026
e17142e
Harden mine resource behavior coverage
DevOpsOfChaos Jun 23, 2026
2cb7660
Polish mine resource behavior compatibility
DevOpsOfChaos Jun 25, 2026
87ca9e6
Address remaining mine resource review comments
DevOpsOfChaos Jul 20, 2026
6e54029
Fix mine resource behavior regressions found during verification
DevOpsOfChaos Jul 20, 2026
773a9c6
Use empty OptionalEnum instead of boost::none
DevOpsOfChaos Jul 20, 2026
9305cea
Keep the legacy mine addon names usable from Lua
DevOpsOfChaos Jul 20, 2026
19acd11
Address Flamefire review: self-documenting tests, GetFeatureLevel bum…
DevOpsOfChaos Jul 30, 2026
86d6f7a
fix: split combined AI test, increase S4-like GF window to 5000
DevOpsOfChaos Jul 30, 2026
81c1ac1
chore: split BOOST_TEST_REQUIRE line, remove dead setSelection in AI …
DevOpsOfChaos Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/lua/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ rttr:SetAddon(ADDON_FRONTIER_DISTANCE_REACHABLE, true)

For addons which can be enabled / disabled simply use true or false, for addons with different settings, add the number according to your setting.

Deprecated addon names, kept working for older scripts:

- `ADDON_INEXHAUSTIBLE_GRANITEMINES` is an alias for `ADDON_GRANITEMINE_RESOURCE_BEHAVIOR`. The old `true` still selects the inexhaustible behavior.
- `ADDON_INEXHAUSTIBLE_MINES` is no longer an addon of its own. Setting it to `true` makes every mine type that is still at its default behavior inexhaustible, setting it to `false` does nothing. Use the per-mine `ADDON_*MINE_RESOURCE_BEHAVIOR` addons instead.

**rttr:ResetAddons()**
Set all addons to S2 defaults

Expand Down
7 changes: 7 additions & 0 deletions libs/common/include/helpers/MaxEnumValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ inline constexpr unsigned MaxEnumValue_v = static_cast<std::underlying_type_t<T_
template<class T_Enum>
inline constexpr unsigned NumEnumValues_v = MaxEnumValue_v<T_Enum> + 1u;

/// Check whether a numeric value is a valid enumerator for an enum type
template<class T_Enum>
constexpr bool isValidEnumValue(unsigned value)
{
return value <= MaxEnumValue_v<T_Enum>;
}

} // namespace helpers
52 changes: 49 additions & 3 deletions libs/s25main/GlobalGameSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@
#include "addons/Addons.h"
#include "helpers/containerUtils.h"
#include "helpers/serializeEnums.h"
#include "gameTypes/BuildingType.h"
#include "gameTypes/MineResourceBehavior.h"
#include "gameData/MilitaryConsts.h"
#include "s25util/Log.h"
#include "s25util/Serializer.h"
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <algorithm>
#include <array>
#include <iostream>
#include <stdexcept>

namespace {
constexpr std::array<BuildingType, 4> MINE_BUILDING_TYPES = {BuildingType::GraniteMine, BuildingType::CoalMine,
BuildingType::IronMine, BuildingType::GoldMine};
} // namespace

GlobalGameSettings::GlobalGameSettings()
: speed(GameSpeed::Normal), objective(GameObjective::None), startWares(StartWares::Normal), lockedTeams(false),
exploration(Exploration::FogOfWar), teamView(true), randomStartPosition(false)
Expand Down Expand Up @@ -78,8 +86,11 @@ void GlobalGameSettings::registerAllAddons()
AddonFrontierDistanceReachable,
AddonHalfCostMilEquip,
AddonInexhaustibleFish,
AddonInexhaustibleGraniteMines,
AddonInexhaustibleMines,
AddonGraniteMineResourceBehavior,
AddonCoalMineResourceBehavior,
AddonIronMineResourceBehavior,
AddonGoldMineResourceBehavior,
AddonMineNoOutputFallback,
AddonLimitCatapults,
AddonManualRoadEnlargement,
AddonMaxRank,
Expand Down Expand Up @@ -187,8 +198,21 @@ void GlobalGameSettings::LoadSettings()
{
resetAddons();

bool migrateLegacyInexhaustibleMines = false;
for(const auto& it : SETTINGS.addons.configuration)
setSelection(static_cast<AddonId>(it.first), it.second);
{
const auto id = static_cast<AddonId>(it.first);
const unsigned status = it.second;
if(id == AddonId::INEXHAUSTIBLE_MINES)
{
migrateLegacyInexhaustibleMines = status != 0;
continue;
}

setSelection(id, status);
}
if(migrateLegacyInexhaustibleMines)
applyLegacyInexhaustibleMines();
}

/**
Expand Down Expand Up @@ -243,12 +267,34 @@ void GlobalGameSettings::Deserialize(Serializer& ser)

resetAddons();

bool migrateLegacyInexhaustibleMines = false;
for(unsigned i = 0; i < count; ++i)
{
auto addon = static_cast<AddonId>(ser.PopUnsignedInt());
unsigned status = ser.PopUnsignedInt();
if(addon == AddonId::INEXHAUSTIBLE_MINES)
{
migrateLegacyInexhaustibleMines = status != 0;
continue;
}

setSelection(addon, status);
}
if(migrateLegacyInexhaustibleMines)
applyLegacyInexhaustibleMines();
}

/// Only mine types with an explicitly configured non-default behavior keep their setting. This is required because
/// GRANITEMINE_RESOURCE_BEHAVIOR reuses the id of the old INEXHAUSTIBLE_GRANITEMINES bool addon, so old data always
/// contains a value for it (usually 0), while the old global setting made granite mines inexhaustible as well.
void GlobalGameSettings::applyLegacyInexhaustibleMines()
{
for(const BuildingType mineType : MINE_BUILDING_TYPES)
{
if(GetMineResourceBehavior(*this, mineType) == MineResourceBehavior::Default)
setSelection(GetMineResourceBehaviorAddonId(mineType),
static_cast<unsigned>(MineResourceBehavior::Inexhaustible));
}
}

void GlobalGameSettings::setSelection(AddonId id, unsigned selection)
Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/GlobalGameSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class GlobalGameSettings
bool isEnabled(AddonId id) const;
unsigned getSelection(AddonId id) const;
void setSelection(AddonId id, unsigned selection);
/// Apply the removed global INEXHAUSTIBLE_MINES addon: make every mine type that is still at its default behavior
/// inexhaustible. Used to migrate old settings, savegames and Lua scripts.
void applyLegacyInexhaustibleMines();

/// loads the saved addon configuration from the SETTINGS.
void LoadSettings();
Expand Down
20 changes: 0 additions & 20 deletions libs/s25main/addons/AddonInexhaustibleGraniteMines.h

This file was deleted.

8 changes: 7 additions & 1 deletion libs/s25main/addons/AddonInexhaustibleMines.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
#include "mygettext/mygettext.h"

/**
* Addon for allowing to have unlimited resources.
* Deprecated global mine setting.
Comment thread
Flamefire marked this conversation as resolved.
*
* Not registered anymore. The ID is still decoded when loading old settings/savegames and migrated to the per-mine
* resource behavior settings.
*
* TODO(Replay) TODO(Savegame): Remove this compatibility class when legacy global mine settings no longer need
* migration.
*/
class AddonInexhaustibleMines : public AddonBool
{
Expand Down
22 changes: 22 additions & 0 deletions libs/s25main/addons/AddonMineNoOutputFallback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "AddonList.h"
#include "const_addons.h"
#include "mygettext/mygettext.h"
#include "gameTypes/MineNoOutputFallback.h"

class AddonMineNoOutputFallback : public AddonList
{
public:
AddonMineNoOutputFallback()
: AddonList(AddonId::MINE_NO_OUTPUT_FALLBACK, AddonGroup::Economy, _("Mine No-Output Fallback"),
_("Configures what mines produce when S4-like exhaustion would produce nothing."),
{_("Produce nothing"), _("Produce granite 25%"), _("Produce granite 50%"),
_("Produce granite 100%"), _("Produce lower grade resource")},
static_cast<unsigned>(MineNoOutputFallback::ProduceNothing))
{}
};
63 changes: 63 additions & 0 deletions libs/s25main/addons/AddonMineResourceBehavior.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "AddonList.h"
#include "const_addons.h"
#include "mygettext/mygettext.h"
#include "gameTypes/MineResourceBehavior.h"
#include <string>

class AddonMineResourceBehaviorBase : public AddonList
{
protected:
AddonMineResourceBehaviorBase(AddonId id, const std::string& name, const std::string& description)
: AddonList(id, AddonGroup::Economy, name, description,
{_("Default"), _("Inexhaustible"), _("S4-like exhaustion"), _("Work everywhere")},
static_cast<unsigned>(MineResourceBehavior::Default))
{}
};

/**
* Granite mine resource behavior list.
*
* Reuses the legacy granite-mine addon value 0x00800000: saved value 0 remains the default behavior and saved value 1
* selects the inexhaustible behavior.
*/
class AddonGraniteMineResourceBehavior : public AddonMineResourceBehaviorBase
{
public:
AddonGraniteMineResourceBehavior()
: AddonMineResourceBehaviorBase(AddonId::GRANITEMINE_RESOURCE_BEHAVIOR, _("Granite Mine Resource Behavior"),
_("Configures how granite mines consume and exhaust stone deposits."))
{}
};

class AddonCoalMineResourceBehavior : public AddonMineResourceBehaviorBase
{
public:
AddonCoalMineResourceBehavior()
: AddonMineResourceBehaviorBase(AddonId::COALMINE_RESOURCE_BEHAVIOR, _("Coal Mine Resource Behavior"),
_("Configures how coal mines consume and exhaust coal deposits."))
{}
};

class AddonIronMineResourceBehavior : public AddonMineResourceBehaviorBase
{
public:
AddonIronMineResourceBehavior()
: AddonMineResourceBehaviorBase(AddonId::IRONMINE_RESOURCE_BEHAVIOR, _("Iron Mine Resource Behavior"),
_("Configures how iron mines consume and exhaust iron deposits."))
{}
};

class AddonGoldMineResourceBehavior : public AddonMineResourceBehaviorBase
{
public:
AddonGoldMineResourceBehavior()
: AddonMineResourceBehaviorBase(AddonId::GOLDMINE_RESOURCE_BEHAVIOR, _("Gold Mine Resource Behavior"),
_("Configures how gold mines consume and exhaust gold deposits."))
{}
};
3 changes: 2 additions & 1 deletion libs/s25main/addons/Addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
#include "addons/AddonToolOrdering.h"

#include "addons/AddonInexhaustibleFish.h"
#include "addons/AddonInexhaustibleGraniteMines.h"
#include "addons/AddonMaxRank.h"
#include "addons/AddonMilitaryAid.h"
#include "addons/AddonMineNoOutputFallback.h"
#include "addons/AddonMineResourceBehavior.h"
#include "addons/AddonSeaAttack.h"

#include "addons/AddonBattlefieldPromotion.h"
Expand Down
6 changes: 5 additions & 1 deletion libs/s25main/addons/const_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
//
// Add the #include for your AddonXXX.h in Addons.h!
//
// TODO(Replay) TODO(Savegame): Remove INEXHAUSTIBLE_MINES once the gamedata version can be raised and legacy
// settings/savegames no longer need migration.
ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x00000001, REFUND_MATERIALS = 0x00000002,
EXHAUSTIBLE_WATER = 0x00000003, REFUND_ON_EMERGENCY = 0x00000004, MANUAL_ROAD_ENLARGEMENT = 0x00000005,
CATAPULT_GRAPHICS = 0x00000006, METALWORKSBEHAVIORONZERO = 0x00000007,
Expand All @@ -57,7 +59,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x

MILITARY_AID = 0x00700000,

INEXHAUSTIBLE_GRANITEMINES = 0x00800000,
GRANITEMINE_RESOURCE_BEHAVIOR = 0x00800000, COALMINE_RESOURCE_BEHAVIOR = 0x00800001,
IRONMINE_RESOURCE_BEHAVIOR = 0x00800002, GOLDMINE_RESOURCE_BEHAVIOR = 0x00800003,
MINE_NO_OUTPUT_FALLBACK = 0x00800004,

MAX_RANK = 0x00900000, SEA_ATTACK = 0x00900001, INEXHAUSTIBLE_FISH = 0x00900002,
MORE_ANIMALS = 0x00900003, BURN_DURATION = 0x00900004, NO_ALLIED_PUSH = 0x00900005,
Expand Down
37 changes: 37 additions & 0 deletions libs/s25main/ai/AIInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "pathfinding/RoadPathFinder.h"
#include "nodeObjs/noFlag.h"
#include "nodeObjs/noTree.h"
#include "gameTypes/MineResourceBehavior.h"
#include "gameData/TerrainDesc.h"
#include <limits>
#include <numeric>
Expand Down Expand Up @@ -45,6 +46,14 @@ bool IsPointOK_RoadPathEvenStep(const GameWorldBase& gwb, const MapPoint pt, con
const auto* prp = static_cast<const Param_RoadPath*>(param);
return prp->boat_road || gwb.GetBQ(pt, gwb.GetNode(pt).owner - 1) != BuildingQuality::Nothing;
}

int GetS4LikeMineResourceRating(const Resource resource, const unsigned defaultRating)
{
if(resource.getAmount() == 0u)
return 0;

return std::max(1u, std::min(static_cast<unsigned>(resource.getAmount()), defaultRating));
}
} // namespace

AIInterface::AIInterface(const GameWorldBase& gwb, std::vector<gc::GameCommandPtr>& gcs, unsigned char playerID)
Expand Down Expand Up @@ -158,6 +167,23 @@ int AIInterface::GetResourceRating(const MapPoint pt, AIResource res) const
case AIResource::Ironore:
case AIResource::Coal:
case AIResource::Granite:
{
const Resource subres = gwb.GetNode(pt).resources;
if(convertToNodeResource(GetSubsurfaceResource(pt)) == res)
{
const auto mineBuildingType = GetMineBuildingType(subres.getType());
if(mineBuildingType
&& GetMineResourceBehavior(gwb.GetGGS(), *mineBuildingType)
== MineResourceBehavior::S4LikeExhaustion)
return GetS4LikeMineResourceRating(subres, RES_RADIUS[res]);

return RES_RADIUS[res];
}
if(IsMineResourceWorkEverywhere(res)
&& gwb.IsOfTerrain(pt, [](const TerrainDesc& desc) { return desc.Is(ETerrain::Mineable); }))
return RES_RADIUS[res];
break;
}
case AIResource::Fish:
if(convertToNodeResource(GetSubsurfaceResource(pt)) == res)
return RES_RADIUS[res];
Expand All @@ -166,6 +192,17 @@ int AIInterface::GetResourceRating(const MapPoint pt, AIResource res) const
return 0;
}

bool AIInterface::IsMineResourceWorkEverywhere(const AIResource res) const
{
const auto resourceType = convertToResourceType(res);
if(!resourceType)
return false;

const auto mineBuildingType = GetMineBuildingType(*resourceType);
return mineBuildingType
&& GetMineResourceBehavior(gwb.GetGGS(), *mineBuildingType) == MineResourceBehavior::WorkEverywhere;
}

int AIInterface::CalcResourceValue(const MapPoint pt, AIResource res, helpers::OptionalEnum<Direction> direction,
int lastval) const
{
Expand Down
2 changes: 2 additions & 0 deletions libs/s25main/ai/AIInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class AIInterface : public GameCommandFactory
int lastval = 0xffff) const;
/// Calculate the resource value for a given point
int GetResourceRating(MapPoint pt, AIResource res) const;
/// Check whether the given mine resource can be produced on otherwise empty mineable mountain.
bool IsMineResourceWorkEverywhere(AIResource res) const;
/// Test whether a given point is part of the border or not
bool IsBorder(const MapPoint pt) const
{
Expand Down
15 changes: 15 additions & 0 deletions libs/s25main/ai/AIResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#pragma once

#include "helpers/EnumArray.h"
#include "helpers/OptionalEnum.h"
#include "gameTypes/Resource.h"
#include "s25util/warningSuppression.h"

// Note: This enums are constructed for performance and easy conversion.
Expand Down Expand Up @@ -86,6 +88,19 @@ constexpr bool operator==(AINodeResource lhs, AIResource rhs)
return lhs == convertToNodeResource(rhs);
}

inline helpers::OptionalEnum<ResourceType> convertToResourceType(AIResource res)
{
switch(res)
{
case AIResource::Gold: return ResourceType::Gold;
case AIResource::Ironore: return ResourceType::Iron;
case AIResource::Coal: return ResourceType::Coal;
case AIResource::Granite: return ResourceType::Granite;
case AIResource::Fish: return ResourceType::Fish;
default: return {};
}
}

constexpr helpers::EnumArray<unsigned, AIResource> SUPPRESS_UNUSED RES_RADIUS = {
2, // Gold
2, // Ironore
Expand Down
Loading
Loading