aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
author Hop311 <Hop3114@gmail.com>2024-05-12 17:17:32 +0200
committer GitHub <noreply@github.com>2024-05-12 17:17:32 +0200
commitbfccdb87d66304604ad018037db1581746646bfa (patch)
treeac4394e8ceaca22fa0deaeebd8cf5eafedecaa3e /extension
parentb0a533f945bbc6201fd7df4bc60746cb98efaba4 (diff)
parentac29e4040fc20c50c8f0eb64b1194f6398165eb0 (diff)
Merge pull request #227 from OpenVicProject/models
Models
Diffstat (limited to 'extension')
-rw-r--r--extension/src/openvic-extension/register_types.cpp9
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.cpp9
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.hpp2
-rw-r--r--extension/src/openvic-extension/singletons/ModelSingleton.cpp458
-rw-r--r--extension/src/openvic-extension/singletons/ModelSingleton.hpp48
5 files changed, 526 insertions, 0 deletions
diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp
index fffb370..b2b6731 100644
--- a/extension/src/openvic-extension/register_types.cpp
+++ b/extension/src/openvic-extension/register_types.cpp
@@ -16,6 +16,7 @@
#include "openvic-extension/singletons/GameSingleton.hpp"
#include "openvic-extension/singletons/LoadLocalisation.hpp"
#include "openvic-extension/singletons/MenuSingleton.hpp"
+#include "openvic-extension/singletons/ModelSingleton.hpp"
using namespace godot;
using namespace OpenVic;
@@ -24,6 +25,7 @@ static Checksum* _checksum_singleton = nullptr;
static LoadLocalisation* _load_localisation = nullptr;
static GameSingleton* _game_singleton = nullptr;
static MenuSingleton* _menu_singleton = nullptr;
+static ModelSingleton* _model_singleton = nullptr;
static AssetManager* _asset_manager_singleton = nullptr;
void initialize_openvic_types(ModuleInitializationLevel p_level) {
@@ -47,6 +49,10 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) {
_menu_singleton = memnew(MenuSingleton);
Engine::get_singleton()->register_singleton("MenuSingleton", MenuSingleton::get_singleton());
+ ClassDB::register_class<ModelSingleton>();
+ _model_singleton = memnew(ModelSingleton);
+ Engine::get_singleton()->register_singleton("ModelSingleton", ModelSingleton::get_singleton());
+
ClassDB::register_class<AssetManager>();
_asset_manager_singleton = memnew(AssetManager);
Engine::get_singleton()->register_singleton("AssetManager", AssetManager::get_singleton());
@@ -86,6 +92,9 @@ void uninitialize_openvic_types(ModuleInitializationLevel p_level) {
Engine::get_singleton()->unregister_singleton("MenuSingleton");
memdelete(_menu_singleton);
+ Engine::get_singleton()->unregister_singleton("ModelSingleton");
+ memdelete(_model_singleton);
+
Engine::get_singleton()->unregister_singleton("AssetManager");
memdelete(_asset_manager_singleton);
}
diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp
index 838542d..ef19a6c 100644
--- a/extension/src/openvic-extension/singletons/GameSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp
@@ -44,6 +44,7 @@ void GameSingleton::_bind_methods() {
OV_BIND_METHOD(GameSingleton::load_defines_compatibility_mode, { "file_paths" });
OV_BIND_SMETHOD(search_for_game_path, { "hint_path" }, DEFVAL(String {}));
+ OV_BIND_METHOD(GameSingleton::lookup_file_path, { "path" });
OV_BIND_METHOD(GameSingleton::setup_game, { "bookmark_index" });
@@ -158,6 +159,10 @@ float GameSingleton::get_map_aspect_ratio() const {
return static_cast<float>(get_map_width()) / static_cast<float>(get_map_height());
}
+Vector2 GameSingleton::map_position_to_world_coords(fvec2_t const& position) const {
+ return Utilities::to_godot_fvec2(position) / get_map_dims();
+}
+
Ref<Texture2DArray> GameSingleton::get_terrain_texture() const {
return terrain_texture;
}
@@ -593,3 +598,7 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi
String GameSingleton::search_for_game_path(String const& hint_path) {
return std_to_godot_string(Dataloader::search_for_game_path(godot_to_std_string(hint_path)).string());
}
+
+String GameSingleton::lookup_file_path(String const& path) const {
+ return std_to_godot_string(dataloader.lookup_file(godot_to_std_string(path)).string());
+}
diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp
index f2b88ac..7f86eb2 100644
--- a/extension/src/openvic-extension/singletons/GameSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp
@@ -59,6 +59,7 @@ namespace OpenVic {
godot::Error load_defines_compatibility_mode(godot::PackedStringArray const& file_paths);
static godot::String search_for_game_path(godot::String const& hint_path = {});
+ godot::String lookup_file_path(godot::String const& path) const;
/* Post-load/restart game setup - reset the game to post-load state and load the specified bookmark. */
godot::Error setup_game(int32_t bookmark_index);
@@ -69,6 +70,7 @@ namespace OpenVic {
int32_t get_map_height() const;
godot::Vector2i get_map_dims() const;
float get_map_aspect_ratio() const;
+ godot::Vector2 map_position_to_world_coords(fvec2_t const& position) const;
/* The cosmetic terrain textures stored in a Texture2DArray. */
godot::Ref<godot::Texture2DArray> get_terrain_texture() const;
diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.cpp b/extension/src/openvic-extension/singletons/ModelSingleton.cpp
new file mode 100644
index 0000000..88a69c9
--- /dev/null
+++ b/extension/src/openvic-extension/singletons/ModelSingleton.cpp
@@ -0,0 +1,458 @@
+#include "ModelSingleton.hpp"
+
+#include <numbers>
+
+#include <godot_cpp/variant/utility_functions.hpp>
+
+#include "openvic-extension/singletons/GameSingleton.hpp"
+#include "openvic-extension/utility/ClassBindings.hpp"
+#include "openvic-extension/utility/Utilities.hpp"
+
+using namespace godot;
+using namespace OpenVic;
+
+using OpenVic::Utilities::godot_to_std_string;
+using OpenVic::Utilities::std_to_godot_string;
+using OpenVic::Utilities::std_view_to_godot_string;
+
+void ModelSingleton::_bind_methods() {
+ OV_BIND_METHOD(ModelSingleton::get_units);
+ OV_BIND_METHOD(ModelSingleton::get_cultural_gun_model, { "culture" });
+ OV_BIND_METHOD(ModelSingleton::get_cultural_helmet_model, { "culture" });
+ OV_BIND_METHOD(ModelSingleton::get_flag_model, { "floating" });
+ OV_BIND_METHOD(ModelSingleton::get_buildings);
+}
+
+ModelSingleton* ModelSingleton::get_singleton() {
+ return singleton;
+}
+
+ModelSingleton::ModelSingleton() {
+ ERR_FAIL_COND(singleton != nullptr);
+ singleton = this;
+}
+
+ModelSingleton::~ModelSingleton() {
+ ERR_FAIL_COND(singleton != this);
+ singleton = nullptr;
+}
+
+GFX::Actor const* ModelSingleton::get_actor(std::string_view name, bool error_on_fail) const {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, nullptr);
+
+ GFX::Actor const* actor =
+ game_singleton->get_game_manager().get_ui_manager().get_cast_object_by_identifier<GFX::Actor>(name);
+
+ if (error_on_fail) {
+ ERR_FAIL_NULL_V_MSG(actor, nullptr, vformat("Failed to find actor \"%s\"", std_view_to_godot_string(name)));
+ }
+
+ return actor;
+}
+
+GFX::Actor const* ModelSingleton::get_cultural_actor(
+ std::string_view culture, std::string_view name, std::string_view fallback_name
+) const {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, nullptr);
+
+ ERR_FAIL_COND_V_MSG(
+ culture.empty() || name.empty(), nullptr, vformat(
+ "Failed to find actor \"%s\" for culture \"%s\" - neither can be empty",
+ std_view_to_godot_string(name), std_view_to_godot_string(culture)
+ )
+ );
+
+ std::string actor_name = StringUtils::append_string_views(culture, name);
+
+ GFX::Actor const* actor = get_actor(actor_name, false);
+
+ // Which should be tried first: "Generic***" or "***Infantry"?
+
+ if (actor == nullptr) {
+ /* If no Actor exists for the specified GraphicalCultureType then try the default instead. */
+ GraphicalCultureType const* default_graphical_culture_type =
+ game_singleton->get_game_manager().get_pop_manager().get_culture_manager().get_default_graphical_culture_type();
+
+ if (default_graphical_culture_type != nullptr && default_graphical_culture_type->get_identifier() != culture) {
+ actor_name = StringUtils::append_string_views(default_graphical_culture_type->get_identifier(), name);
+
+ actor = get_actor(actor_name, false);
+ }
+
+ if (actor == nullptr && !fallback_name.empty() && fallback_name != name) {
+ return get_cultural_actor(culture, fallback_name, {});
+ }
+ }
+
+ ERR_FAIL_NULL_V_MSG(
+ actor, nullptr, vformat(
+ "Failed to find actor \"%s\" for culture \"%s\"", std_view_to_godot_string(name),
+ std_view_to_godot_string(culture)
+ )
+ );
+
+ return actor;
+}
+
+Dictionary ModelSingleton::make_animation_dict(GFX::Actor::Animation const& animation) const {
+ static const StringName file_key = "file";
+ static const StringName time_key = "time";
+
+ Dictionary dict;
+
+ dict[file_key] = std_view_to_godot_string(animation.get_file());
+ dict[time_key] = animation.get_scroll_time().to_float();
+
+ return dict;
+}
+
+Dictionary ModelSingleton::make_model_dict(GFX::Actor const& actor) const {
+ static const StringName file_key = "file";
+ static const StringName scale_key = "scale";
+ static const StringName idle_key = "idle";
+ static const StringName move_key = "move";
+ static const StringName attack_key = "attack";
+ static const StringName attachments_key = "attachments";
+
+ Dictionary dict;
+
+ dict[file_key] = std_view_to_godot_string(actor.get_model_file());
+ dict[scale_key] = actor.get_scale().to_float();
+
+ const auto set_animation = [this, &dict](StringName const& key, std::optional<GFX::Actor::Animation> const& animation) {
+ if (animation.has_value()) {
+ dict[key] = make_animation_dict(*animation);
+ }
+ };
+
+ set_animation(idle_key, actor.get_idle_animation());
+ set_animation(move_key, actor.get_move_animation());
+ set_animation(attack_key, actor.get_attack_animation());
+
+ std::vector<GFX::Actor::Attachment> const& attachments = actor.get_attachments();
+
+ if (!attachments.empty()) {
+ static const StringName attachment_node_key = "node";
+ static const StringName attachment_model_key = "model";
+
+ TypedArray<Dictionary> attachments_array;
+
+ if (attachments_array.resize(attachments.size()) == OK) {
+
+ for (size_t idx = 0; idx < attachments_array.size(); ++idx) {
+
+ GFX::Actor::Attachment const& attachment = attachments[idx];
+
+ GFX::Actor const* attachment_actor = get_actor(attachment.get_actor_name());
+
+ ERR_CONTINUE_MSG(
+ attachment_actor == nullptr, vformat(
+ "Failed to find \"%s\" attachment actor for actor \"%s\"",
+ std_view_to_godot_string(attachment.get_actor_name()), std_view_to_godot_string(actor.get_name())
+ )
+ );
+
+ Dictionary attachment_dict;
+
+ attachment_dict[attachment_node_key] = std_view_to_godot_string(attachment.get_attach_node());
+ attachment_dict[attachment_model_key] = make_model_dict(*attachment_actor);
+
+ attachments_array[idx] = std::move(attachment_dict);
+
+ }
+
+ if (!attachments_array.is_empty()) {
+ dict[attachments_key] = std::move(attachments_array);
+ }
+
+ } else {
+ UtilityFunctions::push_error(
+ "Failed to resize attachments array to the correct size (", static_cast<int64_t>(attachments.size()),
+ ") for model for actor \"", std_view_to_godot_string(actor.get_name()), "\""
+ );
+ }
+ }
+
+ return dict;
+}
+
+/* Returns false if an error occurs while trying to add a unit model for the province, true otherwise.
+ * Returning true doesn't necessarily mean a unit was added, e.g. when units is empty. */
+template<utility::is_derived_from_specialization_of<UnitInstanceGroup> T>
+bool ModelSingleton::add_unit_dict(ordered_set<T*> const& units, TypedArray<Dictionary>& unit_array) const {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, false);
+
+ static const StringName culture_key = "culture";
+ static const StringName model_key = "model";
+ static const StringName mount_model_key = "mount_model";
+ static const StringName mount_attach_node_key = "mount_attach_node";
+ static const StringName flag_index_key = "flag_index";
+ static const StringName flag_floating_key = "flag_floating";
+ static const StringName position_key = "position";
+ static const StringName rotation_key = "rotation";
+ static const StringName primary_colour_key = "primary_colour";
+ static const StringName secondary_colour_key = "secondary_colour";
+ static const StringName tertiary_colour_key = "tertiary_colour";
+
+ if (units.empty()) {
+ return true;
+ }
+
+ bool ret = true;
+
+ /* Last unit to enter the province is shown on top. */
+ T const& unit = *units.back();
+ ERR_FAIL_COND_V_MSG(unit.empty(), false, vformat("Empty unit \"%s\"", std_view_to_godot_string(unit.get_name())));
+
+ Country const* country = unit.get_country()->get_base_country();
+
+ GraphicalCultureType const& graphical_culture_type = country->get_graphical_culture();
+ UnitType const* display_unit_type = unit.get_display_unit_type();
+ ERR_FAIL_NULL_V_MSG(
+ display_unit_type, false, vformat(
+ "Failed to get display unit type for unit \"%s\"", std_view_to_godot_string(unit.get_name())
+ )
+ );
+
+ std::string_view actor_name = display_unit_type->get_sprite();
+ std::string_view mount_actor_name, mount_attach_node_name;
+
+ if constexpr (std::same_as<T, ArmyInstance>) {
+ RegimentType const* regiment_type = reinterpret_cast<RegimentType const*>(display_unit_type);
+
+ if (!regiment_type->get_sprite_override().empty()) {
+ actor_name = regiment_type->get_sprite_override();
+ }
+
+ if (regiment_type->get_sprite_mount().empty() == regiment_type->get_sprite_mount_attach_node().empty()) {
+ if (!regiment_type->get_sprite_mount().empty()) {
+ mount_actor_name = regiment_type->get_sprite_mount();
+ mount_attach_node_name = regiment_type->get_sprite_mount_attach_node();
+ }
+ } else {
+ UtilityFunctions::push_error(
+ "Mount sprite and attach node must both be set or both be empty - regiment type \"",
+ std_view_to_godot_string(regiment_type->get_identifier()), "\" has mount \"",
+ std_view_to_godot_string(regiment_type->get_sprite_mount()), "\" and attach node \"",
+ std_view_to_godot_string(regiment_type->get_sprite_mount_attach_node()), "\""
+ );
+ ret = false;
+ }
+ }
+
+ // TODO - default without requiring hardcoded name
+ static constexpr std::string_view default_fallback_actor_name = "Infantry";
+ GFX::Actor const* actor = get_cultural_actor(
+ graphical_culture_type.get_identifier(), actor_name, default_fallback_actor_name
+ );
+
+ ERR_FAIL_NULL_V_MSG(
+ actor, false, vformat(
+ "Failed to find \"%s\" actor of graphical culture type \"%s\" for unit \"%s\"",
+ std_view_to_godot_string(display_unit_type->get_sprite()),
+ std_view_to_godot_string(graphical_culture_type.get_identifier()),
+ std_view_to_godot_string(unit.get_name())
+ )
+ );
+
+ Dictionary dict;
+
+ dict[culture_key] = std_view_to_godot_string(graphical_culture_type.get_identifier());
+
+ dict[model_key] = make_model_dict(*actor);
+
+ if (!mount_actor_name.empty() && !mount_attach_node_name.empty()) {
+ GFX::Actor const* mount_actor = get_actor(mount_actor_name);
+
+ if (mount_actor != nullptr) {
+ dict[mount_model_key] = make_model_dict(*mount_actor);
+ dict[mount_attach_node_key] = std_view_to_godot_string(mount_attach_node_name);
+ } else {
+ UtilityFunctions::push_error(vformat(
+ "Failed to find \"%s\" mount actor of graphical culture type \"%s\" for unit \"%s\"",
+ std_view_to_godot_string(mount_actor_name),
+ std_view_to_godot_string(graphical_culture_type.get_identifier()),
+ std_view_to_godot_string(unit.get_name())
+ ));
+ ret = false;
+ }
+ }
+
+ // TODO - government type based flag type
+ dict[flag_index_key] = game_singleton->get_flag_sheet_index(country->get_index(), {});
+
+ if (display_unit_type->has_floating_flag()) {
+ dict[flag_floating_key] = true;
+ }
+
+ dict[position_key] = game_singleton->map_position_to_world_coords(unit.get_position()->get_unit_position());
+
+ if (display_unit_type->get_unit_category() != UnitType::unit_category_t::INFANTRY) {
+ dict[rotation_key] = -0.25f * std::numbers::pi_v<float>;
+ }
+
+ dict[primary_colour_key] = Utilities::to_godot_color(country->get_primary_unit_colour());
+ dict[secondary_colour_key] = Utilities::to_godot_color(country->get_secondary_unit_colour());
+ dict[tertiary_colour_key] = Utilities::to_godot_color(country->get_tertiary_unit_colour());
+
+ // TODO - move dict into unit_array ?
+ unit_array.push_back(dict);
+
+ return ret;
+}
+
+TypedArray<Dictionary> ModelSingleton::get_units() const {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, {});
+
+ TypedArray<Dictionary> ret;
+
+ for (Province const& province : game_singleton->get_game_manager().get_map().get_provinces()) {
+ if (province.is_water()) {
+ if (!add_unit_dict(province.get_navies(), ret)) {
+ UtilityFunctions::push_error(
+ "Error adding navy to province \"", std_view_to_godot_string(province.get_identifier()), "\""
+ );
+ }
+ } else {
+ if (!add_unit_dict(province.get_armies(), ret)) {
+ UtilityFunctions::push_error(
+ "Error adding army to province \"", std_view_to_godot_string(province.get_identifier()), "\""
+ );
+ }
+ }
+
+ // TODO - land units in ships
+ }
+
+ return ret;
+}
+
+Dictionary ModelSingleton::get_cultural_gun_model(String const& culture) const {
+ static constexpr std::string_view gun_actor_name = "Gun1";
+
+ GFX::Actor const* actor = get_cultural_actor(godot_to_std_string(culture), gun_actor_name, {});
+
+ ERR_FAIL_NULL_V(actor, {});
+
+ return make_model_dict(*actor);
+}
+
+Dictionary ModelSingleton::get_cultural_helmet_model(String const& culture) const {
+ static constexpr std::string_view helmet_actor_name = "Helmet1";
+
+ GFX::Actor const* actor = get_cultural_actor(godot_to_std_string(culture), helmet_actor_name, {});
+
+ ERR_FAIL_NULL_V(actor, {});
+
+ return make_model_dict(*actor);
+}
+
+Dictionary ModelSingleton::get_flag_model(bool floating) const {
+ static constexpr std::string_view flag_name = "Flag";
+ static constexpr std::string_view flag_floating_name = "FlagFloating";
+
+ GFX::Actor const* actor = get_actor(floating ? flag_floating_name : flag_name);
+
+ ERR_FAIL_NULL_V(actor, {});
+
+ return make_model_dict(*actor);
+}
+
+bool ModelSingleton::add_building_dict(
+ BuildingInstance const& building, Province const& province, TypedArray<Dictionary>& building_array
+) const {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, false);
+
+ static const StringName model_key = "model";
+ static const StringName position_key = "position";
+ static const StringName rotation_key = "rotation";
+
+ std::string suffix;
+
+ if (
+ &building.get_building_type() ==
+ game_singleton->get_game_manager().get_economy_manager().get_building_type_manager().get_port_building_type()
+ ) {
+ /* Port */
+ if (!province.has_port()) {
+ return true;
+ }
+
+ if (building.get_level() > 0) {
+ suffix = std::to_string(building.get_level());
+ }
+
+ if (!province.get_navies().empty()) {
+ suffix += "_ships";
+ }
+ } else if (building.get_identifier() == "fort") {
+ /* Fort */
+ if (building.get_level() < 1) {
+ return true;
+ }
+
+ if (building.get_level() > 1) {
+ suffix = std::to_string(building.get_level());
+ }
+ } else {
+ // TODO - railroad (trainstations)
+ return true;
+ }
+
+ fvec2_t const* position_ptr = province.get_building_position(&building.get_building_type());
+ const float rotation = province.get_building_rotation(&building.get_building_type());
+
+ const std::string actor_name = StringUtils::append_string_views("building_", building.get_identifier(), suffix);
+
+ GFX::Actor const* actor = get_actor(actor_name);
+ ERR_FAIL_NULL_V_MSG(
+ actor, false, vformat(
+ "Failed to find \"%s\" actor for building \"%s\" in province \"%s\"",
+ std_to_godot_string(actor_name), std_view_to_godot_string(building.get_identifier()),
+ std_view_to_godot_string(province.get_identifier())
+ )
+ );
+
+ Dictionary dict;
+
+ dict[model_key] = make_model_dict(*actor);
+
+ dict[position_key] =
+ game_singleton->map_position_to_world_coords(position_ptr != nullptr ? *position_ptr : province.get_centre());
+
+ if (rotation != 0.0f) {
+ dict[rotation_key] = rotation;
+ }
+
+ // TODO - move dict into unit_array ?
+ building_array.push_back(dict);
+
+ return true;
+}
+
+TypedArray<Dictionary> ModelSingleton::get_buildings() const {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, {});
+
+ TypedArray<Dictionary> ret;
+
+ for (Province const& province : game_singleton->get_game_manager().get_map().get_provinces()) {
+ if (!province.is_water()) {
+ for (BuildingInstance const& building : province.get_buildings()) {
+ if (!add_building_dict(building, province, ret)) {
+ UtilityFunctions::push_error(
+ "Error adding building \"", std_view_to_godot_string(building.get_identifier()), "\" to province \"",
+ std_view_to_godot_string(province.get_identifier()), "\""
+ );
+ }
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.hpp b/extension/src/openvic-extension/singletons/ModelSingleton.hpp
new file mode 100644
index 0000000..17c2dd0
--- /dev/null
+++ b/extension/src/openvic-extension/singletons/ModelSingleton.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <godot_cpp/classes/object.hpp>
+
+#include <openvic-simulation/interface/GFXObject.hpp>
+#include <openvic-simulation/military/UnitInstance.hpp>
+
+namespace OpenVic {
+ class ModelSingleton : public godot::Object {
+ GDCLASS(ModelSingleton, godot::Object)
+
+ static inline ModelSingleton* singleton = nullptr;
+
+ protected:
+ static void _bind_methods();
+
+ public:
+ static ModelSingleton* get_singleton();
+
+ ModelSingleton();
+ ~ModelSingleton();
+
+ private:
+ GFX::Actor const* get_actor(std::string_view name, bool error_on_fail = true) const;
+ GFX::Actor const* get_cultural_actor(
+ std::string_view culture, std::string_view name, std::string_view fallback_name
+ ) const;
+
+ godot::Dictionary make_animation_dict(GFX::Actor::Animation const& animation) const;
+ godot::Dictionary make_model_dict(GFX::Actor const& actor) const;
+
+ template<utility::is_derived_from_specialization_of<UnitInstanceGroup> T>
+ bool add_unit_dict(ordered_set<T*> const& units, godot::TypedArray<godot::Dictionary>& unit_array) const;
+
+ bool add_building_dict(
+ BuildingInstance const& building, Province const& province, godot::TypedArray<godot::Dictionary>& building_array
+ ) const;
+
+ public:
+ godot::TypedArray<godot::Dictionary> get_units() const;
+ godot::Dictionary get_cultural_gun_model(godot::String const& culture) const;
+ godot::Dictionary get_cultural_helmet_model(godot::String const& culture) const;
+
+ godot::Dictionary get_flag_model(bool floating) const;
+
+ godot::TypedArray<godot::Dictionary> get_buildings() const;
+ };
+}