diff options
Diffstat (limited to 'extension/src')
-rw-r--r-- | extension/src/GameSingleton.cpp | 83 | ||||
-rw-r--r-- | extension/src/GameSingleton.hpp | 18 | ||||
-rw-r--r-- | extension/src/LoadGameCompatibility.cpp | 80 | ||||
-rw-r--r-- | extension/src/LoadGameOpenVic.cpp | 12 | ||||
-rw-r--r-- | extension/src/Utilities.cpp | 90 | ||||
-rw-r--r-- | extension/src/Utilities.hpp | 10 |
6 files changed, 241 insertions, 52 deletions
diff --git a/extension/src/GameSingleton.cpp b/extension/src/GameSingleton.cpp index efacb8e..18fd67a 100644 --- a/extension/src/GameSingleton.cpp +++ b/extension/src/GameSingleton.cpp @@ -1,7 +1,5 @@ #include "GameSingleton.hpp" -#include <cassert> - #include <godot_cpp/variant/utility_functions.hpp> #include "openvic/utility/Logger.hpp" @@ -11,10 +9,9 @@ using namespace godot; using namespace OpenVic; -TerrainVariant::TerrainVariant(std::string const& new_identfier, +TerrainVariant::TerrainVariant(const std::string_view new_identfier, colour_t new_colour, Ref<Image> const& new_image) - : HasIdentifier { new_identfier }, - HasColour { new_colour, true }, + : HasIdentifierAndColour { new_identfier, new_colour, true }, image { new_image } {} Ref<Image> TerrainVariant::get_image() const { @@ -75,6 +72,9 @@ void GameSingleton::_bind_methods() { ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_region_key"), &GameSingleton::get_province_info_region_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_life_rating_key"), &GameSingleton::get_province_info_life_rating_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_total_population_key"), &GameSingleton::get_province_info_total_population_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_pop_types_key"), &GameSingleton::get_province_info_pop_types_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_pop_ideologies_key"), &GameSingleton::get_province_info_pop_ideologies_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_pop_cultures_key"), &GameSingleton::get_province_info_pop_cultures_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_rgo_key"), &GameSingleton::get_province_info_rgo_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_buildings_key"), &GameSingleton::get_province_info_buildings_key); @@ -84,6 +84,25 @@ void GameSingleton::_bind_methods() { ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_start_date_key"), &GameSingleton::get_building_info_start_date_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_end_date_key"), &GameSingleton::get_building_info_end_date_key); ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_expansion_progress_key"), &GameSingleton::get_building_info_expansion_progress_key); + + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_piechart_info_size_key"), &GameSingleton::get_piechart_info_size_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_piechart_info_colour_key"), &GameSingleton::get_piechart_info_colour_key); + + ClassDB::bind_static_method("GameSingleton", D_METHOD("draw_pie_chart", "image", "stopAngles", "colours", "radius", + "shadow_displacement", "shadow_tightness", "shadow_radius", "shadow_thickness", + "trim_colour", "trim_size", "gradient_falloff", "gradient_base", + "donut", "donut_inner_trim", "donut_inner_radius"), &GameSingleton::draw_pie_chart); +} + +void GameSingleton::draw_pie_chart(Ref<Image> image, + Array const& stopAngles, Array const& colours, float radius, + Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius) { + + OpenVic::draw_pie_chart(image, stopAngles, colours, radius, shadow_displacement, shadow_tightness, shadow_radius, shadow_thickness, + trim_colour, trim_size, gradient_falloff, gradient_base, + donut, donut_inner_trim, donut_inner_radius); } GameSingleton* GameSingleton::get_singleton() { @@ -153,11 +172,25 @@ Error GameSingleton::_load_hardcoded_defines() { } return HIGH_ALPHA_VALUE | val; } - return HIGH_ALPHA_VALUE; + return NULL_COLOUR; } }, { "mapmode_population", [](Map const& map, Province const& province) -> colour_t { return HIGH_ALPHA_VALUE | (fraction_to_colour_byte(province.get_total_population(), map.get_highest_province_population() + 1, 0.1f, 1.0f) << 8); + } }, + { "mapmode_culture", + [](Map const& map, Province const& province) -> colour_t { + distribution_t const& cultures = province.get_culture_distribution(); + if (!cultures.empty()) { + // This breaks if replaced with distribution_t::value_type, something + // about operator=(volatile const&) being deleted. + std::pair<HasIdentifierAndColour const*, float> culture = *cultures.begin(); + for (distribution_t::value_type const p : cultures) { + if (p.second > culture.second) culture = p; + } + return HIGH_ALPHA_VALUE | culture.first->get_colour(); + } + return NULL_COLOUR; } } }; for (mapmode_t const& mapmode : mapmodes) @@ -208,6 +241,18 @@ StringName const& GameSingleton::get_province_info_total_population_key() { static const StringName key = "total_population"; return key; } +StringName const& GameSingleton::get_province_info_pop_types_key() { + static const StringName key = "pop_types"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_ideologies_key() { + static const StringName key = "pop_ideologies"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_cultures_key() { + static const StringName key = "pop_cultures"; + return key; +} StringName const& GameSingleton::get_province_info_rgo_key() { static const StringName key = "rgo"; return key; @@ -242,6 +287,26 @@ StringName const& GameSingleton::get_building_info_expansion_progress_key() { return key; } +StringName const& GameSingleton::get_piechart_info_size_key() { + static const StringName key = "size"; + return key; +} +StringName const& GameSingleton::get_piechart_info_colour_key() { + static const StringName key = "colour"; + return key; +} + +Dictionary GameSingleton::_distribution_to_dictionary(distribution_t const& dist) const { + Dictionary dict; + for (distribution_t::value_type const& p : dist) { + Dictionary sub_dict; + sub_dict[get_piechart_info_size_key()] = p.second; + sub_dict[get_piechart_info_colour_key()] = to_godot_color(p.first->get_colour()); + dict[std_to_godot_string(p.first->get_identifier())] = sub_dict; + } + return dict; +} + Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { Province const* province = game_manager.map.get_province_by_index(index); if (province == nullptr) return {}; @@ -257,6 +322,12 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { ret[get_province_info_life_rating_key()] = province->get_life_rating(); ret[get_province_info_total_population_key()] = province->get_total_population(); + distribution_t const& pop_types = province->get_pop_type_distribution(); + if (!pop_types.empty()) ret[get_province_info_pop_types_key()] = _distribution_to_dictionary(pop_types); + //distribution_t const& ideologies = province->get_ideology_distribution(); + //if (!ideologies.empty()) ret[get_province_info_pop_ideologies_key()] = _distribution_to_dictionary(ideologies); + distribution_t const& cultures = province->get_culture_distribution(); + if (!cultures.empty()) ret[get_province_info_pop_cultures_key()] = _distribution_to_dictionary(cultures); std::vector<Building> const& buildings = province->get_buildings(); if (!buildings.empty()) { diff --git a/extension/src/GameSingleton.hpp b/extension/src/GameSingleton.hpp index 23eb334..cd8cdd0 100644 --- a/extension/src/GameSingleton.hpp +++ b/extension/src/GameSingleton.hpp @@ -6,13 +6,13 @@ #include "openvic/GameManager.hpp" namespace OpenVic { - struct TerrainVariant : HasIdentifier, HasColour { + struct TerrainVariant : HasIdentifierAndColour { friend class GameSingleton; private: const godot::Ref<godot::Image> image; - TerrainVariant(std::string const& new_identfier, colour_t new_colour, + TerrainVariant(const std::string_view new_identfier, colour_t new_colour, godot::Ref<godot::Image> const& new_image); public: static constexpr size_t MAX_INDEX = 1 << (8 * sizeof(Map::terrain_t)); @@ -64,10 +64,18 @@ namespace OpenVic { godot::Error _update_colour_image(); void _on_state_updated(); + godot::Dictionary _distribution_to_dictionary(distribution_t const& dist) const; + protected: static void _bind_methods(); public: + static void draw_pie_chart(godot::Ref<godot::Image> image, + godot::Array const& stopAngles, godot::Array const& colours, float radius, + godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + godot::Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius); + static GameSingleton* get_singleton(); GameSingleton(); @@ -106,6 +114,9 @@ namespace OpenVic { static godot::StringName const& get_province_info_region_key(); static godot::StringName const& get_province_info_life_rating_key(); static godot::StringName const& get_province_info_total_population_key(); + static godot::StringName const& get_province_info_pop_types_key(); + static godot::StringName const& get_province_info_pop_ideologies_key(); + static godot::StringName const& get_province_info_pop_cultures_key(); static godot::StringName const& get_province_info_rgo_key(); static godot::StringName const& get_province_info_buildings_key(); @@ -116,6 +127,9 @@ namespace OpenVic { static godot::StringName const& get_building_info_end_date_key(); static godot::StringName const& get_building_info_expansion_progress_key(); + static godot::StringName const& get_piechart_info_size_key(); + static godot::StringName const& get_piechart_info_colour_key(); + /* Get info to display in Province Overview Panel, packaged in * a Dictionary using the StringNames above as keys. */ diff --git a/extension/src/LoadGameCompatibility.cpp b/extension/src/LoadGameCompatibility.cpp index ddde5b8..71435a4 100644 --- a/extension/src/LoadGameCompatibility.cpp +++ b/extension/src/LoadGameCompatibility.cpp @@ -17,57 +17,57 @@ Error GameSingleton::_load_province_identifier_file_compatibility_mode(String co Error err = FileAccess::get_open_error(); if (err != OK || file.is_null()) { UtilityFunctions::push_error("Failed to load compatibility mode province identifier file: ", file_path); - return err == OK ? FAILED : err; - } - - int line_number = 0; - while (!file->eof_reached()) { - const PackedStringArray line = file->get_csv_line(";"); - line_number++; + if (err == OK) err = FAILED; + } else { + int line_number = 0; + while (!file->eof_reached()) { + const PackedStringArray line = file->get_csv_line(";"); + line_number++; - if (line.is_empty() || (line.size() == 1 && line[0].is_empty())) - continue; + if (line.is_empty() || (line.size() == 1 && line[0].is_empty())) + continue; - if (line_number < 2) continue; // skip header line - index_t id = NULL_INDEX; - colour_t colour = NULL_COLOUR; - if (line.size() > 0) { - if (line[0].is_empty()) { - id = game_manager.map.get_province_count() + 1; - } else if (line[0].is_valid_int()) { - const int64_t val = line[0].to_int(); - if (val > NULL_INDEX && val <= MAX_INDEX) id = val; - } - for (int i = 1; i < 4; ++i) { - if (line.size() > i) { - if (line[i].is_valid_int()) { - const int64_t int_val = line[i].to_int(); - if (int_val >= NULL_COLOUR && int_val <= FULL_COLOUR) { - colour = (colour << 8) | int_val; - continue; - } - } else if (line[i].is_valid_float()) { - const double double_val = line[i].to_float(); - if (std::trunc(double_val) == double_val) { - const int64_t int_val = double_val; + if (line_number < 2) continue; // skip header line + index_t id = NULL_INDEX; + colour_t colour = NULL_COLOUR; + if (line.size() > 0) { + if (line[0].is_empty()) { + id = game_manager.map.get_province_count() + 1; + } else if (line[0].is_valid_int()) { + const int64_t val = line[0].to_int(); + if (val > NULL_INDEX && val <= MAX_INDEX) id = val; + } + for (int i = 1; i < 4; ++i) { + if (line.size() > i) { + if (line[i].is_valid_int()) { + const int64_t int_val = line[i].to_int(); if (int_val >= NULL_COLOUR && int_val <= FULL_COLOUR) { colour = (colour << 8) | int_val; continue; } + } else if (line[i].is_valid_float()) { + const double double_val = line[i].to_float(); + if (std::trunc(double_val) == double_val) { + const int64_t int_val = double_val; + if (int_val >= NULL_COLOUR && int_val <= FULL_COLOUR) { + colour = (colour << 8) | int_val; + continue; + } + } } } + colour = NULL_COLOUR; + break; } - colour = NULL_COLOUR; - break; } + if (id == NULL_INDEX || colour == NULL_COLOUR) { + UtilityFunctions::push_error("Invalid province ID-colour entry \"", line, "\" on line ", line_number, " in file: ", file_path); + err = FAILED; + continue; + } + static const std::string province_prefix = "PROV"; + if (game_manager.map.add_province(province_prefix + std::to_string(id), colour) != SUCCESS) err = FAILED; } - if (id == NULL_INDEX || colour == NULL_COLOUR) { - UtilityFunctions::push_error("Invalid province ID-colour entry \"", line, "\" on line ", line_number, " in file: ", file_path); - err = FAILED; - continue; - } - static const std::string province_prefix = "PROV"; - if (game_manager.map.add_province(province_prefix + std::to_string(id), colour) != SUCCESS) err = FAILED; } game_manager.map.lock_provinces(); return err; diff --git a/extension/src/LoadGameOpenVic.cpp b/extension/src/LoadGameOpenVic.cpp index 27fb265..627578f 100644 --- a/extension/src/LoadGameOpenVic.cpp +++ b/extension/src/LoadGameOpenVic.cpp @@ -157,7 +157,11 @@ Error GameSingleton::_parse_region_entry(String const& identifier, Variant const UtilityFunctions::push_error("Invalid province list for region \"", identifier, "\": ", entry); return FAILED; } - return ERR(game_manager.map.add_region(godot_to_std_string(identifier), province_identifiers)); + std::vector<std::string_view> province_identifier_views; + for (std::string const& str : province_identifiers) { + province_identifier_views.push_back(str); + } + return ERR(game_manager.map.add_region(godot_to_std_string(identifier), province_identifier_views)); } Error GameSingleton::_load_region_file(String const& file_path) { @@ -271,7 +275,7 @@ Error GameSingleton::_load_map_images(String const& province_image_path, String // Generate interleaved province and terrain ID image if (game_manager.map.generate_province_shape_image(province_dims.x, province_dims.y, province_image->get_data().ptr(), - terrain_image->get_data().ptr(), terrain_variant_map) != SUCCESS) err = FAILED; + terrain_image->get_data().ptr(), terrain_variant_map, true) != SUCCESS) err = FAILED; static constexpr int32_t GPU_DIM_LIMIT = 0x3FFF; // For each dimension of the image, this finds the small number of equal subdivisions required get the individual texture dims under GPU_DIM_LIMIT @@ -364,8 +368,8 @@ Error GameSingleton::_parse_good_entry(String const& identifier, Variant const& if (var_overseas_maintenance.get_type() == Variant::BOOL) overseas_maintenance = var_overseas_maintenance; else UtilityFunctions::push_error("Invalid good overseas maintenance bool value for ", identifier, ": ", var_overseas_maintenance); - return ERR(game_manager.good_manager.add_good(godot_to_std_string(identifier), godot_to_std_string(category), - colour, base_price, default_available, tradeable, currency, overseas_maintenance)); + return ERR(game_manager.good_manager.add_good(godot_to_std_string(identifier), colour, godot_to_std_string(category), + base_price, default_available, tradeable, currency, overseas_maintenance)); } Error GameSingleton::_load_goods(String const& defines_path, String const& icons_dir_path) { diff --git a/extension/src/Utilities.cpp b/extension/src/Utilities.cpp index a912490..f09eae0 100644 --- a/extension/src/Utilities.cpp +++ b/extension/src/Utilities.cpp @@ -1,5 +1,7 @@ #include "Utilities.hpp" +#include <numbers> + #include <godot_cpp/classes/resource_loader.hpp> #include <godot_cpp/variant/utility_functions.hpp> @@ -14,3 +16,91 @@ Ref<Image> OpenVic::load_godot_image(String const& path) { return Image::load_from_file(path); } } + +// Get the polar coordinates of a pixel relative to the center +static Vector2 getPolar(Vector2 UVin, Vector2 center) { + Vector2 relcoord = (UVin-center); + float dist = relcoord.length(); + float theta = std::numbers::pi / 2 + atan2(relcoord.y, relcoord.x); + if (theta < 0.0f) theta += std::numbers::pi * 2; + return { dist, theta }; +} + +// From thebookofshaders, returns a gradient falloff +static inline float parabola(float base, float x, float k){ + return powf(base * x * (1.0 - x), k); +} + +static inline float parabola_shadow(float base, float x){ + return base * x * x; +} + +static Color pie_chart_fragment(Vector2 UV, float radius, Array const& stopAngles, Array const& colours, + Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius) { + + Vector2 coords = getPolar(UV, { 0.5, 0.5 }); + float dist = coords.x; + float theta = coords.y; + + Vector2 shadow_polar = getPolar(UV, shadow_displacement); + float shadow_peak = radius + (radius - donut_inner_radius) / 2.0; + float shadow_gradient = shadow_thickness + parabola_shadow(shadow_tightness * -10.0, shadow_polar.x + shadow_peak - shadow_radius); + + // Inner hole of the donut => make it transparent + if (donut && dist <= donut_inner_radius) { + return { 0.1, 0.1, 0.1, shadow_gradient }; + } + // Inner trim + else if (donut && donut_inner_trim && dist <= donut_inner_radius + trim_size) { + return { trim_colour, 1.0 }; + } + // Interior + else if (dist <= radius-trim_size) { + Color col { 1.0f, 0.0f, 0.0f }; + for (int i = 0; i < stopAngles.size(); i++){ + if (theta <= float(stopAngles[i])) { + col = colours[i]; + break; + } + } + float gradient = parabola(gradient_base, dist, gradient_falloff); + return { col * (1.0 - gradient), 1.0 }; + } + // Outer trim + else if (dist <= radius) { + return { trim_colour, 1.0 }; + } + // Outside the circle + else{ + return { 0.1, 0.1, 0.1, shadow_gradient }; + } +} + +void OpenVic::draw_pie_chart(Ref<Image> image, + Array const& stopAngles, Array const& colours, float radius, + Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius) { + + ERR_FAIL_NULL_EDMSG(image, "Cannot draw pie chart to null image."); + const int32_t width = image->get_width(); + const int32_t height = image->get_height(); + ERR_FAIL_COND_EDMSG(width <= 0 || height <= 0, "Cannot draw pie chart to empty image."); + if (width != height) { + UtilityFunctions::push_warning("Drawing pie chart to non-square image: ", width, "x", height); + } + const int32_t size = std::min(width, height); + for (int32_t y = 0; y < size; ++y) { + for (int32_t x = 0; x < size; ++x) { + image->set_pixel(x, y, pie_chart_fragment( + { static_cast<float>(x) / static_cast<float>(size), + static_cast<float>(y) / static_cast<float>(size) }, + radius, stopAngles, colours, + shadow_displacement, shadow_tightness, shadow_radius, shadow_thickness, + trim_colour, trim_size, gradient_falloff, gradient_base, + donut, donut_inner_trim, donut_inner_radius)); + } + } +} diff --git a/extension/src/Utilities.hpp b/extension/src/Utilities.hpp index 681b893..e8796e9 100644 --- a/extension/src/Utilities.hpp +++ b/extension/src/Utilities.hpp @@ -16,5 +16,15 @@ namespace OpenVic { return str.c_str(); } + inline godot::Color to_godot_color(colour_t colour) { + return { colour_byte_to_float((colour >> 16) & 0xFF), colour_byte_to_float((colour >> 8) & 0xFF), colour_byte_to_float(colour & 0xFF) }; + } + godot::Ref<godot::Image> load_godot_image(godot::String const& path); + + void draw_pie_chart(godot::Ref<godot::Image> image, + godot::Array const& stopAngles, godot::Array const& colours, float radius, + godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, + godot::Color trim_colour, float trim_size, float gradient_falloff, float gradient_base, + bool donut, bool donut_inner_trim, float donut_inner_radius); } |