aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/openvic-simulation/GameManager.cpp14
-rw-r--r--src/openvic-simulation/GameManager.hpp5
-rw-r--r--src/openvic-simulation/country/Country.cpp15
-rw-r--r--src/openvic-simulation/country/Country.hpp3
-rw-r--r--src/openvic-simulation/country/CountryInstance.cpp104
-rw-r--r--src/openvic-simulation/country/CountryInstance.hpp21
-rw-r--r--src/openvic-simulation/dataloader/Dataloader.cpp10
-rw-r--r--src/openvic-simulation/dataloader/NodeTools.hpp31
-rw-r--r--src/openvic-simulation/history/CountryHistory.cpp14
-rw-r--r--src/openvic-simulation/history/ProvinceHistory.cpp8
-rw-r--r--src/openvic-simulation/interface/GFXObject.cpp104
-rw-r--r--src/openvic-simulation/interface/GFXObject.hpp42
-rw-r--r--src/openvic-simulation/interface/UI.cpp6
-rw-r--r--src/openvic-simulation/interface/UI.hpp5
-rw-r--r--src/openvic-simulation/map/Map.cpp64
-rw-r--r--src/openvic-simulation/map/Map.hpp14
-rw-r--r--src/openvic-simulation/map/Province.cpp130
-rw-r--r--src/openvic-simulation/map/Province.hpp38
-rw-r--r--src/openvic-simulation/military/Deployment.cpp63
-rw-r--r--src/openvic-simulation/military/Deployment.hpp40
-rw-r--r--src/openvic-simulation/military/Leader.hpp2
-rw-r--r--src/openvic-simulation/military/MilitaryManager.hpp4
-rw-r--r--src/openvic-simulation/military/UnitInstance.cpp170
-rw-r--r--src/openvic-simulation/military/UnitInstance.hpp116
-rw-r--r--src/openvic-simulation/pop/Culture.cpp30
-rw-r--r--src/openvic-simulation/pop/Culture.hpp7
-rw-r--r--src/openvic-simulation/types/IdentifierRegistry.hpp55
-rw-r--r--src/openvic-simulation/types/Vector.hpp9
-rw-r--r--src/openvic-simulation/types/fixed_point/FixedPoint.hpp4
-rw-r--r--src/openvic-simulation/types/fixed_point/FixedPointMap.hpp13
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 = &regiments.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