aboutsummaryrefslogtreecommitdiff
path: root/src/openvic-simulation/modifier
diff options
context:
space:
mode:
author Hop311 <Hop3114@gmail.com>2024-09-20 14:18:59 +0200
committer GitHub <noreply@github.com>2024-09-20 14:18:59 +0200
commit85da7fb84197d617d1b3cde6969bf7bcfbb93d1d (patch)
tree0f103095b45de46688206d0e33e24f0331afbabd /src/openvic-simulation/modifier
parent7fe8ba2b3bd3bafad374a691280bbf5102b58678 (diff)
parent3703851ced696a2d4559c3e3340c537982d5ebc7 (diff)
Merge pull request #203 from OpenVicProject/modifier-sumHEADmaster
Add ModifierSum and improve ModifierValue
Diffstat (limited to 'src/openvic-simulation/modifier')
-rw-r--r--src/openvic-simulation/modifier/Modifier.cpp640
-rw-r--r--src/openvic-simulation/modifier/Modifier.hpp225
-rw-r--r--src/openvic-simulation/modifier/ModifierSum.cpp58
-rw-r--r--src/openvic-simulation/modifier/ModifierSum.hpp30
4 files changed, 953 insertions, 0 deletions
diff --git a/src/openvic-simulation/modifier/Modifier.cpp b/src/openvic-simulation/modifier/Modifier.cpp
new file mode 100644
index 0000000..28aa777
--- /dev/null
+++ b/src/openvic-simulation/modifier/Modifier.cpp
@@ -0,0 +1,640 @@
+#include "Modifier.hpp"
+
+#include <string>
+
+#include <openvic-dataloader/v2script/AbstractSyntaxTree.hpp>
+
+#include <dryad/node.hpp>
+
+#include "openvic-simulation/types/OrderedContainers.hpp"
+#include "openvic-simulation/utility/TslHelper.hpp"
+
+using namespace OpenVic;
+using namespace OpenVic::NodeTools;
+
+static std::string make_default_modifier_effect_localisation_key(std::string_view identifier) {
+ return "MODIFIER_" + StringUtils::string_toupper(identifier);
+}
+
+ModifierEffect::ModifierEffect(
+ std::string_view new_identifier, bool new_positive_good, format_t new_format, std::string_view new_localisation_key
+) : HasIdentifier { new_identifier }, positive_good { new_positive_good }, format { new_format },
+ localisation_key {
+ new_localisation_key.empty() ? make_default_modifier_effect_localisation_key(new_identifier) : new_localisation_key
+ } {}
+
+ModifierValue::ModifierValue() = default;
+ModifierValue::ModifierValue(effect_map_t&& new_values) : values { std::move(new_values) } {}
+ModifierValue::ModifierValue(ModifierValue const&) = default;
+ModifierValue::ModifierValue(ModifierValue&&) = default;
+
+ModifierValue& ModifierValue::operator=(ModifierValue const&) = default;
+ModifierValue& ModifierValue::operator=(ModifierValue&&) = default;
+
+void ModifierValue::trim() {
+ erase_if(values, [](effect_map_t::value_type const& value) -> bool {
+ return value.second == fixed_point_t::_0();
+ });
+}
+
+size_t ModifierValue::get_effect_count() const {
+ return values.size();
+}
+
+void ModifierValue::clear() {
+ values.clear();
+}
+
+bool ModifierValue::empty() const {
+ return values.empty();
+}
+
+fixed_point_t ModifierValue::get_effect(ModifierEffect const& effect, bool* effect_found) const {
+ const effect_map_t::const_iterator it = values.find(&effect);
+ if (it != values.end()) {
+ if (effect_found != nullptr) {
+ *effect_found = true;
+ }
+ return it->second;
+ }
+ if (effect_found != nullptr) {
+ *effect_found = false;
+ }
+ return fixed_point_t::_0();
+}
+
+bool ModifierValue::has_effect(ModifierEffect const& effect) const {
+ return values.contains(&effect);
+}
+
+void ModifierValue::set_effect(ModifierEffect const& effect, fixed_point_t value) {
+ values[&effect] = value;
+}
+
+ModifierValue& ModifierValue::operator+=(ModifierValue const& right) {
+ for (effect_map_t::value_type const& value : right.values) {
+ values[value.first] += value.second;
+ }
+ return *this;
+}
+
+ModifierValue ModifierValue::operator+(ModifierValue const& right) const {
+ ModifierValue copy = *this;
+ return copy += right;
+}
+
+ModifierValue ModifierValue::operator-() const {
+ ModifierValue copy = *this;
+ for (auto value : mutable_iterator(copy.values)) {
+ value.second = -value.second;
+ }
+ return copy;
+}
+
+ModifierValue& ModifierValue::operator-=(ModifierValue const& right) {
+ for (effect_map_t::value_type const& value : right.values) {
+ values[value.first] -= value.second;
+ }
+ return *this;
+}
+
+ModifierValue ModifierValue::operator-(ModifierValue const& right) const {
+ ModifierValue copy = *this;
+ return copy -= right;
+}
+
+ModifierValue& ModifierValue::operator*=(fixed_point_t const& right) {
+ for (auto value : mutable_iterator(values)) {
+ value.second *= right;
+ }
+ return *this;
+}
+
+ModifierValue ModifierValue::operator*(fixed_point_t const& right) const {
+ ModifierValue copy = *this;
+ return copy *= right;
+}
+
+fixed_point_t& ModifierValue::operator[](ModifierEffect const& effect) {
+ return values[&effect];
+}
+
+void ModifierValue::multiply_add(ModifierValue const& other, fixed_point_t multiplier) {
+ if (multiplier == fixed_point_t::_1()) {
+ *this += other;
+ } else if (multiplier != fixed_point_t::_0()) {
+ for (effect_map_t::value_type const& value : other.values) {
+ values[value.first] += value.second * multiplier;
+ }
+ }
+}
+
+Modifier::Modifier(std::string_view new_identifier, ModifierValue&& new_values, icon_t new_icon)
+ : HasIdentifier { new_identifier }, ModifierValue { std::move(new_values) }, icon { new_icon } {}
+
+TriggeredModifier::TriggeredModifier(
+ std::string_view new_identifier, ModifierValue&& new_values, icon_t new_icon, ConditionScript&& new_trigger
+) : Modifier { new_identifier, std::move(new_values), new_icon }, trigger { std::move(new_trigger) } {}
+
+bool TriggeredModifier::parse_scripts(DefinitionManager const& definition_manager) {
+ return trigger.parse_script(false, definition_manager);
+}
+
+ModifierInstance::ModifierInstance(Modifier const& new_modifier, Date new_expiry_date)
+ : modifier { &new_modifier }, expiry_date { new_expiry_date } {}
+
+bool ModifierManager::add_modifier_effect(
+ std::string_view identifier, bool positive_good, ModifierEffect::format_t format, std::string_view localisation_key
+) {
+ if (identifier.empty()) {
+ Logger::error("Invalid modifier effect identifier - empty!");
+ return false;
+ }
+ return modifier_effects.add_item({ std::move(identifier), positive_good, format, localisation_key });
+}
+
+bool ModifierManager::setup_modifier_effects() {
+ bool ret = true;
+
+ using enum ModifierEffect::format_t;
+ /* Tech/inventions only */
+ ret &= add_modifier_effect("cb_creation_speed", true, PROPORTION_DECIMAL, "CB_MANUFACTURE_TECH");
+ ret &= add_modifier_effect("combat_width", false);
+ ret &= add_modifier_effect("plurality", true, PERCENTAGE_DECIMAL, "TECH_PLURALITY");
+ ret &= add_modifier_effect("pop_growth", true, PROPORTION_DECIMAL, "TECH_POP_GROWTH");
+ ret &= add_modifier_effect("regular_experience_level", true, RAW_DECIMAL, "REGULAR_EXP_TECH");
+ ret &= add_modifier_effect("reinforce_rate", true, PROPORTION_DECIMAL, "REINFORCE_TECH");
+ ret &= add_modifier_effect("seperatism", false, PROPORTION_DECIMAL, "SEPARATISM_TECH"); // paradox typo
+ ret &= add_modifier_effect("shared_prestige", true, RAW_DECIMAL, "SHARED_PRESTIGE_TECH");
+ ret &= add_modifier_effect("tax_eff", true, PROPORTION_DECIMAL, "TECH_TAX_EFF");
+
+ /* Country Modifier Effects */
+ ret &= add_modifier_effect("administrative_efficiency", true);
+ ret &= add_modifier_effect(
+ "administrative_efficiency_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("administrative_efficiency")
+ );
+ 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_generation_speed_modifier", true);
+ ret &= add_modifier_effect(
+ "civilization_progress_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("civilization_progress")
+ );
+ ret &= add_modifier_effect("colonial_life_rating", false, INT, "COLONIAL_LIFE_TECH");
+ ret &= add_modifier_effect("colonial_migration", true, PROPORTION_DECIMAL, "COLONIAL_MIGRATION_TECH");
+ ret &= add_modifier_effect("colonial_points", true, INT, "COLONIAL_POINTS_TECH");
+ ret &= add_modifier_effect("colonial_prestige", true, PROPORTION_DECIMAL, "COLONIAL_PRESTIGE_MODIFIER_TECH");
+ ret &= add_modifier_effect("core_pop_consciousness_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("core_pop_militancy_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("dig_in_cap", true, INT, "DIGIN_FROM_TECH");
+ ret &= add_modifier_effect("diplomatic_points", true, PROPORTION_DECIMAL, "DIPLOMATIC_POINTS_TECH");
+ ret &= add_modifier_effect(
+ "diplomatic_points_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("diplopoints_gain")
+ );
+ ret &= add_modifier_effect("education_efficiency", true);
+ ret &= add_modifier_effect(
+ "education_efficiency_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("education_efficiency")
+ );
+ 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);
+ ret &= add_modifier_effect(
+ "global_assimilation_rate", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("assimilation_rate")
+ );
+ ret &= add_modifier_effect(
+ "global_immigrant_attract", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("immigant_attract")
+ );
+ ret &= add_modifier_effect("global_pop_consciousness_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("global_pop_militancy_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect(
+ "global_population_growth", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("population_growth")
+ );
+ ret &= add_modifier_effect("goods_demand", false);
+ ret &= add_modifier_effect("import_cost", false);
+ ret &= add_modifier_effect("increase_research", true, PROPORTION_DECIMAL, "INC_RES_TECH");
+ ret &= add_modifier_effect("influence", true, PROPORTION_DECIMAL, "TECH_GP_INFLUENCE");
+ ret &= add_modifier_effect(
+ "influence_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("greatpower_influence_gain")
+ );
+ ret &= add_modifier_effect("issue_change_speed", true);
+ ret &= add_modifier_effect(
+ "land_attack_modifier", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("land_attack")
+ );
+ ret &= add_modifier_effect("land_attrition", false, PROPORTION_DECIMAL, "LAND_ATTRITION_TECH");
+ ret &= add_modifier_effect(
+ "land_defense_modifier", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("land_defense")
+ );
+ ret &= add_modifier_effect("land_organisation", true);
+ ret &= add_modifier_effect("land_unit_start_experience", true, RAW_DECIMAL);
+ ret &= add_modifier_effect("leadership", true, RAW_DECIMAL, "LEADERSHIP");
+ ret &= add_modifier_effect(
+ "leadership_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("global_leadership_modifier")
+ );
+ ret &= add_modifier_effect("literacy_con_impact", false);
+ ret &= add_modifier_effect("loan_interest", false);
+ ret &= add_modifier_effect(
+ "max_loan_modifier", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("max_loan_amount")
+ );
+ ret &= add_modifier_effect("max_military_spending", true);
+ ret &= add_modifier_effect("max_national_focus", true, INT, "TECH_MAX_FOCUS");
+ ret &= add_modifier_effect("max_social_spending", true);
+ ret &= add_modifier_effect("max_tariff", true);
+ ret &= add_modifier_effect("max_tax", true);
+ ret &= add_modifier_effect("max_war_exhaustion", true, PERCENTAGE_DECIMAL, "MAX_WAR_EXHAUSTION");
+ ret &= add_modifier_effect("military_tactics", true, PROPORTION_DECIMAL, "MIL_TACTICS_TECH");
+ ret &= add_modifier_effect("min_military_spending", true);
+ ret &= add_modifier_effect("min_social_spending", true);
+ ret &= add_modifier_effect("min_tariff", true);
+ ret &= add_modifier_effect("min_tax", true);
+ ret &= add_modifier_effect(
+ "minimum_wage", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("minimun_wage")
+ );
+ ret &= add_modifier_effect("mobilisation_economy_impact", false);
+ ret &= add_modifier_effect("mobilisation_size", true);
+ ret &= add_modifier_effect("mobilization_impact", false);
+ ret &= add_modifier_effect(
+ "naval_attack_modifier", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("naval_attack")
+ );
+ ret &= add_modifier_effect("naval_attrition", false, PROPORTION_DECIMAL, "NAVAL_ATTRITION_TECH");
+ ret &= add_modifier_effect(
+ "naval_defense_modifier", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("naval_defense")
+ );
+ ret &= add_modifier_effect("naval_organisation", true);
+ ret &= add_modifier_effect("naval_unit_start_experience", true, RAW_DECIMAL);
+ ret &= add_modifier_effect("non_accepted_pop_consciousness_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("non_accepted_pop_militancy_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("org_regain", true);
+ ret &= add_modifier_effect("pension_level", true);
+ ret &= add_modifier_effect("permanent_prestige", true, RAW_DECIMAL, "PERMANENT_PRESTIGE_TECH");
+ ret &= add_modifier_effect("political_reform_desire", false);
+ ret &= add_modifier_effect("poor_savings_modifier", true);
+ ret &= add_modifier_effect("prestige", true, RAW_DECIMAL);
+ ret &= add_modifier_effect("reinforce_speed", true);
+ ret &= add_modifier_effect("research_points", true, RAW_DECIMAL);
+ ret &= add_modifier_effect("research_points_modifier", true);
+ ret &= add_modifier_effect("research_points_on_conquer", true);
+ ret &= add_modifier_effect("rgo_output", true);
+ ret &= add_modifier_effect("rgo_throughput", true);
+ ret &= add_modifier_effect("ruling_party_support", true);
+ ret &= add_modifier_effect(
+ "self_unciv_economic_modifier", false, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("self_unciv_economic")
+ );
+ ret &= add_modifier_effect(
+ "self_unciv_military_modifier", false, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("self_unciv_military")
+ );
+ ret &= add_modifier_effect("social_reform_desire", false);
+ ret &= add_modifier_effect("soldier_to_pop_loss", true, PROPORTION_DECIMAL, "SOLDIER_TO_POP_LOSS_TECH");
+ ret &= add_modifier_effect("supply_consumption", false);
+ ret &= add_modifier_effect("supply_range", true, PROPORTION_DECIMAL, "SUPPLY_RANGE_TECH");
+ ret &= add_modifier_effect("suppression_points_modifier", true, PROPORTION_DECIMAL, "SUPPRESSION_TECH");
+ ret &= add_modifier_effect(
+ "tariff_efficiency_modifier", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("tariff_efficiency")
+ );
+ ret &= add_modifier_effect("tax_efficiency", true);
+ ret &= add_modifier_effect("unemployment_benefit", true);
+ ret &= add_modifier_effect(
+ "unciv_economic_modifier", false, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("unciv_economic")
+ );
+ ret &= add_modifier_effect(
+ "unciv_military_modifier", false, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("unciv_military")
+ );
+ ret &= add_modifier_effect("unit_recruitment_time", false);
+ ret &= add_modifier_effect("war_exhaustion", false, PROPORTION_DECIMAL, "WAR_EXHAUST_BATTLES");
+
+ /* State only */
+ // replace $VAL$ with effect value * 30 (as proportion decimal, only used in national foci)
+ ret &= add_modifier_effect("flashpoint_tension", true, PROPORTION_DECIMAL, "increase_tension_focus_desc");
+ // capitalist likelihood for railroads vs factories
+ // replace $VAL$ with effect value (as proportion decimal, only used in national foci)
+ ret &= add_modifier_effect("railroads", true, PROPORTION_DECIMAL, "railroad_build_desc");
+
+ /* Province Modifier Effects */
+ ret &= add_modifier_effect("assimilation_rate", true);
+ ret &= add_modifier_effect("boost_strongest_party", false);
+ ret &= add_modifier_effect("farm_rgo_eff", true, PROPORTION_DECIMAL, "TECH_FARM_OUTPUT");
+ ret &= add_modifier_effect(
+ "farm_rgo_size", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("farm_size")
+ );
+ ret &= add_modifier_effect(
+ "immigrant_attract", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("immigant_attract")
+ );
+ ret &= add_modifier_effect(
+ "immigrant_push", false, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("immigant_push")
+ );
+ ret &= add_modifier_effect("life_rating", true);
+ ret &= add_modifier_effect(
+ "local_artisan_input", false, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("artisan_input")
+ );
+ ret &= add_modifier_effect(
+ "local_artisan_output", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("artisan_output")
+ );
+ ret &= add_modifier_effect(
+ "local_artisan_throughput", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("artisan_throughput")
+ );
+ ret &= add_modifier_effect(
+ "local_factory_input", false, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("factory_input")
+ );
+ ret &= add_modifier_effect(
+ "local_factory_output", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("factory_output")
+ );
+ ret &= add_modifier_effect(
+ "local_factory_throughput", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("factory_throughput")
+ );
+ ret &= add_modifier_effect("local_repair", true);
+ ret &= add_modifier_effect(
+ "local_rgo_output", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("rgo_output")
+ );
+ ret &= add_modifier_effect(
+ "local_rgo_throughput", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("rgo_throughput")
+ );
+ ret &= add_modifier_effect(
+ "local_ruling_party_support", true, PROPORTION_DECIMAL,
+ make_default_modifier_effect_localisation_key("ruling_party_support")
+ );
+ ret &= add_modifier_effect("local_ship_build", false);
+ ret &= add_modifier_effect("max_attrition", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("mine_rgo_eff", true, PROPORTION_DECIMAL, "TECH_MINE_OUTPUT");
+ ret &= add_modifier_effect(
+ "mine_rgo_size", true, PROPORTION_DECIMAL, make_default_modifier_effect_localisation_key("mine_size")
+ );
+ ret &= add_modifier_effect("movement_cost", false);
+ ret &= add_modifier_effect("number_of_voters", false);
+ ret &= add_modifier_effect("pop_consciousness_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("pop_militancy_modifier", false, RAW_DECIMAL);
+ ret &= add_modifier_effect("population_growth", true);
+ ret &= add_modifier_effect("supply_limit", true, RAW_DECIMAL);
+
+ /* Military Modifier Effects */
+ ret &= add_modifier_effect("attack", true, INT, "TRAIT_ATTACK");
+ ret &= add_modifier_effect("attrition", false, RAW_DECIMAL, "ATTRITION");
+ ret &= add_modifier_effect("defence", true, INT, "TRAIT_DEFEND");
+ ret &= add_modifier_effect("experience", true, PROPORTION_DECIMAL, "TRAIT_EXPERIENCE");
+ ret &= add_modifier_effect("morale", true, PROPORTION_DECIMAL, "TRAIT_MORALE");
+ ret &= add_modifier_effect("organisation", true, PROPORTION_DECIMAL, "TRAIT_ORGANISATION");
+ ret &= add_modifier_effect("reconnaissance", true, PROPORTION_DECIMAL, "TRAIT_RECONAISSANCE");
+ ret &= add_modifier_effect("reliability", true, RAW_DECIMAL, "TRAIT_RELIABILITY");
+ ret &= add_modifier_effect("speed", true, PROPORTION_DECIMAL, "TRAIT_SPEED");
+
+ return ret;
+}
+
+bool ModifierManager::register_complex_modifier(std::string_view identifier) {
+ if (complex_modifiers.emplace(identifier).second) {
+ return true;
+ } else {
+ Logger::error("Duplicate complex modifier: ", identifier);
+ return false;
+ }
+}
+
+std::string ModifierManager::get_flat_identifier(
+ std::string_view complex_modifier_identifier, std::string_view variant_identifier
+) {
+ return StringUtils::append_string_views(complex_modifier_identifier, " ", variant_identifier);
+}
+
+bool ModifierManager::add_event_modifier(std::string_view identifier, ModifierValue&& values, Modifier::icon_t icon) {
+ if (identifier.empty()) {
+ Logger::error("Invalid event modifier effect identifier - empty!");
+ return false;
+ }
+ return event_modifiers.add_item({ identifier, std::move(values), icon }, duplicate_warning_callback);
+}
+
+bool ModifierManager::load_event_modifiers(ast::NodeCPtr root) {
+ const bool ret = expect_dictionary_reserve_length(
+ event_modifiers,
+ [this](std::string_view key, ast::NodeCPtr value) -> bool {
+ ModifierValue modifier_value;
+ Modifier::icon_t icon = 0;
+ bool ret = expect_modifier_value_and_keys(
+ move_variable_callback(modifier_value),
+ "icon", ZERO_OR_ONE, expect_uint(assign_variable_callback(icon))
+ )(value);
+ ret &= add_event_modifier(key, std::move(modifier_value), icon);
+ return ret;
+ }
+ )(root);
+ lock_event_modifiers();
+ return ret;
+}
+
+bool ModifierManager::add_static_modifier(std::string_view identifier, ModifierValue&& values) {
+ if (identifier.empty()) {
+ Logger::error("Invalid static modifier effect identifier - empty!");
+ return false;
+ }
+ return static_modifiers.add_item({ identifier, std::move(values), 0 }, duplicate_warning_callback);
+}
+
+bool ModifierManager::load_static_modifiers(ast::NodeCPtr root) {
+ const bool ret = expect_dictionary_reserve_length(
+ static_modifiers,
+ [this](std::string_view key, ast::NodeCPtr value) -> bool {
+ ModifierValue modifier_value;
+ bool ret = expect_modifier_value(move_variable_callback(modifier_value))(value);
+ ret &= add_static_modifier(key, std::move(modifier_value));
+ return ret;
+ }
+ )(root);
+ lock_static_modifiers();
+ return ret;
+}
+
+bool ModifierManager::add_triggered_modifier(
+ std::string_view identifier, ModifierValue&& values, Modifier::icon_t icon, ConditionScript&& trigger
+) {
+ if (identifier.empty()) {
+ Logger::error("Invalid triggered modifier effect identifier - empty!");
+ return false;
+ }
+ return triggered_modifiers.add_item({ identifier, std::move(values), icon, std::move(trigger) }, duplicate_warning_callback);
+}
+
+bool ModifierManager::load_triggered_modifiers(ast::NodeCPtr root) {
+ const bool ret = expect_dictionary_reserve_length(
+ triggered_modifiers,
+ [this](std::string_view key, ast::NodeCPtr value) -> bool {
+ ModifierValue modifier_value;
+ Modifier::icon_t icon = 0;
+ ConditionScript trigger { scope_t::COUNTRY, scope_t::COUNTRY, scope_t::NO_SCOPE };
+
+ bool ret = expect_modifier_value_and_keys(
+ move_variable_callback(modifier_value),
+ "icon", ZERO_OR_ONE, expect_uint(assign_variable_callback(icon)),
+ "trigger", ONE_EXACTLY, trigger.expect_script()
+ )(value);
+ ret &= add_triggered_modifier(key, std::move(modifier_value), icon, std::move(trigger));
+ return ret;
+ }
+ )(root);
+ lock_triggered_modifiers();
+ return ret;
+}
+
+bool ModifierManager::parse_scripts(DefinitionManager const& definition_manager) {
+ bool ret = true;
+ for (TriggeredModifier& modifier : triggered_modifiers.get_items()) {
+ ret &= modifier.parse_scripts(definition_manager);
+ }
+ return ret;
+}
+
+key_value_callback_t ModifierManager::_modifier_effect_callback(
+ ModifierValue& modifier, key_value_callback_t default_callback, ModifierEffectValidator auto effect_validator
+) const {
+ const auto add_modifier_cb = [this, &modifier, effect_validator](
+ ModifierEffect const* effect, ast::NodeCPtr value
+ ) -> bool {
+ if (effect_validator(*effect)) {
+ static const case_insensitive_string_set_t no_effect_modifiers {
+ "boost_strongest_party", "poor_savings_modifier", "local_artisan_input", "local_artisan_throughput",
+ "local_artisan_output", "artisan_input", "artisan_throughput", "artisan_output",
+ "import_cost", "unciv_economic_modifier", "unciv_military_modifier"
+ };
+ if (no_effect_modifiers.contains(effect->get_identifier())) {
+ Logger::warning("This modifier does nothing: ", effect->get_identifier());
+ }
+ return expect_fixed_point(map_callback(modifier.values, effect))(value);
+ } else {
+ Logger::error("Failed to validate modifier effect: ", effect->get_identifier());
+ return false;
+ }
+ };
+
+ const auto add_flattened_modifier_cb = [this, add_modifier_cb](
+ std::string_view prefix, std::string_view key, ast::NodeCPtr value
+ ) -> bool {
+ const std::string flat_identifier = get_flat_identifier(prefix, key);
+ ModifierEffect const* effect = get_modifier_effect_by_identifier(flat_identifier);
+ if (effect != nullptr) {
+ return add_modifier_cb(effect, value);
+ } else {
+ Logger::error("Could not find flattened modifier: ", flat_identifier);
+ return false;
+ }
+ };
+
+ return [this, default_callback, add_modifier_cb, add_flattened_modifier_cb](
+ std::string_view key, ast::NodeCPtr value
+ ) -> bool {
+ ModifierEffect const* effect = get_modifier_effect_by_identifier(key);
+ if (effect != nullptr && dryad::node_has_kind<ast::IdentifierValue>(value)) {
+ return add_modifier_cb(effect, value);
+ } else if (complex_modifiers.contains(key) && dryad::node_has_kind<ast::ListValue>(value)) {
+ if (key == "rebel_org_gain") { //because of course there's a special one
+ std::string_view faction_identifier;
+ ast::NodeCPtr value_node = nullptr;
+ bool ret = expect_dictionary_keys(
+ "faction", ONE_EXACTLY, expect_identifier(assign_variable_callback(faction_identifier)),
+ "value", ONE_EXACTLY, assign_variable_callback(value_node)
+ )(value);
+ ret &= add_flattened_modifier_cb(key, faction_identifier, value_node);
+ return ret;
+ } else {
+ return expect_dictionary(std::bind_front(add_flattened_modifier_cb, key))(value);
+ }
+ } else if (key == "war_exhaustion_effect") {
+ Logger::warning("war_exhaustion_effect does nothing (vanilla issues have it).");
+ return true;
+ } else {
+ return default_callback(key, value);
+ }
+ };
+}
+
+node_callback_t ModifierManager::expect_validated_modifier_value_and_default(
+ callback_t<ModifierValue&&> modifier_callback, key_value_callback_t default_callback,
+ ModifierEffectValidator auto effect_validator
+) const {
+ return [this, modifier_callback, default_callback, effect_validator](ast::NodeCPtr root) -> bool {
+ ModifierValue modifier;
+ bool ret = expect_dictionary_reserve_length(
+ modifier.values,
+ _modifier_effect_callback(modifier, default_callback, effect_validator)
+ )(root);
+ ret &= modifier_callback(std::move(modifier));
+ return ret;
+ };
+}
+
+node_callback_t ModifierManager::expect_validated_modifier_value(
+ callback_t<ModifierValue&&> modifier_callback, ModifierEffectValidator auto effect_validator
+) const {
+ return expect_validated_modifier_value_and_default(modifier_callback, key_value_invalid_callback, effect_validator);
+}
+
+node_callback_t ModifierManager::expect_modifier_value_and_default(
+ callback_t<ModifierValue&&> modifier_callback, key_value_callback_t default_callback
+) const {
+ return expect_validated_modifier_value_and_default(modifier_callback, default_callback, [](ModifierEffect const&) -> bool {
+ return true;
+ });
+}
+
+node_callback_t ModifierManager::expect_modifier_value(callback_t<ModifierValue&&> modifier_callback) const {
+ return expect_modifier_value_and_default(modifier_callback, key_value_invalid_callback);
+}
+
+node_callback_t ModifierManager::expect_whitelisted_modifier_value_and_default(
+ callback_t<ModifierValue&&> modifier_callback, string_set_t const& whitelist, key_value_callback_t default_callback
+) const {
+ return expect_validated_modifier_value_and_default(
+ modifier_callback, default_callback,
+ [&whitelist](ModifierEffect const& effect) -> bool {
+ return whitelist.contains(effect.get_identifier());
+ }
+ );
+}
+
+node_callback_t ModifierManager::expect_whitelisted_modifier_value(
+ callback_t<ModifierValue&&> modifier_callback, string_set_t const& whitelist
+) const {
+ return expect_whitelisted_modifier_value_and_default(modifier_callback, whitelist, key_value_invalid_callback);
+}
+
+node_callback_t ModifierManager::expect_modifier_value_and_key_map_and_default(
+ callback_t<ModifierValue&&> modifier_callback, key_value_callback_t default_callback, key_map_t&& key_map
+) const {
+ return [this, modifier_callback, default_callback, key_map = std::move(key_map)](ast::NodeCPtr node) mutable -> bool {
+ bool ret = expect_modifier_value_and_default(
+ modifier_callback,
+ dictionary_keys_callback(key_map, default_callback)
+ )(node);
+ ret &= check_key_map_counts(key_map);
+ return ret;
+ };
+}
+
+node_callback_t ModifierManager::expect_modifier_value_and_key_map(
+ callback_t<ModifierValue&&> modifier_callback, key_map_t&& key_map
+) const {
+ return expect_modifier_value_and_key_map_and_default(modifier_callback, key_value_invalid_callback, std::move(key_map));
+}
+
+namespace OpenVic { // so the compiler shuts up
+ std::ostream& operator<<(std::ostream& stream, ModifierValue const& value) {
+ for (ModifierValue::effect_map_t::value_type const& effect : value.values) {
+ stream << effect.first << ": " << effect.second << "\n";
+ }
+ return stream;
+ }
+}
diff --git a/src/openvic-simulation/modifier/Modifier.hpp b/src/openvic-simulation/modifier/Modifier.hpp
new file mode 100644
index 0000000..f525d6a
--- /dev/null
+++ b/src/openvic-simulation/modifier/Modifier.hpp
@@ -0,0 +1,225 @@
+#pragma once
+
+#include "openvic-simulation/scripts/ConditionScript.hpp"
+#include "openvic-simulation/types/IdentifierRegistry.hpp"
+
+namespace OpenVic {
+ struct ModifierManager;
+
+ struct ModifierEffect : HasIdentifier {
+ friend struct ModifierManager;
+
+ enum class format_t {
+ PROPORTION_DECIMAL, /* An unscaled fraction/ratio, with 1 being "full"/"whole" */
+ PERCENTAGE_DECIMAL, /* A fraction/ratio scaled so that 100 is "full"/"whole" */
+ RAW_DECIMAL, /* A continuous quantity, e.g. attack strength */
+ INT /* A discrete quantity, e.g. building count limit */
+ };
+
+ private:
+ /* If true, positive values will be green and negative values will be red.
+ * If false, the colours will be switced.
+ */
+ const bool PROPERTY_CUSTOM_PREFIX(positive_good, is);
+ const format_t PROPERTY(format);
+ std::string PROPERTY(localisation_key);
+
+ // TODO - format/precision, e.g. 80% vs 0.8 vs 0.800, 2 vs 2.0 vs 200%
+
+ ModifierEffect(
+ std::string_view new_identifier, bool new_positive_good, format_t new_format, std::string_view new_localisation_key
+ );
+
+ public:
+ ModifierEffect(ModifierEffect&&) = default;
+ };
+
+ struct ModifierValue {
+ friend struct ModifierManager;
+
+ using effect_map_t = fixed_point_map_t<ModifierEffect const*>;
+
+ private:
+ effect_map_t PROPERTY(values);
+
+ public:
+ ModifierValue();
+ ModifierValue(effect_map_t&& new_values);
+ ModifierValue(ModifierValue const&);
+ ModifierValue(ModifierValue&&);
+
+ ModifierValue& operator=(ModifierValue const&);
+ ModifierValue& operator=(ModifierValue&&);
+
+ /* Removes effect entries with a value of zero. */
+ void trim();
+ size_t get_effect_count() const;
+ void clear();
+ bool empty() const;
+
+ fixed_point_t get_effect(ModifierEffect const& effect, bool* effect_found = nullptr) const;
+ bool has_effect(ModifierEffect const& effect) const;
+ void set_effect(ModifierEffect const& effect, fixed_point_t value);
+
+ ModifierValue& operator+=(ModifierValue const& right);
+ ModifierValue operator+(ModifierValue const& right) const;
+ ModifierValue operator-() const;
+ ModifierValue& operator-=(ModifierValue const& right);
+ ModifierValue operator-(ModifierValue const& right) const;
+ ModifierValue& operator*=(fixed_point_t const& right);
+ ModifierValue operator*(fixed_point_t const& right) const;
+
+ fixed_point_t& operator[](ModifierEffect const& effect);
+
+ void multiply_add(ModifierValue const& other, fixed_point_t multiplier);
+
+ friend std::ostream& operator<<(std::ostream& stream, ModifierValue const& value);
+ };
+
+ struct Modifier : HasIdentifier, ModifierValue {
+ friend struct ModifierManager;
+
+ using icon_t = uint8_t;
+
+ private:
+ /* A modifier can have no icon (zero). */
+ const icon_t PROPERTY(icon);
+
+ protected:
+ Modifier(std::string_view new_identifier, ModifierValue&& new_values, icon_t new_icon = 0);
+
+ public:
+ Modifier(Modifier&&) = default;
+ };
+
+ struct TriggeredModifier : Modifier {
+ friend struct ModifierManager;
+
+ private:
+ ConditionScript trigger;
+
+ protected:
+ TriggeredModifier(
+ std::string_view new_identifier, ModifierValue&& new_values, icon_t new_icon, ConditionScript&& new_trigger
+ );
+
+ bool parse_scripts(DefinitionManager const& definition_manager);
+
+ public:
+ TriggeredModifier(TriggeredModifier&&) = default;
+ };
+
+ struct ModifierInstance {
+
+ private:
+ Modifier const* PROPERTY(modifier); // We can assume this is never null
+ Date PROPERTY(expiry_date);
+
+ public:
+ ModifierInstance(Modifier const& new_modifier, Date new_expiry_date);
+ };
+
+ template<typename Fn>
+ concept ModifierEffectValidator = std::predicate<Fn, ModifierEffect const&>;
+
+ struct ModifierManager {
+ /* Some ModifierEffects are generated mid-load, such as max/min count modifiers for each building, so
+ * we can't lock it until loading is over. This means we can't rely on locking for pointer stability,
+ * so instead we store the effects in a deque which doesn't invalidate pointers on insert.
+ */
+ private:
+ CaseInsensitiveIdentifierRegistry<ModifierEffect, RegistryStorageInfoDeque> IDENTIFIER_REGISTRY(modifier_effect);
+ case_insensitive_string_set_t complex_modifiers;
+
+ IdentifierRegistry<Modifier> IDENTIFIER_REGISTRY(event_modifier);
+ IdentifierRegistry<Modifier> IDENTIFIER_REGISTRY(static_modifier);
+ IdentifierRegistry<TriggeredModifier> IDENTIFIER_REGISTRY(triggered_modifier);
+
+ /* effect_validator takes in ModifierEffect const& */
+ NodeTools::key_value_callback_t _modifier_effect_callback(
+ ModifierValue& modifier, NodeTools::key_value_callback_t default_callback,
+ ModifierEffectValidator auto effect_validator
+ ) const;
+
+ public:
+ bool add_modifier_effect(
+ std::string_view identifier, bool positive_good,
+ ModifierEffect::format_t format = ModifierEffect::format_t::PROPORTION_DECIMAL,
+ std::string_view localisation_key = {}
+ );
+
+ bool register_complex_modifier(std::string_view identifier);
+ static std::string get_flat_identifier(
+ std::string_view complex_modifier_identifier, std::string_view variant_identifier
+ );
+
+ bool setup_modifier_effects();
+
+ bool add_event_modifier(std::string_view identifier, ModifierValue&& values, Modifier::icon_t icon);
+ bool load_event_modifiers(ast::NodeCPtr root);
+
+ bool add_static_modifier(std::string_view identifier, ModifierValue&& values);
+ bool load_static_modifiers(ast::NodeCPtr root);
+
+ bool add_triggered_modifier(
+ std::string_view identifier, ModifierValue&& values, Modifier::icon_t icon, ConditionScript&& trigger
+ );
+ bool load_triggered_modifiers(ast::NodeCPtr root);
+
+ bool parse_scripts(DefinitionManager const& definition_manager);
+
+ NodeTools::node_callback_t expect_validated_modifier_value_and_default(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, NodeTools::key_value_callback_t default_callback,
+ ModifierEffectValidator auto effect_validator
+ ) const;
+ NodeTools::node_callback_t expect_validated_modifier_value(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, ModifierEffectValidator auto effect_validator
+ ) const;
+
+ NodeTools::node_callback_t expect_modifier_value_and_default(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, NodeTools::key_value_callback_t default_callback
+ ) const;
+ NodeTools::node_callback_t expect_modifier_value(NodeTools::callback_t<ModifierValue&&> modifier_callback) const;
+
+ NodeTools::node_callback_t expect_whitelisted_modifier_value_and_default(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, string_set_t const& whitelist,
+ NodeTools::key_value_callback_t default_callback
+ ) const;
+ NodeTools::node_callback_t expect_whitelisted_modifier_value(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, string_set_t const& whitelist
+ ) const;
+
+ NodeTools::node_callback_t expect_modifier_value_and_key_map_and_default(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, NodeTools::key_value_callback_t default_callback,
+ NodeTools::key_map_t&& key_map
+ ) const;
+ NodeTools::node_callback_t expect_modifier_value_and_key_map(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, NodeTools::key_map_t&& key_map
+ ) const;
+
+ template<typename... Args>
+ NodeTools::node_callback_t expect_modifier_value_and_key_map_and_default(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, NodeTools::key_value_callback_t default_callback,
+ NodeTools::key_map_t&& key_map, Args... args
+ ) const {
+ NodeTools::add_key_map_entries(key_map, args...);
+ return expect_modifier_value_and_key_map_and_default(modifier_callback, default_callback, std::move(key_map));
+ }
+
+ template<typename... Args>
+ NodeTools::node_callback_t expect_modifier_value_and_keys_and_default(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, NodeTools::key_value_callback_t default_callback,
+ Args... args
+ ) const {
+ return expect_modifier_value_and_key_map_and_default(modifier_callback, default_callback, {}, args...);
+ }
+ template<typename... Args>
+ NodeTools::node_callback_t expect_modifier_value_and_keys(
+ NodeTools::callback_t<ModifierValue&&> modifier_callback, Args... args
+ ) const {
+ return expect_modifier_value_and_key_map_and_default(
+ modifier_callback, NodeTools::key_value_invalid_callback, {}, args...
+ );
+ }
+ };
+}
diff --git a/src/openvic-simulation/modifier/ModifierSum.cpp b/src/openvic-simulation/modifier/ModifierSum.cpp
new file mode 100644
index 0000000..8e5ce48
--- /dev/null
+++ b/src/openvic-simulation/modifier/ModifierSum.cpp
@@ -0,0 +1,58 @@
+#include "ModifierSum.hpp"
+
+using namespace OpenVic;
+
+void ModifierSum::clear() {
+ modifiers.clear();
+ value_sum.clear();
+}
+
+bool ModifierSum::empty() {
+ return modifiers.empty();
+}
+
+fixed_point_t ModifierSum::get_effect(ModifierEffect const& effect, bool* effect_found) const {
+ return value_sum.get_effect(effect, effect_found);
+}
+
+bool ModifierSum::has_effect(ModifierEffect const& effect) const {
+ return value_sum.has_effect(effect);
+}
+
+void ModifierSum::add_modifier(Modifier const& modifier, fixed_point_t multiplier) {
+ modifiers[&modifier] += multiplier;
+ value_sum.multiply_add(modifier, multiplier);
+}
+
+void ModifierSum::add_modifier_sum(ModifierSum const& modifier_sum) {
+ modifiers += modifier_sum.modifiers;
+ value_sum += modifier_sum.value_sum;
+}
+
+ModifierSum& ModifierSum::operator+=(Modifier const& modifier) {
+ add_modifier(modifier);
+ return *this;
+}
+
+ModifierSum& ModifierSum::operator+=(ModifierSum const& modifier_sum) {
+ add_modifier_sum(modifier_sum);
+ return *this;
+}
+
+// TODO - include value_sum[effect] in result? Early return if lookup in value_sum fails?
+std::vector<std::pair<Modifier const*, fixed_point_t>> ModifierSum::get_contributing_modifiers(
+ ModifierEffect const& effect
+) const {
+ std::vector<std::pair<Modifier const*, fixed_point_t>> ret;
+
+ for (auto const& [modifier, multiplier] : modifiers) {
+ bool effect_found = false;
+ const fixed_point_t value = modifier->get_effect(effect, &effect_found);
+
+ if (effect_found) {
+ ret.emplace_back(modifier, value * multiplier);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/openvic-simulation/modifier/ModifierSum.hpp b/src/openvic-simulation/modifier/ModifierSum.hpp
new file mode 100644
index 0000000..957ffab
--- /dev/null
+++ b/src/openvic-simulation/modifier/ModifierSum.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "openvic-simulation/modifier/Modifier.hpp"
+#include "openvic-simulation/types/fixed_point/FixedPointMap.hpp"
+
+namespace OpenVic {
+ struct ModifierSum {
+ private:
+ fixed_point_map_t<Modifier const*> PROPERTY(modifiers);
+ ModifierValue PROPERTY(value_sum);
+
+ public:
+ ModifierSum() = default;
+ ModifierSum(ModifierSum&&) = default;
+
+ void clear();
+ bool empty();
+
+ fixed_point_t get_effect(ModifierEffect const& effect, bool* effect_found = nullptr) const;
+ bool has_effect(ModifierEffect const& effect) const;
+
+ void add_modifier(Modifier const& modifier, fixed_point_t multiplier = fixed_point_t::_1());
+ void add_modifier_sum(ModifierSum const& modifier_sum);
+
+ ModifierSum& operator+=(Modifier const& modifier);
+ ModifierSum& operator+=(ModifierSum const& modifier_sum);
+
+ std::vector<std::pair<Modifier const*, fixed_point_t>> get_contributing_modifiers(ModifierEffect const& effect) const;
+ };
+}