diff options
author | Hop311 <Hop3114@gmail.com> | 2023-11-17 21:17:55 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-17 21:17:55 +0100 |
commit | 9165f5980c5cfe75b3bad4303a5822340f6adcfc (patch) | |
tree | 79a69cb7601bf1367e4ee4f10ebf61b698319bf3 | |
parent | 72d893d55d26ae9dc6739a853d1773b3cb286123 (diff) | |
parent | aefc61a1aff091e31436c60d004531b5905cecba (diff) |
Merge pull request #166 from OpenVicProject/gui
GUI elements -> Godot UI nodes generator
29 files changed, 1347 insertions, 518 deletions
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation -Subproject fd85491cdbb13760f039787e0d19d534a24027f +Subproject e76336cd92639f4ec71088fc4c80aea4c25528c diff --git a/extension/src/openvic-extension/UIAdapter.cpp b/extension/src/openvic-extension/UIAdapter.cpp new file mode 100644 index 0000000..d688754 --- /dev/null +++ b/extension/src/openvic-extension/UIAdapter.cpp @@ -0,0 +1,404 @@ +#include "UIAdapter.hpp" + +#include <godot_cpp/classes/button.hpp> +#include <godot_cpp/classes/check_box.hpp> +#include <godot_cpp/classes/color_rect.hpp> +#include <godot_cpp/classes/label.hpp> +#include <godot_cpp/classes/panel.hpp> +#include <godot_cpp/classes/style_box_texture.hpp> +#include <godot_cpp/classes/texture_progress_bar.hpp> +#include <godot_cpp/classes/texture_rect.hpp> +#include <godot_cpp/classes/theme.hpp> +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/classes/GFXIconTexture.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +using OpenVic::Utilities::std_view_to_godot_string; +using OpenVic::Utilities::std_view_to_godot_string_name; + +bool GodotGUIBuilder::generate_element(GUI::Element const* element, AssetManager& asset_manager, Control*& result) { + if (element == nullptr) { + UtilityFunctions::push_error("Invalid element passed to GodotGUIBuilder - null!"); + return false; + } + static const std::map<std::string_view, bool (*)(GUI::Element const&, AssetManager&, Control*&)> type_map { + { GUI::Icon::get_type_static(), &generate_icon }, + { GUI::Button::get_type_static(), &generate_button }, + { GUI::Checkbox::get_type_static(), &generate_checkbox }, + { GUI::Text::get_type_static(), &generate_text }, + { GUI::OverlappingElementsBox::get_type_static(), &generate_overlapping_elements }, + { GUI::ListBox::get_type_static(), &generate_listbox }, + { GUI::Window::get_type_static(), &generate_window } + }; + const decltype(type_map)::const_iterator it = type_map.find(element->get_type()); + if (it != type_map.end()) { + return it->second(*element, asset_manager, result); + } else { + UtilityFunctions::push_error("Invalid GUI element type: ", std_view_to_godot_string(element->get_type())); + result = nullptr; + return false; + } +} + +template<std::derived_from<Control> T> +static T* new_control(GUI::Element const& element) { + T* node = memnew(T); + ERR_FAIL_NULL_V(node, nullptr); + + using enum GUI::Element::orientation_t; + using enum Control::LayoutPreset; + static const std::map<GUI::Element::orientation_t, Control::LayoutPreset> orientation_map { + { UPPER_LEFT, PRESET_TOP_LEFT }, { LOWER_LEFT, PRESET_BOTTOM_LEFT }, + { LOWER_RIGHT, PRESET_BOTTOM_RIGHT }, { UPPER_RIGHT, PRESET_TOP_RIGHT }, + { CENTER, PRESET_CENTER } + }; + + node->set_name(std_view_to_godot_string(element.get_name())); + const decltype(orientation_map)::const_iterator it = orientation_map.find(element.get_orientation()); + if (it != orientation_map.end()) { + node->set_anchors_and_offsets_preset(it->second); + } else { + UtilityFunctions::push_error("Invalid orientation for GUI element ", + std_view_to_godot_string(element.get_name())); + } + node->set_position(Utilities::to_godot_fvec2(element.get_position())); + node->set_focus_mode(Control::FOCUS_NONE); + + return node; +} + +bool GodotGUIBuilder::generate_icon(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { + GUI::Icon const& icon = static_cast<GUI::Icon const&>(element); + + result = nullptr; + const String icon_name = std_view_to_godot_string(icon.get_name()); + + /* Change to use sprite type to choose Godot node type! */ + bool ret = true; + if (icon.get_sprite() != nullptr) { + if (icon.get_sprite()->is_type<GFX::TextureSprite>()) { + TextureRect* godot_texture_rect = new_control<TextureRect>(icon); + if (godot_texture_rect == nullptr) { + UtilityFunctions::push_error("Failed to create TextureRect for GUI icon ", icon_name); + return false; + } + + GFX::TextureSprite const* texture_sprite = icon.get_sprite()->cast_to<GFX::TextureSprite>(); + Ref<GFXIconTexture> texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite, icon.get_frame()); + if (texture.is_valid()) { + godot_texture_rect->set_texture(texture); + } else { + UtilityFunctions::push_error("Failed to make GFXIconTexture for GUI icon ", icon_name); + ret = false; + } + + result = godot_texture_rect; + } else if (icon.get_sprite()->is_type<GFX::MaskedFlag>()) { + TextureRect* godot_texture_rect = new_control<TextureRect>(icon); + if (godot_texture_rect == nullptr) { + UtilityFunctions::push_error("Failed to create TextureRect for GUI icon ", icon_name); + return false; + } + + const StringName texture_file = + std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::MaskedFlag>()->get_texture_file()); + const Ref<ImageTexture> texture = asset_manager.get_texture(texture_file); + if (texture.is_valid()) { + godot_texture_rect->set_texture(texture); + } else { + UtilityFunctions::push_error("Failed to load masked flag sprite ", texture_file, " for GUI icon ", icon_name); + ret = false; + } + + result = godot_texture_rect; + } else if (icon.get_sprite()->is_type<GFX::ProgressBar>()) { + TextureProgressBar* godot_progress_bar = new_control<TextureProgressBar>(icon); + if (godot_progress_bar == nullptr) { + UtilityFunctions::push_error("Failed to create TextureProgressBar for GUI icon ", icon_name); + return false; + } + + const StringName back_texture_file = + std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::ProgressBar>()->get_back_texture_file()); + const Ref<ImageTexture> back_texture = asset_manager.get_texture(back_texture_file); + if (back_texture.is_valid()) { + godot_progress_bar->set_under_texture(back_texture); + } else { + UtilityFunctions::push_error("Failed to load progress bar base sprite ", back_texture_file, " for GUI icon ", icon_name); + ret = false; + } + + const StringName progress_texture_file = + std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::ProgressBar>()->get_progress_texture_file()); + const Ref<ImageTexture> progress_texture = asset_manager.get_texture(progress_texture_file); + if (progress_texture.is_valid()) { + godot_progress_bar->set_progress_texture(progress_texture); + } else { + UtilityFunctions::push_error( + "Failed to load progress bar base sprite ", progress_texture_file, " for GUI icon ", icon_name + ); + ret = false; + } + + result = godot_progress_bar; + } else if (icon.get_sprite()->is_type<GFX::PieChart>()) { + + } else if (icon.get_sprite()->is_type<GFX::LineChart>()) { + + } else { + UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(icon.get_sprite()->get_type()), + " for GUI icon ", icon_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Null sprite for GUI icon ", icon_name); + ret = false; + } + return ret; +} + +bool GodotGUIBuilder::generate_button(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { + GUI::Button const& button = static_cast<GUI::Button const&>(element); + + // TODO - shortcut, sprite, text + result = nullptr; + const String button_name = std_view_to_godot_string(button.get_name()); + + Button* godot_button = new_control<Button>(button); + if (godot_button == nullptr) { + UtilityFunctions::push_error("Failed to create Button for GUI button ", button_name); + return false; + } + + godot_button->set_text(std_view_to_godot_string(button.get_text())); + //godot_button->set_flat(true); + + bool ret = true; + if (button.get_sprite() != nullptr) { + Ref<Texture2D> texture; + if (button.get_sprite()->is_type<GFX::TextureSprite>()) { + GFX::TextureSprite const* texture_sprite = button.get_sprite()->cast_to<GFX::TextureSprite>(); + texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite); + if (texture.is_null()) { + UtilityFunctions::push_error("Failed to make GFXIconTexture for GUI button ", button_name); + ret = false; + } + } else if (button.get_sprite()->is_type<GFX::MaskedFlag>()) { + texture = asset_manager.get_texture(std_view_to_godot_string_name( + button.get_sprite()->cast_to<GFX::MaskedFlag>()->get_texture_file())); + if (texture.is_null()) { + UtilityFunctions::push_error("Failed to load masked flag sprite for GUI button ", button_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(button.get_sprite()->get_type()), + " for GUI button ", button_name); + ret = false; + } + + if (texture.is_valid()) { + godot_button->set_size(texture->get_size()); + Ref<StyleBoxTexture> stylebox; + stylebox.instantiate(); + if (stylebox.is_valid()) { + stylebox->set_texture(texture); + godot_button->add_theme_stylebox_override("normal", stylebox); + } else { + UtilityFunctions::push_error("Failed to load instantiate texture stylebox for GUI button ", button_name); + ret = false; + } + } + } else { + UtilityFunctions::push_error("Null sprite for GUI button ", button_name); + ret = false; + } + + if (button.get_font() != nullptr) { + const StringName font_file = std_view_to_godot_string_name(button.get_font()->get_fontname()); + const Ref<Font> font = asset_manager.get_font(font_file); + if (font.is_valid()) { + godot_button->add_theme_font_override("font", font); + } else { + UtilityFunctions::push_error("Failed to load font for GUI button ", button_name); + ret = false; + } + const Color colour = Utilities::to_godot_color(button.get_font()->get_colour()); + godot_button->add_theme_color_override("font_color", colour); + } + + result = godot_button; + return ret; +} + +bool GodotGUIBuilder::generate_checkbox(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { + GUI::Checkbox const& checkbox = static_cast<GUI::Checkbox const&>(element); + + // TODO - shortcut, sprite, text + result = nullptr; + const String checkbox_name = std_view_to_godot_string(checkbox.get_name()); + + CheckBox* godot_checkbox = new_control<CheckBox>(checkbox); + if (godot_checkbox == nullptr) { + UtilityFunctions::push_error("Failed to create CheckBox for GUI checkbox ", checkbox_name); + return false; + } + + bool ret = true; + if (checkbox.get_sprite() != nullptr) { + GFX::TextureSprite const* texture_sprite = checkbox.get_sprite()->cast_to<GFX::TextureSprite>(); + if (texture_sprite != nullptr) { + Ref<GFXIconTexture> icon_texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite, 1); + if (icon_texture.is_valid()) { + godot_checkbox->set_size(icon_texture->get_size()); + godot_checkbox->add_theme_icon_override("unchecked", icon_texture); + } else { + UtilityFunctions::push_error("Failed to make unchecked GFXIconTexture for GUI checkbox ", checkbox_name); + ret = false; + } + icon_texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite, 2); + if (icon_texture.is_valid()) { + godot_checkbox->add_theme_icon_override("checked", icon_texture); + } else { + UtilityFunctions::push_error("Failed to make checked GFXIconTexture for GUI checkbox ", checkbox_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(checkbox.get_sprite()->get_type()), + " for GUI checkbox ", checkbox_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Null sprite for GUI checkbox ", checkbox_name); + ret = false; + } + + result = godot_checkbox; + return ret; +} + +bool GodotGUIBuilder::generate_text(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { + GUI::Text const& text = static_cast<GUI::Text const&>(element); + + result = nullptr; + const String text_name = std_view_to_godot_string(text.get_name()); + + Label* godot_label = new_control<Label>(text); + if (godot_label == nullptr) { + UtilityFunctions::push_error("Failed to create Label for GUI text ", text_name); + return false; + } + + godot_label->set_text(std_view_to_godot_string(text.get_text())); + godot_label->set_size(Utilities::to_godot_fvec2(text.get_max_size())); + + using enum GUI::AlignedElement::format_t; + using enum HorizontalAlignment; + static const std::map<GUI::AlignedElement::format_t, HorizontalAlignment> format_map { + { left, HORIZONTAL_ALIGNMENT_LEFT }, + { centre, HORIZONTAL_ALIGNMENT_CENTER }, + { right, HORIZONTAL_ALIGNMENT_RIGHT } + }; + + const decltype(format_map)::const_iterator it = format_map.find(text.get_format()); + if (it != format_map.end()) { + godot_label->set_horizontal_alignment(it->second); + } else { + UtilityFunctions::push_error("Invalid text format (horizontal alignment) for GUI text ", text_name); + } + + bool ret = true; + if (text.get_font() != nullptr) { + const StringName font_file = std_view_to_godot_string_name(text.get_font()->get_fontname()); + const Ref<Font> font = asset_manager.get_font(font_file); + if (font.is_valid()) { + godot_label->add_theme_font_override("font", font); + } else { + UtilityFunctions::push_error("Failed to load font for GUI text ", text_name); + ret = false; + } + const Color colour = Utilities::to_godot_color(text.get_font()->get_colour()); + godot_label->add_theme_color_override("font_color", colour); + } + + result = godot_label; + return ret; +} + +bool GodotGUIBuilder::generate_overlapping_elements( + GUI::Element const& element, AssetManager& asset_manager, Control*& result +) { + GUI::OverlappingElementsBox const& overlapping_elements = static_cast<GUI::OverlappingElementsBox const&>(element); + + result = nullptr; + const String overlapping_elements_name = std_view_to_godot_string(overlapping_elements.get_name()); + + ColorRect* godot_rect = new_control<ColorRect>(overlapping_elements); + if (godot_rect == nullptr) { + UtilityFunctions::push_error("Failed to create ColorRect for GUI overlapping elements ", + overlapping_elements_name); + return false; + } + + godot_rect->set_size(Utilities::to_godot_fvec2(overlapping_elements.get_size())); + godot_rect->set_color({ 0.0f, 0.5f, 1.0f, 0.2f }); + + result = godot_rect; + return true; +} + +bool GodotGUIBuilder::generate_listbox(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { + GUI::ListBox const& listbox = static_cast<GUI::ListBox const&>(element); + + result = nullptr; + const String listbox_name = std_view_to_godot_string(listbox.get_name()); + + ColorRect* godot_rect = new_control<ColorRect>(listbox); + if (godot_rect == nullptr) { + UtilityFunctions::push_error("Failed to create ColorRect for GUI listbox ", listbox_name); + return false; + } + + godot_rect->set_size(Utilities::to_godot_fvec2(listbox.get_size())); + godot_rect->set_color({ 1.0f, 0.5f, 0.0f, 0.2f }); + + result = godot_rect; + return true; +} + +bool GodotGUIBuilder::generate_window(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { + GUI::Window const& window = static_cast<GUI::Window const&>(element); + + // TODO - moveable, fullscreen, dontRender (disable visibility?) + result = nullptr; + const String window_name = std_view_to_godot_string(window.get_name()); + + Panel* godot_panel = new_control<Panel>(window); + if (godot_panel == nullptr) { + UtilityFunctions::push_error("Failed to create Panel for GUI window ", window_name); + return false; + } + + godot_panel->set_size(Utilities::to_godot_fvec2(window.get_size())); + godot_panel->set_self_modulate({ 1.0f, 1.0f, 1.0f, 0.0f }); + + bool ret = true; + for (std::unique_ptr<GUI::Element> const& element : window.get_elements()) { + Control* node = nullptr; + const bool element_ret = generate_element(element.get(), asset_manager, node); + if (element_ret) { + if (node != nullptr) { + godot_panel->add_child(node); + } + } else { + UtilityFunctions::push_error("Failed to generate GUI element ", std_view_to_godot_string(element->get_name())); + ret = false; + } + } + + result = godot_panel; + return ret; +} diff --git a/extension/src/openvic-extension/UIAdapter.hpp b/extension/src/openvic-extension/UIAdapter.hpp new file mode 100644 index 0000000..d54a6b5 --- /dev/null +++ b/extension/src/openvic-extension/UIAdapter.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <godot_cpp/classes/control.hpp> + +#include <openvic-simulation/interface/GUI.hpp> + +#include "openvic-extension/singletons/AssetManager.hpp" + +namespace OpenVic::GodotGUIBuilder { + bool generate_element(GUI::Element const* element, AssetManager& asset_manager, godot::Control*& result); + + bool generate_icon(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); + bool generate_button(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); + bool generate_checkbox(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); + bool generate_text(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); + bool generate_overlapping_elements(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); + bool generate_listbox(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); + bool generate_window(GUI::Element const& element, AssetManager& asset_manager, godot::Control*& result); +} diff --git a/extension/src/openvic-extension/classes/GFXIconTexture.cpp b/extension/src/openvic-extension/classes/GFXIconTexture.cpp new file mode 100644 index 0000000..9edfb1b --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXIconTexture.cpp @@ -0,0 +1,112 @@ +#include "GFXIconTexture.hpp" + +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/singletons/AssetManager.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::std_view_to_godot_string; +using OpenVic::Utilities::std_view_to_godot_string_name; + +void GFXIconTexture::_bind_methods() { + OV_BIND_METHOD(GFXIconTexture::clear); + + OV_BIND_METHOD(GFXIconTexture::set_gfx_texture_sprite_name, { "gfx_texture_sprite_name" }, DEFVAL(GFX::NO_FRAMES)); + OV_BIND_METHOD(GFXIconTexture::get_gfx_texture_sprite_name); + + OV_BIND_METHOD(GFXIconTexture::set_icon_index, { "new_icon_index" }); + OV_BIND_METHOD(GFXIconTexture::get_icon_index); + OV_BIND_METHOD(GFXIconTexture::get_icon_count); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_index"), "set_icon_index", "get_icon_index"); +} + +GFXIconTexture::GFXIconTexture() + : gfx_texture_sprite { nullptr }, icon_index { GFX::NO_FRAMES }, icon_count { GFX::NO_FRAMES } {} + +Ref<GFXIconTexture> GFXIconTexture::make_gfx_icon_texture(GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon) { + Ref<GFXIconTexture> icon_texture; + icon_texture.instantiate(); + ERR_FAIL_NULL_V(icon_texture, icon_texture); + icon_texture->set_gfx_texture_sprite(gfx_texture_sprite, icon); + return icon_texture; +} + +void GFXIconTexture::clear() { + gfx_texture_sprite = nullptr; + set_atlas(nullptr); + set_region({}); + icon_index = GFX::NO_FRAMES; + icon_count = GFX::NO_FRAMES; +} + +Error GFXIconTexture::set_gfx_texture_sprite(GFX::TextureSprite const* new_gfx_texture_sprite, GFX::frame_t icon) { + if (gfx_texture_sprite != new_gfx_texture_sprite) { + if (new_gfx_texture_sprite == nullptr) { + clear(); + return OK; + } + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + const StringName texture_file = std_view_to_godot_string_name(new_gfx_texture_sprite->get_texture_file()); + const Ref<ImageTexture> texture = asset_manager->get_texture(texture_file); + ERR_FAIL_NULL_V_MSG(texture, FAILED, "Failed to load texture: " + texture_file); + gfx_texture_sprite = new_gfx_texture_sprite; + set_atlas(texture); + icon_index = GFX::NO_FRAMES; + icon_count = gfx_texture_sprite->get_no_of_frames(); + } + return set_icon_index(icon); +} + +Error GFXIconTexture::set_gfx_texture_sprite_name(String const& gfx_texture_sprite_name, GFX::frame_t icon) { + if (gfx_texture_sprite_name.is_empty()) { + return set_gfx_texture_sprite(nullptr); + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + GFX::Sprite const* sprite = game_singleton->get_gfx_sprite(gfx_texture_sprite_name); + ERR_FAIL_NULL_V_MSG(sprite, FAILED, "GFX sprite not found: " + gfx_texture_sprite_name); + GFX::TextureSprite const* new_texture_sprite = sprite->cast_to<GFX::TextureSprite>(); + ERR_FAIL_NULL_V_MSG( + new_texture_sprite, FAILED, "Invalid type for GFX sprite " + gfx_texture_sprite_name + ": " + + std_view_to_godot_string(sprite->get_type()) + " (expected " + + std_view_to_godot_string(GFX::TextureSprite::get_type_static()) + ")" + ); + return set_gfx_texture_sprite(new_texture_sprite, icon); +} + +String GFXIconTexture::get_gfx_texture_sprite_name() const { + return gfx_texture_sprite != nullptr ? std_view_to_godot_string(gfx_texture_sprite->get_name()) : String {}; +} + +Error GFXIconTexture::set_icon_index(int32_t new_icon_index) { + const Ref<Texture2D> atlas_texture = get_atlas(); + ERR_FAIL_NULL_V(atlas_texture, FAILED); + const Vector2 size = atlas_texture->get_size(); + if (icon_count <= GFX::NO_FRAMES) { + if (new_icon_index > GFX::NO_FRAMES) { + UtilityFunctions::push_warning("Invalid icon index ", new_icon_index, " for texture with no frames!"); + } + icon_index = GFX::NO_FRAMES; + set_region({ {}, size }); + return OK; + } + if (GFX::NO_FRAMES < new_icon_index && new_icon_index <= icon_count) { + icon_index = new_icon_index; + } else { + icon_index = icon_count; + if (new_icon_index > icon_count) { + UtilityFunctions::push_warning( + "Invalid icon index ", new_icon_index, " out of count ", icon_count, " - defaulting to ", icon_index + ); + } + } + set_region({ (icon_index - 1) * size.x / icon_count, 0, size.x / icon_count, size.y }); + return OK; +} diff --git a/extension/src/openvic-extension/classes/GFXIconTexture.hpp b/extension/src/openvic-extension/classes/GFXIconTexture.hpp new file mode 100644 index 0000000..3ed5b3e --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXIconTexture.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include <godot_cpp/classes/atlas_texture.hpp> + +#include <openvic-simulation/interface/GFX.hpp> + +namespace OpenVic { + class GFXIconTexture : public godot::AtlasTexture { + GDCLASS(GFXIconTexture, godot::AtlasTexture) + + /* PROPERTY automatically defines getter functions: + * - get_gfx_texture_sprite + * - get_icon_index + * - get_icon_count */ + GFX::TextureSprite const* PROPERTY(gfx_texture_sprite); + GFX::frame_t PROPERTY(icon_index); + GFX::frame_t PROPERTY(icon_count); + + protected: + static void _bind_methods(); + + public: + GFXIconTexture(); + + static godot::Ref<GFXIconTexture> make_gfx_icon_texture( + GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon = GFX::NO_FRAMES + ); + + /* Discard the GFX::TextureSprite, atlas texture and icon index. */ + void clear(); + + /* Set the GFX::TextureSprite, load its texture as an atlas and set its displayed icon */ + godot::Error set_gfx_texture_sprite( + GFX::TextureSprite const* new_gfx_texture_sprite, GFX::frame_t icon = GFX::NO_FRAMES + ); + + /* Search for a GFX::TextureSprite with the specfied name and, + * if successful, call set_gfx_texture_sprite to set it and its icon */ + godot::Error set_gfx_texture_sprite_name( + godot::String const& gfx_texture_sprite_name, GFX::frame_t icon = GFX::NO_FRAMES + ); + + /* Return the name of the GFX::TextureSprite, or an empty String if it's null */ + godot::String get_gfx_texture_sprite_name() const; + + /* Set icon_index to a value between one and icon_count (inclusive), and update the AtlasTexture's region + * to display that frame. An index of zero can be used if gfx_texture_sprite has no frames (zero icon_count). + * If zero is used but icon_count is non-zero, icon_index defaults to icon_count (the last frame, + * not the first frame because it is often empty). */ + godot::Error set_icon_index(GFX::frame_t new_icon_index); + }; +} diff --git a/extension/src/openvic-extension/MapMesh.cpp b/extension/src/openvic-extension/classes/MapMesh.cpp index a557105..a557105 100644 --- a/extension/src/openvic-extension/MapMesh.cpp +++ b/extension/src/openvic-extension/classes/MapMesh.cpp diff --git a/extension/src/openvic-extension/MapMesh.hpp b/extension/src/openvic-extension/classes/MapMesh.hpp index 38b208c..38b208c 100644 --- a/extension/src/openvic-extension/MapMesh.hpp +++ b/extension/src/openvic-extension/classes/MapMesh.hpp diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 2739e2e..2f6f799 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -2,17 +2,20 @@ #include <godot_cpp/classes/engine.hpp> -#include "openvic-extension/Checksum.hpp" -#include "openvic-extension/GameSingleton.hpp" -#include "openvic-extension/LoadLocalisation.hpp" -#include "openvic-extension/MapMesh.hpp" +#include "openvic-extension/classes/GFXIconTexture.hpp" +#include "openvic-extension/classes/MapMesh.hpp" +#include "openvic-extension/singletons/AssetManager.hpp" +#include "openvic-extension/singletons/Checksum.hpp" +#include "openvic-extension/singletons/GameSingleton.hpp" +#include "openvic-extension/singletons/LoadLocalisation.hpp" using namespace godot; using namespace OpenVic; -static Checksum* _checksum; +static Checksum* _checksum_singleton; static LoadLocalisation* _load_localisation; -static GameSingleton* _map_singleton; +static GameSingleton* _game_singleton; +static AssetManager* _asset_manager_singleton; void initialize_openvic_types(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { @@ -20,7 +23,7 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { } ClassDB::register_class<Checksum>(); - _checksum = memnew(Checksum); + _checksum_singleton = memnew(Checksum); Engine::get_singleton()->register_singleton("Checksum", Checksum::get_singleton()); ClassDB::register_class<LoadLocalisation>(); @@ -28,10 +31,15 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { Engine::get_singleton()->register_singleton("LoadLocalisation", LoadLocalisation::get_singleton()); ClassDB::register_class<GameSingleton>(); - _map_singleton = memnew(GameSingleton); + _game_singleton = memnew(GameSingleton); Engine::get_singleton()->register_singleton("GameSingleton", GameSingleton::get_singleton()); + ClassDB::register_class<AssetManager>(); + _asset_manager_singleton = memnew(AssetManager); + Engine::get_singleton()->register_singleton("AssetManager", AssetManager::get_singleton()); + ClassDB::register_class<MapMesh>(); + ClassDB::register_class<GFXIconTexture>(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { @@ -40,13 +48,16 @@ void uninitialize_openvic_types(ModuleInitializationLevel p_level) { } Engine::get_singleton()->unregister_singleton("Checksum"); - memdelete(_checksum); + memdelete(_checksum_singleton); Engine::get_singleton()->unregister_singleton("LoadLocalisation"); memdelete(_load_localisation); Engine::get_singleton()->unregister_singleton("GameSingleton"); - memdelete(_map_singleton); + memdelete(_game_singleton); + + Engine::get_singleton()->unregister_singleton("AssetManager"); + memdelete(_asset_manager_singleton); } extern "C" { diff --git a/extension/src/openvic-extension/singletons/AssetManager.cpp b/extension/src/openvic-extension/singletons/AssetManager.cpp new file mode 100644 index 0000000..b50cae8 --- /dev/null +++ b/extension/src/openvic-extension/singletons/AssetManager.cpp @@ -0,0 +1,152 @@ +#include "AssetManager.hpp" + +#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; + +void AssetManager::_bind_methods() { + OV_BIND_METHOD(AssetManager::get_image, { "path" }); + OV_BIND_METHOD(AssetManager::get_texture, { "path" }); + + OV_BIND_SMETHOD(AssetManager::make_icon, { "texture", "frame", "frame_count" }); + OV_BIND_METHOD(AssetManager::get_icon, { "texture", "frame", "frame_count" }); + + OV_BIND_METHOD(AssetManager::get_texture_or_icon, { "path", "frame", "frame_count" }); + OV_BIND_METHOD(AssetManager::get_font, { "name" }); +} + +AssetManager* AssetManager::get_singleton() { + return _singleton; +} + +AssetManager::AssetManager() { + ERR_FAIL_COND(_singleton != nullptr); + _singleton = this; +} + +AssetManager::~AssetManager() { + ERR_FAIL_COND(_singleton != this); + _singleton = nullptr; +} + +AssetManager::image_asset_map_t::iterator AssetManager::_get_image_asset(StringName path) { + const image_asset_map_t::iterator it = image_assets.find(path); + if (it != image_assets.end()) { + return it; + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, image_assets.end()); + const String lookedup_path = + std_to_godot_string(game_singleton->get_dataloader().lookup_image_file_or_dds(godot_to_std_string(path)).string()); + if (lookedup_path.is_empty()) { + UtilityFunctions::push_error("Failed to look up image: ", path); + return image_assets.end(); + } + const Ref<Image> image = Utilities::load_godot_image(lookedup_path); + if (image.is_null() || image->is_empty()) { + UtilityFunctions::push_error("Failed to load image: ", lookedup_path, " (looked up from ", path, ")"); + return image_assets.end(); + } + return image_assets.emplace(std::move(path), AssetManager::image_asset_t { image, nullptr }).first; +} + +Ref<Image> AssetManager::get_image(StringName path) { + const image_asset_map_t::const_iterator it = _get_image_asset(path); + if (it != image_assets.end()) { + return it->second.image; + } else { + return nullptr; + } +} + +Ref<ImageTexture> AssetManager::get_texture(StringName path) { + const image_asset_map_t::iterator it = _get_image_asset(path); + if (it != image_assets.end()) { + if (it->second.texture.is_null()) { + it->second.texture = ImageTexture::create_from_image(it->second.image); + if (it->second.texture.is_null()) { + UtilityFunctions::push_error("Failed to turn image into texture: ", path); + } + } + return it->second.texture; + } else { + return nullptr; + } +} + +Ref<AtlasTexture> AssetManager::make_icon(Ref<Texture2D> texture, GFX::frame_t frame, GFX::frame_t frame_count) { + ERR_FAIL_NULL_V(texture, nullptr); + + if (frame_count <= GFX::NO_FRAMES) { + UtilityFunctions::push_warning("No frames!"); + frame_count = 1; + } + if (frame <= GFX::NO_FRAMES || frame > frame_count) { + UtilityFunctions::push_warning("Invalid frame index ", frame, " out of count ", frame_count); + frame = frame_count; + } + frame--; + const Vector2i size = texture->get_size(); + const Rect2i region { frame * size.x / frame_count, 0, size.x / frame_count, size.y }; + + Ref<AtlasTexture> atlas; + atlas.instantiate(); + ERR_FAIL_NULL_V(atlas, nullptr); + atlas->set_atlas(texture); + atlas->set_region(region); + return atlas; +} + +Ref<AtlasTexture> AssetManager::get_icon(StringName path, GFX::frame_t frame, GFX::frame_t frame_count) { + Ref<ImageTexture> texture = get_texture(path); + ERR_FAIL_NULL_V(texture, nullptr); + return make_icon(texture, frame, frame_count); +} + +Ref<Texture2D> AssetManager::get_texture_or_icon(StringName path, GFX::frame_t frame, GFX::frame_t frame_count) { + if (frame_count < 2) { + if (frame > frame_count) { + UtilityFunctions::push_warning("Invalid frame index ", frame, " out of count ", frame_count); + } + return get_texture(path); + } else { + return get_icon(path, frame, frame_count); + } +} + +Ref<Font> AssetManager::get_font(StringName name) { + const font_map_t::const_iterator it = fonts.find(name); + if (it != fonts.end()) { + return it->second; + } + + static const String font_dir = "gfx/fonts/"; + static const String font_ext = ".fnt"; + static const String image_ext = ".tga"; + + const String image_path = font_dir + name + image_ext; + const Ref<Image> image = get_image(image_path); + if (image.is_null()) { + UtilityFunctions::push_error("Failed to load font image: ", image_path, " for the font named ", name); + return nullptr; + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, nullptr); + const String lookedup_font_path = + std_to_godot_string(game_singleton->get_dataloader().lookup_file(godot_to_std_string(font_dir + name + font_ext)).string()); + const Ref<Font> font = Utilities::load_godot_font(lookedup_font_path, image); + if (font.is_null()) { + UtilityFunctions::push_error("Failed to load font file ", lookedup_font_path, " for the font named ", name); + return nullptr; + } + fonts.emplace(std::move(name), font); + return font; +} diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp new file mode 100644 index 0000000..7cfc31b --- /dev/null +++ b/extension/src/openvic-extension/singletons/AssetManager.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include <godot_cpp/classes/atlas_texture.hpp> +#include <godot_cpp/classes/font.hpp> +#include <godot_cpp/classes/image_texture.hpp> +#include <godot_cpp/core/class_db.hpp> + +#include <openvic-simulation/interface/GFX.hpp> + +namespace OpenVic { + class AssetManager : public godot::Object { + GDCLASS(AssetManager, godot::Object) + + static inline AssetManager* _singleton = nullptr; + + struct image_asset_t { + godot::Ref<godot::Image> image; + godot::Ref<godot::ImageTexture> texture; + }; + using image_asset_map_t = std::map<godot::StringName, image_asset_t>; + using font_map_t = std::map<godot::StringName, godot::Ref<godot::Font>>; + + image_asset_map_t image_assets; + font_map_t fonts; + + image_asset_map_t::iterator _get_image_asset(godot::StringName path); + + protected: + static void _bind_methods(); + + public: + static AssetManager* get_singleton(); + + AssetManager(); + ~AssetManager(); + + /* Search for and load an image at the specified path relative to the game defines, first checking the AssetManager's + * image cache in case it has already been loaded, and returning nullptr if image loading fails. */ + godot::Ref<godot::Image> get_image(godot::StringName path); + + /* Create a texture from an image found at the specified path relative to the game defines, fist checking + * AssetManager's texture cache in case it has already been loaded, and returning nullptr if image loading + * or texture creation fails. */ + godot::Ref<godot::ImageTexture> get_texture(godot::StringName path); + + /* Extract the specified frame of the texture, which is treated as a single row of frame_count frames. */ + static godot::Ref<godot::AtlasTexture> make_icon( + godot::Ref<godot::Texture2D> texture, GFX::frame_t frame, GFX::frame_t frame_count + ); + + /* Load a texture as with get_texture, and extract the specified frame as with make_icon. */ + godot::Ref<godot::AtlasTexture> get_icon(godot::StringName path, GFX::frame_t frame, GFX::frame_t frame_count); + + /* Load a texture as with get_texture if frame_count <= 1 otherwise as with get_icon. */ + godot::Ref<godot::Texture2D> get_texture_or_icon(godot::StringName path, GFX::frame_t frame, GFX::frame_t frame_count); + + /* Search for and load a font with the specified name from the game defines' font directory, first checking the + * AssetManager's font cache in case it has already been loaded, and returning nullptr if font loading fails. */ + godot::Ref<godot::Font> get_font(godot::StringName name); + }; +} diff --git a/extension/src/openvic-extension/Checksum.cpp b/extension/src/openvic-extension/singletons/Checksum.cpp index 6da5afe..9f48647 100644 --- a/extension/src/openvic-extension/Checksum.cpp +++ b/extension/src/openvic-extension/singletons/Checksum.cpp @@ -13,17 +13,17 @@ void Checksum::_bind_methods() { } Checksum* Checksum::get_singleton() { - return _checksum; + return _singleton; } Checksum::Checksum() { - ERR_FAIL_COND(_checksum != nullptr); - _checksum = this; + ERR_FAIL_COND(_singleton != nullptr); + _singleton = this; } Checksum::~Checksum() { - ERR_FAIL_COND(_checksum != this); - _checksum = nullptr; + ERR_FAIL_COND(_singleton != this); + _singleton = nullptr; } /* REQUIREMENTS: diff --git a/extension/src/openvic-extension/Checksum.hpp b/extension/src/openvic-extension/singletons/Checksum.hpp index 2b2f959..532a50d 100644 --- a/extension/src/openvic-extension/Checksum.hpp +++ b/extension/src/openvic-extension/singletons/Checksum.hpp @@ -8,8 +8,7 @@ namespace OpenVic { class Checksum : public godot::Object { GDCLASS(Checksum, godot::Object) - // BEGIN BOILERPLATE - static inline Checksum* _checksum = nullptr; + static inline Checksum* _singleton = nullptr; protected: static void _bind_methods(); @@ -19,7 +18,6 @@ namespace OpenVic { Checksum(); ~Checksum(); - // END BOILERPLATE godot::String get_checksum_text(); }; diff --git a/extension/src/openvic-extension/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index 9dae7f2..d101a86 100644 --- a/extension/src/openvic-extension/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -6,9 +6,9 @@ #include <openvic-simulation/utility/Logger.hpp> -#include "openvic-extension/LoadLocalisation.hpp" -#include "openvic-extension/Utilities.hpp" +#include "openvic-extension/singletons/LoadLocalisation.hpp" #include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/Utilities.hpp" using namespace godot; using namespace OpenVic; @@ -23,15 +23,14 @@ 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" }); OV_BIND_METHOD(GameSingleton::setup_game); OV_BIND_METHOD(GameSingleton::get_province_index_from_uv_coords, { "coords" }); OV_BIND_METHOD(GameSingleton::get_province_info_from_index, { "index" }); - OV_BIND_METHOD(GameSingleton::get_width); - OV_BIND_METHOD(GameSingleton::get_height); - OV_BIND_METHOD(GameSingleton::get_aspect_ratio); + OV_BIND_METHOD(GameSingleton::get_map_width); + OV_BIND_METHOD(GameSingleton::get_map_height); + OV_BIND_METHOD(GameSingleton::get_map_aspect_ratio); OV_BIND_METHOD(GameSingleton::get_terrain_texture); OV_BIND_METHOD(GameSingleton::get_province_shape_image_subdivisions); OV_BIND_METHOD(GameSingleton::get_province_shape_texture); @@ -55,30 +54,11 @@ void GameSingleton::_bind_methods() { OV_BIND_METHOD(GameSingleton::get_longform_date); OV_BIND_METHOD(GameSingleton::try_tick); + OV_BIND_METHOD(GameSingleton::generate_gui, { "gui_file", "gui_element" }); + ADD_SIGNAL(MethodInfo("state_updated")); ADD_SIGNAL(MethodInfo("province_selected", PropertyInfo(Variant::INT, "index"))); - OV_BIND_SMETHOD(get_province_info_province_key); - OV_BIND_SMETHOD(get_province_info_region_key); - OV_BIND_SMETHOD(get_province_info_life_rating_key); - OV_BIND_SMETHOD(get_province_info_terrain_type_key); - OV_BIND_SMETHOD(get_province_info_total_population_key); - OV_BIND_SMETHOD(get_province_info_pop_types_key); - OV_BIND_SMETHOD(get_province_info_pop_ideologies_key); - OV_BIND_SMETHOD(get_province_info_pop_cultures_key); - OV_BIND_SMETHOD(get_province_info_rgo_key); - OV_BIND_SMETHOD(get_province_info_buildings_key); - - OV_BIND_SMETHOD(get_building_info_building_key); - OV_BIND_SMETHOD(get_building_info_level_key); - OV_BIND_SMETHOD(get_building_info_expansion_state_key); - OV_BIND_SMETHOD(get_building_info_start_date_key); - OV_BIND_SMETHOD(get_building_info_end_date_key); - OV_BIND_SMETHOD(get_building_info_expansion_progress_key); - - OV_BIND_SMETHOD(get_piechart_info_size_key); - OV_BIND_SMETHOD(get_piechart_info_colour_key); - OV_BIND_SMETHOD( draw_pie_chart, { "image", "stopAngles", "colours", "radius", "shadow_displacement", "shadow_tightness", "shadow_radius", @@ -89,6 +69,31 @@ void GameSingleton::_bind_methods() { OV_BIND_SMETHOD(load_image, { "path" }); } +Control* GameSingleton::generate_gui(String const& gui_file, String const& gui_element) { + GUI::Scene const* scene = game_manager.get_ui_manager().get_scene_by_identifier(godot_to_std_string(gui_file)); + if (scene == nullptr) { + UtilityFunctions::push_error("Failed to find GUI file ", gui_file); + return nullptr; + } + GUI::Element const* element = scene->get_element_by_identifier(godot_to_std_string(gui_element)); + if (element == nullptr) { + UtilityFunctions::push_error("Failed to find GUI element ", gui_element, " in GUI file ", gui_file); + return nullptr; + } + + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, nullptr); + Control* result = nullptr; + if (!GodotGUIBuilder::generate_element(element, *asset_manager, result)) { + UtilityFunctions::push_error("Failed to generate GUI element ", gui_element, " in GUI file ", gui_file); + } + return result; +} + +GFX::Sprite const* GameSingleton::get_gfx_sprite(String const& sprite_name) const { + return game_manager.get_ui_manager().get_sprite_by_identifier(godot_to_std_string(sprite_name)); +} + void GameSingleton::draw_pie_chart( Ref<Image> image, Array const& stopAngles, Array const& colours, float radius, Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, Color trim_colour, float trim_size, @@ -121,6 +126,10 @@ GameSingleton::GameSingleton() ERR_FAIL_COND(singleton != nullptr); singleton = this; } +GameSingleton::~GameSingleton() { + ERR_FAIL_COND(singleton != this); + singleton = nullptr; +} void GameSingleton::setup_logger() { Logger::set_info_func([](std::string&& str) { @@ -134,106 +143,38 @@ void GameSingleton::setup_logger() { }); } -GameSingleton::~GameSingleton() { - ERR_FAIL_COND(singleton != this); - singleton = nullptr; +Dataloader const& GameSingleton::get_dataloader() const { + return dataloader; } Error GameSingleton::setup_game() { - bool ret = game_manager.setup(); + BookmarkManager const& bookmark_manager = game_manager.get_history_manager().get_bookmark_manager(); + if (bookmark_manager.bookmarks_empty()) { + UtilityFunctions::push_error("No bookmark to load!"); + return FAILED; + } + bool ret = game_manager.load_bookmark(&bookmark_manager.get_bookmarks().front()); + // TODO - load pop history with the new history system ret &= dataloader.load_pop_history(game_manager, "history/pops/" + game_manager.get_today().to_string()); return ERR(ret); } int32_t GameSingleton::get_province_index_from_uv_coords(Vector2 const& coords) const { - const size_t x_mod_w = UtilityFunctions::fposmod(coords.x, 1.0f) * get_width(); - const size_t y_mod_h = UtilityFunctions::fposmod(coords.y, 1.0f) * get_height(); + const size_t x_mod_w = UtilityFunctions::fposmod(coords.x, 1.0f) * get_map_width(); + const size_t y_mod_h = UtilityFunctions::fposmod(coords.y, 1.0f) * get_map_height(); return game_manager.get_map().get_province_index_at(x_mod_w, y_mod_h); } -StringName const& GameSingleton::get_province_info_province_key() { - static const StringName key = "province"; - return key; -} -StringName const& GameSingleton::get_province_info_region_key() { - static const StringName key = "region"; - return key; -} -StringName const& GameSingleton::get_province_info_life_rating_key() { - static const StringName key = "life_rating"; - return key; -} -StringName const& GameSingleton::get_province_info_terrain_type_key() { - static const StringName key = "terrain_type"; - return key; -} -StringName const& GameSingleton::get_province_info_total_population_key() { - static const StringName key = "total_population"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_types_key() { - static const StringName key = "pop_types"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_ideologies_key() { - static const StringName key = "pop_ideologies"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_cultures_key() { - static const StringName key = "pop_cultures"; - return key; -} -StringName const& GameSingleton::get_province_info_rgo_key() { - static const StringName key = "rgo"; - return key; -} -StringName const& GameSingleton::get_province_info_buildings_key() { - static const StringName key = "buildings"; - return key; -} - -StringName const& GameSingleton::get_building_info_building_key() { - static const StringName key = "building"; - return key; -} -StringName const& GameSingleton::get_building_info_level_key() { - static const StringName key = "level"; - return key; -} -StringName const& GameSingleton::get_building_info_expansion_state_key() { - static const StringName key = "expansion_state"; - return key; -} -StringName const& GameSingleton::get_building_info_start_date_key() { - static const StringName key = "start_date"; - return key; -} -StringName const& GameSingleton::get_building_info_end_date_key() { - static const StringName key = "end_date"; - return key; -} -StringName const& GameSingleton::get_building_info_expansion_progress_key() { - static const StringName key = "expansion_progress"; - return key; -} - -StringName const& GameSingleton::get_piechart_info_size_key() { - static const StringName key = "size"; - return key; -} -StringName const& GameSingleton::get_piechart_info_colour_key() { - static const StringName key = "colour"; - return key; -} - template<std::derived_from<HasIdentifierAndColour> T> -static Dictionary _distribution_to_dictionary(decimal_map_t<T const*> const& dist) { +static Dictionary _distribution_to_dictionary(fixed_point_map_t<T const*> const& dist) { + static const StringName piechart_info_size_key = "size"; + static const StringName piechart_info_colour_key = "colour"; Dictionary dict; for (auto const& [key, val] : dist) { if (key != nullptr) { Dictionary sub_dict; - sub_dict[GameSingleton::get_piechart_info_size_key()] = val.to_float(); - sub_dict[GameSingleton::get_piechart_info_colour_key()] = Utilities::to_godot_color(key->get_colour()); + sub_dict[piechart_info_size_key] = val.to_float(); + sub_dict[piechart_info_colour_key] = Utilities::to_godot_color(key->get_colour()); dict[std_view_to_godot_string(key->get_identifier())] = std::move(sub_dict); } else { UtilityFunctions::push_error("Null distribution key with value ", val.to_float()); @@ -243,45 +184,63 @@ static Dictionary _distribution_to_dictionary(decimal_map_t<T const*> const& dis } Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { + static const StringName province_info_province_key = "province"; + static const StringName province_info_region_key = "region"; + static const StringName province_info_life_rating_key = "life_rating"; + static const StringName province_info_terrain_type_key = "terrain_type"; + static const StringName province_info_total_population_key = "total_population"; + static const StringName province_info_pop_types_key = "pop_types"; + static const StringName province_info_pop_ideologies_key = "pop_ideologies"; + static const StringName province_info_pop_cultures_key = "pop_cultures"; + static const StringName province_info_rgo_key = "rgo"; + static const StringName province_info_buildings_key = "buildings"; + Province const* province = game_manager.get_map().get_province_by_index(index); if (province == nullptr) { return {}; } Dictionary ret; - ret[get_province_info_province_key()] = std_view_to_godot_string(province->get_identifier()); + ret[province_info_province_key] = std_view_to_godot_string(province->get_identifier()); Region const* region = province->get_region(); if (region != nullptr) { - ret[get_province_info_region_key()] = std_view_to_godot_string(region->get_identifier()); + ret[province_info_region_key] = std_view_to_godot_string(region->get_identifier()); } Good const* rgo = province->get_rgo(); if (rgo != nullptr) { - ret[get_province_info_rgo_key()] = std_view_to_godot_string(rgo->get_identifier()); + ret[province_info_rgo_key] = std_view_to_godot_string(rgo->get_identifier()); } - ret[get_province_info_life_rating_key()] = province->get_life_rating(); + ret[province_info_life_rating_key] = province->get_life_rating(); TerrainType const* terrain_type = province->get_terrain_type(); if (terrain_type != nullptr) { - ret[get_province_info_terrain_type_key()] = std_view_to_godot_string(terrain_type->get_identifier()); + ret[province_info_terrain_type_key] = std_view_to_godot_string(terrain_type->get_identifier()); } - ret[get_province_info_total_population_key()] = province->get_total_population(); - decimal_map_t<PopType const*> const& pop_types = province->get_pop_type_distribution(); + ret[province_info_total_population_key] = province->get_total_population(); + fixed_point_map_t<PopType const*> const& pop_types = province->get_pop_type_distribution(); if (!pop_types.empty()) { - ret[get_province_info_pop_types_key()] = _distribution_to_dictionary(pop_types); + ret[province_info_pop_types_key] = _distribution_to_dictionary(pop_types); } - decimal_map_t<Ideology const*> const& ideologies = province->get_ideology_distribution(); + fixed_point_map_t<Ideology const*> const& ideologies = province->get_ideology_distribution(); if (!ideologies.empty()) { - ret[get_province_info_pop_ideologies_key()] = _distribution_to_dictionary(ideologies); + ret[province_info_pop_ideologies_key] = _distribution_to_dictionary(ideologies); } - decimal_map_t<Culture const*> const& cultures = province->get_culture_distribution(); + fixed_point_map_t<Culture const*> const& cultures = province->get_culture_distribution(); if (!cultures.empty()) { - ret[get_province_info_pop_cultures_key()] = _distribution_to_dictionary(cultures); + ret[province_info_pop_cultures_key] = _distribution_to_dictionary(cultures); } + static const StringName building_info_building_key = "building"; + static const StringName building_info_level_key = "level"; + static const StringName building_info_expansion_state_key = "expansion_state"; + static const StringName building_info_start_date_key = "start_date"; + static const StringName building_info_end_date_key = "end_date"; + static const StringName building_info_expansion_progress_key = "expansion_progress"; + std::vector<BuildingInstance> const& buildings = province->get_buildings(); if (!buildings.empty()) { Array buildings_array; @@ -290,30 +249,30 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { BuildingInstance const& building = buildings[idx]; Dictionary building_dict; - building_dict[get_building_info_building_key()] = std_view_to_godot_string(building.get_identifier()); - building_dict[get_building_info_level_key()] = static_cast<int32_t>(building.get_current_level()); - building_dict[get_building_info_expansion_state_key()] = static_cast<int32_t>(building.get_expansion_state()); - building_dict[get_building_info_start_date_key()] = std_to_godot_string(building.get_start_date().to_string()); - building_dict[get_building_info_end_date_key()] = std_to_godot_string(building.get_end_date().to_string()); - building_dict[get_building_info_expansion_progress_key()] = building.get_expansion_progress(); + building_dict[building_info_building_key] = std_view_to_godot_string(building.get_identifier()); + building_dict[building_info_level_key] = static_cast<int32_t>(building.get_level()); + building_dict[building_info_expansion_state_key] = static_cast<int32_t>(building.get_expansion_state()); + building_dict[building_info_start_date_key] = std_to_godot_string(building.get_start_date().to_string()); + building_dict[building_info_end_date_key] = std_to_godot_string(building.get_end_date().to_string()); + building_dict[building_info_expansion_progress_key] = building.get_expansion_progress(); buildings_array[idx] = building_dict; } - ret[get_province_info_buildings_key()] = buildings_array; + ret[province_info_buildings_key] = buildings_array; } return ret; } -int32_t GameSingleton::get_width() const { +int32_t GameSingleton::get_map_width() const { return game_manager.get_map().get_width(); } -int32_t GameSingleton::get_height() const { +int32_t GameSingleton::get_map_height() const { return game_manager.get_map().get_height(); } -float GameSingleton::get_aspect_ratio() const { - return static_cast<float>(get_width()) / static_cast<float>(get_height()); +float GameSingleton::get_map_aspect_ratio() const { + return static_cast<float>(get_map_width()) / static_cast<float>(get_map_height()); } Ref<Texture> GameSingleton::get_terrain_texture() const { @@ -335,7 +294,7 @@ Ref<Texture> GameSingleton::get_province_colour_texture() const { Error GameSingleton::_update_colour_image() { static PackedByteArray colour_data_array; static constexpr int64_t colour_data_array_size = - (static_cast<int64_t>(Province::MAX_INDEX) + 1) * Map::MAPMODE_COLOUR_SIZE; + (static_cast<int64_t>(Province::MAX_INDEX) + 1) * sizeof(Mapmode::base_stripe_t); colour_data_array.resize(colour_data_array_size); Error err = OK; @@ -343,12 +302,16 @@ Error GameSingleton::_update_colour_image() { err = FAILED; } + /* We reshape the list of colours into a square, as each texture dimensions cannot exceed 16384. */ static constexpr int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * 4); if (province_colour_image.is_null()) { province_colour_image.instantiate(); ERR_FAIL_NULL_V_EDMSG(province_colour_image, FAILED, "Failed to create province colour image"); } - province_colour_image->set_data(PROVINCE_INDEX_SQRT, PROVINCE_INDEX_SQRT, false, Image::FORMAT_RGBA8, colour_data_array); + /* Width is doubled as each province has a (base, stripe) colour pair. */ + province_colour_image->set_data( + PROVINCE_INDEX_SQRT * 2, PROVINCE_INDEX_SQRT, false, Image::FORMAT_RGBA8, colour_data_array + ); if (province_colour_texture.is_null()) { province_colour_texture = ImageTexture::create_from_image(province_colour_image); ERR_FAIL_NULL_V_EDMSG(province_colour_texture, FAILED, "Failed to create province colour texture"); @@ -589,7 +552,3 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi String GameSingleton::search_for_game_path(String hint_path) { return std_to_godot_string(Dataloader::search_for_game_path(godot_to_std_string(hint_path)).string()); } - -String GameSingleton::lookup_file(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/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index ec26c3c..1346a5f 100644 --- a/extension/src/openvic-extension/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -1,11 +1,14 @@ #pragma once +#include <godot_cpp/classes/control.hpp> #include <godot_cpp/classes/image_texture.hpp> #include <godot_cpp/classes/texture2d_array.hpp> #include <openvic-simulation/GameManager.hpp> #include <openvic-simulation/dataloader/Dataloader.hpp> +#include "openvic-extension/UIAdapter.hpp" + namespace OpenVic { class GameSingleton : public godot::Object { @@ -37,6 +40,10 @@ namespace OpenVic { static void _bind_methods(); public: + + godot::Control* generate_gui(godot::String const& gui_file, godot::String const& gui_element); + GFX::Sprite const* get_gfx_sprite(godot::String const& sprite_name) const; + static void draw_pie_chart( godot::Ref<godot::Image> image, godot::Array const& stopAngles, godot::Array const& colours, float radius, godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, @@ -53,6 +60,8 @@ namespace OpenVic { static void setup_logger(); + Dataloader const& get_dataloader() const; + /* Load the game's defines in compatiblity mode from the filepath * pointing to the defines folder. */ @@ -60,8 +69,6 @@ namespace OpenVic { static godot::String search_for_game_path(godot::String hint_path = {}); - godot::String lookup_file(godot::String const& path) const; - /* Post-load/restart game setup - reset the game to post-load state * and (re)generate starting data, e.g. buildings. */ @@ -69,35 +76,14 @@ namespace OpenVic { int32_t get_province_index_from_uv_coords(godot::Vector2 const& coords) const; - static godot::StringName const& get_province_info_province_key(); - static godot::StringName const& get_province_info_region_key(); - static godot::StringName const& get_province_info_life_rating_key(); - static godot::StringName const& get_province_info_terrain_type_key(); - static godot::StringName const& get_province_info_total_population_key(); - static godot::StringName const& get_province_info_pop_types_key(); - static godot::StringName const& get_province_info_pop_ideologies_key(); - static godot::StringName const& get_province_info_pop_cultures_key(); - static godot::StringName const& get_province_info_rgo_key(); - static godot::StringName const& get_province_info_buildings_key(); - - static godot::StringName const& get_building_info_building_key(); - static godot::StringName const& get_building_info_level_key(); - static godot::StringName const& get_building_info_expansion_state_key(); - static godot::StringName const& get_building_info_start_date_key(); - static godot::StringName const& get_building_info_end_date_key(); - static godot::StringName const& get_building_info_expansion_progress_key(); - - static godot::StringName const& get_piechart_info_size_key(); - static godot::StringName const& get_piechart_info_colour_key(); - /* Get info to display in Province Overview Panel, packaged in - * a Dictionary using the StringNames above as keys. + * a Dictionary using StringName constants as keys. */ godot::Dictionary get_province_info_from_index(int32_t index) const; - int32_t get_width() const; - int32_t get_height() const; - float get_aspect_ratio() const; + int32_t get_map_width() const; + int32_t get_map_height() const; + float get_map_aspect_ratio() const; /* The cosmetic terrain textures stored in a Texture2DArray. */ diff --git a/extension/src/openvic-extension/LoadLocalisation.cpp b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp index ee90633..96c67e8 100644 --- a/extension/src/openvic-extension/LoadLocalisation.cpp +++ b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp @@ -5,13 +5,11 @@ #include <godot_cpp/classes/translation_server.hpp> #include <godot_cpp/variant/utility_functions.hpp> -#include "openvic-extension/Utilities.hpp" +#include "openvic-extension/utility/Utilities.hpp" using namespace godot; using namespace OpenVic; -LoadLocalisation* LoadLocalisation::singleton = nullptr; - void LoadLocalisation::_bind_methods() { ClassDB::bind_method(D_METHOD("load_file", "file_path", "locale"), &LoadLocalisation::load_file); ClassDB::bind_method(D_METHOD("load_locale_dir", "dir_path", "locale"), &LoadLocalisation::load_locale_dir); @@ -19,17 +17,17 @@ void LoadLocalisation::_bind_methods() { } LoadLocalisation* LoadLocalisation::get_singleton() { - return singleton; + return _singleton; } LoadLocalisation::LoadLocalisation() { - ERR_FAIL_COND(singleton != nullptr); - singleton = this; + ERR_FAIL_COND(_singleton != nullptr); + _singleton = this; } LoadLocalisation::~LoadLocalisation() { - ERR_FAIL_COND(singleton != this); - singleton = nullptr; + ERR_FAIL_COND(_singleton != this); + _singleton = nullptr; } Error LoadLocalisation::_load_file(String const& file_path, Ref<Translation> translation) const { @@ -65,10 +63,7 @@ Error LoadLocalisation::_load_file(String const& file_path, Ref<Translation> tra Ref<Translation> LoadLocalisation::_get_translation(String const& locale) const { TranslationServer* server = TranslationServer::get_singleton(); - if (server == nullptr) { - UtilityFunctions::push_error("Failed to get TranslationServer singleton"); - return nullptr; - } + ERR_FAIL_NULL_V(server, nullptr); Ref<Translation> translation = server->get_translation_object(locale); if (translation.is_null() || translation->get_locale() != locale) { translation.instantiate(); @@ -127,10 +122,7 @@ Error LoadLocalisation::load_localisation_dir(String const& dir_path) const { return FAILED; } TranslationServer* server = TranslationServer::get_singleton(); - if (server == nullptr) { - UtilityFunctions::push_error("Failed to get TranslationServer singleton"); - return FAILED; - } + ERR_FAIL_NULL_V(server, FAILED); Error err = OK; for (String const& locale_name : dirs) { if (locale_name != server->standardize_locale(locale_name)) { @@ -146,14 +138,14 @@ bool LoadLocalisation::add_message(std::string_view key, Dataloader::locale_t lo static Ref<Translation> translations[Dataloader::_LocaleCount] = { nullptr }; Ref<Translation>& translation = translations[locale]; if (translation.is_null()) { - translation = singleton->_get_translation(Dataloader::locale_names[locale]); + translation = _singleton->_get_translation(Dataloader::locale_names[locale]); if (translation.is_null()) { UtilityFunctions::push_error("Failed to get translation object: ", Dataloader::locale_names[locale]); return false; } } - const StringName godot_key = Utilities::std_view_to_godot_string(key); - const StringName godot_localisation = Utilities::std_view_to_godot_string(localisation); + const StringName godot_key = Utilities::std_view_to_godot_string_name(key); + const StringName godot_localisation = Utilities::std_view_to_godot_string_name(localisation); if (0) { const StringName old_localisation = translation->get_message(godot_key); if (!old_localisation.is_empty()) { diff --git a/extension/src/openvic-extension/LoadLocalisation.hpp b/extension/src/openvic-extension/singletons/LoadLocalisation.hpp index 8f6423e..b093fdf 100644 --- a/extension/src/openvic-extension/LoadLocalisation.hpp +++ b/extension/src/openvic-extension/singletons/LoadLocalisation.hpp @@ -6,10 +6,9 @@ namespace OpenVic { class LoadLocalisation : public godot::Object { - GDCLASS(LoadLocalisation, godot::Object) - static LoadLocalisation* singleton; + static inline LoadLocalisation* _singleton = nullptr; godot::Error _load_file(godot::String const& file_path, godot::Ref<godot::Translation> translation) const; godot::Ref<godot::Translation> _get_translation(godot::String const& locale) const; diff --git a/extension/src/openvic-extension/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index 520f78e..5940373 100644 --- a/extension/src/openvic-extension/Utilities.cpp +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -29,30 +29,48 @@ static Ref<Image> load_dds_image(String const& path) { } } + const gli::texture2d::extent_type extent { texture.extent() }; + const int width = extent.x, height = extent.y, size = width * height * 4; + + /* Only fail if there aren't enough bytes, everything seems to work fine if there are extra bytes and we ignore them */ + if (size > texture.size()) { + UtilityFunctions::push_error( + "Texture size ", static_cast<int64_t>(texture.size()), " mismatched with dims-based size ", size, " for ", path + ); + return nullptr; + } + PackedByteArray pixels; - pixels.resize(texture.size()); - memcpy(pixels.ptrw(), texture.data(), pixels.size()); - UtilityFunctions::print("needs_bgr_to_rgb = ", needs_bgr_to_rgb); - if (needs_bgr_to_rgb) { - for (size_t i = 0; i < pixels.size(); i += 4) { - std::swap(pixels[i], pixels[i + 2]); - } + pixels.resize(size); + /* Index offset used to control whether we are reading */ + const size_t rb_idx = 2 * needs_bgr_to_rgb; + uint8_t const* ptr = static_cast<uint8_t const*>(texture.data()); + for (size_t i = 0; i < size; i += 4) { + pixels[i + 0] = ptr[i + rb_idx]; + pixels[i + 1] = ptr[i + 1]; + pixels[i + 2] = ptr[i + 2 - rb_idx]; + pixels[i + 3] = ptr[i + 3]; } - const gli::texture2d::extent_type extent { texture.extent() }; - return Image::create_from_data(extent.x, extent.y, false, Image::FORMAT_RGBA8, pixels); + return Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, pixels); } Ref<Image> Utilities::load_godot_image(String const& path) { - if (path.begins_with("res://")) { - ResourceLoader* loader = ResourceLoader::get_singleton(); - return loader ? loader->load(path) : nullptr; - } else { - if (path.ends_with(".dds")) { - return load_dds_image(path); - } - return Image::load_from_file(path); + if (path.ends_with(".dds")) { + return load_dds_image(path); + } + return Image::load_from_file(path); +} + +Ref<FontFile> Utilities::load_godot_font(String const& fnt_path, Ref<Image> const& image) { + Ref<FontFile> font; + font.instantiate(); + const Error err = font->load_bitmap_font(fnt_path); + font->set_texture_image(0, { font->get_fixed_size(), 0 }, 0, image); + if (err != OK) { + UtilityFunctions::push_error("Failed to load font (error ", err, "): ", fnt_path); } + return font; } // Get the polar coordinates of a pixel relative to the center diff --git a/extension/src/openvic-extension/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index f789f84..752f495 100644 --- a/extension/src/openvic-extension/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -1,5 +1,6 @@ #pragma once +#include <godot_cpp/classes/font_file.hpp> #include <godot_cpp/classes/image.hpp> #include <openvic-simulation/types/Colour.hpp> @@ -21,6 +22,14 @@ namespace OpenVic::Utilities { return std_to_godot_string(static_cast<std::string>(str)); } + inline godot::StringName std_to_godot_string_name(std::string const& str) { + return str.c_str(); + } + + inline godot::StringName std_view_to_godot_string_name(std::string_view str) { + return std_to_godot_string_name(static_cast<std::string>(str)); + } + inline godot::Color to_godot_color(colour_t colour) { return { colour_byte_to_float((colour >> 16) & 0xFF), @@ -33,8 +42,14 @@ namespace OpenVic::Utilities { return { vec.x, vec.y }; } + inline godot::Vector2 to_godot_fvec2(fvec2_t vec) { + return { vec.x, vec.y }; + } + godot::Ref<godot::Image> load_godot_image(godot::String const& path); + godot::Ref<godot::FontFile> load_godot_font(godot::String const& fnt_path, godot::Ref<godot::Image> const& image); + void draw_pie_chart( godot::Ref<godot::Image> image, godot::Array const& stopAngles, godot::Array const& colours, float radius, godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, diff --git a/game/localisation/en_GB/mapmodes.csv b/game/localisation/en_GB/mapmodes.csv index 2aa4cd2..0372192 100644 --- a/game/localisation/en_GB/mapmodes.csv +++ b/game/localisation/en_GB/mapmodes.csv @@ -3,6 +3,7 @@ mapmode_province;Province mapmode_region;Region mapmode_terrain;Terrain +mapmode_political;Political mapmode_index;Index mapmode_terrain_type;Terrain Type mapmode_rgo;RGO diff --git a/game/src/Game/GameSession/GameSession.gd b/game/src/Game/GameSession/GameSession.gd index 2a27e3d..afff820 100644 --- a/game/src/Game/GameSession/GameSession.gd +++ b/game/src/Game/GameSession/GameSession.gd @@ -7,6 +7,12 @@ func _ready(): if GameSingleton.setup_game() != OK: push_error("Failed to setup game") + # Temporarily here for cosmetic reasons, will be moved to its + # own child node later, similar to ProvinceOverviewPanel + add_child(GameSingleton.generate_gui("topbar.gui", "topbar")) + $topbar/topbar_outlinerbutton_bg.visible = false + $topbar/topbar_outlinerbutton.visible = false + func _process(_delta : float): GameSingleton.try_tick() diff --git a/game/src/Game/GameSession/GameSession.tscn b/game/src/Game/GameSession/GameSession.tscn index c9f4095..755139e 100644 --- a/game/src/Game/GameSession/GameSession.tscn +++ b/game/src/Game/GameSession/GameSession.tscn @@ -5,7 +5,7 @@ [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="PackedScene" uid="uid://dkehmdnuxih2r" path="res://src/Game/GameSession/MapView.tscn" id="4_xkg5j"] -[ext_resource type="PackedScene" uid="uid://byq323jbel48u" path="res://src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn" id="5_osjnn"] +[ext_resource type="Script" path="res://src/Game/GameSession/ProvinceOverviewPanel.gd" id="5_lfv8l"] [ext_resource type="PackedScene" uid="uid://cnbfxjy1m6wja" path="res://src/Game/Menu/OptionMenu/OptionsMenu.tscn" id="6_p5mnx"] [ext_resource type="PackedScene" uid="uid://dd8k3p7r3huwc" path="res://src/Game/GameSession/GameSpeedPanel.tscn" id="7_myy4q"] [ext_resource type="PackedScene" uid="uid://d3g6wbvwflmyk" path="res://src/Game/Menu/SaveLoadMenu/SaveLoadMenu.tscn" id="8_4g7ko"] @@ -24,6 +24,16 @@ _game_session_menu = NodePath("GameSessionMenu") [node name="MapView" parent="." instance=ExtResource("4_xkg5j")] +[node name="ProvinceOverviewPanel" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 1 +script = ExtResource("5_lfv8l") + [node name="MapControlPanel" parent="." instance=ExtResource("3_afh6d")] layout_mode = 1 anchors_preset = 3 @@ -34,9 +44,6 @@ anchor_bottom = 1.0 grow_horizontal = 0 grow_vertical = 0 -[node name="ProvinceOverviewPanel" parent="." instance=ExtResource("5_osjnn")] -layout_mode = 1 - [node name="GameSpeedPanel" parent="." instance=ExtResource("7_myy4q")] layout_mode = 0 offset_right = 302.0 @@ -86,8 +93,6 @@ grow_horizontal = 0 [connection signal="mouse_exited" from="MapControlPanel" to="MapView" method="_on_mouse_entered_viewport"] [connection signal="zoom_in_button_pressed" from="MapControlPanel" to="MapView" method="zoom_in"] [connection signal="zoom_out_button_pressed" from="MapControlPanel" to="MapView" method="zoom_out"] -[connection signal="mouse_entered" from="ProvinceOverviewPanel" to="MapView" method="_on_mouse_exited_viewport"] -[connection signal="mouse_exited" from="ProvinceOverviewPanel" to="MapView" method="_on_mouse_entered_viewport"] [connection signal="load_button_pressed" from="GameSessionMenu" to="SaveLoadMenu" method="show_for_load"] [connection signal="options_button_pressed" from="GameSessionMenu" to="OptionsMenu" method="show"] [connection signal="save_button_pressed" from="GameSessionMenu" to="SaveLoadMenu" method="show_for_save"] diff --git a/game/src/Game/GameSession/MapControlPanel/Minimap.gd b/game/src/Game/GameSession/MapControlPanel/Minimap.gd index be65db5..5564821 100644 --- a/game/src/Game/GameSession/MapControlPanel/Minimap.gd +++ b/game/src/Game/GameSession/MapControlPanel/Minimap.gd @@ -10,7 +10,7 @@ var _minimap_shader : ShaderMaterial var _viewport_points : PackedVector2Array func _ready(): - _minimap_texture.custom_minimum_size = Vector2(GameSingleton.get_aspect_ratio(), 1.0) * 150 + _minimap_texture.custom_minimum_size = Vector2(GameSingleton.get_map_aspect_ratio(), 1.0) * 150 var minimap_material := _minimap_texture.get_material() if GameLoader.ShaderManager.set_up_shader(minimap_material, false) != OK: push_error("Failed to set up minimap shader") diff --git a/game/src/Game/GameSession/MapView.gd b/game/src/Game/GameSession/MapView.gd index 345ab9e..7093c69 100644 --- a/game/src/Game/GameSession/MapView.gd +++ b/game/src/Game/GameSession/MapView.gd @@ -74,7 +74,10 @@ func _ready(): # Set map mesh size and get bounds const pixels_per_terrain_tile : float = 32.0 _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_terrain_tile_factor, - float(GameSingleton.get_height()) / pixels_per_terrain_tile) + float(GameSingleton.get_map_height()) / pixels_per_terrain_tile) + const pixels_per_stripe_tile : float = 16.0 + _map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_stripe_tile_factor, + float(GameSingleton.get_map_height()) / pixels_per_stripe_tile) var map_mesh_aabb := _map_mesh.get_core_aabb() * _map_mesh_instance.transform _map_mesh_corner = Vector2( min(map_mesh_aabb.position.x, map_mesh_aabb.end.x), diff --git a/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc b/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc index 65f73d8..1adcd95 100644 --- a/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc +++ b/game/src/Game/GameSession/ProvinceIndexSampler.gdshaderinc @@ -4,15 +4,21 @@ uniform sampler2DArray province_shape_tex : repeat_enable, filter_nearest; // Province shape subdivisions uniform vec2 province_shape_subdivisions; +// Convert a vector of 3 normalised floats to a vector of 3 unsigned bytes uvec3 vec3_to_uvec3(vec3 v) { return uvec3(v * 255.0); } + +// Create a uint triplet describing the province and terrain data at a map-space UV coordinate: +// (u, v) -> (province index bottom byte, province index top byte, terrain index byte) uvec3 read_uvec3(vec2 uv) { uv *= province_shape_subdivisions; vec2 subdivision_coords = mod(floor(uv), province_shape_subdivisions); float idx = subdivision_coords.x + subdivision_coords.y * province_shape_subdivisions.x; return vec3_to_uvec3(texture(province_shape_tex, vec3(uv, idx)).rgb); } + +// Combine a (lower byte, upper byte) uint pair into a single 2-byte uint uint uvec2_to_uint(uvec2 v) { return (v.y << 8u) | v.x; } diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel.gd new file mode 100644 index 0000000..1292eb4 --- /dev/null +++ b/game/src/Game/GameSession/ProvinceOverviewPanel.gd @@ -0,0 +1,215 @@ +extends Control + +var _province_name_label : Label +var _region_name_label : Label +var _life_rating_bar : TextureProgressBar +var _total_population_label : Label +var _rgo_icon_texture : AtlasTexture + +const _missing_suffix : String = "_MISSING" + +const _province_info_province_key : StringName = &"province" +const _province_info_region_key : StringName = &"region" +const _province_info_life_rating_key : StringName = &"life_rating" +const _province_info_terrain_type_key : StringName = &"terrain_type" +const _province_info_total_population_key : StringName = &"total_population" +const _province_info_pop_types_key : StringName = &"pop_types" +const _province_info_pop_ideologies_key : StringName = &"pop_ideologies" +const _province_info_pop_cultures_key : StringName = &"pop_cultures" +const _province_info_rgo_key : StringName = &"rgo" +const _province_info_buildings_key : StringName = &"buildings" + +const _building_info_building_key : StringName = &"building" +const _building_info_level_key : StringName = &"level" +const _building_info_expansion_state_key : StringName = &"expansion_state" +const _building_info_start_date_key : StringName = &"start_date" +const _building_info_end_date_key : StringName = &"end_date" +const _building_info_expansion_progress_key : StringName = &"expansion_progress" + +const _piechart_info_size_key : StringName = &"size" +const _piechart_info_colour_key : StringName = &"colour" + +var _selected_index : int: + get: return _selected_index + set(v): + _selected_index = v + _update_info() +var _province_info : Dictionary + +func _check_class(object : Object, klass : String, name : String) -> bool: + if object.get_class() == klass: + return true + else: + push_error("Invalid ", name, " class: ", object.get_class(), " (expected ", klass, ")") + return false + +func _try_get_node(path : NodePath, klass : String) -> Node: + var node : Node = get_node(path) + if node != null: + if _check_class(node, klass, path): + return node + else: + push_error("Failed to get node: ", path, " (returned null)") + return null + +func _ready(): + GameSingleton.province_selected.connect(_on_province_selected) + GameSingleton.state_updated.connect(_update_info) + + add_child(GameSingleton.generate_gui("province_interface.gui", "province_view")) + + var close_button : Button = _try_get_node(^"./province_view/close_button", "Button") + if close_button != null: + close_button.pressed.connect(_on_close_button_pressed) + + _region_name_label = _try_get_node(^"./province_view/province_view_header/state_name", "Label") + + _province_name_label = _try_get_node(^"./province_view/province_view_header/province_name", "Label") + + _life_rating_bar = _try_get_node(^"./province_view/province_view_header/liferating", "TextureProgressBar") + + var goods_icon : TextureRect = _try_get_node(^"./province_view/province_statistics/goods_type", "TextureRect") + if goods_icon != null: + var texture := goods_icon.texture + if _check_class(texture, "GFXIconTexture", "good_texture"): + _rgo_icon_texture = texture + + + var rgo_population_label : Label = _try_get_node(^"./province_view/province_statistics/rgo_population", "Label") + if rgo_population_label != null: + rgo_population_label.text = "0" + + #^"./province_view/province_statistics/crime_icon" + + _total_population_label = _try_get_node(^"./province_view/province_statistics/total_population", "Label") + + #^"./province_view/province_statistics/growth" + #^"./province_view/province_statistics/workforce_chart" + #^"./province_view/province_statistics/ideology_chart" + #^"./province_view/province_statistics/culture_chart" + + $province_view/province_view_header/occupation_progress.visible = false + $province_view/province_view_header/occupation_icon.visible = false + $province_view/province_view_header/occupation_flag.visible = false + $province_view/province_colony.visible = false + $province_view/province_other.visible = false + $province_view/province_buildings/army_size.visible = false + $province_view/province_buildings/army_text.visible = false + $province_view/province_buildings/navy_text.visible = false + $province_view/national_focus_window.visible = false + + _update_info() + +func _notification(what : int): + match what: + NOTIFICATION_TRANSLATION_CHANGED: + _update_info() +""" +enum { CANNOT_EXPAND, CAN_EXPAND, PREPARING, EXPANDING } + +func _expand_building(building_identifier : String) -> void: + if GameSingleton.expand_building(_selected_index, building_identifier) != OK: + push_error("Failed to expand ", building_identifier, " in province #", _selected_index); + +# Each building row contains: +# level - Level Label +# name - Name Label +# button - Expansion Button +# progress_bar - Expansion ProgressBar +var _building_rows : Array[Dictionary] + + +# REQUIREMENTS: +# * UI-183, UI-185, UI-186, UI-765, UI-187, UI-188, UI-189 +# * UI-191, UI-193, UI-194, UI-766, UI-195, UI-196, UI-197 +# * UI-199, UI-201, UI-202, UI-767, UI-203, UI-204, UI-205 +func _add_building_row() -> void: + var row : Dictionary = {} + + row.level = Label.new() + row.level.text = "X" + _buildings_container.add_child(row.level) + + row.name = Label.new() + row.name.text = _building_info_building_key + _missing_suffix + _buildings_container.add_child(row.name) + + row.button = Button.new() + row.button.text = "EXPAND_PROVINCE_BUILDING" + row.button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.button.mouse_filter = Control.MOUSE_FILTER_PASS + row.button.focus_mode = FOCUS_NONE + row.button.pressed.connect(func(): _expand_building(row.name.text)) + _buildings_container.add_child(row.button) + + row.progress_bar = ProgressBar.new() + row.progress_bar.max_value = 1 + row.progress_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.progress_bar.mouse_filter = Control.MOUSE_FILTER_PASS + _buildings_container.add_child(row.progress_bar) + + _building_rows.append(row) + _set_building_row(_building_rows.size() - 1, {}) + +func _set_building_row(index : int, building : Dictionary) -> void: + if index < 0 or index > _building_rows.size(): + push_error("Invalid building row index: ", index, " (max ", _building_rows.size(), ")") + return + if index == _building_rows.size(): _add_building_row() + var row := _building_rows[index] + if building.is_empty(): + row.level.visible = false + row.name.visible = false + row.progress_bar.visible = false + row.button.visible = false + return + row.level.text = str(building.get(_building_info_level_key, 0)) + row.level.visible = true + row.name.text = building.get(_building_info_building_key, + _building_info_building_key + _missing_suffix) + row.name.visible = true + + var expansion_state : int = building.get(_building_info_expansion_state_key, + CANNOT_EXPAND) + var show_progress_bar := expansion_state == PREPARING or expansion_state == EXPANDING + row.progress_bar.value = building.get(_building_info_expansion_progress_key, 0) + row.progress_bar.visible = show_progress_bar + row.button.disabled = expansion_state != CAN_EXPAND + row.button.visible = not show_progress_bar +""" +func _update_info() -> void: + _province_info = GameSingleton.get_province_info_from_index(_selected_index) + if _province_info: + if _province_name_label: + _province_name_label.text = "PROV" + _province_info.get(_province_info_province_key, + _province_info_province_key + _missing_suffix) + if _region_name_label: + _region_name_label.text = _province_info.get(_province_info_region_key, + _province_info_region_key + _missing_suffix) + if _life_rating_bar: + _life_rating_bar.value = _province_info.get(_province_info_life_rating_key, 0) * 0 + + if _total_population_label: + _total_population_label.text = Localisation.tr_number(_province_info.get(_province_info_total_population_key, 0)) + + #_pop_type_chart.set_to_distribution(_province_info.get(_province_info_pop_types_key, {})) + #_pop_ideology_chart.set_to_distribution(_province_info.get(_province_info_pop_ideologies_key, {})) + #_pop_culture_chart.set_to_distribution(_province_info.get(_province_info_pop_cultures_key, {})) + + if _rgo_icon_texture: + _rgo_icon_texture.set_icon_index((_selected_index % 40) + 1) + + #var buildings : Array = _province_info.get(_province_info_buildings_key, []) + #for i in max(buildings.size(), _building_rows.size()): + # _set_building_row(i, buildings[i] if i < buildings.size() else {}) + + show() + else: + hide() + mouse_exited.emit() + +func _on_province_selected(index : int) -> void: + _selected_index = index + +func _on_close_button_pressed() -> void: + GameSingleton.set_selected_province(0) diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd deleted file mode 100644 index ae42450..0000000 --- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd +++ /dev/null @@ -1,142 +0,0 @@ -extends PanelContainer - -@export var _province_name_label : Label -@export var _region_name_label : Label -@export var _life_rating_bar : ProgressBar -@export var _terrain_type_name_label : Label -@export var _total_population_label : Label -@export var _rgo_icon_texture_rect : TextureRect -@export var _rgo_name_label : Label -@export var _buildings_container : Container -@export var _pop_type_chart : PieChart -@export var _pop_ideology_chart : PieChart -@export var _pop_culture_chart : PieChart - -const _missing_suffix : String = "_MISSING" - -var _selected_index : int: - get: return _selected_index - set(v): - _selected_index = v - _update_info() -var _province_info : Dictionary - -func _ready(): - GameSingleton.province_selected.connect(_on_province_selected) - GameSingleton.state_updated.connect(_update_info) - _update_info() - -func _notification(what : int): - match what: - NOTIFICATION_TRANSLATION_CHANGED: - _update_info() - -enum { CANNOT_EXPAND, CAN_EXPAND, PREPARING, EXPANDING } - -func _expand_building(building_identifier : String) -> void: - if GameSingleton.expand_building(_selected_index, building_identifier) != OK: - push_error("Failed to expand ", building_identifier, " in province #", _selected_index); - -# Each building row contains: -# level - Level Label -# name - Name Label -# button - Expansion Button -# progress_bar - Expansion ProgressBar -var _building_rows : Array[Dictionary] - -# REQUIREMENTS: -# * UI-183, UI-185, UI-186, UI-765, UI-187, UI-188, UI-189 -# * UI-191, UI-193, UI-194, UI-766, UI-195, UI-196, UI-197 -# * UI-199, UI-201, UI-202, UI-767, UI-203, UI-204, UI-205 -func _add_building_row() -> void: - var row : Dictionary = {} - - row.level = Label.new() - row.level.text = "X" - _buildings_container.add_child(row.level) - - row.name = Label.new() - row.name.text = GameSingleton.get_building_info_building_key() + _missing_suffix - _buildings_container.add_child(row.name) - - row.button = Button.new() - row.button.text = "EXPAND_PROVINCE_BUILDING" - row.button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - row.button.mouse_filter = Control.MOUSE_FILTER_PASS - row.button.focus_mode = FOCUS_NONE - row.button.pressed.connect(func(): _expand_building(row.name.text)) - _buildings_container.add_child(row.button) - - row.progress_bar = ProgressBar.new() - row.progress_bar.max_value = 1 - row.progress_bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL - row.progress_bar.mouse_filter = Control.MOUSE_FILTER_PASS - _buildings_container.add_child(row.progress_bar) - - _building_rows.append(row) - _set_building_row(_building_rows.size() - 1, {}) - -func _set_building_row(index : int, building : Dictionary) -> void: - if index < 0 or index > _building_rows.size(): - push_error("Invalid building row index: ", index, " (max ", _building_rows.size(), ")") - return - if index == _building_rows.size(): _add_building_row() - var row := _building_rows[index] - if building.is_empty(): - row.level.visible = false - row.name.visible = false - row.progress_bar.visible = false - row.button.visible = false - return - row.level.text = str(building.get(GameSingleton.get_building_info_level_key(), 0)) - row.level.visible = true - row.name.text = building.get(GameSingleton.get_building_info_building_key(), - GameSingleton.get_building_info_building_key() + _missing_suffix) - row.name.visible = true - - var expansion_state : int = building.get(GameSingleton.get_building_info_expansion_state_key(), - CANNOT_EXPAND) - var show_progress_bar := expansion_state == PREPARING or expansion_state == EXPANDING - row.progress_bar.value = building.get(GameSingleton.get_building_info_expansion_progress_key(), 0) - row.progress_bar.visible = show_progress_bar - row.button.disabled = expansion_state != CAN_EXPAND - row.button.visible = not show_progress_bar - -func _update_info() -> void: - _province_info = GameSingleton.get_province_info_from_index(_selected_index) - if _province_info: - _province_name_label.text = "PROV" + _province_info.get(GameSingleton.get_province_info_province_key(), - GameSingleton.get_province_info_province_key() + _missing_suffix) - _region_name_label.text = _province_info.get(GameSingleton.get_province_info_region_key(), - GameSingleton.get_province_info_region_key() + _missing_suffix) - _life_rating_bar.value = _province_info.get(GameSingleton.get_province_info_life_rating_key(), 0) - _life_rating_bar.tooltip_text = tr("LIFE_RATING_TOOLTIP").format({ - "life_rating": Localisation.tr_number(_life_rating_bar.value) - }) - _terrain_type_name_label.text = _province_info.get(GameSingleton.get_province_info_terrain_type_key(), - GameSingleton.get_province_info_terrain_type_key() + _missing_suffix) - - _total_population_label.text = Localisation.tr_number(_province_info.get(GameSingleton.get_province_info_total_population_key(), 0)) - - _pop_type_chart.set_to_distribution(_province_info.get(GameSingleton.get_province_info_pop_types_key(), {})) - _pop_ideology_chart.set_to_distribution(_province_info.get(GameSingleton.get_province_info_pop_ideologies_key(), {})) - _pop_culture_chart.set_to_distribution(_province_info.get(GameSingleton.get_province_info_pop_cultures_key(), {})) - - _rgo_name_label.text = _province_info.get(GameSingleton.get_province_info_rgo_key(), - GameSingleton.get_province_info_rgo_key() + _missing_suffix) - _rgo_icon_texture_rect.texture = null - - var buildings : Array = _province_info.get(GameSingleton.get_province_info_buildings_key(), []) - for i in max(buildings.size(), _building_rows.size()): - _set_building_row(i, buildings[i] if i < buildings.size() else {}) - - show() - else: - hide() - mouse_exited.emit() - -func _on_province_selected(index : int) -> void: - _selected_index = index - -func _on_close_button_pressed() -> void: - GameSingleton.set_selected_province(0) diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn deleted file mode 100644 index bbd0b95..0000000 --- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn +++ /dev/null @@ -1,124 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://byq323jbel48u"] - -[ext_resource type="Script" path="res://src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.gd" id="1_3n8k5"] -[ext_resource type="PackedScene" uid="uid://cr7p1k2xm7mum" path="res://src/Game/Theme/PieChart/PieChart.tscn" id="2_3oytt"] - -[node name="ProvinceOverviewPanel" type="PanelContainer" node_paths=PackedStringArray("_province_name_label", "_region_name_label", "_life_rating_bar", "_terrain_type_name_label", "_total_population_label", "_rgo_icon_texture_rect", "_rgo_name_label", "_buildings_container", "_pop_type_chart", "_pop_ideology_chart", "_pop_culture_chart")] -editor_description = "UI-56" -anchors_preset = 2 -anchor_top = 1.0 -anchor_bottom = 1.0 -offset_top = -300.0 -offset_right = 200.0 -grow_vertical = 0 -mouse_filter = 1 -script = ExtResource("1_3n8k5") -_province_name_label = NodePath("PanelList/TopBarList/NameList/ProvinceName") -_region_name_label = NodePath("PanelList/TopBarList/NameList/RegionName") -_life_rating_bar = NodePath("PanelList/TopBarList/NameList/LifeRatingBar") -_terrain_type_name_label = NodePath("PanelList/TopBarList/NameList/TerrainTypeName") -_total_population_label = NodePath("PanelList/InteractList/TotalPopulation") -_rgo_icon_texture_rect = NodePath("PanelList/InteractList/RGOInfo/RGOIcon") -_rgo_name_label = NodePath("PanelList/InteractList/RGOInfo/RGOName") -_buildings_container = NodePath("PanelList/InteractList/BuildingsContainer") -_pop_type_chart = NodePath("PanelList/InteractList/PopStats/PopTypeChart") -_pop_ideology_chart = NodePath("PanelList/InteractList/PopStats/PopIdeologyChart") -_pop_culture_chart = NodePath("PanelList/InteractList/PopStats/PopCultureChart") - -[node name="PanelList" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="TopBarList" type="HBoxContainer" parent="PanelList"] -layout_mode = 2 - -[node name="NameList" type="VBoxContainer" parent="PanelList/TopBarList"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 0 - -[node name="ProvinceName" type="Label" parent="PanelList/TopBarList/NameList"] -editor_description = "UI-57" -layout_mode = 2 -text = "province_MISSING" -vertical_alignment = 1 - -[node name="RegionName" type="Label" parent="PanelList/TopBarList/NameList"] -editor_description = "UI-58" -layout_mode = 2 -text = "region_MISSING" -vertical_alignment = 1 - -[node name="LifeRatingBar" type="ProgressBar" parent="PanelList/TopBarList/NameList"] -editor_description = "UI-62" -layout_mode = 2 -mouse_filter = 1 - -[node name="TerrainTypeName" type="Label" parent="PanelList/TopBarList/NameList"] -layout_mode = 2 -text = "terrain_type_MISSING" -vertical_alignment = 1 - -[node name="CloseButton" type="Button" parent="PanelList/TopBarList"] -custom_minimum_size = Vector2(30, 30) -layout_mode = 2 -size_flags_vertical = 0 -focus_mode = 0 -mouse_filter = 1 -text = "X" - -[node name="InteractList" type="VBoxContainer" parent="PanelList"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="HSeparator" type="HSeparator" parent="PanelList/InteractList"] -layout_mode = 2 -mouse_filter = 1 - -[node name="TotalPopulation" type="Label" parent="PanelList/InteractList"] -editor_description = "UI-121" -layout_mode = 2 -tooltip_text = "PROVINCE_POPULATION_TOOLTIP" -mouse_filter = 1 -text = "total_population_MISSING" - -[node name="RGOInfo" type="HBoxContainer" parent="PanelList/InteractList"] -editor_description = "UI-112" -layout_mode = 2 - -[node name="RGOIcon" type="TextureRect" parent="PanelList/InteractList/RGOInfo"] -editor_description = "UI-100" -layout_mode = 2 - -[node name="RGOName" type="Label" parent="PanelList/InteractList/RGOInfo"] -layout_mode = 2 -text = "rgo_MISSING" -vertical_alignment = 1 - -[node name="HSeparator2" type="HSeparator" parent="PanelList/InteractList"] -layout_mode = 2 -mouse_filter = 1 - -[node name="PopStats" type="HBoxContainer" parent="PanelList/InteractList"] -editor_description = "UI-124" -layout_mode = 2 - -[node name="PopTypeChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")] -editor_description = "UI-125" -layout_mode = 2 - -[node name="PopIdeologyChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")] -layout_mode = 2 - -[node name="PopCultureChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")] -editor_description = "UI-127" -layout_mode = 2 - -[node name="HSeparator3" type="HSeparator" parent="PanelList/InteractList"] -layout_mode = 2 -mouse_filter = 1 - -[node name="BuildingsContainer" type="GridContainer" parent="PanelList/InteractList"] -layout_mode = 2 -columns = 3 - -[connection signal="pressed" from="PanelList/TopBarList/CloseButton" to="." method="_on_close_button_pressed"] diff --git a/game/src/Game/GameSession/TerrainMap.gdshader b/game/src/Game/GameSession/TerrainMap.gdshader index 852ccc3..35a108b 100644 --- a/game/src/Game/GameSession/TerrainMap.gdshader +++ b/game/src/Game/GameSession/TerrainMap.gdshader @@ -14,34 +14,89 @@ uniform uint selected_index; uniform sampler2DArray terrain_tex: source_color, repeat_enable, filter_linear; // The number of times the terrain textures should tile vertically uniform float terrain_tile_factor; +// Map stripe mask texture +uniform sampler2D stripe_tex: source_color, repeat_enable, filter_linear; +// The number of times the stripe texture should tile vertically +uniform float stripe_tile_factor; +// Land map tint +uniform sampler2D colormap_land_tex: source_color, repeat_enable, filter_linear; +// Water map tint +uniform sampler2D colormap_water_tex: source_color, repeat_enable, filter_linear; const vec3 highlight_colour = vec3(1.0); -vec3 get_terrain_colour(vec2 uv, vec2 corner, vec2 half_pixel_size, vec2 terrain_uv) { +vec3 get_terrain_colour( + vec2 uv, vec2 corner, vec2 half_pixel_size, // Components for calculating province sampling UV + float stripe_mask, // Stripe mask value - between 0 (base) and 1 (stripe) + vec2 terrain_uv, // UV coordinates scaled for terrain texture tiling + vec3 land_tint_colour, vec3 water_tint_colour // Colours for tinting terrain +) { + uvec3 province_data = read_uvec3(fma(corner, half_pixel_size, uv)); - vec4 province_colour = texelFetch(province_colour_tex, ivec2(province_data.rg), 0); - vec3 terrain_colour = texture(terrain_tex, vec3(terrain_uv, float(province_data.b))).rgb; - vec3 mixed_colour = mix(terrain_colour, province_colour.rgb, province_colour.a); - uint index = uvec2_to_uint(province_data.rg); - float mix_val = 0.1 * (float(index == hover_index) + float(index == selected_index)); - return mix(mixed_colour, highlight_colour, mix_val); + uint province_index = uvec2_to_uint(province_data.rg); + uint terrain_index = province_data.b; + + province_data.r *= 2u; // Double "x coordinate" as colours come in (base, stripe) pairs + vec4 province_base_colour = texelFetch(province_colour_tex, ivec2(province_data.rg), 0); + province_data.r += 1u; // Add 1 to "x coordinate" to move from base to strip colour + vec4 province_stripe_colour = texelFetch(province_colour_tex, ivec2(province_data.rg), 0); + vec4 province_colour = mix(province_base_colour, province_stripe_colour, stripe_mask); + + vec3 terrain_colour = texture(terrain_tex, vec3(terrain_uv, float(terrain_index))).rgb; + vec3 tint_colour = mix(land_tint_colour, water_tint_colour, float(terrain_index == 0u)); + vec3 tinted_terrain_colour = mix(terrain_colour, tint_colour, 0.3); + vec3 mixed_colour = mix(tinted_terrain_colour, province_colour.rgb, province_colour.a); + + float highlight_mix_val = 0.1 * (float(province_index == hover_index) + float(province_index == selected_index)); + return mix(mixed_colour, highlight_colour, highlight_mix_val); +} + +// Rescale UV coordinates to remove squashing caused by normalisation +vec2 denormalise(vec2 uv, vec2 dims) { + return vec2(uv.x * dims.x / dims.y, uv.y); } vec3 mix_terrain_colour(vec2 uv) { vec2 map_size = vec2(textureSize(province_shape_tex, 0).xy) * province_shape_subdivisions; - vec2 pixel_offset = fract(fma(uv, map_size, vec2(0.5))); + vec2 uv_centred = fma(uv, map_size, vec2(0.5)); + vec2 pixel_offset = fract(uv_centred); vec2 half_pixel_size = 0.49 / map_size; - vec2 terrain_uv = uv; - terrain_uv.x *= map_size.x / map_size.y; - terrain_uv *= terrain_tile_factor; + // UV coords adjusted to remove squashing caused by normalisation relative to map dimensions + vec2 unscaled_uv = denormalise(uv, map_size); + + vec2 stripe_uv = unscaled_uv * stripe_tile_factor; + // Stripe mask value - between 0 (base) and 1 (stripe) + float stripe_mask = texture(stripe_tex, stripe_uv).b; + + vec2 terrain_uv = unscaled_uv * terrain_tile_factor; + vec2 colormap_uv = vec2(uv.x, 1.0 - uv.y); + vec3 colormap_land_colour = texture(colormap_land_tex, colormap_uv).rgb; + vec3 colormap_water_colour = texture(colormap_water_tex, colormap_uv).rgb; return mix( - mix(get_terrain_colour(uv, vec2(-1, -1), half_pixel_size, terrain_uv), - get_terrain_colour(uv, vec2(+1, -1), half_pixel_size, terrain_uv), pixel_offset.x), - mix(get_terrain_colour(uv, vec2(-1, +1), half_pixel_size, terrain_uv), - get_terrain_colour(uv, vec2(+1, +1), half_pixel_size, terrain_uv), pixel_offset.x), - pixel_offset.y); + mix( + get_terrain_colour( + uv, vec2(-1, -1), half_pixel_size, stripe_mask, + terrain_uv, colormap_land_colour, colormap_water_colour + ), + get_terrain_colour( + uv, vec2(+1, -1), half_pixel_size, stripe_mask, + terrain_uv, colormap_land_colour, colormap_water_colour + ), pixel_offset.x + ), + mix( + get_terrain_colour( + uv, vec2(-1, +1), half_pixel_size, stripe_mask, + terrain_uv, colormap_land_colour, colormap_water_colour + ), + get_terrain_colour( + uv, vec2(+1, +1), half_pixel_size, stripe_mask, + terrain_uv, colormap_land_colour, colormap_water_colour + ), pixel_offset.x + ), + pixel_offset.y + ); } void fragment() { diff --git a/game/src/Game/GlobalClass/ShaderManager.gd b/game/src/Game/GlobalClass/ShaderManager.gd index fd91e31..ace3c3a 100644 --- a/game/src/Game/GlobalClass/ShaderManager.gd +++ b/game/src/Game/GlobalClass/ShaderManager.gd @@ -8,6 +8,18 @@ const param_hover_index : StringName = &"hover_index" const param_selected_index : StringName = &"selected_index" const param_terrain_tex : StringName = &"terrain_tex" const param_terrain_tile_factor : StringName = &"terrain_tile_factor" +const param_stripe_tex : StringName = &"stripe_tex" +const param_stripe_tile_factor : StringName = &"stripe_tile_factor" +const param_colormap_land_tex : StringName = &"colormap_land_tex" +const param_colormap_water_tex : StringName = &"colormap_water_tex" + +func _set_shader_texture(shader_material : ShaderMaterial, texture_path : StringName, texture_param : StringName) -> Error: + var texture := AssetManager.get_texture(texture_path) + if texture == null: + push_error("Failed to get texture: ", texture_path) + return FAILED + shader_material.set_shader_parameter(texture_param, texture) + return OK func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: # Shader Material @@ -19,31 +31,45 @@ func set_up_shader(material : Material, add_cosmetic_textures : bool) -> Error: return FAILED var shader_material : ShaderMaterial = material + var ret : Error = OK + # Province shape texture var province_shape_texture := GameSingleton.get_province_shape_texture() if province_shape_texture == null: push_error("Failed to get province shape texture!") - return FAILED + ret = FAILED shader_material.set_shader_parameter(param_province_shape_tex, province_shape_texture) var subdivisions := GameSingleton.get_province_shape_image_subdivisions() if subdivisions.x < 1 or subdivisions.y < 1: push_error("Invalid province shape image subdivision: ", subdivisions.x, "x", subdivisions.y) - return FAILED + ret = FAILED shader_material.set_shader_parameter(param_province_shape_subdivisions, Vector2(subdivisions)) if add_cosmetic_textures: + # Province colour texture var map_province_colour_texture := GameSingleton.get_province_colour_texture() if map_province_colour_texture == null: push_error("Failed to get province colour image!") - return FAILED + ret = FAILED shader_material.set_shader_parameter(param_province_colour_tex, map_province_colour_texture) # Terrain texture var terrain_texture := GameSingleton.get_terrain_texture() if terrain_texture == null: push_error("Failed to get terrain texture!") - return FAILED + ret = FAILED shader_material.set_shader_parameter(param_terrain_tex, terrain_texture) - return OK + # Stripe texture + if _set_shader_texture(shader_material, &"map/terrain/stripes.dds", param_stripe_tex) != OK: + ret = FAILED + + # Land colormap + if _set_shader_texture(shader_material, &"map/terrain/colormap.dds", param_colormap_land_tex) != OK: + ret = FAILED + # Water colormap + if _set_shader_texture(shader_material, &"map/terrain/colormap_water.dds", param_colormap_water_tex) != OK: + ret = FAILED + + return ret |