diff options
author | hop311 <hop3114@gmail.com> | 2023-12-07 23:45:19 +0100 |
---|---|---|
committer | hop311 <hop3114@gmail.com> | 2023-12-11 10:51:01 +0100 |
commit | fd375bdb35d8a7b2ac9cf3dd02cdb0f197451a0b (patch) | |
tree | b22d464dbf8e0e2569b9be5aa130e4def2e51207 /extension | |
parent | a6952efba078e49d6555b0586230986a2cb7ed40 (diff) |
Big UI commit - GUINode, MaskedFlag, PieChart, etc
Diffstat (limited to 'extension')
14 files changed, 975 insertions, 117 deletions
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation -Subproject 444a27726695478e44e0166e75df1f354b6432d +Subproject 6b9cf7f9dff1570b10a0a0f988e1b1ef418d024 diff --git a/extension/src/openvic-extension/UIAdapter.cpp b/extension/src/openvic-extension/UIAdapter.cpp index 1478a5a..cbe898c 100644 --- a/extension/src/openvic-extension/UIAdapter.cpp +++ b/extension/src/openvic-extension/UIAdapter.cpp @@ -12,6 +12,8 @@ #include <godot_cpp/variant/utility_functions.hpp> #include "openvic-extension/classes/GFXIconTexture.hpp" +#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" +#include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/utility/Utilities.hpp" using namespace godot; @@ -20,12 +22,14 @@ 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) { +bool GodotGUIBuilder::generate_element( + GUI::Element const* element, String const& name, 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 { + static const std::map<std::string_view, bool (*)(GUI::Element const&, String 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 }, @@ -36,7 +40,7 @@ bool GodotGUIBuilder::generate_element(GUI::Element const* element, AssetManager }; 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); + return it->second(*element, name, asset_manager, result); } else { UtilityFunctions::push_error("Invalid GUI element type: ", std_view_to_godot_string(element->get_type())); result = nullptr; @@ -45,7 +49,7 @@ bool GodotGUIBuilder::generate_element(GUI::Element const* element, AssetManager } template<std::derived_from<Control> T> -static T* new_control(GUI::Element const& element) { +static T* new_control(GUI::Element const& element, String const& name) { T* node = memnew(T); ERR_FAIL_NULL_V(node, nullptr); @@ -57,7 +61,12 @@ static T* new_control(GUI::Element const& element) { { CENTER, PRESET_CENTER } }; - node->set_name(std_view_to_godot_string(element.get_name())); + if (name.is_empty()) { + node->set_name(std_view_to_godot_string(element.get_name())); + } else { + node->set_name(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); @@ -71,7 +80,9 @@ static T* new_control(GUI::Element const& element) { return node; } -bool GodotGUIBuilder::generate_icon(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { +bool GodotGUIBuilder::generate_icon( + GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result +) { GUI::Icon const& icon = static_cast<GUI::Icon const&>(element); result = nullptr; @@ -81,11 +92,8 @@ bool GodotGUIBuilder::generate_icon(GUI::Element const& element, AssetManager& a 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; - } + TextureRect* godot_texture_rect = new_control<TextureRect>(icon, name); + ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); 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()); @@ -98,29 +106,24 @@ bool GodotGUIBuilder::generate_icon(GUI::Element const& element, AssetManager& a 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; - } + TextureRect* godot_texture_rect = new_control<TextureRect>(icon, name); + ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); - const StringName texture_file = - std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::MaskedFlag>()->get_overlay_file()); - const Ref<ImageTexture> texture = asset_manager.get_texture(texture_file); + GFX::MaskedFlag const* masked_flag = icon.get_sprite()->cast_to<GFX::MaskedFlag>(); + Ref<GFXMaskedFlagTexture> texture = GFXMaskedFlagTexture::make_gfx_masked_flag_texture(masked_flag); 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); + UtilityFunctions::push_error("Failed to make GFXMaskedFlagTexture 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; - } + TextureProgressBar* godot_progress_bar = new_control<TextureProgressBar>(icon, name); + ERR_FAIL_NULL_V_MSG( + godot_progress_bar, false, vformat("Failed to create TextureProgressBar for GUI icon %s", icon_name) + ); const StringName back_texture_file = std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::ProgressBar>()->get_back_texture_file()); @@ -146,7 +149,23 @@ bool GodotGUIBuilder::generate_icon(GUI::Element const& element, AssetManager& a result = godot_progress_bar; } else if (icon.get_sprite()->is_type<GFX::PieChart>()) { + TextureRect* godot_texture_rect = new_control<TextureRect>(icon, name); + ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); + + GFX::PieChart const* pie_chart = icon.get_sprite()->cast_to<GFX::PieChart>(); + Ref<GFXPieChartTexture> texture = GFXPieChartTexture::make_gfx_pie_chart_texture(pie_chart); + if (texture.is_valid()) { + godot_texture_rect->set_texture(texture); + // TODO - work out why this is needed + Vector2 pos = godot_texture_rect->get_position(); + pos.x -= texture->get_width() / 2; + godot_texture_rect->set_position(pos); + } else { + UtilityFunctions::push_error("Failed to make GFXPieChartTexture for GUI icon ", icon_name); + ret = false; + } + result = godot_texture_rect; } else if (icon.get_sprite()->is_type<GFX::LineChart>()) { } else { @@ -161,21 +180,21 @@ bool GodotGUIBuilder::generate_icon(GUI::Element const& element, AssetManager& a return ret; } -bool GodotGUIBuilder::generate_button(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { +bool GodotGUIBuilder::generate_button( + GUI::Element const& element, String const& name, 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; - } + Button* godot_button = new_control<Button>(button, name); + ERR_FAIL_NULL_V_MSG(godot_button, false, vformat("Failed to create Button for GUI button %s", button_name)); - godot_button->set_text(std_view_to_godot_string(button.get_text())); - //godot_button->set_flat(true); + if (!button.get_text().empty()) { + godot_button->set_text(std_view_to_godot_string(button.get_text())); + } bool ret = true; if (button.get_sprite() != nullptr) { @@ -188,10 +207,10 @@ bool GodotGUIBuilder::generate_button(GUI::Element const& element, AssetManager& 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_overlay_file())); + GFX::MaskedFlag const* masked_flag = button.get_sprite()->cast_to<GFX::MaskedFlag>(); + texture = GFXMaskedFlagTexture::make_gfx_masked_flag_texture(masked_flag); if (texture.is_null()) { - UtilityFunctions::push_error("Failed to load masked flag sprite for GUI button ", button_name); + UtilityFunctions::push_error("Failed to make GFXMaskedFlagTexture for GUI button ", button_name); ret = false; } } else { @@ -205,8 +224,9 @@ bool GodotGUIBuilder::generate_button(GUI::Element const& element, AssetManager& Ref<StyleBoxTexture> stylebox; stylebox.instantiate(); if (stylebox.is_valid()) { + static const StringName theme_name_normal = "normal"; stylebox->set_texture(texture); - godot_button->add_theme_stylebox_override("normal", stylebox); + godot_button->add_theme_stylebox_override(theme_name_normal, stylebox); } else { UtilityFunctions::push_error("Failed to load instantiate texture stylebox for GUI button ", button_name); ret = false; @@ -234,18 +254,17 @@ bool GodotGUIBuilder::generate_button(GUI::Element const& element, AssetManager& return ret; } -bool GodotGUIBuilder::generate_checkbox(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { +bool GodotGUIBuilder::generate_checkbox( + GUI::Element const& element, String const& name, 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; - } + CheckBox* godot_checkbox = new_control<CheckBox>(checkbox, name); + ERR_FAIL_NULL_V_MSG(godot_checkbox, false, vformat("Failed to create CheckBox for GUI checkbox %s", checkbox_name)); bool ret = true; if (checkbox.get_sprite() != nullptr) { @@ -267,8 +286,10 @@ bool GodotGUIBuilder::generate_checkbox(GUI::Element const& element, AssetManage ret = false; } } else { - UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(checkbox.get_sprite()->get_type()), - " for GUI checkbox ", checkbox_name); + UtilityFunctions::push_error( + "Invalid sprite type ", std_view_to_godot_string(checkbox.get_sprite()->get_type()), " for GUI checkbox ", + checkbox_name + ); ret = false; } } else { @@ -280,17 +301,16 @@ bool GodotGUIBuilder::generate_checkbox(GUI::Element const& element, AssetManage return ret; } -bool GodotGUIBuilder::generate_text(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { +bool GodotGUIBuilder::generate_text( + GUI::Element const& element, String const& name, 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; - } + Label* godot_label = new_control<Label>(text, name); + ERR_FAIL_NULL_V_MSG(godot_label, false, vformat("Failed to create Label for GUI text %s", text_name)); godot_label->set_text(std_view_to_godot_string(text.get_text())); godot_label->set_size(Utilities::to_godot_fvec2(text.get_max_size())); @@ -329,19 +349,17 @@ bool GodotGUIBuilder::generate_text(GUI::Element const& element, AssetManager& a } bool GodotGUIBuilder::generate_overlapping_elements( - GUI::Element const& element, AssetManager& asset_manager, Control*& result + GUI::Element const& element, String const& name, 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; - } + ColorRect* godot_rect = new_control<ColorRect>(overlapping_elements, name); + ERR_FAIL_NULL_V_MSG( + godot_rect, false, vformat("Failed to create ColorRect for GUI overlapping elements %s", overlapping_elements_name) + ); godot_rect->set_size(Utilities::to_godot_fvec2(overlapping_elements.get_size())); godot_rect->set_color({ 0.0f, 0.5f, 1.0f, 0.2f }); @@ -350,17 +368,16 @@ bool GodotGUIBuilder::generate_overlapping_elements( return true; } -bool GodotGUIBuilder::generate_listbox(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { +bool GodotGUIBuilder::generate_listbox( + GUI::Element const& element, String const& name, 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; - } + ColorRect* godot_rect = new_control<ColorRect>(listbox, name); + ERR_FAIL_NULL_V_MSG(godot_rect, false, vformat("Failed to create ColorRect for GUI listbox %s", listbox_name)); godot_rect->set_size(Utilities::to_godot_fvec2(listbox.get_size())); godot_rect->set_color({ 1.0f, 0.5f, 0.0f, 0.2f }); @@ -369,32 +386,30 @@ bool GodotGUIBuilder::generate_listbox(GUI::Element const& element, AssetManager return true; } -bool GodotGUIBuilder::generate_window(GUI::Element const& element, AssetManager& asset_manager, Control*& result) { +bool GodotGUIBuilder::generate_window( + GUI::Element const& element, String const& name, 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; - } + Panel* godot_panel = new_control<Panel>(window, name); + ERR_FAIL_NULL_V_MSG(godot_panel, false, vformat("Failed to create Panel for GUI window %s", window_name)); 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()) { + for (std::unique_ptr<GUI::Element> const& element : window.get_window_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())); + const bool element_ret = generate_element(element.get(), "", asset_manager, node); + if (node != nullptr) { + godot_panel->add_child(node); + } + if (!element_ret) { + UtilityFunctions::push_error("Errors generating GUI element ", std_view_to_godot_string(element->get_name())); ret = false; } } diff --git a/extension/src/openvic-extension/UIAdapter.hpp b/extension/src/openvic-extension/UIAdapter.hpp index d54a6b5..258b5e9 100644 --- a/extension/src/openvic-extension/UIAdapter.hpp +++ b/extension/src/openvic-extension/UIAdapter.hpp @@ -7,13 +7,22 @@ #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); + + bool generate_element( + GUI::Element const* element, godot::String const& name, AssetManager& asset_manager, godot::Control*& result + ); + +#define GEN_GUI_ARGS \ + GUI::Element const& element, godot::String const& name, AssetManager& asset_manager, godot::Control*& result + + bool generate_icon(GEN_GUI_ARGS); + bool generate_button(GEN_GUI_ARGS); + bool generate_checkbox(GEN_GUI_ARGS); + bool generate_text(GEN_GUI_ARGS); + bool generate_overlapping_elements(GEN_GUI_ARGS); + bool generate_listbox(GEN_GUI_ARGS); + bool generate_window(GEN_GUI_ARGS); + +#undef GEN_GUI_ARGS + } diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp new file mode 100644 index 0000000..3636855 --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp @@ -0,0 +1,183 @@ +#include "GFXMaskedFlagTexture.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::godot_to_std_string; +using OpenVic::Utilities::std_view_to_godot_string; +using OpenVic::Utilities::std_view_to_godot_string_name; + +Error GFXMaskedFlagTexture::_generate_combined_image() { + ERR_FAIL_NULL_V(overlay_image, FAILED); + bool can_update = true; + if (combined_image.is_null() || combined_image->get_size() != overlay_image->get_size()) { + combined_image = Image::create( + overlay_image->get_width(), overlay_image->get_height(), false, overlay_image->get_format() + ); + ERR_FAIL_NULL_V(combined_image, FAILED); + can_update = false; + } + + if (mask_image.is_valid() && flag_image.is_valid()) { + const Vector2i centre_translation = (mask_image->get_size() - combined_image->get_size()) / 2; + for (Vector2i combined_image_point { 0, 0 }; combined_image_point.y < combined_image->get_height(); ++combined_image_point.y) { + for (combined_image_point.x = 0; combined_image_point.x < combined_image->get_width(); ++combined_image_point.x) { + const Color overlay_image_colour = overlay_image->get_pixelv(combined_image_point); + // Translate to mask_image coordinates, keeping the centres of each image aligned. + const Vector2i mask_image_point = combined_image_point + centre_translation; + if ( + 0 <= mask_image_point.x && mask_image_point.x < mask_image->get_width() && + 0 <= mask_image_point.y && mask_image_point.y < mask_image->get_height() + ) { + const Color mask_image_colour = mask_image->get_pixelv(mask_image_point); + // Rescale from mask_image to flag_image coordinates. + const Vector2i flag_image_point = mask_image_point * flag_image->get_size() / mask_image->get_size(); + Color flag_image_colour = flag_image->get_pixelv(flag_image_point); + flag_image_colour.a = mask_image_colour.a; + combined_image->set_pixelv(combined_image_point, flag_image_colour.blend(overlay_image_colour)); + } else { + combined_image->set_pixelv(combined_image_point, overlay_image_colour); + } + } + } + } else { + combined_image->blit_rect(overlay_image, overlay_image->get_used_rect(), {}); + } + + if (can_update) { + update(combined_image); + } else { + set_image(combined_image); + } + return OK; +} + +void GFXMaskedFlagTexture::_bind_methods() { + OV_BIND_METHOD(GFXMaskedFlagTexture::clear); + + OV_BIND_METHOD(GFXMaskedFlagTexture::set_gfx_masked_flag_name, { "gfx_masked_flag_name" }); + OV_BIND_METHOD(GFXMaskedFlagTexture::get_gfx_masked_flag_name); + + OV_BIND_METHOD(GFXMaskedFlagTexture::set_flag_country_name_and_type, { "new_flag_country_name", "new_flag_type" }); + OV_BIND_METHOD(GFXMaskedFlagTexture::get_flag_country_name); + OV_BIND_METHOD(GFXMaskedFlagTexture::get_flag_type); +} + +GFXMaskedFlagTexture::GFXMaskedFlagTexture() : gfx_masked_flag { nullptr }, flag_country { nullptr } {} + +Ref<GFXMaskedFlagTexture> GFXMaskedFlagTexture::make_gfx_masked_flag_texture(GFX::MaskedFlag const* gfx_masked_flag) { + Ref<GFXMaskedFlagTexture> masked_flag_texture; + masked_flag_texture.instantiate(); + ERR_FAIL_NULL_V(masked_flag_texture, nullptr); + if (masked_flag_texture->set_gfx_masked_flag(gfx_masked_flag) == OK) { + return masked_flag_texture; + } else { + return nullptr; + } +} + +void GFXMaskedFlagTexture::clear() { + gfx_masked_flag = nullptr; + flag_country = nullptr; + flag_type = String {}; + + overlay_image.unref(); + mask_image.unref(); + flag_image.unref(); +} + +Error GFXMaskedFlagTexture::set_gfx_masked_flag(GFX::MaskedFlag const* new_gfx_masked_flag) { + if (gfx_masked_flag == new_gfx_masked_flag) { + return OK; + } + if (new_gfx_masked_flag == nullptr) { + clear(); + return OK; + } + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + + const StringName overlay_file = std_view_to_godot_string_name(new_gfx_masked_flag->get_overlay_file()); + const Ref<Image> new_overlay_image = asset_manager->get_image(overlay_file); + ERR_FAIL_NULL_V_MSG(new_overlay_image, FAILED, vformat("Failed to load flag overlay image: %s", overlay_file)); + + const StringName mask_file = std_view_to_godot_string_name(new_gfx_masked_flag->get_mask_file()); + const Ref<Image> new_mask_image = asset_manager->get_image(mask_file); + ERR_FAIL_NULL_V_MSG(new_mask_image, FAILED, vformat("Failed to load flag mask image: %s", mask_file)); + + gfx_masked_flag = new_gfx_masked_flag; + overlay_image = new_overlay_image; + mask_image = new_mask_image; + + return _generate_combined_image(); +} + +Error GFXMaskedFlagTexture::set_gfx_masked_flag_name(String const& gfx_masked_flag_name) { + if (gfx_masked_flag_name.is_empty()) { + return set_gfx_masked_flag(nullptr); + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + GFX::Sprite const* sprite = game_singleton->get_game_manager().get_ui_manager().get_sprite_by_identifier( + godot_to_std_string(gfx_masked_flag_name) + ); + ERR_FAIL_NULL_V_MSG(sprite, FAILED, vformat("GFX sprite not found: %s", gfx_masked_flag_name)); + GFX::MaskedFlag const* new_masked_flag = sprite->cast_to<GFX::MaskedFlag>(); + ERR_FAIL_NULL_V_MSG( + new_masked_flag, FAILED, vformat( + "Invalid type for GFX sprite %s: %s (expected %s)", gfx_masked_flag_name, + std_view_to_godot_string(sprite->get_type()), std_view_to_godot_string(GFX::MaskedFlag::get_type_static()) + ) + ); + return set_gfx_masked_flag(new_masked_flag); +} + +String GFXMaskedFlagTexture::get_gfx_masked_flag_name() const { + return gfx_masked_flag != nullptr ? std_view_to_godot_string(gfx_masked_flag->get_name()) : String {}; +} + +Error GFXMaskedFlagTexture::set_flag_country_and_type(Country const* new_flag_country, StringName const& new_flag_type) { + if (flag_country == new_flag_country && flag_type == new_flag_type) { + return OK; + } + if (new_flag_country != nullptr) { + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + + const Ref<Image> new_flag_image = game_singleton->get_flag_image(new_flag_country, new_flag_type); + ERR_FAIL_NULL_V(new_flag_image, FAILED); + + flag_country = new_flag_country; + flag_type = new_flag_type; + flag_image = new_flag_image; + } else { + flag_country = nullptr; + flag_type = String {}; + flag_image.unref(); + } + return _generate_combined_image(); +} + +Error GFXMaskedFlagTexture::set_flag_country_name_and_type(String const& new_flag_country_name, StringName const& new_flag_type) { + if (new_flag_country_name.is_empty()) { + return set_flag_country_and_type(nullptr, {}); + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + Country const* new_flag_country = game_singleton->get_game_manager().get_country_manager().get_country_by_identifier( + godot_to_std_string(new_flag_country_name) + ); + ERR_FAIL_NULL_V_MSG(new_flag_country, FAILED, vformat("Country not found: %s", new_flag_country_name)); + return set_flag_country_and_type(new_flag_country, new_flag_type); +} + +String GFXMaskedFlagTexture::get_flag_country_name() const { + return flag_country != nullptr ? std_view_to_godot_string(flag_country->get_identifier()) : String {}; +} diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp new file mode 100644 index 0000000..be3b15a --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include <godot_cpp/classes/image_texture.hpp> + +#include <openvic-simulation/country/Country.hpp> +#include <openvic-simulation/interface/GFX.hpp> + +namespace OpenVic { + class GFXMaskedFlagTexture : public godot::ImageTexture { + GDCLASS(GFXMaskedFlagTexture, godot::ImageTexture) + + GFX::MaskedFlag const* PROPERTY(gfx_masked_flag); + Country const* PROPERTY(flag_country); + godot::StringName PROPERTY(flag_type); + + godot::Ref<godot::Image> overlay_image, mask_image, flag_image, combined_image; + + godot::Error _generate_combined_image(); + + protected: + static void _bind_methods(); + + public: + GFXMaskedFlagTexture(); + + /* Create a GFXMaskedFlagTexture using the specific GFX::MaskedFlag. + * Returns nullptr if setting gfx_masked_flag fails. */ + static godot::Ref<GFXMaskedFlagTexture> make_gfx_masked_flag_texture(GFX::MaskedFlag const* gfx_masked_flag); + + /* Reset gfx_masked_flag, flag_country and flag_type to nullptr/an empty string, and unreference all images. + * This does not affect the godot::ImageTexture, which cannot be reset to a null or empty image. */ + void clear(); + + /* Set the GFX::MaskedFlag, load its overlay and mask textures, and regenerate the combined image. */ + godot::Error set_gfx_masked_flag(GFX::MaskedFlag const* new_gfx_masked_flag); + + /* Search for a GFX::MaskedFlag with the specfied name and, if successful, set it using set_gfx_masked_flag. */ + godot::Error set_gfx_masked_flag_name(godot::String const& gfx_masked_flag_name); + + /* Return the name of the GFX::MaskedFlag, or an empty String if it's null */ + godot::String get_gfx_masked_flag_name() const; + + /* Set flag_country and flag_type and update the combined image to use that flag, or no flag if it doesn't exist. */ + godot::Error set_flag_country_and_type(Country const* new_flag_country, godot::StringName const& new_flag_type); + + /* Look up the country with the specified identifier, then call set_flag_country_and_type with the country and + * specified flag_type as arguments. */ + godot::Error set_flag_country_name_and_type( + godot::String const& new_flag_country_name, godot::StringName const& new_flag_type + ); + + /* Return the name of the selected flag's country, or an empty String if it's null */ + godot::String get_flag_country_name() const; + }; +} diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp new file mode 100644 index 0000000..6fe2fe0 --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp @@ -0,0 +1,167 @@ +#include "GFXPieChartTexture.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::godot_to_std_string; +using OpenVic::Utilities::std_view_to_godot_string; +using OpenVic::Utilities::std_view_to_godot_string_name; + +#define PI std::numbers::pi_v<float> + +Error GFXPieChartTexture::_generate_pie_chart_image() { + ERR_FAIL_NULL_V(gfx_pie_chart, FAILED); + if (gfx_pie_chart->get_size() <= 0) { + UtilityFunctions::push_error("Invalid GFX::PieChart size for GFXPieChartTexture - ", gfx_pie_chart->get_size()); + return FAILED; + } + const int32_t pie_chart_size = 2 * gfx_pie_chart->get_size(); + bool can_update = true; + if ( + pie_chart_image.is_null() || pie_chart_image->get_width() != pie_chart_size || + pie_chart_image->get_height() != pie_chart_size + ) { + pie_chart_image = Image::create(pie_chart_size, pie_chart_size, false, Image::FORMAT_RGBA8); + ERR_FAIL_NULL_V(pie_chart_image, FAILED); + can_update = false; + } + + static const Color background_colour { 0.0f, 0.0f, 0.0f, 0.0f }; + if (!slices.empty()) { + const float pie_chart_radius = gfx_pie_chart->get_size(); + const Vector2 centre_translation = Vector2 { 0.5f, 0.5f } - static_cast<Vector2>(pie_chart_image->get_size()) * 0.5f; + for (Vector2i point { 0, 0 }; point.y < pie_chart_image->get_height(); ++point.y) { + for (point.x = 0; point.x < pie_chart_image->get_width(); ++point.x) { + const Vector2 offset = centre_translation + point; + if (offset.length() <= pie_chart_radius) { + float theta = 0.5f * PI + atan2(offset.y, offset.x); + if (theta < 0.0f) { + theta += 2.0f * PI; + } + /* Rescale angle so that total_weight is a full rotation. */ + theta *= total_weight / (2.0f * PI); + Color colour = slices.front().first; + /* Find the slice theta lies in. */ + for (slice_t const& slice : slices) { + if (theta <= slice.second) { + colour = slice.first; + break; + } else { + theta -= slice.second; + } + } + pie_chart_image->set_pixelv(point, colour); + } else { + pie_chart_image->set_pixelv(point, background_colour); + } + } + } + } else { + pie_chart_image->fill(background_colour); + } + + if (can_update) { + update(pie_chart_image); + } else { + set_image(pie_chart_image); + } + return OK; +} + +Error GFXPieChartTexture::set_slices(Array const& new_slices) { + static const StringName colour_key = "colour"; + static const StringName weight_key = "weight"; + + slices.clear(); + total_weight = 0.0f; + for (int32_t i = 0; i < new_slices.size(); ++i) { + Dictionary const& slice_dict = new_slices[i]; + if (!slice_dict.has(colour_key) || !slice_dict.has(weight_key)) { + UtilityFunctions::push_error("Invalid slice keys at index ", i, " - ", slice_dict); + continue; + } + const slice_t slice = std::make_pair(slice_dict[colour_key], slice_dict[weight_key]); + if (slice.second <= 0.0f) { + UtilityFunctions::push_error("Invalid slice weight at index ", i, " - ", slice.second); + continue; + } + total_weight += slice.second; + slices.emplace_back(std::move(slice)); + } + return _generate_pie_chart_image(); +} + +void GFXPieChartTexture::_bind_methods() { + OV_BIND_METHOD(GFXPieChartTexture::clear); + + OV_BIND_METHOD(GFXPieChartTexture::set_gfx_pie_chart_name, { "gfx_pie_chart_name" }); + OV_BIND_METHOD(GFXPieChartTexture::get_gfx_pie_chart_name); + + OV_BIND_METHOD(GFXPieChartTexture::set_slices, { "new_slices" }); +} + +GFXPieChartTexture::GFXPieChartTexture() : total_weight { 0.0f } {} + +Ref<GFXPieChartTexture> GFXPieChartTexture::make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart) { + Ref<GFXPieChartTexture> pie_chart_texture; + pie_chart_texture.instantiate(); + ERR_FAIL_NULL_V(pie_chart_texture, nullptr); + if (pie_chart_texture->set_gfx_pie_chart(gfx_pie_chart) == OK) { + return pie_chart_texture; + } else { + return nullptr; + } +} + +void GFXPieChartTexture::clear() { + gfx_pie_chart = nullptr; + slices.clear(); + total_weight = 0.0f; + + pie_chart_image.unref(); +} + +Error GFXPieChartTexture::set_gfx_pie_chart(GFX::PieChart const* new_gfx_pie_chart) { + if (gfx_pie_chart == new_gfx_pie_chart) { + return OK; + } + if (new_gfx_pie_chart == nullptr) { + clear(); + return OK; + } + + gfx_pie_chart = new_gfx_pie_chart; + + return _generate_pie_chart_image(); +} + +Error GFXPieChartTexture::set_gfx_pie_chart_name(String const& gfx_pie_chart_name) { + if (gfx_pie_chart_name.is_empty()) { + return set_gfx_pie_chart(nullptr); + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + GFX::Sprite const* sprite = game_singleton->get_game_manager().get_ui_manager().get_sprite_by_identifier( + godot_to_std_string(gfx_pie_chart_name) + ); + ERR_FAIL_NULL_V_MSG(sprite, FAILED, vformat("GFX sprite not found: %s", gfx_pie_chart_name)); + GFX::PieChart const* new_pie_chart = sprite->cast_to<GFX::PieChart>(); + ERR_FAIL_NULL_V_MSG( + new_pie_chart, FAILED, vformat( + "Invalid type for GFX sprite %s: %s (expected %s)", gfx_pie_chart_name, + std_view_to_godot_string(sprite->get_type()), std_view_to_godot_string(GFX::PieChart::get_type_static()) + ) + ); + return set_gfx_pie_chart(new_pie_chart); +} + +String GFXPieChartTexture::get_gfx_pie_chart_name() const { + return gfx_pie_chart != nullptr ? std_view_to_godot_string(gfx_pie_chart->get_name()) : String {}; +} diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp new file mode 100644 index 0000000..315b00e --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include <godot_cpp/classes/image_texture.hpp> + +#include <openvic-simulation/interface/GFX.hpp> + +namespace OpenVic { + class GFXPieChartTexture : public godot::ImageTexture { + GDCLASS(GFXPieChartTexture, godot::ImageTexture) + + using slice_t = std::pair<godot::Color, float>; + + GFX::PieChart const* PROPERTY(gfx_pie_chart); + std::vector<slice_t> slices; + float total_weight; + godot::Ref<godot::Image> pie_chart_image; + + godot::Error _generate_pie_chart_image(); + + protected: + static void _bind_methods(); + + public: + GFXPieChartTexture(); + + /* Set slices given new_slices, an Array of Dictionaries, each with the following keys: + * - colour: Color + * - weight: float + */ + godot::Error set_slices(godot::Array const& new_slices); + + /* Create a GFXPieChartTexture using the specific GFX::PieChart. + * Returns nullptr if setting gfx_pie_chart fails. */ + static godot::Ref<GFXPieChartTexture> make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart); + + /* Reset gfx_pie_chart, flag_country and flag_type to nullptr/an empty string, and unreference all images. + * This does not affect the godot::ImageTexture, which cannot be reset to a null or empty image. */ + void clear(); + + /* Set the GFX::PieChart and regenerate the pie chart image. */ + godot::Error set_gfx_pie_chart(GFX::PieChart const* new_gfx_pie_chart); + + /* Search for a GFX::PieChart with the specfied name and, if successful, set it using set_gfx_pie_chart. */ + godot::Error set_gfx_pie_chart_name(godot::String const& gfx_pie_chart_name); + + /* Return the name of the GFX::PieChart, or an empty String if it's null */ + godot::String get_gfx_pie_chart_name() const; + }; +} diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp new file mode 100644 index 0000000..1d55c54 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -0,0 +1,161 @@ +#include "GUINode.hpp" + +#include <godot_cpp/classes/style_box_texture.hpp> +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/UIAdapter.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::godot_to_std_string; +using OpenVic::Utilities::std_view_to_godot_string; + +void GUINode::_bind_methods() { + OV_BIND_METHOD(GUINode::add_gui_element, { "gui_file", "gui_element", "name" }, DEFVAL(String {})); + + OV_BIND_METHOD(GUINode::get_button_node, { "path" }); + OV_BIND_METHOD(GUINode::get_check_box_node, { "path" }); + OV_BIND_METHOD(GUINode::get_label_node, { "path" }); + OV_BIND_METHOD(GUINode::get_panel_node, { "path" }); + OV_BIND_METHOD(GUINode::get_progress_bar_node, { "path" }); + OV_BIND_METHOD(GUINode::get_texture_rect_node, { "path" }); + + OV_BIND_METHOD(GUINode::get_texture_from_node, { "path" }); + OV_BIND_METHOD(GUINode::get_gfx_icon_texture_from_node, { "path" }); + OV_BIND_METHOD(GUINode::get_gfx_masked_flag_texture_from_node, { "path" }); + OV_BIND_METHOD(GUINode::get_gfx_pie_chart_texture_from_node, { "path" }); + + OV_BIND_METHOD(GUINode::hide_node, { "path" }); + OV_BIND_METHOD(GUINode::hide_nodes, { "paths" }); +} + +Error GUINode::_add_gui_element(GUI::Element const* element, String const& name) { + ERR_FAIL_NULL_V(element, FAILED); + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + Error err = OK; + Control* result = nullptr; + if (!GodotGUIBuilder::generate_element(element, name, *asset_manager, result)) { + UtilityFunctions::push_error("Failed to generate GUI element ", std_view_to_godot_string(element->get_name())); + err = FAILED; + } + if (result != nullptr) { + add_child(result); + } + return err; +} + +Error GUINode::add_gui_element(String const& gui_file, String const& gui_element, String const& name) { + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + GUI::Scene const* scene = + game_singleton->get_game_manager().get_ui_manager().get_scene_by_identifier(godot_to_std_string(gui_file)); + ERR_FAIL_NULL_V_MSG(scene, FAILED, vformat("Failed to find GUI file %s", gui_file)); + GUI::Element const* element = scene->get_scene_element_by_identifier(godot_to_std_string(gui_element)); + ERR_FAIL_NULL_V_MSG(element, FAILED, vformat("Failed to find GUI element %s in GUI file %s", gui_element, gui_file)); + return _add_gui_element(element, name); +} + +template<std::derived_from<godot::Node> T> +T* GUINode::_get_cast_node(NodePath const& path) const { + Node* node = get_node_or_null(path); + ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to find node %s", path)); + T* result = Object::cast_to<T>(node); + ERR_FAIL_NULL_V_MSG(result, nullptr, vformat("Failed to cast node %s to type %s", path, T::get_class_static())); + return result; +} + +Button* GUINode::get_button_node(NodePath const& path) const { + return _get_cast_node<Button>(path); +} + +CheckBox* GUINode::get_check_box_node(NodePath const& path) const { + return _get_cast_node<CheckBox>(path); +} + +Label* GUINode::get_label_node(NodePath const& path) const { + return _get_cast_node<Label>(path); +} + +Panel* GUINode::get_panel_node(NodePath const& path) const { + return _get_cast_node<Panel>(path); +} + +TextureProgressBar* GUINode::get_progress_bar_node(NodePath const& path) const { + return _get_cast_node<TextureProgressBar>(path); +} + +TextureRect* GUINode::get_texture_rect_node(NodePath const& path) const { + return _get_cast_node<TextureRect>(path); +} + +Ref<Texture2D> GUINode::get_texture_from_node(NodePath const& path) const { + Node* node = get_node_or_null(path); + ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to find node %s", path)); + if (TextureRect const* texture_rect = Object::cast_to<TextureRect>(node); texture_rect != nullptr) { + const Ref<Texture2D> texture = texture_rect->get_texture(); + ERR_FAIL_NULL_V_MSG(texture, nullptr, vformat("Failed to get Texture2D from TextureRect %s", path)); + return texture; + } else if (Button const* button = Object::cast_to<Button>(node); button != nullptr) { + static const StringName theme_name_normal = "normal"; + const Ref<StyleBox> stylebox = button->get_theme_stylebox(theme_name_normal); + ERR_FAIL_NULL_V_MSG(stylebox, nullptr, vformat("Failed to get StyleBox %s from Button %s", theme_name_normal, path)); + const Ref<StyleBoxTexture> stylebox_texture = stylebox; + ERR_FAIL_NULL_V_MSG( + stylebox_texture, nullptr, vformat( + "Failed to cast StyleBox %s from Button %s to type StyleBoxTexture", theme_name_normal, path + ) + ); + const Ref<Texture2D> result = stylebox_texture->get_texture(); + ERR_FAIL_NULL_V_MSG( + result, nullptr, vformat("Failed to get Texture2D from StyleBoxTexture %s from Button %s", theme_name_normal, path) + ); + return result; + } + ERR_FAIL_V_MSG(nullptr, vformat("Failed to cast node %s to type TextureRect or Button", path)); +} + +template<std::derived_from<godot::Texture2D> T> +Ref<T> GUINode::_get_cast_texture_from_node(NodePath const& path) const { + const Ref<Texture2D> texture = get_texture_from_node(path); + ERR_FAIL_NULL_V(texture, nullptr); + const Ref<T> result = texture; + ERR_FAIL_NULL_V_MSG(result, nullptr, vformat("Failed to cast Texture2D from %s to type %s", path, T::get_class_static())); + return result; +} + +Ref<GFXIconTexture> GUINode::get_gfx_icon_texture_from_node(NodePath const& path) const { + return _get_cast_texture_from_node<GFXIconTexture>(path); +} + +Ref<GFXMaskedFlagTexture> GUINode::get_gfx_masked_flag_texture_from_node(NodePath const& path) const { + return _get_cast_texture_from_node<GFXMaskedFlagTexture>(path); +} + +Ref<GFXPieChartTexture> GUINode::get_gfx_pie_chart_texture_from_node(NodePath const& path) const { + return _get_cast_texture_from_node<GFXPieChartTexture>(path); +} + +Error GUINode::hide_node(NodePath const& path) const { + CanvasItem* node = _get_cast_node<CanvasItem>(path); + if (node == nullptr) { + return FAILED; + } + node->hide(); + return OK; +} + +Error GUINode::hide_nodes(Array const& paths) const { + Error ret = OK; + for (int32_t i = 0; i < paths.size(); ++i) { + if (hide_node(paths[i]) != OK) { + ret = FAILED; + } + } + return ret; +} diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp new file mode 100644 index 0000000..1671547 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include <godot_cpp/classes/button.hpp> +#include <godot_cpp/classes/check_box.hpp> +#include <godot_cpp/classes/label.hpp> +#include <godot_cpp/classes/panel.hpp> +#include <godot_cpp/classes/texture_progress_bar.hpp> +#include <godot_cpp/classes/texture_rect.hpp> + +#include <openvic-simulation/interface/GUI.hpp> + +#include "openvic-extension/classes/GFXIconTexture.hpp" +#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" +#include "openvic-extension/classes/GFXPieChartTexture.hpp" + +namespace OpenVic { + class GUINode : public godot::Control { + GDCLASS(GUINode, godot::Control) + + template<std::derived_from<godot::Node> T> + T* _get_cast_node(godot::NodePath const& path) const; + + template<std::derived_from<godot::Texture2D> T> + godot::Ref<T> _get_cast_texture_from_node(godot::NodePath const& path) const; + + protected: + static void _bind_methods(); + + public: + GUINode() = default; + + godot::Error _add_gui_element(GUI::Element const* element, godot::String const& name); + godot::Error add_gui_element( + godot::String const& gui_file, godot::String const& gui_element, godot::String const& name = "" + ); + + godot::Button* get_button_node(godot::NodePath const& path) const; + godot::CheckBox* get_check_box_node(godot::NodePath const& path) const; + godot::Label* get_label_node(godot::NodePath const& path) const; + godot::Panel* get_panel_node(godot::NodePath const& path) const; + godot::TextureProgressBar* get_progress_bar_node(godot::NodePath const& path) const; + godot::TextureRect* get_texture_rect_node(godot::NodePath const& path) const; + + /* Helper functions to get textures from TextureRects and Buttons. */ + godot::Ref<godot::Texture2D> get_texture_from_node(godot::NodePath const& path) const; + godot::Ref<GFXIconTexture> get_gfx_icon_texture_from_node(godot::NodePath const& path) const; + godot::Ref<GFXMaskedFlagTexture> get_gfx_masked_flag_texture_from_node(godot::NodePath const& path) const; + godot::Ref<GFXPieChartTexture> get_gfx_pie_chart_texture_from_node(godot::NodePath const& path) const; + + godot::Error hide_node(godot::NodePath const& path) const; + godot::Error hide_nodes(godot::Array const& paths) const; + }; +} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 2f6f799..65bdc60 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -3,6 +3,9 @@ #include <godot_cpp/classes/engine.hpp> #include "openvic-extension/classes/GFXIconTexture.hpp" +#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" +#include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUINode.hpp" #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" @@ -40,6 +43,9 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { ClassDB::register_class<MapMesh>(); ClassDB::register_class<GFXIconTexture>(); + ClassDB::register_class<GFXMaskedFlagTexture>(); + ClassDB::register_class<GFXPieChartTexture>(); + ClassDB::register_class<GUINode>(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index 4a80eb9..ac029d9 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -17,6 +17,7 @@ using namespace OpenVic; using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_to_godot_string; +using OpenVic::Utilities::std_to_godot_string_name; using OpenVic::Utilities::std_view_to_godot_string; /* Maximum width or height a GPU texture can have. */ @@ -48,44 +49,27 @@ void GameSingleton::_bind_methods() { OV_BIND_METHOD(GameSingleton::set_selected_province, { "index" }); OV_BIND_METHOD(GameSingleton::expand_building, { "province_index", "building_type_identifier" }); + OV_BIND_METHOD(GameSingleton::get_slave_pop_icon_index); + OV_BIND_METHOD(GameSingleton::get_administrative_pop_icon_index); + OV_BIND_METHOD(GameSingleton::get_rgo_owner_pop_icon_index); + OV_BIND_SMETHOD(int_to_formatted_string, { "val" }); + OV_BIND_SMETHOD(float_to_formatted_string, { "val" }); OV_BIND_METHOD(GameSingleton::set_paused, { "paused" }); OV_BIND_METHOD(GameSingleton::toggle_paused); OV_BIND_METHOD(GameSingleton::is_paused); OV_BIND_METHOD(GameSingleton::increase_speed); OV_BIND_METHOD(GameSingleton::decrease_speed); + OV_BIND_METHOD(GameSingleton::get_speed); OV_BIND_METHOD(GameSingleton::can_increase_speed); OV_BIND_METHOD(GameSingleton::can_decrease_speed); 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"))); } -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; -} - GameSingleton* GameSingleton::get_singleton() { return singleton; } @@ -139,8 +123,8 @@ Error GameSingleton::setup_game() { ret &= dataloader.load_pop_history(game_manager, "history/pops/" + game_manager.get_today().to_string()); for (Province& province : game_manager.get_map().get_provinces()) { province.set_crime( - game_manager.get_modifier_manager().get_crime_modifier_by_index( - (province.get_index() - 1) % game_manager.get_modifier_manager().get_crime_modifier_count() + game_manager.get_crime_manager().get_crime_modifier_by_index( + (province.get_index() - 1) % game_manager.get_crime_manager().get_crime_modifier_count() ) ); } @@ -185,13 +169,19 @@ static Array _distribution_to_pie_chart_array(fixed_point_map_t<T const*> const& 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_controller_key = "controller"; 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_crime_name_key = "crime_name"; + static const StringName province_info_crime_icon_key = "crime_icon"; 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_rgo_name_key = "rgo_name"; + static const StringName province_info_rgo_icon_key = "rgo_icon"; + static const StringName province_info_colony_status_key = "colony_status"; + static const StringName province_info_slave_status_key = "slave_status"; static const StringName province_info_buildings_key = "buildings"; Province const* province = game_manager.get_map().get_province_by_index(index); @@ -207,9 +197,15 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { ret[province_info_region_key] = std_view_to_godot_string(region->get_identifier()); } + Country const* controller = province->get_controller(); + if (controller != nullptr) { + ret[province_info_controller_key] = std_view_to_godot_string(controller->get_identifier()); + } + Good const* rgo = province->get_rgo(); if (rgo != nullptr) { - ret[province_info_rgo_key] = std_view_to_godot_string(rgo->get_identifier()); + ret[province_info_rgo_name_key] = std_view_to_godot_string(rgo->get_identifier()); + ret[province_info_rgo_icon_key] = static_cast<int32_t>(rgo->get_index()); } ret[province_info_life_rating_key] = province->get_life_rating(); @@ -219,6 +215,15 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { ret[province_info_terrain_type_key] = std_view_to_godot_string(terrain_type->get_identifier()); } + Crime const* crime = province->get_crime(); + if (crime != nullptr) { + ret[province_info_crime_name_key] = std_view_to_godot_string(crime->get_identifier()); + ret[province_info_crime_icon_key] = static_cast<int32_t>(crime->get_icon()); + } + + ret[province_info_colony_status_key] = static_cast<int32_t>(province->get_colony_status()); + ret[province_info_slave_status_key] = province->get_slave(); + 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()) { @@ -278,6 +283,27 @@ Ref<Texture> GameSingleton::get_terrain_texture() const { return terrain_texture; } +Ref<Image> GameSingleton::get_flag_image(Country const* country, StringName const& flag_type) const { + if (country != nullptr) { + const typename decltype(flag_image_map)::const_iterator it = flag_image_map.find(country); + if (it != flag_image_map.end()) { + const typename decltype(it->second)::const_iterator it2 = it->second.find(flag_type); + if (it2 != it->second.end()) { + return it2->second; + } else { + UtilityFunctions::push_error( + "Failed to find ", flag_type, " flag for country: ", std_view_to_godot_string(country->get_identifier()) + ); + } + } else { + UtilityFunctions::push_error( + "Failed to find flags for country: ", std_view_to_godot_string(country->get_identifier()) + ); + } + } + return nullptr; +} + Vector2i GameSingleton::get_province_shape_image_subdivisions() const { return image_subdivisions; } @@ -370,6 +396,41 @@ Error GameSingleton::expand_building(int32_t province_index, String const& build return OK; } +int32_t GameSingleton::get_slave_pop_icon_index() const { + const PopType::sprite_t sprite = game_manager.get_pop_manager().get_slave_sprite(); + if (sprite <= 0) { + UtilityFunctions::push_error("Slave sprite unset!"); + return 0; + } + return sprite; +} + +int32_t GameSingleton::get_administrative_pop_icon_index() const { + const PopType::sprite_t sprite = game_manager.get_pop_manager().get_administrative_sprite(); + if (sprite <= 0) { + UtilityFunctions::push_error("Administrative sprite unset!"); + return 0; + } + return sprite; +} + +int32_t GameSingleton::get_rgo_owner_pop_icon_index() const { + const PopType::sprite_t sprite = game_manager.get_economy_manager().get_production_type_manager().get_rgo_owner_sprite(); + if (sprite <= 0) { + UtilityFunctions::push_error("RGO owner sprite unset!"); + return 0; + } + return sprite; +} + +String GameSingleton::int_to_formatted_string(int64_t val) { + return Utilities::int_to_formatted_string(val); +} + +String GameSingleton::float_to_formatted_string(float val) { + return Utilities::float_to_formatted_string(val); +} + void GameSingleton::set_paused(bool paused) { game_manager.get_clock().is_paused = paused; } @@ -390,6 +451,10 @@ void GameSingleton::decrease_speed() { game_manager.get_clock().decrease_simulation_speed(); } +int32_t GameSingleton::get_speed() const { + return game_manager.get_clock().get_simulation_speed(); +} + bool GameSingleton::can_increase_speed() const { return game_manager.get_clock().can_increase_simulation_speed(); } @@ -527,6 +592,57 @@ Error GameSingleton::_load_terrain_variants() { return err; } +Error GameSingleton::_load_flag_images() { + if (!flag_image_map.empty()) { + UtilityFunctions::push_error("Flag images have already been loaded!"); + return FAILED; + } + + GovernmentTypeManager const& government_type_manager = game_manager.get_politics_manager().get_government_type_manager(); + if (!government_type_manager.government_types_are_locked()) { + UtilityFunctions::push_error("Cannot load flag images before government types are locked!"); + return FAILED; + } + CountryManager const& country_manager = game_manager.get_country_manager(); + if (!country_manager.countries_are_locked()) { + UtilityFunctions::push_error("Cannot load flag images before countries are locked!"); + return FAILED; + } + + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + + static const String flag_directory = "gfx/flags/"; + static const String flag_separator = "_"; + static const String flag_extension = ".tga"; + + std::vector<StringName> flag_types; + for (std::string const& type : government_type_manager.get_flag_types()) { + flag_types.emplace_back(std_to_godot_string_name(type)); + } + + Error ret = OK; + for (Country const& country : country_manager.get_countries()) { + std::map<StringName, Ref<Image>>& flag_images = flag_image_map[&country]; + const String country_name = std_view_to_godot_string(country.get_identifier()); + for (StringName const& flag_type : flag_types) { + String flag_path = flag_directory + country_name; + if (!flag_type.is_empty()) { + flag_path += flag_separator + flag_type; + } + flag_path += flag_extension; + const Ref<Image> flag_image = asset_manager->get_image(flag_path); + if (flag_image.is_valid()) { + flag_images.emplace(flag_type, flag_image); + } else { + UtilityFunctions::push_error("Failed to load flag image: ", flag_path); + ret = FAILED; + } + } + } + return ret; +} + Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& file_paths) { Dataloader::path_vector_t roots; for (String const& path : file_paths) { @@ -547,6 +663,10 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi UtilityFunctions::push_error("Failed to load terrain variants!"); err = FAILED; } + if (_load_flag_images() != OK) { + UtilityFunctions::push_error("Failed to load flag textures!"); + err = FAILED; + } if (_load_map_images(true) != OK) { UtilityFunctions::push_error("Failed to load map images!"); err = FAILED; diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index 108fd28..5895899 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -25,10 +25,12 @@ namespace OpenVic { godot::Ref<godot::ImageTexture> province_colour_texture; Mapmode::index_t mapmode_index = 0; godot::Ref<godot::Texture2DArray> terrain_texture; + std::map<Country const*, std::map<godot::StringName, godot::Ref<godot::Image>>> flag_image_map; godot::Error _generate_terrain_texture_array(); godot::Error _load_map_images(bool flip_vertical); godot::Error _load_terrain_variants(); + godot::Error _load_flag_images(); /* Generate the province_colour_texture from the current mapmode. */ godot::Error _update_colour_image(); @@ -38,9 +40,6 @@ namespace OpenVic { static void _bind_methods(); public: - - godot::Control* generate_gui(godot::String const& gui_file, godot::String const& gui_element); - static GameSingleton* get_singleton(); GameSingleton(); @@ -98,12 +97,18 @@ namespace OpenVic { void set_selected_province(int32_t index); godot::Error expand_building(int32_t province_index, godot::String const& building_type_identifier); + int32_t get_slave_pop_icon_index() const; + int32_t get_administrative_pop_icon_index() const; + int32_t get_rgo_owner_pop_icon_index() const; + static godot::String int_to_formatted_string(int64_t val); + static godot::String float_to_formatted_string(float val); void set_paused(bool paused); void toggle_paused(); bool is_paused() const; void increase_speed(); void decrease_speed(); + int32_t get_speed() const; bool can_increase_speed() const; bool can_decrease_speed() const; godot::String get_longform_date() const; diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index e3bcce6..099b5a9 100644 --- a/extension/src/openvic-extension/utility/Utilities.cpp +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -13,6 +13,37 @@ using namespace godot; using namespace OpenVic; +/* Int to 2 decimal place string in terms of the largest suffix less than or equal to it, + * or normal integer string if less than the smallest suffix. */ +String Utilities::int_to_formatted_string(int64_t val) { + static const std::vector<std::pair<int64_t, String>> suffixes { + { 1'000'000'000'000, "T" }, + { 1'000'000'000, "B" }, + { 1'000'000, "M" }, + { 1'000, "k" } + }; + static constexpr int64_t decimal_places_multiplier = 100; + const bool negative = val < 0; + if (negative) { + val = -val; + } + for (auto const& [suffix_val, suffix_str] : suffixes) { + if (val >= suffix_val) { + const int64_t whole = val / suffix_val; + const int64_t frac = (val * decimal_places_multiplier / suffix_val) % decimal_places_multiplier; + return (negative ? "-" : "") + String::num_int64(whole) + "." + + (frac < 10 ? "0" : "") + String::num_int64(frac) + suffix_str; + } + } + return (negative ? "-" : "") + String::num_int64(val); +} + +/* Float to formatted to 4 decimal place string. */ +String Utilities::float_to_formatted_string(float val) { + static constexpr int64_t decimal_places = 4; + return String::num(val, decimal_places).pad_decimals(decimal_places); +} + /* Date formatted like this: "January 1, 1836" (with the month localised, if possible). */ String Utilities::date_to_formatted_string(Date date) { std::string const& month_name = date.get_month_name(); diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index 9537dda..6eeb285 100644 --- a/extension/src/openvic-extension/utility/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -31,6 +31,10 @@ namespace OpenVic::Utilities { return std_to_godot_string_name(static_cast<std::string>(str)); } + godot::String int_to_formatted_string(int64_t val); + + godot::String float_to_formatted_string(float val); + godot::String date_to_formatted_string(Date date); inline godot::Color to_godot_color(colour_t colour) { |