aboutsummaryrefslogtreecommitdiff
path: root/extension/src/openvic-extension/singletons/GameSingleton.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'extension/src/openvic-extension/singletons/GameSingleton.cpp')
-rw-r--r--extension/src/openvic-extension/singletons/GameSingleton.cpp236
1 files changed, 186 insertions, 50 deletions
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) {