From 2ac43ba7df3b2c3dc40c6b87c2bc57c4b02ffa42 Mon Sep 17 00:00:00 2001 From: hop311 Date: Sun, 5 May 2024 18:36:21 +0100 Subject: Add GDScript XAC and XSM loaders --- extension/src/openvic-extension/singletons/GameSingleton.hpp | 1 + 1 file changed, 1 insertion(+) (limited to 'extension/src/openvic-extension/singletons/GameSingleton.hpp') diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index f2b88ac..f793fae 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); -- cgit v1.2.3-56-ga3b1 From ac1504f6fa981947bd9543d24325b3751236e925 Mon Sep 17 00:00:00 2001 From: hop311 Date: Tue, 7 May 2024 22:05:55 +0100 Subject: Generate army and navy models --- extension/src/openvic-extension/register_types.cpp | 9 + .../openvic-extension/singletons/GameSingleton.cpp | 4 + .../openvic-extension/singletons/GameSingleton.hpp | 1 + .../singletons/ModelSingleton.cpp | 362 +++++++++++++++++++++ .../singletons/ModelSingleton.hpp | 42 +++ game/src/Game/GameSession/GameSession.gd | 3 + game/src/Game/GameSession/GameSession.tscn | 11 +- game/src/Game/GameSession/MapView.tscn | 6 + game/src/Game/GameSession/ModelManager.gd | 131 ++++++++ 9 files changed, 567 insertions(+), 2 deletions(-) create mode 100644 extension/src/openvic-extension/singletons/ModelSingleton.cpp create mode 100644 extension/src/openvic-extension/singletons/ModelSingleton.hpp create mode 100644 game/src/Game/GameSession/ModelManager.gd (limited to 'extension/src/openvic-extension/singletons/GameSingleton.hpp') 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(); + _model_singleton = memnew(ModelSingleton); + Engine::get_singleton()->register_singleton("ModelSingleton", ModelSingleton::get_singleton()); + ClassDB::register_class(); _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 12e2df1..ef19a6c 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -159,6 +159,10 @@ float GameSingleton::get_map_aspect_ratio() const { return static_cast(get_map_width()) / static_cast(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 GameSingleton::get_terrain_texture() const { return terrain_texture; } diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index f793fae..7f86eb2 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -70,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 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..349d336 --- /dev/null +++ b/extension/src/openvic-extension/singletons/ModelSingleton.cpp @@ -0,0 +1,362 @@ +#include "ModelSingleton.hpp" + +#include + +#include + +#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" }); +} + +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(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 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 const& attachments = actor.get_attachments(); + + if (!attachments.empty()) { + static const StringName attachment_node_key = "node"; + static const StringName attachment_model_key = "model"; + + TypedArray 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(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 T> +bool ModelSingleton::add_unit_dict(ordered_set const& units, TypedArray& 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) { + RegimentType const* regiment_type = reinterpret_cast(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; + } + + 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 ModelSingleton::get_units() const { + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, {}); + + TypedArray 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); +} diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.hpp b/extension/src/openvic-extension/singletons/ModelSingleton.hpp new file mode 100644 index 0000000..9ec163e --- /dev/null +++ b/extension/src/openvic-extension/singletons/ModelSingleton.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + +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 T> + bool add_unit_dict(ordered_set const& units, godot::TypedArray& unit_array) const; + + public: + godot::TypedArray 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; + }; +} diff --git a/game/src/Game/GameSession/GameSession.gd b/game/src/Game/GameSession/GameSession.gd index f085073..a07ce32 100644 --- a/game/src/Game/GameSession/GameSession.gd +++ b/game/src/Game/GameSession/GameSession.gd @@ -1,5 +1,6 @@ extends Control +@export var _model_manager : ModelManager @export var _game_session_menu : Control func _ready() -> void: @@ -7,6 +8,8 @@ func _ready() -> void: if GameSingleton.setup_game(0) != OK: push_error("Failed to setup game") + _model_manager.generate_units() + func _process(_delta : float) -> void: GameSingleton.try_tick() diff --git a/game/src/Game/GameSession/GameSession.tscn b/game/src/Game/GameSession/GameSession.tscn index 343ddfe..d54970f 100644 --- a/game/src/Game/GameSession/GameSession.tscn +++ b/game/src/Game/GameSession/GameSession.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=18 format=3 uid="uid://bgnupcshe1m7r"] +[gd_scene load_steps=19 format=3 uid="uid://bgnupcshe1m7r"] [ext_resource type="Script" path="res://src/Game/GameSession/GameSession.gd" id="1_eklvp"] [ext_resource type="PackedScene" uid="uid://cvl76duuym1wq" path="res://src/Game/MusicConductor/MusicPlayer.tscn" id="2_kt6aa"] [ext_resource type="PackedScene" uid="uid://g524p8lr574w" path="res://src/Game/GameSession/MapControlPanel/MapControlPanel.tscn" id="3_afh6d"] [ext_resource type="PackedScene" uid="uid://dvdynl6eir40o" path="res://src/Game/GameSession/GameSessionMenu.tscn" id="3_bvmqh"] +[ext_resource type="Script" path="res://src/Game/GameSession/ModelManager.gd" id="3_qwk4j"] [ext_resource type="Script" path="res://src/Game/GameSession/Topbar.gd" id="4_2kbih"] [ext_resource type="PackedScene" uid="uid://dkehmdnuxih2r" path="res://src/Game/GameSession/MapView.tscn" id="4_xkg5j"] [ext_resource type="Script" path="res://src/Game/GameSession/NationManagementScreen/ProductionMenu.gd" id="5_16755"] @@ -18,7 +19,7 @@ [ext_resource type="Script" path="res://src/Game/GameSession/NationManagementScreen/DiplomacyMenu.gd" id="11_fu7ys"] [ext_resource type="Script" path="res://src/Game/GameSession/NationManagementScreen/MilitaryMenu.gd" id="12_6h6nc"] -[node name="GameSession" type="Control" node_paths=PackedStringArray("_game_session_menu")] +[node name="GameSession" type="Control" node_paths=PackedStringArray("_model_manager", "_game_session_menu")] editor_description = "SS-102, UI-546" layout_mode = 3 anchors_preset = 15 @@ -28,10 +29,15 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 script = ExtResource("1_eklvp") +_model_manager = NodePath("ModelManager") _game_session_menu = NodePath("UICanvasLayer/UI/GameSessionMenu") [node name="MapView" parent="." instance=ExtResource("4_xkg5j")] +[node name="ModelManager" type="Node3D" parent="." node_paths=PackedStringArray("_map_view")] +script = ExtResource("3_qwk4j") +_map_view = NodePath("../MapView") + [node name="UICanvasLayer" type="CanvasLayer" parent="."] [node name="UI" type="Control" parent="UICanvasLayer"] @@ -143,6 +149,7 @@ offset_left = -150.0 offset_right = 0.0 grow_horizontal = 0 +[connection signal="detailed_view_changed" from="MapView" to="ModelManager" method="set_visible"] [connection signal="map_view_camera_changed" from="MapView" to="UICanvasLayer/UI/MapControlPanel" method="_on_map_view_camera_changed"] [connection signal="game_session_menu_button_pressed" from="UICanvasLayer/UI/MapControlPanel" to="." method="_on_game_session_menu_button_pressed"] [connection signal="minimap_clicked" from="UICanvasLayer/UI/MapControlPanel" to="MapView" method="_on_minimap_clicked"] diff --git a/game/src/Game/GameSession/MapView.tscn b/game/src/Game/GameSession/MapView.tscn index dff02a6..385a24d 100644 --- a/game/src/Game/GameSession/MapView.tscn +++ b/game/src/Game/GameSession/MapView.tscn @@ -50,4 +50,10 @@ mesh = SubResource("MapMesh_3gtsd") transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, -1, 0) mesh = SubResource("PlaneMesh_fnhgl") +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 10, 10) +light_energy = 1.5 +light_bake_mode = 0 +sky_mode = 1 + [connection signal="detailed_view_changed" from="." to="MapText" method="set_visible"] diff --git a/game/src/Game/GameSession/ModelManager.gd b/game/src/Game/GameSession/ModelManager.gd new file mode 100644 index 0000000..17d2c1e --- /dev/null +++ b/game/src/Game/GameSession/ModelManager.gd @@ -0,0 +1,131 @@ +class_name ModelManager +extends Node3D + +@export var _map_view : MapView + +const MODEL_SCALE : float = 1.0 / 256.0 + +func generate_units() -> void: + XACLoader.setup_flag_shader() + + for unit : Dictionary in ModelSingleton.get_units(): + _generate_unit(unit) + +func _generate_unit(unit_dict : Dictionary) -> void: + const culture_key : StringName = &"culture" + const model_key : StringName = &"model" + const mount_model_key : StringName = &"mount_model" + const mount_attach_node_key : StringName = &"mount_attach_node" + const flag_index_key : StringName = &"flag_index" + const flag_floating_key : StringName = &"flag_floating" + const position_key : StringName = &"position" + const rotation_key : StringName = &"rotation" + const primary_colour_key : StringName = &"primary_colour" + const secondary_colour_key : StringName = &"secondary_colour" + const tertiary_colour_key : StringName = &"tertiary_colour" + + var model : Node3D = _generate_model(unit_dict[model_key], unit_dict[culture_key]) + if not model: + return + + if mount_model_key in unit_dict and mount_attach_node_key in unit_dict: + # This must be a UnitModel so we can attach the rider to it + var mount_model : Node3D = _generate_model(unit_dict[mount_model_key], unit_dict[culture_key], true) + if mount_model: + mount_model.attach_model(unit_dict[mount_attach_node_key], model) + model = mount_model + + var rotation : float = unit_dict.get(rotation_key, 0.0) + + var flag_dict : Dictionary = ModelSingleton.get_flag_model(unit_dict.get(flag_floating_key, false)) + if flag_dict: + var flag_model : UnitModel = _generate_model(flag_dict, "", true) + if flag_model: + flag_model.set_flag_index(unit_dict[flag_index_key]) + flag_model.current_anim = UnitModel.Anim.IDLE + flag_model.scale /= model.scale + flag_model.rotate_y(-rotation) + + model.add_child(flag_model) + + model.scale *= MODEL_SCALE + model.rotate_y(PI + rotation) + model.set_position(_map_view._map_to_world_coords(unit_dict[position_key]) + Vector3(0, 0.1 * MODEL_SCALE, 0)) + + if model is UnitModel: + model.current_anim = UnitModel.Anim.IDLE + + model.primary_colour = unit_dict[primary_colour_key] + model.secondary_colour = unit_dict[secondary_colour_key] + model.tertiary_colour = unit_dict[tertiary_colour_key] + + add_child(model) + +func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : bool = false) -> Node3D: + const file_key : StringName = &"file" + const scale_key : StringName = &"scale" + const idle_key : StringName = &"idle" + const move_key : StringName = &"move" + const attack_key : StringName = &"attack" + const attachments_key : StringName = &"attachments" + + const animation_file_key : StringName = &"file" + const animation_time_key : StringName = &"time" + + const attachment_node_key : StringName = &"node" + const attachment_model_key : StringName = &"model" + + # Model + is_unit = is_unit or ( + # Needed for animations + idle_key in model_dict or move_key in model_dict or attack_key in model_dict + # Currently needs UnitModel's attach_model helper function + or attachments_key in model_dict + ) + + var model : Node3D = XACLoader.get_xac_model(model_dict[file_key], is_unit) + if not model: + return null + model.scale *= model_dict[scale_key] + + if model is UnitModel: + # Animations + var idle_dict : Dictionary = model_dict.get(idle_key, {}) + if idle_dict: + model.idle_anim = XSMLoader.get_xsm_animation(idle_dict[animation_file_key]) + model.scroll_speed_idle = idle_dict[animation_time_key] + + var move_dict : Dictionary = model_dict.get(move_key, {}) + if move_dict: + model.move_anim = XSMLoader.get_xsm_animation(move_dict[animation_file_key]) + model.scroll_speed_move = move_dict[animation_time_key] + + var attack_dict : Dictionary = model_dict.get(attack_key, {}) + if attack_dict: + model.attack_anim = XSMLoader.get_xsm_animation(attack_dict[animation_file_key]) + model.scroll_speed_attack = attack_dict[animation_time_key] + + # Attachments + for attachment_dict : Dictionary in model_dict.get(attachments_key, []): + var attachment_model : Node3D = _generate_model(attachment_dict[attachment_model_key], culture) + if attachment_model: + model.attach_model(attachment_dict[attachment_node_key], attachment_model) + + if culture: + const gun_bone_name : String = "GunNode" + if model.has_bone(gun_bone_name): + var gun_dict : Dictionary = ModelSingleton.get_cultural_gun_model(culture) + if gun_dict: + var gun_model : Node3D = _generate_model(gun_dict, culture) + if gun_model: + model.attach_model(gun_bone_name, gun_model) + + const helmet_bone_name : String = "HelmetNode" + if model.has_bone(helmet_bone_name): + var helmet_dict : Dictionary = ModelSingleton.get_cultural_helmet_model(culture) + if helmet_dict: + var helmet_model : Node3D = _generate_model(helmet_dict, culture) + if helmet_model: + model.attach_model(helmet_bone_name, helmet_model) + + return model -- cgit v1.2.3-56-ga3b1