aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
m---------extension/deps/openvic-simulation0
-rw-r--r--extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp14
-rw-r--r--extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp3
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.hpp8
-rw-r--r--extension/src/openvic-extension/classes/GFXSpriteTexture.hpp2
-rw-r--r--extension/src/openvic-extension/classes/MapMesh.cpp22
-rw-r--r--extension/src/openvic-extension/singletons/AssetManager.cpp130
-rw-r--r--extension/src/openvic-extension/singletons/AssetManager.hpp45
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.cpp236
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.hpp24
-rw-r--r--extension/src/openvic-extension/singletons/LoadLocalisation.cpp2
-rw-r--r--extension/src/openvic-extension/singletons/MenuSingleton.cpp113
-rw-r--r--extension/src/openvic-extension/singletons/MenuSingleton.hpp96
-rw-r--r--extension/src/openvic-extension/singletons/PopulationMenu.cpp729
-rw-r--r--extension/src/openvic-extension/utility/UITools.cpp24
-rw-r--r--extension/src/openvic-extension/utility/UITools.hpp2
-rw-r--r--extension/src/openvic-extension/utility/Utilities.cpp3
-rw-r--r--extension/src/openvic-extension/utility/Utilities.hpp8
-rw-r--r--game/project.godot4
-rw-r--r--game/src/Game/GameSession/MapText.gd50
-rw-r--r--game/src/Game/GameSession/MapView.gd64
-rw-r--r--game/src/Game/GameSession/MapView.tscn12
-rw-r--r--game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd574
-rw-r--r--game/src/Game/GameSession/ProvinceOverviewPanel.gd9
-rw-r--r--game/src/Game/GameSession/TerrainMap.gdshader119
26 files changed, 2090 insertions, 206 deletions
diff --git a/README.md b/README.md
index dc04ac8..fe90e61 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,9 @@ For detailed instructions, view the Contributor Quickstart Guide [here](docs/con
* [Godot 4.2.1](https://github.com/godotengine/godot/releases/tag/4.2.1-stable)
* [scons](https://scons.org/)
+> [!WARNING]
+> If you are using Arch Linux, do not use the Arch repo package, it is known to break under some GDExtensions, use the official release file.
+
See [System Requirements](docs/contribution/system-requirements.md).
## Repo Setup
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation
-Subproject c11d262a4d2c987c8cf8e0d4b24929cbe56bb28
+Subproject e286cfef29d7c431ba33cd77283e838e6fba05d
diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp
index 2c315a9..51a4235 100644
--- a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp
+++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp
@@ -34,7 +34,7 @@ Error GFXMaskedFlagTexture::_generate_combined_image() {
set_region({ {}, button_image->get_size() });
}
- if (mask_image.is_valid() && flag_image.is_valid()) {
+ if (mask_image.is_valid() && flag_image.is_valid() && flag_image_rect.has_area()) {
const Vector2i centre_translation = (mask_image->get_size() - button_image->get_size()) / 2;
for (Vector2i combined_image_point { 0, 0 }; combined_image_point.y < button_image->get_height(); ++combined_image_point.y) {
for (combined_image_point.x = 0; combined_image_point.x < button_image->get_width(); ++combined_image_point.x) {
@@ -46,10 +46,14 @@ Error GFXMaskedFlagTexture::_generate_combined_image() {
0 <= mask_image_point.y && mask_image_point.y < mask_image->get_height()
) {
const Color mask_image_colour = mask_image->get_pixelv(mask_image_point);
+
// Rescale from mask_image to flag_image coordinates.
- const Vector2i flag_image_point = mask_image_point * flag_image->get_size() / mask_image->get_size();
+ const Vector2i flag_image_point =
+ flag_image_rect.position + mask_image_point * flag_image_rect.size / mask_image->get_size();
+
Color flag_image_colour = flag_image->get_pixelv(flag_image_point);
flag_image_colour.a = mask_image_colour.a;
+
button_image->set_pixelv(combined_image_point, flag_image_colour.blend(overlay_image_colour));
} else {
button_image->set_pixelv(combined_image_point, overlay_image_colour);
@@ -157,12 +161,12 @@ Error GFXMaskedFlagTexture::set_flag_country_and_type(Country const* new_flag_co
GameSingleton* game_singleton = GameSingleton::get_singleton();
ERR_FAIL_NULL_V(game_singleton, FAILED);
- const Ref<Image> new_flag_image = game_singleton->get_flag_image(new_flag_country, new_flag_type);
- ERR_FAIL_NULL_V(new_flag_image, FAILED);
+ flag_image_rect = game_singleton->get_flag_sheet_rect(new_flag_country->get_index(), new_flag_type);
+ ERR_FAIL_COND_V(!flag_image_rect.has_area(), FAILED);
flag_country = new_flag_country;
flag_type = new_flag_type;
- flag_image = new_flag_image;
+ flag_image = game_singleton->get_flag_sheet_image();
} else {
// TODO - use REB flag as default/error flag
flag_country = nullptr;
diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp
index 1e85dd8..9290d5c 100644
--- a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp
+++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp
@@ -1,7 +1,7 @@
#pragma once
#include <openvic-simulation/country/Country.hpp>
-#include <openvic-simulation/interface/GFX.hpp>
+#include <openvic-simulation/interface/GFXSprite.hpp>
#include "openvic-extension/classes/GFXButtonStateTexture.hpp"
@@ -14,6 +14,7 @@ namespace OpenVic {
godot::StringName PROPERTY(flag_type);
godot::Ref<godot::Image> overlay_image, mask_image, flag_image;
+ godot::Rect2i flag_image_rect;
godot::Ref<godot::ImageTexture> combined_texture;
godot::Error _generate_combined_image();
diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
index abeca1e..8683b10 100644
--- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
+++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
@@ -2,7 +2,7 @@
#include <godot_cpp/classes/image_texture.hpp>
-#include <openvic-simulation/interface/GFX.hpp>
+#include <openvic-simulation/interface/GFXSprite.hpp>
#include "openvic-extension/utility/Utilities.hpp"
@@ -54,12 +54,14 @@ namespace OpenVic {
return lhs.first < rhs.first;
});
godot_pie_chart_data_t array;
- for (auto const& [key, val] : sorted_dist) {
+ ERR_FAIL_COND_V(array.resize(sorted_dist.size()) != OK, {});
+ for (size_t idx = 0; idx < array.size(); ++idx) {
+ auto const& [key, val] = sorted_dist[idx];
Dictionary sub_dict;
sub_dict[_slice_identifier_key()] = Utilities::std_view_to_godot_string(key->get_identifier());
sub_dict[_slice_colour_key()] = Utilities::to_godot_color(key->get_colour());
sub_dict[_slice_weight_key()] = val.to_float();
- array.push_back(sub_dict);
+ array[idx] = std::move(sub_dict);
}
return array;
}
diff --git a/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp b/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp
index 34ec405..4e93e62 100644
--- a/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp
+++ b/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp
@@ -2,7 +2,7 @@
#include <godot_cpp/classes/atlas_texture.hpp>
-#include <openvic-simulation/interface/GFX.hpp>
+#include <openvic-simulation/interface/GFXSprite.hpp>
#include "openvic-extension/classes/GFXButtonStateTexture.hpp"
diff --git a/extension/src/openvic-extension/classes/MapMesh.cpp b/extension/src/openvic-extension/classes/MapMesh.cpp
index a557105..8db3b84 100644
--- a/extension/src/openvic-extension/classes/MapMesh.cpp
+++ b/extension/src/openvic-extension/classes/MapMesh.cpp
@@ -92,7 +92,7 @@ bool MapMesh::is_valid_uv_coord(godot::Vector2 const& uv) const {
Array MapMesh::_create_mesh_array() const {
Array arr;
- arr.resize(Mesh::ARRAY_MAX);
+ ERR_FAIL_COND_V(arr.resize(Mesh::ARRAY_MAX) != OK, {});
const int32_t vertex_count = (subdivide_w + 2) * (subdivide_d + 2);
const int32_t indice_count = (subdivide_w + 1) * (subdivide_d + 1) * 6;
@@ -103,11 +103,11 @@ Array MapMesh::_create_mesh_array() const {
PackedVector2Array uvs;
PackedInt32Array indices;
- points.resize(vertex_count);
- normals.resize(vertex_count);
- tangents.resize(vertex_count * 4);
- uvs.resize(vertex_count);
- indices.resize(indice_count);
+ ERR_FAIL_COND_V(points.resize(vertex_count) != OK, {});
+ ERR_FAIL_COND_V(normals.resize(vertex_count) != OK, {});
+ ERR_FAIL_COND_V(tangents.resize(vertex_count * 4) != OK, {});
+ ERR_FAIL_COND_V(uvs.resize(vertex_count) != OK, {});
+ ERR_FAIL_COND_V(indices.resize(indice_count) != OK, {});
static const Vector3 normal { 0.0f, 1.0f, 0.0f };
const Size2 uv_size { 1.0f + 2.0f * repeat_proportion, 1.0f };
@@ -153,11 +153,11 @@ Array MapMesh::_create_mesh_array() const {
thisrow = point_index;
}
- arr[Mesh::ARRAY_VERTEX] = points;
- arr[Mesh::ARRAY_NORMAL] = normals;
- arr[Mesh::ARRAY_TANGENT] = tangents;
- arr[Mesh::ARRAY_TEX_UV] = uvs;
- arr[Mesh::ARRAY_INDEX] = indices;
+ arr[Mesh::ARRAY_VERTEX] = std::move(points);
+ arr[Mesh::ARRAY_NORMAL] = std::move(normals);
+ arr[Mesh::ARRAY_TANGENT] = std::move(tangents);
+ arr[Mesh::ARRAY_TEX_UV] = std::move(uvs);
+ arr[Mesh::ARRAY_INDEX] = std::move(indices);
return arr;
}
diff --git a/extension/src/openvic-extension/singletons/AssetManager.cpp b/extension/src/openvic-extension/singletons/AssetManager.cpp
index 083d934..6646c8b 100644
--- a/extension/src/openvic-extension/singletons/AssetManager.cpp
+++ b/extension/src/openvic-extension/singletons/AssetManager.cpp
@@ -13,9 +13,14 @@ 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", "cache", "flip_y" }, DEFVAL(true), DEFVAL(false));
- OV_BIND_METHOD(AssetManager::get_texture, { "path", "flip_y" }, DEFVAL(false));
+ OV_BIND_METHOD(AssetManager::get_image, { "path", "load_flags" }, DEFVAL(LOAD_FLAG_CACHE_IMAGE));
+ OV_BIND_METHOD(AssetManager::get_texture, { "path", "load_flags" }, DEFVAL(LOAD_FLAG_CACHE_TEXTURE));
OV_BIND_METHOD(AssetManager::get_font, { "name" });
+
+ BIND_ENUM_CONSTANT(LOAD_FLAG_NONE);
+ BIND_ENUM_CONSTANT(LOAD_FLAG_CACHE_IMAGE);
+ BIND_ENUM_CONSTANT(LOAD_FLAG_CACHE_TEXTURE);
+ BIND_ENUM_CONSTANT(LOAD_FLAG_FLIP_Y);
}
AssetManager* AssetManager::get_singleton() {
@@ -32,55 +37,95 @@ AssetManager::~AssetManager() {
_singleton = nullptr;
}
-Ref<Image> AssetManager::_load_image(StringName const& path) {
+Ref<Image> AssetManager::_load_image(StringName const& path, bool flip_y) {
GameSingleton* game_singleton = GameSingleton::get_singleton();
ERR_FAIL_NULL_V(game_singleton, nullptr);
+
const String lookedup_path =
std_to_godot_string(game_singleton->get_dataloader().lookup_image_file(godot_to_std_string(path)).string());
ERR_FAIL_COND_V_MSG(lookedup_path.is_empty(), nullptr, vformat("Failed to look up image: %s", path));
+
const Ref<Image> image = Utilities::load_godot_image(lookedup_path);
ERR_FAIL_COND_V_MSG(
- image.is_null() || image->is_empty(), nullptr, vformat("Failed to load image: %s (looked up: %s)", path, lookedup_path)
+ image.is_null() || image->is_empty(), nullptr,
+ vformat("Failed to load image: %s (looked up: %s)", path, lookedup_path)
);
- return image;
-}
-AssetManager::image_asset_t* AssetManager::_get_image_asset(StringName const& path, bool flip_y) {
- image_asset_map_t::iterator it = image_assets.find(path);
- if (it != image_assets.end()) {
- return &it.value();
- }
- const Ref<Image> image = _load_image(path);
- ERR_FAIL_NULL_V(image, nullptr);
if (flip_y) {
image->flip_y();
}
- return &image_assets.emplace(std::move(path), AssetManager::image_asset_t { image, nullptr }).first.value();
+
+ return image;
}
-Ref<Image> AssetManager::get_image(StringName const& path, bool cache, bool flip_y) {
- if (cache) {
- image_asset_t const* asset = _get_image_asset(path, flip_y);
- ERR_FAIL_NULL_V(asset, nullptr);
- return asset->image;
+Ref<Image> AssetManager::get_image(StringName const& path, LoadFlags load_flags) {
+ /* Check for an existing image entry indicating a previous load attempt, whether successful or not. */
+ const image_asset_map_t::iterator it = image_assets.find(path);
+ if (it != image_assets.end()) {
+ std::optional<Ref<Image>> const& cached_image = it->second.image;
+
+ if (cached_image.has_value()) {
+ ERR_FAIL_NULL_V_MSG(*cached_image, nullptr, vformat("Failed to load image previously: %s", path));
+
+ return *cached_image;
+ }
+ }
+
+ /* No load attempt has been made yet, so we try now. */
+ const Ref<Image> image = _load_image(path, load_flags & LOAD_FLAG_FLIP_Y);
+
+ if (image.is_valid()) {
+ if (load_flags & LOAD_FLAG_CACHE_IMAGE) {
+ image_assets[path].image = image;
+ }
+
+ return image;
} else {
- return _load_image(path);
+ /* Mark both image and texture as failures, regardless of cache flags, in case of future load/creation attempts. */
+ image_assets[path] = { nullptr, nullptr };
+
+ ERR_FAIL_V_MSG(nullptr, vformat("Failed to load image: %s", path));
}
}
-Ref<ImageTexture> AssetManager::get_texture(StringName const& path, bool flip_y) {
- image_asset_t* asset = _get_image_asset(path, flip_y);
- ERR_FAIL_NULL_V(asset, nullptr);
- if (asset->texture.is_null()) {
- asset->texture = ImageTexture::create_from_image(asset->image);
- ERR_FAIL_NULL_V_MSG(asset->texture, nullptr, vformat("Failed to turn image into texture: %s", path));
+Ref<ImageTexture> AssetManager::get_texture(StringName const& path, LoadFlags load_flags) {
+ /* Check for an existing texture entry indicating a previous creation attempt, whether successful or not. */
+ const image_asset_map_t::const_iterator it = image_assets.find(path);
+ if (it != image_assets.end()) {
+ std::optional<Ref<ImageTexture>> const& cached_texture = it->second.texture;
+
+ if (cached_texture.has_value()) {
+ ERR_FAIL_NULL_V_MSG(*cached_texture, nullptr, vformat("Failed to create texture previously: %s", path));
+
+ return *cached_texture;
+ }
+ }
+
+ /* No creation attempt has yet been made, so we try now starting by finding the corresponding image. */
+ const Ref<Image> image = get_image(path, load_flags);
+ ERR_FAIL_NULL_V_MSG(image, nullptr, vformat("Failed to load image for texture: %s", path));
+
+ const Ref<ImageTexture> texture = ImageTexture::create_from_image(image);
+
+ if (texture.is_valid()) {
+ if (load_flags & LOAD_FLAG_CACHE_TEXTURE) {
+ image_assets[path].texture = texture;
+ }
+
+ return texture;
+ } else {
+ /* Mark texture as a failure, regardless of cache flags, in case of future creation attempts. */
+ image_assets[path].texture = nullptr;
+
+ ERR_FAIL_V_MSG(nullptr, vformat("Failed to create texture: %s", path));
}
- return asset->texture;
}
Ref<Font> AssetManager::get_font(StringName const& name) {
const font_map_t::const_iterator it = fonts.find(name);
if (it != fonts.end()) {
+ ERR_FAIL_NULL_V_MSG(it->second, nullptr, vformat("Failed to load font previously: %s", name));
+
return it->second;
}
@@ -89,18 +134,35 @@ Ref<Font> AssetManager::get_font(StringName const& name) {
static const String image_ext = ".tga";
const StringName image_path = font_dir + name + image_ext;
- const Ref<Image> image = get_image(image_path);
- ERR_FAIL_NULL_V_MSG(image, nullptr, vformat("Failed to load font image %s for the font named %s", image_path, name));
+ const Ref<Image> image = get_image(image_path, LOAD_FLAG_NONE);
+ if (image.is_null()) {
+ fonts.emplace(name, nullptr);
+
+ ERR_FAIL_V_MSG(nullptr, vformat("Failed to load font image %s for the font named %s", image_path, name));
+ }
+
GameSingleton* game_singleton = GameSingleton::get_singleton();
ERR_FAIL_NULL_V(game_singleton, nullptr);
+
const String font_path = font_dir + name + font_ext;
const String lookedup_font_path =
std_to_godot_string(game_singleton->get_dataloader().lookup_file(godot_to_std_string(font_path)).string());
+ if (lookedup_font_path.is_empty()) {
+ fonts.emplace(name, nullptr);
+
+ ERR_FAIL_V_MSG(nullptr, vformat("Failed to look up font: %s", font_path));
+ }
+
const Ref<Font> font = Utilities::load_godot_font(lookedup_font_path, image);
- ERR_FAIL_NULL_V_MSG(
- font, nullptr,
- vformat("Failed to load font file %s (looked up: %s) for the font named %s", font_path, lookedup_font_path, name)
- );
- fonts.emplace(std::move(name), font);
+ if (font.is_null()) {
+ fonts.emplace(name, nullptr);
+
+ ERR_FAIL_V_MSG(
+ nullptr,
+ vformat("Failed to load font file %s (looked up: %s) for the font named %s", font_path, lookedup_font_path, name)
+ );
+ }
+
+ fonts.emplace(name, font);
return font;
}
diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp
index 0416e5b..0856d05 100644
--- a/extension/src/openvic-extension/singletons/AssetManager.hpp
+++ b/extension/src/openvic-extension/singletons/AssetManager.hpp
@@ -5,7 +5,7 @@
#include <godot_cpp/classes/image_texture.hpp>
#include <godot_cpp/core/class_db.hpp>
-#include <openvic-simulation/interface/GFX.hpp>
+#include <openvic-simulation/interface/GFXSprite.hpp>
namespace OpenVic {
class AssetManager : public godot::Object {
@@ -13,9 +13,22 @@ namespace OpenVic {
static inline AssetManager* _singleton = nullptr;
+ public:
+ enum LoadFlags {
+ LOAD_FLAG_NONE = 0,
+ LOAD_FLAG_CACHE_IMAGE = 1 << 0,
+ LOAD_FLAG_CACHE_TEXTURE = 1 << 1,
+ LOAD_FLAG_FLIP_Y = 1 << 2
+ };
+
+ constexpr friend LoadFlags operator|(LoadFlags lhs, LoadFlags rhs) {
+ return static_cast<LoadFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
+ }
+
+ private:
struct image_asset_t {
- godot::Ref<godot::Image> image;
- godot::Ref<godot::ImageTexture> texture;
+ std::optional<godot::Ref<godot::Image>> image;
+ std::optional<godot::Ref<godot::ImageTexture>> texture;
};
/* deque_ordered_map to avoid the need to reallocate. */
using image_asset_map_t = deque_ordered_map<godot::StringName, image_asset_t>;
@@ -24,8 +37,7 @@ namespace OpenVic {
image_asset_map_t image_assets;
font_map_t fonts;
- static godot::Ref<godot::Image> _load_image(godot::StringName const& path);
- image_asset_t* _get_image_asset(godot::StringName const& path, bool flip_y);
+ static godot::Ref<godot::Image> _load_image(godot::StringName const& path, bool flip_y);
protected:
static void _bind_methods();
@@ -37,16 +49,27 @@ namespace OpenVic {
~AssetManager();
/* Search for and load an image at the specified path relative to the game defines, first checking the AssetManager's
- * image cache (if cache is true) in case it has already been loaded, and returning nullptr if image loading fails. */
- godot::Ref<godot::Image> get_image(godot::StringName const& path, bool cache = true, bool flip_y = false);
+ * image cache in case it has already been loaded, and returning nullptr if image loading fails. If the cache image
+ * load flag is set then the loaded image will be stored in the AssetManager's image cache for future access; if the
+ * flip y load flag is set then the image will be flipped vertically before being returned (if the image is already
+ * in the cache then no flipping will occur, regardless of whether it was orginally flipped or not). */
+ godot::Ref<godot::Image> get_image(godot::StringName const& path, LoadFlags load_flags = LOAD_FLAG_CACHE_IMAGE);
- /* 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 const& path, bool flip_y = false);
+ /* Create a texture from an image found at the specified path relative to the game defines, fist checking the
+ * AssetManager's texture cache in case it has already been loaded, and returning nullptr if image loading or texture
+ * creation fails. If the cache image load flag is set then the loaded image will be stored in the AssetManager's
+ * image cache for future access; if the cache texture load flag is set then the created texture will be stored in the
+ * AssetManager's texture cache for future access; if the flip y load flag is set then the image will be flipped
+ * vertically before being used to create the texture (if the image is already in the cache then no flipping will
+ * occur, regardless of whether it was orginally flipped or not). */
+ godot::Ref<godot::ImageTexture> get_texture(
+ godot::StringName const& path, LoadFlags load_flags = LOAD_FLAG_CACHE_TEXTURE
+ );
/* 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 const& name);
};
}
+
+VARIANT_ENUM_CAST(OpenVic::AssetManager::LoadFlags);
diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp
index 459b2c8..838542d 100644
--- a/extension/src/openvic-extension/singletons/GameSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp
@@ -9,6 +9,7 @@
#include "openvic-extension/singletons/AssetManager.hpp"
#include "openvic-extension/singletons/LoadLocalisation.hpp"
+#include "openvic-extension/singletons/MenuSingleton.hpp"
#include "openvic-extension/utility/ClassBindings.hpp"
#include "openvic-extension/utility/Utilities.hpp"
@@ -50,12 +51,17 @@ void GameSingleton::_bind_methods() {
OV_BIND_METHOD(GameSingleton::get_map_width);
OV_BIND_METHOD(GameSingleton::get_map_height);
+ OV_BIND_METHOD(GameSingleton::get_map_dims);
OV_BIND_METHOD(GameSingleton::get_map_aspect_ratio);
OV_BIND_METHOD(GameSingleton::get_terrain_texture);
+ OV_BIND_METHOD(GameSingleton::get_flag_dims);
+ OV_BIND_METHOD(GameSingleton::get_flag_sheet_texture);
OV_BIND_METHOD(GameSingleton::get_province_shape_image_subdivisions);
OV_BIND_METHOD(GameSingleton::get_province_shape_texture);
OV_BIND_METHOD(GameSingleton::get_province_colour_texture);
+ OV_BIND_METHOD(GameSingleton::get_province_names);
+
OV_BIND_METHOD(GameSingleton::get_mapmode_count);
OV_BIND_METHOD(GameSingleton::get_mapmode_identifier);
OV_BIND_METHOD(GameSingleton::set_mapmode, { "identifier" });
@@ -115,6 +121,7 @@ Error GameSingleton::setup_game(int32_t bookmark_index) {
Bookmark const* bookmark = game_manager.get_history_manager().get_bookmark_manager().get_bookmark_by_index(bookmark_index);
ERR_FAIL_NULL_V_MSG(bookmark, FAILED, vformat("Failed to get bookmark with index: %d", bookmark_index));
bool ret = game_manager.load_bookmark(bookmark);
+
for (Province& province : game_manager.get_map().get_provinces()) {
province.set_crime(
game_manager.get_crime_manager().get_crime_modifier_by_index(
@@ -122,13 +129,17 @@ Error GameSingleton::setup_game(int32_t bookmark_index) {
)
);
}
+
+ MenuSingleton* menu_singleton = MenuSingleton::get_singleton();
+ ERR_FAIL_NULL_V(menu_singleton, FAILED);
+ menu_singleton->_population_menu_update_provinces();
+
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_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);
+ const Vector2 pos = coords.posmod(1.0f) * get_map_dims();
+ return game_manager.get_map().get_province_index_at(Utilities::from_godot_ivec2(pos));
}
int32_t GameSingleton::get_map_width() const {
@@ -139,6 +150,10 @@ int32_t GameSingleton::get_map_height() const {
return game_manager.get_map().get_height();
}
+Vector2i GameSingleton::get_map_dims() const {
+ return Utilities::to_godot_ivec2(game_manager.get_map().get_dims());
+}
+
float GameSingleton::get_map_aspect_ratio() const {
return static_cast<float>(get_map_width()) / static_cast<float>(get_map_height());
}
@@ -147,19 +162,36 @@ Ref<Texture2DArray> GameSingleton::get_terrain_texture() const {
return terrain_texture;
}
-Ref<Image> GameSingleton::get_flag_image(Country const* country, StringName const& flag_type) const {
- ERR_FAIL_NULL_V(country, nullptr);
- const typename decltype(flag_image_map)::const_iterator it = flag_image_map.find(country);
+Ref<Image> GameSingleton::get_flag_sheet_image() const {
+ return flag_sheet_image;
+}
+
+Ref<ImageTexture> GameSingleton::get_flag_sheet_texture() const {
+ return flag_sheet_texture;
+}
+
+int32_t GameSingleton::get_flag_sheet_index(int32_t country_index, godot::StringName const& flag_type) const {
ERR_FAIL_COND_V_MSG(
- it == flag_image_map.end(), nullptr,
- vformat("Failed to find flags for country: %s", std_view_to_godot_string(country->get_identifier()))
+ country_index < 0 || country_index >= game_manager.get_country_manager().get_country_count(), -1,
+ vformat("Invalid country index: %d", country_index)
);
- const typename decltype(it->second)::const_iterator it2 = it->second.find(flag_type);
+
+ const typename decltype(flag_type_index_map)::const_iterator it = flag_type_index_map.find(flag_type);
+ ERR_FAIL_COND_V_MSG(it == flag_type_index_map.end(), -1, vformat("Invalid flag type %s", flag_type));
+
+ return flag_type_index_map.size() * country_index + it->second;
+}
+
+Rect2i GameSingleton::get_flag_sheet_rect(int32_t flag_index) const {
ERR_FAIL_COND_V_MSG(
- it2 == it->second.end(), nullptr,
- vformat("Failed to find %s flag for country: %s", flag_type, std_view_to_godot_string(country->get_identifier()))
+ flag_index < 0 || flag_index >= flag_sheet_count, {}, vformat("Invalid flag sheet index: %d", flag_index)
);
- return it2->second;
+
+ return { Vector2i { flag_index % flag_sheet_dims.x, flag_index / flag_sheet_dims.x } * flag_dims, flag_dims };
+}
+
+Rect2i GameSingleton::get_flag_sheet_rect(int32_t country_index, godot::StringName const& flag_type) const {
+ return get_flag_sheet_rect(get_flag_sheet_index(country_index, flag_type));
}
Vector2i GameSingleton::get_province_shape_image_subdivisions() const {
@@ -185,11 +217,11 @@ Error GameSingleton::_update_colour_image() {
static constexpr int32_t colour_image_width = PROVINCE_INDEX_SQRT * sizeof(Mapmode::base_stripe_t) / sizeof(colour_argb_t);
/* Province count + null province, rounded up to next multiple of PROVINCE_INDEX_SQRT.
* Rearranged from: (map.get_province_count() + 1) + (PROVINCE_INDEX_SQRT - 1) */
- static const int32_t colour_image_height = (map.get_province_count() + PROVINCE_INDEX_SQRT) / PROVINCE_INDEX_SQRT;
+ const int32_t colour_image_height = (map.get_province_count() + PROVINCE_INDEX_SQRT) / PROVINCE_INDEX_SQRT;
static PackedByteArray colour_data_array;
- static const int64_t colour_data_array_size = colour_image_width * colour_image_height * sizeof(colour_argb_t);
- colour_data_array.resize(colour_data_array_size);
+ const int64_t colour_data_array_size = colour_image_width * colour_image_height * sizeof(colour_argb_t);
+ ERR_FAIL_COND_V(colour_data_array.resize(colour_data_array_size) != OK, FAILED);
Error err = OK;
if (!map.generate_mapmode_colours(mapmode_index, colour_data_array.ptrw())) {
@@ -213,6 +245,39 @@ Error GameSingleton::_update_colour_image() {
return err;
}
+TypedArray<Dictionary> GameSingleton::get_province_names() const {
+ static const StringName identifier_key = "identifier";
+ static const StringName position_key = "position";
+ static const StringName rotation_key = "rotation";
+ static const StringName scale_key = "scale";
+
+ TypedArray<Dictionary> ret;
+ ERR_FAIL_COND_V(ret.resize(game_manager.get_map().get_province_count()) != OK, {});
+
+ for (int32_t index = 0; index < game_manager.get_map().get_province_count(); ++index) {
+ Province const& province = game_manager.get_map().get_provinces()[index];
+
+ Dictionary province_dict;
+
+ province_dict[identifier_key] = std_view_to_godot_string(province.get_identifier());
+ province_dict[position_key] = Utilities::to_godot_fvec2(province.get_text_position()) / get_map_dims();
+
+ const float rotation = province.get_text_rotation().to_float();
+ if (rotation != 0.0f) {
+ province_dict[rotation_key] = rotation;
+ }
+
+ const float scale = province.get_text_scale().to_float();
+ if (scale != 1.0f) {
+ province_dict[scale_key] = scale;
+ }
+
+ ret[index] = std::move(province_dict);
+ }
+
+ return ret;
+}
+
int32_t GameSingleton::get_mapmode_count() const {
return game_manager.get_map().get_mapmode_count();
}
@@ -275,19 +340,25 @@ Error GameSingleton::_load_map_images() {
}
Map::shape_pixel_t const* province_shape_data = game_manager.get_map().get_province_shape_image().data();
+
const Vector2i divided_dims = province_dims / image_subdivisions;
+ const int64_t subdivision_width = divided_dims.x * sizeof(Map::shape_pixel_t);
+ const int64_t subdivision_size = subdivision_width * divided_dims.y;
+
TypedArray<Image> province_shape_images;
- province_shape_images.resize(image_subdivisions.x * image_subdivisions.y);
+ ERR_FAIL_COND_V(province_shape_images.resize(image_subdivisions.x * image_subdivisions.y) != OK, FAILED);
+
+ PackedByteArray index_data_array;
+ ERR_FAIL_COND_V(index_data_array.resize(subdivision_size) != OK, FAILED);
+
for (int32_t v = 0; v < image_subdivisions.y; ++v) {
for (int32_t u = 0; u < image_subdivisions.x; ++u) {
- PackedByteArray index_data_array;
- index_data_array.resize(divided_dims.x * divided_dims.y * sizeof(Map::shape_pixel_t));
for (int32_t y = 0; y < divided_dims.y; ++y) {
memcpy(
- index_data_array.ptrw() + y * divided_dims.x * sizeof(Map::shape_pixel_t),
+ index_data_array.ptrw() + y * subdivision_width,
province_shape_data + (v * divided_dims.y + y) * province_dims.x + u * divided_dims.x,
- divided_dims.x * sizeof(Map::shape_pixel_t)
+ subdivision_width
);
}
@@ -322,7 +393,7 @@ Error GameSingleton::_load_terrain_variants() {
AssetManager* asset_manager = AssetManager::get_singleton();
ERR_FAIL_NULL_V(asset_manager, FAILED);
// Load the terrain texture sheet and prepare to slice it up
- Ref<Image> terrain_sheet = asset_manager->get_image(terrain_texturesheet_path);
+ Ref<Image> terrain_sheet = asset_manager->get_image(terrain_texturesheet_path, AssetManager::LOAD_FLAG_NONE);
ERR_FAIL_NULL_V_MSG(terrain_sheet, FAILED, vformat("Failed to load terrain texture sheet: %s", terrain_texturesheet_path));
static constexpr int32_t SHEET_DIMS = 8, SHEET_SIZE = SHEET_DIMS * SHEET_DIMS;
@@ -337,6 +408,7 @@ Error GameSingleton::_load_terrain_variants() {
const int32_t slice_size = sheet_width / SHEET_DIMS;
TypedArray<Image> terrain_images;
+ ERR_FAIL_COND_V(terrain_images.resize(SHEET_SIZE + 1) != OK, FAILED);
{
/* This is a placeholder image so that we don't have to branch to avoid looking up terrain index 0 (water).
* It should never appear in game, and so is bright red to to make it obvious if it slips through. */
@@ -344,72 +416,136 @@ Error GameSingleton::_load_terrain_variants() {
{ 1.0f, 0.0f, 0.0f }, slice_size, slice_size, terrain_sheet->get_format()
);
ERR_FAIL_NULL_V_EDMSG(water_image, FAILED, "Failed to create water terrain image");
- terrain_images.append(water_image);
+ terrain_images[0] = water_image;
}
- Error err = OK;
+
for (int32_t idx = 0; idx < SHEET_SIZE; ++idx) {
const Rect2i slice { idx % SHEET_DIMS * slice_size, idx / SHEET_DIMS * slice_size, slice_size, slice_size };
const Ref<Image> terrain_image = terrain_sheet->get_region(slice);
- if (terrain_image.is_null() || terrain_image->is_empty()) {
- UtilityFunctions::push_error(
- "Failed to extract terrain texture slice ", slice, " from ", terrain_texturesheet_path
- );
- err = FAILED;
- }
- terrain_images.append(terrain_image);
+
+ ERR_FAIL_COND_V_MSG(terrain_image.is_null() || terrain_image->is_empty(), FAILED, vformat(
+ "Failed to extract terrain texture slice %s from %s", slice, terrain_texturesheet_path
+ ));
+
+ terrain_images[idx + 1] = terrain_image;
}
terrain_texture.instantiate();
ERR_FAIL_COND_V_MSG(
terrain_texture->create_from_images(terrain_images) != OK, FAILED, "Failed to create terrain texture array!"
);
- return err;
+ return OK;
}
-Error GameSingleton::_load_flag_images() {
- ERR_FAIL_COND_V_MSG(!flag_image_map.empty(), FAILED, "Flag images have already been loaded!");
+const Vector2i GameSingleton::flag_dims { 128, 64 };
+
+Error GameSingleton::_load_flag_sheet() {
+ ERR_FAIL_COND_V_MSG(
+ flag_sheet_image.is_valid() || flag_sheet_texture.is_valid(), FAILED,
+ "Flag sheet image and/or texture has already been generated!"
+ );
GovernmentTypeManager const& government_type_manager = game_manager.get_politics_manager().get_government_type_manager();
ERR_FAIL_COND_V_MSG(
- !government_type_manager.government_types_are_locked(), FAILED,
- "Cannot load flag images before government types are locked!"
+ government_type_manager.get_flag_types().empty() || !government_type_manager.government_types_are_locked(), FAILED,
+ "Cannot load flag images if flag types are empty or government types are not locked!"
);
CountryManager const& country_manager = game_manager.get_country_manager();
ERR_FAIL_COND_V_MSG(
- !country_manager.countries_are_locked(), FAILED, "Cannot load flag images before countries are locked!"
+ country_manager.countries_empty() || !country_manager.countries_are_locked(), FAILED,
+ "Cannot load flag images if countries are empty or not locked!"
);
AssetManager* asset_manager = AssetManager::get_singleton();
ERR_FAIL_NULL_V(asset_manager, FAILED);
- static const String flag_directory = "gfx/flags/";
- static const String flag_separator = "_";
- static const String flag_extension = ".tga";
-
- std::vector<StringName> flag_types;
+ /* Generate flag type - index lookup map */
+ flag_type_index_map.clear();
for (std::string const& type : government_type_manager.get_flag_types()) {
- flag_types.emplace_back(std_to_godot_string_name(type));
+ flag_type_index_map.emplace(std_to_godot_string_name(type), static_cast<int32_t>(flag_type_index_map.size()));
}
- flag_image_map.reserve(country_manager.get_countries().size());
+ flag_sheet_count = country_manager.get_countries().size() * flag_type_index_map.size();
+
+ std::vector<Ref<Image>> flag_images;
+ flag_images.reserve(flag_sheet_count);
+
+ static constexpr Image::Format flag_format = Image::FORMAT_RGB8;
Error ret = OK;
for (Country const& country : country_manager.get_countries()) {
- ordered_map<StringName, Ref<Image>>& flag_images = flag_image_map[&country];
- flag_images.reserve(flag_types.size());
const String country_name = std_view_to_godot_string(country.get_identifier());
- for (StringName const& flag_type : flag_types) {
+
+ for (auto const& [flag_type, flag_type_index] : flag_type_index_map) {
+ static const String flag_directory = "gfx/flags/";
+ static const String flag_separator = "_";
+ static const String flag_extension = ".tga";
+
const StringName flag_path =
flag_directory + country_name + (flag_type.is_empty() ? "" : flag_separator + flag_type) + flag_extension;
- const Ref<Image> flag_image = asset_manager->get_image(flag_path);
+
+ /* Do not cache flag image, they should be freed after the flag sheet has been generated. */
+ const Ref<Image> flag_image = asset_manager->get_image(flag_path, AssetManager::LOAD_FLAG_NONE);
+
if (flag_image.is_valid()) {
- flag_images.emplace(flag_type, flag_image);
+
+ if (flag_image->get_format() != flag_format) {
+ flag_image->convert(flag_format);
+ }
+
+ if (flag_image->get_size() != flag_dims) {
+ if (flag_image->get_width() > flag_dims.x || flag_image->get_height() > flag_dims.y) {
+ UtilityFunctions::push_warning(
+ "Flag image ", flag_path, " (", flag_image->get_size(), ") is larger than the sheet flag size (",
+ flag_dims, ")"
+ );
+ }
+
+ flag_image->resize(flag_dims.x, flag_dims.y, Image::INTERPOLATE_NEAREST);
+ }
} else {
UtilityFunctions::push_error("Failed to load flag image: ", flag_path);
ret = FAILED;
}
+
+ /* Add flag_image to the vector even if it's null to ensure each flag has the right index. */
+ flag_images.push_back(flag_image);
}
}
+
+ ERR_FAIL_COND_V(flag_images.size() != flag_sheet_count, FAILED);
+
+ /* Calculate the width that will make the sheet as close to a square as possible (taking flag dimensions into account.) */
+ flag_sheet_dims.x = (fixed_point_t { static_cast<int32_t>(flag_images.size()) } * flag_dims.y / flag_dims.x).sqrt().ceil();
+
+ /* Calculated corresponding height (rounded up). */
+ flag_sheet_dims.y = (static_cast<int32_t>(flag_images.size()) + flag_sheet_dims.x - 1 ) / flag_sheet_dims.x;
+
+ const Vector2i sheet_dims = flag_sheet_dims * flag_dims;
+
+ flag_sheet_image = Image::create(sheet_dims.x, sheet_dims.y, false, flag_format);
+ ERR_FAIL_NULL_V_MSG(flag_sheet_image, FAILED, "Failed to create flag sheet image!");
+
+ static const Rect2i flag_rect { { 0, 0 }, flag_dims };
+
+ /* Fill the flag sheet with the flag images. */
+ for (int32_t index = 0; index < flag_images.size(); ++index) {
+ Ref<Image> const& flag_image = flag_images[index];
+
+ const Vector2i sheet_pos = Vector2i { index % flag_sheet_dims.x, index / flag_sheet_dims.x } * flag_dims;
+
+ if (flag_image.is_valid()) {
+ flag_sheet_image->blit_rect(flag_image, flag_rect, sheet_pos);
+ } else {
+ static const Color error_colour { 1.0f, 0.0f, 1.0f, 1.0f }; /* Magenta */
+
+ flag_sheet_image->fill_rect({ sheet_pos, flag_dims }, error_colour);
+ }
+ }
+
+ flag_sheet_texture = ImageTexture::create_from_image(flag_sheet_image);
+ ERR_FAIL_NULL_V_MSG(flag_sheet_texture, FAILED, "Failed to create flag sheet texture!");
+
return ret;
}
@@ -433,8 +569,8 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi
UtilityFunctions::push_error("Failed to load terrain variants!");
err = FAILED;
}
- if (_load_flag_images() != OK) {
- UtilityFunctions::push_error("Failed to load flag textures!");
+ if (_load_flag_sheet() != OK) {
+ UtilityFunctions::push_error("Failed to load flag sheet!");
err = FAILED;
}
if (_load_map_images() != OK) {
diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp
index e84e366..f2b88ac 100644
--- a/extension/src/openvic-extension/singletons/GameSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp
@@ -22,7 +22,13 @@ namespace OpenVic {
godot::Ref<godot::ImageTexture> province_colour_texture;
Mapmode::index_t mapmode_index = 0;
godot::Ref<godot::Texture2DArray> terrain_texture;
- ordered_map<Country const*, ordered_map<godot::StringName, godot::Ref<godot::Image>>> flag_image_map;
+
+ static const godot::Vector2i PROPERTY(flag_dims); /* The size in pixels of an individual flag. */
+ int32_t flag_sheet_count = 0; /* The number of flags in the flag sheet. */
+ godot::Vector2i flag_sheet_dims; /* The size of the flag sheet in flags, rather than pixels. */
+ godot::Ref<godot::Image> flag_sheet_image;
+ godot::Ref<godot::ImageTexture> flag_sheet_texture;
+ ordered_map<godot::StringName, int32_t> flag_type_index_map;
static godot::StringName const& _signal_gamestate_updated();
static godot::StringName const& _signal_province_selected();
@@ -30,7 +36,7 @@ namespace OpenVic {
godot::Error _load_map_images();
godot::Error _load_terrain_variants();
- godot::Error _load_flag_images();
+ godot::Error _load_flag_sheet();
/* Generate the province_colour_texture from the current mapmode. */
godot::Error _update_colour_image();
@@ -61,14 +67,20 @@ namespace OpenVic {
int32_t get_map_width() const;
int32_t get_map_height() const;
+ godot::Vector2i get_map_dims() const;
float get_map_aspect_ratio() const;
/* The cosmetic terrain textures stored in a Texture2DArray. */
godot::Ref<godot::Texture2DArray> get_terrain_texture() const;
- /* The flag image corresponding to the requested country / flag_type
- * combination, or nullptr if no such flag can be found. */
- godot::Ref<godot::Image> get_flag_image(Country const* country, godot::StringName const& flag_type) const;
+ godot::Ref<godot::Image> get_flag_sheet_image() const;
+ godot::Ref<godot::ImageTexture> get_flag_sheet_texture() const;
+
+ /* The index of the flag in the flag sheet corresponding to the requested country / flag_type
+ * combination, or -1 if no such flag can be found. */
+ int32_t get_flag_sheet_index(int32_t country_index, godot::StringName const& flag_type) const;
+ godot::Rect2i get_flag_sheet_rect(int32_t flag_index) const;
+ godot::Rect2i get_flag_sheet_rect(int32_t country_index, godot::StringName const& flag_type) const;
/* Number of (vertical, horizontal) subdivisions the province shape image
* was split into when making the province_shape_texture to ensure no
@@ -83,6 +95,8 @@ namespace OpenVic {
/* The base and stripe colours for each province. */
godot::Ref<godot::ImageTexture> get_province_colour_texture() const;
+ godot::TypedArray<godot::Dictionary> get_province_names() const;
+
int32_t get_mapmode_count() const;
godot::String get_mapmode_identifier(int32_t index) const;
godot::Error set_mapmode(godot::String const& identifier);
diff --git a/extension/src/openvic-extension/singletons/LoadLocalisation.cpp b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp
index 8860105..16ebe57 100644
--- a/extension/src/openvic-extension/singletons/LoadLocalisation.cpp
+++ b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp
@@ -111,7 +111,7 @@ Error LoadLocalisation::load_localisation_dir(String const& dir_path) const {
ERR_FAIL_COND_V_MSG(
!DirAccess::dir_exists_absolute(dir_path), FAILED, vformat("Localisation directory does not exist: %s", dir_path)
);
- PackedStringArray const dirs = DirAccess::get_directories_at(dir_path);
+ const PackedStringArray dirs = DirAccess::get_directories_at(dir_path);
ERR_FAIL_COND_V_MSG(
dirs.size() < 1, FAILED, vformat("Localisation directory does not contain any sub-directories: %s", dir_path)
);
diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp
index 52fb6af..993549c 100644
--- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp
@@ -15,6 +15,19 @@ using namespace OpenVic;
using OpenVic::Utilities::std_to_godot_string;
using OpenVic::Utilities::std_view_to_godot_string;
+StringName const& MenuSingleton::_signal_population_menu_province_list_changed() {
+ static const StringName signal_population_menu_province_list_changed = "population_menu_province_list_changed";
+ return signal_population_menu_province_list_changed;
+}
+StringName const& MenuSingleton::_signal_population_menu_province_list_selected_changed() {
+ static const StringName signal_population_menu_province_list_selected_changed = "population_menu_province_list_selected_changed";
+ return signal_population_menu_province_list_selected_changed;
+}
+StringName const& MenuSingleton::_signal_population_menu_pops_changed() {
+ static const StringName signal_population_menu_pops_changed = "population_menu_pops_changed";
+ return signal_population_menu_pops_changed;
+}
+
void MenuSingleton::_bind_methods() {
/* PROVINCE OVERVIEW PANEL */
OV_BIND_METHOD(MenuSingleton::get_province_info_from_index, { "index" });
@@ -35,6 +48,60 @@ void MenuSingleton::_bind_methods() {
OV_BIND_METHOD(MenuSingleton::can_increase_speed);
OV_BIND_METHOD(MenuSingleton::can_decrease_speed);
OV_BIND_METHOD(MenuSingleton::get_longform_date);
+
+ /* POPULATION MENU */
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_province_list_row_count);
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_province_list_rows, { "start", "count" });
+ OV_BIND_METHOD(
+ MenuSingleton::population_menu_select_province_list_entry, { "select_index", "set_scroll_index" }, DEFVAL(false)
+ );
+ OV_BIND_METHOD(MenuSingleton::population_menu_select_province, { "province_index" });
+ OV_BIND_METHOD(MenuSingleton::population_menu_toggle_expanded, { "toggle_index", "emit_selected_changed" }, DEFVAL(true));
+
+ OV_BIND_METHOD(MenuSingleton::population_menu_select_sort_key, { "sort_key" });
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_rows, { "start", "count" });
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_row_count);
+
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_filter_setup_info);
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_pop_filter_info);
+ OV_BIND_METHOD(MenuSingleton::population_menu_toggle_pop_filter, { "filter_index" });
+ OV_BIND_METHOD(MenuSingleton::population_menu_select_all_pop_filters);
+ OV_BIND_METHOD(MenuSingleton::population_menu_deselect_all_pop_filters);
+
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_distribution_setup_info);
+ OV_BIND_METHOD(MenuSingleton::get_population_menu_distribution_info);
+
+ ADD_SIGNAL(MethodInfo(_signal_population_menu_province_list_changed()));
+ ADD_SIGNAL(
+ MethodInfo(_signal_population_menu_province_list_selected_changed(), PropertyInfo(Variant::INT, "scroll_index"))
+ );
+ ADD_SIGNAL(MethodInfo(_signal_population_menu_pops_changed()));
+
+ using enum population_menu_t::ProvinceListEntry;
+ BIND_ENUM_CONSTANT(LIST_ENTRY_NONE);
+ BIND_ENUM_CONSTANT(LIST_ENTRY_COUNTRY);
+ BIND_ENUM_CONSTANT(LIST_ENTRY_STATE);
+ BIND_ENUM_CONSTANT(LIST_ENTRY_PROVINCE);
+
+ using enum population_menu_t::PopSortKey;
+ BIND_ENUM_CONSTANT(NONE);
+ BIND_ENUM_CONSTANT(SORT_SIZE);
+ BIND_ENUM_CONSTANT(SORT_TYPE);
+ BIND_ENUM_CONSTANT(SORT_CULTURE);
+ BIND_ENUM_CONSTANT(SORT_RELIGION);
+ BIND_ENUM_CONSTANT(SORT_LOCATION);
+ BIND_ENUM_CONSTANT(SORT_MILITANCY);
+ BIND_ENUM_CONSTANT(SORT_CONSCIOUSNESS);
+ BIND_ENUM_CONSTANT(SORT_IDEOLOGY);
+ BIND_ENUM_CONSTANT(SORT_ISSUES);
+ BIND_ENUM_CONSTANT(SORT_UNEMPLOYMENT);
+ BIND_ENUM_CONSTANT(SORT_CASH);
+ BIND_ENUM_CONSTANT(SORT_LIFE_NEEDS);
+ BIND_ENUM_CONSTANT(SORT_EVERYDAY_NEEDS);
+ BIND_ENUM_CONSTANT(SORT_LUXURY_NEEDS);
+ BIND_ENUM_CONSTANT(SORT_REBEL_FACTION);
+ BIND_ENUM_CONSTANT(SORT_SIZE_CHANGE);
+ BIND_ENUM_CONSTANT(SORT_LITERACY);
}
MenuSingleton* MenuSingleton::get_singleton() {
@@ -143,11 +210,17 @@ Dictionary MenuSingleton::get_province_info_from_index(int32_t index) const {
std::vector<Country const*> const& cores = province->get_cores();
if (!cores.empty()) {
PackedStringArray cores_array;
- cores_array.resize(cores.size());
- for (size_t idx = 0; idx < cores.size(); ++idx) {
- cores_array[idx] = std_view_to_godot_string(cores[idx]->get_identifier());
+ if (cores_array.resize(cores.size()) == OK) {
+ for (size_t idx = 0; idx < cores.size(); ++idx) {
+ cores_array[idx] = std_view_to_godot_string(cores[idx]->get_identifier());
+ }
+ ret[province_info_cores_key] = std::move(cores_array);
+ } else {
+ UtilityFunctions::push_error(
+ "Failed to resize cores array to the correct size (", static_cast<int64_t>(cores.size()), ") for province ",
+ std_view_to_godot_string(province->get_identifier())
+ );
}
- ret[province_info_cores_key] = cores_array;
}
static const StringName building_info_level_key = "level";
@@ -161,20 +234,26 @@ Dictionary MenuSingleton::get_province_info_from_index(int32_t index) const {
/* This system relies on the province buildings all being present in the right order. It will have to
* be changed if we want to support variable combinations and permutations of province buildings. */
TypedArray<Dictionary> buildings_array;
- buildings_array.resize(buildings.size());
- for (size_t idx = 0; idx < buildings.size(); ++idx) {
- BuildingInstance const& building = buildings[idx];
-
- Dictionary building_dict;
- 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;
+ if (buildings_array.resize(buildings.size()) == OK) {
+ for (size_t idx = 0; idx < buildings.size(); ++idx) {
+ BuildingInstance const& building = buildings[idx];
+
+ Dictionary building_dict;
+ 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] = std::move(building_dict);
+ }
+ ret[province_info_buildings_key] = std::move(buildings_array);
+ } else {
+ UtilityFunctions::push_error(
+ "Failed to resize buildings array to the correct size (", static_cast<int64_t>(buildings.size()),
+ ") for province ", std_view_to_godot_string(province->get_identifier())
+ );
}
- ret[province_info_buildings_key] = buildings_array;
}
return ret;
}
diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp
index 6bd90c5..fd1b6c5 100644
--- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp
@@ -2,10 +2,12 @@
#include <godot_cpp/classes/image.hpp>
+#include <openvic-simulation/pop/Pop.hpp>
#include <openvic-simulation/types/OrderedContainers.hpp>
namespace OpenVic {
struct GameManager;
+ struct Region;
class MenuSingleton : public godot::Object {
GDCLASS(MenuSingleton, godot::Object)
@@ -14,6 +16,70 @@ namespace OpenVic {
GameManager* game_manager;
+ public:
+ struct population_menu_t {
+ enum ProvinceListEntry {
+ LIST_ENTRY_NONE, LIST_ENTRY_COUNTRY, LIST_ENTRY_STATE, LIST_ENTRY_PROVINCE
+ };
+
+ struct country_entry_t {
+ Country const& country;
+ bool selected = true;
+ };
+
+ struct state_entry_t {
+ // TODO - change to State
+ Region const& state;
+ bool selected = true, expanded = false;
+ };
+
+ struct province_entry_t {
+ Province const& province;
+ bool selected = true;
+ };
+
+ using province_list_entry_t = std::variant<country_entry_t, state_entry_t, province_entry_t>;
+
+ std::vector<province_list_entry_t> province_list_entries;
+ int32_t visible_province_list_entries = 0;
+
+ struct pop_filter_t {
+ Pop::pop_size_t count, promotion_demotion_change;
+ bool selected;
+ };
+ ordered_map<PopType const*, pop_filter_t> pop_filters;
+
+ static constexpr int32_t DISTRIBUTION_COUNT = 6;
+ /* Distributions:
+ * - Workforce (PopType)
+ * - Religion
+ * - Ideology
+ * - Nationality (Culture)
+ * - Issues
+ * - Vote */
+ std::array<fixed_point_map_t<HasIdentifierAndColour const*>, DISTRIBUTION_COUNT> distributions;
+
+ enum PopSortKey {
+ NONE, SORT_SIZE, SORT_TYPE, SORT_CULTURE, SORT_RELIGION, SORT_LOCATION, SORT_MILITANCY, SORT_CONSCIOUSNESS,
+ SORT_IDEOLOGY, SORT_ISSUES, SORT_UNEMPLOYMENT, SORT_CASH, SORT_LIFE_NEEDS, SORT_EVERYDAY_NEEDS,
+ SORT_LUXURY_NEEDS, SORT_REBEL_FACTION, SORT_SIZE_CHANGE, SORT_LITERACY, MAX_SORT_KEY
+ } sort_key = NONE;
+ bool sort_descending = true;
+
+ std::vector<Pop const*> pops, filtered_pops;
+ };
+
+ private:
+ population_menu_t population_menu;
+
+ /* Emitted when the number of visible province list rows changes (list generated or state entry expanded).*/
+ static godot::StringName const& _signal_population_menu_province_list_changed();
+ /* Emitted when the state of visible province list rows changes (selection changes). Provides an integer argument
+ * which, if not negative, the province list scroll index should be updated to. */
+ static godot::StringName const& _signal_population_menu_province_list_selected_changed();
+ /* Emitted when the selected/filtered collection of pops changes. */
+ static godot::StringName const& _signal_population_menu_pops_changed();
+
protected:
static void _bind_methods();
@@ -44,5 +110,35 @@ namespace OpenVic {
bool can_increase_speed() const;
bool can_decrease_speed() const;
godot::String get_longform_date() const;
+
+ /* POPULATION MENU */
+ void _population_menu_update_provinces();
+ int32_t get_population_menu_province_list_row_count() const;
+ godot::TypedArray<godot::Dictionary> get_population_menu_province_list_rows(int32_t start, int32_t count) const;
+ godot::Error population_menu_select_province_list_entry(int32_t select_index, bool set_scroll_index = false);
+ godot::Error population_menu_select_province(int32_t province_index);
+ godot::Error population_menu_toggle_expanded(int32_t toggle_index, bool emit_selected_changed = true);
+
+ void _population_menu_update_pops();
+ void _population_menu_update_filtered_pops();
+ using sort_func_t = std::function<bool(Pop const*, Pop const*)>;
+ sort_func_t _get_population_menu_sort_func(population_menu_t::PopSortKey sort_key) const;
+ void _population_menu_sort_pops();
+ godot::Error population_menu_select_sort_key(population_menu_t::PopSortKey sort_key);
+ godot::TypedArray<godot::Dictionary> get_population_menu_pop_rows(int32_t start, int32_t count) const;
+ int32_t get_population_menu_pop_row_count() const;
+
+ godot::PackedInt32Array get_population_menu_pop_filter_setup_info();
+ godot::TypedArray<godot::Dictionary> get_population_menu_pop_filter_info() const;
+ godot::Error population_menu_toggle_pop_filter(int32_t filter_index);
+ void population_menu_select_all_pop_filters();
+ void population_menu_deselect_all_pop_filters();
+
+ godot::PackedStringArray get_population_menu_distribution_setup_info() const;
+ /* Array of GFXPieChartTexture::godot_pie_chart_data_t. */
+ godot::TypedArray<godot::Array> get_population_menu_distribution_info() const;
};
}
+
+VARIANT_ENUM_CAST(OpenVic::MenuSingleton::population_menu_t::ProvinceListEntry);
+VARIANT_ENUM_CAST(OpenVic::MenuSingleton::population_menu_t::PopSortKey);
diff --git a/extension/src/openvic-extension/singletons/PopulationMenu.cpp b/extension/src/openvic-extension/singletons/PopulationMenu.cpp
new file mode 100644
index 0000000..a598ceb
--- /dev/null
+++ b/extension/src/openvic-extension/singletons/PopulationMenu.cpp
@@ -0,0 +1,729 @@
+#include "MenuSingleton.hpp"
+
+#include <godot_cpp/variant/utility_functions.hpp>
+
+#include <openvic-simulation/GameManager.hpp>
+
+#include "openvic-extension/classes/GFXPieChartTexture.hpp"
+#include "openvic-extension/utility/Utilities.hpp"
+
+using namespace godot;
+using namespace OpenVic;
+
+using OpenVic::Utilities::std_view_to_godot_string;
+
+/* POPULATION MENU */
+
+void MenuSingleton::_population_menu_update_provinces() {
+ ERR_FAIL_NULL(game_manager);
+
+ population_menu.province_list_entries.clear();
+ population_menu.visible_province_list_entries = 0;
+
+ for (Country const* country : {
+ // Example country
+ game_manager->get_country_manager().get_country_by_identifier("ENG")
+ }) {
+ ERR_CONTINUE(country == nullptr);
+
+ population_menu.province_list_entries.emplace_back(population_menu_t::country_entry_t { *country });
+ population_menu.visible_province_list_entries++;
+
+ // TODO - change to State
+ for (Region const& state : game_manager->get_map().get_regions()) {
+
+ population_menu.province_list_entries.emplace_back(population_menu_t::state_entry_t { state });
+ population_menu.visible_province_list_entries++;
+
+ for (Province const* province : state.get_provinces()) {
+ population_menu.province_list_entries.emplace_back(population_menu_t::province_entry_t { *province });
+ }
+ }
+ }
+
+ population_menu.sort_key = population_menu_t::NONE;
+
+ emit_signal(_signal_population_menu_province_list_changed());
+
+ // TODO - may need to emit population_menu_province_list_selected_changed if _update_info cannot be guaranteed
+
+ _population_menu_update_pops();
+}
+
+int32_t MenuSingleton::get_population_menu_province_list_row_count() const {
+ return population_menu.visible_province_list_entries;
+}
+
+TypedArray<Dictionary> MenuSingleton::get_population_menu_province_list_rows(int32_t start, int32_t count) const {
+ // TODO - remove when country population is used instead of total map population
+ ERR_FAIL_NULL_V(game_manager, {});
+
+ if (population_menu.province_list_entries.empty()) {
+ return {};
+ }
+
+ ERR_FAIL_INDEX_V_MSG(
+ start, population_menu.visible_province_list_entries, {},
+ vformat("Invalid start for population menu province list rows: %d", start)
+ );
+ ERR_FAIL_COND_V_MSG(count <= 0, {}, vformat("Invalid count for population menu province list rows: %d", count));
+
+ static const StringName type_key = "type";
+ static const StringName index_key = "index";
+ static const StringName name_key = "name";
+ static const StringName size_key = "size";
+ static const StringName change_key = "change";
+ static const StringName selected_key = "selected";
+ /* State-only keys */
+ static const StringName expanded_key = "expanded";
+ static const StringName colonial_status_key = "colony";
+ // TODO - national focus
+
+ struct entry_visitor_t {
+
+ int32_t& start_counter;
+ int32_t& count_counter;
+
+ // TODO - remove when country population is used instead of total map population
+ const Pop::pop_size_t total_map_population;
+
+ /* This is the index among all entries, not just visible ones unlike start and count. */
+ int32_t index = 0;
+
+ bool is_expanded = true;
+
+ TypedArray<Dictionary> array {};
+
+ /* Overloads return false if count_counter reaches 0 and the function should return,
+ * otherwise true indicating the province list loop should continue. */
+
+ bool operator()(population_menu_t::country_entry_t const& country_entry) {
+ if (start_counter-- <= 0) {
+ Dictionary country_dict;
+
+ country_dict[type_key] = population_menu_t::LIST_ENTRY_COUNTRY;
+ country_dict[index_key] = index;
+ country_dict[name_key] = std_view_to_godot_string(country_entry.country.get_identifier());
+ country_dict[size_key] = total_map_population;
+ country_dict[change_key] = 0;
+ country_dict[selected_key] = country_entry.selected;
+
+ array.push_back(country_dict);
+
+ return --count_counter > 0;
+ }
+
+ return true;
+ }
+
+ bool operator()(population_menu_t::state_entry_t const& state_entry) {
+ is_expanded = state_entry.expanded;
+
+ if (start_counter-- <= 0) {
+ Dictionary state_dict;
+
+ state_dict[type_key] = population_menu_t::LIST_ENTRY_STATE;
+ state_dict[index_key] = index;
+ state_dict[name_key] = std_view_to_godot_string(state_entry.state.get_identifier());
+ state_dict[size_key] = state_entry.state.calculate_total_population();
+ state_dict[change_key] = 0;
+ state_dict[selected_key] = state_entry.selected;
+ state_dict[expanded_key] = state_entry.expanded;
+ state_dict[colonial_status_key] = false;
+
+ array.push_back(state_dict);
+
+ return --count_counter > 0;
+ }
+
+ return true;
+ }
+
+ bool operator()(population_menu_t::province_entry_t const& province_entry) {
+ if (is_expanded && start_counter-- <= 0) {
+ Dictionary province_dict;
+
+ province_dict[type_key] = population_menu_t::LIST_ENTRY_PROVINCE;
+ province_dict[index_key] = index;
+ province_dict[name_key] = std_view_to_godot_string(province_entry.province.get_identifier());
+ province_dict[size_key] = province_entry.province.get_total_population();
+ province_dict[change_key] = 0;
+ province_dict[selected_key] = province_entry.selected;
+
+ array.push_back(province_dict);
+
+ return --count_counter > 0;
+ }
+
+ return true;
+ }
+ } entry_visitor { start, count, game_manager->get_map().get_total_map_population() };
+
+ while (entry_visitor.index < population_menu.province_list_entries.size()
+ && std::visit(entry_visitor, population_menu.province_list_entries[entry_visitor.index])) {
+ entry_visitor.index++;
+ }
+
+ return entry_visitor.array;
+}
+
+Error MenuSingleton::population_menu_select_province_list_entry(int32_t select_index, bool set_scroll_index) {
+ ERR_FAIL_INDEX_V(select_index, population_menu.province_list_entries.size(), FAILED);
+
+ struct entry_visitor {
+
+ const int32_t _select_index;
+
+ int32_t index = 0, visible_index = 0;
+ bool is_expanded = true;
+
+ int32_t selected_visible_index = -1;
+
+ using enum population_menu_t::ProvinceListEntry;
+ population_menu_t::ProvinceListEntry select_level = LIST_ENTRY_NONE;
+
+ void operator()(population_menu_t::country_entry_t& country_entry) {
+ if (index == _select_index) {
+ select_level = LIST_ENTRY_COUNTRY;
+
+ country_entry.selected = true;
+
+ selected_visible_index = visible_index;
+ } else {
+ select_level = LIST_ENTRY_NONE;
+
+ country_entry.selected = false;
+ }
+
+ visible_index++;
+ }
+
+ void operator()(population_menu_t::state_entry_t& state_entry) {
+ if (select_level == LIST_ENTRY_COUNTRY) {
+ state_entry.selected = true;
+ } else if (index == _select_index) {
+ select_level = LIST_ENTRY_STATE;
+
+ state_entry.selected = true;
+
+ selected_visible_index = visible_index;
+ } else {
+ select_level = LIST_ENTRY_NONE;
+ state_entry.selected = false;
+ }
+
+ visible_index++;
+
+ is_expanded = state_entry.expanded;
+ }
+
+ void operator()(population_menu_t::province_entry_t& province_entry) {
+ if (select_level == LIST_ENTRY_COUNTRY || select_level == LIST_ENTRY_STATE) {
+ province_entry.selected = true;
+ } else if (index == _select_index) {
+ province_entry.selected = true;
+
+ selected_visible_index = visible_index;
+ } else {
+ province_entry.selected = false;
+ }
+
+ if (is_expanded) {
+ visible_index++;
+ }
+ }
+
+ } entry_visitor { select_index };
+
+ while (entry_visitor.index < population_menu.province_list_entries.size()) {
+ std::visit(entry_visitor, population_menu.province_list_entries[entry_visitor.index]);
+ entry_visitor.index++;
+ }
+
+ emit_signal(
+ _signal_population_menu_province_list_selected_changed(),
+ set_scroll_index ? entry_visitor.selected_visible_index : -1
+ );
+
+ _population_menu_update_pops();
+
+ return OK;
+}
+
+Error MenuSingleton::population_menu_select_province(int32_t province_index) {
+ ERR_FAIL_NULL_V(game_manager, FAILED);
+
+ ERR_FAIL_COND_V(province_index <= 0 || province_index > game_manager->get_map().get_province_count(), FAILED);
+
+ struct entry_visitor_t {
+
+ MenuSingleton& menu_singleton;
+
+ const int32_t _province_index;
+
+ int32_t index = 0;
+
+ int32_t state_entry_to_expand = -1;
+
+ bool ret = true;
+
+ /* Overloads return false if the province entry is found and the loop can stop, true otherwise. */
+
+ bool operator()(population_menu_t::country_entry_t& country_entry) {
+ return true;
+ }
+
+ bool operator()(population_menu_t::state_entry_t& state_entry) {
+ if (state_entry.expanded) {
+ state_entry_to_expand = -1;
+ } else {
+ state_entry_to_expand = index;
+ }
+ return true;
+ }
+
+ bool operator()(population_menu_t::province_entry_t& province_entry) {
+ if (province_entry.province.get_index() == _province_index) {
+
+ if (state_entry_to_expand >= 0) {
+ ret &= menu_singleton.population_menu_toggle_expanded(state_entry_to_expand, false) == OK;
+ }
+
+ ret &= menu_singleton.population_menu_select_province_list_entry(index, true) == OK;
+
+ return false;
+ }
+ return true;
+ }
+
+ } entry_visitor { *this, province_index };
+
+ while (entry_visitor.index < population_menu.province_list_entries.size()
+ && std::visit(entry_visitor, population_menu.province_list_entries[entry_visitor.index])) {
+ entry_visitor.index++;
+ }
+
+ ERR_FAIL_COND_V_MSG(
+ entry_visitor.index >= population_menu.province_list_entries.size(), FAILED,
+ vformat("Cannot select province index %d - not found in population menu province list!", province_index)
+ );
+
+ return ERR(entry_visitor.ret);
+}
+
+Error MenuSingleton::population_menu_toggle_expanded(int32_t toggle_index, bool emit_selected_changed) {
+ ERR_FAIL_INDEX_V(toggle_index, population_menu.province_list_entries.size(), FAILED);
+
+ population_menu_t::state_entry_t* state_entry =
+ std::get_if<population_menu_t::state_entry_t>(&population_menu.province_list_entries[toggle_index]);
+
+ ERR_FAIL_NULL_V_MSG(state_entry, FAILED, vformat("Cannot toggle expansion of a non-state entry! (%d)", toggle_index));
+
+ int32_t provinces = 0;
+
+ while (++toggle_index < population_menu.province_list_entries.size()
+ && std::holds_alternative<population_menu_t::province_entry_t>(population_menu.province_list_entries[toggle_index])) {
+ provinces++;
+ }
+
+ if (state_entry->expanded) {
+ state_entry->expanded = false;
+ population_menu.visible_province_list_entries -= provinces;
+ } else {
+ state_entry->expanded = true;
+ population_menu.visible_province_list_entries += provinces;
+ }
+
+ emit_signal(_signal_population_menu_province_list_changed());
+
+ if (emit_selected_changed) {
+ emit_signal(_signal_population_menu_province_list_selected_changed(), -1);
+ }
+
+ return OK;
+}
+
+void MenuSingleton::_population_menu_update_pops() {
+ for (auto [pop_type, filter] : mutable_iterator(population_menu.pop_filters)) {
+ filter.count = 0;
+ filter.promotion_demotion_change = 0;
+ }
+
+ population_menu.pops.clear();
+
+ for (int32_t index = 0; index < population_menu.province_list_entries.size(); index++) {
+ population_menu_t::province_entry_t const* province_entry =
+ std::get_if<population_menu_t::province_entry_t>(&population_menu.province_list_entries[index]);
+
+ if (province_entry != nullptr && province_entry->selected) {
+ for (Pop const& pop : province_entry->province.get_pops()) {
+ population_menu.pops.push_back(&pop);
+ population_menu_t::pop_filter_t& filter = population_menu.pop_filters[&pop.get_type()];
+ filter.count += pop.get_size();
+ // TODO - set filter.promotion_demotion_change
+ }
+ }
+ }
+
+ _population_menu_update_filtered_pops();
+}
+
+void MenuSingleton::_population_menu_update_filtered_pops() {
+ population_menu.filtered_pops.clear();
+
+ for (fixed_point_map_t<HasIdentifierAndColour const*>& distribution : population_menu.distributions) {
+ distribution.clear();
+ }
+
+ for (Pop const* pop : population_menu.pops) {
+ if (population_menu.pop_filters[&pop->get_type()].selected) {
+ population_menu.filtered_pops.push_back(pop);
+ }
+ }
+
+ for (Pop const* pop : population_menu.filtered_pops) {
+ population_menu.distributions[0][&pop->get_type()] += pop->get_size();
+ population_menu.distributions[1][&pop->get_religion()] += pop->get_size();
+ population_menu.distributions[2] +=
+ cast_map<HasIdentifierAndColour>(pop->get_ideologies() * static_cast<int32_t>(pop->get_size()));
+ population_menu.distributions[3][&pop->get_culture()] += pop->get_size();
+ population_menu.distributions[4] +=
+ cast_map<HasIdentifierAndColour>(pop->get_issues() * static_cast<int32_t>(pop->get_size()));
+ population_menu.distributions[5] +=
+ cast_map<HasIdentifierAndColour>(pop->get_votes() * static_cast<int32_t>(pop->get_size()));
+ }
+
+ for (fixed_point_map_t<HasIdentifierAndColour const*>& distribution : population_menu.distributions) {
+ normalise_fixed_point_map(distribution);
+ }
+
+ _population_menu_sort_pops();
+}
+
+MenuSingleton::sort_func_t MenuSingleton::_get_population_menu_sort_func(population_menu_t::PopSortKey sort_key) const {
+ using enum population_menu_t::PopSortKey;
+ switch (sort_key) {
+ case SORT_SIZE:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_size() < b->get_size();
+ };
+ case SORT_TYPE:
+ return [this](Pop const* a, Pop const* b) -> bool {
+ return tr(std_view_to_godot_string(a->get_type().get_identifier()))
+ < tr(std_view_to_godot_string(b->get_type().get_identifier()));
+ };
+ case SORT_CULTURE:
+ return [this](Pop const* a, Pop const* b) -> bool {
+ return tr(std_view_to_godot_string(a->get_culture().get_identifier()))
+ < tr(std_view_to_godot_string(b->get_culture().get_identifier()));
+ };
+ case SORT_RELIGION:
+ return [this](Pop const* a, Pop const* b) -> bool {
+ return tr(std_view_to_godot_string(a->get_religion().get_identifier()))
+ < tr(std_view_to_godot_string(b->get_religion().get_identifier()));
+ };
+ case SORT_LOCATION:
+ return [this](Pop const* a, Pop const* b) -> bool {
+ return tr(a->get_location() != nullptr ? std_view_to_godot_string(a->get_location()->get_identifier()) : String {})
+ < tr(b->get_location() != nullptr ? std_view_to_godot_string(b->get_location()->get_identifier()) : String {});
+ };
+ case SORT_MILITANCY:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_militancy() < b->get_militancy();
+ };
+ case SORT_CONSCIOUSNESS:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_consciousness() < b->get_consciousness();
+ };
+ case SORT_IDEOLOGY:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return sorted_fixed_map_less_than(a->get_ideologies(), b->get_ideologies());
+ };
+ case SORT_ISSUES:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return sorted_fixed_map_less_than(a->get_issues(), b->get_issues());
+ };
+ case SORT_UNEMPLOYMENT:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_unemployment() < b->get_unemployment();
+ };
+ case SORT_CASH:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_cash() < b->get_cash();
+ };
+ case SORT_LIFE_NEEDS:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_life_needs_fulfilled() < b->get_life_needs_fulfilled();
+ };
+ case SORT_EVERYDAY_NEEDS:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_everyday_needs_fulfilled() < b->get_everyday_needs_fulfilled();
+ };
+ case SORT_LUXURY_NEEDS:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_luxury_needs_fulfilled() < b->get_luxury_needs_fulfilled();
+ };
+ case SORT_REBEL_FACTION:
+ return [](Pop const* a, Pop const* b) -> bool { return false; }; // TODO - implement
+ case SORT_SIZE_CHANGE:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_total_change() < b->get_total_change();
+ };
+ case SORT_LITERACY:
+ return [](Pop const* a, Pop const* b) -> bool {
+ return a->get_literacy() < b->get_literacy();
+ };
+ default:
+ UtilityFunctions::push_error("Invalid population menu sort key: ", sort_key);
+ return [](Pop const* a, Pop const* b) -> bool { return false; };
+ }
+}
+
+void MenuSingleton::_population_menu_sort_pops() {
+ if (population_menu.sort_key != population_menu_t::NONE) {
+ const sort_func_t base_sort_func = _get_population_menu_sort_func(population_menu.sort_key);
+
+ const sort_func_t sort_func = population_menu.sort_descending
+ ? base_sort_func
+ : [base_sort_func](Pop const* a, Pop const* b) { return base_sort_func(b, a); };
+
+ std::sort(population_menu.filtered_pops.begin(), population_menu.filtered_pops.end(), sort_func);
+ }
+
+ emit_signal(_signal_population_menu_pops_changed());
+}
+
+Error MenuSingleton::population_menu_select_sort_key(population_menu_t::PopSortKey sort_key) {
+ using enum population_menu_t::PopSortKey;
+ /* sort_key must be cast here to avoid causing clang to segfault during compilation. */
+ ERR_FAIL_INDEX_V_MSG(
+ static_cast<int32_t>(sort_key), static_cast<int32_t>(MAX_SORT_KEY), FAILED,
+ vformat("Invalid population menu sort key: %d (must be under %d)", sort_key, MAX_SORT_KEY)
+ );
+
+ if (sort_key == population_menu.sort_key) {
+ /* Re-selecting the current sort key reverses sort order. */
+ population_menu.sort_descending = !population_menu.sort_descending;
+ } else {
+ /* Selecting a new sort key switches sorting to that key, preserving the existing sort order. */
+ population_menu.sort_key = sort_key;
+ }
+
+ _population_menu_sort_pops();
+
+ return OK;
+}
+
+TypedArray<Dictionary> MenuSingleton::get_population_menu_pop_rows(int32_t start, int32_t count) const {
+ if (population_menu.filtered_pops.empty()) {
+ return {};
+ }
+ ERR_FAIL_INDEX_V_MSG(
+ start, population_menu.filtered_pops.size(), {}, vformat("Invalid start for population menu pop rows: %d", start)
+ );
+ ERR_FAIL_COND_V_MSG(count <= 0, {}, vformat("Invalid count for population menu pop rows: %d", count));
+
+ if (start + count > population_menu.filtered_pops.size()) {
+ count = population_menu.filtered_pops.size() - start;
+ }
+
+ static const StringName pop_size_key = "size";
+
+ static const StringName pop_type_icon_key = "pop_type_icon";
+ // TODO - pop type name
+ // TODO - promotions (target pop type and count)
+ // TODO - demotions (target pop type and count)
+ // TODO - good being produced (artisans, farmers, labourers, slaves)
+ // TODO - military unit and army (soldiers)
+
+ static const StringName pop_culture_key = "culture";
+ // TODO - cultural assimilation (primary/accepted, or number, target culture, and conditional weights breakdown)
+
+ static const StringName pop_religion_icon_key = "religion_icon";
+ // TODO - religion name
+ // TODO - religious conversion (accepted, or number, target religion, and conditional weights breakdown)
+
+ static const StringName pop_location_key = "location";
+ // TODO - internal, external and colonial migration
+
+ static const StringName pop_militancy_key = "militancy";
+ // TODO - monthly militancy change and modifier breakdown
+
+ static const StringName pop_consciousness_key = "consciousness";
+ // TODO - monthly consciousness change and modifier breakdown
+
+ static const StringName pop_ideology_key = "ideology";
+
+ static const StringName pop_issues_key = "issues";
+
+ static const StringName pop_unemployment_key = "unemployment";
+
+ static const StringName pop_cash_key = "cash";
+ // TODO - daily income, needs, salary and savings
+
+ static const StringName pop_life_needs_key = "life_needs";
+ static const StringName pop_everyday_needs_key = "everyday_needs";
+ static const StringName pop_luxury_needs_key = "luxury_needs";
+ // TODO - goods not available on market or goods not affordale + price (for all 3 needs types)
+
+ // TODO - rebel faction icon and name/description
+
+ static const StringName pop_size_change_key = "size_change";
+ // TODO - size change breakdown
+
+ static const StringName pop_literacy_key = "literacy";
+ // TODO - monthly change
+
+ TypedArray<Dictionary> array;
+ ERR_FAIL_COND_V(array.resize(count) != OK, {});
+
+ for (int32_t idx = 0; idx < count; ++idx) {
+ Pop const* pop = population_menu.filtered_pops[start + idx];
+ Dictionary pop_dict;
+
+ pop_dict[pop_size_key] = pop->get_size();
+ pop_dict[pop_type_icon_key] = pop->get_type().get_sprite();
+ pop_dict[pop_culture_key] = std_view_to_godot_string(pop->get_culture().get_identifier());
+ pop_dict[pop_religion_icon_key] = pop->get_religion().get_icon();
+ pop_dict[pop_location_key] =
+ pop->get_location() != nullptr ? std_view_to_godot_string(pop->get_location()->get_identifier()) : String {};
+ pop_dict[pop_militancy_key] = pop->get_militancy().to_float();
+ pop_dict[pop_consciousness_key] = pop->get_consciousness().to_float();
+ pop_dict[pop_ideology_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_ideologies());
+ pop_dict[pop_issues_key] = GFXPieChartTexture::distribution_to_slices_array(pop->get_issues());
+ pop_dict[pop_unemployment_key] = pop->get_unemployment().to_float();
+ pop_dict[pop_cash_key] = pop->get_cash().to_float();
+ pop_dict[pop_life_needs_key] = pop->get_life_needs_fulfilled().to_float();
+ pop_dict[pop_everyday_needs_key] = pop->get_everyday_needs_fulfilled().to_float();
+ pop_dict[pop_luxury_needs_key] = pop->get_luxury_needs_fulfilled().to_float();
+ pop_dict[pop_size_change_key] = pop->get_total_change();
+ pop_dict[pop_literacy_key] = pop->get_literacy().to_float();
+
+ array[idx] = std::move(pop_dict);
+ }
+
+ return array;
+}
+
+int32_t MenuSingleton::get_population_menu_pop_row_count() const {
+ return population_menu.filtered_pops.size();
+}
+
+PackedInt32Array MenuSingleton::get_population_menu_pop_filter_setup_info() {
+ ERR_FAIL_NULL_V(game_manager, {});
+
+ if (population_menu.pop_filters.empty()) {
+ for (PopType const& pop_type : game_manager->get_pop_manager().get_pop_types()) {
+ population_menu.pop_filters.emplace(&pop_type, population_menu_t::pop_filter_t { 0, 0, true });
+ }
+ }
+ ERR_FAIL_COND_V_MSG(population_menu.pop_filters.empty(), {}, "Failed to generate population menu pop filters!");
+
+ PackedInt32Array array;
+ ERR_FAIL_COND_V(array.resize(population_menu.pop_filters.size()) != OK, {});
+
+ for (int32_t idx = 0; idx < array.size(); ++idx) {
+ array[idx] = population_menu.pop_filters.data()[idx].first->get_sprite();
+ }
+
+ return array;
+}
+
+TypedArray<Dictionary> MenuSingleton::get_population_menu_pop_filter_info() const {
+ static const StringName pop_filter_count_key = "count";
+ static const StringName pop_filter_change_key = "change";
+ static const StringName pop_filter_selected_key = "selected";
+
+ TypedArray<Dictionary> array;
+ ERR_FAIL_COND_V(array.resize(population_menu.pop_filters.size()) != OK, {});
+
+ for (int32_t idx = 0; idx < array.size(); ++idx) {
+ population_menu_t::pop_filter_t const& filter = population_menu.pop_filters.data()[idx].second;
+
+ Dictionary filter_dict;
+
+ filter_dict[pop_filter_count_key] = filter.count;
+ filter_dict[pop_filter_change_key] = filter.promotion_demotion_change;
+ filter_dict[pop_filter_selected_key] = filter.selected;
+
+ array[idx] = std::move(filter_dict);
+ }
+
+ return array;
+}
+
+Error MenuSingleton::population_menu_toggle_pop_filter(int32_t index) {
+ ERR_FAIL_COND_V_MSG(
+ index < 0 || index >= population_menu.pop_filters.size(), FAILED, vformat("Invalid pop filter index: %d", index)
+ );
+
+ population_menu_t::pop_filter_t& filter = mutable_iterator(population_menu.pop_filters).begin()[index].second;
+ filter.selected = !filter.selected;
+
+ _population_menu_update_filtered_pops();
+
+ return OK;
+}
+
+void MenuSingleton::population_menu_select_all_pop_filters() {
+ bool changed = false;
+
+ for (auto [pop_type, filter] : mutable_iterator(population_menu.pop_filters)) {
+ if (!filter.selected) {
+ filter.selected = true;
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ _population_menu_update_filtered_pops();
+ }
+}
+
+void MenuSingleton::population_menu_deselect_all_pop_filters() {
+ bool changed = false;
+ for (auto [pop_type, filter] : mutable_iterator(population_menu.pop_filters)) {
+ if (filter.selected) {
+ filter.selected = false;
+ changed = true;
+ }
+ }
+ if (changed) {
+ _population_menu_update_filtered_pops();
+ }
+}
+
+PackedStringArray MenuSingleton::get_population_menu_distribution_setup_info() const {
+ static const PackedStringArray distribution_names = []() -> PackedStringArray {
+ constexpr std::array<char const*, population_menu_t::DISTRIBUTION_COUNT> NAMES {
+ /* Workforce (PopType) */ "WORKFORCE_DISTTITLE",
+ /* Religion */ "RELIGION_DISTTITLE",
+ /* Ideology */ "IDEOLOGY_DISTTITLE",
+ /* Nationality (Culture) */ "NATIONALITY_DISTTITLE",
+ /* Issues */ "DOMINANT_ISSUES_DISTTITLE",
+ /* Vote */ "ELECTORATE_DISTTITLE"
+ };
+
+ PackedStringArray array;
+ ERR_FAIL_COND_V(array.resize(NAMES.size()) != OK, {});
+
+ for (int32_t idx = 0; idx < array.size(); ++idx) {
+ array[idx] = NAMES[idx];
+ }
+
+ return array;
+ }();
+
+ return distribution_names;
+}
+
+TypedArray<Array> MenuSingleton::get_population_menu_distribution_info() const {
+ TypedArray<Array> array;
+ ERR_FAIL_COND_V(array.resize(population_menu.distributions.size()) != OK, {});
+
+ for (int32_t idx = 0; idx < array.size(); ++idx) {
+ array[idx] = GFXPieChartTexture::distribution_to_slices_array(population_menu.distributions[idx]);
+ }
+
+ return array;
+}
diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp
index 4af2b74..510c6da 100644
--- a/extension/src/openvic-extension/utility/UITools.cpp
+++ b/extension/src/openvic-extension/utility/UITools.cpp
@@ -187,10 +187,12 @@ static bool generate_icon(generate_gui_args_t&& args) {
GFX::ProgressBar const* progress_bar = icon.get_sprite()->cast_to<GFX::ProgressBar>();
+ using enum AssetManager::LoadFlags;
+
Ref<ImageTexture> back_texture;
if (!progress_bar->get_back_texture_file().empty()) {
const StringName back_texture_file = std_view_to_godot_string_name(progress_bar->get_back_texture_file());
- back_texture = args.asset_manager.get_texture(back_texture_file, true);
+ back_texture = args.asset_manager.get_texture(back_texture_file, LOAD_FLAG_CACHE_TEXTURE | LOAD_FLAG_FLIP_Y);
if (back_texture.is_null()) {
UtilityFunctions::push_error(
"Failed to load progress bar sprite back texture ", back_texture_file, " for GUI icon ", icon_name
@@ -221,11 +223,14 @@ static bool generate_icon(generate_gui_args_t&& args) {
Ref<ImageTexture> progress_texture;
if (!progress_bar->get_progress_texture_file().empty()) {
- const StringName progress_texture_file = std_view_to_godot_string_name(progress_bar->get_progress_texture_file());
- progress_texture = args.asset_manager.get_texture(progress_texture_file, true);
+ const StringName progress_texture_file =
+ std_view_to_godot_string_name(progress_bar->get_progress_texture_file());
+ progress_texture =
+ args.asset_manager.get_texture(progress_texture_file, LOAD_FLAG_CACHE_TEXTURE | LOAD_FLAG_FLIP_Y);
if (progress_texture.is_null()) {
UtilityFunctions::push_error(
- "Failed to load progress bar sprite progress texture ", progress_texture_file, " for GUI icon ", icon_name
+ "Failed to load progress bar sprite progress texture ", progress_texture_file, " for GUI icon ",
+ icon_name
);
ret = false;
}
@@ -237,7 +242,8 @@ static bool generate_icon(generate_gui_args_t&& args) {
);
if (progress_texture.is_null()) {
UtilityFunctions::push_error(
- "Failed to generate progress bar sprite ", progress_colour, " progress texture for GUI icon ", icon_name
+ "Failed to generate progress bar sprite ", progress_colour, " progress texture for GUI icon ",
+ icon_name
);
ret = false;
}
@@ -252,7 +258,9 @@ static bool generate_icon(generate_gui_args_t&& args) {
}
// TODO - work out why progress bar is missing bottom border pixel (e.g. province building expansion bar)
- godot_progress_bar->set_custom_minimum_size(Utilities::to_godot_fvec2(static_cast<fvec2_t>(progress_bar->get_size())));
+ godot_progress_bar->set_custom_minimum_size(
+ Utilities::to_godot_fvec2(static_cast<fvec2_t>(progress_bar->get_size()))
+ );
args.result = godot_progress_bar;
} else if (icon.get_sprite()->is_type<GFX::PieChart>()) {
@@ -286,7 +294,9 @@ static bool generate_icon(generate_gui_args_t&& args) {
const float rotation = icon.get_rotation();
if (rotation != 0.0f) {
args.result->set_position(
- args.result->get_position() - args.result->get_custom_minimum_size().height * Vector2 { sin(rotation), cos(rotation) - 1.0f }
+ args.result->get_position() - args.result->get_custom_minimum_size().height * Vector2 {
+ sin(rotation), cos(rotation) - 1.0f
+ }
);
args.result->set_rotation(-rotation);
}
diff --git a/extension/src/openvic-extension/utility/UITools.hpp b/extension/src/openvic-extension/utility/UITools.hpp
index 6092853..566318a 100644
--- a/extension/src/openvic-extension/utility/UITools.hpp
+++ b/extension/src/openvic-extension/utility/UITools.hpp
@@ -2,7 +2,7 @@
#include <godot_cpp/classes/control.hpp>
-#include <openvic-simulation/interface/GFX.hpp>
+#include <openvic-simulation/interface/GFXSprite.hpp>
#include <openvic-simulation/interface/GUI.hpp>
namespace OpenVic::UITools {
diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp
index 7450212..4389e95 100644
--- a/extension/src/openvic-extension/utility/Utilities.cpp
+++ b/extension/src/openvic-extension/utility/Utilities.cpp
@@ -80,7 +80,8 @@ static Ref<Image> load_dds_image(String const& path) {
);
PackedByteArray pixels;
- pixels.resize(size);
+ ERR_FAIL_COND_V(pixels.resize(size) != OK, nullptr);
+
/* 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());
diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp
index f39be3e..f7a0d67 100644
--- a/extension/src/openvic-extension/utility/Utilities.hpp
+++ b/extension/src/openvic-extension/utility/Utilities.hpp
@@ -41,11 +41,15 @@ namespace OpenVic::Utilities {
return { colour.redf(), colour.greenf(), colour.bluef(), colour.alphaf() };
}
- _FORCE_INLINE_ godot::Vector2i to_godot_ivec2(ivec2_t vec) {
+ _FORCE_INLINE_ godot::Vector2i to_godot_ivec2(ivec2_t const& vec) {
return { vec.x, vec.y };
}
- _FORCE_INLINE_ godot::Vector2 to_godot_fvec2(fvec2_t vec) {
+ _FORCE_INLINE_ godot::Vector2 to_godot_fvec2(fvec2_t const& vec) {
+ return { vec.x, vec.y };
+ }
+
+ _FORCE_INLINE_ ivec2_t from_godot_ivec2(godot::Vector2i const& vec) {
return { vec.x, vec.y };
}
diff --git a/game/project.godot b/game/project.godot
index 9d005b0..ac627a3 100644
--- a/game/project.godot
+++ b/game/project.godot
@@ -58,6 +58,10 @@ window/per_pixel_transparency/allowed=true
enabled=PackedStringArray("res://addons/keychain/plugin.cfg", "res://addons/openvic-plugin/plugin.cfg")
+[filesystem]
+
+import/blender/enabled=false
+
[gui]
theme/custom="res://assets/graphics/theme/default_theme.tres"
diff --git a/game/src/Game/GameSession/MapText.gd b/game/src/Game/GameSession/MapText.gd
new file mode 100644
index 0000000..22eba10
--- /dev/null
+++ b/game/src/Game/GameSession/MapText.gd
@@ -0,0 +1,50 @@
+class_name MapText
+extends Node3D
+
+@export var _map_view : MapView
+
+var _province_name_font : Font
+
+const _province_name_scale : float = 1.0 / 48.0
+
+func _ready() -> void:
+ _province_name_font = AssetManager.get_font(&"mapfont_56")
+
+func _clear_children() -> void:
+ var child_count : int = get_child_count()
+ while child_count > 0:
+ child_count -= 1
+ remove_child(get_child(child_count))
+
+func generate_map_names() -> void:
+ _clear_children()
+
+ for dict : Dictionary in GameSingleton.get_province_names():
+ _add_province_name(dict)
+
+func _add_province_name(dict : Dictionary) -> void:
+ const identifier_key : StringName = &"identifier"
+ const position_key : StringName = &"position"
+ const rotation_key : StringName = &"rotation"
+ const scale_key : StringName = &"scale"
+
+ var label : Label3D = Label3D.new()
+
+ label.set_draw_flag(Label3D.FLAG_DOUBLE_SIDED, false)
+ label.set_modulate(Color.BLACK)
+ label.set_outline_size(0)
+ label.set_font(_province_name_font)
+ label.set_vertical_alignment(VERTICAL_ALIGNMENT_BOTTOM)
+
+ var identifier : String = dict[identifier_key]
+ label.set_name(identifier)
+ label.set_text(GUINode.format_province_name(identifier))
+
+ label.set_position(_map_view._map_to_world_coords(dict[position_key]) + Vector3(0, 0.001, 0))
+
+ label.rotate_x(-PI / 2)
+ label.rotate_y(dict.get(rotation_key, 0.0))
+
+ label.scale *= dict.get(scale_key, 1.0) * _province_name_scale
+
+ add_child(label)
diff --git a/game/src/Game/GameSession/MapView.gd b/game/src/Game/GameSession/MapView.gd
index f522bcb..2ab7c34 100644
--- a/game/src/Game/GameSession/MapView.gd
+++ b/game/src/Game/GameSession/MapView.gd
@@ -1,6 +1,9 @@
+class_name MapView
extends Node3D
signal map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2)
+signal parchment_view_changed(is_parchment_view : bool)
+signal detailed_view_changed(is_detailed_view : bool)
const _action_north : StringName = &"map_north"
const _action_east : StringName = &"map_east"
@@ -22,9 +25,9 @@ var _drag_active : bool = false
var _mouse_over_viewport : bool = true
var _window_in_focus : bool = true
-@export var _zoom_target_min : float = 0.15
+@export var _zoom_target_min : float = 0.10
@export var _zoom_target_max : float = 5.0
-@export var _zoom_target_step : float = (_zoom_target_max - _zoom_target_min) / 64.0
+@export var _zoom_target_step : float = (_zoom_target_max - _zoom_target_min) / 40.0
@export var _zoom_epsilon : float = _zoom_target_step * 0.005
@export var _zoom_speed : float = 5.0
# _zoom_target's starting value is ignored as it is updated to the camera's height by _ready,
@@ -35,8 +38,13 @@ var _zoom_target : float = _zoom_target_max:
const _zoom_position_multiplier = 3.14159 # Horizontal movement coefficient during zoom
var _zoom_position : Vector2
-# Display the detailed terrain map below this height, and the parchment map above it
+# Display the parchment map above this height
@export var _zoom_parchment_threshold : float = _zoom_target_min + (_zoom_target_max - _zoom_target_min) / 4
+# Display details like models and province names below this height
+@export var _zoom_detailed_threshold : float = _zoom_parchment_threshold / 2
+
+var _is_parchment_view : bool = false
+var _is_detailed_view : bool = false
@export var _map_mesh_instance : MeshInstance3D
var _map_mesh : MapMesh
@@ -50,6 +58,8 @@ var _mouse_pos_viewport : Vector2 = Vector2(0.5, 0.5)
var _mouse_pos_map : Vector2 = Vector2(0.5, 0.5)
var _viewport_dims : Vector2 = Vector2(1, 1)
+@export var _map_text : MapText
+
# ??? Strange Godot/GDExtension Bug ???
# Upon first opening a clone of this repo with the Godot Editor,
# if GameSingleton.get_province_index_image is called before MapMesh
@@ -61,7 +71,12 @@ func _ready() -> void:
if not _camera:
push_error("MapView's _camera variable hasn't been set!")
return
+
+ # Start just under the parchment threshold
+ _camera.position.y = _zoom_parchment_threshold - _zoom_target_step
_zoom_target = _camera.position.y
+ _update_view_states(true)
+
if not _map_mesh_instance:
push_error("MapView's _map_mesh_instance variable hasn't been set!")
return
@@ -105,6 +120,8 @@ func _ready() -> void:
scaled_dims.z *= 2.0
(_map_background_instance.mesh as PlaneMesh).set_size(Vector2(scaled_dims.x, scaled_dims.z))
+ _map_text.generate_map_names()
+
func _notification(what : int) -> void:
match what:
NOTIFICATION_WM_MOUSE_ENTER: # Mouse inside window
@@ -119,6 +136,10 @@ func _notification(what : int) -> void:
func _world_to_map_coords(pos : Vector3) -> Vector2:
return (Vector2(pos.x, pos.z) - _map_mesh_corner) / _map_mesh_dims
+func _map_to_world_coords(pos : Vector2) -> Vector3:
+ pos = pos * _map_mesh_dims + _map_mesh_corner
+ return Vector3(pos.x, 0, pos.y)
+
func _viewport_to_map_coords(pos_viewport : Vector2) -> Vector2:
var ray_origin := _camera.project_ray_origin(pos_viewport)
var ray_normal := _camera.project_ray_normal(pos_viewport)
@@ -149,12 +170,13 @@ func _on_province_selected(index : int) -> void:
# REQUIREMENTS
# * SS-31
func _unhandled_input(event : InputEvent) -> void:
- if _mouse_over_viewport and event.is_action_pressed(_action_click):
- # Check if the mouse is outside of bounds
- if _map_mesh.is_valid_uv_coord(_mouse_pos_map):
- GameSingleton.set_selected_province(GameSingleton.get_province_index_from_uv_coords(_mouse_pos_map))
- else:
- print("Clicked outside the map!")
+ if event.is_action_pressed(_action_click):
+ if _mouse_over_viewport:
+ # Check if the mouse is outside of bounds
+ if _map_mesh.is_valid_uv_coord(_mouse_pos_map):
+ GameSingleton.set_selected_province(GameSingleton.get_province_index_from_uv_coords(_mouse_pos_map))
+ else:
+ print("Clicked outside the map!")
elif event.is_action_pressed(_action_drag):
if _drag_active:
push_warning("Drag being activated while already active!")
@@ -221,6 +243,17 @@ func _clamp_over_map() -> void:
_camera.position.x = _map_mesh_corner.x + fposmod(_camera.position.x - _map_mesh_corner.x, _map_mesh_dims.x)
_camera.position.z = clamp(_camera.position.z, _map_mesh_corner.y, _map_mesh_corner.y + _map_mesh_dims.y)
+func _update_view_states(force_signal : bool) -> void:
+ var new_is_parchment_view : bool = _camera.position.y >= _zoom_parchment_threshold - _zoom_epsilon
+ if force_signal or new_is_parchment_view != _is_parchment_view:
+ _is_parchment_view = new_is_parchment_view
+ parchment_view_changed.emit(_is_parchment_view)
+
+ var new_is_detailed_view : bool = _camera.position.y <= _zoom_detailed_threshold + _zoom_epsilon
+ if force_signal or new_is_detailed_view != _is_detailed_view:
+ _is_detailed_view = new_is_detailed_view
+ detailed_view_changed.emit(_is_detailed_view)
+
# REQUIREMENTS
# * SS-74
# * UIFUN-123
@@ -237,12 +270,17 @@ func _zoom_process(delta : float) -> void:
_zoom_position.y * zoom_delta * int(_mouse_over_viewport)
)
# TODO - smooth transition similar to smooth zoom
- var parchment_mapmode : bool = GameSingleton.is_parchment_mapmode_allowed() and _camera.position.y > _zoom_parchment_threshold
+ _update_view_states(false)
+ var parchment_mapmode : bool = GameSingleton.is_parchment_mapmode_allowed() and _is_parchment_view
_map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_parchment_mix, float(parchment_mapmode))
func _update_orientation() -> void:
const up := Vector3(0, 0, -1)
- var dir := Vector3(0, -1, -1.25 * exp(-10 * _camera.position.y - _zoom_target_min))
+ var dir := Vector3(0, -1, 0)
+ if _is_detailed_view:
+ # Zero at the transition point, increases as you zoom further in
+ var delta : float = (_zoom_detailed_threshold - _camera.position.y) / _zoom_detailed_threshold
+ dir.z = -(delta ** 4)
_camera.look_at(_camera.position + dir, up)
func _update_minimap_viewport() -> void:
@@ -253,9 +291,9 @@ func _update_minimap_viewport() -> void:
map_view_camera_changed.emit(near_left, far_left, far_right, near_right)
func _update_mouse_map_position() -> void:
- _mouse_pos_map = _viewport_to_map_coords(_mouse_pos_viewport)
- var hover_index := GameSingleton.get_province_index_from_uv_coords(_mouse_pos_map)
if _mouse_over_viewport:
+ _mouse_pos_map = _viewport_to_map_coords(_mouse_pos_viewport)
+ var hover_index := GameSingleton.get_province_index_from_uv_coords(_mouse_pos_map)
_map_shader_material.set_shader_parameter(GameLoader.ShaderManager.param_hover_index, hover_index)
func _on_mouse_entered_viewport() -> void:
diff --git a/game/src/Game/GameSession/MapView.tscn b/game/src/Game/GameSession/MapView.tscn
index bf22ef8..dff02a6 100644
--- a/game/src/Game/GameSession/MapView.tscn
+++ b/game/src/Game/GameSession/MapView.tscn
@@ -1,7 +1,8 @@
-[gd_scene load_steps=7 format=3 uid="uid://dkehmdnuxih2r"]
+[gd_scene load_steps=8 format=3 uid="uid://dkehmdnuxih2r"]
[ext_resource type="Script" path="res://src/Game/GameSession/MapView.gd" id="1_exccw"]
[ext_resource type="Shader" path="res://src/Game/GameSession/TerrainMap.gdshader" id="1_upocn"]
+[ext_resource type="Script" path="res://src/Game/GameSession/MapText.gd" id="2_13bgq"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_tayeg"]
render_priority = 0
@@ -23,17 +24,22 @@ albedo_color = Color(0, 0, 0, 1)
material = SubResource("StandardMaterial3D_irk50")
size = Vector2(6, 2)
-[node name="MapView" type="Node3D" node_paths=PackedStringArray("_camera", "_map_mesh_instance", "_map_background_instance")]
+[node name="MapView" type="Node3D" node_paths=PackedStringArray("_camera", "_map_mesh_instance", "_map_background_instance", "_map_text")]
editor_description = "SS-73"
script = ExtResource("1_exccw")
_camera = NodePath("MapCamera")
_map_mesh_instance = NodePath("MapMeshInstance")
_map_background_instance = NodePath("MapBackgroundInstance")
+_map_text = NodePath("MapText")
[node name="MapCamera" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0.25, 1.5, -2.75)
near = 0.01
+[node name="MapText" type="Node3D" parent="." node_paths=PackedStringArray("_map_view")]
+script = ExtResource("2_13bgq")
+_map_view = NodePath("..")
+
[node name="MapMeshInstance" type="MeshInstance3D" parent="."]
editor_description = "FS-343"
transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0)
@@ -43,3 +49,5 @@ mesh = SubResource("MapMesh_3gtsd")
[node name="MapBackgroundInstance" type="MeshInstance3D" parent="."]
transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, -1, 0)
mesh = SubResource("PlaneMesh_fnhgl")
+
+[connection signal="detailed_view_changed" from="." to="MapText" method="set_visible"]
diff --git a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd
index 29bd56b..5de2d25 100644
--- a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd
+++ b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd
@@ -4,19 +4,371 @@ var _active : bool = false
const _screen : NationManagement.Screen = NationManagement.Screen.POPULATION
+const _scene_name : String = "country_pops"
+
+var _pop_screen_panel : Panel
+
+var _province_listbox : GUIListBox
+var _province_list_scroll_index : int = 0
+var _province_list_types : Array[MenuSingleton.ProvinceListEntry]
+var _province_list_indices : PackedInt32Array
+var _province_list_panels : Array[Panel]
+var _province_list_button_icons : Array[GFXSpriteTexture]
+var _province_list_name_labels : Array[Label]
+var _province_list_size_labels : Array[Label]
+var _province_list_growth_icons : Array[GFXSpriteTexture]
+var _province_list_colony_buttons : Array[Button]
+var _province_list_national_focus_icons : Array[GFXSpriteTexture]
+var _province_list_expand_icons : Array[GFXSpriteTexture]
+
+var _pop_filter_buttons : Array[Button]
+var _pop_filter_icons : Array[GFXSpriteTexture]
+var _pop_filter_selected_icons : Array[GFXButtonStateTexture]
+var _pop_filter_hover_icons : Array[GFXButtonStateTexture]
+
+var _distribution_charts : Array[GFXPieChartTexture]
+var _distribution_lists : Array[GUIListBox]
+
+var _pop_list_scrollbar : GUIScrollbar
+var _pop_list_scroll_index : int = 0
+
+var _pop_list_rows : Array[Panel]
+var _pop_list_size_labels : Array[Label]
+var _pop_list_type_buttons : Array[Button]
+var _pop_list_type_icons : Array[GFXSpriteTexture]
+var _pop_list_producing_icons : Array[GFXSpriteTexture]
+var _pop_list_culture_labels : Array[Label]
+var _pop_list_religion_icons : Array[GFXSpriteTexture]
+var _pop_list_location_labels : Array[Label]
+var _pop_list_militancy_labels : Array[Label]
+var _pop_list_consciousness_labels : Array[Label]
+var _pop_list_ideology_charts : Array[GFXPieChartTexture]
+var _pop_list_issues_charts : Array[GFXPieChartTexture]
+var _pop_list_unemployment_progressbars : Array[TextureProgressBar]
+var _pop_list_cash_labels : Array[Label]
+var _pop_list_life_needs_progressbars : Array[TextureProgressBar]
+var _pop_list_everyday_needs_progressbars : Array[TextureProgressBar]
+var _pop_list_luxury_needs_progressbars : Array[TextureProgressBar]
+var _pop_list_rebel_icons : Array[GFXSpriteTexture]
+var _pop_list_social_movement_icons : Array[GFXSpriteTexture]
+var _pop_list_political_movement_icons : Array[GFXSpriteTexture]
+var _pop_list_national_movement_flags : Array[GFXMaskedFlagTexture]
+var _pop_list_size_change_icons : Array[GFXSpriteTexture]
+var _pop_list_literacy_labels : Array[Label]
+
func _ready() -> void:
GameSingleton.gamestate_updated.connect(_update_info)
+ MenuSingleton.population_menu_province_list_changed.connect(_setup_province_list)
+ MenuSingleton.population_menu_province_list_selected_changed.connect(_update_province_list)
+ MenuSingleton.population_menu_pops_changed.connect(_update_pops)
Events.NationManagementScreens.update_active_nation_management_screen.connect(_on_update_active_nation_management_screen)
- add_gui_element("country_pops", "country_pop")
+ add_gui_element(_scene_name, "country_pop")
var close_button : Button = get_button_from_nodepath(^"./country_pop/close_button")
if close_button:
close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen))
+ _pop_screen_panel = get_panel_from_nodepath(^"./country_pop")
+
+ # province list is set up via the population_menu_provinces_changed signal
+ _setup_sort_buttons()
+ _setup_pop_filter_buttons()
+ _setup_distribution_windows()
+ _setup_pop_list()
+
_update_info()
+func _generate_province_list_row(index : int, type : MenuSingleton.ProvinceListEntry) -> Error:
+ while _province_list_types.size() <= index:
+ _province_list_types.push_back(MenuSingleton.LIST_ENTRY_NONE)
+ _province_list_indices.push_back(-1)
+ _province_list_panels.push_back(null)
+ _province_list_button_icons.push_back(null)
+ _province_list_name_labels.push_back(null)
+ _province_list_size_labels.push_back(null)
+ _province_list_growth_icons.push_back(null)
+ _province_list_colony_buttons.push_back(null)
+ _province_list_national_focus_icons.push_back(null)
+ _province_list_expand_icons.push_back(null)
+
+ if _province_list_types[index] == type:
+ return OK
+
+ if _province_list_panels[index]:
+ _province_listbox.remove_child(_province_list_panels[index])
+
+ _province_list_types[index] = MenuSingleton.LIST_ENTRY_NONE
+ _province_list_indices[index] = -1
+ _province_list_panels[index] = null
+ _province_list_button_icons[index] = null
+ _province_list_name_labels[index] = null
+ _province_list_size_labels[index] = null
+ _province_list_growth_icons[index] = null
+ _province_list_colony_buttons[index] = null
+ _province_list_national_focus_icons[index] = null
+ _province_list_expand_icons[index] = null
+
+ if type == MenuSingleton.LIST_ENTRY_NONE:
+ return OK
+
+ const gui_element_names : Dictionary = {
+ MenuSingleton.LIST_ENTRY_COUNTRY: "poplistitem_country",
+ MenuSingleton.LIST_ENTRY_STATE: "poplistitem_state",
+ MenuSingleton.LIST_ENTRY_PROVINCE: "poplistitem_province"
+ }
+
+ var entry_panel : Panel = GUINode.generate_gui_element(_scene_name, gui_element_names[type])
+
+ if not entry_panel:
+ return FAILED
+
+ _province_list_types[index] = type
+
+ _province_list_panels[index] = entry_panel
+
+ var base_button : Button = GUINode.get_button_from_node(entry_panel.get_node(^"./poplistbutton"))
+ if base_button:
+ base_button.pressed.connect(
+ func() -> void: MenuSingleton.population_menu_select_province_list_entry(_province_list_indices[index])
+ )
+ _province_list_button_icons[index] = GUINode.get_gfx_sprite_texture_from_node(base_button)
+
+ _province_list_name_labels[index] = GUINode.get_label_from_node(entry_panel.get_node(^"./poplist_name"))
+
+ _province_list_size_labels[index] = GUINode.get_label_from_node(entry_panel.get_node(^"./poplist_numpops"))
+
+ _province_list_growth_icons[index] = GUINode.get_gfx_sprite_texture_from_node(entry_panel.get_node(^"./growth_indicator"))
+
+ if type == MenuSingleton.LIST_ENTRY_STATE:
+ _province_list_colony_buttons[index] = GUINode.get_button_from_node(entry_panel.get_node(^"./colonial_state_icon"))
+
+ var national_focus_button : Button = GUINode.get_button_from_node(entry_panel.get_node(^"./state_focus"))
+ if national_focus_button:
+ # TODO - connect national focus button to national focus selection submenu
+ _province_list_national_focus_icons[index] = GUINode.get_gfx_sprite_texture_from_node(national_focus_button)
+
+ var expand_button : Button = GUINode.get_button_from_node(entry_panel.get_node(^"./expand"))
+ if expand_button:
+ expand_button.pressed.connect(
+ func() -> void: MenuSingleton.population_menu_toggle_expanded(_province_list_indices[index])
+ )
+ _province_list_expand_icons[index] = GUINode.get_gfx_sprite_texture_from_node(expand_button)
+
+ _province_listbox.add_child(entry_panel)
+ _province_listbox.move_child(entry_panel, index)
+
+ return OK
+
+func _setup_province_list() -> void:
+ if not _province_listbox:
+ _province_listbox = get_gui_listbox_from_nodepath(^"./country_pop/pop_province_list")
+
+ if not _province_listbox:
+ return
+ _province_listbox.scroll_index_changed.connect(_update_province_list)
+
+ if _province_list_panels.size() < 1 or not _province_list_panels[0]:
+ if _generate_province_list_row(0, MenuSingleton.LIST_ENTRY_COUNTRY) != OK or _province_list_panels.size() < 1 or not _province_list_panels[0]:
+ push_error("Failed to generate country row in population menu province list to determine row height!")
+ return
+
+ _province_listbox.set_fixed(MenuSingleton.get_population_menu_province_list_row_count(), _province_list_panels[0].size.y, false)
+
+func _setup_sort_buttons() -> void:
+ # button_path : NodePath, clear_text : bool, sort_key : GameSingleton.PopSortKey
+ const sort_button_info : Array[Array] = [
+ [^"./country_pop/sortby_size_button", false, MenuSingleton.SORT_SIZE],
+ [^"./country_pop/sortby_type_button", false, MenuSingleton.SORT_TYPE],
+ [^"./country_pop/sortby_nationality_button", false, MenuSingleton.SORT_CULTURE],
+ [^"./country_pop/sortby_religion_button", false, MenuSingleton.SORT_RELIGION],
+ [^"./country_pop/sortby_location_button", false, MenuSingleton.SORT_LOCATION],
+ [^"./country_pop/sortby_mil_button", true, MenuSingleton.SORT_MILITANCY],
+ [^"./country_pop/sortby_con_button", true, MenuSingleton.SORT_CONSCIOUSNESS],
+ [^"./country_pop/sortby_ideology_button", true, MenuSingleton.SORT_IDEOLOGY],
+ [^"./country_pop/sortby_issues_button", true, MenuSingleton.SORT_ISSUES],
+ [^"./country_pop/sortby_unemployment_button", true, MenuSingleton.SORT_UNEMPLOYMENT],
+ [^"./country_pop/sortby_cash_button", true, MenuSingleton.SORT_CASH],
+ [^"./country_pop/sortby_subsistence_button", true, MenuSingleton.SORT_LIFE_NEEDS],
+ [^"./country_pop/sortby_eve_button", true, MenuSingleton.SORT_EVERYDAY_NEEDS],
+ [^"./country_pop/sortby_luxury_button", true, MenuSingleton.SORT_LUXURY_NEEDS],
+ [^"./country_pop/sortby_revoltrisk_button", true, MenuSingleton.SORT_REBEL_FACTION],
+ [^"./country_pop/sortby_change_button", true, MenuSingleton.SORT_SIZE_CHANGE],
+ [^"./country_pop/sortby_literacy_button", true, MenuSingleton.SORT_LITERACY]
+ ]
+
+ for button_info : Array in sort_button_info:
+ var sort_button : Button = get_button_from_nodepath(button_info[0])
+ if sort_button:
+ if button_info[1]:
+ sort_button.set_text("")
+ sort_button.pressed.connect(MenuSingleton.population_menu_select_sort_key.bind(button_info[2]))
+
+func _setup_pop_filter_buttons() -> void:
+ if not _pop_screen_panel:
+ push_error("Cannot set up pop filter buttons without pop screen to add them to")
+ return
+
+ var pop_filter_sprite_indices : PackedInt32Array = MenuSingleton.get_population_menu_pop_filter_setup_info()
+
+ var pop_filter_start : Vector2 = GUINode.get_gui_position(_scene_name, "popfilter_start")
+ var pop_filter_step : Vector2 = GUINode.get_gui_position(_scene_name, "popfilter_offset")
+
+ for index : int in pop_filter_sprite_indices.size():
+ var pop_filter_button : Button = GUINode.get_button_from_node(GUINode.generate_gui_element(_scene_name, "pop_filter_button"))
+ var pop_filter_icon : GFXSpriteTexture = null
+ var pop_filter_selected_icon : GFXButtonStateTexture = null
+ var pop_filter_hover_icon : GFXButtonStateTexture = null
+
+ if pop_filter_button:
+ _pop_screen_panel.add_child(pop_filter_button)
+ pop_filter_button.set_position(pop_filter_start + pop_filter_step * index)
+ pop_filter_button.pressed.connect(MenuSingleton.population_menu_toggle_pop_filter.bind(index))
+ pop_filter_icon = GUINode.get_gfx_sprite_texture_from_node(pop_filter_button)
+
+ if pop_filter_icon:
+ pop_filter_icon.set_icon_index(pop_filter_sprite_indices[index])
+ pop_filter_selected_icon = pop_filter_icon.get_button_state_texture(GFXButtonStateTexture.SELECTED)
+ pop_filter_hover_icon = pop_filter_icon.get_button_state_texture(GFXButtonStateTexture.HOVER)
+
+ _pop_filter_buttons.push_back(pop_filter_button)
+ _pop_filter_icons.push_back(pop_filter_icon)
+ _pop_filter_selected_icons.push_back(pop_filter_selected_icon)
+ _pop_filter_hover_icons.push_back(pop_filter_hover_icon)
+
+ var select_all_button : Button = get_button_from_nodepath(^"./country_pop/popfilter_ALL")
+ if select_all_button:
+ select_all_button.pressed.connect(MenuSingleton.population_menu_select_all_pop_filters)
+
+ var deselect_all_button : Button = get_button_from_nodepath(^"./country_pop/popfilter_DESELECT_ALL")
+ if deselect_all_button:
+ deselect_all_button.pressed.connect(MenuSingleton.population_menu_deselect_all_pop_filters)
+
+func _setup_distribution_windows() -> void:
+ if not _pop_screen_panel:
+ push_error("Cannot set up distribution windows without pop screen to add them to")
+ return
+
+ const columns : int = 3
+
+ var distribution_names : PackedStringArray = MenuSingleton.get_population_menu_distribution_setup_info()
+
+ var distribution_start : Vector2 = GUINode.get_gui_position(_scene_name, "popdistribution_start")
+ var distribution_step : Vector2 = GUINode.get_gui_position(_scene_name, "popdistribution_offset")
+
+ for index : int in distribution_names.size():
+ var distribution_panel : Panel = GUINode.generate_gui_element(_scene_name, "distribution_window")
+ var distribution_chart : GFXPieChartTexture = null
+ var distribution_list : GUIListBox = null
+
+ if distribution_panel:
+ _pop_screen_panel.add_child(distribution_panel)
+ distribution_panel.set_position(distribution_start + distribution_step * Vector2(index % columns, index / columns))
+
+ var name_label : Label = GUINode.get_label_from_node(distribution_panel.get_node(^"./item_name"))
+ if name_label:
+ name_label.text = distribution_names[index]
+
+ distribution_chart = GUINode.get_gfx_pie_chart_texture_from_node(distribution_panel.get_node(^"./chart"))
+ distribution_list = GUINode.get_gui_listbox_from_node(distribution_panel.get_node(^"./member_names"))
+
+ _distribution_charts.push_back(distribution_chart)
+ _distribution_lists.push_back(distribution_list)
+
+func _setup_pop_list() -> void:
+ _pop_list_scrollbar = get_gui_scrollbar_from_nodepath(^"./country_pop/external_scroll_slider")
+
+ var pop_list_panel : Panel = get_panel_from_nodepath(^"./country_pop/pop_list")
+ if not pop_list_panel:
+ return
+
+ if _pop_list_scrollbar:
+ _pop_list_scrollbar.value_changed.connect(
+ func (value : int) -> void:
+ _pop_list_scroll_index = value
+ _update_pop_list()
+ )
+
+ pop_list_panel.gui_input.connect(
+ func (event : InputEvent) -> void:
+ if event is InputEventMouseButton:
+ if event.is_pressed():
+ if event.get_button_index() == MOUSE_BUTTON_WHEEL_UP:
+ _pop_list_scrollbar.decrement_value()
+ elif event.get_button_index() == MOUSE_BUTTON_WHEEL_DOWN:
+ _pop_list_scrollbar.increment_value()
+ )
+
+ var height : float = 0.0
+ while height < pop_list_panel.size.y:
+ var pop_row_panel : Panel = GUINode.generate_gui_element(_scene_name, "popinfomember_popview")
+ if not pop_row_panel:
+ break
+
+ pop_list_panel.add_child(pop_row_panel)
+ pop_row_panel.set_position(Vector2(0, height))
+ height += pop_row_panel.size.y
+ _pop_list_rows.push_back(pop_row_panel)
+
+ _pop_list_size_labels.push_back(GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_size")))
+
+ var pop_type_button : Button = GUINode.get_button_from_node(pop_row_panel.get_node(^"./pop_type"))
+ # TODO - open pop details menu on pop type button press
+ _pop_list_type_buttons.push_back(pop_type_button)
+
+ _pop_list_type_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_type_button))
+
+ _pop_list_producing_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_producing_icon")))
+
+ var culture_label : Label = GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_nation"))
+ if culture_label:
+ culture_label.set_text_overrun_behavior(TextServer.OVERRUN_TRIM_ELLIPSIS)
+ _pop_list_culture_labels.push_back(culture_label)
+
+ _pop_list_religion_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_religion")))
+
+ var location_label : Label = GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_location"))
+ if location_label:
+ location_label.set_text_overrun_behavior(TextServer.OVERRUN_TRIM_ELLIPSIS)
+ _pop_list_location_labels.push_back(location_label)
+
+ _pop_list_militancy_labels.push_back(GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_mil")))
+
+ _pop_list_consciousness_labels.push_back(GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_con")))
+
+ _pop_list_ideology_charts.push_back(GUINode.get_gfx_pie_chart_texture_from_node(pop_row_panel.get_node(^"./pop_ideology")))
+
+ _pop_list_issues_charts.push_back(GUINode.get_gfx_pie_chart_texture_from_node(pop_row_panel.get_node(^"./pop_issues")))
+
+ _pop_list_unemployment_progressbars.push_back(GUINode.get_progress_bar_from_node(pop_row_panel.get_node(^"./pop_unemployment_bar")))
+
+ _pop_list_cash_labels.push_back(GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_cash")))
+
+ var pop_list_life_needs_progressbar : TextureProgressBar = GUINode.get_progress_bar_from_node(pop_row_panel.get_node(^"./lifeneed_progress"))
+ if pop_list_life_needs_progressbar:
+ pop_list_life_needs_progressbar.position += Vector2(1, 0)
+ _pop_list_life_needs_progressbars.push_back(pop_list_life_needs_progressbar)
+
+ var pop_list_everyday_needs_progressbar : TextureProgressBar = GUINode.get_progress_bar_from_node(pop_row_panel.get_node(^"./eveneed_progress"))
+ if pop_list_everyday_needs_progressbar:
+ pop_list_everyday_needs_progressbar.position += Vector2(1, 0)
+ _pop_list_everyday_needs_progressbars.push_back(pop_list_everyday_needs_progressbar)
+
+ _pop_list_luxury_needs_progressbars.push_back(GUINode.get_progress_bar_from_node(pop_row_panel.get_node(^"./luxneed_progress")))
+
+ _pop_list_rebel_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_revolt")))
+
+ _pop_list_social_movement_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_movement_social")))
+
+ _pop_list_political_movement_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_movement_political")))
+
+ _pop_list_national_movement_flags.push_back(GUINode.get_gfx_masked_flag_texture_from_node(pop_row_panel.get_node(^"./pop_movement_flag")))
+
+ _pop_list_size_change_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./growth_indicator")))
+
+ _pop_list_literacy_labels.push_back(GUINode.get_label_from_node(pop_row_panel.get_node(^"./pop_literacy")))
+
func _notification(what : int) -> void:
match what:
NOTIFICATION_TRANSLATION_CHANGED:
@@ -28,7 +380,225 @@ func _on_update_active_nation_management_screen(active_screen : NationManagement
func _update_info() -> void:
if _active:
- # TODO - update UI state
+ # Province list
+ _update_province_list()
+
+ # Pop filter buttons, Distributions, Pop list
+ _update_pops()
+
show()
else:
hide()
+
+func get_growth_icon_index(size_change : int) -> int:
+ return 1 + int(size_change <= 0) + int(size_change < 0)
+
+func _update_province_list(scroll_index : int = -1) -> void:
+ if not _province_listbox:
+ return
+
+ if scroll_index >= 0:
+ _province_listbox.set_scroll_index(scroll_index, false)
+
+ _province_list_scroll_index = _province_listbox.get_scroll_index()
+
+ var province_list_info_list : Array[Dictionary] = MenuSingleton.get_population_menu_province_list_rows(_province_list_scroll_index, _province_listbox.get_fixed_visible_items())
+
+ for index : int in province_list_info_list.size():
+ const type_key : StringName = &"type"
+ const index_key : StringName = &"index"
+ const name_key : StringName = &"name"
+ const size_key : StringName = &"size"
+ const change_key : StringName = &"change"
+ const selected_key : StringName = &"selected"
+ const expanded_key : StringName = &"expanded"
+ const colony_key : StringName = &"colony"
+
+ var province_list_info : Dictionary = province_list_info_list[index]
+
+ var type : MenuSingleton.ProvinceListEntry = province_list_info[type_key]
+
+ if _generate_province_list_row(index, type) != OK:
+ continue
+
+ if type == MenuSingleton.LIST_ENTRY_NONE or type != _province_list_types[index]:
+ continue
+
+ _province_list_indices[index] = province_list_info[index_key]
+
+ if _province_list_button_icons[index]:
+ _province_list_button_icons[index].set_icon_index(1 + int(province_list_info[selected_key]))
+
+ if _province_list_name_labels[index]:
+ _province_list_name_labels[index].set_text(
+ GUINode.format_province_name(province_list_info[name_key]) if type == MenuSingleton.LIST_ENTRY_PROVINCE
+ else province_list_info[name_key]
+ )
+
+ if _province_list_size_labels[index]:
+ _province_list_size_labels[index].set_text(GUINode.int_to_formatted_string(province_list_info[size_key]))
+
+ if _province_list_growth_icons[index]:
+ _province_list_growth_icons[index].set_icon_index(get_growth_icon_index(province_list_info[change_key]))
+
+ if type == MenuSingleton.LIST_ENTRY_STATE:
+ if _province_list_colony_buttons[index]:
+ _province_list_colony_buttons[index].set_visible(province_list_info[colony_key])
+
+ if _province_list_expand_icons[index]:
+ _province_list_expand_icons[index].set_icon_index(1 + int(province_list_info[expanded_key]))
+
+ # TODO - set _province_list_national_focus_icons[index]
+
+ # Clear any excess rows
+ for index : int in range(province_list_info_list.size(), _province_list_types.size()):
+ _generate_province_list_row(index, MenuSingleton.LIST_ENTRY_NONE)
+
+func _update_pops() -> void:
+ _update_pop_filters()
+ _update_distributions()
+ _update_pop_list()
+
+func _update_pop_filters() -> void:
+ var pop_filter_info_list : Array[Dictionary] = MenuSingleton.get_population_menu_pop_filter_info()
+
+ for index : int in pop_filter_info_list.size():
+ const pop_filter_count_key : StringName = &"count"
+ const pop_filter_change_key : StringName = &"change"
+ const pop_filter_selected_key : StringName = &"selected"
+
+ var pop_filter_info : Dictionary = pop_filter_info_list[index]
+
+ var pop_filter_button : Button = _pop_filter_buttons[index]
+ if not pop_filter_button:
+ continue
+ pop_filter_button.disabled = pop_filter_info[pop_filter_count_key] <= 0
+
+ const normal_theme : StringName = &"normal"
+ const hover_theme : StringName = &"hover"
+
+ if pop_filter_info[pop_filter_selected_key] or pop_filter_button.disabled:
+ pop_filter_button.get_theme_stylebox(normal_theme).set_texture(_pop_filter_icons[index])
+ pop_filter_button.get_theme_stylebox(hover_theme).set_texture(_pop_filter_hover_icons[index])
+ else:
+ pop_filter_button.get_theme_stylebox(normal_theme).set_texture(_pop_filter_selected_icons[index])
+ pop_filter_button.get_theme_stylebox(hover_theme).set_texture(_pop_filter_selected_icons[index])
+ # TODO - size and promotion/demotion change tooltip
+
+func _update_distributions():
+ const slice_identifier_key : StringName = &"identifier"
+ const slice_colour_key : StringName = &"colour"
+ const slice_weight_key : StringName = &"weight"
+
+ var distribution_info_list : Array[Array] = MenuSingleton.get_population_menu_distribution_info()
+
+ for distribution_index : int in distribution_info_list.size():
+ var distribution_info : Array[Dictionary] = distribution_info_list[distribution_index]
+
+ if _distribution_charts[distribution_index]:
+ _distribution_charts[distribution_index].set_slices_array(distribution_info)
+
+ if _distribution_lists[distribution_index]:
+ distribution_info.sort_custom(func(a : Dictionary, b : Dictionary) -> bool: return a[slice_weight_key] > b[slice_weight_key])
+
+ var list : GUIListBox = _distribution_lists[distribution_index]
+
+ list.clear_children(distribution_info.size())
+
+ while list.get_child_count() < distribution_info.size():
+ var child : Panel = GUINode.generate_gui_element(_scene_name, "pop_legend_item")
+ if not child:
+ break
+ child.set_mouse_filter(Control.MOUSE_FILTER_IGNORE)
+ list.add_child(child)
+
+ for list_index in min(list.get_child_count(), distribution_info.size()):
+
+ var child : Panel = list.get_child(list_index)
+
+ var distribution_row : Dictionary = distribution_info[list_index]
+
+ var colour_icon_rect : TextureRect = GUINode.get_texture_rect_from_node(child.get_node(^"./legend_color"))
+ if colour_icon_rect:
+ colour_icon_rect.set_modulate(distribution_row[slice_colour_key])
+
+ var identifier_label : Label = GUINode.get_label_from_node(child.get_node(^"./legend_title"))
+ if identifier_label:
+ identifier_label.set_text_overrun_behavior(TextServer.OVERRUN_TRIM_ELLIPSIS)
+ identifier_label.set_text(distribution_row[slice_identifier_key])
+
+ var weight_label : Label = GUINode.get_label_from_node(child.get_node(^"./legend_value"))
+ if weight_label:
+ weight_label.set_text("%s%%" % GUINode.float_to_formatted_string(distribution_row[slice_weight_key] * 100.0, 1))
+
+func _update_pop_list() -> void:
+ if _pop_list_scrollbar:
+ var max_scroll_index : int = MenuSingleton.get_population_menu_pop_row_count() - _pop_list_rows.size()
+ if max_scroll_index > 0:
+ _pop_list_scrollbar.set_limits(0, max_scroll_index)
+ _pop_list_scrollbar.show()
+ else:
+ _pop_list_scrollbar.set_limits(0, 0)
+ _pop_list_scrollbar.hide()
+
+ var pop_rows = MenuSingleton.get_population_menu_pop_rows(_pop_list_scroll_index, _pop_list_rows.size())
+
+ for index : int in _pop_list_rows.size():
+ if not _pop_list_rows[index]:
+ continue
+ if index < pop_rows.size():
+ const pop_size_key : StringName = &"size"
+ const pop_type_icon_key : StringName = &"pop_type_icon"
+ const pop_culture_key : StringName = &"culture"
+ const pop_religion_icon_key : StringName = &"religion_icon"
+ const pop_location_key : StringName = &"location"
+ const pop_militancy_key : StringName = &"militancy"
+ const pop_consciousness_key : StringName = &"consciousness"
+ const pop_ideology_key : StringName = &"ideology"
+ const pop_issues_key : StringName = &"issues"
+ const pop_unemployment_key : StringName = &"unemployment"
+ const pop_cash_key : StringName = &"cash"
+ const pop_life_needs_key : StringName = &"life_needs"
+ const pop_everyday_needs_key : StringName = &"everyday_needs"
+ const pop_luxury_needs_key : StringName = &"luxury_needs"
+ const pop_size_change_key : StringName = &"size_change"
+ const pop_literacy_key : StringName = &"literacy"
+
+ var pop_row : Dictionary = pop_rows[index]
+
+ if _pop_list_size_labels[index]:
+ _pop_list_size_labels[index].set_text(GUINode.int_to_formatted_string(pop_row[pop_size_key]))
+ if _pop_list_type_icons[index]:
+ _pop_list_type_icons[index].set_icon_index(pop_row[pop_type_icon_key])
+ if _pop_list_culture_labels[index]:
+ _pop_list_culture_labels[index].set_text(pop_row[pop_culture_key])
+ if _pop_list_religion_icons[index]:
+ _pop_list_religion_icons[index].set_icon_index(pop_row[pop_religion_icon_key])
+ if _pop_list_location_labels[index]:
+ _pop_list_location_labels[index].set_text(GUINode.format_province_name(pop_row[pop_location_key]))
+ if _pop_list_militancy_labels[index]:
+ _pop_list_militancy_labels[index].set_text(GUINode.float_to_formatted_string(pop_row[pop_militancy_key], 2))
+ if _pop_list_consciousness_labels[index]:
+ _pop_list_consciousness_labels[index].set_text(GUINode.float_to_formatted_string(pop_row[pop_consciousness_key], 2))
+ if _pop_list_ideology_charts[index]:
+ _pop_list_ideology_charts[index].set_slices_array(pop_row[pop_ideology_key])
+ if _pop_list_issues_charts[index]:
+ _pop_list_issues_charts[index].set_slices_array(pop_row[pop_issues_key])
+ if _pop_list_unemployment_progressbars[index]:
+ _pop_list_unemployment_progressbars[index].set_value_no_signal(pop_row[pop_unemployment_key])
+ if _pop_list_cash_labels[index]:
+ _pop_list_cash_labels[index].set_text(GUINode.float_to_formatted_string(pop_row[pop_cash_key], 2))
+ if _pop_list_life_needs_progressbars[index]:
+ _pop_list_life_needs_progressbars[index].set_value_no_signal(pop_row[pop_life_needs_key])
+ if _pop_list_everyday_needs_progressbars[index]:
+ _pop_list_everyday_needs_progressbars[index].set_value_no_signal(pop_row[pop_everyday_needs_key])
+ if _pop_list_luxury_needs_progressbars[index]:
+ _pop_list_luxury_needs_progressbars[index].set_value_no_signal(pop_row[pop_luxury_needs_key])
+ if _pop_list_size_change_icons[index]:
+ _pop_list_size_change_icons[index].set_icon_index(get_growth_icon_index(pop_row[pop_size_change_key]))
+ if _pop_list_literacy_labels[index]:
+ _pop_list_literacy_labels[index].set_text("%s%%" % GUINode.float_to_formatted_string(pop_row[pop_literacy_key], 2))
+
+ _pop_list_rows[index].show()
+ else:
+ _pop_list_rows[index].hide()
diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel.gd
index 731d02c..13e7111 100644
--- a/game/src/Game/GameSession/ProvinceOverviewPanel.gd
+++ b/game/src/Game/GameSession/ProvinceOverviewPanel.gd
@@ -166,6 +166,15 @@ func _ready() -> void:
_pop_types_piechart = get_gfx_pie_chart_texture_from_nodepath(^"./province_view/province_statistics/workforce_chart")
_pop_ideologies_piechart = get_gfx_pie_chart_texture_from_nodepath(^"./province_view/province_statistics/ideology_chart")
_pop_cultures_piechart = get_gfx_pie_chart_texture_from_nodepath(^"./province_view/province_statistics/culture_chart")
+ var population_menu_button : Button = get_button_from_nodepath(^"./province_view/province_statistics/open_popscreen")
+ if population_menu_button:
+ population_menu_button.pressed.connect(
+ func() -> void:
+ pass
+ MenuSingleton.population_menu_select_province(_selected_index)
+ _on_close_button_pressed()
+ Events.NationManagementScreens.open_nation_management_screen(NationManagement.Screen.POPULATION)
+ )
_supply_limit_label = get_label_from_nodepath(^"./province_view/province_statistics/supply_limit_label")
_cores_overlapping_elements_box = get_gui_overlapping_elements_box_from_nodepath(^"./province_view/province_statistics/core_icons")
if _cores_overlapping_elements_box and _cores_overlapping_elements_box.set_gui_child_element_name("province_interface", "province_core") != OK:
diff --git a/game/src/Game/GameSession/TerrainMap.gdshader b/game/src/Game/GameSession/TerrainMap.gdshader
index 98f9efd..467e277 100644
--- a/game/src/Game/GameSession/TerrainMap.gdshader
+++ b/game/src/Game/GameSession/TerrainMap.gdshader
@@ -34,37 +34,87 @@ uniform sampler2D colormap_water_tex: repeat_enable, filter_linear;
// Overlay map tint
uniform sampler2D colormap_overlay_tex: repeat_enable, filter_linear;
-struct terrain_args_t {
- vec2 uv, half_pixel_size; // Components for calculating terrain sampling UV
+struct corner_args_t {
+ vec2 uv, half_pixel_size; // Components for calculating a corner's sampling UV
vec2 terrain_uv; // UV coordinates scaled for terrain texture tiling
vec3 land_tint_colour, water_tint_colour; // Colours for tinting the terrain
+ float stripe_mask; // Weight for mixing base and stripe province colours
+};
+
+struct corner_ret_t {
+ vec3 terrain_colour;
+ vec4 province_colour;
+ float highlight_mix_val;
};
// Calculate terrain colour at the specified corner of the current pixel
-vec3 get_terrain_colour(const terrain_args_t terrain_args, const vec2 corner) {
+corner_ret_t get_corner_colour(const corner_args_t corner_args, const vec2 corner) {
+ corner_ret_t ret;
+
+ uvec3 province_data = read_uvec3(fma(corner, corner_args.half_pixel_size, corner_args.uv));
+
// Find the terrain index at the specified corner of the current pixel
- uint terrain_index = read_uvec3(fma(corner, terrain_args.half_pixel_size, terrain_args.uv)).z;
+ uint terrain_index = province_data.z;
// Get the tinted land colour at the current position
- vec3 land_colour = texture(terrain_tex, vec3(terrain_args.terrain_uv, float(terrain_index))).rgb;
- land_colour = mix(land_colour, terrain_args.land_tint_colour, 0.3);
+ vec3 land_colour = texture(terrain_tex, vec3(corner_args.terrain_uv, float(terrain_index))).rgb;
+ land_colour = mix(land_colour, corner_args.land_tint_colour, 0.3);
// TODO - proper water texture
- vec3 water_colour = terrain_args.water_tint_colour;
+ vec3 water_colour = corner_args.water_tint_colour;
// Select land or water colour based on the terrain index (0 is water, otherwise land)
- vec3 terrain_colour = mix(land_colour, water_colour, float(terrain_index == 0u));
+ ret.terrain_colour = mix(land_colour, water_colour, float(terrain_index == 0u));
+
+ uint province_index = uvec2_to_uint(province_data.xy);
+
+ // Get base and stripe colours for province at the current position
+ province_data.x *= 2u; // Double "x coordinate" as colours come in (base, stripe) pairs
+ vec4 province_base_colour = texelFetch(province_colour_tex, ivec2(province_data.xy), 0);
+
+ province_data.x += 1u; // Add 1 to "x coordinate" to move from base to strip colour
+ vec4 province_stripe_colour = texelFetch(province_colour_tex, ivec2(province_data.xy), 0);
+
+ // Blend the base and stripe colours according to the current position's stripe mask value
+ ret.province_colour = mix(province_base_colour, province_stripe_colour, corner_args.stripe_mask);
+
+ ret.province_colour = mix(ret.province_colour, vec4(corner_args.water_tint_colour, 0.0), float(terrain_index == 0u));
+
+ ret.highlight_mix_val = 0.4 * (
+ float(province_index == hover_index) + float(province_index == selected_index)
+ ) * float(province_index != 0u);
- return terrain_colour;
+ return ret;
}
// Blend together terrain colours from the four corners of the current pixel
-vec3 mix_terrain_colour(const terrain_args_t terrain_args, const vec2 pixel_offset) {
- return mix(
- mix(get_terrain_colour(terrain_args, vec2(-1, -1)), get_terrain_colour(terrain_args, vec2(+1, -1)), pixel_offset.x),
- mix(get_terrain_colour(terrain_args, vec2(-1, +1)), get_terrain_colour(terrain_args, vec2(+1, +1)), pixel_offset.x),
+corner_ret_t mix_terrain_colour(const corner_args_t corner_args, const vec2 pixel_offset) {
+ corner_ret_t mm = get_corner_colour(corner_args, vec2(-1, -1));
+ corner_ret_t pm = get_corner_colour(corner_args, vec2(+1, -1));
+ corner_ret_t mp = get_corner_colour(corner_args, vec2(-1, +1));
+ corner_ret_t pp = get_corner_colour(corner_args, vec2(+1, +1));
+
+ corner_ret_t ret;
+
+ ret.terrain_colour = mix(
+ mix(mm.terrain_colour, pm.terrain_colour, pixel_offset.x),
+ mix(mp.terrain_colour, pp.terrain_colour, pixel_offset.x),
+ pixel_offset.y
+ );
+
+ ret.province_colour = mix(
+ mix(mm.province_colour, pm.province_colour, pixel_offset.x),
+ mix(mp.province_colour, pp.province_colour, pixel_offset.x),
+ pixel_offset.y
+ );
+
+ ret.highlight_mix_val = mix(
+ mix(mm.highlight_mix_val, pm.highlight_mix_val, pixel_offset.x),
+ mix(mp.highlight_mix_val, pp.highlight_mix_val, pixel_offset.x),
pixel_offset.y
);
+
+ return ret;
}
// Mix overlay and base colours, used for the parchment map
@@ -84,41 +134,33 @@ vec3 get_map_colour(vec2 uv) {
// Offset of uv_map_pixels from the top left corner of the current pixel
vec2 pixel_offset = fract(uv_map_pixels);
- terrain_args_t terrain_args;
- terrain_args.uv = uv;
- terrain_args.half_pixel_size = 0.49 / map_size;
+ corner_args_t corner_args;
+ corner_args.uv = uv;
+ corner_args.half_pixel_size = 0.49 / map_size;
// Terrain texture tiling UV
- terrain_args.terrain_uv = 0.5 - uv_map_pixels * terrain_tile_factor;
+ corner_args.terrain_uv = 0.5 - uv_map_pixels * terrain_tile_factor;
+
+ vec2 stripe_uv = uv_map_pixels * stripe_tile_factor;
+ // Stripe mask value - between 0 (base) and 1 (stripe)
+ corner_args.stripe_mask = texture(stripe_tex, stripe_uv).b;
vec2 colormap_uv = vec2(uv.x, 1.0 - uv.y);
// Terrain tinting colours
- terrain_args.land_tint_colour = texture(colormap_land_tex, colormap_uv).rgb;
- terrain_args.water_tint_colour = texture(colormap_water_tex, colormap_uv).rgb;
+ corner_args.land_tint_colour = texture(colormap_land_tex, colormap_uv).rgb;
+ corner_args.water_tint_colour = texture(colormap_water_tex, colormap_uv).rgb;
// Parchment tint colour
vec3 overlay_tint_colour = texture(colormap_overlay_tex, colormap_uv).rgb;
- // Blended terrain colour (average of four corners of current pixel)
- vec3 terrain_colour = mix_terrain_colour(terrain_args, pixel_offset);
+ corner_ret_t colours = mix_terrain_colour(corner_args, pixel_offset);
- vec2 stripe_uv = uv_map_pixels * stripe_tile_factor;
- // Stripe mask value - between 0 (base) and 1 (stripe)
- float stripe_mask = texture(stripe_tex, stripe_uv).b;
+ // Blended terrain colour (average of four corners of current pixel)
+ vec3 terrain_colour = colours.terrain_colour;
vec2 overlay_uv = vec2(uv_map_pixels.x, map_size.y - uv_map_pixels.y) * overlay_tile_factor;
// Parchment overlay colour
vec3 overlay_colour = texture(overlay_tex, overlay_uv).rgb;
- // Current province index as a pair of byte coordinates and as a combined 16 bit value
- uvec2 province_data = read_uvec3(uv).xy;
- uint province_index = uvec2_to_uint(province_data);
-
- // Get base and stripe colours for province at the current position
- province_data.x *= 2u; // Double "x coordinate" as colours come in (base, stripe) pairs
- vec4 province_base_colour = texelFetch(province_colour_tex, ivec2(province_data), 0);
- province_data.x += 1u; // Add 1 to "x coordinate" to move from base to strip colour
- vec4 province_stripe_colour = texelFetch(province_colour_tex, ivec2(province_data), 0);
- // Blend the base and stripe colours according to the current position's stripe mask value
- vec4 province_colour = mix(province_base_colour, province_stripe_colour, stripe_mask);
+ vec4 province_colour = colours.province_colour;
// Darken the province colour
province_colour.rgb -= 0.7;
@@ -129,22 +171,21 @@ vec3 get_map_colour(vec2 uv) {
// Near colour is either the terrain's luma component tinted with the province colour and brightened,
// or the normal terrain colour
vec3 near_province_colour = mix(vec3(terrain_luma), province_colour.rgb, 0.3) * 1.5;
- vec3 near_colour = mix(near_province_colour, terrain_colour, float(province_colour.a == 0.0));
+ vec3 near_colour = mix(terrain_colour, near_province_colour, province_colour.a);
// Far colour is either the province colour mixed with the overlay texture, tinted with the overlay colormap and brightened,
// or the normal terrain colour mixed with the overlay texture (primarily for water)
vec3 far_province_colour = mix_overlay(overlay_colour, province_colour.rgb);
far_province_colour = mix(overlay_tint_colour, far_province_colour, 0.3) * 1.5;
vec3 far_terrain_colour = mix_overlay(overlay_colour, terrain_colour);
- vec3 far_colour = mix(far_province_colour, far_terrain_colour, float(province_colour.a == 0.0));
+ vec3 far_colour = mix(far_terrain_colour, far_province_colour, province_colour.a);
// Blend the near (detailed terrain) and far (parchment) colours according to the parchment mix factor (0 for near, 1 for far)
vec3 final_colour = mix(near_colour, far_colour, parchment_mix);
// Significantly brighted the colour if it is hovered over and/or selected, but not if it has province index 0 (all invalid pixels)
const vec3 highlight_colour = vec3(1.0);
- float highlight_mix_val = 0.4 * (float(province_index == hover_index) + float(province_index == selected_index)) * float(province_index != 0u);
- vec3 highlighted_colour = mix(final_colour, highlight_colour, highlight_mix_val);
+ vec3 highlighted_colour = mix(final_colour, highlight_colour, colours.highlight_mix_val);
return highlighted_colour;
}