Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions libs/s25main/GlobalGameSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ void GlobalGameSettings::registerAllAddons()
AddonHalfCostMilEquip,
AddonInexhaustibleFish,
AddonInexhaustibleGraniteMines,
AddonGraniteMinesWorkEverywhere,
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."))
{}
};
1 change: 1 addition & 0 deletions libs/s25main/addons/Addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

#include "addons/AddonInexhaustibleFish.h"
#include "addons/AddonInexhaustibleGraniteMines.h"
#include "addons/AddonGraniteMinesWorkEverywhere.h"
#include "addons/AddonMaxRank.h"
#include "addons/AddonMilitaryAid.h"
#include "addons/AddonSeaAttack.h"
Expand Down
2 changes: 1 addition & 1 deletion libs/s25main/addons/const_addons.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x

MILITARY_AID = 0x00700000,

INEXHAUSTIBLE_GRANITEMINES = 0x00800000,
INEXHAUSTIBLE_GRANITEMINES = 0x00800000, GRANITEMINES_WORK_EVERYWHERE = 0x00800001,

MAX_RANK = 0x00900000, SEA_ATTACK = 0x00900001, INEXHAUSTIBLE_FISH = 0x00900002,
MORE_ANIMALS = 0x00900003, BURN_DURATION = 0x00900004, NO_ALLIED_PUSH = 0x00900005,
Expand Down
57 changes: 49 additions & 8 deletions libs/s25main/figures/nofMiner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#include "network/GameClient.h"
#include "ogl/glArchivItem_Bitmap_Player.h"
#include "world/GameWorld.h"
#include "gameTypes/Resource.h"
#include "gameData/GameConsts.h"
#include "random/Random.h"

nofMiner::nofMiner(const MapPoint pos, const unsigned char player, nobUsual* workplace)
: nofWorkman(Job::Miner, pos, player, workplace)
Expand Down Expand Up @@ -69,25 +72,63 @@ helpers::OptionalEnum<GoodType> nofMiner::ProduceWare()
}
}

MapPoint nofMiner::FindPointWithResourceQuiet(ResourceType type) const
{
const auto pts = world->GetMatchingPointsInRadius<1>(
pos, MINER_RADIUS, [this, type](const MapPoint pt) { return world->GetNode(pt).resources.has(type); }, true);
return pts.empty() ? MapPoint::Invalid() : pts.front();
}

bool nofMiner::CanCreateWorkEverywhereGraniteResource() const
{
return workplace->GetBuildingType() == BuildingType::GraniteMine
&& world->GetGGS().isEnabled(AddonId::GRANITEMINES_WORK_EVERYWHERE)
&& world->GetNode(pos).resources.getType() == ResourceType::Nothing;
}

MapPoint nofMiner::CreateWorkEverywhereGraniteResource()
{
if(!CanCreateWorkEverywhereGraniteResource())
return MapPoint::Invalid();

world->SetResource(pos, Resource(ResourceType::Granite, static_cast<uint8_t>(8 + RANDOM_RAND(8))));
return pos;
}

bool nofMiner::AreWaresAvailable() const
{
return nofWorkman::AreWaresAvailable() && FindPointWithResource(GetRequiredResType()).isValid();
if(!nofWorkman::AreWaresAvailable())
return false;

if(FindPointWithResourceQuiet(GetRequiredResType()).isValid() || CanCreateWorkEverywhereGraniteResource())
return true;

workplace->OnOutOfResources();
return false;
}

bool nofMiner::StartWorking()
{
MapPoint resPt = FindPointWithResource(GetRequiredResType());
if(!resPt.isValid())
return false;
const GlobalGameSettings& settings = world->GetGGS();
bool inexhaustibleRes = settings.isEnabled(AddonId::INEXHAUSTIBLE_MINES)
|| (workplace->GetBuildingType() == BuildingType::GraniteMine
&& settings.isEnabled(AddonId::INEXHAUSTIBLE_GRANITEMINES));
MapPoint resPt = FindPointWithResourceQuiet(GetRequiredResType());
if(!resPt.isValid())
{
resPt = CreateWorkEverywhereGraniteResource();
if(!resPt.isValid())
{
workplace->OnOutOfResources();
return false;
}
}

const bool inexhaustibleRes = settings.isEnabled(AddonId::INEXHAUSTIBLE_MINES)
|| (workplace->GetBuildingType() == BuildingType::GraniteMine
&& settings.isEnabled(AddonId::INEXHAUSTIBLE_GRANITEMINES));
if(!inexhaustibleRes)
world->ReduceResource(resPt);

return nofWorkman::StartWorking();
}

