diff options
author | Hop311 <Hop3114@gmail.com> | 2024-09-20 14:18:59 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-20 14:18:59 +0200 |
commit | 85da7fb84197d617d1b3cde6969bf7bcfbb93d1d (patch) | |
tree | 0f103095b45de46688206d0e33e24f0331afbabd /src/openvic-simulation/modifier | |
parent | 7fe8ba2b3bd3bafad374a691280bbf5102b58678 (diff) | |
parent | 3703851ced696a2d4559c3e3340c537982d5ebc7 (diff) |
Merge pull request #203 from OpenVicProject/modifier-sum
Add ModifierSum and improve ModifierValue
Diffstat (limited to 'src/openvic-simulation/modifier')
-rw-r--r-- | src/openvic-simulation/modifier/Modifier.cpp | 640 | ||||
-rw-r--r-- | src/openvic-simulation/modifier/Modifier.hpp | 225 | ||||
-rw-r--r-- | src/openvic-simulation/modifier/ModifierSum.cpp | 58 | ||||
-rw-r--r-- | src/openvic-simulation/modifier/ModifierSum.hpp | 30 |
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; + }; +} |