aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
Diffstat (limited to 'extension')
m---------extension/deps/openvic-simulation0
-rw-r--r--extension/src/openvic-extension/UIAdapter.cpp171
-rw-r--r--extension/src/openvic-extension/UIAdapter.hpp27
-rw-r--r--extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp183
-rw-r--r--extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp55
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.cpp167
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.hpp49
-rw-r--r--extension/src/openvic-extension/classes/GUINode.cpp161
-rw-r--r--extension/src/openvic-extension/classes/GUINode.hpp53
-rw-r--r--extension/src/openvic-extension/register_types.cpp6
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.cpp174
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.hpp11
-rw-r--r--extension/src/openvic-extension/utility/Utilities.cpp31
-rw-r--r--extension/src/openvic-extension/utility/Utilities.hpp4
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) {