Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
25 changes: 25 additions & 0 deletions libs/s25main/BuildingRegister.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ helpers::EnumArray<uint16_t, BuildingType> BuildingRegister::CalcProductivities(
return productivities;
}

helpers::EnumArray<uint16_t, BuildingType> BuildingRegister::CalcDisplayProductivities() const
{
helpers::EnumArray<uint16_t, BuildingType> productivities;

for(const auto bld : helpers::enumRange<BuildingType>())
productivities[bld] = static_cast<uint16_t>(CalcAverageDisplayProductivity(bld));
return productivities;
}

unsigned BuildingRegister::CalcAverageProductivity(BuildingType bldType) const
{
if(holds_alternative<boost::none_t>(BLD_WORK_DESC[bldType].producedWare))
Expand All @@ -171,6 +180,22 @@ unsigned BuildingRegister::CalcAverageProductivity(BuildingType bldType) const
return productivity;
}

unsigned BuildingRegister::CalcAverageDisplayProductivity(BuildingType bldType) const
{
if(holds_alternative<boost::none_t>(BLD_WORK_DESC[bldType].producedWare))
return 0;
unsigned productivity = 0;
const auto& buildings = GetBuildings(bldType);
const unsigned numBlds = buildings.size();
if(numBlds > 0)
{
for(const nobUsual* bld : buildings)
productivity += bld->GetDisplayProductivity();
productivity /= numBlds;
}
return productivity;
}

unsigned short BuildingRegister::CalcAverageProductivity() const
{
unsigned totalProductivity = 0;
Expand Down
4 changes: 4 additions & 0 deletions libs/s25main/BuildingRegister.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ class BuildingRegister
BuildingCount GetBuildingNums() const;
/// Calculate and fill the average productivities for all buildings.
helpers::EnumArray<uint16_t, BuildingType> CalcProductivities() const;
/// Calculate and fill the average productivities shown in UI.
helpers::EnumArray<uint16_t, BuildingType> CalcDisplayProductivities() const;
/// Calculate the average productivity for a building type
unsigned CalcAverageProductivity(BuildingType bldType) const;
/// Calculate the average productivity shown in UI for a building type
unsigned CalcAverageDisplayProductivity(BuildingType bldType) const;
/// Calculate the average productivity for all buildings
unsigned short CalcAverageProductivity() const;

Expand Down
6 changes: 6 additions & 0 deletions libs/s25main/GlobalGameSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ void GlobalGameSettings::registerAllAddons()
AddonHalfCostMilEquip,
AddonInexhaustibleFish,
AddonInexhaustibleGraniteMines,
AddonGraniteMinesWorkEverywhere,
AddonCoalMineResourceBehavior,
AddonIronMineResourceBehavior,
AddonGoldMineResourceBehavior,
AddonGraniteMineResourceBehavior,
AddonMineNoOutputFallback,
AddonInexhaustibleMines,
AddonLimitCatapults,
AddonManualRoadEnlargement,
Expand Down
21 changes: 21 additions & 0 deletions libs/s25main/addons/AddonGraniteMinesWorkEverywhere.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "AddonBool.h"
#include "const_addons.h"
#include "mygettext/mygettext.h"

/**
* Addon for creating finite granite resources below granite mines without explicit stone resources.
*/
class AddonGraniteMinesWorkEverywhere : public AddonBool
{
public:
AddonGraniteMinesWorkEverywhere()
: AddonBool(AddonId::GRANITEMINES_WORK_EVERYWHERE, AddonGroup::Economy, _("Granite Mines Work Everywhere"),
_("Granite mines can create a finite stone resource on otherwise empty mountain spots."))
{}
};
4 changes: 2 additions & 2 deletions libs/s25main/addons/AddonInexhaustibleGraniteMines.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
#include "mygettext/mygettext.h"

/**
* Addon for allowing to have unlimited resources.
* Addon for allowing granite mines to have unlimited resources.
*/
class AddonInexhaustibleGraniteMines : public AddonBool
{
public:
AddonInexhaustibleGraniteMines()
: AddonBool(AddonId::INEXHAUSTIBLE_GRANITEMINES, AddonGroup::Economy, _("Inexhaustible Granite Mines"),
_("Granite mines will never be depleted."))
_("Granite mines will never deplete stone resources."))
{}
};
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))
{}
};
57 changes: 57 additions & 0 deletions libs/s25main/addons/AddonMineResourceBehavior.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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"), _("S4-like exhaustion"), _("Inexhaustible"), _("Work everywhere")},
static_cast<unsigned>(MineResourceBehavior::Default))
{}
};

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."))
{}
};

class AddonGraniteMineResourceBehavior : public AddonMineResourceBehaviorBase
{
public:
AddonGraniteMineResourceBehavior()
: AddonMineResourceBehaviorBase(AddonId::GRANITEMINE_RESOURCE_BEHAVIOR, _("Granite Mine Resource Behavior"),
_("Configures how granite mines consume and exhaust stone deposits."))
{}
};
3 changes: 3 additions & 0 deletions libs/s25main/addons/Addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@

#include "addons/AddonToolOrdering.h"

#include "addons/AddonGraniteMinesWorkEverywhere.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
5 changes: 4 additions & 1 deletion libs/s25main/addons/const_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x

MILITARY_AID = 0x00700000,

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

