diff options
30 files changed, 900 insertions, 241 deletions
diff --git a/src/openvic-simulation/GameManager.cpp b/src/openvic-simulation/GameManager.cpp index bb807c9..94d9c62 100644 --- a/src/openvic-simulation/GameManager.cpp +++ b/src/openvic-simulation/GameManager.cpp @@ -54,21 +54,33 @@ bool GameManager::reset() { bool GameManager::load_bookmark(Bookmark const* new_bookmark) { bool ret = reset(); + bookmark = new_bookmark; if (bookmark == nullptr) { + Logger::error("Cannot load bookmark - null!"); return ret; } + Logger::info("Loading bookmark ", bookmark->get_name(), " with start date ", bookmark->get_date()); + if (!define_manager.in_game_period(bookmark->get_date())) { Logger::warning("Bookmark date ", bookmark->get_date(), " is not in the game's time period!"); } + today = bookmark->get_date(); + ret &= map.apply_history_to_provinces( history_manager.get_province_manager(), today, politics_manager.get_ideology_manager(), politics_manager.get_issue_manager(), *country_manager.get_country_by_identifier("ENG") ); + map.get_state_manager().generate_states(map); - // TODO - apply country history + + ret &= country_instance_manager.generate_country_instances(country_manager); + ret &= country_instance_manager.apply_history_to_countries( + history_manager.get_country_manager(), today, military_manager.get_unit_instance_manager(), map + ); + return ret; } diff --git a/src/openvic-simulation/GameManager.hpp b/src/openvic-simulation/GameManager.hpp index 66b7e57..8e0bd00 100644 --- a/src/openvic-simulation/GameManager.hpp +++ b/src/openvic-simulation/GameManager.hpp @@ -23,7 +23,6 @@ namespace OpenVic { using gamestate_updated_func_t = std::function<void()>; private: - Map PROPERTY_REF(map); DefineManager PROPERTY_REF(define_manager); EconomyManager PROPERTY_REF(economy_manager); MilitaryManager PROPERTY_REF(military_manager); @@ -33,11 +32,15 @@ namespace OpenVic { ResearchManager PROPERTY_REF(research_manager); PopManager PROPERTY_REF(pop_manager); CountryManager PROPERTY_REF(country_manager); + CountryInstanceManager PROPERTY_REF(country_instance_manager); CrimeManager PROPERTY_REF(crime_manager); EventManager PROPERTY_REF(event_manager); DecisionManager PROPERTY_REF(decision_manager); UIManager PROPERTY_REF(ui_manager); DiplomacyManager PROPERTY_REF(diplomacy_manager); + /* Near the end so it is freed after other managers that may depend on it, + * e.g. if we want to remove military units from the province they're in when they're destructed. */ + Map PROPERTY_REF(map); ScriptManager PROPERTY_REF(script_manager); SimulationClock PROPERTY_REF(simulation_clock); diff --git a/src/openvic-simulation/country/Country.cpp b/src/openvic-simulation/country/Country.cpp index 5ec4aee..0fdeb60 100644 --- a/src/openvic-simulation/country/Country.cpp +++ b/src/openvic-simulation/country/Country.cpp @@ -43,14 +43,12 @@ Country::Country( alternative_colours { std::move(new_alternative_colours) }, primary_unit_colour { new_primary_unit_colour }, secondary_unit_colour { new_secondary_unit_colour }, - tertiary_unit_colour { new_tertiary_unit_colour } - {} + tertiary_unit_colour { new_tertiary_unit_colour } {} bool CountryManager::add_country( std::string_view identifier, colour_t colour, GraphicalCultureType const* graphical_culture, IdentifierRegistry<CountryParty>&& parties, Country::unit_names_map_t&& unit_names, bool dynamic_tag, - Country::government_colour_map_t&& alternative_colours, - colour_t primary_unit_colour, colour_t secondary_unit_colour, colour_t tertiary_unit_colour + Country::government_colour_map_t&& alternative_colours ) { if (identifier.empty()) { Logger::error("Invalid country identifier - empty!"); @@ -67,9 +65,13 @@ bool CountryManager::add_country( return false; } + static constexpr colour_t default_colour = colour_t::fill_as(colour_t::max_value); + return countries.add_item({ identifier, colour, *graphical_culture, std::move(parties), std::move(unit_names), dynamic_tag, - std::move(alternative_colours), primary_unit_colour, secondary_unit_colour, tertiary_unit_colour + std::move(alternative_colours), + /* Default to country colour for the chest and grey for the others. Update later if necessary. */ + colour, default_colour, default_colour }); } @@ -205,8 +207,7 @@ bool CountryManager::load_country_data_file( )(root); ret &= add_country( - name, colour, graphical_culture, std::move(parties), std::move(unit_names), is_dynamic, - std::move(alternative_colours), colour_t::null(), colour_t::null(), colour_t::null() + name, colour, graphical_culture, std::move(parties), std::move(unit_names), is_dynamic, std::move(alternative_colours) ); return ret; } diff --git a/src/openvic-simulation/country/Country.hpp b/src/openvic-simulation/country/Country.hpp index ea8f732..1d960c2 100644 --- a/src/openvic-simulation/country/Country.hpp +++ b/src/openvic-simulation/country/Country.hpp @@ -85,8 +85,7 @@ namespace OpenVic { bool add_country( std::string_view identifier, colour_t colour, GraphicalCultureType const* graphical_culture, IdentifierRegistry<CountryParty>&& parties, Country::unit_names_map_t&& unit_names, bool dynamic_tag, - Country::government_colour_map_t&& alternative_colours, - colour_t primary_unit_colour, colour_t secondary_unit_colour, colour_t tertiary_unit_colour + Country::government_colour_map_t&& alternative_colours ); bool load_country_colours(ast::NodeCPtr root); diff --git a/src/openvic-simulation/country/CountryInstance.cpp b/src/openvic-simulation/country/CountryInstance.cpp index 11b1e90..b7b818e 100644 --- a/src/openvic-simulation/country/CountryInstance.cpp +++ b/src/openvic-simulation/country/CountryInstance.cpp @@ -1,7 +1,18 @@ #include "CountryInstance.hpp" +#include "openvic-simulation/military/UnitInstance.hpp" + using namespace OpenVic; +CountryInstance::CountryInstance(Country const* new_base_country) + : base_country { new_base_country }, primary_culture { nullptr }, religion { nullptr }, ruling_party { nullptr }, + last_election {}, capital { nullptr }, government_type { nullptr }, plurality { 0 }, national_value { nullptr }, + civilised { false }, prestige { 0 } {} + +std::string_view CountryInstance::get_identifier() const { + return base_country != nullptr ? base_country->get_identifier() : "NULL"; +} + bool CountryInstance::add_accepted_culture(Culture const* new_accepted_culture) { if (std::find(accepted_cultures.begin(), accepted_cultures.end(), new_accepted_culture) != accepted_cultures.end()) { Logger::warning("Attempted to add accepted culture ", new_accepted_culture->get_identifier(), " to country ", base_country->get_identifier(), ": already present!"); @@ -48,32 +59,79 @@ bool CountryInstance::remove_reform(Reform const* reform_to_remove) { return true; } -bool CountryInstance::apply_history_to_country(CountryHistoryMap const& history, Date date) { - accepted_cultures.clear(); - upper_house.clear(); - reforms.clear(); +bool CountryInstance::apply_history_to_country(CountryHistoryEntry const* entry) { + if (entry == nullptr) { + Logger::error("Trying to apply null country history to ", get_identifier()); + return false; + } - bool ret = true; - for (CountryHistoryEntry const* entry : history.get_entries_up_to(date)) { - if (entry->get_primary_culture()) primary_culture = *entry->get_primary_culture(); - for (Culture const* culture : entry->get_accepted_cultures()) { - ret &= add_accepted_culture(culture); - } - if (entry->get_religion()) religion = *entry->get_religion(); - if (entry->get_ruling_party()) ruling_party = *entry->get_ruling_party(); - if (entry->get_last_election()) last_election = *entry->get_last_election(); - for (auto const& [ideology, popularity] : entry->get_upper_house()) { - add_to_upper_house(ideology, popularity); + constexpr auto set_optional = []<typename T>(T& target, std::optional<T> const& source) { + if (source) { + target = *source; } - if (entry->get_capital()) capital = *entry->get_capital(); - if (entry->get_government_type()) government_type = *entry->get_government_type(); - if (entry->get_plurality()) plurality = *entry->get_plurality(); - if (entry->get_national_value()) national_value = *entry->get_national_value(); - if (entry->is_civilised()) civilised = *entry->is_civilised(); - if (entry->get_prestige()) prestige = *entry->get_prestige(); - for (Reform const* reform : entry->get_reforms()) { - ret &= add_reform(reform); + }; + + bool ret = true; + + set_optional(primary_culture, entry->get_primary_culture()); + for (Culture const* culture : entry->get_accepted_cultures()) { + ret &= add_accepted_culture(culture); + } + set_optional(religion, entry->get_religion()); + set_optional(ruling_party, entry->get_ruling_party()); + set_optional(last_election, entry->get_last_election()); + for (auto const& [ideology, popularity] : entry->get_upper_house()) { + add_to_upper_house(ideology, popularity); + } + set_optional(capital, entry->get_capital()); + set_optional(government_type, entry->get_government_type()); + set_optional(plurality, entry->get_plurality()); + set_optional(national_value, entry->get_national_value()); + set_optional(civilised, entry->is_civilised()); + set_optional(prestige, entry->get_prestige()); + for (Reform const* reform : entry->get_reforms()) { + ret &= add_reform(reform); + } + + return ret; +} + +bool CountryInstanceManager::generate_country_instances(CountryManager const& country_manager) { + reserve_more(country_instances, country_manager.get_country_count()); + + for (Country const& country : country_manager.get_countries()) { + country_instances.push_back({ &country }); + } + + return true; +} + +bool CountryInstanceManager::apply_history_to_countries( + CountryHistoryManager const& history_manager, Date date, UnitInstanceManager& unit_instance_manager, Map& map +) { + bool ret = true; + + for (CountryInstance& country_instance : country_instances) { + if (!country_instance.get_base_country()->is_dynamic_tag()) { + CountryHistoryMap const* history_map = history_manager.get_country_history(country_instance.get_base_country()); + + if (history_map != nullptr) { + CountryHistoryEntry const* oob_history_entry = nullptr; + + for (CountryHistoryEntry const* entry : history_map->get_entries_up_to(date)) { + country_instance.apply_history_to_country(entry); + + if (entry->get_inital_oob()) { + oob_history_entry = entry; + } + } + + if (oob_history_entry != nullptr) { + unit_instance_manager.generate_deployment(map, country_instance, *oob_history_entry->get_inital_oob()); + } + } } } + return ret; } diff --git a/src/openvic-simulation/country/CountryInstance.hpp b/src/openvic-simulation/country/CountryInstance.hpp index 98c6e90..7efd930 100644 --- a/src/openvic-simulation/country/CountryInstance.hpp +++ b/src/openvic-simulation/country/CountryInstance.hpp @@ -6,8 +6,11 @@ #include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { + struct UnitInstanceManager; + /* Representation of an existing country that is currently in-game. */ struct CountryInstance { + friend struct CountryInstanceManager; private: Country const* PROPERTY_RW(base_country); Culture const* PROPERTY_RW(primary_culture); @@ -25,7 +28,11 @@ namespace OpenVic { std::vector<Reform const*> PROPERTY(reforms); // TODO: should be map of reform groups to active reforms: must set defaults & validate applied history // TODO: Military units + OOBs; will probably need an extensible deployment class + CountryInstance(Country const* new_base_country); + public: + std::string_view get_identifier() const; + bool add_accepted_culture(Culture const* new_accepted_culture); bool remove_accepted_culture(Culture const* culture_to_remove); /* Add or modify a party in the upper house. */ @@ -34,6 +41,18 @@ namespace OpenVic { bool add_reform(Reform const* new_reform); bool remove_reform(Reform const* reform_to_remove); - bool apply_history_to_country(CountryHistoryMap const& history, Date date); + bool apply_history_to_country(CountryHistoryEntry const* entry); + }; + + struct CountryInstanceManager { + private: + std::vector<CountryInstance> PROPERTY(country_instances); + + public: + bool generate_country_instances(CountryManager const& country_manager); + + bool apply_history_to_countries( + CountryHistoryManager const& history_manager, Date date, UnitInstanceManager& unit_instance_manager, Map& map + ); }; } // namespace OpenVic diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index d01f6ff..f99417f 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -280,8 +280,7 @@ bool Dataloader::_load_interface_files(UIManager& ui_manager) const { return ui_manager.load_gfx_file(parse_defines(file).get_file_node()); } ); - ui_manager.lock_sprites(); - ui_manager.lock_fonts(); + ui_manager.lock_gfx_registries(); /* Hard-coded GUI file names, might be replaced with a dynamic system but everything should still be loaded on startup. */ static const std::vector<std::string_view> gui_files { @@ -513,12 +512,14 @@ bool Dataloader::_load_history(GameManager& game_manager, bool unused_history_fi { /* Country History */ CountryHistoryManager& country_history_manager = game_manager.get_history_manager().get_country_manager(); + DeploymentManager& deployment_manager = game_manager.get_military_manager().get_deployment_manager(); static constexpr std::string_view country_history_directory = "history/countries"; const path_vector_t country_history_files = lookup_basic_indentifier_prefixed_files_in_dir(country_history_directory, ".txt"); country_history_manager.reserve_more_country_histories(country_history_files.size()); + deployment_manager.reserve_more_deployments(country_history_files.size()); ret &= apply_to_files( country_history_files, @@ -541,11 +542,6 @@ bool Dataloader::_load_history(GameManager& game_manager, bool unused_history_fi ); country_history_manager.lock_country_histories(); - } - - { - DeploymentManager& deployment_manager = game_manager.get_military_manager().get_deployment_manager(); - deployment_manager.lock_deployments(); if (deployment_manager.get_missing_oob_file_count() > 0) { diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp index c41c09e..0bb4d5b 100644 --- a/src/openvic-simulation/dataloader/NodeTools.hpp +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -443,9 +443,17 @@ namespace OpenVic { }; } + /* By default this will only allow an optional to be set once. Set allow_overwrite + * to true to allow multiple assignments, with the last taking precedence. */ template<typename T> - Callback<T const&> auto assign_variable_callback_pointer(std::optional<T const*>& var) { - return [&var](T const& val) -> bool { + Callback<T const&> auto assign_variable_callback_pointer_opt( + std::optional<T const*>& var, bool allow_overwrite = false + ) { + return [&var, allow_overwrite](T const& val) -> bool { + if (!allow_overwrite && var.has_value()) { + Logger::error("Canoot assign pointer value to already-initialised optional!"); + return false; + } var = &val; return true; }; @@ -506,5 +514,24 @@ namespace OpenVic { return warn; }; } + + /* Often used for rotations which must be negated due to OpenVic's coordinate system being orientated + * oppositely to Vic2's. */ + template<typename T = fixed_point_t> + constexpr Callback<T> auto negate_callback(Callback<T> auto callback) { + return [callback](T val) -> bool { + return callback(-val); + }; + } + + /* Often used for map-space coordinates which must have their y-coordinate flipped due to OpenVic using the + * top-left of the map as the origin as opposed Vic2 using the bottom-left. */ + template<typename T> + constexpr Callback<vec2_t<T>> auto flip_y_callback(Callback<vec2_t<T>> auto callback, T height) { + return [callback, height](vec2_t<T> val) -> bool { + val.y = height - val.y; + return callback(val); + }; + } } } diff --git a/src/openvic-simulation/history/CountryHistory.cpp b/src/openvic-simulation/history/CountryHistory.cpp index 5ee5e38..1bd3dd0 100644 --- a/src/openvic-simulation/history/CountryHistory.cpp +++ b/src/openvic-simulation/history/CountryHistory.cpp @@ -68,25 +68,25 @@ bool CountryHistoryMap::_load_history_entry( ); }, "capital", ZERO_OR_ONE, - game_manager.get_map().expect_province_identifier(assign_variable_callback_pointer(entry.capital)), + game_manager.get_map().expect_province_identifier(assign_variable_callback_pointer_opt(entry.capital)), "primary_culture", ZERO_OR_ONE, - culture_manager.expect_culture_identifier(assign_variable_callback_pointer(entry.primary_culture)), + culture_manager.expect_culture_identifier(assign_variable_callback_pointer_opt(entry.primary_culture)), "culture", ZERO_OR_MORE, culture_manager.expect_culture_identifier( vector_callback_pointer(entry.accepted_cultures) ), "religion", ZERO_OR_ONE, game_manager.get_pop_manager().get_religion_manager().expect_religion_identifier( - assign_variable_callback_pointer(entry.religion) + assign_variable_callback_pointer_opt(entry.religion) ), "government", ZERO_OR_ONE, politics_manager.get_government_type_manager().expect_government_type_identifier( - assign_variable_callback_pointer(entry.government_type) + assign_variable_callback_pointer_opt(entry.government_type) ), "plurality", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(entry.plurality)), "nationalvalue", ZERO_OR_ONE, politics_manager.get_national_value_manager().expect_national_value_identifier( - assign_variable_callback_pointer(entry.national_value) + assign_variable_callback_pointer_opt(entry.national_value) ), "civilized", ZERO_OR_ONE, expect_bool(assign_variable_callback(entry.civilised)), "prestige", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(entry.prestige)), - "ruling_party", ZERO_OR_ONE, country.expect_party_identifier(assign_variable_callback_pointer(entry.ruling_party)), + "ruling_party", ZERO_OR_ONE, country.expect_party_identifier(assign_variable_callback_pointer_opt(entry.ruling_party)), "last_election", ZERO_OR_ONE, expect_date(assign_variable_callback(entry.last_election)), "upper_house", ZERO_OR_ONE, politics_manager.get_ideology_manager().expect_ideology_dictionary_reserve_length( entry.upper_house, @@ -105,7 +105,7 @@ bool CountryHistoryMap::_load_history_entry( } ), "schools", ZERO_OR_ONE, technology_manager.expect_technology_school_identifier( - assign_variable_callback_pointer(entry.tech_school) + assign_variable_callback_pointer_opt(entry.tech_school) ), "foreign_investment", ZERO_OR_ONE, country_manager.expect_country_decimal_map(move_variable_callback(entry.foreign_investment)), diff --git a/src/openvic-simulation/history/ProvinceHistory.cpp b/src/openvic-simulation/history/ProvinceHistory.cpp index b5b2210..c22567b 100644 --- a/src/openvic-simulation/history/ProvinceHistory.cpp +++ b/src/openvic-simulation/history/ProvinceHistory.cpp @@ -53,9 +53,9 @@ bool ProvinceHistoryMap::_load_history_entry( return _load_history_sub_entry_callback(game_manager, entry.get_date(), value, key, value); }, "owner", ZERO_OR_ONE, - country_manager.expect_country_identifier(assign_variable_callback_pointer(entry.owner)), + country_manager.expect_country_identifier(assign_variable_callback_pointer_opt(entry.owner, true)), "controller", ZERO_OR_ONE, - country_manager.expect_country_identifier(assign_variable_callback_pointer(entry.controller)), + country_manager.expect_country_identifier(assign_variable_callback_pointer_opt(entry.controller, true)), "add_core", ZERO_OR_MORE, country_manager.expect_country_identifier(vector_callback_pointer(entry.add_cores)), "remove_core", ZERO_OR_MORE, country_manager.expect_country_identifier( vector_callback_pointer(entry.remove_cores) @@ -65,10 +65,10 @@ bool ProvinceHistoryMap::_load_history_entry( "colony", ZERO_OR_ONE, expect_identifier(expect_mapped_string(colony_status_map, assign_variable_callback(entry.colonial))), "is_slave", ZERO_OR_ONE, expect_bool(assign_variable_callback(entry.slave)), - "trade_goods", ZERO_OR_ONE, good_manager.expect_good_identifier(assign_variable_callback_pointer(entry.rgo)), + "trade_goods", ZERO_OR_ONE, good_manager.expect_good_identifier(assign_variable_callback_pointer_opt(entry.rgo)), "life_rating", ZERO_OR_ONE, expect_uint<Province::life_rating_t>(assign_variable_callback(entry.life_rating)), "terrain", ZERO_OR_ONE, terrain_type_manager.expect_terrain_type_identifier( - assign_variable_callback_pointer(entry.terrain_type) + assign_variable_callback_pointer_opt(entry.terrain_type) ), "party_loyalty", ZERO_OR_MORE, [&ideology_manager, &entry](ast::NodeCPtr node) -> bool { Ideology const* ideology = nullptr; diff --git a/src/openvic-simulation/interface/GFXObject.cpp b/src/openvic-simulation/interface/GFXObject.cpp index d873db1..67abc4d 100644 --- a/src/openvic-simulation/interface/GFXObject.cpp +++ b/src/openvic-simulation/interface/GFXObject.cpp @@ -101,44 +101,92 @@ node_callback_t Object::expect_objects(length_callback_t length_callback, callba ); } -Actor::Attachment::Attachment() : node {}, attach_id { 0 } {} +Actor::Attachment::Attachment(std::string_view new_actor_name, std::string_view new_attach_node, attach_id_t new_attach_id) + : actor_name { new_actor_name }, attach_node { new_attach_node }, attach_id { new_attach_id } {} -bool Actor::Attachment::_fill_key_map(NodeTools::case_insensitive_key_map_t& key_map) { - bool ret = Named::_fill_key_map(key_map); - ret &= add_key_map_entries(key_map, - "node", ONE_EXACTLY, expect_string(assign_variable_callback_string(node)), - "attachId", ONE_EXACTLY, expect_uint(assign_variable_callback(attach_id)) - ); - return ret; -} +Actor::Animation::Animation(std::string_view new_file, fixed_point_t new_scroll_time) + : file { new_file }, scroll_time { new_scroll_time } {} -Actor::Animation::Animation() : file {}, default_time { 0 } {} +Actor::Actor() : model_file {}, scale { 1 }, idle_animation {}, move_animation {}, attack_animation {} {} -bool Actor::Animation::_fill_key_map(NodeTools::case_insensitive_key_map_t& key_map) { - bool ret = Named::_fill_key_map(key_map); - ret &= add_key_map_entries(key_map, - "file", ONE_EXACTLY, expect_string(assign_variable_callback_string(file)), - "defaultAnimationTime", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(default_time)) - ); - return ret; -} +bool Actor::_set_animation(std::string_view name, std::string_view file, fixed_point_t scroll_time) { + std::optional<Animation>* animation = nullptr; + + if (name == "idle") { + animation = &idle_animation; + } else if (name == "move") { + animation = &move_animation; + } else if (name == "attack") { + animation = &attack_animation; + } else { + Logger::error( + "Unknown animation type \"", name, "\" for actor ", get_name(), " (with file ", file, " and scroll time ", + scroll_time, ")" + ); + return false; + } + + if (animation->has_value()) { + Logger::error( + "Duplicate ", name, " animation for actor ", get_name(), ": ", file, " with scroll time ", + scroll_time, " (already set to ", (*animation)->file, " with scroll time ", (*animation)->scroll_time, ")" + ); + return false; + } -Actor::Actor() : model_file {}, idle_animation_file {}, move_animation_file {}, attack_animation_file {}, scale { 1 } {} + /* Don't set static/non-moving animation, to avoid needing an AnimationPlayer for the generated Godot Skeleton3D. */ + static constexpr std::string_view null_animation = "static.xsm"; + if (file.ends_with(null_animation)) { + return true; + } + + animation->emplace(Animation { file, scroll_time }); + return true; +} bool Actor::_fill_key_map(NodeTools::case_insensitive_key_map_t& key_map) { bool ret = Object::_fill_key_map(key_map); + ret &= add_key_map_entries(key_map, "actorfile", ONE_EXACTLY, expect_string(assign_variable_callback_string(model_file)), - "idle", ZERO_OR_ONE, expect_string(assign_variable_callback_string(idle_animation_file)), - "move", ZERO_OR_ONE, expect_string(assign_variable_callback_string(move_animation_file)), - "attack", ZERO_OR_ONE, expect_string(assign_variable_callback_string(attack_animation_file)), "scale", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(scale)), - "attach", ZERO_OR_MORE, Attachment::_expect_value<Attachment>([this](Attachment&& attachment) -> bool { - return attachments.add_item(std::move(attachment)); - }), - "animation", ZERO_OR_MORE, Animation::_expect_value<Animation>([this](Animation&& animation) -> bool { - return animations.add_item(std::move(animation)); - }) + + "attach", ZERO_OR_MORE, [this](ast::NodeCPtr node) -> bool { + std::string_view actor_name {}, attach_node {}; + Attachment::attach_id_t attach_id = 0; + + if (!expect_dictionary_keys<StringMapCaseInsensitive>( + "name", ONE_EXACTLY, expect_string(assign_variable_callback(actor_name)), + "node", ONE_EXACTLY, expect_string(assign_variable_callback(attach_node)), + "attachId", ONE_EXACTLY, expect_uint(assign_variable_callback(attach_id)) + )(node)) { + Logger::error("Failed to load attachment for actor ", get_name()); + return false; + } + + attachments.push_back({ actor_name, attach_node, attach_id }); + return true; + }, + + "idle", ZERO_OR_ONE, expect_string(std::bind(&Actor::_set_animation, this, "idle", std::placeholders::_1, 0)), + "move", ZERO_OR_ONE, expect_string(std::bind(&Actor::_set_animation, this, "move", std::placeholders::_1, 0)), + "attack", ZERO_OR_ONE, expect_string(std::bind(&Actor::_set_animation, this, "attack", std::placeholders::_1, 0)), + "animation", ZERO_OR_MORE, [this](ast::NodeCPtr node) -> bool { + std::string_view name {}, file {}; + fixed_point_t scroll_time = 0; + + if (!expect_dictionary_keys<StringMapCaseInsensitive>( + "name", ONE_EXACTLY, expect_string(assign_variable_callback(name)), + "file", ONE_EXACTLY, expect_string(assign_variable_callback(file)), + "defaultAnimationTime", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(scroll_time)) + )(node)) { + Logger::error("Failed to load animation for actor ", get_name()); + return false; + } + + return _set_animation(name, file, scroll_time); + } ); + return ret; } diff --git a/src/openvic-simulation/interface/GFXObject.hpp b/src/openvic-simulation/interface/GFXObject.hpp index 840e4a9..db15096 100644 --- a/src/openvic-simulation/interface/GFXObject.hpp +++ b/src/openvic-simulation/interface/GFXObject.hpp @@ -24,48 +24,44 @@ namespace OpenVic::GFX { friend std::unique_ptr<Actor> std::make_unique<Actor>(); public: - class Attachment : public Named<> { - friend class LoadBase; + class Attachment { + friend class Actor; - private: - std::string PROPERTY(node); - int32_t PROPERTY(attach_id); + public: + using attach_id_t = uint32_t; - protected: - Attachment(); + private: + std::string PROPERTY(actor_name); + std::string PROPERTY(attach_node); + attach_id_t PROPERTY(attach_id); - bool _fill_key_map(NodeTools::case_insensitive_key_map_t& key_map) override; + Attachment(std::string_view new_actor_name, std::string_view new_attach_node, attach_id_t new_attach_id); public: Attachment(Attachment&&) = default; - virtual ~Attachment() = default; }; - class Animation : public Named<> { - friend class LoadBase; + class Animation { + friend class Actor; std::string PROPERTY(file); - fixed_point_t PROPERTY(default_time); + fixed_point_t PROPERTY(scroll_time); - protected: - Animation(); - - bool _fill_key_map(NodeTools::case_insensitive_key_map_t& key_map) override; + Animation(std::string_view new_file, fixed_point_t new_scroll_time); public: Animation(Animation&&) = default; - virtual ~Animation() = default; }; private: - std::string PROPERTY(model_file); - std::string PROPERTY(idle_animation_file); - std::string PROPERTY(move_animation_file); - std::string PROPERTY(attack_animation_file); fixed_point_t PROPERTY(scale); + std::string PROPERTY(model_file); + std::optional<Animation> PROPERTY(idle_animation); + std::optional<Animation> PROPERTY(move_animation); + std::optional<Animation> PROPERTY(attack_animation); + std::vector<Attachment> PROPERTY(attachments); - NamedRegistry<Attachment> IDENTIFIER_REGISTRY(attachment); - NamedRegistry<Animation> IDENTIFIER_REGISTRY(animation); + bool _set_animation(std::string_view name, std::string_view file, fixed_point_t scroll_time); protected: Actor(); diff --git a/src/openvic-simulation/interface/UI.cpp b/src/openvic-simulation/interface/UI.cpp index 479948d..ea871de 100644 --- a/src/openvic-simulation/interface/UI.cpp +++ b/src/openvic-simulation/interface/UI.cpp @@ -55,6 +55,12 @@ NodeCallback auto UIManager::_load_fonts(std::string_view font_key) { ); } +void UIManager::lock_gfx_registries() { + lock_sprites(); + lock_fonts(); + lock_objects(); +} + bool UIManager::load_gfx_file(ast::NodeCPtr root) { return expect_dictionary_keys( "spriteTypes", ZERO_OR_ONE, Sprite::expect_sprites( diff --git a/src/openvic-simulation/interface/UI.hpp b/src/openvic-simulation/interface/UI.hpp index ada540a..9aec96c 100644 --- a/src/openvic-simulation/interface/UI.hpp +++ b/src/openvic-simulation/interface/UI.hpp @@ -7,10 +7,11 @@ namespace OpenVic { class UIManager { NamedInstanceRegistry<GFX::Sprite> IDENTIFIER_REGISTRY(sprite); - NamedInstanceRegistry<GUI::Scene, UIManager const&> IDENTIFIER_REGISTRY(scene); IdentifierRegistry<GFX::Font> IDENTIFIER_REGISTRY(font); NamedInstanceRegistry<GFX::Object> IDENTIFIER_REGISTRY(object); + NamedInstanceRegistry<GUI::Scene, UIManager const&> IDENTIFIER_REGISTRY(scene); + bool _load_font(ast::NodeCPtr node); NodeTools::NodeCallback auto _load_fonts(std::string_view font_key); @@ -20,6 +21,8 @@ namespace OpenVic { uint32_t height ); + void lock_gfx_registries(); + bool load_gfx_file(ast::NodeCPtr root); bool load_gui_file(std::string_view scene_name, ast::NodeCPtr root); }; diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index 81a599e..afbcf62 100644 --- a/src/openvic-simulation/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -31,7 +31,7 @@ Mapmode::base_stripe_t Mapmode::get_base_stripe_colours(Map const& map, Province } Map::Map() - : width { 0 }, height { 0 }, max_provinces { Province::MAX_INDEX }, selected_province { nullptr }, + : dims { 0, 0 }, max_provinces { Province::MAX_INDEX }, selected_province { nullptr }, highest_province_population { 0 }, total_map_population { 0 } {} bool Map::add_province(std::string_view identifier, colour_t colour) { @@ -75,8 +75,8 @@ Province::distance_t Map::calculate_distance_between(Province const& from, Provi const fixed_point_t min_x = std::min( (to_pos.x - from_pos.x).abs(), std::min( - (to_pos.x - from_pos.x + width).abs(), - (to_pos.x - from_pos.x - width).abs() + (to_pos.x - from_pos.x + get_width()).abs(), + (to_pos.x - from_pos.x - get_width()).abs() ) ); @@ -327,13 +327,21 @@ Province::index_t Map::get_index_from_colour(colour_t colour) const { return Province::NULL_INDEX; } -Province::index_t Map::get_province_index_at(size_t x, size_t y) const { - if (x < width && y < height) { - return province_shape_image[x + y * width].index; +Province::index_t Map::get_province_index_at(ivec2_t pos) const { + if (pos.nonnegative() && pos.less_than(dims)) { + return province_shape_image[get_pixel_index_from_pos(pos)].index; } return Province::NULL_INDEX; } +Province* Map::get_province_at(ivec2_t pos) { + return get_province_by_index(get_province_index_at(pos)); +} + +Province const* Map::get_province_at(ivec2_t pos) const { + return get_province_by_index(get_province_index_at(pos)); +} + bool Map::set_max_provinces(Province::index_t new_max_provinces) { if (new_max_provinces <= Province::NULL_INDEX) { Logger::error( @@ -568,8 +576,8 @@ bool Map::load_province_definitions(std::vector<LineObject> const& lines) { } bool Map::load_province_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) { - return expect_province_dictionary([&building_type_manager](Province& province, ast::NodeCPtr node) -> bool { - return province.load_positions(building_type_manager, node); + return expect_province_dictionary([this, &building_type_manager](Province& province, ast::NodeCPtr node) -> bool { + return province.load_positions(*this, building_type_manager, node); })(root); } @@ -665,9 +673,9 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain return false; } - width = province_bmp.get_width(); - height = province_bmp.get_height(); - province_shape_image.resize(width * height); + dims.x = province_bmp.get_width(); + dims.y = province_bmp.get_height(); + province_shape_image.resize(dims.x * dims.y); uint8_t const* province_data = province_bmp.get_pixel_data().data(); uint8_t const* terrain_data = terrain_bmp.get_pixel_data().data(); @@ -680,13 +688,13 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain std::vector<fixed_point_t> pixels_per_province(provinces.size()); std::vector<fvec2_t> pixel_position_sum_per_province(provinces.size()); - for (int32_t y = 0; y < height; ++y) { - for (int32_t x = 0; x < width; ++x) { - const size_t pixel_index = x + y * width; + for (ivec2_t pos {}; pos.y < get_height(); ++pos.y) { + for (pos.x = 0; pos.x < get_width(); ++pos.x) { + const size_t pixel_index = get_pixel_index_from_pos(pos); const colour_t province_colour = colour_at(province_data, pixel_index); Province::index_t province_index = Province::NULL_INDEX; - if (x > 0) { + if (pos.x > 0) { const size_t jdx = pixel_index - 1; if (colour_at(province_data, jdx) == province_colour) { province_index = province_shape_image[jdx].index; @@ -694,8 +702,8 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain } } - if (y > 0) { - const size_t jdx = pixel_index - width; + if (pos.y > 0) { + const size_t jdx = pixel_index - get_width(); if (colour_at(province_data, jdx) == province_colour) { province_index = province_shape_image[jdx].index; goto index_found; @@ -708,7 +716,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain unrecognised_province_colours.insert(province_colour); if (detailed_errors) { Logger::warning( - "Unrecognised province colour ", province_colour, " at (", x, ", ", y, ")" + "Unrecognised province colour ", province_colour, " at ", pos ); } } @@ -719,7 +727,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain if (province_index != Province::NULL_INDEX) { const Province::index_t array_index = province_index - 1; pixels_per_province[array_index]++; - pixel_position_sum_per_province[array_index] += static_cast<fvec2_t>(ivec2_t { x, y }); + pixel_position_sum_per_province[array_index] += static_cast<fvec2_t>(pos); } const TerrainTypeMapping::index_t terrain = terrain_data[pixel_index]; @@ -755,7 +763,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain province->on_map = pixel_count > 0; if (province->on_map) { - province->positions.centre = pixel_position_sum_per_province[array_index] / pixel_count; + province->centre = pixel_position_sum_per_province[array_index] / pixel_count; } else { if (detailed_errors) { Logger::warning("Province missing from shape image: ", province->to_string()); @@ -776,23 +784,21 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain bool Map::_generate_standard_province_adjacencies() { bool changed = false; - const auto generate_adjacency = [this, &changed](Province* current, size_t x, size_t y) -> bool { - Province* neighbour = get_province_by_index(province_shape_image[x + y * width].index); + const auto generate_adjacency = [this](Province* current, ivec2_t pos) -> bool { + Province* neighbour = get_province_at(pos); if (neighbour != nullptr) { return add_standard_adjacency(*current, *neighbour); } return false; }; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; ++x) { - Province* cur = get_province_by_index(province_shape_image[x + y * width].index); + for (ivec2_t pos {}; pos.y < get_height(); ++pos.y) { + for (pos.x = 0; pos.x < get_width(); ++pos.x) { + Province* cur = get_province_at(pos); if (cur != nullptr) { - changed |= generate_adjacency(cur, (x + 1) % width, y); - if (y + 1 < height) { - changed |= generate_adjacency(cur, x, y + 1); - } + changed |= generate_adjacency(cur, { (pos.x + 1) % get_width(), pos.y }); + changed |= generate_adjacency(cur, { pos.x, pos.y + 1 }); } } } diff --git a/src/openvic-simulation/map/Map.hpp b/src/openvic-simulation/map/Map.hpp index 807945a..2a3f224 100644 --- a/src/openvic-simulation/map/Map.hpp +++ b/src/openvic-simulation/map/Map.hpp @@ -69,8 +69,7 @@ namespace OpenVic { ProvinceSet water_provinces; TerrainTypeManager PROPERTY_REF(terrain_type_manager); - int32_t PROPERTY(width); - int32_t PROPERTY(height); + ivec2_t PROPERTY(dims); std::vector<shape_pixel_t> PROPERTY(province_shape_image); colour_index_map_t colour_index_map; @@ -84,9 +83,16 @@ namespace OpenVic { StateManager PROPERTY_REF(state_manager); + inline constexpr int32_t get_pixel_index_from_pos(ivec2_t pos) const { + return pos.x + pos.y * dims.x; + } + public: Map(); + inline constexpr int32_t get_width() const { return dims.x; } + inline constexpr int32_t get_height() const { return dims.y; } + bool add_province(std::string_view identifier, colour_t colour); IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS_CUSTOM_INDEX_OFFSET(province, 1); @@ -110,7 +116,9 @@ namespace OpenVic { bool set_water_province_list(std::vector<std::string_view> const& list); void lock_water_provinces(); - Province::index_t get_province_index_at(size_t x, size_t y) const; + Province::index_t get_province_index_at(ivec2_t pos) const; + Province* get_province_at(ivec2_t pos); + Province const* get_province_at(ivec2_t pos) const; bool set_max_provinces(Province::index_t new_max_provinces); void set_selected_province(Province::index_t index); Province* get_selected_province(); diff --git a/src/openvic-simulation/map/Province.cpp b/src/openvic-simulation/map/Province.cpp index d1183f5..89ddd39 100644 --- a/src/openvic-simulation/map/Province.cpp +++ b/src/openvic-simulation/map/Province.cpp @@ -1,6 +1,8 @@ #include "Province.hpp" #include "openvic-simulation/history/ProvinceHistory.hpp" +#include "openvic-simulation/map/Map.hpp" +#include "openvic-simulation/military/UnitInstance.hpp" using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -9,9 +11,10 @@ Province::Province( std::string_view new_identifier, colour_t new_colour, index_t new_index ) : HasIdentifierAndColour { new_identifier, new_colour, true }, index { new_index }, region { nullptr }, climate { nullptr }, continent { nullptr }, on_map { false }, has_region { false }, water { false }, coastal { false }, - port { false }, default_terrain_type { nullptr }, positions {}, terrain_type { nullptr }, life_rating { 0 }, - colony_status { colony_status_t::STATE }, state { nullptr }, owner { nullptr }, controller { nullptr }, slave { false }, - crime { nullptr }, rgo { nullptr }, buildings { "buildings", false }, total_population { 0 } { + port { false }, port_adjacent_province { nullptr }, default_terrain_type { nullptr }, positions {}, + terrain_type { nullptr }, life_rating { 0 }, colony_status { colony_status_t::STATE }, state { nullptr }, + owner { nullptr }, controller { nullptr }, slave { false }, crime { nullptr }, rgo { nullptr }, + buildings { "buildings", false }, total_population { 0 } { assert(index != NULL_INDEX); } @@ -25,33 +28,68 @@ std::string Province::to_string() const { return stream.str(); } -bool Province::load_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) { +bool Province::load_positions(Map const& map, BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) { + const fixed_point_t map_height = map.get_height(); + const bool ret = expect_dictionary_keys( - "text_position", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.text)), - "text_rotation", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(positions.text_rotation)), + "text_position", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.text), map_height)), + "text_rotation", ZERO_OR_ONE, + expect_fixed_point(negate_callback<fixed_point_t>(assign_variable_callback(positions.text_rotation))), "text_scale", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(positions.text_scale)), - "unit", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.unit)), - "town", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.city)), - "city", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.city)), - "factory", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.factory)), - "building_construction", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.building_construction)), - "military_construction", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.military_construction)), + + "unit", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.unit), map_height)), + "town", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.city), map_height)), + "city", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.city), map_height)), + "factory", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.factory), map_height)), + + "building_construction", ZERO_OR_ONE, + expect_fvec2(flip_y_callback(assign_variable_callback(positions.building_construction), map_height)), + "military_construction", ZERO_OR_ONE, + expect_fvec2(flip_y_callback(assign_variable_callback(positions.military_construction), map_height)), + "building_position", ZERO_OR_ONE, building_type_manager.expect_building_type_dictionary_reserve_length( positions.building_position, - [this](BuildingType const& type, ast::NodeCPtr value) -> bool { - return expect_fvec2(map_callback(positions.building_position, &type))(value); + [this, map_height](BuildingType const& type, ast::NodeCPtr value) -> bool { + return expect_fvec2(flip_y_callback(map_callback(positions.building_position, &type), map_height))(value); } ), "building_rotation", ZERO_OR_ONE, building_type_manager.expect_building_type_decimal_map( - move_variable_callback(positions.building_rotation) + move_variable_callback(positions.building_rotation), std::negate {} ), + /* the below are esoteric clausewitz leftovers that either have no impact or whose functionality is lost to time */ "spawn_railway_track", ZERO_OR_ONE, success_callback, "railroad_visibility", ZERO_OR_ONE, success_callback, "building_nudge", ZERO_OR_ONE, success_callback )(root); - port = coastal && positions.building_position.contains(building_type_manager.get_port_building_type()); + if (coastal) { + fvec2_t const* port_position = get_building_position(building_type_manager.get_port_building_type()); + if (port_position != nullptr) { + const fixed_point_t rotation = get_building_rotation(building_type_manager.get_port_building_type()); + + /* At 0 rotation the port faces west, as rotation increases the port rotates anti-clockwise. */ + const fvec2_t port_dir { -rotation.cos(), rotation.sin() }; + const ivec2_t port_facing_position = static_cast<ivec2_t>(*port_position + port_dir / 4); + + Province const* province = map.get_province_at(port_facing_position); + + if (province != nullptr) { + if (province->is_water() && is_adjacent_to(province)) { + port = true; + port_adjacent_province = province; + } else { + /* Expected provinces with invalid ports: 39, 296, 1047, 1406, 2044 */ + Logger::warning( + "Invalid port for province ", get_identifier(), ": facing province ", province, + " which has: water = ", province->is_water(), ", adjacent = ", is_adjacent_to(province) + ); + } + } else { + Logger::warning("Invalid port for province ", get_identifier(), ": facing null province!"); + } + } + } return ret; } @@ -65,6 +103,28 @@ bool Province::expand_building(size_t building_index) { return building->expand(); } +fvec2_t const* Province::get_building_position(BuildingType const* building_type) const { + if (building_type != nullptr) { + const decltype(positions.building_position)::const_iterator it = positions.building_position.find(building_type); + + if (it != positions.building_position.end()) { + return &it->second; + } + } + return nullptr; +} + +fixed_point_t Province::get_building_rotation(BuildingType const* building_type) const { + if (building_type != nullptr) { + const decltype(positions.building_rotation)::const_iterator it = positions.building_rotation.find(building_type); + + if (it != positions.building_rotation.end()) { + return it->second; + } + } + return 0; +} + void Province::_add_pop(Pop pop) { pop.set_location(this); pops.push_back(std::move(pop)); @@ -178,7 +238,43 @@ bool Province::has_adjacency_going_through(Province const* province) const { } fvec2_t Province::get_unit_position() const { - return positions.unit.value_or(positions.centre); + return positions.unit.value_or(centre); +} + +bool Province::add_army(ArmyInstance& army) { + if (armies.emplace(&army).second) { + return true; + } else { + Logger::error("Trying to add already-existing army ", army.get_name(), " to province ", get_identifier()); + return false; + } +} + +bool Province::remove_army(ArmyInstance& army) { + if (armies.erase(&army) > 0) { + return true; + } else { + Logger::error("Trying to remove non-existent army ", army.get_name(), " from province ", get_identifier()); + return false; + } +} + +bool Province::add_navy(NavyInstance& navy) { + if (navies.emplace(&navy).second) { + return true; + } else { + Logger::error("Trying to add already-existing navy ", navy.get_name(), " to province ", get_identifier()); + return false; + } +} + +bool Province::remove_navy(NavyInstance& navy) { + if (navies.erase(&navy) > 0) { + return true; + } else { + Logger::error("Trying to remove non-existent navy ", navy.get_name(), " from province ", get_identifier()); + return false; + } } bool Province::reset(BuildingTypeManager const& building_type_manager) { diff --git a/src/openvic-simulation/map/Province.hpp b/src/openvic-simulation/map/Province.hpp index 476ecc9..cfe5ed6 100644 --- a/src/openvic-simulation/map/Province.hpp +++ b/src/openvic-simulation/map/Province.hpp @@ -1,7 +1,5 @@ #pragma once -#include <cassert> - #include "openvic-simulation/country/Country.hpp" #include "openvic-simulation/economy/BuildingInstance.hpp" #include "openvic-simulation/politics/Ideology.hpp" @@ -20,6 +18,8 @@ namespace OpenVic { struct ProvinceSetModifier; using Climate = ProvinceSetModifier; using Continent = ProvinceSetModifier; + struct ArmyInstance; + struct NavyInstance; /* REQUIREMENTS: * MAP-5, MAP-7, MAP-8, MAP-43, MAP-47 @@ -68,20 +68,17 @@ namespace OpenVic { }; struct province_positions_t { - /* Calculated average */ - fvec2_t centre; - /* Province name placement */ - fvec2_t text; - fixed_point_t text_rotation; - fixed_point_t text_scale; + std::optional<fvec2_t> text; + std::optional<fvec2_t> text_rotation; + std::optional<fvec2_t> text_scale; /* Model positions */ std::optional<fvec2_t> unit; - fvec2_t city; - fvec2_t factory; - fvec2_t building_construction; - fvec2_t military_construction; + std::optional<fvec2_t> city; + std::optional<fvec2_t> factory; + std::optional<fvec2_t> building_construction; + std::optional<fvec2_t> military_construction; ordered_map<BuildingType const*, fvec2_t> building_position; fixed_point_map_t<BuildingType const*> building_rotation; }; @@ -99,11 +96,14 @@ namespace OpenVic { bool PROPERTY_CUSTOM_PREFIX(water, is); bool PROPERTY_CUSTOM_PREFIX(coastal, is); bool PROPERTY_CUSTOM_PREFIX(port, has); + Province const* PROPERTY(port_adjacent_province); /* Terrain type calculated from terrain image */ TerrainType const* PROPERTY(default_terrain_type); std::vector<adjacency_t> PROPERTY(adjacencies); - province_positions_t PROPERTY(positions); + /* Calculated mean pixel position. */ + fvec2_t PROPERTY(centre); + province_positions_t positions; /* Mutable attributes (reset before loading history) */ TerrainType const* PROPERTY(terrain_type); @@ -118,6 +118,8 @@ namespace OpenVic { // TODO - change this into a factory-like structure Good const* PROPERTY(rgo); IdentifierRegistry<BuildingInstance> IDENTIFIER_REGISTRY(building); + ordered_set<ArmyInstance*> PROPERTY(armies); + ordered_set<NavyInstance*> PROPERTY(navies); std::vector<Pop> PROPERTY(pops); Pop::pop_size_t PROPERTY(total_population); @@ -136,9 +138,13 @@ namespace OpenVic { bool operator==(Province const& other) const; std::string to_string() const; - bool load_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root); + /* The positions' y coordinates need to be inverted. */ + bool load_positions(Map const& map, BuildingTypeManager const& building_type_manager, ast::NodeCPtr root); bool expand_building(size_t building_index); + /* This returns a pointer to the position of the specified building type, or nullptr if none exists. */ + fvec2_t const* get_building_position(BuildingType const* building_type) const; + fixed_point_t get_building_rotation(BuildingType const* building_type) const; bool add_pop(Pop&& pop); bool add_pop_vec(std::vector<Pop> const& pop_vec); @@ -154,6 +160,10 @@ namespace OpenVic { bool has_adjacency_going_through(Province const* province) const; fvec2_t get_unit_position() const; + bool add_army(ArmyInstance& army); + bool remove_army(ArmyInstance& army); + bool add_navy(NavyInstance& navy); + bool remove_navy(NavyInstance& navy); bool reset(BuildingTypeManager const& building_type_manager); bool apply_history_to_province(ProvinceHistoryEntry const* entry); diff --git a/src/openvic-simulation/military/Deployment.cpp b/src/openvic-simulation/military/Deployment.cpp index 903df3a..1f8b820 100644 --- a/src/openvic-simulation/military/Deployment.cpp +++ b/src/openvic-simulation/military/Deployment.cpp @@ -5,16 +5,19 @@ using namespace OpenVic; using namespace OpenVic::NodeTools; -RegimentDeployment::RegimentDeployment(std::string_view new_name, RegimentType const* new_type, Province const* new_home) +RegimentDeployment::RegimentDeployment(std::string_view new_name, RegimentType const& new_type, Province const* new_home) : name { new_name }, type { new_type }, home { new_home } {} -ShipDeployment::ShipDeployment(std::string_view new_name, ShipType const* new_type) : name { new_name }, type { new_type } {} +ShipDeployment::ShipDeployment(std::string_view new_name, ShipType const& new_type) + : name { new_name }, type { new_type } {} -ArmyDeployment::ArmyDeployment(std::string_view new_name, Province const* new_location, std::vector<RegimentDeployment>&& new_regiments) - : name { new_name }, location { new_location }, regiments { std::move(new_regiments) } {} +ArmyDeployment::ArmyDeployment( + std::string_view new_name, Province const* new_location, std::vector<RegimentDeployment>&& new_regiments +) : name { new_name }, location { new_location }, regiments { std::move(new_regiments) } {} -NavyDeployment::NavyDeployment(std::string_view new_name, Province const* new_location, std::vector<ShipDeployment>&& new_ships) - : name { new_name }, location { new_location }, ships { std::move(new_ships) } {} +NavyDeployment::NavyDeployment( + std::string_view new_name, Province const* new_location, std::vector<ShipDeployment>&& new_ships +) : name { new_name }, location { new_location }, ships { std::move(new_ships) } {} Deployment::Deployment( std::string_view new_path, std::vector<ArmyDeployment>&& new_armies, std::vector<NavyDeployment>&& new_navies, @@ -23,16 +26,15 @@ Deployment::Deployment( leaders { std::move(new_leaders) } {} bool DeploymentManager::add_deployment( - std::string_view path, std::vector<ArmyDeployment>&& armies, std::vector<NavyDeployment>&& navies, std::vector<Leader>&& leaders + std::string_view path, std::vector<ArmyDeployment>&& armies, std::vector<NavyDeployment>&& navies, + std::vector<Leader>&& leaders ) { if (path.empty()) { Logger::error("Attemped to load order of battle with no path! Something is very wrong!"); return false; } - return deployments.add_item( - std::make_unique<Deployment>(std::move(path), std::move(armies), std::move(navies), std::move(leaders)) - ); + return deployments.add_item({ path, std::move(armies), std::move(navies), std::move(leaders) }); } bool DeploymentManager::load_oob_file( @@ -43,12 +45,16 @@ bool DeploymentManager::load_oob_file( if (deployment != nullptr) { return true; } + if (missing_oob_files.contains(history_path)) { return !fail_on_missing; } + static constexpr std::string_view oob_directory = "history/units/"; + const fs::path lookedup_path = dataloader.lookup_file(StringUtils::append_string_views(oob_directory, history_path), false); + if (lookedup_path.empty()) { missing_oob_files.emplace(history_path); if (fail_on_missing) { @@ -58,9 +64,11 @@ bool DeploymentManager::load_oob_file( return true; } } + std::vector<ArmyDeployment> armies; std::vector<NavyDeployment> navies; std::vector<Leader> leaders; + bool ret = expect_dictionary_keys_and_default( key_value_success_callback, // TODO: load SOI information "leader", ZERO_OR_MORE, [&leaders, &game_manager](ast::NodeCPtr node) -> bool { @@ -95,6 +103,7 @@ bool DeploymentManager::load_oob_file( ); ret = false; } + if (leader_background != nullptr && !leader_background->is_background_trait()) { Logger::error( "Leader ", leader_name, " has background ", leader_background->get_identifier(), @@ -102,9 +111,11 @@ bool DeploymentManager::load_oob_file( ); ret = false; } + leaders.emplace_back( leader_name, leader_branch, leader_date, leader_personality, leader_background, leader_prestige, picture ); + return ret; }, "army", ZERO_OR_MORE, [&armies, &game_manager](ast::NodeCPtr node) -> bool { @@ -120,6 +131,7 @@ bool DeploymentManager::load_oob_file( std::string_view regiment_name {}; RegimentType const* regiment_type = nullptr; Province const* regiment_home = nullptr; + const bool ret = expect_dictionary_keys( "name", ONE_EXACTLY, expect_string(assign_variable_callback(regiment_name)), "type", ONE_EXACTLY, game_manager.get_military_manager().get_unit_type_manager() @@ -127,16 +139,26 @@ bool DeploymentManager::load_oob_file( "home", ZERO_OR_ONE, game_manager.get_map() .expect_province_identifier(assign_variable_callback_pointer(regiment_home)) )(node); + if (regiment_home == nullptr) { Logger::warning("RegimentDeployment ", regiment_name, " has no home province!"); } - army_regiments.emplace_back(regiment_name, regiment_type, regiment_home); + + if (regiment_type == nullptr) { + Logger::error("RegimentDeployment ", regiment_name, " has no type!"); + return false; + } + + army_regiments.push_back({regiment_name, *regiment_type, regiment_home}); + return ret; }, /* Another paradox gem, tested in game and they don't lead the army or even show up */ "leader", ZERO_OR_MORE, success_callback )(node); - armies.emplace_back(army_name, army_location, std::move(army_regiments)); + + armies.push_back({ army_name, army_location, std::move(army_regiments) }); + return ret; }, "navy", ZERO_OR_MORE, [&navies, &game_manager](ast::NodeCPtr node) -> bool { @@ -151,26 +173,39 @@ bool DeploymentManager::load_oob_file( "ship", ONE_OR_MORE, [&game_manager, &navy_ships](ast::NodeCPtr node) -> bool { std::string_view ship_name {}; ShipType const* ship_type = nullptr; + const bool ret = expect_dictionary_keys( "name", ONE_EXACTLY, expect_string(assign_variable_callback(ship_name)), "type", ONE_EXACTLY, game_manager.get_military_manager().get_unit_type_manager() .expect_ship_type_identifier(assign_variable_callback_pointer(ship_type)) )(node); - navy_ships.emplace_back(ship_name, ship_type); + + if (ship_type == nullptr) { + Logger::error("ShipDeployment ", ship_name, " has no type!"); + return false; + } + + navy_ships.push_back({ ship_name, *ship_type }); + return ret; }, /* Another paradox gem, tested in game and they don't lead the army or even show up */ "leader", ZERO_OR_MORE, success_callback )(node); - navies.emplace_back(navy_name, navy_location, std::move(navy_ships)); + + navies.push_back({ navy_name, navy_location, std::move(navy_ships) }); + return ret; } )(Dataloader::parse_defines(lookedup_path).get_file_node()); + ret &= add_deployment(history_path, std::move(armies), std::move(navies), std::move(leaders)); + deployment = get_deployment_by_identifier(history_path); if (deployment == nullptr) { ret = false; } + return ret; } diff --git a/src/openvic-simulation/military/Deployment.hpp b/src/openvic-simulation/military/Deployment.hpp index 742914d..8966397 100644 --- a/src/openvic-simulation/military/Deployment.hpp +++ b/src/openvic-simulation/military/Deployment.hpp @@ -11,48 +11,64 @@ namespace OpenVic { struct RegimentDeployment { + friend struct DeploymentManager; + private: std::string PROPERTY(name); - RegimentType const* PROPERTY(type); + RegimentType const& PROPERTY(type); Province const* PROPERTY(home); + RegimentDeployment(std::string_view new_name, RegimentType const& new_type, Province const* new_home); + public: - RegimentDeployment(std::string_view new_name, RegimentType const* new_type, Province const* new_home); + RegimentDeployment(RegimentDeployment&&) = default; }; struct ShipDeployment { + friend struct DeploymentManager; + private: std::string PROPERTY(name); - ShipType const* PROPERTY(type); + ShipType const& PROPERTY(type); + + ShipDeployment(std::string_view new_name, ShipType const& new_type); public: - ShipDeployment(std::string_view new_name, ShipType const* new_type); + ShipDeployment(ShipDeployment&&) = default; }; struct ArmyDeployment { + friend struct DeploymentManager; + private: std::string PROPERTY(name); Province const* PROPERTY(location); std::vector<RegimentDeployment> PROPERTY(regiments); + ArmyDeployment( + std::string_view new_name, Province const* new_location, std::vector<RegimentDeployment>&& new_regiments + ); + public: - ArmyDeployment(std::string_view new_name, Province const* new_location, std::vector<RegimentDeployment>&& new_regiments); + ArmyDeployment(ArmyDeployment&&) = default; }; struct NavyDeployment { + friend struct DeploymentManager; + private: std::string PROPERTY(name); Province const* PROPERTY(location); std::vector<ShipDeployment> PROPERTY(ships); - public: NavyDeployment(std::string_view new_name, Province const* new_location, std::vector<ShipDeployment>&& new_ships); + + public: + NavyDeployment(NavyDeployment&&) = default; }; struct Deployment : HasIdentifier { - friend std::unique_ptr<Deployment> std::make_unique<Deployment>( - std::string_view&&, std::vector<OpenVic::ArmyDeployment>&&, std::vector<OpenVic::NavyDeployment>&&, std::vector<OpenVic::Leader>&& - ); + friend struct DeploymentManager; private: std::vector<ArmyDeployment> PROPERTY(armies); @@ -70,18 +86,20 @@ namespace OpenVic { struct DeploymentManager { private: - IdentifierInstanceRegistry<Deployment> IDENTIFIER_REGISTRY(deployment); + IdentifierRegistry<Deployment> IDENTIFIER_REGISTRY(deployment); string_set_t missing_oob_files; public: bool add_deployment( - std::string_view path, std::vector<ArmyDeployment>&& armies, std::vector<NavyDeployment>&& navies, std::vector<Leader>&& leaders + std::string_view path, std::vector<ArmyDeployment>&& armies, std::vector<NavyDeployment>&& navies, + std::vector<Leader>&& leaders ); bool load_oob_file( GameManager const& game_manager, Dataloader const& dataloader, std::string_view history_path, Deployment const*& deployment, bool fail_on_missing ); + size_t get_missing_oob_file_count() const; }; } // namespace OpenVic diff --git a/src/openvic-simulation/military/Leader.hpp b/src/openvic-simulation/military/Leader.hpp index 180fd39..5995164 100644 --- a/src/openvic-simulation/military/Leader.hpp +++ b/src/openvic-simulation/military/Leader.hpp @@ -19,5 +19,7 @@ namespace OpenVic { std::string_view new_name, UnitType::branch_t new_branch, Date new_date, LeaderTrait const* new_personality, LeaderTrait const* new_background, fixed_point_t new_prestige, std::string_view new_picture ); + + Leader(Leader&&) = default; }; }
\ No newline at end of file diff --git a/src/openvic-simulation/military/MilitaryManager.hpp b/src/openvic-simulation/military/MilitaryManager.hpp index 343d789..c2fd058 100644 --- a/src/openvic-simulation/military/MilitaryManager.hpp +++ b/src/openvic-simulation/military/MilitaryManager.hpp @@ -2,6 +2,7 @@ #include "openvic-simulation/military/Deployment.hpp" #include "openvic-simulation/military/LeaderTrait.hpp" +#include "openvic-simulation/military/UnitInstance.hpp" #include "openvic-simulation/military/UnitType.hpp" #include "openvic-simulation/military/Wargoal.hpp" @@ -12,5 +13,8 @@ namespace OpenVic { LeaderTraitManager PROPERTY_REF(leader_trait_manager); DeploymentManager PROPERTY_REF(deployment_manager); WargoalTypeManager PROPERTY_REF(wargoal_type_manager); + + // TODO - separate this mutable game data manager from const defines data managers. + UnitInstanceManager PROPERTY_REF(unit_instance_manager); }; } diff --git a/src/openvic-simulation/military/UnitInstance.cpp b/src/openvic-simulation/military/UnitInstance.cpp index 7927b0f..fcb9e3b 100644 --- a/src/openvic-simulation/military/UnitInstance.cpp +++ b/src/openvic-simulation/military/UnitInstance.cpp @@ -1,10 +1,14 @@ #include "UnitInstance.hpp" + #include <vector> + +#include "openvic-simulation/country/CountryInstance.hpp" +#include "openvic-simulation/map/Map.hpp" #include "openvic-simulation/military/UnitType.hpp" using namespace OpenVic; -RegimentInstance::RegimentInstance(std::string_view new_name, RegimentType const& new_regiment_type, Pop& new_pop) +RegimentInstance::RegimentInstance(std::string_view new_name, RegimentType const& new_regiment_type, Pop* new_pop) : UnitInstance { new_name, new_regiment_type }, pop { new_pop } {} ShipInstance::ShipInstance(std::string_view new_name, ShipType const& new_ship_type) @@ -14,18 +18,172 @@ MovementInfo::MovementInfo() : path {}, movement_progress {} {} //TODO: pathfinding logic MovementInfo::MovementInfo(Province const* starting_province, Province const* target_province) - : path { std::vector { starting_province, target_province } }, movement_progress { 0 } {} + : path { starting_province, target_province }, movement_progress { 0 } {} ArmyInstance::ArmyInstance( std::string_view new_name, std::vector<RegimentInstance*>&& new_units, Leader const* new_leader, - Province const* new_position -) : UnitInstanceGroup { new_name, UnitType::branch_t::LAND, std::move(new_units), new_leader, new_position } {} + CountryInstance* new_country +) : UnitInstanceGroup { new_name, UnitType::branch_t::LAND, std::move(new_units), new_leader, new_country } {} + +void ArmyInstance::set_position(Province* new_position) { + if (position != new_position) { + if (position != nullptr) { + position->remove_army(*this); + } + position = new_position; + if (position != nullptr) { + position->add_army(*this); + } + } +} NavyInstance::NavyInstance( std::string_view new_name, std::vector<ShipInstance*>&& new_units, Leader const* new_leader, - Province const* new_position -) : UnitInstanceGroup { new_name, UnitType::branch_t::NAVAL, std::move(new_units), new_leader, new_position } {}
\ No newline at end of file + CountryInstance* new_country +) : UnitInstanceGroup { new_name, UnitType::branch_t::NAVAL, std::move(new_units), new_leader, new_country } {} + +void NavyInstance::set_position(Province* new_position) { + if (position != new_position) { + if (position != nullptr) { + position->remove_navy(*this); + } + position = new_position; + if (position != nullptr) { + position->add_navy(*this); + } + } +} + +bool UnitInstanceManager::generate_regiment(RegimentDeployment const& regiment_deployment, RegimentInstance*& regiment) { + // TODO - get pop from Province regiment_deployment.get_home() + regiments.push_back({ regiment_deployment.get_name(), regiment_deployment.get_type(), nullptr }); + + regiment = ®iments.back(); + + return true; +} + +bool UnitInstanceManager::generate_ship(ShipDeployment const& ship_deployment, ShipInstance*& ship) { + ships.push_back({ ship_deployment.get_name(), ship_deployment.get_type() }); + + ship = &ships.back(); + + return true; +} + +bool UnitInstanceManager::generate_army(Map& map, CountryInstance& country, ArmyDeployment const& army_deployment) { + if (army_deployment.get_regiments().empty()) { + Logger::error( + "Trying to generate army \"", army_deployment.get_name(), "\" with no regiments for country \"", + country.get_identifier(), "\"" + ); + return false; + } + + if (army_deployment.get_location() == nullptr) { + Logger::error( + "Trying to generate army \"", army_deployment.get_name(), "\" with no location for country \"", + country.get_identifier(), "\"" + ); + return false; + } + + bool ret = true; + + std::vector<RegimentInstance*> army_regiments; + + for (RegimentDeployment const& regiment_deployment : army_deployment.get_regiments()) { + RegimentInstance* regiment = nullptr; + + ret &= generate_regiment(regiment_deployment, regiment); + + if (regiment != nullptr) { + army_regiments.push_back(regiment); + } + } + + if (army_regiments.empty()) { + Logger::error( + "Failed to generate any regiments for army \"", army_deployment.get_name(), "\" for country \"", + country.get_identifier(), "\"" + ); + return false; + } + + armies.push_back({ army_deployment.get_name(), std::move(army_regiments), nullptr, &country }); + + armies.back().set_position(map.remove_province_const(army_deployment.get_location())); + + return ret; +} + +bool UnitInstanceManager::generate_navy(Map& map, CountryInstance& country, NavyDeployment const& navy_deployment) { + if (navy_deployment.get_ships().empty()) { + Logger::error( + "Trying to generate navy \"", navy_deployment.get_name(), "\" with no ships for country \"", + country.get_identifier(), "\"" + ); + return false; + } + + if (navy_deployment.get_location() == nullptr) { + Logger::error( + "Trying to generate navy \"", navy_deployment.get_name(), "\" with no location for country \"", + country.get_identifier(), "\"" + ); + return false; + } + + bool ret = true; + + std::vector<ShipInstance*> navy_ships; + + for (ShipDeployment const& ship_deployment : navy_deployment.get_ships()) { + ShipInstance* ship = nullptr; + + ret &= generate_ship(ship_deployment, ship); + + if (ship != nullptr) { + navy_ships.push_back(ship); + } + } + + if (navy_ships.empty()) { + Logger::error( + "Failed to generate any ships for navy \"", navy_deployment.get_name(), "\" for country \"", + country.get_identifier(), "\"" + ); + return false; + } + + navies.push_back({ navy_deployment.get_name(), std::move(navy_ships), nullptr, &country }); + + navies.back().set_position(map.remove_province_const(navy_deployment.get_location())); + + return ret; +} + +bool UnitInstanceManager::generate_deployment(Map& map, CountryInstance& country, Deployment const* deployment) { + if (deployment == nullptr) { + Logger::error("Trying to generate null deployment for ", country.get_identifier()); + return false; + } + + // TODO - Leaders (could be stored in CountryInstance?) + + bool ret = true; + + for (ArmyDeployment const& army_deployment : deployment->get_armies()) { + ret &= generate_army(map, country, army_deployment); + } + + for (NavyDeployment const& navy_deployment : deployment->get_navies()) { + ret &= generate_navy(map, country, navy_deployment); + } + + return ret; +} diff --git a/src/openvic-simulation/military/UnitInstance.hpp b/src/openvic-simulation/military/UnitInstance.hpp index dcca18a..e3d541a 100644 --- a/src/openvic-simulation/military/UnitInstance.hpp +++ b/src/openvic-simulation/military/UnitInstance.hpp @@ -3,7 +3,9 @@ #include <concepts> #include <string_view> #include <vector> + #include "openvic-simulation/map/Province.hpp" +#include "openvic-simulation/military/Deployment.hpp" #include "openvic-simulation/military/Leader.hpp" #include "openvic-simulation/military/UnitType.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" @@ -11,7 +13,7 @@ namespace OpenVic { template<std::derived_from<UnitType> T> struct UnitInstance { - protected: + private: std::string PROPERTY(unit_name); T const& PROPERTY(unit_type); //can't change @@ -26,23 +28,35 @@ namespace OpenVic { organisation { new_unit_type.get_default_organisation() }, //TODO: modifiers morale { 0 }, //TODO: modifiers strength { new_unit_type.get_max_strength() } {} + public: + UnitInstance(UnitInstance&&) = default; + void set_unit_name(std::string_view new_unit_name) { unit_name = new_unit_name; } }; struct RegimentInstance : UnitInstance<RegimentType> { + friend struct UnitInstanceManager; + private: - Pop& PROPERTY(pop); + Pop* PROPERTY(pop); + + RegimentInstance(std::string_view new_name, RegimentType const& new_regiment_type, Pop* new_pop); public: - RegimentInstance(std::string_view new_name, RegimentType const& new_regiment_type, Pop& new_pop); + RegimentInstance(RegimentInstance&&) = default; }; struct ShipInstance : UnitInstance<ShipType> { - public: + friend struct UnitInstanceManager; + + private: ShipInstance(std::string_view new_name, ShipType const& new_ship_type); + + public: + ShipInstance(ShipInstance&&) = default; }; struct MovementInfo { @@ -55,56 +69,130 @@ namespace OpenVic { MovementInfo(Province const* starting_province, Province const* target_province); // contains/calls pathfinding logic }; + struct CountryInstance; + template<utility::is_derived_from_specialization_of<UnitInstance> I> struct UnitInstanceGroup { private: std::string PROPERTY(name); const UnitType::branch_t PROPERTY(branch); std::vector<I*> PROPERTY(units); - Leader const* PROPERTY_RW(leader); - Province const* PROPERTY_RW(position); + Leader const* PROPERTY(leader); MovementInfo PROPERTY_REF(movement_info); protected: + Province* PROPERTY_ACCESS(position, protected); + CountryInstance* PROPERTY_ACCESS(country, protected); + UnitInstanceGroup( std::string_view new_name, UnitType::branch_t new_branch, std::vector<I*>&& new_units, Leader const* new_leader, - Province const* new_position + CountryInstance* new_country ) : name { new_name }, branch { new_branch }, units { std::move(new_units) }, leader { new_leader }, - position { new_position } {} - + position { nullptr }, + country { new_country } {} + public: + UnitInstanceGroup(UnitInstanceGroup&&) = default; + UnitInstanceGroup(UnitInstanceGroup const&) = delete; + void set_name(std::string_view new_name) { name = new_name; } + + size_t get_unit_count() const { + return units.size(); + } + + bool empty() const { + return units.empty(); + } + + size_t get_unit_category_count(UnitType::unit_category_t unit_category) const { + return std::count_if(units.begin(), units.end(), [unit_category](I const* unit) { + return unit->unit_type.get_unit_category() == unit_category; + }); + } + + UnitType const* get_display_unit_type() const { + if (units.empty()) { + return nullptr; + } + + fixed_point_map_t<UnitType const*> weighted_unit_types; + + for (I const* unit : units) { + UnitType const& unit_type = unit->get_unit_type(); + weighted_unit_types[&unit_type] += unit_type.get_weighted_value(); + } + + return get_largest_item_tie_break( + weighted_unit_types, + [](UnitType const* lhs, UnitType const* rhs) -> bool { + return lhs->get_weighted_value() < rhs->get_weighted_value(); + } + )->first; + } + + virtual void set_position(Province* new_position) = 0; }; struct ArmyInstance : UnitInstanceGroup<RegimentInstance> { - public: + friend struct UnitInstanceManager; + + private: ArmyInstance( std::string_view new_name, std::vector<RegimentInstance*>&& new_units, Leader const* new_leader, - Province const* new_position + CountryInstance* new_country ); + + public: + ArmyInstance(ArmyInstance&&) = default; + + void set_position(Province* new_position) override; }; struct NavyInstance : UnitInstanceGroup<ShipInstance> { + friend struct UnitInstanceManager; + private: std::vector<ArmyInstance const*> PROPERTY(carried_armies); - public: NavyInstance( std::string_view new_name, std::vector<ShipInstance*>&& new_ships, Leader const* new_leader, - Province const* new_position + CountryInstance* new_country ); + + public: + NavyInstance(NavyInstance&&) = default; + + void set_position(Province* new_position) override; + }; + + struct UnitInstanceManager { + private: + std::deque<RegimentInstance> PROPERTY(regiments); + std::deque<ShipInstance> PROPERTY(ships); + + std::deque<ArmyInstance> PROPERTY(armies); + std::deque<NavyInstance> PROPERTY(navies); + + bool generate_regiment(RegimentDeployment const& regiment_deployment, RegimentInstance*& regiment); + bool generate_ship(ShipDeployment const& ship_deployment, ShipInstance*& ship); + bool generate_army(Map& map, CountryInstance& country, ArmyDeployment const& army_deployment); + bool generate_navy(Map& map, CountryInstance& country, NavyDeployment const& navy_deployment); + + public: + bool generate_deployment(Map& map, CountryInstance& country, Deployment const* deployment); }; -}
\ No newline at end of file +} diff --git a/src/openvic-simulation/pop/Culture.cpp b/src/openvic-simulation/pop/Culture.cpp index 9466e9f..18ed3d8 100644 --- a/src/openvic-simulation/pop/Culture.cpp +++ b/src/openvic-simulation/pop/Culture.cpp @@ -22,6 +22,8 @@ Culture::Culture( first_names { std::move(new_first_names) }, last_names { std::move(new_last_names) }, radicalism { new_radicalism }, primary_country { new_primary_country } {} +CultureManager::CultureManager() : default_graphical_culture_type { nullptr } {} + bool CultureManager::add_graphical_culture_type(std::string_view identifier) { if (identifier.empty()) { Logger::error("Invalid culture group identifier - empty!"); @@ -78,17 +80,26 @@ bool CultureManager::load_graphical_culture_type_file(ast::NodeCPtr root) { graphical_culture_types, expect_identifier(std::bind_front(&CultureManager::add_graphical_culture_type, this)) )(root); + lock_graphical_culture_types(); + + if (graphical_culture_types_empty()) { + Logger::error("Cannot set default graphical culture type - none loaded!"); + return false; + } + + /* Last defined graphical culture type is used as default. */ + default_graphical_culture_type = &get_graphical_culture_types().back(); + return ret; } bool CultureManager::_load_culture_group( - CountryManager const& country_manager, size_t& total_expected_cultures, - GraphicalCultureType const* default_unit_graphical_culture_type, std::string_view culture_group_key, + CountryManager const& country_manager, size_t& total_expected_cultures, std::string_view culture_group_key, ast::NodeCPtr culture_group_node ) { std::string_view leader {}; - GraphicalCultureType const* unit_graphical_culture_type = default_unit_graphical_culture_type; + GraphicalCultureType const* unit_graphical_culture_type = default_graphical_culture_type; bool is_overseas = true; Country const* union_country = nullptr; @@ -152,21 +163,12 @@ bool CultureManager::load_culture_file(CountryManager const& country_manager, as return false; } - static constexpr std::string_view default_unit_graphical_culture_type_identifier = "Generic"; - GraphicalCultureType const* const default_unit_graphical_culture_type = - get_graphical_culture_type_by_identifier(default_unit_graphical_culture_type_identifier); - if (default_unit_graphical_culture_type == nullptr) { - Logger::error("Failed to find default unit graphical culture type: ", default_unit_graphical_culture_type_identifier); - } - size_t total_expected_cultures = 0; bool ret = expect_dictionary_reserve_length(culture_groups, - [this, &country_manager, default_unit_graphical_culture_type, &total_expected_cultures]( + [this, &country_manager, &total_expected_cultures]( std::string_view key, ast::NodeCPtr value ) -> bool { - return _load_culture_group( - country_manager, total_expected_cultures, default_unit_graphical_culture_type, key, value - ); + return _load_culture_group(country_manager, total_expected_cultures, key, value); } )(root); lock_culture_groups(); diff --git a/src/openvic-simulation/pop/Culture.hpp b/src/openvic-simulation/pop/Culture.hpp index 8807123..72ea3ee 100644 --- a/src/openvic-simulation/pop/Culture.hpp +++ b/src/openvic-simulation/pop/Culture.hpp @@ -61,10 +61,11 @@ namespace OpenVic { IdentifierRegistry<CultureGroup> IDENTIFIER_REGISTRY(culture_group); IdentifierRegistry<Culture> IDENTIFIER_REGISTRY(culture); + GraphicalCultureType const* PROPERTY(default_graphical_culture_type); + bool _load_culture_group( CountryManager const& country_manager, size_t& total_expected_cultures, - GraphicalCultureType const* default_unit_graphical_culture_type, std::string_view culture_group_key, - ast::NodeCPtr culture_group_node + std::string_view culture_group_key, ast::NodeCPtr culture_group_node ); bool _load_culture( CountryManager const& country_manager, CultureGroup const& culture_group, std::string_view culture_key, @@ -72,6 +73,8 @@ namespace OpenVic { ); public: + CultureManager(); + bool add_graphical_culture_type(std::string_view identifier); bool add_culture_group( diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index 393ea37..533273a 100644 --- a/src/openvic-simulation/types/IdentifierRegistry.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -269,6 +269,23 @@ namespace OpenVic { } \ return nullptr; \ } \ + template<std::derived_from<external_value_type> T> \ + requires requires(external_value_type const& value) { \ + { value.get_type() } -> std::same_as<std::string_view>; \ + { T::get_type_static() } -> std::same_as<std::string_view>; \ + } \ + constexpr T CONST* get_cast_item_by_identifier(std::string_view identifier) CONST { \ + external_value_type CONST* item = get_item_by_identifier(identifier); \ + if (item != nullptr) { \ + if (item->get_type() == T::get_type_static()) { \ + return reinterpret_cast<T CONST*>(item); \ + } \ + Logger::error( \ + "Invalid type for item \"", identifier, "\": ", item->get_type(), " (expected ", T::get_type_static(), ")" \ + ); \ + } \ + return nullptr; \ + } \ constexpr external_value_type CONST* get_item_by_index(std::size_t index) CONST { \ if (index < items.size()) { \ return std::addressof(ValueInfo::get_external_value(ItemInfo::get_value(items[index]))); \ @@ -421,18 +438,30 @@ namespace OpenVic { return identifiers; } + /* Parses a dictionary with item keys and decimal number values (in the form of fixed point values), + * with the resulting map move-returned via `callback`. The values can be transformed by providing + * a fixed point to fixed point function fixed_point_functor, which will be applied to ever parsed value. */ + template<NodeTools::FunctorConvertible<fixed_point_t, fixed_point_t> FixedPointFunctor = std::identity> constexpr NodeTools::NodeCallback auto expect_item_decimal_map( - NodeTools::Callback<fixed_point_map_t<external_value_type const*>&&> auto callback + NodeTools::Callback<fixed_point_map_t<external_value_type const*>&&> auto callback, + FixedPointFunctor fixed_point_functor = {} ) const { - return [this, callback](ast::NodeCPtr node) -> bool { + return [this, callback, fixed_point_functor](ast::NodeCPtr node) -> bool { fixed_point_map_t<external_value_type const*> map; - bool ret = expect_item_dictionary([&map](external_value_type const& key, ast::NodeCPtr value) -> bool { - fixed_point_t val; - const bool ret = NodeTools::expect_fixed_point(NodeTools::assign_variable_callback(val))(value); - map.emplace(&key, std::move(val)); - return ret; - })(node); + + bool ret = expect_item_dictionary( + [&map, fixed_point_functor](external_value_type const& key, ast::NodeCPtr value) -> bool { + return NodeTools::expect_fixed_point( + [&map, fixed_point_functor, &key](fixed_point_t val) -> bool { + map.emplace(&key, fixed_point_functor(val)); + return true; + } + )(value); + } + )(node); + ret &= callback(std::move(map)); + return ret; }; } @@ -525,10 +554,12 @@ public: \ std::vector<std::string_view> get_##singular##_identifiers() const { \ return registry.get_item_identifiers(); \ } \ + template<NodeTools::FunctorConvertible<fixed_point_t, fixed_point_t> FixedPointFunctor = std::identity> \ constexpr NodeTools::NodeCallback auto expect_##singular##_decimal_map( \ - NodeTools::Callback<fixed_point_map_t<decltype(registry)::external_value_type const*>&&> auto callback \ + NodeTools::Callback<fixed_point_map_t<decltype(registry)::external_value_type const*>&&> auto callback, \ + FixedPointFunctor fixed_point_functor = {} \ ) const { \ - return registry.expect_item_decimal_map(callback); \ + return registry.expect_item_decimal_map(callback, fixed_point_functor); \ } \ IDENTIFIER_REGISTRY_INTERNAL_SHARED(singular, plural, registry, index_offset, const) \ private: @@ -551,6 +582,10 @@ private: constexpr decltype(registry)::external_value_type const_kw* get_##singular##_by_identifier(std::string_view identifier) const_kw { \ return registry.get_item_by_identifier(identifier); \ } \ + template<std::derived_from<decltype(registry)::external_value_type> T> \ + constexpr T const_kw* get_cast_##singular##_by_identifier(std::string_view identifier) const_kw { \ + return registry.get_cast_item_by_identifier<T>(identifier); \ + } \ constexpr decltype(registry)::external_value_type const_kw* get_##singular##_by_index(std::size_t index) const_kw { \ return index >= index_offset ? registry.get_item_by_index(index - index_offset) : nullptr; \ } \ diff --git a/src/openvic-simulation/types/Vector.hpp b/src/openvic-simulation/types/Vector.hpp index 6897835..7ed952a 100644 --- a/src/openvic-simulation/types/Vector.hpp +++ b/src/openvic-simulation/types/Vector.hpp @@ -34,6 +34,15 @@ namespace OpenVic { return x * x + y * y; } + constexpr bool nonnegative() const { + return x >= 0 && y >= 0; + } + /* Checks that both coordinates are less than their corresponding values in the argument vector. + * This is intended for checking coordinates lie within certain bounds, it is not suitable for sorting vectors. */ + constexpr bool less_than(vec2_t const& vec) const { + return x < vec.x && y < vec.y; + } + constexpr T* data() { return reinterpret_cast<T*>(this); } diff --git a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp index 84d2b70..7752226 100644 --- a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp +++ b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp @@ -235,6 +235,10 @@ namespace OpenVic { return !negative ? result : -result; } + constexpr fixed_point_t cos() const { + return (*this + pi_half()).sin(); + } + constexpr bool is_negative() const { return value < 0; } diff --git a/src/openvic-simulation/types/fixed_point/FixedPointMap.hpp b/src/openvic-simulation/types/fixed_point/FixedPointMap.hpp index 5df537a..bf07ac2 100644 --- a/src/openvic-simulation/types/fixed_point/FixedPointMap.hpp +++ b/src/openvic-simulation/types/fixed_point/FixedPointMap.hpp @@ -151,6 +151,19 @@ namespace OpenVic { return std::max_element(map.begin(), map.end(), pred); } + /* This function includes a key comparator to choose between entries with equal values. */ + template<typename T> + constexpr fixed_point_map_const_iterator_t<T> get_largest_item_tie_break( + fixed_point_map_t<T> const& map, const auto key_pred + ) { + constexpr auto pred = + [key_pred](fixed_point_map_value_t<T> const& lhs, fixed_point_map_value_t<T> const& rhs) -> bool { + return lhs.second < rhs.second || (lhs.second == rhs.second && key_pred(lhs.first, rhs.first)); + }; + + return std::max_element(map.begin(), map.end(), pred); + } + template<typename T> constexpr std::pair<fixed_point_map_const_iterator_t<T>, fixed_point_map_const_iterator_t<T>> get_largest_two_items( fixed_point_map_t<T> const& map |