diff options
Diffstat (limited to 'extension/src')
17 files changed, 1001 insertions, 223 deletions
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, |