diff --git a/src/openvic-simulation/country/CountryInstance.cpp b/src/openvic-simulation/country/CountryInstance.cpp index f82f30d9..75ad9547 100644 --- a/src/openvic-simulation/country/CountryInstance.cpp +++ b/src/openvic-simulation/country/CountryInstance.cpp @@ -34,6 +34,7 @@ #include "openvic-simulation/politics/NationalValue.hpp" #include "openvic-simulation/politics/PartyPolicy.hpp" #include "openvic-simulation/politics/Reform.hpp" +#include "openvic-simulation/politics/RuleSet.hpp" #include "openvic-simulation/population/Culture.hpp" #include "openvic-simulation/population/Pop.hpp" #include "openvic-simulation/population/PopType.hpp" @@ -1681,23 +1682,23 @@ void CountryInstance::_update_military() { } bool CountryInstance::update_rule_set() { - rule_set.clear(); + rule_set = {}; CountryParty const* ruling_party_copy = ruling_party.get_untracked(); if (ruling_party_copy != nullptr) { for (PartyPolicy const* party_policy : ruling_party_copy->get_policies().get_values()) { if (party_policy != nullptr) { - rule_set |= party_policy->get_rules(); + rule_set.add_ruleset(party_policy->get_rules()); } } } for (Reform const* reform : reforms.get_values()) { if (reform != nullptr) { - rule_set |= reform->get_rules(); + rule_set.add_ruleset(reform->get_rules()); } } - return rule_set.trim_and_resolve_conflicts(true); + return true; } static constexpr Modifier const& get_country_status_static_effect( diff --git a/src/openvic-simulation/country/CountryInstance.hpp b/src/openvic-simulation/country/CountryInstance.hpp index 55749e0e..97393ca3 100644 --- a/src/openvic-simulation/country/CountryInstance.hpp +++ b/src/openvic-simulation/country/CountryInstance.hpp @@ -8,7 +8,7 @@ #include "openvic-simulation/military/CombatWidth.hpp" #include "openvic-simulation/military/UnitBranchedGetterMacro.hpp" #include "openvic-simulation/modifier/ModifierSum.hpp" -#include "openvic-simulation/politics/Rule.hpp" +#include "openvic-simulation/politics/RuleSet.hpp" #include "openvic-simulation/population/PopsAggregate.hpp" #include "openvic-simulation/types/ClampedValue.hpp" #include "openvic-simulation/types/Date.hpp" diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index 42ea17a0..4d7fd6d3 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -1054,12 +1054,6 @@ bool Dataloader::load_defines( ret = false; } definition_manager.get_modifier_manager().lock_base_country_modifier_effects(); - if (!definition_manager.get_politics_manager().get_rule_manager().setup_rules( - definition_manager.get_economy_manager().get_building_type_manager() - )) { - spdlog::critical_s("Failed to set up rules!"); - ret = false; - } if (!definition_manager.get_politics_manager().load_issues_file( definition_manager.get_modifier_manager(), parse_defines_cached(lookup_file(issues_file)).get_file_node() diff --git a/src/openvic-simulation/politics/BaseIssue.hpp b/src/openvic-simulation/politics/BaseIssue.hpp index d1d79ad0..60e89c44 100644 --- a/src/openvic-simulation/politics/BaseIssue.hpp +++ b/src/openvic-simulation/politics/BaseIssue.hpp @@ -1,7 +1,7 @@ #pragma once #include "openvic-simulation/modifier/Modifier.hpp" -#include "openvic-simulation/politics/Rule.hpp" +#include "openvic-simulation/politics/RuleSet.hpp" #include "openvic-simulation/types/HasIdentifier.hpp" namespace OpenVic { diff --git a/src/openvic-simulation/politics/IssueManager.cpp b/src/openvic-simulation/politics/IssueManager.cpp index f37ad54d..89aef34b 100644 --- a/src/openvic-simulation/politics/IssueManager.cpp +++ b/src/openvic-simulation/politics/IssueManager.cpp @@ -164,7 +164,7 @@ static constexpr colour_t create_issue_reform_colour(size_t index) { } bool IssueManager::_load_party_policy( - ModifierManager const& modifier_manager, RuleManager const& rule_manager, std::string_view identifier, + ModifierManager const& modifier_manager, std::string_view identifier, PartyPolicyGroup& party_policy_group, ast::NodeCPtr node ) { spdlog::scope scope { fmt::format("party policy {}", identifier) }; @@ -176,7 +176,7 @@ bool IssueManager::_load_party_policy( bool ret = NodeTools::expect_dictionary_keys_and_default( modifier_manager.expect_base_country_modifier(values), "is_jingoism", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_jingoism)), - "rules", ZERO_OR_ONE, rule_manager.expect_rule_set(move_variable_callback(rules)), + "rules", ZERO_OR_ONE, RuleSet::expect_rule_set(move_variable_callback(rules)), "war_exhaustion_effect", ZERO_OR_ONE, [](const ast::NodeCPtr _) -> bool { spdlog::warn_s("war_exhaustion_effect does nothing (vanilla issues have it)."); return true; @@ -209,7 +209,7 @@ bool IssueManager::_load_reform_group( } bool IssueManager::_load_reform( - ModifierManager const& modifier_manager, RuleManager const& rule_manager, size_t ordinal, std::string_view identifier, + ModifierManager const& modifier_manager, size_t ordinal, std::string_view identifier, ReformGroup& reform_group, ast::NodeCPtr node ) { spdlog::scope scope { fmt::format("reform {}", identifier) }; @@ -228,7 +228,7 @@ bool IssueManager::_load_reform( "administrative_multiplier", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(administrative_multiplier)), "technology_cost", ZERO_OR_ONE, expect_uint(assign_variable_callback(technology_cost)), "allow", ZERO_OR_MORE, allow.expect_script(), - "rules", ZERO_OR_ONE, rule_manager.expect_rule_set(move_variable_callback(rules)), + "rules", ZERO_OR_ONE, RuleSet::expect_rule_set(move_variable_callback(rules)), "on_execute", ZERO_OR_ONE, expect_dictionary_keys( "trigger", ZERO_OR_ONE, on_execute_trigger.expect_script(), "effect", ONE_EXACTLY, on_execute_effect.expect_script() @@ -254,7 +254,7 @@ bool IssueManager::_load_reform( * POL-113, POL-114, POL-115, POL-116 */ bool IssueManager::load_issues_file( - ModifierManager const& modifier_manager, RuleManager const& rule_manager, ast::NodeCPtr root + ModifierManager const& modifier_manager, ast::NodeCPtr root ) { spdlog::scope scope { "common/issues.txt" }; bool party_issues_found = false; @@ -337,7 +337,7 @@ bool IssueManager::load_issues_file( /* Load issues and reforms. */ ret &= expect_dictionary( - [this, &party_issues_found, &modifier_manager, &rule_manager]( + [this, &party_issues_found, &modifier_manager]( std::string_view type_key, ast::NodeCPtr type_value ) -> bool { if (type_key == "party_issues") { @@ -346,7 +346,7 @@ bool IssueManager::load_issues_file( } party_issues_found = true; - return expect_dictionary([this, &modifier_manager, &rule_manager]( + return expect_dictionary([this, &modifier_manager]( std::string_view group_key, ast::NodeCPtr group_value ) -> bool { PartyPolicyGroup* party_policy_group = party_policy_groups.get_item_by_identifier(group_key); @@ -356,14 +356,14 @@ bool IssueManager::load_issues_file( return false; } - return expect_dictionary([this, &modifier_manager, &rule_manager, party_policy_group]( + return expect_dictionary([this, &modifier_manager, party_policy_group]( std::string_view key, ast::NodeCPtr value ) -> bool { - return _load_party_policy(modifier_manager, rule_manager, key, *party_policy_group, value); + return _load_party_policy(modifier_manager, key, *party_policy_group, value); })(group_value); })(type_value); } else { - return expect_dictionary([this, &party_issues_found, &modifier_manager, &rule_manager]( + return expect_dictionary([this, &party_issues_found, &modifier_manager]( std::string_view group_key, ast::NodeCPtr group_value ) -> bool { ReformGroup* reform_group = reform_groups.get_item_by_identifier(group_key); @@ -375,14 +375,14 @@ bool IssueManager::load_issues_file( size_t ordinal = 0; - return expect_dictionary([this, &modifier_manager, &rule_manager, reform_group, &ordinal]( + return expect_dictionary([this, &modifier_manager, reform_group, &ordinal]( std::string_view key, ast::NodeCPtr value ) -> bool { if (key == "next_step_only" || key == "administrative") { return true; } - return _load_reform(modifier_manager, rule_manager, ordinal++, key, *reform_group, value); + return _load_reform(modifier_manager, ordinal++, key, *reform_group, value); })(group_value); })(type_value); } diff --git a/src/openvic-simulation/politics/IssueManager.hpp b/src/openvic-simulation/politics/IssueManager.hpp index 7754f6ff..ac326876 100644 --- a/src/openvic-simulation/politics/IssueManager.hpp +++ b/src/openvic-simulation/politics/IssueManager.hpp @@ -1,15 +1,14 @@ #pragma once +#include "openvic-simulation/politics/BaseIssue.hpp" #include "openvic-simulation/politics/PartyPolicy.hpp" #include "openvic-simulation/politics/Reform.hpp" #include "openvic-simulation/types/IdentifierRegistry.hpp" -#include "BaseIssue.hpp" namespace OpenVic { struct ConditionScript; struct EffectScript; struct ModifierManager; - struct RuleManager; struct IssueManager { private: @@ -21,14 +20,14 @@ namespace OpenVic { bool _load_party_policy_group(size_t& expected_party_policies, std::string_view identifier, ast::NodeCPtr node); bool _load_party_policy( - ModifierManager const& modifier_manager, RuleManager const& rule_manager, std::string_view identifier, + ModifierManager const& modifier_manager, std::string_view identifier, PartyPolicyGroup& party_policy_group, ast::NodeCPtr node ); bool _load_reform_group( size_t& expected_reforms, std::string_view identifier, ReformType& reform_type, ast::NodeCPtr node ); bool _load_reform( - ModifierManager const& modifier_manager, RuleManager const& rule_manager, size_t ordinal, + ModifierManager const& modifier_manager, size_t ordinal, std::string_view identifier, ReformGroup& reform_group, ast::NodeCPtr node ); @@ -84,7 +83,7 @@ namespace OpenVic { size_t ordinal, fixed_point_t administrative_multiplier, RuleSet&& rules, Reform::tech_cost_t technology_cost, ConditionScript&& allow, ConditionScript&& on_execute_trigger, EffectScript&& on_execute_effect ); - bool load_issues_file(ModifierManager const& modifier_manager, RuleManager const& rule_manager, ast::NodeCPtr root); + bool load_issues_file(ModifierManager const& modifier_manager, ast::NodeCPtr root); bool parse_scripts(DefinitionManager const& definition_manager); }; diff --git a/src/openvic-simulation/politics/PoliticsManager.hpp b/src/openvic-simulation/politics/PoliticsManager.hpp index d7adeab4..1c9b750b 100644 --- a/src/openvic-simulation/politics/PoliticsManager.hpp +++ b/src/openvic-simulation/politics/PoliticsManager.hpp @@ -6,14 +6,12 @@ #include "openvic-simulation/politics/NationalFocus.hpp" #include "openvic-simulation/politics/NationalValue.hpp" #include "openvic-simulation/politics/Rebel.hpp" -#include "openvic-simulation/politics/Rule.hpp" namespace OpenVic { struct PoliticsManager { private: GovernmentTypeManager PROPERTY_REF(government_type_manager); IdeologyManager PROPERTY_REF(ideology_manager); - RuleManager PROPERTY_REF(rule_manager); IssueManager PROPERTY_REF(issue_manager); NationalValueManager PROPERTY_REF(national_value_manager); NationalFocusManager PROPERTY_REF(national_focus_manager); @@ -35,7 +33,7 @@ namespace OpenVic { return rebel_manager.load_rebels_file(ideology_manager, government_type_manager, root); } inline bool load_issues_file(ModifierManager const& modifier_manager, ast::NodeCPtr root) { - return issue_manager.load_issues_file(modifier_manager, rule_manager, root); + return issue_manager.load_issues_file(modifier_manager, root); } }; } diff --git a/src/openvic-simulation/politics/Rule.cpp b/src/openvic-simulation/politics/Rule.cpp deleted file mode 100644 index 96429638..00000000 --- a/src/openvic-simulation/politics/Rule.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "Rule.hpp" - -#include "openvic-simulation/economy/BuildingType.hpp" -#include "openvic-simulation/utility/TslHelper.hpp" -#include "openvic-simulation/types/HasIdentifier.hpp" -#include "openvic-simulation/utility/Containers.hpp" -#include "openvic-simulation/core/FormatValidate.hpp" - -using namespace OpenVic; -using namespace OpenVic::NodeTools; - -static memory::string make_default_rule_localisation_key(std::string_view identifier) { - return "RULE_" + ascii_toupper(identifier); -} - -Rule::Rule(std::string_view new_identifier, rule_group_t new_group, index_t new_index, std::string_view new_localisation_key) - : HasIdentifier { new_identifier }, HasIndex { new_index }, group { new_group }, - localisation_key { - new_localisation_key.empty() ? make_default_rule_localisation_key(new_identifier) : new_localisation_key - } {} - -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->index < rule->index) { - primary_rule = rule; - } - ret = false; - } - } - } - if (log) { - for (auto const& [rule, value] : rule_map) { - if (value) { - if (rule != primary_rule) { - spdlog::error_s( - "Conflicting mutually exclusive rule: {} superseded by {} - removing!", - ovfmt::validate(rule), ovfmt::validate(primary_rule) - ); - } - } else { - spdlog::warn_s("Disabled mutually exclusive rule: {} - removing!", ovfmt::validate(rule)); - } - } - } - 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 { - size_t ret = 0; - for (auto const& [group, rule_map] : rule_groups) { - ret += rule_map.size(); - } - return ret; -} - -void RuleSet::clear() { - rule_groups.clear(); -} - -bool RuleSet::empty() const { - for (auto const& [group, rule_map] : rule_groups) { - if (!rule_map.empty()) { - return false; - } - } - - return true; -} - -RuleSet::rule_map_t const& RuleSet::get_rule_group(Rule::rule_group_t group, bool* rule_group_found) const { - const rule_group_map_t::const_iterator it = rule_groups.find(group); - if (it != rule_groups.end()) { - if (rule_group_found != nullptr) { - *rule_group_found = true; - } - return it->second; - } - if (rule_group_found != nullptr) { - *rule_group_found = false; - } - static const rule_map_t empty_map {}; - return empty_map; -} - -bool RuleSet::get_rule(Rule const& rule, bool* rule_found) const { - rule_map_t const& rule_map = get_rule_group(rule.group); - const rule_map_t::const_iterator it = rule_map.find(&rule); - if (it != rule_map.end()) { - if (rule_found != nullptr) { - *rule_found = true; - } - return it->second; - } - if (rule_found != nullptr) { - *rule_found = false; - } - return Rule::is_default_enabled(rule.group); -} - -bool RuleSet::has_rule(Rule const& rule) const { - return get_rule_group(rule.group).contains(&rule); -} - -bool RuleSet::set_rule(Rule const& rule, bool value) { - rule_groups[rule.group][&rule] = value; - return true; -} - -RuleSet& RuleSet::operator|=(RuleSet const& right) { - for (auto const& [group, rule_map] : right.rule_groups) { - for (auto const& [rule, value] : rule_map) { - rule_groups[group][rule] |= value; - } - } - return *this; -} - -RuleSet RuleSet::operator|(RuleSet const& right) const { - RuleSet copy = *this; - return copy |= right; -} - -bool RuleManager::add_rule(std::string_view identifier, Rule::rule_group_t group, std::string_view localisation_key) { - if (identifier.empty()) { - spdlog::error_s("Invalid rule identifier - empty!"); - return false; - } - return rules.emplace_item( - identifier, - identifier, group, Rule::index_t { rule_group_sizes[group]++ }, localisation_key - ); -} - -bool RuleManager::setup_rules(BuildingTypeManager const& building_type_manager) { - bool ret = true; - - using enum Rule::rule_group_t; - - static const ordered_map> hardcoded_rules { - { ECONOMY, { - "build_railway", "build_factory", "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", "pop_open_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 */ - } } - }; - - 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(); - } - reserve_more_rules(rule_count); - - for (auto const& [group, rule_list] : hardcoded_rules) { - for (std::string_view const& rule : rule_list) { - ret &= add_rule(rule, group); - } - } - - lock_rules(); - - return ret; -} - -node_callback_t RuleManager::expect_rule_set(callback_t ruleset_callback) const { - return [this, ruleset_callback](ast::NodeCPtr root) mutable -> bool { - RuleSet ruleset; - bool ret = expect_dictionary( - [this, &ruleset](std::string_view rule_key, ast::NodeCPtr rule_value) -> bool { - Rule const* rule = get_rule_by_identifier(rule_key); - if (rule != nullptr) { - return expect_bool( - [&ruleset, rule](bool value) -> bool { - /* Wrapped in a lambda function so that the rule group is only initialised - * if the value bool is successfully parsed. */ - return map_callback(ruleset.rule_groups[rule->group], rule)(value); - } - )(rule_value); - } else { - spdlog::error_s("Invalid rule identifier: {}", rule_key); - return false; - } - } - )(root); - ret &= ruleset.trim_and_resolve_conflicts(true); - ret &= ruleset_callback(std::move(ruleset)); - return ret; - }; -} - -namespace OpenVic { // so the compiler shuts up (copied from Modifier.cpp) - std::ostream& operator<<(std::ostream& stream, RuleSet const& value) { - for (auto const& [group, rule_map] : value.rule_groups) { - for (auto const& [rule, value] : rule_map) { - stream << rule << ": " << bool_to_yes_no(value) << "\n"; - } - } - return stream; - } -} diff --git a/src/openvic-simulation/politics/Rule.hpp b/src/openvic-simulation/politics/Rule.hpp deleted file mode 100644 index bc5f0ab7..00000000 --- a/src/openvic-simulation/politics/Rule.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include "openvic-simulation/types/HasIdentifier.hpp" -#include "openvic-simulation/types/HasIndex.hpp" -#include "openvic-simulation/types/IdentifierRegistry.hpp" -#include "openvic-simulation/types/OrderedContainers.hpp" -#include "openvic-simulation/types/TypedIndices.hpp" - -namespace OpenVic { - struct RuleManager; - struct BuildingTypeManager; - - /* The index of the Rule within its group, used to determine precedence in mutually exclusive rule groups. */ - struct Rule : HasIdentifier, HasIndex { - 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: - memory::string PROPERTY(localisation_key); - - public: - const rule_group_t group; - - Rule( - std::string_view new_identifier, rule_group_t new_group, index_t new_index, std::string_view new_localisation_key - ); - Rule(Rule&&) = default; - }; - - struct RuleSet { - friend struct RuleManager; - - using rule_map_t = ordered_map; - using rule_group_map_t = ordered_map; - - private: - rule_group_map_t PROPERTY(rule_groups); - - public: - RuleSet() {}; - 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; - void clear(); - bool empty() const; - - rule_map_t const& get_rule_group(Rule::rule_group_t group, bool* rule_group_found = nullptr) const; - bool get_rule(Rule const& rule, bool* rule_found = 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; - - friend std::ostream& operator<<(std::ostream& stream, RuleSet const& value); - }; - - struct RuleManager { - private: - IdentifierRegistry IDENTIFIER_REGISTRY(rule); - ordered_map rule_group_sizes; - - public: - bool add_rule(std::string_view identifier, Rule::rule_group_t group, std::string_view localisation_key = {}); - - bool setup_rules(BuildingTypeManager const& building_type_manager); - - NodeTools::node_callback_t expect_rule_set(NodeTools::callback_t ruleset_callback) const; - }; -} diff --git a/src/openvic-simulation/politics/RuleSet.hpp b/src/openvic-simulation/politics/RuleSet.hpp new file mode 100644 index 00000000..e7b71010 --- /dev/null +++ b/src/openvic-simulation/politics/RuleSet.hpp @@ -0,0 +1,256 @@ +#pragma once + +#include +#include + +#include "openvic-simulation/core/string/StringLiteral.hpp" +#include "openvic-simulation/core/string/Utility.hpp" +#include "openvic-simulation/core/Typedefs.hpp" +#include "openvic-simulation/dataloader/NodeTools.hpp" +#include "openvic-simulation/types/OptionalBool.hpp" +#include "openvic-simulation/types/OrderedContainers.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +namespace OpenVic { + //TODO replace with strategy pattern? + enum struct ApportionmentMethod : std::uint8_t { + largest_share, + dhont, + sainte_laque + }; + enum struct UpperHouseComposition : std::uint8_t { + same_as_ruling_party, + rich_only, + state_vote, + population_vote + }; + enum struct CulturalVotingRight : std::uint8_t { + primary_culture_voting, + culture_voting, + all_voting + }; + + struct RuleSet { + private: + struct RuleDefinition { + const std::string_view name; + OptionalBool const& value; + + template + static constexpr RuleDefinition Define(OptionalBool const& value) { + return RuleDefinition { Name, value }; + } + }; + + //in the order of Vic2 tooltip + #define DO_FOR_ALL_RULES_SEP(F, SEP) \ + F(build_factory) SEP \ + F(expand_factory) SEP \ + F(open_factory) SEP \ + F(destroy_factory) SEP \ + F(build_railway) SEP \ + F(factory_priority) SEP \ + F(can_subsidise) SEP \ + F(pop_build_factory) SEP \ + F(pop_expand_factory) SEP \ + F(pop_open_factory) SEP \ + F(delete_factory_if_no_input) SEP \ + F(build_factory_invest) SEP \ + F(expand_factory_invest) SEP \ + F(open_factory_invest) SEP \ + F(build_railway_invest) SEP \ + F(can_invest_in_pop_projects) SEP \ + F(pop_build_factory_invest) SEP \ + F(pop_expand_factory_invest) SEP \ + F(pop_open_factory_invest) SEP \ + F(allow_foreign_investment) SEP \ + F(primary_culture_voting) SEP \ + F(culture_voting) SEP \ + F(all_voting) SEP \ + F(largest_share) SEP \ + F(dhont) SEP \ + F(sainte_laque) SEP \ + F(rich_only) SEP \ + F(same_as_ruling_party) SEP \ + F(state_vote) SEP \ + F(population_vote) SEP \ + F(slavery_allowed) + + #define RULE_FIELDS(RULE) \ + OptionalBool RULE : 2 = OptionalBool::UNSPECIFIED; + + DO_FOR_ALL_RULES_SEP(RULE_FIELDS,) + #undef RULE_FIELDS + + template + EnumType select_with_priority(const RuleDefinition (&rules)[N]) const { + const std::string_view context_name = type_name(); + + // 1. Check for explicit TRUE flags (Highest Priority) + for (size_t i = 0; i < N; ++i) { + if (rules[i].value == OptionalBool::TRUE) { + // Log conflicts with lower-priority methods + for (size_t j = i + 1; j < N; ++j) { + if (OV_unlikely(rules[j].value == OptionalBool::TRUE)) { + spdlog::warn_s( + "Both {} and {} are enabled for {}. {} is picked.", + rules[i].name, + rules[j].name, + context_name, + rules[i].name + ); + } + } + return EnumType(i); + } + } + + // 2. Check for UNSPECIFIED (Defaulting logic) + for (size_t i = 0; i < N; ++i) { + if (rules[i].value == OptionalBool::UNSPECIFIED) { + spdlog::warn_s("No {} enabled. Picking {} (unspecified).", context_name, rules[i].name); + return EnumType(i); + } + } + + // 3. Absolute Fallback + spdlog::error_s("All {} options are disabled. Falling back to {}.", context_name, rules[0].name); + return EnumType(0); + } + + #define DEF(VALUE) RuleDefinition::Define<#VALUE>(VALUE) + + #define RESOLVE_DEFAULT_FALSE(METHOD_NAME, FIELD) \ + bool METHOD_NAME() const { \ + const bool is_true = FIELD == OptionalBool::TRUE; \ + if (FIELD == OptionalBool::UNSPECIFIED) { \ + spdlog::warn_s("{} is not specified, returning {}.", #FIELD, is_true); \ + } \ + return is_true; \ + } + + [[nodiscard]] OptionalBool merge_rule(OptionalBool current, OptionalBool other, std::string_view name) { + // If they are the same or 'other' provides no new info, stay as we are. + if (current == other || other == OptionalBool::UNSPECIFIED) { + return current; + } + + // If we were unspecified, take whatever 'other' has. + if (current == OptionalBool::UNSPECIFIED) { + return other; + } + + // Conflict: One is TRUE and the other is FALSE. + // Since 'other' isn't UNSPECIFIED and isn't the same as 'current', + // and we know 'current' isn't UNSPECIFIED, they must be TRUE/FALSE opposites. + spdlog::warn_s("{} is both false and true, true wins.", name); + return OptionalBool::TRUE; + } + + static memory::string make_rule_localisation_key(std::string_view identifier) { + return "RULE_" + ascii_toupper(identifier); + } + + public: + #define MAP_FROM_BOOL(RULE) \ + #RULE, ZERO_OR_ONE, NodeTools::expect_bool( \ + [&ruleset](const bool value) mutable -> bool { \ + const bool ret = ruleset.RULE == OptionalBool::UNSPECIFIED; \ + ruleset.RULE = value ? OptionalBool::TRUE : OptionalBool::FALSE; \ + return ret; \ + } \ + ) + #define COMMA , + + static NodeTools::node_callback_t expect_rule_set(NodeTools::callback_t ruleset_callback) { + return [ruleset_callback](ast::NodeCPtr root) mutable -> bool { + RuleSet ruleset; + using enum NodeTools::dictionary_entry_t::expected_count_t; + bool outer_result = NodeTools::expect_dictionary_keys( + DO_FOR_ALL_RULES_SEP(MAP_FROM_BOOL, COMMA) + )(root); + outer_result &= ruleset_callback(std::move(ruleset)); + return outer_result; + }; + } + + #undef COMMA + #undef MAP_FROM_BOOL + + #define ADD_IF_NOT_UNSPECIFIED(RULE) \ + if (RULE != OptionalBool::UNSPECIFIED) { \ + result.emplace(make_rule_localisation_key(#RULE), RULE == OptionalBool::TRUE); \ + } + + ordered_map get_localisation_keys_and_values() const { + ordered_map result {}; + DO_FOR_ALL_RULES_SEP(ADD_IF_NOT_UNSPECIFIED,) + return result; + } + #undef ADD_IF_NOT_UNSPECIFIED + + //political + ApportionmentMethod get_apportionment_method() const { + const RuleDefinition rules[] { + DEF(largest_share), + DEF(dhont), + DEF(sainte_laque) + }; + return select_with_priority(rules); + } + UpperHouseComposition get_upper_house_composition() const { + const RuleDefinition rules[] { + DEF(same_as_ruling_party), + DEF(rich_only), + DEF(state_vote), + DEF(population_vote) + }; + return select_with_priority(rules); + } + CulturalVotingRight get_cultural_voting_rights() const { + const RuleDefinition rules[] { + DEF(primary_culture_voting), + DEF(culture_voting), + DEF(all_voting) + }; + return select_with_priority(rules); + } + #undef DEF + RESOLVE_DEFAULT_FALSE(is_slavery_legal, slavery_allowed) + //economic + RESOLVE_DEFAULT_FALSE(may_build_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) + bool may_close_factory_domestically() const { + return may_open_factory_domestically(); + } + RESOLVE_DEFAULT_FALSE(may_destroy_factory_domestically, destroy_factory) + RESOLVE_DEFAULT_FALSE(may_subsidise_factory_domestically, can_subsidise) + RESOLVE_DEFAULT_FALSE(may_set_factory_priority_domestically, factory_priority) + + RESOLVE_DEFAULT_FALSE(pop_may_build_factory_domestically, pop_build_factory) + RESOLVE_DEFAULT_FALSE(pop_may_expand_factory_domestically, pop_expand_factory) + RESOLVE_DEFAULT_FALSE(pop_may_open_factory_domestically, pop_open_factory) + RESOLVE_DEFAULT_FALSE(may_automatically_delete_factory_if_no_input_domestically, delete_factory_if_no_input) + RESOLVE_DEFAULT_FALSE(may_invest_in_pop_projects_domestically, can_invest_in_pop_projects) + + RESOLVE_DEFAULT_FALSE(may_invest_in_building_factory_abroad, build_factory_invest) + RESOLVE_DEFAULT_FALSE(may_invest_in_expanding_factory_abroad, expand_factory_invest) + RESOLVE_DEFAULT_FALSE(may_invest_in_opening_factory_abroad, open_factory_invest) + RESOLVE_DEFAULT_FALSE(pop_may_invest_in_building_factory_abroad, pop_build_factory_invest) + RESOLVE_DEFAULT_FALSE(pop_may_invest_in_expanding_factory_abroad, pop_expand_factory_invest) + RESOLVE_DEFAULT_FALSE(pop_may_invest_in_opening_factory_abroad, pop_open_factory_invest) + RESOLVE_DEFAULT_FALSE(may_invest_in_expanding_infrastructure_abroad, build_railway_invest) + + RESOLVE_DEFAULT_FALSE(foreigners_may_invest, allow_foreign_investment) + #undef RESOLVE_DEFAULT_FALSE + + #define ASSIGN_MERGED_RULE(RULE) RULE = merge_rule(RULE, other.RULE, #RULE); + void add_ruleset(RuleSet const& other) { + DO_FOR_ALL_RULES_SEP(ASSIGN_MERGED_RULE,) + } + #undef ASSIGN_MERGED_RULE + #undef DO_FOR_ALL_RULES + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/types/OptionalBool.hpp b/src/openvic-simulation/types/OptionalBool.hpp new file mode 100644 index 00000000..4770eeba --- /dev/null +++ b/src/openvic-simulation/types/OptionalBool.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace OpenVic { + enum struct OptionalBool : std::uint8_t { + UNSPECIFIED = 0, + TRUE = 1, + FALSE = 2 + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/types/TypedIndices.hpp b/src/openvic-simulation/types/TypedIndices.hpp index d8a8487d..a17451ed 100644 --- a/src/openvic-simulation/types/TypedIndices.hpp +++ b/src/openvic-simulation/types/TypedIndices.hpp @@ -46,7 +46,6 @@ TYPED_INDEX(rebel_type_index_t) TYPED_INDEX(reform_index_t) TYPED_INDEX(reform_group_index_t) TYPED_INDEX(regiment_type_index_t) -TYPED_INDEX(rule_index_t) TYPED_INDEX(ship_type_index_t) TYPED_INDEX(strata_index_t) TYPED_INDEX(technology_index_t)