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