diff options
author | zaaarf <me@zaaarf.foo> | 2024-02-12 12:15:04 +0100 |
---|---|---|
committer | zaaarf <me@zaaarf.foo> | 2024-03-16 14:01:15 +0100 |
commit | 023860d53a90774dcdba84ad7c0dca3c944c9a49 (patch) | |
tree | d5b186facf7c3a08e6ccf2e8261b7de054957a41 /src/openvic-simulation/military/UnitType.cpp | |
parent | 2c892c99a6647be15ef23cabf6cc40f08769283d (diff) |
feat: unit instance type definition, renamed existing structs for consistencymilitary-units
Diffstat (limited to 'src/openvic-simulation/military/UnitType.cpp')
-rw-r--r-- | src/openvic-simulation/military/UnitType.cpp | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/src/openvic-simulation/military/UnitType.cpp b/src/openvic-simulation/military/UnitType.cpp new file mode 100644 index 0000000..1fafe35 --- /dev/null +++ b/src/openvic-simulation/military/UnitType.cpp @@ -0,0 +1,343 @@ +#include "UnitType.hpp" + +#include "openvic-simulation/map/TerrainType.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +using enum UnitType::branch_t; +using enum UnitType::unit_category_t; + +UnitType::UnitType( + std::string_view new_identifier, branch_t new_branch, unit_type_args_t& unit_args +) : HasIdentifier { new_identifier }, + branch { new_branch }, + icon { unit_args.icon }, + sprite { unit_args.sprite }, + active { unit_args.active }, + unit_category { unit_args.unit_category }, + floating_flag { unit_args.floating_flag }, + priority { unit_args.priority }, + max_strength { unit_args.max_strength }, + default_organisation { unit_args.default_organisation }, + maximum_speed { unit_args.maximum_speed }, + weighted_value { unit_args.weighted_value }, + move_sound { unit_args.move_sound }, + select_sound { unit_args.select_sound }, + build_time { unit_args.build_time }, + build_cost { std::move(unit_args.build_cost) }, + supply_consumption { unit_args.supply_consumption }, + supply_cost { std::move(unit_args.supply_cost) }, + terrain_modifiers { std::move(unit_args.terrain_modifiers) } {} + +RegimentType::RegimentType( + std::string_view new_identifier, unit_type_args_t& unit_args, regiment_type_args_t const& regiment_type_args +) : UnitType { new_identifier, LAND, unit_args }, + allowed_cultures { regiment_type_args.allowed_cultures }, + sprite_override { regiment_type_args.sprite_override }, + sprite_mount { regiment_type_args.sprite_mount }, + sprite_mount_attach_node { regiment_type_args.sprite_mount_attach_node }, + reconnaissance { regiment_type_args.reconnaissance }, + attack { regiment_type_args.attack }, + defence { regiment_type_args.defence }, + discipline { regiment_type_args.discipline }, + support { regiment_type_args.support }, + maneuver { regiment_type_args.maneuver }, + siege { regiment_type_args.siege } {} + +ShipType::ShipType( + std::string_view new_identifier, unit_type_args_t& unit_args, ship_type_args_t const& ship_type_args +) : UnitType { new_identifier, NAVAL, unit_args }, + naval_icon { ship_type_args.naval_icon }, + sail { ship_type_args.sail }, + transport { ship_type_args.transport }, + capital { ship_type_args.capital }, + colonial_points { ship_type_args.colonial_points }, + build_overseas { ship_type_args.build_overseas }, + min_port_level { ship_type_args.min_port_level }, + limit_per_port { ship_type_args.limit_per_port }, + supply_consumption_score { ship_type_args.supply_consumption_score }, + hull { ship_type_args.hull }, + gun_power { ship_type_args.gun_power }, + fire_range { ship_type_args.fire_range }, + evasion { ship_type_args.evasion }, + torpedo_attack { ship_type_args.torpedo_attack } {} + +void UnitTypeManager::reserve_all_unit_types(size_t size) { + reserve_more_unit_types(size); + reserve_more_regiment_types(size); + reserve_more_ship_types(size); +} + +void UnitTypeManager::lock_all_unit_types() { + unit_types.lock(); + regiment_types.lock(); + ship_types.lock(); +} + +static bool _check_shared_parameters(std::string_view identifier, UnitType::unit_type_args_t const& unit_args) { + if (identifier.empty()) { + Logger::error("Invalid unit identifier - empty!"); + return false; + } + + if (unit_args.icon <= 0) { + Logger::error("Invalid icon for unit ", identifier, " - ", unit_args.icon, " (must be positive)"); + return false; + } + + if (unit_args.unit_category == INVALID_UNIT_CATEGORY) { + Logger::error("Invalid unit type for unit ", identifier, "!"); + return false; + } + + // TODO check that sprite, move_sound and select_sound exist + + return true; +} + +bool UnitTypeManager::add_regiment_type( + std::string_view identifier, UnitType::unit_type_args_t& unit_args, RegimentType::regiment_type_args_t const& regiment_type_args +) { + if (!_check_shared_parameters(identifier, unit_args)) { + return false; + } + + // TODO check that sprite_override, sprite_mount, and sprite_mount_attach_node exist + + if (ship_types.has_identifier(identifier)) { + Logger::error("Land unit ", identifier, " already exists as a naval unit!"); + return false; + } + + bool ret = regiment_types.add_item({ identifier, unit_args, std::move(regiment_type_args) }); + if (ret) { + ret &= unit_types.add_item(®iment_types.get_items().back()); + } + return ret; +} + +bool UnitTypeManager::add_ship_type( + std::string_view identifier, UnitType::unit_type_args_t& unit_args, ShipType::ship_type_args_t const& ship_type_args +) { + if (!_check_shared_parameters(identifier, unit_args)) { + return false; + } + + if (ship_type_args.naval_icon <= 0) { + Logger::error("Invalid naval icon identifier - ", ship_type_args.naval_icon, " (must be positive)"); + return false; + } + + if (ship_type_args.supply_consumption_score <= 0) { + Logger::warning("Supply consumption score for ", identifier, " is not positive!"); + } + + if (regiment_types.has_identifier(identifier)) { + Logger::error("Naval unit ", identifier, " already exists as a land unit!"); + return false; + } + + bool ret = ship_types.add_item({ identifier, unit_args, ship_type_args }); + if (ret) { + ret &= unit_types.add_item(&ship_types.get_items().back()); + } + return ret; +} + +bool UnitTypeManager::load_unit_type_file( + GoodManager const& good_manager, TerrainTypeManager const& terrain_type_manager, ModifierManager const& modifier_manager, + ast::NodeCPtr root +) { + return expect_dictionary([this, &good_manager, &terrain_type_manager, &modifier_manager]( + std::string_view key, ast::NodeCPtr value + ) -> bool { + + UnitType::branch_t branch = INVALID_BRANCH; + + bool ret = expect_key("type", expect_branch_identifier(assign_variable_callback(branch)))(value); + + /* We shouldn't just check ret as it can be false even if branch was successfully parsed, + * but more than one instance of the key was found. */ + if (branch != LAND && branch != NAVAL) { + Logger::error("Failed to read branch for unit: ", key); + return false; + } + + UnitType::unit_type_args_t unit_args {}; + + static const string_map_t<UnitType::unit_category_t> unit_type_map { + { "infantry", INFANTRY }, + { "cavalry", CAVALRY }, + { "support", SUPPORT }, + { "special", SPECIAL }, + { "big_ship", BIG_SHIP }, + { "light_ship", LIGHT_SHIP }, + { "transport", TRANSPORT } + }; + + key_map_t key_map {}; + /* Shared dictionary entries */ + ret &= add_key_map_entries(key_map, + "icon", ONE_EXACTLY, expect_uint(assign_variable_callback(unit_args.icon)), + "type", ONE_EXACTLY, success_callback, /* Already loaded above using expect_key */ + "sprite", ONE_EXACTLY, expect_identifier(assign_variable_callback(unit_args.sprite)), + "active", ZERO_OR_ONE, expect_bool(assign_variable_callback(unit_args.active)), + "unit_type", ONE_EXACTLY, + expect_identifier(expect_mapped_string(unit_type_map, assign_variable_callback(unit_args.unit_category))), + "floating_flag", ONE_EXACTLY, expect_bool(assign_variable_callback(unit_args.floating_flag)), + "priority", ONE_EXACTLY, expect_uint(assign_variable_callback(unit_args.priority)), + "max_strength", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(unit_args.max_strength)), + "default_organisation", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(unit_args.default_organisation)), + "maximum_speed", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(unit_args.maximum_speed)), + "weighted_value", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(unit_args.weighted_value)), + "move_sound", ZERO_OR_ONE, expect_identifier(assign_variable_callback(unit_args.move_sound)), + "select_sound", ZERO_OR_ONE, expect_identifier(assign_variable_callback(unit_args.select_sound)), + "build_time", ONE_EXACTLY, expect_days(assign_variable_callback(unit_args.build_time)), + "build_cost", ONE_EXACTLY, good_manager.expect_good_decimal_map(move_variable_callback(unit_args.build_cost)), + "supply_consumption", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(unit_args.supply_consumption)), + "supply_cost", ONE_EXACTLY, good_manager.expect_good_decimal_map(move_variable_callback(unit_args.supply_cost)) + ); + + const auto add_terrain_modifier = [&unit_args, &terrain_type_manager, &modifier_manager]( + std::string_view default_key, ast::NodeCPtr default_value + ) -> bool { + TerrainType const* terrain_type = terrain_type_manager.get_terrain_type_by_identifier(default_key); + if (terrain_type != nullptr) { + // TODO - restrict what modifier effects can be used here + return modifier_manager.expect_modifier_value( + map_callback(unit_args.terrain_modifiers, terrain_type) + )(default_value); + } + return key_value_invalid_callback(default_key, default_value); + }; + + switch (branch) { + case LAND: { + RegimentType::regiment_type_args_t regiment_type_args {}; + bool is_restricted_to_primary_culture = false; + bool is_restricted_to_accepted_cultures = false; + + ret &= add_key_map_entries(key_map, + "primary_culture", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_restricted_to_primary_culture)), + "accepted_culture", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_restricted_to_accepted_cultures)), + "sprite_override", ZERO_OR_ONE, expect_identifier(assign_variable_callback(regiment_type_args.sprite_override)), + "sprite_mount", ZERO_OR_ONE, expect_identifier(assign_variable_callback(regiment_type_args.sprite_mount)), + "sprite_mount_attach_node", ZERO_OR_ONE, + expect_identifier(assign_variable_callback(regiment_type_args.sprite_mount_attach_node)), + "reconnaissance", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(regiment_type_args.reconnaissance)), + "attack", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(regiment_type_args.attack)), + "defence", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(regiment_type_args.defence)), + "discipline", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(regiment_type_args.discipline)), + "support", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(regiment_type_args.support)), + "maneuver", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(regiment_type_args.maneuver)), + "siege", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(regiment_type_args.siege)) + ); + + if (is_restricted_to_accepted_cultures) { + regiment_type_args.allowed_cultures = RegimentType::allowed_cultures_t::ACCEPTED_CULTURES; + } else if (is_restricted_to_primary_culture) { + regiment_type_args.allowed_cultures = RegimentType::allowed_cultures_t::PRIMARY_CULTURE; + } else { + regiment_type_args.allowed_cultures = RegimentType::allowed_cultures_t::ALL_CULTURES; + } + + ret &= expect_dictionary_key_map_and_default(key_map, add_terrain_modifier)(value); + + ret &= add_regiment_type(key, unit_args, regiment_type_args); + + return ret; + } + case NAVAL: { + ShipType::ship_type_args_t ship_type_args {}; + + ret &= add_key_map_entries(key_map, + "naval_icon", ONE_EXACTLY, expect_uint(assign_variable_callback(ship_type_args.naval_icon)), + "sail", ZERO_OR_ONE, expect_bool(assign_variable_callback(ship_type_args.sail)), + "transport", ZERO_OR_ONE, expect_bool(assign_variable_callback(ship_type_args.transport)), + "capital", ZERO_OR_ONE, expect_bool(assign_variable_callback(ship_type_args.capital)), + "colonial_points", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(ship_type_args.colonial_points)), + "can_build_overseas", ZERO_OR_ONE, expect_bool(assign_variable_callback(ship_type_args.build_overseas)), + "min_port_level", ONE_EXACTLY, expect_uint(assign_variable_callback(ship_type_args.min_port_level)), + "limit_per_port", ONE_EXACTLY, expect_int(assign_variable_callback(ship_type_args.limit_per_port)), + "supply_consumption_score", ZERO_OR_ONE, + expect_fixed_point(assign_variable_callback(ship_type_args.supply_consumption_score)), + "hull", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(ship_type_args.hull)), + "gun_power", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(ship_type_args.gun_power)), + "fire_range", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(ship_type_args.fire_range)), + "evasion", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(ship_type_args.evasion)), + "torpedo_attack", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(ship_type_args.torpedo_attack)) + ); + + ret &= expect_dictionary_key_map_and_default(key_map, add_terrain_modifier)(value); + + ret &= add_ship_type(key, unit_args, ship_type_args); + + return ret; + } + default: + /* Unreachable - an earlier check terminates the function if branch isn't LAND or NAVAL. */ + Logger::error("Unknown branch for unit ", key, ": ", static_cast<int>(branch)); + return false; + } + })(root); +} + +bool UnitTypeManager::generate_modifiers(ModifierManager& modifier_manager) const { + bool ret = true; + + const auto generate_stat_modifiers = [&modifier_manager, &ret](std::string_view identifier, UnitType::branch_t branch) -> void { + const auto stat_modifier = [&modifier_manager, &ret, &identifier]( + std::string_view suffix, bool is_positive_good, ModifierEffect::format_t format + ) -> void { + ret &= modifier_manager.add_modifier_effect( + ModifierManager::get_flat_identifier(identifier, suffix), is_positive_good, format + ); + }; + + using enum ModifierEffect::format_t; + + ret &= modifier_manager.register_complex_modifier(identifier); + + stat_modifier("attack", true, RAW_DECIMAL); + stat_modifier("defence", true, RAW_DECIMAL); + stat_modifier("default_organisation", true, RAW_DECIMAL); + stat_modifier("maximum_speed", true, RAW_DECIMAL); + stat_modifier("build_time", false, INT); + stat_modifier("supply_consumption", false, PROPORTION_DECIMAL); + + switch (branch) { + case LAND: + stat_modifier("reconnaissance", true, RAW_DECIMAL); + stat_modifier("discipline", true, PROPORTION_DECIMAL); + stat_modifier("support", true, PROPORTION_DECIMAL); + stat_modifier("maneuver", true, INT); + stat_modifier("siege", true, RAW_DECIMAL); + break; + case NAVAL: + stat_modifier("colonial_points", true, INT); + stat_modifier("supply_consumption_score", false, INT); + stat_modifier("hull", true, RAW_DECIMAL); + stat_modifier("gun_power", true, RAW_DECIMAL); + stat_modifier("fire_range", true, RAW_DECIMAL); + stat_modifier("evasion", true, PROPORTION_DECIMAL); + stat_modifier("torpedo_attack", true, RAW_DECIMAL); + break; + default: + /* Unreachable - unit types are only added via add_regiment_type or add_ship_type which set branch to LAND or NAVAL. */ + Logger::error("Invalid branch for unit ", identifier, ": ", static_cast<int>(branch)); + } + }; + + generate_stat_modifiers("army_base", LAND); + for (RegimentType const& regiment_type : get_regiment_types()) { + generate_stat_modifiers(regiment_type.get_identifier(), LAND); + } + + generate_stat_modifiers("navy_base", NAVAL); + for (ShipType const& ship_type : get_ship_types()) { + generate_stat_modifiers(ship_type.get_identifier(), NAVAL); + } + + return ret; +} |