diff --git a/libs/s25main/GlobalGameSettings.cpp b/libs/s25main/GlobalGameSettings.cpp index b186bbff27..96631342d3 100644 --- a/libs/s25main/GlobalGameSettings.cpp +++ b/libs/s25main/GlobalGameSettings.cpp @@ -88,6 +88,10 @@ void GlobalGameSettings::registerAllAddons() AddonMilitaryAid, AddonMilitaryControl, AddonMilitaryHitpoints, + AddonMiningOverhaulCoal, + AddonMiningOverhaulGold, + AddonMiningOverhaulGranite, + AddonMiningOverhaulIron, AddonMoreAnimals, AddonNoAlliedPush, AddonNoCoinsDefault, diff --git a/libs/s25main/SerializedGameData.cpp b/libs/s25main/SerializedGameData.cpp index f43774af01..0abd822bf4 100644 --- a/libs/s25main/SerializedGameData.cpp +++ b/libs/s25main/SerializedGameData.cpp @@ -86,16 +86,17 @@ /// Then reset this number to 1. /// TODO: Let GO_Type start at 0 again when resetting this /// Changelog: -/// 2: All player buildings together, variable width size for containers and ship names -/// 3: Landscape and terrain names stored as strings -/// 4: HunterWaitingForAnimalReady introduced as sub-state of HunterFindingShootingpoint -/// 5: Make RoadPathDirection contiguous and use optional for ware in nofBuildingWorker -/// 6: Make TradeDirection contiguous, Serialize only nobUsuals in BuildingRegister::buildings, -/// include water and fish in geologists resourceFound -/// 7: Use helpers::push/popContainer (uses var size) -/// 8: noFlag::Wares converted to static_vector -/// 9: Drop serialization of node BQ -static const unsigned currentGameDataVersion = 9; +/// 2: All player buildings together, variable width size for containers and ship names +/// 3: Landscape and terrain names stored as strings +/// 4: HunterWaitingForAnimalReady introduced as sub-state of HunterFindingShootingpoint +/// 5: Make RoadPathDirection contiguous and use optional for ware in nofBuildingWorker +/// 6: Make TradeDirection contiguous, Serialize only nobUsuals in BuildingRegister::buildings, +/// include water and fish in geologists resourceFound +/// 7: Use helpers::push/popContainer (uses var size) +/// 8: noFlag::Wares converted to static_vector +/// 9: Drop serialization of node BQ +/// 10: nofMiner saves isAlteredWorkcycle boolean variable +static const unsigned currentGameDataVersion = 10; // clang-format on std::unique_ptr SerializedGameData::Create_GameObject(const GO_Type got, const unsigned obj_id) diff --git a/libs/s25main/addons/AddonMiningOverhaul.h b/libs/s25main/addons/AddonMiningOverhaul.h new file mode 100644 index 0000000000..bc78d6d8b9 --- /dev/null +++ b/libs/s25main/addons/AddonMiningOverhaul.h @@ -0,0 +1,62 @@ +// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "AddonList.h" +#include "mygettext/mygettext.h" + +enum class MiningBehavior +{ + Normal, + S4Like, + Inexhaustible, + AlwaysAvailable +}; + +class AddonMiningOverhaulBase : public AddonList +{ +protected: + AddonMiningOverhaulBase(AddonId addonId, const std::string& addonName) + : AddonList(addonId, AddonGroup::Economy, addonName, + _("This addon allows you to change the ore mining behavior.\n\n" + "No change: Original behavior\n" + "Settlers IV: Mines never fully deplete. Range is decreased to 1. Chance for production" + " depends on remaining resource amount in range.\n" + "Inexhaustible: Mines never deplete\n" + "Everywhere: Mines never deplete, can mine everywhere"), + { + _("No change"), + _("Settlers IV"), + _("Inexhaustible"), + _("Everywhere"), + }) + {} +}; + +class AddonMiningOverhaulCoal : public AddonMiningOverhaulBase +{ +public: + AddonMiningOverhaulCoal() : AddonMiningOverhaulBase(AddonId::MINING_OVERHAUL_COAL, _("Mining overhaul: Coal")) {} +}; + +class AddonMiningOverhaulGold : public AddonMiningOverhaulBase +{ +public: + AddonMiningOverhaulGold() : AddonMiningOverhaulBase(AddonId::MINING_OVERHAUL_GOLD, _("Mining overhaul: Gold")) {} +}; + +class AddonMiningOverhaulGranite : public AddonMiningOverhaulBase +{ +public: + AddonMiningOverhaulGranite() + : AddonMiningOverhaulBase(AddonId::MINING_OVERHAUL_GRANITE, _("Mining overhaul: Granite")) + {} +}; + +class AddonMiningOverhaulIron : public AddonMiningOverhaulBase +{ +public: + AddonMiningOverhaulIron() : AddonMiningOverhaulBase(AddonId::MINING_OVERHAUL_IRON, _("Mining overhaul: Iron")) {} +}; \ No newline at end of file diff --git a/libs/s25main/addons/Addons.h b/libs/s25main/addons/Addons.h index 8ac5366105..fe7c075630 100644 --- a/libs/s25main/addons/Addons.h +++ b/libs/s25main/addons/Addons.h @@ -34,11 +34,13 @@ #include "addons/AddonToolOrdering.h" #include "addons/AddonInexhaustibleFish.h" -#include "addons/AddonInexhaustibleGraniteMines.h" #include "addons/AddonMaxRank.h" #include "addons/AddonMilitaryAid.h" #include "addons/AddonSeaAttack.h" +#include "addons/AddonInexhaustibleGraniteMines.h" +#include "addons/AddonMiningOverhaul.h" + #include "addons/AddonBattlefieldPromotion.h" #include "addons/AddonBurnDuration.h" #include "addons/AddonHalfCostMilEquip.h" diff --git a/libs/s25main/addons/const_addons.h b/libs/s25main/addons/const_addons.h index 376d3aaec3..9971e93628 100644 --- a/libs/s25main/addons/const_addons.h +++ b/libs/s25main/addons/const_addons.h @@ -55,6 +55,9 @@ ENUM_WITH_STRING(AddonId, LIMIT_CATAPULTS = 0x00000000, INEXHAUSTIBLE_MINES = 0x INEXHAUSTIBLE_GRANITEMINES = 0x00800000, + MINING_OVERHAUL_COAL = 0x00800001, MINING_OVERHAUL_GOLD = 0x00800002, + MINING_OVERHAUL_GRANITE = 0x00800003, MINING_OVERHAUL_IRON = 0x00800004, + MAX_RANK = 0x00900000, SEA_ATTACK = 0x00900001, INEXHAUSTIBLE_FISH = 0x00900002, MORE_ANIMALS = 0x00900003, BURN_DURATION = 0x00900004, NO_ALLIED_PUSH = 0x00900005, BATTLEFIELD_PROMOTION = 0x00900006, HALF_COST_MIL_EQUIP = 0x00900007, MILITARY_CONTROL = 0x00900008, diff --git a/libs/s25main/ai/aijh/AIPlayerJH.cpp b/libs/s25main/ai/aijh/AIPlayerJH.cpp index cd80887511..e110361365 100644 --- a/libs/s25main/ai/aijh/AIPlayerJH.cpp +++ b/libs/s25main/ai/aijh/AIPlayerJH.cpp @@ -977,13 +977,8 @@ MapPoint AIPlayerJH::FindPositionForBuildingAround(BuildingType type, const MapP foundPos = FindBestPosition(around, AIResource::Ironore, BuildingQuality::Mine, searchRadius); break; case BuildingType::GraniteMine: - if(!ggs.isEnabled( - AddonId::INEXHAUSTIBLE_GRANITEMINES)) // inexhaustible granite mines do not require granite - foundPos = FindBestPosition(around, AIResource::Granite, BuildingQuality::Mine, searchRadius); - else - foundPos = SimpleFindPosition(around, BuildingQuality::Mine, searchRadius); + foundPos = FindBestPosition(around, AIResource::Granite, BuildingQuality::Mine, searchRadius); break; - case BuildingType::Fishery: foundPos = FindBestPosition(around, AIResource::Fish, BUILDING_SIZE[type], searchRadius); if(foundPos.isValid() && !ValidFishInRange(foundPos)) diff --git a/libs/s25main/figures/nofMiner.cpp b/libs/s25main/figures/nofMiner.cpp index 912619b58a..67fea3dd65 100644 --- a/libs/s25main/figures/nofMiner.cpp +++ b/libs/s25main/figures/nofMiner.cpp @@ -5,18 +5,27 @@ #include "nofMiner.h" #include "GlobalGameSettings.h" #include "Loader.h" +#include "SerializedGameData.h" #include "SoundManager.h" #include "addons/const_addons.h" #include "buildings/nobUsual.h" #include "network/GameClient.h" #include "ogl/glArchivItem_Bitmap_Player.h" #include "world/GameWorld.h" +#include +#include nofMiner::nofMiner(const MapPoint pos, const unsigned char player, nobUsual* workplace) - : nofWorkman(Job::Miner, pos, player, workplace) + : nofWorkman(Job::Miner, pos, player, workplace), isAlteredWorkcycle(false) {} -nofMiner::nofMiner(SerializedGameData& sgd, const unsigned obj_id) : nofWorkman(sgd, obj_id) {} +nofMiner::nofMiner(SerializedGameData& sgd, const unsigned obj_id) : nofWorkman(sgd, obj_id) +{ + if(sgd.GetGameDataVersion() >= 10) + isAlteredWorkcycle = sgd.PopBool(); + else + isAlteredWorkcycle = false; +} void nofMiner::DrawWorking(DrawPoint drawPt) { @@ -60,6 +69,9 @@ unsigned short nofMiner::GetCarryID() const helpers::OptionalEnum nofMiner::ProduceWare() { + if(isAlteredWorkcycle) + return boost::none; + switch(workplace->GetBuildingType()) { case BuildingType::GoldMine: return GoodType::Gold; @@ -71,20 +83,103 @@ helpers::OptionalEnum nofMiner::ProduceWare() bool nofMiner::AreWaresAvailable() const { - return nofWorkman::AreWaresAvailable() && FindPointWithResource(GetRequiredResType()).isValid(); + if(!nofWorkman::AreWaresAvailable()) + return false; + + const MiningBehavior addonSetting = GetMiningBehavior(); + + if(addonSetting == MiningBehavior::AlwaysAvailable) + return true; + + if(addonSetting == MiningBehavior::S4Like) + return FindPointWithResource(GetRequiredResType(), MINER_ORE_RADIUS_SETTLERSIV).isValid(); + else + return FindPointWithResource(GetRequiredResType()).isValid(); } -bool nofMiner::StartWorking() +MiningBehavior nofMiner::GetMiningBehavior() const { - 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)); - if(!inexhaustibleRes) - world->ReduceResource(resPt); + + auto miningBehavior = MiningBehavior::Normal; + + switch(workplace->GetBuildingType()) + { + case BuildingType::GoldMine: + miningBehavior = static_cast(settings.getSelection(AddonId::MINING_OVERHAUL_GOLD)); + break; + case BuildingType::IronMine: + miningBehavior = static_cast(settings.getSelection(AddonId::MINING_OVERHAUL_IRON)); + break; + case BuildingType::CoalMine: + miningBehavior = static_cast(settings.getSelection(AddonId::MINING_OVERHAUL_COAL)); + break; + case BuildingType::GraniteMine: + miningBehavior = static_cast(settings.getSelection(AddonId::MINING_OVERHAUL_GRANITE)); + break; + default: miningBehavior = MiningBehavior::Normal; + } + + return miningBehavior; +} + +bool nofMiner::StartWorking() +{ + isAlteredWorkcycle = false; + + switch(GetMiningBehavior()) + { + case MiningBehavior::S4Like: + { + int sumResAmount = 0; + MapPoint nonMinimumResPt; + + const std::vector reachablePts = + world->GetPointsInRadiusWithCenter(workplace->GetPos(), MINER_ORE_RADIUS_SETTLERSIV); + + for(const MapPoint curPt : reachablePts) + { + const Resource resource = world->GetNode(curPt).resources; + + if(resource.getType() != GetRequiredResType()) + continue; + + const auto resAmount = resource.getAmount(); + sumResAmount += resAmount; + + if(resAmount > 1u && !nonMinimumResPt.isValid()) + nonMinimumResPt = curPt; + } + + // depending on remaining resources, roll if this workcycle needs to be altered or not + if(RANDOM_RAND(reachablePts.size() * MAX_ORE_QUANTITY) > sumResAmount) + { + isAlteredWorkcycle = true; + } else + { + if(nonMinimumResPt.isValid()) + world->ReduceResource(nonMinimumResPt); + } + } + break; + case MiningBehavior::Inexhaustible: + { + if(!FindPointWithResource(GetRequiredResType()).isValid()) + return false; + } + break; + case MiningBehavior::AlwaysAvailable: break; + case MiningBehavior::Normal: + { + const MapPoint resPt = FindPointWithResource(GetRequiredResType()); + if(!resPt.isValid()) + return false; + + world->ReduceResource(resPt); + } + break; + } + return nofWorkman::StartWorking(); } @@ -98,3 +193,12 @@ ResourceType nofMiner::GetRequiredResType() const default: return ResourceType::Granite; } } + +void nofMiner::Serialize(SerializedGameData& sgd) const +{ + nofWorkman::Serialize(sgd); + + sgd.PushBool(isAlteredWorkcycle); + + sgd.GetGameDataVersion(); +} diff --git a/libs/s25main/figures/nofMiner.h b/libs/s25main/figures/nofMiner.h index c3fdc810ad..906cf23a2a 100644 --- a/libs/s25main/figures/nofMiner.h +++ b/libs/s25main/figures/nofMiner.h @@ -5,11 +5,11 @@ #pragma once #include "nofWorkman.h" +#include "addons/AddonMiningOverhaul.h" class SerializedGameData; class nobUsual; -/// Klasse für den Schreiner class nofMiner : public nofWorkman { protected: @@ -19,14 +19,19 @@ class nofMiner : public nofWorkman unsigned short GetCarryID() const override; /// Der Arbeiter erzeugt eine Ware helpers::OptionalEnum ProduceWare() override; + /// alter workcycle (addon) + bool isAlteredWorkcycle; bool AreWaresAvailable() const override; bool StartWorking() override; ResourceType GetRequiredResType() const; + MiningBehavior GetMiningBehavior() const; public: nofMiner(MapPoint pos, unsigned char player, nobUsual* workplace); nofMiner(SerializedGameData& sgd, unsigned obj_id); GO_Type GetGOT() const final { return GO_Type::NofMiner; } + + void Serialize(SerializedGameData& sgd) const override; }; diff --git a/libs/s25main/figures/nofWorkman.cpp b/libs/s25main/figures/nofWorkman.cpp index 61f7a8a2f9..50bf74c808 100644 --- a/libs/s25main/figures/nofWorkman.cpp +++ b/libs/s25main/figures/nofWorkman.cpp @@ -100,13 +100,16 @@ struct NodeHasResource MapPoint nofWorkman::FindPointWithResource(ResourceType type) const { - // Alle Punkte durchgehen, bis man einen findet, wo man graben kann + return FindPointWithResource(type, MINER_RADIUS); +} + +MapPoint nofWorkman::FindPointWithResource(ResourceType type, unsigned radius) const { const std::vector pts = - world->GetMatchingPointsInRadius<1>(pos, MINER_RADIUS, NodeHasResource(*world, type), true); + world->GetMatchingPointsInRadius<1>(pos, radius, NodeHasResource(*world, type), true); if(!pts.empty()) return pts.front(); workplace->OnOutOfResources(); return MapPoint::Invalid(); -} +} \ No newline at end of file diff --git a/libs/s25main/figures/nofWorkman.h b/libs/s25main/figures/nofWorkman.h index 9621d2f072..0c6ccf4cf6 100644 --- a/libs/s25main/figures/nofWorkman.h +++ b/libs/s25main/figures/nofWorkman.h @@ -38,6 +38,7 @@ class nofWorkman : public nofBuildingWorker /// Looks for a point with a given resource on the node MapPoint FindPointWithResource(ResourceType type) const; + MapPoint FindPointWithResource(ResourceType type, unsigned radius) const; public: /// Going to workplace diff --git a/libs/s25main/gameData/GameConsts.h b/libs/s25main/gameData/GameConsts.h index 51f7fdf1bf..b001904599 100644 --- a/libs/s25main/gameData/GameConsts.h +++ b/libs/s25main/gameData/GameConsts.h @@ -20,6 +20,10 @@ constexpr GameSpeed referenceSpeed = GameSpeed::Normal; /// Reichweite der Bergarbeiter constexpr unsigned MINER_RADIUS = 2; +constexpr unsigned MINER_ORE_RADIUS_SETTLERSIV = 1u; + +/// maximum quantity for ores +constexpr unsigned MAX_ORE_QUANTITY = 7u; /// Konstante für die Pfadrichtung bei einer Schiffsverbindung constexpr unsigned char SHIP_DIR = 100; diff --git a/libs/s25main/world/MapBase.h b/libs/s25main/world/MapBase.h index cb0d9a1454..a983b08dd7 100644 --- a/libs/s25main/world/MapBase.h +++ b/libs/s25main/world/MapBase.h @@ -86,6 +86,15 @@ class MapBase template bool CheckPointsInRadius(MapPoint pt, unsigned radius, T_IsValidPt&& isValid, bool includePt) const; + /// Returns number of points in the given radius + /// If includePt is true, then the point itself is also counted + constexpr unsigned GetNumPointsInRadius(unsigned radius, bool includePt) const + { + // For every additional radius we get 6 * curRadius more points. Hence we have 6 * sum(1..radius) points + the + // center point if requested This can be reduced via the gauss formula to the following: + return (radius * radius + radius) * 3u + (includePt ? 1u : 0u); + } + /// Return the distance between 2 points on the map (includes wrapping around map borders) unsigned CalcDistance(const Position& p1, const Position& p2) const; unsigned CalcDistance(const MapPoint p1, const MapPoint p2) const @@ -122,9 +131,7 @@ detail::GetPointsResult_t MapBase::GetPointsInRadius(const MapPoi result.reserve(T_maxResults); else if(std::is_same::value) { - // For every additional radius we get 6 * curRadius more points. Hence we have 6 * sum(1..radius) points + the - // center point if requested This can be reduced via the gauss formula to the following: - result.reserve((radius * radius + radius) * 3u + (includePt ? 1u : 0u)); + result.reserve(GetNumPointsInRadius(radius, includePt)); } if(includePt) {