From e067471f55cd11c6cd86920f9486991d6b552726 Mon Sep 17 00:00:00 2001 From: hop311 Date: Wed, 3 Jan 2024 00:13:39 +0000 Subject: Mutually exclusive rule groups + modded building rules --- src/openvic-simulation/dataloader/Dataloader.cpp | 36 ++-- src/openvic-simulation/economy/BuildingType.cpp | 2 + src/openvic-simulation/economy/BuildingType.hpp | 1 + src/openvic-simulation/economy/Good.cpp | 23 ++- src/openvic-simulation/map/Map.cpp | 2 +- src/openvic-simulation/misc/Modifier.cpp | 8 +- src/openvic-simulation/politics/Issue.cpp | 20 ++- src/openvic-simulation/politics/Rule.cpp | 201 ++++++++++++++++------- src/openvic-simulation/politics/Rule.hpp | 43 ++++- 9 files changed, 237 insertions(+), 99 deletions(-) diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index a09b999..26f3c4c 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -780,10 +780,6 @@ bool Dataloader::load_defines(GameManager& game_manager) { Logger::error("Failed to set up modifier effects!"); ret = false; } - if (!game_manager.get_politics_manager().get_rule_manager().setup_rules()) { - Logger::error("Failed to set up rules!"); - ret = false; - } if (!game_manager.get_define_manager().load_defines_file(parse_lua_defines(lookup_file(defines_file)).get_file_node())) { Logger::error("Failed to load defines!"); ret = false; @@ -830,19 +826,6 @@ bool Dataloader::load_defines(GameManager& game_manager) { Logger::error("Failed to load pop types!"); ret = false; } - if (!game_manager.get_politics_manager().load_issues_file( - game_manager.get_modifier_manager(), - parse_defines_cached(lookup_file(issues_file)).get_file_node() - )) { - Logger::error("Failed to load issues and reforms!"); - ret = false; - } - if (!game_manager.get_pop_manager().load_delayed_parse_pop_type_data( - game_manager.get_politics_manager().get_issue_manager() - )) { - Logger::error("Failed to load delayed parse pop type data (promotion and issue weights)!"); - ret = false; - } if (!game_manager.get_economy_manager().load_production_types_file(game_manager.get_pop_manager(), parse_defines_cached(lookup_file(production_types_file)).get_file_node() )) { @@ -862,6 +845,25 @@ bool Dataloader::load_defines(GameManager& game_manager) { if (!_load_technologies(game_manager)) { ret = false; } + if (!game_manager.get_politics_manager().get_rule_manager().setup_rules( + game_manager.get_economy_manager().get_building_type_manager() + )) { + Logger::error("Failed to set up rules!"); + ret = false; + } + if (!game_manager.get_politics_manager().load_issues_file( + game_manager.get_modifier_manager(), + parse_defines_cached(lookup_file(issues_file)).get_file_node() + )) { + Logger::error("Failed to load issues and reforms!"); + ret = false; + } + if (!game_manager.get_pop_manager().load_delayed_parse_pop_type_data( + game_manager.get_politics_manager().get_issue_manager() + )) { + Logger::error("Failed to load delayed parse pop type data (promotion and issue weights)!"); + ret = false; + } if (!game_manager.get_politics_manager().load_national_foci_file( game_manager.get_pop_manager(), game_manager.get_economy_manager().get_good_manager(), game_manager.get_modifier_manager(), parse_defines_cached(lookup_file(national_foci_file)).get_file_node() diff --git a/src/openvic-simulation/economy/BuildingType.cpp b/src/openvic-simulation/economy/BuildingType.cpp index c11933f..c9ed410 100644 --- a/src/openvic-simulation/economy/BuildingType.cpp +++ b/src/openvic-simulation/economy/BuildingType.cpp @@ -22,6 +22,8 @@ bool BuildingTypeManager::add_building_type(std::string_view identifier, ARGS) { return false; } + building_type_types.emplace(type); + return building_types.add_item({ identifier, type, std::move(modifier), on_completion, completion_size, max_level, std::move(goods_cost), cost, build_time, visibility, on_map, default_enabled, production_type, pop_build_factory, strategic_factory, diff --git a/src/openvic-simulation/economy/BuildingType.hpp b/src/openvic-simulation/economy/BuildingType.hpp index b1eb6f8..d751ff1 100644 --- a/src/openvic-simulation/economy/BuildingType.hpp +++ b/src/openvic-simulation/economy/BuildingType.hpp @@ -74,6 +74,7 @@ namespace OpenVic { private: IdentifierRegistry IDENTIFIER_REGISTRY(building_type); + string_set_t PROPERTY(building_type_types); std::vector PROPERTY(province_building_types); BuildingType const* PROPERTY(port_building_type); diff --git a/src/openvic-simulation/economy/Good.cpp b/src/openvic-simulation/economy/Good.cpp index 3b420f1..2170666 100644 --- a/src/openvic-simulation/economy/Good.cpp +++ b/src/openvic-simulation/economy/Good.cpp @@ -93,25 +93,24 @@ bool GoodManager::load_goods_file(ast::NodeCPtr root) { bool GoodManager::generate_modifiers(ModifierManager& modifier_manager) const { bool ret = true; - const auto good_modifier = [this, &modifier_manager, &ret](std::string_view name) -> void { + const auto good_modifier = [this, &modifier_manager, &ret](std::string_view name, bool positive_good) -> void { ret &= modifier_manager.register_complex_modifier(name); for (Good const& good : get_goods()) { ret &= modifier_manager.add_modifier_effect( - StringUtils::append_string_views(name, "_", good.get_identifier()), true + StringUtils::append_string_views(name, "_", good.get_identifier()), positive_good ); } }; - good_modifier("artisan_goods_input"); - good_modifier("artisan_goods_output"); - good_modifier("artisan_goods_throughput"); - good_modifier("factory_goods_input"); - good_modifier("factory_goods_output"); - good_modifier("factory_goods_throughput"); - good_modifier("rgo_goods_input"); - good_modifier("rgo_goods_output"); - good_modifier("rgo_goods_throughput"); - good_modifier("rgo_size"); + good_modifier("artisan_goods_input", false); + good_modifier("artisan_goods_output", true); + good_modifier("artisan_goods_throughput", true); + good_modifier("factory_goods_input", false); + good_modifier("factory_goods_output", true); + good_modifier("factory_goods_throughput", true); + good_modifier("rgo_goods_output", true); + good_modifier("rgo_goods_throughput", true); + good_modifier("rgo_size", true); return ret; } diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index 95f4991..a8cc4a0 100644 --- a/src/openvic-simulation/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -845,7 +845,7 @@ bool Map::load_climate_file(ModifierManager const& modifier_manager, ast::NodeCP } else { Logger::warning( "Province with id ", province.get_identifier(), - "defined twice in climate ", identifier + " defined twice in climate ", identifier ); } return true; diff --git a/src/openvic-simulation/misc/Modifier.cpp b/src/openvic-simulation/misc/Modifier.cpp index bbc8f59..ca4c950 100644 --- a/src/openvic-simulation/misc/Modifier.cpp +++ b/src/openvic-simulation/misc/Modifier.cpp @@ -111,6 +111,9 @@ bool ModifierManager::setup_modifier_effects() { /* Country Modifier Effects */ ret &= add_modifier_effect("administrative_efficiency", true); ret &= add_modifier_effect("administrative_efficiency_modifier", true); + ret &= add_modifier_effect("artisan_input", false); + ret &= add_modifier_effect("artisan_output", true); + ret &= add_modifier_effect("artisan_throughput", true); ret &= add_modifier_effect("badboy", false, RAW_DECIMAL); ret &= add_modifier_effect("cb_creation_speed", true); //seemingly works the same way as cb_generation_speed_modifier ret &= add_modifier_effect("cb_generation_speed_modifier", true); @@ -127,6 +130,7 @@ bool ModifierManager::setup_modifier_effects() { ret &= add_modifier_effect("education_efficiency_modifier", true); ret &= add_modifier_effect("factory_cost", false); ret &= add_modifier_effect("factory_input", false); + ret &= add_modifier_effect("factory_maintenance", false); ret &= add_modifier_effect("factory_output", true); ret &= add_modifier_effect("factory_owner_cost", false); ret &= add_modifier_effect("factory_throughput", true); @@ -226,10 +230,10 @@ bool ModifierManager::setup_modifier_effects() { ret &= add_modifier_effect("immigrant_attract", true); ret &= add_modifier_effect("immigrant_push", false); ret &= add_modifier_effect("life_rating", true); - ret &= add_modifier_effect("local_artisan_input", true); + ret &= add_modifier_effect("local_artisan_input", false); ret &= add_modifier_effect("local_artisan_output", true); ret &= add_modifier_effect("local_artisan_throughput", true); - ret &= add_modifier_effect("local_factory_input", true); + ret &= add_modifier_effect("local_factory_input", false); ret &= add_modifier_effect("local_factory_output", true); ret &= add_modifier_effect("local_factory_throughput", true); ret &= add_modifier_effect("local_repair", true); diff --git a/src/openvic-simulation/politics/Issue.cpp b/src/openvic-simulation/politics/Issue.cpp index 3e64af4..28e34cb 100644 --- a/src/openvic-simulation/politics/Issue.cpp +++ b/src/openvic-simulation/politics/Issue.cpp @@ -95,8 +95,19 @@ bool IssueManager::add_reform( } if (group->get_type().is_uncivilised()) { - if (technology_cost <= 0) { - Logger::warning("Non-positive technology cost ", technology_cost, " found in uncivilised reform ", identifier, "!"); + if (ordinal == 0) { + if (technology_cost != 0) { + Logger::warning( + "Non-zero technology cost ", technology_cost, " found in ordinal 0 uncivilised reform ", identifier, "!" + ); + } + } else { + if (technology_cost <= 0) { + Logger::warning( + "Non-positive technology cost ", technology_cost, " found in ordinal ", ordinal, + " uncivilised reform ", identifier, "!" + ); + } } } else if (technology_cost != 0) { Logger::warning("Non-zero technology cost ", technology_cost, " found in civilised reform ", identifier, "!"); @@ -184,8 +195,11 @@ bool IssueManager::load_issues_file(ModifierManager const& modifier_manager, Rul if (key == "party_issues") { return expect_length(add_variable_callback(expected_issue_groups))(value); } else { + static const string_set_t uncivilised_reform_groups { + "economic_reforms", "education_reforms", "military_reforms" + }; return expect_length(add_variable_callback(expected_reform_groups))(value) - & add_reform_type(key, key == "economic_reforms" || key == "education_reforms" || key == "military_reforms"); + & add_reform_type(key, uncivilised_reform_groups.contains(key)); } } )(root); diff --git a/src/openvic-simulation/politics/Rule.cpp b/src/openvic-simulation/politics/Rule.cpp index 12db4f4..9ac992a 100644 --- a/src/openvic-simulation/politics/Rule.cpp +++ b/src/openvic-simulation/politics/Rule.cpp @@ -1,19 +1,90 @@ #include "Rule.hpp" +#include "openvic-simulation/economy/BuildingType.hpp" +#include "openvic-simulation/utility/TslHelper.hpp" + using namespace OpenVic; using namespace OpenVic::NodeTools; -Rule::Rule(std::string_view new_identifier) : HasIdentifier { new_identifier } {} +Rule::Rule(std::string_view new_identifier, rule_group_t new_group, size_t new_index) + : HasIdentifier { new_identifier }, group { new_group }, index { new_index } {} -RuleSet::RuleSet(rule_map_t&& new_rules) : rules { std::move(new_rules) } {} +RuleSet::RuleSet(rule_group_map_t&& new_rule_groups) : rule_groups { std::move(new_rule_groups) } {} + +bool RuleSet::trim_and_resolve_conflicts(bool log) { + bool ret = true; + for (auto [group, rule_map] : mutable_iterator(rule_groups)) { + if (Rule::is_mutually_exclusive_group(group)) { + Rule const* primary_rule = nullptr; + for (auto const& [rule, value] : rule_map) { + if (value) { + if (primary_rule == nullptr) { + primary_rule = rule; + } else { + if (primary_rule->get_index() < rule->get_index()) { + primary_rule = rule; + } + ret = false; + } + } + } + if (log) { + for (auto const& [rule, value] : rule_map) { + if (value) { + if (rule != primary_rule) { + Logger::error( + "Conflicting mutually exclusive rule: ", rule, " superceeded by ", primary_rule, " - removing!" + ); + } + } else { + Logger::warning("Disabled mutually exclusive rule: ", rule, " - removing!"); + } + } + } + rule_map.clear(); + if (primary_rule != nullptr) { + rule_map.emplace(primary_rule, true); + } + } + } + return ret; +} + +size_t RuleSet::get_rule_group_count() const { + return rule_groups.size(); +} size_t RuleSet::get_rule_count() const { - return rules.size(); + size_t ret = 0; + for (auto const& [group, rule_map] : rule_groups) { + ret += rule_map.size(); + } + return ret; +} + +RuleSet::rule_map_t const& RuleSet::get_rule_group(Rule::rule_group_t group, bool* successful) const { + const rule_group_map_t::const_iterator it = rule_groups.find(group); + if (it != rule_groups.end()) { + if (successful != nullptr) { + *successful = true; + } + return it->second; + } + if (successful != nullptr) { + *successful = false; + } + static const rule_map_t empty_map {}; + return empty_map; } -bool RuleSet::get_rule(Rule const* rule, bool* successful) { - const rule_map_t::const_iterator it = rules.find(rule); - if (it != rules.end()) { +bool RuleSet::get_rule(Rule const* rule, bool* successful) const { + if (rule == nullptr) { + Logger::error("Invalid rule - null!"); + return false; + } + rule_map_t const& rule_map = get_rule_group(rule->get_group()); + const rule_map_t::const_iterator it = rule_map.find(rule); + if (it != rule_map.end()) { if (successful != nullptr) { *successful = true; } @@ -22,16 +93,31 @@ bool RuleSet::get_rule(Rule const* rule, bool* successful) { if (successful != nullptr) { *successful = false; } - return false; + return Rule::is_default_enabled(rule->get_group()); } bool RuleSet::has_rule(Rule const* rule) const { - return rules.contains(rule); + if (rule == nullptr) { + Logger::error("Invalid rule - null!"); + return false; + } + return get_rule_group(rule->get_group()).contains(rule); +} + +bool RuleSet::set_rule(Rule const* rule, bool value) { + if (rule == nullptr) { + Logger::error("Invalid rule - null!"); + return false; + } + rule_map_t& rule_map = rule_groups[rule->get_group()]; + return rule_groups[rule->get_group()].emplace(rule, value).second; } RuleSet& RuleSet::operator|=(RuleSet const& right) { - for (rule_map_t::value_type const& value : right.rules) { - rules[value.first] |= value.second; + for (auto const& [group, rule_map] : right.rule_groups) { + for (auto const& [rule, value] : rule_map) { + rule_groups[group][rule] |= value; + } } return *this; } @@ -41,61 +127,57 @@ RuleSet RuleSet::operator|(RuleSet const& right) const { return ret |= right; } -bool RuleManager::add_rule(std::string_view identifier) { +bool RuleManager::add_rule(std::string_view identifier, Rule::rule_group_t group) { if (identifier.empty()) { Logger::error("Invalid rule identifier - empty!"); return false; } - return rules.add_item({ identifier }); + return rules.add_item({ identifier, group, rule_group_sizes[group]++ }); } -bool RuleManager::setup_rules() { +bool RuleManager::setup_rules(BuildingTypeManager const& building_type_manager) { bool ret = true; - /* Economic Rules */ - ret &= add_rule("build_factory"); - ret &= add_rule("expand_factory"); - ret &= add_rule("open_factory"); - ret &= add_rule("destroy_factory"); - - ret &= add_rule("pop_build_factory"); - ret &= add_rule("pop_expand_factory"); - ret &= add_rule("pop_open_factory"); - - ret &= add_rule("build_railway"); - ret &= add_rule("can_subsidise"); - ret &= add_rule("factory_priority"); - ret &= add_rule("delete_factory_if_no_input"); - - ret &= add_rule("build_factory_invest"); - ret &= add_rule("expand_factory_invest"); - ret &= add_rule("open_factory_invest"); - ret &= add_rule("build_railway_invest"); - - ret &= add_rule("pop_build_factory_invest"); - ret &= add_rule("pop_expand_factory_invest"); - - ret &= add_rule("can_invest_in_pop_projects"); - ret &= add_rule("allow_foreign_investment"); - - /* Citizenship Rules */ - ret &= add_rule("primary_culture_voting"); - ret &= add_rule("culture_voting"); - ret &= add_rule("all_voting"); + using enum Rule::rule_group_t; + + static const ordered_map> hardcoded_rules { + { ECONOMY, { + "expand_factory", "open_factory", "destroy_factory", "pop_build_factory", "pop_expand_factory", "pop_open_factory", + "can_subsidise", "factory_priority", "delete_factory_if_no_input", "build_factory_invest", "expand_factory_invest", + "open_factory_invest", "build_railway_invest", "pop_build_factory_invest", "pop_expand_factory_invest", + "can_invest_in_pop_projects", "allow_foreign_investment" + } }, + { CITIZENSHIP, { "primary_culture_voting", "culture_voting", "all_voting" } }, + { SLAVERY, { "slavery_allowed" } }, + { UPPER_HOUSE, { "same_as_ruling_party", "rich_only", "state_vote", "population_vote" } }, + { VOTING, { + "largest_share" /* First Past the Post */, + "dhont" /* Jefferson Method */, + "sainte_laque" /* Proportional Representation */ + } } + }; - /* Slavery Rule */ - ret &= add_rule("slavery_allowed"); + size_t rule_count = building_type_manager.get_building_type_types().size(); + for (auto const& [group, rule_list] : hardcoded_rules) { + rule_count += rule_list.size(); + } + rules.reserve(rule_count); - /* Upper House Composition Rules */ - ret &= add_rule("same_as_ruling_party"); - ret &= add_rule("rich_only"); - ret &= add_rule("state_vote"); - ret &= add_rule("population_vote"); + for (auto const& [group, rule_list] : hardcoded_rules) { + for (std::string_view const& rule : rule_list) { + ret &= add_rule(rule, group); + } + } - /* Voting System Rules */ - ret &= add_rule("largest_share"); // First Past the Post - ret &= add_rule("dhont"); // Jefferson Method - ret &= add_rule("sainte_laque"); // Proportional Representation + for (std::string const& type : building_type_manager.get_building_type_types()) { + std::string build_rule_string = "build_"; + if (type != "infrastructure") { + build_rule_string += type; + } else { + build_rule_string += "railway"; + } + ret &= add_rule(build_rule_string, ECONOMY); + } lock_rules(); @@ -111,8 +193,8 @@ node_callback_t RuleManager::expect_rule_set(callback_t ruleset_callb if (rule != nullptr) { return expect_bool( [&ruleset, rule](bool value) -> bool { - if (!ruleset.rules.emplace(rule, value).second) { - Logger::warning("Duplicate rule entry: ", rule, " - overwriting existing value!"); + if (!ruleset.rule_groups[rule->get_group()].emplace(rule, value).second) { + Logger::warning("Duplicate rule entry: ", rule, " - ignoring!"); } return true; } @@ -123,6 +205,7 @@ node_callback_t RuleManager::expect_rule_set(callback_t ruleset_callb } } )(root); + ret &= ruleset.trim_and_resolve_conflicts(true); ret &= ruleset_callback(std::move(ruleset)); return ret; }; @@ -130,8 +213,10 @@ node_callback_t RuleManager::expect_rule_set(callback_t ruleset_callb namespace OpenVic { // so the compiler shuts up (copied from Modifier.cpp) std::ostream& operator<<(std::ostream& stream, RuleSet const& value) { - for (RuleSet::rule_map_t::value_type const& rule : value.rules) { - stream << rule.first << ": " << (rule.second ? "yes" : "no") << "\n"; + for (auto const& [group, rule_map] : value.rule_groups) { + for (auto const& [rule, value] : rule_map) { + stream << rule << ": " << (value ? "yes" : "no") << "\n"; + } } return stream; } diff --git a/src/openvic-simulation/politics/Rule.hpp b/src/openvic-simulation/politics/Rule.hpp index 4b47abf..70a59e8 100644 --- a/src/openvic-simulation/politics/Rule.hpp +++ b/src/openvic-simulation/politics/Rule.hpp @@ -5,12 +5,32 @@ namespace OpenVic { struct RuleManager; + struct BuildingTypeManager; struct Rule : HasIdentifier { friend struct RuleManager; + enum class rule_group_t : uint8_t { + ECONOMY, CITIZENSHIP, SLAVERY, UPPER_HOUSE, VOTING + }; + + static constexpr bool is_mutually_exclusive_group(rule_group_t group) { + using enum rule_group_t; + return group == CITIZENSHIP || group == UPPER_HOUSE || group == VOTING; + } + + /* Mutually exclusive groups must be default disabled! */ + static constexpr bool is_default_enabled(rule_group_t group) { + using enum rule_group_t; + return !is_mutually_exclusive_group(group) && group != SLAVERY; + } + private: - Rule(std::string_view new_identifier); + const rule_group_t PROPERTY(group); + /* The index of the Rule within its group, used to determine precedence in mutually exclusive rule groups. */ + const size_t PROPERTY(index); + + Rule(std::string_view new_identifier, rule_group_t new_group, size_t new_index); public: Rule(Rule&&) = default; @@ -20,24 +40,34 @@ namespace OpenVic { friend struct RuleManager; using rule_map_t = ordered_map; + using rule_group_map_t = ordered_map; private: - rule_map_t rules; + rule_group_map_t rule_groups; public: RuleSet() = default; - RuleSet(rule_map_t&& new_rules); + RuleSet(rule_group_map_t&& new_rule_groups); RuleSet(RuleSet const&) = default; RuleSet(RuleSet&&) = default; RuleSet& operator=(RuleSet const&) = default; RuleSet& operator=(RuleSet&&) = default; + /* Removes conflicting and disabled mutually exclusive rules. If log is true, a warning will be emitted for each + * removed disabled rule and an error will be emitted for each removed conflicting rule. Returns true if no conflicts + * are found (regardless of whether disabled rules are removed or not), false otherwise. */ + bool trim_and_resolve_conflicts(bool log); + size_t get_rule_group_count() const; size_t get_rule_count() const; - bool get_rule(Rule const* rule, bool* successful = nullptr); + rule_map_t const& get_rule_group(Rule::rule_group_t group, bool* successful = nullptr) const; + bool get_rule(Rule const* rule, bool* successful = nullptr) const; bool has_rule(Rule const* rule) const; + /* Sets the rule to the specified value. Returns false if there was an existing rule, regardless of its value. */ + bool set_rule(Rule const* rule, bool value); + RuleSet& operator|=(RuleSet const& right); RuleSet operator|(RuleSet const& right) const; @@ -47,11 +77,12 @@ namespace OpenVic { struct RuleManager { private: IdentifierRegistry IDENTIFIER_REGISTRY(rule); + ordered_map rule_group_sizes; public: - bool add_rule(std::string_view identifier); + bool add_rule(std::string_view identifier, Rule::rule_group_t group); - bool setup_rules(); + bool setup_rules(BuildingTypeManager const& building_type_manager); NodeTools::node_callback_t expect_rule_set(NodeTools::callback_t ruleset_callback) const; }; -- cgit v1.2.3-56-ga3b1