From 8ac9e0aea53cf7b264eaed8f143843af8d97d850 Mon Sep 17 00:00:00 2001 From: wvpm <24685035+wvpm@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:28:23 +0100 Subject: [PATCH] Implement building restrictions --- .../country/CountryInstance.cpp | 59 +++++++++-- .../country/CountryInstance.hpp | 87 ++++++++-------- .../economy/BuildingInstance.cpp | 51 ++++++++-- .../economy/BuildingInstance.hpp | 12 ++- .../economy/BuildingRestrictionCategory.hpp | 9 ++ .../economy/BuildingType.cpp | 98 ++++++++++++++++--- .../economy/BuildingType.hpp | 77 ++++++++++----- .../history/ProvinceHistory.cpp | 83 +++++++++------- src/openvic-simulation/map/Mapmode.cpp | 6 +- .../map/ProvinceInstance.cpp | 17 +++- .../map/ProvinceInstance.hpp | 11 ++- src/openvic-simulation/misc/GameAction.cpp | 15 ++- src/openvic-simulation/misc/GameAction.hpp | 2 +- src/openvic-simulation/politics/RuleSet.hpp | 2 +- 14 files changed, 381 insertions(+), 148 deletions(-) create mode 100644 src/openvic-simulation/economy/BuildingRestrictionCategory.hpp diff --git a/src/openvic-simulation/country/CountryInstance.cpp b/src/openvic-simulation/country/CountryInstance.cpp index 75ad9547..4fda7a7d 100644 --- a/src/openvic-simulation/country/CountryInstance.cpp +++ b/src/openvic-simulation/country/CountryInstance.cpp @@ -17,6 +17,7 @@ #include "openvic-simulation/defines/EconomyDefines.hpp" #include "openvic-simulation/defines/MilitaryDefines.hpp" #include "openvic-simulation/diplomacy/CountryRelation.hpp" +#include "openvic-simulation/economy/BuildingLevel.hpp" #include "openvic-simulation/economy/BuildingType.hpp" #include "openvic-simulation/economy/GoodInstance.hpp" #include "openvic-simulation/economy/production/ProductionType.hpp" @@ -275,7 +276,7 @@ CountryInstance::CountryInstance( update_parties_for_votes(new_country_definition); for (BuildingType const& building_type : building_type_unlock_levels.get_keys()) { - if (building_type.is_default_enabled()) { + if (building_type.is_enabled_by_default) { unlock_building_type(building_type); } } @@ -335,6 +336,45 @@ bool CountryInstance::is_neighbour(CountryInstance const& country) const { return neighbouring_countries.contains(&country); } +bool CountryInstance::may_build_in(const BuildingRestrictionCategory restriction_category, ProvinceInstance const& location) const { + CountryInstance const* const owner_ptr = location.get_owner(); + + if (owner_ptr == nullptr) { + //Can't build in uncolonised provinces + return false; + } + CountryInstance const& owner = *owner_ptr; + + if (owner == *this) { + switch(restriction_category) { + case BuildingRestrictionCategory::UNRESTRICTED: + return true; + case BuildingRestrictionCategory::INFRASTRUCTURE: + return rule_set.may_expand_infrastructure_domestically(); + case BuildingRestrictionCategory::FACTORY: + return rule_set.may_build_factory_domestically(); + } + } + + if (is_at_war_with(owner)) { + //Not allowed to build in hostile lands. + return false; + } + + if (!owner.rule_set.foreigners_may_invest()) { + return false; + } + + switch(restriction_category) { + case BuildingRestrictionCategory::UNRESTRICTED: + return false; //For example you can't invest in foreign forts. + case BuildingRestrictionCategory::INFRASTRUCTURE: + return rule_set.may_invest_in_expanding_infrastructure_abroad(); + case BuildingRestrictionCategory::FACTORY: + return rule_set.may_invest_in_building_factory_abroad(); + } +} + CountryRelationManager::relation_value_type CountryInstance::get_relations_with(CountryInstance const& country) const { return country_relations_manager.get_country_relation(this, &country); } @@ -870,12 +910,13 @@ bool CountryInstance::is_unit_type_unlocked(UnitType const& unit_type) const { } bool CountryInstance::modify_building_type_unlock( - BuildingType const& building_type, technology_unlock_level_t unlock_level_change + BuildingType const& building_type, technology_unlock_level_t tech_unlock_level_change ) { - technology_unlock_level_t& unlock_level = building_type_unlock_levels.at(building_type); + building_level_t& unlock_level = building_type_unlock_levels.at(building_type); + building_level_t unlock_level_change = building_level_t(type_safe::get(tech_unlock_level_change)); // This catches subtracting below 0 or adding above the int types maximum value - if (unlock_level + unlock_level_change < 0) { + if (unlock_level + unlock_level_change < building_level_t(0)) { spdlog::error_s( "Attempted to change unlock level for building type {} in country {} to invalid value: current level = {}, change = {}, invalid new value = {}", building_type, *this, unlock_level, unlock_level_change, @@ -886,8 +927,8 @@ bool CountryInstance::modify_building_type_unlock( unlock_level += unlock_level_change; - if (building_type.get_production_type() != nullptr) { - good_instance_manager.enable_good(building_type.get_production_type()->output_good); + if (building_type.production_type != nullptr) { + good_instance_manager.enable_good(building_type.production_type->output_good); } return true; @@ -897,8 +938,12 @@ bool CountryInstance::unlock_building_type(BuildingType const& building_type) { return modify_building_type_unlock(building_type, technology_unlock_level_t { 1 }); } +building_level_t const& CountryInstance::get_building_type_unlock_levels(BuildingType const& building_type) const { + return building_type_unlock_levels.at(building_type); +} + bool CountryInstance::is_building_type_unlocked(BuildingType const& building_type) const { - return building_type_unlock_levels.at(building_type) > 0; + return building_type_unlock_levels.at(building_type) > building_level_t(0); } bool CountryInstance::modify_crime_unlock(Crime const& crime, technology_unlock_level_t unlock_level_change) { diff --git a/src/openvic-simulation/country/CountryInstance.hpp b/src/openvic-simulation/country/CountryInstance.hpp index 97393ca3..69286585 100644 --- a/src/openvic-simulation/country/CountryInstance.hpp +++ b/src/openvic-simulation/country/CountryInstance.hpp @@ -5,6 +5,8 @@ #include #include "openvic-simulation/diplomacy/CountryRelation.hpp" +#include "openvic-simulation/economy/BuildingLevel.hpp" +#include "openvic-simulation/economy/BuildingRestrictionCategory.hpp" #include "openvic-simulation/military/CombatWidth.hpp" #include "openvic-simulation/military/UnitBranchedGetterMacro.hpp" #include "openvic-simulation/modifier/ModifierSum.hpp" @@ -155,7 +157,7 @@ namespace OpenVic { memory::vector> SPAN_PROPERTY(industrial_power_from_investments); size_t PROPERTY(industrial_rank, 0); fixed_point_map_t PROPERTY(foreign_investments); - OV_IFLATMAP_PROPERTY(BuildingType, technology_unlock_level_t, building_type_unlock_levels); + OV_IFLATMAP_PROPERTY(BuildingType, building_level_t, building_type_unlock_levels); // TODO - total amount of each good produced /* Budget */ @@ -410,57 +412,58 @@ namespace OpenVic { OV_UNIT_BRANCHED_GETTER(get_leaders, generals, admirals); OV_UNIT_BRANCHED_GETTER_CONST(get_leaders, generals, admirals); - inline size_t get_general_count() const { + [[nodiscard]] inline size_t get_general_count() const { return generals.size(); } - inline bool has_generals() const { + [[nodiscard]] inline bool has_generals() const { return !generals.empty(); } - inline size_t get_admiral_count() const { + [[nodiscard]] inline size_t get_admiral_count() const { return admirals.size(); } - inline bool has_admirals() const { + [[nodiscard]] inline bool has_admirals() const { return !admirals.empty(); } - inline size_t get_leader_count() const { + [[nodiscard]] inline size_t get_leader_count() const { return get_general_count() + get_admiral_count(); } - inline bool has_leaders() const { + [[nodiscard]] inline bool has_leaders() const { return has_generals() || has_admirals(); } - inline size_t get_army_count() const { + [[nodiscard]] inline size_t get_army_count() const { return armies.size(); } - inline bool has_armies() const { + [[nodiscard]] inline bool has_armies() const { return !armies.empty(); } - inline size_t get_navy_count() const { + [[nodiscard]] inline size_t get_navy_count() const { return navies.size(); } - inline bool has_navies() const { + [[nodiscard]] inline bool has_navies() const { return !navies.empty(); } - std::string_view get_identifier() const; + [[nodiscard]] std::string_view get_identifier() const; - bool exists() const; - bool is_rebel_country() const; - bool is_civilised() const; - bool can_colonise() const; - bool is_great_power() const; - bool is_secondary_power() const; - bool is_at_war() const; - bool is_neighbour(CountryInstance const& country) const; + [[nodiscard]] bool exists() const; + [[nodiscard]] bool is_rebel_country() const; + [[nodiscard]] bool is_civilised() const; + [[nodiscard]] bool can_colonise() const; + [[nodiscard]] bool is_great_power() const; + [[nodiscard]] bool is_secondary_power() const; + [[nodiscard]] bool is_at_war() const; + [[nodiscard]] bool is_neighbour(CountryInstance const& country) const; + [[nodiscard]] bool may_build_in(const BuildingRestrictionCategory restriction_category, ProvinceInstance const& location) const; // Double-sided diplomacy functions CountryRelationManager::relation_value_type get_relations_with(CountryInstance const& country) const; void set_relations_with(CountryInstance& country, CountryRelationManager::relation_value_type relations); - bool has_alliance_with(CountryInstance const& country) const; + [[nodiscard]] bool has_alliance_with(CountryInstance const& country) const; void set_alliance_with(CountryInstance& country, bool alliance = true); - bool is_at_war_with(CountryInstance const& country) const; + [[nodiscard]] bool is_at_war_with(CountryInstance const& country) const; // Low-level setter function, should not be used to declare or join wars // Should generally be the basis for higher-level war functions void set_at_war_with(CountryInstance& country, bool at_war = true); @@ -469,16 +472,16 @@ namespace OpenVic { // Only detects military access diplomacy, do not use to validate troop movement // Prefer can_units_enter - bool has_military_access_to(CountryInstance const& country) const; + [[nodiscard]] bool has_military_access_to(CountryInstance const& country) const; void set_military_access_to(CountryInstance& country, bool access = true); - bool is_sending_war_subsidy_to(CountryInstance const& country) const; + [[nodiscard]] bool is_sending_war_subsidy_to(CountryInstance const& country) const; void set_sending_war_subsidy_to(CountryInstance& country, bool sending = true); - bool is_commanding_units(CountryInstance const& country) const; + [[nodiscard]] bool is_commanding_units(CountryInstance const& country) const; void set_commanding_units(CountryInstance& country, bool commanding = true); - bool has_vision_of(CountryInstance const& country) const; + [[nodiscard]] bool has_vision_of(CountryInstance const& country) const; void set_has_vision_of(CountryInstance& country, bool vision = true); CountryRelationManager::OpinionType get_opinion_of(CountryInstance const& country) const; @@ -498,8 +501,8 @@ namespace OpenVic { std::optional get_embass_banned_from_date(CountryInstance const& country) const; void set_embassy_banned_from(CountryInstance& country, Date until); - bool can_army_units_enter(CountryInstance const& country) const; - bool can_navy_units_enter(CountryInstance const& country) const; + [[nodiscard]] bool can_army_units_enter(CountryInstance const& country) const; + [[nodiscard]] bool can_navy_units_enter(CountryInstance const& country) const; // These functions take "std::string const&" rather than "std::string_view" as they're only used with script arguments // which are always stored as "std::string"s and it significantly simplifies mutable value access. @@ -545,36 +548,36 @@ namespace OpenVic { bool add_leader(LeaderInstance& leader); bool remove_leader(LeaderInstance const& leader); - bool has_leader_with_name(std::string_view name) const; + [[nodiscard]] bool has_leader_with_name(std::string_view name) const; template bool modify_unit_type_unlock(UnitTypeBranched const& unit_type, technology_unlock_level_t unlock_level_change); bool modify_unit_type_unlock(UnitType const& unit_type, technology_unlock_level_t unlock_level_change); bool unlock_unit_type(UnitType const& unit_type); - bool is_unit_type_unlocked(UnitType const& unit_type) const; + [[nodiscard]] bool is_unit_type_unlocked(UnitType const& unit_type) const; bool modify_building_type_unlock( BuildingType const& building_type, technology_unlock_level_t unlock_level_change ); bool unlock_building_type(BuildingType const& building_type); - bool is_building_type_unlocked(BuildingType const& building_type) const; + [[nodiscard]] bool is_building_type_unlocked(BuildingType const& building_type) const; bool modify_crime_unlock(Crime const& crime, technology_unlock_level_t unlock_level_change); bool unlock_crime(Crime const& crime); - bool is_crime_unlocked(Crime const& crime) const; + [[nodiscard]] bool is_crime_unlocked(Crime const& crime) const; bool modify_gas_attack_unlock(technology_unlock_level_t unlock_level_change); bool unlock_gas_attack(); - bool is_gas_attack_unlocked() const; + [[nodiscard]] bool is_gas_attack_unlocked() const; bool modify_gas_defence_unlock(technology_unlock_level_t unlock_level_change); bool unlock_gas_defence(); - bool is_gas_defence_unlocked() const; + [[nodiscard]] bool is_gas_defence_unlocked() const; bool modify_unit_variant_unlock(unit_variant_t unit_variant, technology_unlock_level_t unlock_level_change); bool unlock_unit_variant(unit_variant_t unit_variant); - unit_variant_t get_max_unlocked_unit_variant() const; + [[nodiscard]] unit_variant_t get_max_unlocked_unit_variant() const; bool modify_technology_unlock( Technology const& technology, technology_unlock_level_t unlock_level_change @@ -583,7 +586,7 @@ namespace OpenVic { Technology const& technology, technology_unlock_level_t unlock_level ); bool unlock_technology(Technology const& technology); - bool is_technology_unlocked(Technology const& technology) const; + [[nodiscard]] bool is_technology_unlocked(Technology const& technology) const; bool modify_invention_unlock( Invention const& invention, technology_unlock_level_t unlock_level_change @@ -592,15 +595,15 @@ namespace OpenVic { Invention const& invention, technology_unlock_level_t unlock_level ); bool unlock_invention(Invention const& invention); - bool is_invention_unlocked(Invention const& invention) const; + [[nodiscard]] bool is_invention_unlocked(Invention const& invention) const; - bool is_primary_culture(Culture const& culture) const; + [[nodiscard]] bool is_primary_culture(Culture const& culture) const; // This only checks the accepted cultures list, ignoring the primary culture. - bool is_accepted_culture(Culture const& culture) const; - bool is_primary_or_accepted_culture(Culture const& culture) const; + [[nodiscard]] bool is_accepted_culture(Culture const& culture) const; + [[nodiscard]] bool is_primary_or_accepted_culture(Culture const& culture) const; - fixed_point_t calculate_research_cost(Technology const& technology) const; - bool can_research_tech(Technology const& technology, const Date today) const; + [[nodiscard]] fixed_point_t calculate_research_cost(Technology const& technology) const; + [[nodiscard]] bool can_research_tech(Technology const& technology, const Date today) const; void start_research(Technology const& technology, const Date today); // Sets the investment of each country in the map (rather than adding to them), leaving the rest unchanged. diff --git a/src/openvic-simulation/economy/BuildingInstance.cpp b/src/openvic-simulation/economy/BuildingInstance.cpp index b4740aed..8cdbf432 100644 --- a/src/openvic-simulation/economy/BuildingInstance.cpp +++ b/src/openvic-simulation/economy/BuildingInstance.cpp @@ -10,16 +10,31 @@ BuildingInstance::BuildingInstance(BuildingType const& new_building_type, buildi level { new_level } {} bool BuildingInstance::_can_expand() const { - return level < building_type.get_max_level(); + return level < building_type.max_level; } -bool BuildingInstance::expand() { - if (expansion_state == ExpansionState::CanExpand) { - expansion_state = ExpansionState::Preparing; - expansion_progress = 0; - return true; +bool BuildingInstance::expand( + ModifierEffectCache const& modifier_effect_cache, + CountryInstance& actor, + ProvinceInstance const& location +) { + if (expansion_state != ExpansionState::CanExpand) { + return false; } - return false; + + if (!building_type.can_be_built_in( + modifier_effect_cache, + level+1, + actor, + location + )) { + return false; + } + + //TODO add construction costs to actor + expansion_state = ExpansionState::Preparing; + expansion_progress = 0; + return true; } /* REQUIREMENTS: @@ -29,7 +44,7 @@ void BuildingInstance::update_gamestate(Date today) { switch (expansion_state) { case ExpansionState::Preparing: start_date = today; - end_date = start_date + building_type.get_build_time(); + end_date = start_date + building_type.build_time; break; case ExpansionState::Expanding: expansion_progress = fixed_point_t { static_cast((today - start_date).to_int()) } @@ -50,3 +65,23 @@ void BuildingInstance::tick(Date today) { } } } + +void BuildingInstance::set_level(const building_level_t new_level) { + if (new_level == level) { + return; + } + + if (new_level < level) { + if (new_level < building_level_t(0)) { + spdlog::error_s("Cannot set building level to {}, the minimum is 0.", new_level); + level = building_level_t(0); + } else { + level = new_level; + } + } else if (new_level > building_type.max_level) { + spdlog::error_s("Cannot set building level to {}, the maximum is {}.", new_level, building_type.max_level); + level = building_type.max_level; + } else { + level = new_level; + } +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/BuildingInstance.hpp b/src/openvic-simulation/economy/BuildingInstance.hpp index 1351f1ce..7fc5431b 100644 --- a/src/openvic-simulation/economy/BuildingInstance.hpp +++ b/src/openvic-simulation/economy/BuildingInstance.hpp @@ -7,12 +7,15 @@ namespace OpenVic { struct BuildingType; + struct CountryInstance; + struct ModifierEffectCache; + struct ProvinceInstance; struct BuildingInstance : HasIdentifier { // used in the actual game enum class ExpansionState { CannotExpand, CanExpand, Preparing, Expanding }; private: - building_level_t PROPERTY_RW(level); + building_level_t PROPERTY(level); ExpansionState PROPERTY(expansion_state, ExpansionState::CannotExpand); Date PROPERTY(start_date); Date PROPERTY(end_date); @@ -26,8 +29,13 @@ namespace OpenVic { BuildingInstance(BuildingType const& new_building_type, building_level_t new_level = building_level_t { 0 }); BuildingInstance(BuildingInstance&&) = default; - bool expand(); + bool expand( + ModifierEffectCache const& modifier_effect_cache, + CountryInstance& actor, + ProvinceInstance const& location + ); void update_gamestate(Date today); void tick(Date today); + void set_level(const building_level_t new_level); }; } diff --git a/src/openvic-simulation/economy/BuildingRestrictionCategory.hpp b/src/openvic-simulation/economy/BuildingRestrictionCategory.hpp new file mode 100644 index 00000000..b2bf0e97 --- /dev/null +++ b/src/openvic-simulation/economy/BuildingRestrictionCategory.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace OpenVic { + enum struct BuildingRestrictionCategory { + UNRESTRICTED, + INFRASTRUCTURE, + FACTORY + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/BuildingType.cpp b/src/openvic-simulation/economy/BuildingType.cpp index ed55e05d..2325b734 100644 --- a/src/openvic-simulation/economy/BuildingType.cpp +++ b/src/openvic-simulation/economy/BuildingType.cpp @@ -2,10 +2,16 @@ #include +#include "openvic-simulation/core/error/ErrorMacros.hpp" +#include "openvic-simulation/country/CountryInstance.hpp" #include "openvic-simulation/economy/GoodDefinition.hpp" #include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/map/ProvinceDefinition.hpp" +#include "openvic-simulation/map/ProvinceInstance.hpp" +#include "openvic-simulation/map/State.hpp" #include "openvic-simulation/modifier/ModifierEffectCache.hpp" #include "openvic-simulation/modifier/ModifierManager.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/TypedIndices.hpp" using namespace OpenVic; @@ -26,24 +32,86 @@ BuildingType::BuildingType( goods_cost { std::move(building_type_args.goods_cost) }, cost { building_type_args.cost }, build_time { building_type_args.build_time }, - on_map { building_type_args.on_map }, - default_enabled { building_type_args.default_enabled }, + should_display_on_map { building_type_args.on_map }, + is_enabled_by_default { building_type_args.default_enabled }, production_type { building_type_args.production_type }, - pop_build_factory { building_type_args.pop_build_factory }, - strategic_factory { building_type_args.strategic_factory }, - advanced_factory { building_type_args.advanced_factory }, + is_pop_build_factory { building_type_args.pop_build_factory }, + is_strategic_factory { building_type_args.strategic_factory }, + is_advanced_factory { building_type_args.advanced_factory }, fort_level { building_type_args.fort_level }, naval_capacity { building_type_args.naval_capacity }, colonial_points { std::move(building_type_args.colonial_points) }, - in_province { building_type_args.in_province }, - one_per_state { building_type_args.one_per_state }, + is_in_province { building_type_args.in_province }, + restriction_category { + building_type_args.pop_build_factory + ? building_type_args.in_province + ? BuildingRestrictionCategory::INFRASTRUCTURE + : BuildingRestrictionCategory::FACTORY + : BuildingRestrictionCategory::UNRESTRICTED + }, + is_limited_to_one_per_state { building_type_args.one_per_state }, colonial_range { building_type_args.colonial_range }, infrastructure { building_type_args.infrastructure }, - spawn_railway_track { building_type_args.spawn_railway_track }, - sail { building_type_args.sail }, - steam { building_type_args.steam }, + should_spawn_railway_track { building_type_args.spawn_railway_track }, + is_sail { building_type_args.sail }, + is_steam { building_type_args.steam }, capital { building_type_args.capital }, - port { building_type_args.port } {} + is_port { building_type_args.port } {} + +bool BuildingType::can_be_built_in( + ModifierEffectCache const& modifier_effect_cache, + const building_level_t desired_level, + CountryInstance const& actor, + ProvinceInstance const& location +) const { + OV_ERR_FAIL_COND_V_MSG( + !is_in_province, + false, + memory::fmt::format("BuildingType::can_be_built_in (province variant) was called on state level building {}", get_identifier()) + ); + + if (desired_level > max_level) { + return false; + } + + if (!can_be_built_in(location.province_definition)) { + return false; + } + + if (!location.may_build_here()) { + return false; + } + + if (!actor.may_build_in(restriction_category, location)) { + return false; + } + + if (is_limited_to_one_per_state) { + State const* state = location.get_state(); + if (state != nullptr) { + for (ProvinceInstance const* province_in_state : state->get_provinces()) { + if (province_in_state == nullptr || *province_in_state == location) { + continue; + } + + const building_level_t other_building_level = province_in_state->get_buildings()[province_building_index.value()].get_level(); + if (other_building_level > building_level_t(0)) { + return false; + } + } + } + } + + building_level_t const& unlocked_max_level = actor.get_building_type_unlock_levels(*this); + ModifierEffectCache::building_type_effects_t const& effects = modifier_effect_cache.get_building_type_effects(*this); + const fixed_point_t min_level_modifier = location.get_modifier_effect_value(*effects.get_min_level()); + return fixed_point_t { type_safe::get(unlocked_max_level) } + >= fixed_point_t { type_safe::get(desired_level) } + min_level_modifier; +} + +bool BuildingType::can_be_built_in(ProvinceDefinition const& location) const { + return !is_port || location.has_port(); +} BuildingTypeManager::BuildingTypeManager() : infrastructure_building_type { nullptr }, port_building_type { nullptr } {} @@ -158,8 +226,8 @@ bool BuildingTypeManager::load_buildings_file( this_building_type_effects.min_level, append_string_views(min_prefix, building_type.get_identifier()), FORMAT_x1_0DP_NEG ); - if (building_type.is_in_province()) { - if (building_type.is_port()) { + if (building_type.is_in_province) { + if (building_type.is_port) { if (port_building_type == nullptr) { port_building_type = &building_type; } else { @@ -169,7 +237,7 @@ bool BuildingTypeManager::load_buildings_file( ); ret = false; } - } else if (building_type.get_type() == "infrastructure") { + } else if (building_type.type == "infrastructure") { if (infrastructure_building_type == nullptr) { infrastructure_building_type = &building_type; } else { @@ -181,7 +249,7 @@ bool BuildingTypeManager::load_buildings_file( } } } else { - if (building_type.is_port()) { + if (building_type.is_port) { spdlog::error_s( "Building type {} is marked as a port, but is not a province building!", building_type ); diff --git a/src/openvic-simulation/economy/BuildingType.hpp b/src/openvic-simulation/economy/BuildingType.hpp index 30acb058..ed8ee36a 100644 --- a/src/openvic-simulation/economy/BuildingType.hpp +++ b/src/openvic-simulation/economy/BuildingType.hpp @@ -4,6 +4,7 @@ #include "openvic-simulation/modifier/Modifier.hpp" #include "openvic-simulation/economy/BuildingLevel.hpp" +#include "openvic-simulation/economy/BuildingRestrictionCategory.hpp" #include "openvic-simulation/types/Date.hpp" #include "openvic-simulation/types/HasIndex.hpp" #include "openvic-simulation/types/IdentifierRegistry.hpp" @@ -12,10 +13,15 @@ #include "openvic-simulation/utility/Containers.hpp" namespace OpenVic { + struct BuildingTypeManager; + struct CountryInstance; struct GoodDefinition; struct GoodDefinitionManager; + struct ModifierEffectCache; struct ProductionType; struct ProductionTypeManager; + struct ProvinceDefinition; + struct ProvinceInstance; /* REQUIREMENTS: * MAP-11, MAP-72, MAP-73 @@ -23,6 +29,7 @@ namespace OpenVic { * MAP-13, MAP-78, MAP-79 */ struct BuildingType : HasIndex, Modifier { + friend BuildingTypeManager; using naval_capacity_t = uint64_t; struct building_type_args_t { @@ -44,39 +51,47 @@ namespace OpenVic { }; private: - memory::string PROPERTY(type); - memory::string PROPERTY(on_completion); // probably sound played on completion - fixed_point_t PROPERTY(completion_size); - building_level_t PROPERTY(max_level); - fixed_point_map_t PROPERTY(goods_cost); - fixed_point_t PROPERTY(cost); - Timespan PROPERTY(build_time); // time - bool PROPERTY(on_map); // onmap + const memory::string type; + const bool is_in_province; + const bool is_port; + const bool capital; // only in naval base - bool PROPERTY_CUSTOM_PREFIX(default_enabled, is); - ProductionType const* PROPERTY(production_type); - bool PROPERTY(pop_build_factory); - bool PROPERTY(strategic_factory); - bool PROPERTY(advanced_factory); + const memory::string on_completion; //unknown - building_level_t PROPERTY(fort_level); // fort bonus step-per-level - naval_capacity_t PROPERTY(naval_capacity); + fixed_point_map_t PROPERTY(goods_cost); memory::vector SPAN_PROPERTY(colonial_points); - bool PROPERTY_CUSTOM_PREFIX(in_province, is); // province - bool PROPERTY(one_per_state); - fixed_point_t PROPERTY(colonial_range); - - fixed_point_t PROPERTY(infrastructure); - bool PROPERTY(spawn_railway_track); - - bool PROPERTY(sail); // only in clipper shipyard - bool PROPERTY(steam); // only in steamer shipyard - bool PROPERTY(capital); // only in naval base - bool PROPERTY_CUSTOM_PREFIX(port, is); // only in naval base - std::optional PROPERTY(province_building_index); public: + //general attributes + const std::optional province_building_index; + + const bool is_pop_build_factory; + const bool is_strategic_factory; + const bool is_advanced_factory; + const bool is_sail; // only in clipper shipyard + const bool is_steam; // only in steamer shipyard + + const bool should_display_on_map; + const bool should_spawn_railway_track; + + const bool is_limited_to_one_per_state; + const BuildingRestrictionCategory restriction_category; + const bool is_enabled_by_default; + const building_level_t max_level; + const fixed_point_t completion_size; + + //costs + const fixed_point_t cost; + const Timespan build_time; + + //effects + ProductionType const* const production_type; + const building_level_t fort_level; // fort bonus step-per-level + const naval_capacity_t naval_capacity; + const fixed_point_t colonial_range; + const fixed_point_t infrastructure; + BuildingType( index_t new_index, std::optional new_province_building_index, @@ -84,6 +99,14 @@ namespace OpenVic { building_type_args_t& building_type_args ); BuildingType(BuildingType&&) = default; + + [[nodiscard]] bool can_be_built_in( + ModifierEffectCache const& modifier_effect_cache, + const building_level_t desired_level, + CountryInstance const& actor, + ProvinceInstance const& location + ) const; + [[nodiscard]] bool can_be_built_in(ProvinceDefinition const& location) const; }; struct BuildingTypeManager { diff --git a/src/openvic-simulation/history/ProvinceHistory.cpp b/src/openvic-simulation/history/ProvinceHistory.cpp index a746671c..903119bc 100644 --- a/src/openvic-simulation/history/ProvinceHistory.cpp +++ b/src/openvic-simulation/history/ProvinceHistory.cpp @@ -2,6 +2,7 @@ #include +#include "openvic-simulation/core/error/ErrorMacros.hpp" #include "openvic-simulation/dataloader/NodeTools.hpp" #include "openvic-simulation/DefinitionManager.hpp" #include "openvic-simulation/economy/BuildingLevel.hpp" @@ -93,31 +94,41 @@ bool ProvinceHistoryMap::_load_history_entry( ast::NodeCPtr value ) -> bool { // used for province buildings like forts or railroads - BuildingType const* building_type = building_type_manager.get_building_type_by_identifier(key); - if (building_type != nullptr) { - if (building_type->is_in_province()) { - return expect_strong_typedef( - /* This is set to warn to prevent vanilla from always having errors because - * of a duplicate railroad entry in the 1861.1.1 history of Manchester (278). */ - [ - &entry, - optional_index = building_type->get_province_building_index() - ](const building_level_t level) -> bool { - if (!optional_index.has_value()) { - return false; - } - - entry.province_building_levels[optional_index.value()] = level; - return true; - } - )(value); - } else { - spdlog::error_s( + BuildingType const* const building_type_ptr = building_type_manager.get_building_type_by_identifier(key); + if (building_type_ptr != nullptr) { + BuildingType const& building_type = *building_type_ptr; + + OV_ERR_FAIL_COND_V_MSG( + !building_type.province_building_index.has_value(), + false, + memory::fmt::format( "Attempted to add state building \"{}\" at top scope of province history for {}", - *building_type, entry.province - ); - return false; - } + building_type, + entry.province + ) + ); + + OV_ERR_FAIL_COND_V_MSG( + !building_type.can_be_built_in(entry.province), + false, + memory::fmt::format( + "Building type {} cannot be built in province {}", + building_type, + entry.province + ) + ); + + return expect_strong_typedef( + /* This is set to warn to prevent vanilla from always having errors because + * of a duplicate railroad entry in the 1861.1.1 history of Manchester (278). */ + [ + &entry, + index = building_type.province_building_index.value() + ](const building_level_t level) -> bool { + entry.province_building_levels[index] = level; + return true; + } + )(value); } return _load_history_sub_entry_callback(definition_manager, entry.get_date(), value, key_map, key, value); @@ -179,26 +190,28 @@ bool ProvinceHistoryMap::_load_history_entry( return ret; }, "state_building", ZERO_OR_MORE, [&building_type_manager, &entry](ast::NodeCPtr node) -> bool { - BuildingType const* building_type = nullptr; + BuildingType const* building_type_ptr = nullptr; building_level_t level = building_level_t { 0 }; bool ret = expect_dictionary_keys( "level", ONE_EXACTLY, expect_strong_typedef(assign_variable_callback(level)), "building", ONE_EXACTLY, building_type_manager.expect_building_type_identifier( - assign_variable_callback_pointer(building_type) + assign_variable_callback_pointer(building_type_ptr) ), "upgrade", ZERO_OR_ONE, success_callback /* Doesn't appear to have an effect */ )(node); - if (building_type != nullptr) { - if (!building_type->is_in_province()) { - ret &= map_callback(entry.state_buildings, building_type->index, true)(level); - } else { - spdlog::error_s( + if (building_type_ptr != nullptr) { + OV_ERR_FAIL_COND_V_MSG( + building_type_ptr->province_building_index.has_value(), + false, + memory::fmt::format( "Attempted to add province building \"{}\" to state building list of province history for {}", - *building_type, entry.province - ); - ret = false; - } + *building_type_ptr, + entry.province + ) + ); + + ret &= map_callback(entry.state_buildings, building_type_ptr->index, true)(level); } return ret; } diff --git a/src/openvic-simulation/map/Mapmode.cpp b/src/openvic-simulation/map/Mapmode.cpp index 161a218e..d49ff029 100644 --- a/src/openvic-simulation/map/Mapmode.cpp +++ b/src/openvic-simulation/map/Mapmode.cpp @@ -230,7 +230,7 @@ bool MapmodeManager::setup_mapmodes(MapDefinition const& map_definition, Buildin if (infrastructure_building_type_ptr == nullptr) { spdlog::error_s("Cannot setup infrastructure mapmode because infrastructure_building_type is null."); ret = false; - } else if (!infrastructure_building_type_ptr->get_province_building_index().has_value()) { + } else if (!infrastructure_building_type_ptr->province_building_index.has_value()) { spdlog::error_s("Cannot setup infrastructure mapmode because infrastructure_building_type has no province_building_index."); ret = false; } else { @@ -241,9 +241,9 @@ bool MapmodeManager::setup_mapmodes(MapDefinition const& map_definition, Buildin CountryInstance const* player_country, ProvinceInstance const* selected_province ) -> Mapmode::base_stripe_t { BuildingType const& infrastructure_building_type = *infrastructure_building_type_ptr; - BuildingInstance const& infrastructure = province.get_buildings()[infrastructure_building_type.get_province_building_index().value()]; + BuildingInstance const& infrastructure = province.get_buildings()[infrastructure_building_type.province_building_index.value()]; const colour_argb_t::value_type val = colour_argb_t::colour_traits::component_from_fraction( - type_safe::get(infrastructure.get_level()), type_safe::get(infrastructure_building_type.get_max_level()) + 1, 0.5f, 1.0f + type_safe::get(infrastructure.get_level()), type_safe::get(infrastructure_building_type.max_level) + 1, 0.5f, 1.0f ); switch (infrastructure.get_expansion_state()) { case BuildingInstance::ExpansionState::CannotExpand: diff --git a/src/openvic-simulation/map/ProvinceInstance.cpp b/src/openvic-simulation/map/ProvinceInstance.cpp index abdf44a2..730cab7a 100644 --- a/src/openvic-simulation/map/ProvinceInstance.cpp +++ b/src/openvic-simulation/map/ProvinceInstance.cpp @@ -173,8 +173,12 @@ bool ProvinceInstance::remove_core(CountryInstance& core_to_remove, bool warn) { return true; } -bool ProvinceInstance::expand_building(const province_building_index_t index) { - return buildings[index].expand(); +bool ProvinceInstance::expand_building( + ModifierEffectCache const& modifier_effect_cache, + const province_building_index_t index, + CountryInstance& actor +) { + return buildings[index].expand(modifier_effect_cache, actor, *this); } void ProvinceInstance::_add_pop(Pop&& pop) { @@ -481,7 +485,14 @@ bool ProvinceInstance::apply_history_to_province(ProvinceHistoryEntry const& ent set_optional(life_rating, entry.get_life_rating()); set_optional(terrain_type, entry.get_terrain_type()); for (province_building_index_t i(0); i < entry.get_province_building_levels().size(); ++i) { - buildings[i].set_level(entry.get_province_building_levels()[i]); + const building_level_t level = entry.get_province_building_levels()[i]; + BuildingInstance& building = buildings[i]; + if (level > building_level_t(0) && !building.building_type.can_be_built_in(province_definition)) { + spdlog::error_s("Building type {} cannot be built in province {}", building.building_type, province_definition); + ret = false; + } else { + building.set_level(level); + } } // TODO: load state buildings - entry.get_state_buildings() // TODO: party loyalties for each POP when implemented on POP side - entry.get_party_loyalties() diff --git a/src/openvic-simulation/map/ProvinceInstance.hpp b/src/openvic-simulation/map/ProvinceInstance.hpp index 94cdde64..9fb949dc 100644 --- a/src/openvic-simulation/map/ProvinceInstance.hpp +++ b/src/openvic-simulation/map/ProvinceInstance.hpp @@ -24,7 +24,7 @@ namespace OpenVic { struct BaseIssue; - struct BuildingTypeManager; + struct BuildingType; struct CountryInstance; struct CountryInstanceManager; struct CountryParty; @@ -168,7 +168,14 @@ namespace OpenVic { return owner == nullptr; } - bool expand_building(const province_building_index_t index); + constexpr bool may_build_here() const { + return !is_occupied(); + } + bool expand_building( + ModifierEffectCache const& modifier_effect_cache, + const province_building_index_t index, + CountryInstance& actor + ); bool add_pop_vec( std::span pop_vec, diff --git a/src/openvic-simulation/misc/GameAction.cpp b/src/openvic-simulation/misc/GameAction.cpp index 4d2e8e1f..87dbca5b 100644 --- a/src/openvic-simulation/misc/GameAction.cpp +++ b/src/openvic-simulation/misc/GameAction.cpp @@ -45,7 +45,14 @@ bool GameActionManager::VariantVisitor::operator() (set_ai_argument_t const& arg // Production bool GameActionManager::VariantVisitor::operator() (expand_province_building_argument_t const& argument) const { - const auto [province_index, province_building_index] = argument; + const auto [country_index, province_index, province_building_index] = argument; + CountryInstance* country = instance_manager.get_country_instance_manager().get_country_instance_by_index(country_index); + + if (OV_unlikely(country == nullptr)) { + spdlog::error_s("GAME_ACTION_EXPAND_PROVINCE_BUILDING called with invalid country index: {}", country_index); + return false; + } + ProvinceInstance* province = instance_manager.get_map_instance().get_province_instance_by_index(province_index); if (OV_unlikely(province == nullptr)) { @@ -53,7 +60,11 @@ bool GameActionManager::VariantVisitor::operator() (expand_province_building_arg return false; } - return province->expand_building(province_building_index); + return province->expand_building( + instance_manager.definition_manager.get_modifier_manager().get_modifier_effect_cache(), + province_building_index, + *country + ); } // Budget diff --git a/src/openvic-simulation/misc/GameAction.hpp b/src/openvic-simulation/misc/GameAction.hpp index fc7f0978..bbd544a3 100644 --- a/src/openvic-simulation/misc/GameAction.hpp +++ b/src/openvic-simulation/misc/GameAction.hpp @@ -69,7 +69,7 @@ X(tick, std::monostate) \ X(set_pause, bool) \ X(set_speed, int64_t) \ X(set_ai, country_index_t, bool) \ -X(expand_province_building, province_index_t, province_building_index_t) \ +X(expand_province_building, country_index_t, province_index_t, province_building_index_t) \ X(set_strata_tax, country_index_t, strata_index_t, fixed_point_t) \ X(set_army_spending, country_index_t, fixed_point_t) \ X(set_navy_spending, country_index_t, fixed_point_t) \ diff --git a/src/openvic-simulation/politics/RuleSet.hpp b/src/openvic-simulation/politics/RuleSet.hpp index e7b71010..b27d50ea 100644 --- a/src/openvic-simulation/politics/RuleSet.hpp +++ b/src/openvic-simulation/politics/RuleSet.hpp @@ -218,7 +218,7 @@ namespace OpenVic { #undef DEF RESOLVE_DEFAULT_FALSE(is_slavery_legal, slavery_allowed) //economic - RESOLVE_DEFAULT_FALSE(may_build_infrastructure_domestically, build_railway) + RESOLVE_DEFAULT_FALSE(may_expand_infrastructure_domestically, build_railway) RESOLVE_DEFAULT_FALSE(may_build_factory_domestically, build_factory) RESOLVE_DEFAULT_FALSE(may_expand_factory_domestically, expand_factory) RESOLVE_DEFAULT_FALSE(may_open_factory_domestically, open_factory)