diff options
author | Hop311 <Hop3114@gmail.com> | 2023-12-11 12:08:55 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-11 12:08:55 +0100 |
commit | 0b3a162d3db840996c3d89e65dfe9270faebffe5 (patch) | |
tree | b22d464dbf8e0e2569b9be5aa130e4def2e51207 | |
parent | a6952efba078e49d6555b0586230986a2cb7ed40 (diff) | |
parent | fd375bdb35d8a7b2ac9cf3dd02cdb0f197451a0b (diff) |
Merge pull request #169 from OpenVicProject/guinode
Big UI commit - GUINode, MaskedFlag, PieChart, etc
23 files changed, 1240 insertions, 318 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) { diff --git a/game/src/Game/Autoload/Events.gd b/game/src/Game/Autoload/Events.gd index da12bf6..da29adb 100644 --- a/game/src/Game/Autoload/Events.gd +++ b/game/src/Game/Autoload/Events.gd @@ -3,9 +3,7 @@ ## It does such by providing a global interface of signals that are connected to and emitted by that are garunteed to exist. extends Node -var Loader: LoaderEventsObject var Options: OptionsEventsObject func _init(): - Loader = LoaderEventsObject.new() Options = OptionsEventsObject.new() diff --git a/game/src/Game/Autoload/Events/Loader.gd b/game/src/Game/Autoload/Events/Loader.gd deleted file mode 100644 index c17dc6f..0000000 --- a/game/src/Game/Autoload/Events/Loader.gd +++ /dev/null @@ -1,6 +0,0 @@ -class_name LoaderEventsObject -extends RefCounted - -signal startup_load_begun() -signal startup_load_changed(percentage : float) -signal startup_load_ended() diff --git a/game/src/Game/GameSession/GameSession.gd b/game/src/Game/GameSession/GameSession.gd index afff820..2a27e3d 100644 --- a/game/src/Game/GameSession/GameSession.gd +++ b/game/src/Game/GameSession/GameSession.gd @@ -7,12 +7,6 @@ func _ready(): if GameSingleton.setup_game() != OK: push_error("Failed to setup game") - # Temporarily here for cosmetic reasons, will be moved to its - # own child node later, similar to ProvinceOverviewPanel - add_child(GameSingleton.generate_gui("topbar.gui", "topbar")) - $topbar/topbar_outlinerbutton_bg.visible = false - $topbar/topbar_outlinerbutton.visible = false - func _process(_delta : float): GameSingleton.try_tick() diff --git a/game/src/Game/GameSession/GameSession.tscn b/game/src/Game/GameSession/GameSession.tscn index 755139e..2dd0e16 100644 --- a/game/src/Game/GameSession/GameSession.tscn +++ b/game/src/Game/GameSession/GameSession.tscn @@ -4,10 +4,10 @@ [ext_resource type="PackedScene" uid="uid://cvl76duuym1wq" path="res://src/Game/MusicConductor/MusicPlayer.tscn" id="2_kt6aa"] [ext_resource type="PackedScene" uid="uid://g524p8lr574w" path="res://src/Game/GameSession/MapControlPanel/MapControlPanel.tscn" id="3_afh6d"] [ext_resource type="PackedScene" uid="uid://dvdynl6eir40o" path="res://src/Game/GameSession/GameSessionMenu.tscn" id="3_bvmqh"] +[ext_resource type="Script" path="res://src/Game/GameSession/Topbar.gd" id="4_2kbih"] [ext_resource type="PackedScene" uid="uid://dkehmdnuxih2r" path="res://src/Game/GameSession/MapView.tscn" id="4_xkg5j"] [ext_resource type="Script" path="res://src/Game/GameSession/ProvinceOverviewPanel.gd" id="5_lfv8l"] [ext_resource type="PackedScene" uid="uid://cnbfxjy1m6wja" path="res://src/Game/Menu/OptionMenu/OptionsMenu.tscn" id="6_p5mnx"] -[ext_resource type="PackedScene" uid="uid://dd8k3p7r3huwc" path="res://src/Game/GameSession/GameSpeedPanel.tscn" id="7_myy4q"] [ext_resource type="PackedScene" uid="uid://d3g6wbvwflmyk" path="res://src/Game/Menu/SaveLoadMenu/SaveLoadMenu.tscn" id="8_4g7ko"] [node name="GameSession" type="Control" node_paths=PackedStringArray("_game_session_menu")] @@ -24,7 +24,7 @@ _game_session_menu = NodePath("GameSessionMenu") [node name="MapView" parent="." instance=ExtResource("4_xkg5j")] -[node name="ProvinceOverviewPanel" type="Control" parent="."] +[node name="ProvinceOverviewPanel" type="GUINode" parent="."] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -34,6 +34,12 @@ grow_vertical = 2 mouse_filter = 1 script = ExtResource("5_lfv8l") +[node name="Topbar" type="GUINode" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("4_2kbih") + [node name="MapControlPanel" parent="." instance=ExtResource("3_afh6d")] layout_mode = 1 anchors_preset = 3 @@ -44,11 +50,6 @@ anchor_bottom = 1.0 grow_horizontal = 0 grow_vertical = 0 -[node name="GameSpeedPanel" parent="." instance=ExtResource("7_myy4q")] -layout_mode = 0 -offset_right = 302.0 -offset_bottom = 31.0 - [node name="GameSessionMenu" parent="." instance=ExtResource("3_bvmqh")] visible = false layout_mode = 1 diff --git a/game/src/Game/GameSession/GameSpeedPanel.gd b/game/src/Game/GameSession/GameSpeedPanel.gd deleted file mode 100644 index 6a4b4de..0000000 --- a/game/src/Game/GameSession/GameSpeedPanel.gd +++ /dev/null @@ -1,43 +0,0 @@ -extends PanelContainer - -# REQUIREMENTS: -# * SS-37, SS-38, SS-39 - -@export var _longform_date_button : Button -@export var _play_pause_display_button : Button -@export var _decrease_speed_button : Button -@export var _increase_speed_button : Button - -func _ready(): - GameSingleton.state_updated.connect(_update_buttons) - _update_buttons() - -func _update_buttons(): - _play_pause_display_button.text = "⏸ " if GameSingleton.is_paused() else "▶" - - _increase_speed_button.disabled = not GameSingleton.can_increase_speed() - _decrease_speed_button.disabled = not GameSingleton.can_decrease_speed() - - _longform_date_button.text = GameSingleton.get_longform_date() - -# REQUIREMENTS: -# * UIFUN-73 -func _on_decrease_speed_button_pressed(): - GameSingleton.decrease_speed() - _update_buttons() - -# REQUIREMENTS: -# * UIFUN-72 -func _on_increase_speed_button_pressed(): - GameSingleton.increase_speed() - _update_buttons() - -# REQUIREMENTS: -# * UIFUN-71 -func _on_play_pause_display_button_pressed(): - GameSingleton.toggle_paused() - _update_buttons() - -func _on_longform_date_label_pressed(): - GameSingleton.toggle_paused() - _update_buttons() diff --git a/game/src/Game/GameSession/GameSpeedPanel.tscn b/game/src/Game/GameSession/GameSpeedPanel.tscn deleted file mode 100644 index 5526427..0000000 --- a/game/src/Game/GameSession/GameSpeedPanel.tscn +++ /dev/null @@ -1,67 +0,0 @@ -[gd_scene load_steps=8 format=3 uid="uid://dd8k3p7r3huwc"] - -[ext_resource type="Script" path="res://src/Game/GameSession/GameSpeedPanel.gd" id="1_pfs8t"] - -[sub_resource type="InputEventAction" id="InputEventAction_3k1tl"] -action = &"time_pause" - -[sub_resource type="Shortcut" id="Shortcut_cg5xm"] -events = [SubResource("InputEventAction_3k1tl")] - -[sub_resource type="InputEventAction" id="InputEventAction_w2rkb"] -action = &"time_speed_decrease" - -[sub_resource type="Shortcut" id="Shortcut_ocrfe"] -events = [SubResource("InputEventAction_w2rkb")] - -[sub_resource type="InputEventAction" id="InputEventAction_7sdhp"] -action = &"time_speed_increase" - -[sub_resource type="Shortcut" id="Shortcut_gwofc"] -events = [SubResource("InputEventAction_7sdhp")] - -[node name="GameSpeedPanel" type="PanelContainer" node_paths=PackedStringArray("_longform_date_button", "_play_pause_display_button", "_decrease_speed_button", "_increase_speed_button")] -script = ExtResource("1_pfs8t") -_longform_date_button = NodePath("ButtonList/LongformDateButton") -_play_pause_display_button = NodePath("ButtonList/PlayPauseDisplayButton") -_decrease_speed_button = NodePath("ButtonList/DecreaseSpeedButton") -_increase_speed_button = NodePath("ButtonList/IncreaseSpeedButton") - -[node name="ButtonList" type="HBoxContainer" parent="."] -layout_mode = 2 - -[node name="LongformDateButton" type="Button" parent="ButtonList"] -editor_description = "UI-74" -custom_minimum_size = Vector2(200, 0) -layout_mode = 2 -focus_mode = 0 -text = "LONGFORM DATE" - -[node name="PlayPauseDisplayButton" type="Button" parent="ButtonList"] -editor_description = "UI-75, UIFUN-90" -custom_minimum_size = Vector2(30, 0) -layout_mode = 2 -focus_mode = 0 -shortcut = SubResource("Shortcut_cg5xm") -text = "⏸ " - -[node name="DecreaseSpeedButton" type="Button" parent="ButtonList"] -editor_description = "UI-77" -custom_minimum_size = Vector2(30, 0) -layout_mode = 2 -focus_mode = 0 -shortcut = SubResource("Shortcut_ocrfe") -text = "-" - -[node name="IncreaseSpeedButton" type="Button" parent="ButtonList"] -editor_description = "UI-76" -custom_minimum_size = Vector2(30, 0) -layout_mode = 2 -focus_mode = 0 -shortcut = SubResource("Shortcut_gwofc") -text = "+" - -[connection signal="pressed" from="ButtonList/LongformDateButton" to="." method="_on_longform_date_label_pressed"] -[connection signal="pressed" from="ButtonList/PlayPauseDisplayButton" to="." method="_on_play_pause_display_button_pressed"] -[connection signal="pressed" from="ButtonList/DecreaseSpeedButton" to="." method="_on_decrease_speed_button_pressed"] -[connection signal="pressed" from="ButtonList/IncreaseSpeedButton" to="." method="_on_increase_speed_button_pressed"] diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel.gd index 1292eb4..6c183fb 100644 --- a/game/src/Game/GameSession/ProvinceOverviewPanel.gd +++ b/game/src/Game/GameSession/ProvinceOverviewPanel.gd @@ -1,22 +1,54 @@ -extends Control +extends GUINode +# Header var _province_name_label : Label var _region_name_label : Label +var _slave_status_icon : TextureRect +var _colony_status_button : Button +var _colony_status_button_texture : GFXIconTexture +var _administrative_percentage_label : Label +var _owner_percentage_label : Label +var _terrain_type_texture : GFXIconTexture var _life_rating_bar : TextureProgressBar +var _controller_flag_texture : GFXMaskedFlagTexture +# state and province modifiers + +# Statistics +var _rgo_icon_texture : GFXIconTexture +var _rgo_produced_label : Label +var _rgo_income_label : Label +# rgo employment progress bar (execpt it isn't a progress bar?) +var _rgo_employment_population_label : Label +var _rgo_employment_percentage_label : Label +var _crime_name_label : Label +var _crime_icon_texture : GFXIconTexture +# crime fighting var _total_population_label : Label -var _rgo_icon_texture : AtlasTexture +var _migration_label : Label +var _population_growth_label : Label +var _pop_types_piechart : GFXPieChartTexture +var _pop_ideologies_piechart : GFXPieChartTexture +var _pop_cultures_piechart : GFXPieChartTexture +# supply_limit_label +# cores const _missing_suffix : String = "_MISSING" const _province_info_province_key : StringName = &"province" const _province_info_region_key : StringName = &"region" +const _province_info_controller_key : StringName = &"controller" const _province_info_life_rating_key : StringName = &"life_rating" const _province_info_terrain_type_key : StringName = &"terrain_type" +const _province_info_crime_name_key : StringName = &"crime_name" +const _province_info_crime_icon_key : StringName = &"crime_icon" const _province_info_total_population_key : StringName = &"total_population" const _province_info_pop_types_key : StringName = &"pop_types" const _province_info_pop_ideologies_key : StringName = &"pop_ideologies" const _province_info_pop_cultures_key : StringName = &"pop_cultures" -const _province_info_rgo_key : StringName = &"rgo" +const _province_info_rgo_name_key : StringName = &"rgo_name" +const _province_info_rgo_icon_key : StringName = &"rgo_icon" +const _province_info_colony_status_key : StringName = &"colony_status" +const _province_info_slave_status_key : StringName = &"slave_status" const _province_info_buildings_key : StringName = &"buildings" const _building_info_building_key : StringName = &"building" @@ -36,67 +68,79 @@ var _selected_index : int: _update_info() var _province_info : Dictionary -func _check_class(object : Object, klass : String, name : String) -> bool: - if object.get_class() == klass: - return true - else: - push_error("Invalid ", name, " class: ", object.get_class(), " (expected ", klass, ")") - return false - -func _try_get_node(path : NodePath, klass : String) -> Node: - var node : Node = get_node(path) - if node != null: - if _check_class(node, klass, path): - return node - else: - push_error("Failed to get node: ", path, " (returned null)") - return null - func _ready(): GameSingleton.province_selected.connect(_on_province_selected) GameSingleton.state_updated.connect(_update_info) - add_child(GameSingleton.generate_gui("province_interface.gui", "province_view")) + add_gui_element("province_interface.gui", "province_view") - var close_button : Button = _try_get_node(^"./province_view/close_button", "Button") - if close_button != null: + var close_button : Button = get_button_node(^"./province_view/close_button") + if close_button: close_button.pressed.connect(_on_close_button_pressed) - _region_name_label = _try_get_node(^"./province_view/province_view_header/state_name", "Label") - - _province_name_label = _try_get_node(^"./province_view/province_view_header/province_name", "Label") - - _life_rating_bar = _try_get_node(^"./province_view/province_view_header/liferating", "TextureProgressBar") - - var goods_icon : TextureRect = _try_get_node(^"./province_view/province_statistics/goods_type", "TextureRect") - if goods_icon != null: - var texture := goods_icon.texture - if _check_class(texture, "GFXIconTexture", "good_texture"): - _rgo_icon_texture = texture - - - var rgo_population_label : Label = _try_get_node(^"./province_view/province_statistics/rgo_population", "Label") - if rgo_population_label != null: - rgo_population_label.text = "0" - - #^"./province_view/province_statistics/crime_icon" - - _total_population_label = _try_get_node(^"./province_view/province_statistics/total_population", "Label") - - #^"./province_view/province_statistics/growth" - #^"./province_view/province_statistics/workforce_chart" - #^"./province_view/province_statistics/ideology_chart" - #^"./province_view/province_statistics/culture_chart" - - $province_view/province_view_header/occupation_progress.visible = false - $province_view/province_view_header/occupation_icon.visible = false - $province_view/province_view_header/occupation_flag.visible = false - $province_view/province_colony.visible = false - $province_view/province_other.visible = false - $province_view/province_buildings/army_size.visible = false - $province_view/province_buildings/army_text.visible = false - $province_view/province_buildings/navy_text.visible = false - $province_view/national_focus_window.visible = false + # Header + _province_name_label = get_label_node(^"./province_view/province_view_header/province_name") + _region_name_label = get_label_node(^"./province_view/province_view_header/state_name") + _slave_status_icon = get_texture_rect_node(^"./province_view/province_view_header/slave_state_icon") + var slave_status_icon_texture : GFXIconTexture = get_gfx_icon_texture_from_node(^"./province_view/province_view_header/slave_state_icon") + if slave_status_icon_texture: + slave_status_icon_texture.set_icon_index(GameSingleton.get_slave_pop_icon_index()) + _colony_status_button = get_button_node(^"./province_view/province_view_header/colony_button") + _colony_status_button_texture = get_gfx_icon_texture_from_node(^"./province_view/province_view_header/colony_button") + var admin_icon_texture : GFXIconTexture = get_gfx_icon_texture_from_node(^"./province_view/province_view_header/admin_icon") + if admin_icon_texture: + admin_icon_texture.set_icon_index(GameSingleton.get_administrative_pop_icon_index()) + _administrative_percentage_label = get_label_node(^"./province_view/province_view_header/admin_efficiency") + _owner_percentage_label = get_label_node(^"./province_view/province_view_header/owner_presence") + _terrain_type_texture = get_gfx_icon_texture_from_node(^"./province_view/province_view_header/prov_terrain") + _life_rating_bar = get_progress_bar_node(^"./province_view/province_view_header/liferating") + _controller_flag_texture = get_gfx_masked_flag_texture_from_node(^"./province_view/province_view_header/controller_flag") + + # Statistics + _rgo_icon_texture = get_gfx_icon_texture_from_node(^"./province_view/province_statistics/goods_type") + _rgo_produced_label = get_label_node(^"./province_view/province_statistics/produced") + _rgo_income_label = get_label_node(^"./province_view/province_statistics/income") + _rgo_employment_population_label = get_label_node(^"./province_view/province_statistics/rgo_population") + _rgo_employment_percentage_label = get_label_node(^"./province_view/province_statistics/rgo_percent") + _crime_name_label = get_label_node(^"./province_view/province_statistics/crime_name") + _crime_icon_texture = get_gfx_icon_texture_from_node(^"./province_view/province_statistics/crime_icon") + _total_population_label = get_label_node(^"./province_view/province_statistics/total_population") + _migration_label = get_label_node(^"./province_view/province_statistics/migration") + _population_growth_label = get_label_node(^"./province_view/province_statistics/growth") + _pop_types_piechart = get_gfx_pie_chart_texture_from_node(^"./province_view/province_statistics/workforce_chart") + _pop_ideologies_piechart = get_gfx_pie_chart_texture_from_node(^"./province_view/province_statistics/ideology_chart") + _pop_cultures_piechart = get_gfx_pie_chart_texture_from_node(^"./province_view/province_statistics/culture_chart") + + #^"./province_view/building" + #^"./province_view/province_core" + #^"./province_view/prov_state_modifier" + + #add_gui_element("province_interface.gui", "building", "building0") + #var building0 : Panel = get_panel_node(^"./building0") + #building0.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT) + #building0.set_position(pop_cultures_piechart_icon.get_position()) + + # TODO - fix checkbox positions + for path in [ + ^"./province_view/province_buildings/rallypoint_checkbox", + ^"./province_view/province_buildings/rallypoint_merge_checkbox", + ^"./province_view/province_buildings/rallypoint_checkbox_naval", + ^"./province_view/province_buildings/rallypoint_merge_checkbox_naval" + ]: + var rally_checkbox : CheckBox = get_check_box_node(path) + rally_checkbox.set_position(rally_checkbox.get_position() - Vector2(3, 3)) + + hide_nodes([ + ^"./province_view/province_view_header/occupation_progress", + ^"./province_view/province_view_header/occupation_icon", + ^"./province_view/province_view_header/occupation_flag", + ^"./province_view/province_colony", + ^"./province_view/province_other", + ^"./province_view/province_buildings/army_size", + ^"./province_view/province_buildings/army_text", + ^"./province_view/province_buildings/navy_text", + ^"./province_view/national_focus_window", + ]) _update_info() @@ -177,27 +221,92 @@ func _set_building_row(index : int, building : Dictionary) -> void: row.button.disabled = expansion_state != CAN_EXPAND row.button.visible = not show_progress_bar """ + +enum { STATE, PROTECTORATE, COLONY } func _update_info() -> void: _province_info = GameSingleton.get_province_info_from_index(_selected_index) if _province_info: + # Header if _province_name_label: _province_name_label.text = "PROV" + _province_info.get(_province_info_province_key, _province_info_province_key + _missing_suffix) + if _region_name_label: _region_name_label.text = _province_info.get(_province_info_region_key, _province_info_region_key + _missing_suffix) + + if _slave_status_icon: + _slave_status_icon.visible = _province_info.get(_province_info_slave_status_key, false) + + var colony_status : int = _province_info.get(_province_info_colony_status_key, 0) + if _colony_status_button: + if colony_status == STATE: + _colony_status_button.hide() + else: + if _colony_status_button_texture: + _colony_status_button_texture.set_icon_index(colony_status) + _colony_status_button.show() + + if _administrative_percentage_label: + pass + + if _owner_percentage_label: + pass + + if _terrain_type_texture: + var terrain_type : String = _province_info.get(_province_info_terrain_type_key, "") + if terrain_type: + const _terrain_type_prefix : String = "GFX_terrainimg_" + if _terrain_type_texture.set_gfx_texture_sprite_name(_terrain_type_prefix + terrain_type) != OK: + push_error("Failed to set terrain type texture: ", terrain_type) + if _life_rating_bar: - _life_rating_bar.value = _province_info.get(_province_info_life_rating_key, 0) * 0 + _life_rating_bar.value = _province_info.get(_province_info_life_rating_key, 0) + + if _controller_flag_texture: + var controller : String = _province_info.get(_province_info_controller_key, "REB") + _controller_flag_texture.set_flag_country_name_and_type(controller, &"") + + # Statistics + if _rgo_icon_texture: + _rgo_icon_texture.set_icon_index(_province_info.get(_province_info_rgo_icon_key, -1) + 2) + + if _rgo_produced_label: + _rgo_produced_label.text = _province_info.get(_province_info_rgo_name_key, _province_info_rgo_name_key + _missing_suffix) + + if _rgo_income_label: + _rgo_income_label.text = GameSingleton.float_to_formatted_string(12.34567) + # TODO - add £ sign + + if _rgo_employment_population_label: + _rgo_employment_population_label.text = GameSingleton.int_to_formatted_string(_province_info.get(_province_info_total_population_key, 0) / 10) + + if _rgo_employment_percentage_label: + pass + + if _crime_name_label: + _crime_name_label.text = _province_info.get(_province_info_crime_name_key, "") + + if _crime_icon_texture: + _crime_icon_texture.set_icon_index(_province_info.get(_province_info_crime_icon_key, 0) + 1) if _total_population_label: - _total_population_label.text = Localisation.tr_number(_province_info.get(_province_info_total_population_key, 0)) + _total_population_label.text = GameSingleton.int_to_formatted_string(_province_info.get(_province_info_total_population_key, 0)) - #_pop_type_chart.set_to_distribution(_province_info.get(_province_info_pop_types_key, {})) - #_pop_ideology_chart.set_to_distribution(_province_info.get(_province_info_pop_ideologies_key, {})) - #_pop_culture_chart.set_to_distribution(_province_info.get(_province_info_pop_cultures_key, {})) + if _migration_label: + pass - if _rgo_icon_texture: - _rgo_icon_texture.set_icon_index((_selected_index % 40) + 1) + if _population_growth_label: + pass + + if _pop_types_piechart: + _pop_types_piechart.set_slices(_province_info.get(_province_info_pop_types_key, [])) + + if _pop_ideologies_piechart: + _pop_ideologies_piechart.set_slices(_province_info.get(_province_info_pop_ideologies_key, [])) + + if _pop_cultures_piechart: + _pop_cultures_piechart.set_slices(_province_info.get(_province_info_pop_cultures_key, [])) #var buildings : Array = _province_info.get(_province_info_buildings_key, []) #for i in max(buildings.size(), _building_rows.size()): diff --git a/game/src/Game/GameSession/Topbar.gd b/game/src/Game/GameSession/Topbar.gd new file mode 100644 index 0000000..6cc710f --- /dev/null +++ b/game/src/Game/GameSession/Topbar.gd @@ -0,0 +1,84 @@ +extends GUINode + +var _speed_up_button : Button +var _speed_down_button : Button +var _speed_indicator_button : Button +var _speed_indicator_texture : GFXIconTexture +var _date_label : Label +var _country_name_label : Label + +func _ready(): + GameSingleton.state_updated.connect(_update_info) + + add_gui_element("topbar.gui", "topbar") + + hide_nodes([ + ^"./topbar/topbar_outlinerbutton_bg", + ^"./topbar/topbar_outlinerbutton" + ]) + + const player_country : String = "SLV" + + var player_flag_texture : GFXMaskedFlagTexture = get_gfx_masked_flag_texture_from_node(^"./topbar/player_flag") + if player_flag_texture: + player_flag_texture.set_flag_country_name_and_type(player_country, &"") + + _speed_up_button = get_button_node(^"./topbar/button_speedup") + if _speed_up_button: + _speed_up_button.pressed.connect(_on_increase_speed_button_pressed) + + _speed_down_button = get_button_node(^"./topbar/button_speeddown") + if _speed_down_button: + _speed_down_button.pressed.connect(_on_decrease_speed_button_pressed) + + var pause_bg_button : Button = get_button_node(^"./topbar/pause_bg") + if pause_bg_button: + pause_bg_button.pressed.connect(_on_play_pause_button_pressed) + + _date_label = get_label_node(^"./topbar/DateText") + + _country_name_label = get_label_node(^"./topbar/CountryName") + if _country_name_label: + _country_name_label.text = player_country + + _speed_indicator_button = get_button_node(^"./topbar/speed_indicator") + _speed_indicator_texture = get_gfx_icon_texture_from_node(^"./topbar/speed_indicator") + +func _update_info() -> void: + if _date_label: + _date_label.text = GameSingleton.get_longform_date() + + # TODO - add disabled state textures so this doesn't hide the buttons + #if _speed_up_button: + # _speed_up_button.disabled = not GameSingleton.can_increase_speed() + + #if _speed_down_button: + # _speed_down_button.disabled = not GameSingleton.can_decrease_speed() + + if _speed_indicator_button and _speed_indicator_texture: + var index : int = 1 + if not GameSingleton.is_paused(): + index += GameSingleton.get_speed() + 1 + _speed_indicator_texture.set_icon_index(index) + _speed_indicator_button.queue_redraw() + +# REQUIREMENTS: +# * UIFUN-71 +func _on_play_pause_button_pressed(): + print("Toggling pause!") + GameSingleton.toggle_paused() + _update_info() + +# REQUIREMENTS: +# * UIFUN-72 +func _on_increase_speed_button_pressed(): + print("Speed up!") + GameSingleton.increase_speed() + _update_info() + +# REQUIREMENTS: +# * UIFUN-73 +func _on_decrease_speed_button_pressed(): + print("Speed down!") + GameSingleton.decrease_speed() + _update_info() diff --git a/game/src/Game/LoadingScreen.gd b/game/src/Game/LoadingScreen.gd index c7dad9c..08cd2a8 100644 --- a/game/src/Game/LoadingScreen.gd +++ b/game/src/Game/LoadingScreen.gd @@ -21,7 +21,6 @@ func start_loading_screen(thread_safe_function : Callable) -> void: thread.wait_to_finish() thread.start(thread_safe_function) - Events.Loader.startup_load_begun.emit() func try_update_loading_screen(percent_complete: float, quote_should_change = false): # forces the function to behave as if deferred @@ -29,11 +28,6 @@ func try_update_loading_screen(percent_complete: float, quote_should_change = fa progress_bar.value = percent_complete if quote_should_change: quote_label.text = quotes[randi() % quotes.size()] - if is_equal_approx(percent_complete, 100): - thread.wait_to_finish() - Events.Loader.startup_load_ended.emit() - else: - Events.Loader.startup_load_changed.emit(percent_complete) func _ready(): if Engine.is_editor_hint(): return |