MAX_RANK = 0x00900000, SEA_ATTACK = 0x00900001, INEXHAUSTIBLE_FISH = 0x00900002,
MORE_ANIMALS = 0x00900003, BURN_DURATION = 0x00900004, NO_ALLIED_PUSH = 0x00900005,
Expand Down
45 changes: 45 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,26 @@ 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;
}

helpers::OptionalEnum<BuildingType> GetMineBuildingType(const AIResource res)
{
switch(res)
{
case AIResource::Gold: return BuildingType::GoldMine;
case AIResource::Ironore: return BuildingType::IronMine;
case AIResource::Coal: return BuildingType::CoalMine;
case AIResource::Granite: return BuildingType::GraniteMine;
default: return boost::none;
}
}

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 +179,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(res);
if(mineBuildingType
&& GetEffectiveMineResourceBehavior(gwb.GetGGS(), *mineBuildingType)
== MineResourceBehavior::S4LikeExhaustion)
return GetS4LikeMineResourceRating(subres, RES_RADIUS[res]);

return RES_RADIUS[res];
}
if(IsMineResourceWorkEverywhere(res) && subres.getType() == ResourceType::Nothing
&& 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 +204,13 @@ int AIInterface::GetResourceRating(const MapPoint pt, AIResource res) const
return 0;
}

bool AIInterface::IsMineResourceWorkEverywhere(const AIResource res) const
{
const auto mineBuildingType = GetMineBuildingType(res);
return mineBuildingType
&& GetEffectiveMineResourceBehavior(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
10 changes: 5 additions & 5 deletions libs/s25main/ai/aijh/AIPlayerJH.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "nodeObjs/noFlag.h"
#include "nodeObjs/noShip.h"
#include "nodeObjs/noTree.h"
#include "gameTypes/MineResourceBehavior.h"
#include "gameData/BuildingConsts.h"
#include "gameData/BuildingProperties.h"
#include "gameData/GameConsts.h"
Expand Down Expand Up @@ -139,11 +140,10 @@ static bool isUnlimitedResource(const AIResource res, const GlobalGameSettings&
{
switch(res)
{
case AIResource::Gold:
case AIResource::Ironore:
case AIResource::Coal: return ggs.isEnabled(AddonId::INEXHAUSTIBLE_MINES);
case AIResource::Granite:
return ggs.isEnabled(AddonId::INEXHAUSTIBLE_MINES) || ggs.isEnabled(AddonId::INEXHAUSTIBLE_GRANITEMINES);
case AIResource::Gold: return !IsMineResourceDepletable(ggs, BuildingType::GoldMine);
case AIResource::Ironore: return !IsMineResourceDepletable(ggs, BuildingType::IronMine);
case AIResource::Coal: return !IsMineResourceDepletable(ggs, BuildingType::CoalMine);
case AIResource::Granite: return !IsMineResourceDepletable(ggs, BuildingType::GraniteMine);
case AIResource::Fish: return ggs.isEnabled(AddonId::INEXHAUSTIBLE_FISH);
default: return false;
}
Expand Down
22 changes: 22 additions & 0 deletions libs/s25main/buildings/nobUsual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
#include "ogl/glArchivItem_Bitmap_Player.h"
#include "postSystem/PostMsgWithBuilding.h"
#include "world/GameWorld.h"
#include "gameTypes/MineResourceBehavior.h"
#include "gameTypes/Resource.h"
#include "gameData/BuildingConsts.h"
#include "gameData/BuildingProperties.h"
#include "gameData/GameConsts.h"
#include <numeric>

/// Number of GFs after which the productivity is recalculated, i.e. productivity is averaged over intervals of this
Expand Down Expand Up @@ -519,6 +522,25 @@ bool nobUsual::HasWorker() const
return worker && worker->GetState() != nofBuildingWorker::State::FigureWork;
}

unsigned short nobUsual::GetDisplayProductivity() const
{
Comment thread
Flamefire marked this conversation as resolved.
if(!BuildingProperties::IsMine(bldType_)
|| GetEffectiveMineResourceBehavior(world->GetGGS(), bldType_) != MineResourceBehavior::S4LikeExhaustion)
return productivity;

const ResourceType resourceType = GetMineResourceType(bldType_);
const std::vector<MapPoint> resourcePts = world->GetMatchingPointsInRadius<1>(
pos, MINER_RADIUS,
[this, resourceType](const MapPoint pt) { return world->GetNode(pt).resources.has(resourceType); }, true);

unsigned resourceAmount = 0;
for(const MapPoint pt : resourcePts)
resourceAmount += world->GetNode(pt).resources.getAmount();

return static_cast<unsigned short>(
(static_cast<unsigned>(productivity) * GetS4LikeMineProductionChance(resourceAmount)) / 100u);
}

void nobUsual::OnOutOfResources()
{
// Post verschicken, keine Rohstoffe mehr da
Expand Down
1 change: 1 addition & 0 deletions libs/s25main/buildings/nobUsual.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class nobUsual : public noBuilding
/// Gibt Pointer auf Produktivität zurück
const unsigned short* GetProductivityPointer() const { return &productivity; }
unsigned short GetProductivity() const { return productivity; }
unsigned short GetDisplayProductivity() const;
Comment thread
Flamefire marked this conversation as resolved.
Outdated
const nofBuildingWorker* GetWorker() const { return worker; }

/// Stoppt/Erlaubt Produktion (visuell)
Expand Down
Loading