ResourceType nofMiner::GetRequiredResType() const
{
switch(workplace->GetBuildingType())
Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/figures/nofMiner.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class nofMiner : public nofWorkman
bool AreWaresAvailable() const override;
bool StartWorking() override;
ResourceType GetRequiredResType() const;
MapPoint FindPointWithResourceQuiet(ResourceType type) const;
bool CanCreateWorkEverywhereGraniteResource() const;
MapPoint CreateWorkEverywhereGraniteResource();

public:
nofMiner(MapPoint pos, unsigned char player, nobUsual* workplace);
Expand Down
78 changes: 78 additions & 0 deletions tests/s25Main/integration/testProduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ static std::ostream& operator<<(std::ostream& os, const PostCategory& cat)

BOOST_AUTO_TEST_SUITE(Production)

namespace {
struct GraniteMineWithoutResourcesFixture : WorldWithGCExecution1P
{
MapPoint CreateGraniteMineWithoutResources()
{
GoodsAndPeopleCounts inv;
inv[GoodType::Fish] = 40;
inv[GoodType::PickAxe] = 1;
inv[Job::Miner] = 1;
world.GetSpecObj<nobBaseWarehouse>(hqPos)->AddToInventory(inv, true);

MapPoint minePos = hqPos + MapPoint(2, 0);
const auto* mine = static_cast<nobUsual*>(
BuildingFactory::CreateBuilding(world, BuildingType::GraniteMine, minePos, curPlayer, Nation::Romans));
BuildRoad(world.GetNeighbour(minePos, Direction::SouthEast), false, std::vector<Direction>(2, Direction::West));
RTTR_EXEC_TILL(500, mine->HasWorker());
return minePos;
}
};
} // namespace

BOOST_FIXTURE_TEST_CASE(MetalWorkerStopped, WorldWithGCExecution1P)
{
addStartResources();
Expand Down Expand Up @@ -102,4 +123,61 @@ BOOST_FIXTURE_TEST_CASE(MetalWorkerOrders, WorldWithGCExecution1P)
RTTR_EXEC_TILL(1300, mw->is_working);
}

BOOST_FIXTURE_TEST_CASE(GraniteMineWithoutResourcesNeedsAddon, GraniteMineWithoutResourcesFixture)
{
CreateGraniteMineWithoutResources();
const Inventory& curInventory = world.GetPlayer(curPlayer).GetInventory();
const unsigned initialStones = curInventory[GoodType::Stones];

RTTR_SKIP_GFS(2000);

BOOST_TEST(curInventory[GoodType::Stones] == initialStones);
}

BOOST_FIXTURE_TEST_CASE(InexhaustibleGraniteMineStillNeedsResourceSpot, GraniteMineWithoutResourcesFixture)
{
ggs.setSelection(AddonId::INEXHAUSTIBLE_GRANITEMINES, 1);
CreateGraniteMineWithoutResources();
const Inventory& curInventory = world.GetPlayer(curPlayer).GetInventory();
const unsigned initialStones = curInventory[GoodType::Stones];

RTTR_SKIP_GFS(2000);

BOOST_TEST(curInventory[GoodType::Stones] == initialStones);
}

BOOST_FIXTURE_TEST_CASE(GraniteMineWorkEverywhereCreatesDepletableResource, GraniteMineWithoutResourcesFixture)
{
ggs.setSelection(AddonId::GRANITEMINES_WORK_EVERYWHERE, 1);
const MapPoint minePos = CreateGraniteMineWithoutResources();
const Inventory& curInventory = world.GetPlayer(curPlayer).GetInventory();
const unsigned initialStones = curInventory[GoodType::Stones];

RTTR_EXEC_TILL(2000, curInventory[GoodType::Stones] > initialStones);
BOOST_TEST(world.GetNode(minePos).resources.has(ResourceType::Granite));

RTTR_EXEC_TILL(50000, world.GetNode(minePos).resources.getType() == ResourceType::Granite
&& world.GetNode(minePos).resources.getAmount() == 0u);
BOOST_TEST(static_cast<unsigned>(world.GetNode(minePos).resources.getType()) == static_cast<unsigned>(ResourceType::Granite));
BOOST_TEST(world.GetNode(minePos).resources.getAmount() == 0u);
}

BOOST_FIXTURE_TEST_CASE(GraniteMineWorkEverywhereResourceIsInexhaustibleWithGraniteAddon, GraniteMineWithoutResourcesFixture)
{
ggs.setSelection(AddonId::GRANITEMINES_WORK_EVERYWHERE, 1);
ggs.setSelection(AddonId::INEXHAUSTIBLE_GRANITEMINES, 1);
const MapPoint minePos = CreateGraniteMineWithoutResources();
const Inventory& curInventory = world.GetPlayer(curPlayer).GetInventory();
const unsigned initialStones = curInventory[GoodType::Stones];

RTTR_EXEC_TILL(2000, curInventory[GoodType::Stones] > initialStones);
BOOST_TEST(world.GetNode(minePos).resources.has(ResourceType::Granite));
const unsigned initialResourceAmount = world.GetNode(minePos).resources.getAmount();

RTTR_SKIP_GFS(10000);

BOOST_TEST(world.GetNode(minePos).resources.has(ResourceType::Granite));
BOOST_TEST(world.GetNode(minePos).resources.getAmount() == initialResourceAmount);
}

BOOST_AUTO_TEST_SUITE_END()