From aa49dbbb3cf9dbff18c08245b0e46a9943df9b15 Mon Sep 17 00:00:00 2001 From: Hop311 Date: Thu, 24 Aug 2023 00:33:21 +0100 Subject: Big Dataloader Commit (openvic) Default compat path + formatting + bits+bobs --- game/src/Game/Autoload/GameLoader.gd | 12 ------------ game/src/Game/GameStart.gd | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 20 deletions(-) (limited to 'game/src') diff --git a/game/src/Game/Autoload/GameLoader.gd b/game/src/Game/Autoload/GameLoader.gd index 1720e3c..8c14c7e 100644 --- a/game/src/Game/Autoload/GameLoader.gd +++ b/game/src/Game/Autoload/GameLoader.gd @@ -1,17 +1,5 @@ extends Node -var define_filepaths_dict : Dictionary = { - GameSingleton.get_province_identifier_file_key(): "res://common/map/provinces.json", - GameSingleton.get_water_province_file_key(): "res://common/map/water.json", - GameSingleton.get_region_file_key(): "res://common/map/regions.json", - GameSingleton.get_terrain_variant_file_key(): "res://common/map/terrain.json", - GameSingleton.get_terrain_texture_dir_key(): "res://art/terrain/", - GameSingleton.get_province_image_file_key(): "res://common/map/provinces.png", - GameSingleton.get_terrain_image_file_key(): "res://common/map/terrain.png", - GameSingleton.get_goods_file_key(): "res://common/goods.json", - GameSingleton.get_good_icons_dir_key(): "res://art/economy/goods" -} - var ShaderManager : ShaderManagerClass func _init(): diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index 723086e..a5524d6 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -36,7 +36,17 @@ func _initialize_game() -> void: # into the mod's dir for a temporary fix) # Usage: OpenVic --compatibility-mode - var compatibility_mode_path : String = ArgumentParser.get_argument(&"compatibility-mode") + var compatibility_mode_path : String = ArgumentParser.get_argument(&"compatibility-mode", "") + + if not compatibility_mode_path: + # TODO - non-Windows default paths + const default_path : String = "C:/Program Files (x86)/Steam/steamapps/common/Victoria 2" + compatibility_mode_path = default_path + + var compatibility_mode_paths : PackedStringArray = [compatibility_mode_path] + + # Example for adding mod paths + #compatibility_mode_paths.push_back("C:/Program Files (x86)/Steam/steamapps/common/Victoria 2/mod/TGC") var start := Time.get_ticks_usec() @@ -49,13 +59,8 @@ func _initialize_game() -> void: # TODO: Loading takes way too long to keep the LoadingScreen at 50% # Should either split this up or seperately multithread the compatibility mode loader # Or both and emit a signal that allows us to add percentages to the LoadingScreen - if compatibility_mode_path: - if GameSingleton.load_defines_compatibility_mode(compatibility_mode_path) != OK: - push_error("Errors loading game defines!") - else: - GameLoader.define_filepaths_dict.make_read_only() - if GameSingleton.load_defines(GameLoader.define_filepaths_dict) != OK: - push_error("Errors loading game defines!") + if GameSingleton.load_defines_compatibility_mode(compatibility_mode_paths) != OK: + push_error("Errors loading game defines!") loading_screen.try_update_loading_screen(100) var end := Time.get_ticks_usec() -- cgit v1.2.3-56-ga3b1 From c715187bffa8c84428acf3631e3d03c088a719d9 Mon Sep 17 00:00:00 2001 From: Hop311 Date: Sat, 2 Sep 2023 12:55:36 +0100 Subject: Followup big dataloader commit --- extension/deps/openvic-simulation | 2 +- extension/src/GameSingleton.cpp | 1 + extension/src/GameSingleton.hpp | 4 ++++ extension/src/LoadGameCompatibility.cpp | 31 ++++++++++++++++++++++--------- extension/src/LoadGameOpenVic.cpp | 6 +++--- game/localisation/en_GB/mapmodes.csv | 1 + game/src/Game/GameStart.gd | 28 +++++++++++++--------------- 7 files changed, 45 insertions(+), 28 deletions(-) (limited to 'game/src') diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index d204599..366f1b3 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit d20459988118dc18ddc9feb12b09f5e65ad03b23 +Subproject commit 366f1b3941315641bdcb0ee98465b4d2134eee86 diff --git a/extension/src/GameSingleton.cpp b/extension/src/GameSingleton.cpp index 07be9aa..6ccde8d 100644 --- a/extension/src/GameSingleton.cpp +++ b/extension/src/GameSingleton.cpp @@ -23,6 +23,7 @@ GameSingleton* GameSingleton::singleton = nullptr; void GameSingleton::_bind_methods() { ClassDB::bind_static_method("GameSingleton", D_METHOD("setup_logger"), &GameSingleton::setup_logger); ClassDB::bind_method(D_METHOD("load_defines_compatibility_mode", "file_paths"), &GameSingleton::load_defines_compatibility_mode); + ClassDB::bind_method(D_METHOD("lookup_file", "path"), &GameSingleton::lookup_file); ClassDB::bind_method(D_METHOD("setup_game"), &GameSingleton::setup_game); ClassDB::bind_method(D_METHOD("get_province_index_from_uv_coords", "coords"), &GameSingleton::get_province_index_from_uv_coords); diff --git a/extension/src/GameSingleton.hpp b/extension/src/GameSingleton.hpp index a484520..eb332f4 100644 --- a/extension/src/GameSingleton.hpp +++ b/extension/src/GameSingleton.hpp @@ -7,6 +7,7 @@ #include "openvic/dataloader/Dataloader.hpp" namespace OpenVic { + struct TerrainVariant : HasIdentifierAndColour { friend class GameSingleton; @@ -23,6 +24,7 @@ namespace OpenVic { godot::Ref get_image() const; }; + class GameSingleton : public godot::Object { GDCLASS(GameSingleton, godot::Object) @@ -76,6 +78,8 @@ namespace OpenVic { */ godot::Error load_defines_compatibility_mode(godot::PackedStringArray const& file_paths); + godot::String lookup_file(godot::String const& path) const; + /* Post-load/restart game setup - reset the game to post-load state * and (re)generate starting data, e.g. buildings. */ diff --git a/extension/src/LoadGameCompatibility.cpp b/extension/src/LoadGameCompatibility.cpp index 1567977..3da5f08 100644 --- a/extension/src/LoadGameCompatibility.cpp +++ b/extension/src/LoadGameCompatibility.cpp @@ -28,14 +28,24 @@ Error GameSingleton::_load_province_identifier_file_compatibility_mode(String co continue; if (line_number < 2) continue; // skip header line - Province::index_t id = Province::NULL_INDEX; + std::string identifier; 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 > Province::NULL_INDEX && val <= Province::MAX_INDEX) id = val; + identifier = godot_to_std_string(line[0]); + if (identifier.empty()) { + identifier = std::to_string(game_manager.map.get_province_count() + 1); + } else { + bool successful = false; + const uint64_t val = StringUtils::string_to_uint64(identifier, &successful, 10); + if (successful) { + if (val <= Province::NULL_INDEX || val > Province::MAX_INDEX) { + UtilityFunctions::push_error("Invalid province index ", line[0], " (out of valid range ", Province::NULL_INDEX, " < index <= ", Province::MAX_INDEX, ")"); + err = FAILED; + } + } else { + UtilityFunctions::push_error("Invalid province index ", line[0], " (not a valid integer)"); + err = FAILED; + } } for (int i = 1; i < 4; ++i) { if (line.size() > i) { @@ -60,12 +70,12 @@ Error GameSingleton::_load_province_identifier_file_compatibility_mode(String co break; } } - if (id == Province::NULL_INDEX || colour == NULL_COLOUR) { + if (identifier.empty() || colour == NULL_COLOUR) { UtilityFunctions::push_error("Invalid province ID-colour entry \"", line, "\" on line ", line_number, " in file: ", file_path); err = FAILED; continue; } - if (game_manager.map.add_province(std::to_string(id), colour) != SUCCESS) err = FAILED; + if (game_manager.map.add_province(identifier, colour) != SUCCESS) err = FAILED; } } game_manager.map.lock_provinces(); @@ -167,10 +177,13 @@ Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& fi UtilityFunctions::push_error("Failed to load map images!"); err = FAILED; } - game_manager.good_manager.lock_goods(); if (game_manager.load_hardcoded_defines() != SUCCESS) { UtilityFunctions::push_error("Failed to hardcoded defines!"); err = FAILED; } return err; } + +String GameSingleton::lookup_file(String const& path) const { + return std_to_godot_string(dataloader.lookup_file(godot_to_std_string(path)).string()); +} diff --git a/extension/src/LoadGameOpenVic.cpp b/extension/src/LoadGameOpenVic.cpp index b262a28..3d3d6c5 100644 --- a/extension/src/LoadGameOpenVic.cpp +++ b/extension/src/LoadGameOpenVic.cpp @@ -11,19 +11,19 @@ using namespace OpenVic; Error GameSingleton::_generate_terrain_texture_array() { Error err = OK; - if (terrain_variants.get_item_count() == 0) { + if (terrain_variants.size() == 0) { UtilityFunctions::push_error("Failed to load terrain textures!"); return FAILED; } // TerrainVariant count is limited by the data type representing it in the map image - if (terrain_variants.get_item_count() > TerrainVariant::MAX_TERRIN_VARIANT_COUNT) { + if (terrain_variants.size() > TerrainVariant::MAX_TERRIN_VARIANT_COUNT) { UtilityFunctions::push_error("Too many terrain textures - all after the first ", static_cast(TerrainVariant::MAX_TERRIN_VARIANT_COUNT), " will be ignored"); err = FAILED; } Array terrain_images; - for (size_t i = 0; i < terrain_variants.get_item_count() && i < TerrainVariant::MAX_TERRIN_VARIANT_COUNT; ++i) { + for (size_t i = 0; i < terrain_variants.size() && i < TerrainVariant::MAX_TERRIN_VARIANT_COUNT; ++i) { TerrainVariant const& var = *terrain_variants.get_item_by_index(i); terrain_variant_map[var.get_colour()] = i; terrain_images.append(var.get_image()); diff --git a/game/localisation/en_GB/mapmodes.csv b/game/localisation/en_GB/mapmodes.csv index 8fa5798..bcb466f 100644 --- a/game/localisation/en_GB/mapmodes.csv +++ b/game/localisation/en_GB/mapmodes.csv @@ -8,3 +8,4 @@ mapmode_rgo;RGO mapmode_infrastructure;Infrastructure mapmode_population;Population Density mapmode_culture;Nationality +mapmode_religion;Religion diff --git a/game/src/Game/GameStart.gd b/game/src/Game/GameStart.gd index a5524d6..422a42a 100644 --- a/game/src/Game/GameStart.gd +++ b/game/src/Game/GameStart.gd @@ -25,12 +25,7 @@ func _ready() -> void: loading_screen.start_loading_screen(_initialize_game) -# REQUIREMENTS -# * FS-333, FS-334, FS-335, FS-341 -func _initialize_game() -> void: - GameSingleton.setup_logger() - loading_screen.try_update_loading_screen(5) - +func _load_compatibility_mode(): # Set this to your Vic2 install dir or a mod's dir to enable compatibility mode # (this won't work for mods which rely on vanilla map assets, copy missing assets # into the mod's dir for a temporary fix) @@ -48,21 +43,24 @@ func _initialize_game() -> void: # Example for adding mod paths #compatibility_mode_paths.push_back("C:/Program Files (x86)/Steam/steamapps/common/Victoria 2/mod/TGC") + if GameSingleton.load_defines_compatibility_mode(compatibility_mode_paths) != OK: + push_error("Errors loading game defines!") + +# REQUIREMENTS +# * FS-333, FS-334, FS-335, FS-341 +func _initialize_game() -> void: var start := Time.get_ticks_usec() + loading_screen.try_update_loading_screen(0) + GameSingleton.setup_logger() - loading_screen.try_update_loading_screen(15) - loading_screen.try_update_loading_screen(25) Localisation.initialize() - loading_screen.try_update_loading_screen(45) - loading_screen.try_update_loading_screen(50, true) + loading_screen.try_update_loading_screen(15, true) - # TODO: Loading takes way too long to keep the LoadingScreen at 50% - # Should either split this up or seperately multithread the compatibility mode loader - # Or both and emit a signal that allows us to add percentages to the LoadingScreen - if GameSingleton.load_defines_compatibility_mode(compatibility_mode_paths) != OK: - push_error("Errors loading game defines!") + _load_compatibility_mode() + loading_screen.try_update_loading_screen(75, true) loading_screen.try_update_loading_screen(100) + var end := Time.get_ticks_usec() print("Loading took ", float(end - start) / 1000000, " seconds") -- cgit v1.2.3-56-ga3b1 From 70c040d042cb536e5ce16b0cfff0e0afa39e8ed7 Mon Sep 17 00:00:00 2001 From: Hop311 Date: Thu, 14 Sep 2023 08:52:25 +0100 Subject: Logger::warning, format cleanup + req comments --- .clang-format | 17 +- extension/deps/openvic-simulation | 2 +- extension/src/Checksum.hpp | 37 -- extension/src/GameSingleton.cpp | 394 -------------------- extension/src/GameSingleton.hpp | 158 --------- extension/src/LoadGameCompatibility.cpp | 110 ------ extension/src/LoadGameOpenVic.cpp | 130 ------- extension/src/LoadLocalisation.cpp | 136 ------- extension/src/LoadLocalisation.hpp | 28 -- extension/src/MapMesh.cpp | 150 -------- extension/src/MapMesh.hpp | 34 -- extension/src/Utilities.cpp | 106 ------ extension/src/Utilities.hpp | 30 -- extension/src/openvic-extension/Checksum.hpp | 37 ++ extension/src/openvic-extension/GameSingleton.cpp | 395 +++++++++++++++++++++ extension/src/openvic-extension/GameSingleton.hpp | 158 +++++++++ .../openvic-extension/LoadGameCompatibility.cpp | 110 ++++++ .../src/openvic-extension/LoadGameOpenVic.cpp | 132 +++++++ .../src/openvic-extension/LoadLocalisation.cpp | 136 +++++++ .../src/openvic-extension/LoadLocalisation.hpp | 28 ++ extension/src/openvic-extension/MapMesh.cpp | 150 ++++++++ extension/src/openvic-extension/MapMesh.hpp | 34 ++ extension/src/openvic-extension/Utilities.cpp | 106 ++++++ extension/src/openvic-extension/Utilities.hpp | 30 ++ extension/src/openvic-extension/register_types.cpp | 63 ++++ extension/src/openvic-extension/register_types.hpp | 6 + extension/src/register_types.cpp | 63 ---- extension/src/register_types.h | 6 - .../GameSession/MapControlPanel/MapControlPanel.gd | 4 +- .../ProvinceOverviewPanel.tscn | 3 + game/src/Game/LoadingScreen.tscn | 1 + 31 files changed, 1404 insertions(+), 1390 deletions(-) delete mode 100644 extension/src/Checksum.hpp delete mode 100644 extension/src/GameSingleton.cpp delete mode 100644 extension/src/GameSingleton.hpp delete mode 100644 extension/src/LoadGameCompatibility.cpp delete mode 100644 extension/src/LoadGameOpenVic.cpp delete mode 100644 extension/src/LoadLocalisation.cpp delete mode 100644 extension/src/LoadLocalisation.hpp delete mode 100644 extension/src/MapMesh.cpp delete mode 100644 extension/src/MapMesh.hpp delete mode 100644 extension/src/Utilities.cpp delete mode 100644 extension/src/Utilities.hpp create mode 100644 extension/src/openvic-extension/Checksum.hpp create mode 100644 extension/src/openvic-extension/GameSingleton.cpp create mode 100644 extension/src/openvic-extension/GameSingleton.hpp create mode 100644 extension/src/openvic-extension/LoadGameCompatibility.cpp create mode 100644 extension/src/openvic-extension/LoadGameOpenVic.cpp create mode 100644 extension/src/openvic-extension/LoadLocalisation.cpp create mode 100644 extension/src/openvic-extension/LoadLocalisation.hpp create mode 100644 extension/src/openvic-extension/MapMesh.cpp create mode 100644 extension/src/openvic-extension/MapMesh.hpp create mode 100644 extension/src/openvic-extension/Utilities.cpp create mode 100644 extension/src/openvic-extension/Utilities.hpp create mode 100644 extension/src/openvic-extension/register_types.cpp create mode 100644 extension/src/openvic-extension/register_types.hpp delete mode 100644 extension/src/register_types.cpp delete mode 100644 extension/src/register_types.h (limited to 'game/src') diff --git a/.clang-format b/.clang-format index bfa989e..e94fe0a 100644 --- a/.clang-format +++ b/.clang-format @@ -24,7 +24,7 @@ SpaceBeforeAssignmentOperators: true SpaceAfterTemplateKeyword: false SpaceAfterLogicalNot: false PointerAlignment: Left -PackConstructorInitializers: CurrentLine +PackConstructorInitializers: BinPack NamespaceIndentation: All LambdaBodyIndentation: Signature IndentExternBlock: Indent @@ -43,8 +43,11 @@ AllowShortIfStatementsOnASingleLine: AllIfsAndElse AllowShortEnumsOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AlignTrailingComments: true -AlignEscapedNewlines: Left -AlignAfterOpenBracket: DontAlign +AlignEscapedNewlines: DontAlign +AlignAfterOpenBracket: BlockIndent +BinPackArguments: true +BinPackParameters: true +IndentRequiresClause: false AccessModifierOffset: -4 IncludeCategories: - Regex: <[[:alnum:]_]+> @@ -53,7 +56,11 @@ IncludeCategories: Priority: 2 - Regex: ^ - -namespace OpenVic { - class Checksum : public godot::Object { - GDCLASS(Checksum, godot::Object) - - // BEGIN BOILERPLATE - inline static Checksum* _checksum = nullptr; - - protected: - static void _bind_methods() { - godot::ClassDB::bind_method(godot::D_METHOD("get_checksum_text"), &Checksum::get_checksum_text); - } - - public: - inline static Checksum* get_singleton() { return _checksum; } - - inline Checksum() { - ERR_FAIL_COND(_checksum != nullptr); - _checksum = this; - } - inline ~Checksum() { - ERR_FAIL_COND(_checksum != this); - _checksum = nullptr; - } - // END BOILERPLATE - - /* REQUIREMENTS: - * DAT-8 - */ - inline godot::String get_checksum_text() { - return godot::String("1234abcd"); - } - }; -} diff --git a/extension/src/GameSingleton.cpp b/extension/src/GameSingleton.cpp deleted file mode 100644 index b435f05..0000000 --- a/extension/src/GameSingleton.cpp +++ /dev/null @@ -1,394 +0,0 @@ -#include "GameSingleton.hpp" - -#include - -#include "openvic-simulation/utility/Logger.hpp" - -#include "Utilities.hpp" - -using namespace godot; -using namespace OpenVic; - -TerrainVariant::TerrainVariant(const std::string_view new_identfier, - colour_t new_colour, Ref const& new_image) - : HasIdentifierAndColour { new_identfier, new_colour, true }, - image { new_image } {} - -Ref TerrainVariant::get_image() const { - return image; -} - -GameSingleton* GameSingleton::singleton = nullptr; - -void GameSingleton::_bind_methods() { - ClassDB::bind_static_method("GameSingleton", D_METHOD("setup_logger"), &GameSingleton::setup_logger); - ClassDB::bind_method(D_METHOD("load_defines_compatibility_mode", "file_paths"), &GameSingleton::load_defines_compatibility_mode); - ClassDB::bind_method(D_METHOD("lookup_file", "path"), &GameSingleton::lookup_file); - ClassDB::bind_method(D_METHOD("setup_game"), &GameSingleton::setup_game); - - ClassDB::bind_method(D_METHOD("get_province_index_from_uv_coords", "coords"), &GameSingleton::get_province_index_from_uv_coords); - ClassDB::bind_method(D_METHOD("get_province_info_from_index", "index"), &GameSingleton::get_province_info_from_index); - ClassDB::bind_method(D_METHOD("get_width"), &GameSingleton::get_width); - ClassDB::bind_method(D_METHOD("get_height"), &GameSingleton::get_height); - ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &GameSingleton::get_aspect_ratio); - ClassDB::bind_method(D_METHOD("get_terrain_texture"), &GameSingleton::get_terrain_texture); - ClassDB::bind_method(D_METHOD("get_province_shape_image_subdivisions"), &GameSingleton::get_province_shape_image_subdivisions); - ClassDB::bind_method(D_METHOD("get_province_shape_texture"), &GameSingleton::get_province_shape_texture); - ClassDB::bind_method(D_METHOD("get_province_colour_texture"), &GameSingleton::get_province_colour_texture); - - ClassDB::bind_method(D_METHOD("get_mapmode_count"), &GameSingleton::get_mapmode_count); - ClassDB::bind_method(D_METHOD("get_mapmode_identifier", "index"), &GameSingleton::get_mapmode_identifier); - ClassDB::bind_method(D_METHOD("set_mapmode", "identifier"), &GameSingleton::set_mapmode); - ClassDB::bind_method(D_METHOD("get_selected_province_index"), &GameSingleton::get_selected_province_index); - ClassDB::bind_method(D_METHOD("set_selected_province", "index"), &GameSingleton::set_selected_province); - - ClassDB::bind_method(D_METHOD("expand_building", "province_index", "building_type_identifier"), &GameSingleton::expand_building); - ClassDB::bind_method(D_METHOD("get_good_icon_texture", "identifier"), &GameSingleton::get_good_icon_texture); - - ClassDB::bind_method(D_METHOD("set_paused", "paused"), &GameSingleton::set_paused); - ClassDB::bind_method(D_METHOD("toggle_paused"), &GameSingleton::toggle_paused); - ClassDB::bind_method(D_METHOD("is_paused"), &GameSingleton::is_paused); - ClassDB::bind_method(D_METHOD("increase_speed"), &GameSingleton::increase_speed); - ClassDB::bind_method(D_METHOD("decrease_speed"), &GameSingleton::decrease_speed); - ClassDB::bind_method(D_METHOD("can_increase_speed"), &GameSingleton::can_increase_speed); - ClassDB::bind_method(D_METHOD("can_decrease_speed"), &GameSingleton::can_decrease_speed); - ClassDB::bind_method(D_METHOD("get_longform_date"), &GameSingleton::get_longform_date); - ClassDB::bind_method(D_METHOD("try_tick"), &GameSingleton::try_tick); - - ADD_SIGNAL(MethodInfo("state_updated")); - ADD_SIGNAL(MethodInfo("province_selected", PropertyInfo(Variant::INT, "index"))); - - ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_province_key"), &GameSingleton::get_province_info_province_key); - 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); - - ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_building_key"), &GameSingleton::get_building_info_building_key); - ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_level_key"), &GameSingleton::get_building_info_level_key); - ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_expansion_state_key"), &GameSingleton::get_building_info_expansion_state_key); - 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, - 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() { - return singleton; -} - -void GameSingleton::_on_state_updated() { - _update_colour_image(); - emit_signal("state_updated"); -} - -/* REQUIREMENTS: - * MAP-21, MAP-23, MAP-25, MAP-32, MAP-33 - */ -GameSingleton::GameSingleton() : game_manager { [this]() { _on_state_updated(); } }, - terrain_variants { "terrain variants" } { - ERR_FAIL_COND(singleton != nullptr); - singleton = this; -} - -void GameSingleton::setup_logger() { - Logger::set_info_func([](std::string&& str) { UtilityFunctions::print(std_to_godot_string(str)); }); - Logger::set_error_func([](std::string&& str) { UtilityFunctions::push_error(std_to_godot_string(str)); }); -} - -GameSingleton::~GameSingleton() { - ERR_FAIL_COND(singleton != this); - singleton = nullptr; -} - -Error GameSingleton::setup_game() { - bool ret = game_manager.setup(); - ret &= dataloader.load_pop_history(game_manager, "history/pops/" + game_manager.get_today().to_string()); - return ERR(ret); -} - -int32_t GameSingleton::get_province_index_from_uv_coords(Vector2 const& coords) const { - const size_t x_mod_w = UtilityFunctions::fposmod(coords.x, 1.0f) * get_width(); - const size_t y_mod_h = UtilityFunctions::fposmod(coords.y, 1.0f) * get_height(); - return game_manager.map.get_province_index_at(x_mod_w, y_mod_h); -} - -StringName const& GameSingleton::get_province_info_province_key() { - static const StringName key = "province"; - return key; -} -StringName const& GameSingleton::get_province_info_region_key() { - static const StringName key = "region"; - return key; -} -StringName const& GameSingleton::get_province_info_life_rating_key() { - static const StringName key = "life_rating"; - return key; -} -StringName const& GameSingleton::get_province_info_total_population_key() { - static const StringName key = "total_population"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_types_key() { - static const StringName key = "pop_types"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_ideologies_key() { - static const StringName key = "pop_ideologies"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_cultures_key() { - static const StringName key = "pop_cultures"; - return key; -} -StringName const& GameSingleton::get_province_info_rgo_key() { - static const StringName key = "rgo"; - return key; -} -StringName const& GameSingleton::get_province_info_buildings_key() { - static const StringName key = "buildings"; - return key; -} - -StringName const& GameSingleton::get_building_info_building_key() { - static const StringName key = "building"; - return key; -} -StringName const& GameSingleton::get_building_info_level_key() { - static const StringName key = "level"; - return key; -} -StringName const& GameSingleton::get_building_info_expansion_state_key() { - static const StringName key = "expansion_state"; - return key; -} -StringName const& GameSingleton::get_building_info_start_date_key() { - static const StringName key = "start_date"; - return key; -} -StringName const& GameSingleton::get_building_info_end_date_key() { - static const StringName key = "end_date"; - return key; -} -StringName const& GameSingleton::get_building_info_expansion_progress_key() { - static const StringName key = "expansion_progress"; - return key; -} - -StringName const& GameSingleton::get_piechart_info_size_key() { - static const StringName key = "size"; - return key; -} -StringName const& GameSingleton::get_piechart_info_colour_key() { - static const StringName key = "colour"; - return key; -} - -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 {}; - Dictionary ret; - - ret[get_province_info_province_key()] = std_to_godot_string(province->get_identifier()); - - Region const* region = province->get_region(); - if (region != nullptr) ret[get_province_info_region_key()] = std_to_godot_string(region->get_identifier()); - - Good const* rgo = province->get_rgo(); - if (rgo != nullptr) ret[get_province_info_rgo_key()] = std_to_godot_string(rgo->get_identifier()); - - 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 const& buildings = province->get_buildings(); - if (!buildings.empty()) { - Array buildings_array; - buildings_array.resize(buildings.size()); - for (size_t idx = 0; idx < buildings.size(); ++idx) { - Building const& building = buildings[idx]; - - Dictionary building_dict; - building_dict[get_building_info_building_key()] = std_to_godot_string(building.get_identifier()); - building_dict[get_building_info_level_key()] = static_cast(building.get_level()); - building_dict[get_building_info_expansion_state_key()] = static_cast(building.get_expansion_state()); - building_dict[get_building_info_start_date_key()] = std_to_godot_string(building.get_start_date().to_string()); - building_dict[get_building_info_end_date_key()] = std_to_godot_string(building.get_end_date().to_string()); - building_dict[get_building_info_expansion_progress_key()] = building.get_expansion_progress(); - - buildings_array[idx] = building_dict; - } - ret[get_province_info_buildings_key()] = buildings_array; - } - return ret; -} - -int32_t GameSingleton::get_width() const { - return game_manager.map.get_width(); -} - -int32_t GameSingleton::get_height() const { - return game_manager.map.get_height(); -} - -float GameSingleton::get_aspect_ratio() const { - return static_cast(get_width()) / static_cast(get_height()); -} - -Ref GameSingleton::get_terrain_texture() const { - return terrain_texture; -} - -Vector2i GameSingleton::get_province_shape_image_subdivisions() const { - return image_subdivisions; -} - -Ref GameSingleton::get_province_shape_texture() const { - return province_shape_texture; -} - -Ref GameSingleton::get_province_colour_texture() const { - return province_colour_texture; -} - -Error GameSingleton::_update_colour_image() { - static PackedByteArray colour_data_array; - static constexpr int64_t colour_data_array_size = (static_cast(Province::MAX_INDEX) + 1) * Map::MAPMODE_COLOUR_SIZE; - colour_data_array.resize(colour_data_array_size); - - Error err = OK; - if (!game_manager.map.generate_mapmode_colours(mapmode_index, colour_data_array.ptrw())) - err = FAILED; - - static constexpr int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * 4); - if (province_colour_image.is_null()) { - province_colour_image.instantiate(); - ERR_FAIL_NULL_V_EDMSG(province_colour_image, FAILED, - "Failed to create province colour image"); - } - province_colour_image->set_data(PROVINCE_INDEX_SQRT, PROVINCE_INDEX_SQRT, - false, Image::FORMAT_RGBA8, colour_data_array); - if (province_colour_texture.is_null()) { - province_colour_texture = ImageTexture::create_from_image(province_colour_image); - ERR_FAIL_NULL_V_EDMSG(province_colour_texture, FAILED, - "Failed to create province colour texture"); - } else province_colour_texture->update(province_colour_image); - return err; -} - -int32_t GameSingleton::get_mapmode_count() const { - return game_manager.map.get_mapmode_count(); -} - -String GameSingleton::get_mapmode_identifier(int32_t index) const { - Mapmode const* mapmode = game_manager.map.get_mapmode_by_index(index); - if (mapmode != nullptr) return std_to_godot_string(mapmode->get_identifier()); - return String {}; -} - -Error GameSingleton::set_mapmode(String const& identifier) { - Mapmode const* mapmode = game_manager.map.get_mapmode_by_identifier(godot_to_std_string(identifier)); - if (mapmode == nullptr) { - UtilityFunctions::push_error("Failed to set mapmode to: ", identifier); - return FAILED; - } - mapmode_index = mapmode->get_index(); - _update_colour_image(); - return OK; -} - -int32_t GameSingleton::get_selected_province_index() const { - return game_manager.map.get_selected_province_index(); -} - -void GameSingleton::set_selected_province(int32_t index) { - game_manager.map.set_selected_province(index); - _update_colour_image(); - emit_signal("province_selected", index); -} - -Error GameSingleton::expand_building(int32_t province_index, String const& building_type_identifier) { - if (!game_manager.expand_building(province_index, godot_to_std_string(building_type_identifier))) { - UtilityFunctions::push_error("Failed to expand ", building_type_identifier, " at province index ", province_index); - return FAILED; - } - return OK; -} - -Ref GameSingleton::get_good_icon_texture(String const& identifier) const { - return good_icons.get(identifier, {}); -} - -void GameSingleton::set_paused(bool paused) { - game_manager.clock.isPaused = paused; -} - -void GameSingleton::toggle_paused() { - game_manager.clock.isPaused = !game_manager.clock.isPaused; -} - -bool GameSingleton::is_paused() const { - return game_manager.clock.isPaused; -} - -void GameSingleton::increase_speed() { - game_manager.clock.increaseSimulationSpeed(); -} - -void GameSingleton::decrease_speed() { - game_manager.clock.decreaseSimulationSpeed(); -} - -bool GameSingleton::can_increase_speed() const { - return game_manager.clock.canIncreaseSimulationSpeed(); -} - -bool GameSingleton::can_decrease_speed() const { - return game_manager.clock.canDecreaseSimulationSpeed(); -} - -String GameSingleton::get_longform_date() const { - return std_to_godot_string(game_manager.get_today().to_string()); -} - -void GameSingleton::try_tick() { - game_manager.clock.conditionallyAdvanceGame(); -} diff --git a/extension/src/GameSingleton.hpp b/extension/src/GameSingleton.hpp deleted file mode 100644 index ac9d160..0000000 --- a/extension/src/GameSingleton.hpp +++ /dev/null @@ -1,158 +0,0 @@ -#pragma once - -#include -#include - -#include "openvic-simulation/GameManager.hpp" -#include "openvic-simulation/dataloader/Dataloader.hpp" - -namespace OpenVic { - - struct TerrainVariant : HasIdentifierAndColour { - friend class GameSingleton; - - private: - const godot::Ref image; - - TerrainVariant(const std::string_view new_identfier, colour_t new_colour, - godot::Ref const& new_image); - - public: - static constexpr size_t MAX_TERRIN_VARIANT_COUNT = 1 << (8 * sizeof(Map::terrain_t)); - - TerrainVariant(TerrainVariant&&) = default; - - godot::Ref get_image() const; - }; - - class GameSingleton : public godot::Object { - GDCLASS(GameSingleton, godot::Object) - - static GameSingleton* singleton; - - GameManager game_manager; - Dataloader dataloader; - - godot::Vector2i image_subdivisions; - godot::Ref province_shape_texture; - godot::Ref province_colour_image; - godot::Ref province_colour_texture; - Mapmode::index_t mapmode_index = 0; - IdentifierRegistry terrain_variants; - Map::terrain_variant_map_t terrain_variant_map; - godot::Ref terrain_texture; - godot::Dictionary good_icons; - - godot::Error _generate_terrain_texture_array(); - godot::Error _load_map_images(godot::String const& province_image_path, godot::String const& terrain_image_path, bool flip_vertical = false); - - godot::Error _load_terrain_variants_compatibility_mode(godot::String const& terrain_image_path, godot::String const& terrain_texturesheet_path); - - /* Generate the province_colour_texture from the current mapmode. - */ - 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 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(); - ~GameSingleton(); - - static void setup_logger(); - - /* Load the game's defines in compatiblity mode from the filepath - * pointing to the defines folder. - */ - godot::Error load_defines_compatibility_mode(godot::PackedStringArray const& file_paths); - - godot::String lookup_file(godot::String const& path) const; - - /* Post-load/restart game setup - reset the game to post-load state - * and (re)generate starting data, e.g. buildings. - */ - godot::Error setup_game(); - - int32_t get_province_index_from_uv_coords(godot::Vector2 const& coords) const; - - static godot::StringName const& get_province_info_province_key(); - static godot::StringName const& get_province_info_region_key(); - static godot::StringName const& get_province_info_life_rating_key(); - static godot::StringName const& get_province_info_total_population_key(); - static godot::StringName const& get_province_info_pop_types_key(); - static godot::StringName const& get_province_info_pop_ideologies_key(); - static godot::StringName const& get_province_info_pop_cultures_key(); - static godot::StringName const& get_province_info_rgo_key(); - static godot::StringName const& get_province_info_buildings_key(); - - static godot::StringName const& get_building_info_building_key(); - static godot::StringName const& get_building_info_level_key(); - static godot::StringName const& get_building_info_expansion_state_key(); - static godot::StringName const& get_building_info_start_date_key(); - static godot::StringName const& get_building_info_end_date_key(); - static godot::StringName const& get_building_info_expansion_progress_key(); - - static godot::StringName const& get_piechart_info_size_key(); - static godot::StringName const& get_piechart_info_colour_key(); - - /* Get info to display in Province Overview Panel, packaged in - * a Dictionary using the StringNames above as keys. - */ - godot::Dictionary get_province_info_from_index(int32_t index) const; - - int32_t get_width() const; - int32_t get_height() const; - float get_aspect_ratio() const; - - /* The cosmetic terrain textures stored in a Texture2DArray. - */ - godot::Ref get_terrain_texture() const; - - /* Number of (vertical, horizontal) subdivisions the province shape image - * was split into when making the province_shape_texture to ensure no - * piece had a dimension greater than 16383. - */ - godot::Vector2i get_province_shape_image_subdivisions() const; - - /* The map, encoded in RGB8 with RG representing province index and B representing terrain texture. - * To support a wider range of GPUs, the image is divided so that no piece has a dimension - * greater than 16383 and the pieces are stored in a Texture2DArray. - */ - godot::Ref get_province_shape_texture() const; - - /* The colour each province should be tinted, arranged in - * index order into a 256x256 RGB8 texture. - */ - godot::Ref get_province_colour_texture() const; - - int32_t get_mapmode_count() const; - godot::String get_mapmode_identifier(int32_t index) const; - godot::Error set_mapmode(godot::String const& identifier); - int32_t get_selected_province_index() const; - void set_selected_province(int32_t index); - - godot::Error expand_building(int32_t province_index, godot::String const& building_type_identifier); - godot::Ref get_good_icon_texture(godot::String const& identifier) const; - - void set_paused(bool paused); - void toggle_paused(); - bool is_paused() const; - void increase_speed(); - void decrease_speed(); - bool can_increase_speed() const; - bool can_decrease_speed() const; - godot::String get_longform_date() const; - void try_tick(); - }; -} diff --git a/extension/src/LoadGameCompatibility.cpp b/extension/src/LoadGameCompatibility.cpp deleted file mode 100644 index bed7442..0000000 --- a/extension/src/LoadGameCompatibility.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "GameSingleton.hpp" - -#include -#include - -#include "openvic-simulation/utility/BMP.hpp" - -#include "Utilities.hpp" - -using namespace godot; -using namespace OpenVic; - -Error GameSingleton::_load_terrain_variants_compatibility_mode(String const& terrain_image_path, String const& terrain_texturesheet_path) { - // Read BMP's palette to determine terrain variant colours which texture they're associated with - BMP bmp; - if (!(bmp.open(godot_to_std_string(terrain_image_path).c_str()) && bmp.read_header() && bmp.read_palette())) { - UtilityFunctions::push_error("Failed to read BMP palette from compatibility mode terrain image: ", terrain_image_path); - return FAILED; - } - std::vector const& palette = bmp.get_palette(); - static constexpr int32_t SHEET_DIMS = 8, PALETTE_SIZE = SHEET_DIMS * SHEET_DIMS; - if (palette.size() == 0 || palette.size() < PALETTE_SIZE) { - UtilityFunctions::push_error("Invalid BMP palette size for terrain image: ", static_cast(palette.size()), " (expected ", PALETTE_SIZE, ")"); - return FAILED; - } - - // Load the terrain texture sheet and prepare to slice it up - Ref terrain_sheet = load_godot_image(terrain_texturesheet_path); - if (terrain_sheet.is_null()) { - UtilityFunctions::push_error("Failed to load terrain texture sheet: ", terrain_texturesheet_path); - return FAILED; - } - terrain_sheet->flip_y(); - const int32_t sheet_width = terrain_sheet->get_width(), sheet_height = terrain_sheet->get_height(); - if (sheet_width < 1 || sheet_width % SHEET_DIMS != 0 || sheet_width != sheet_height) { - UtilityFunctions::push_error("Invalid terrain texture sheet dims: ", sheet_width, "x", sheet_height, " (must be square with dims positive multiples of ", SHEET_DIMS, ")"); - return FAILED; - } - const int32_t slice_size = sheet_width / SHEET_DIMS; - - { - static constexpr colour_t TERRAIN_WATER_INDEX_COLOUR = 0xFFFFFF; - Ref water_image = Image::create(slice_size, slice_size, false, terrain_sheet->get_format()); - ERR_FAIL_NULL_V_EDMSG(water_image, FAILED, "Failed to create water terrain image"); - water_image->fill({ 0.1f, 0.1f, 0.5f }); - terrain_variants.add_item({ "terrain_water", TERRAIN_WATER_INDEX_COLOUR, water_image }); - } - Error err = OK; - for (int32_t idx = 0; idx < PALETTE_SIZE; ++idx) { - const Rect2i slice { (idx % SHEET_DIMS) * slice_size, (7 - (idx / SHEET_DIMS)) * slice_size, slice_size, slice_size }; - const Ref 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; - continue; - } - if (!terrain_variants.add_item({ "terrain_" + std::to_string(idx), palette[idx], terrain_image })) err = FAILED; - } - terrain_variants.lock(); - if (_generate_terrain_texture_array() != OK) return FAILED; - return err; -} - -Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& file_paths) { - static const std::filesystem::path province_image_file = "map/provinces.bmp"; - static const std::filesystem::path terrain_image_file = "map/terrain.bmp"; - static const std::filesystem::path terrain_texture_file = "map/terrain/texturesheet.tga"; - - std::vector roots; - for (String const& path : file_paths) { - roots.push_back(godot_to_std_string(path)); - } - - Error err = OK; - - if (!dataloader.set_roots(roots)) { - Logger::error("Failed to set dataloader roots!"); - err = FAILED; - } - - if (!dataloader.load_defines(game_manager)) { - UtilityFunctions::push_error("Failed to load defines!"); - err = FAILED; - } - - game_manager.map.lock_regions(); - if (_load_terrain_variants_compatibility_mode( - std_to_godot_string(dataloader.lookup_file(terrain_image_file).string()), - std_to_godot_string(dataloader.lookup_file(terrain_texture_file).string()) - ) != OK) { - UtilityFunctions::push_error("Failed to load terrain variants!"); - err = FAILED; - } - if (_load_map_images( - std_to_godot_string(dataloader.lookup_file(province_image_file).string()), - std_to_godot_string(dataloader.lookup_file(terrain_image_file).string()), - true) != OK) { - UtilityFunctions::push_error("Failed to load map images!"); - err = FAILED; - } - if (!game_manager.load_hardcoded_defines()) { - UtilityFunctions::push_error("Failed to hardcoded defines!"); - err = FAILED; - } - return err; -} - -String GameSingleton::lookup_file(String const& path) const { - return std_to_godot_string(dataloader.lookup_file(godot_to_std_string(path)).string()); -} diff --git a/extension/src/LoadGameOpenVic.cpp b/extension/src/LoadGameOpenVic.cpp deleted file mode 100644 index fafc864..0000000 --- a/extension/src/LoadGameOpenVic.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "GameSingleton.hpp" - -#include -#include -#include - -#include "Utilities.hpp" - -using namespace godot; -using namespace OpenVic; - -Error GameSingleton::_generate_terrain_texture_array() { - Error err = OK; - if (terrain_variants.size() == 0) { - UtilityFunctions::push_error("Failed to load terrain textures!"); - return FAILED; - } - // TerrainVariant count is limited by the data type representing it in the map image - if (terrain_variants.size() > TerrainVariant::MAX_TERRIN_VARIANT_COUNT) { - UtilityFunctions::push_error("Too many terrain textures - all after the first ", - static_cast(TerrainVariant::MAX_TERRIN_VARIANT_COUNT), " will be ignored"); - err = FAILED; - } - - Array terrain_images; - for (size_t i = 0; i < terrain_variants.size() && i < TerrainVariant::MAX_TERRIN_VARIANT_COUNT; ++i) { - TerrainVariant const& var = *terrain_variants.get_item_by_index(i); - terrain_variant_map[var.get_colour()] = i; - terrain_images.append(var.get_image()); - } - - terrain_texture.instantiate(); - if (terrain_texture->create_from_images(terrain_images) != OK) { - UtilityFunctions::push_error("Failed to create terrain texture array!"); - return FAILED; - } - return err; -} - -Error GameSingleton::_load_map_images(String const& province_image_path, String const& terrain_image_path, bool flip_vertical) { - if (province_shape_texture.is_valid()) { - UtilityFunctions::push_error("Map images have already been loaded, cannot load: ", province_image_path, " and ", terrain_image_path); - return FAILED; - } - - // Load images - Ref province_image = load_godot_image(province_image_path); - if (province_image.is_null()) { - UtilityFunctions::push_error("Failed to load province image: ", province_image_path); - return FAILED; - } - Ref terrain_image = load_godot_image(terrain_image_path); - if (terrain_image.is_null()) { - UtilityFunctions::push_error("Failed to load terrain image: ", terrain_image_path); - return FAILED; - } - - if (flip_vertical) { - province_image->flip_y(); - terrain_image->flip_y(); - } - - // Validate dimensions and format - Error err = OK; - const Vector2i province_dims = province_image->get_size(), terrain_dims = terrain_image->get_size(); - if (province_dims.x < 1 || province_dims.y < 1) { - UtilityFunctions::push_error("Invalid dimensions (", province_dims.x, "x", province_dims.y, ") for province image: ", province_image_path); - err = FAILED; - } - if (province_dims != terrain_dims) { - UtilityFunctions::push_error("Invalid dimensions (", terrain_dims.x, "x", terrain_dims.y, ") for terrain image: ", - terrain_image_path, " (must match province image: (", province_dims.x, "x", province_dims.x, "))"); - err = FAILED; - } - static constexpr Image::Format expected_format = Image::FORMAT_RGB8; - if (province_image->get_format() == Image::FORMAT_RGBA8) province_image->convert(expected_format); - if (terrain_image->get_format() == Image::FORMAT_RGBA8) terrain_image->convert(expected_format); - if (province_image->get_format() != expected_format) { - UtilityFunctions::push_error("Invalid format (", province_image->get_format(), ", should be ", expected_format, ") for province image: ", province_image_path); - err = FAILED; - } - if (terrain_image->get_format() != expected_format) { - UtilityFunctions::push_error("Invalid format (", terrain_image->get_format(), ", should be ", expected_format, ") for terrain image: ", terrain_image_path); - err = FAILED; - } - if (err != OK) return err; - - // 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, false)) 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 - for (int i = 0; i < 2; ++i) - for (image_subdivisions[i] = 1; province_dims[i] / image_subdivisions[i] > GPU_DIM_LIMIT || - province_dims[i] % image_subdivisions[i] != 0; ++image_subdivisions[i]); - - Map::shape_pixel_t const* province_shape_data = game_manager.map.get_province_shape_image().data(); - const Vector2i divided_dims = province_dims / image_subdivisions; - Array province_shape_images; - province_shape_images.resize(image_subdivisions.x * image_subdivisions.y); - 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), - province_shape_data + (v * divided_dims.y + y) * province_dims.x + u * divided_dims.x, - divided_dims.x * sizeof(Map::shape_pixel_t)); - - const Ref province_shape_subimage = Image::create_from_data(divided_dims.x, divided_dims.y, false, Image::FORMAT_RGB8, index_data_array); - if (province_shape_subimage.is_null()) { - UtilityFunctions::push_error("Failed to create province shape image (", u, ", ", v, ")"); - err = FAILED; - } - province_shape_images[u + v * image_subdivisions.x] = province_shape_subimage; - } - } - - province_shape_texture.instantiate(); - if (province_shape_texture->create_from_images(province_shape_images) != OK) { - UtilityFunctions::push_error("Failed to create terrain texture array!"); - err = FAILED; - } - - if (_update_colour_image() != OK) err = FAILED; - - return err; -} diff --git a/extension/src/LoadLocalisation.cpp b/extension/src/LoadLocalisation.cpp deleted file mode 100644 index dc7702c..0000000 --- a/extension/src/LoadLocalisation.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "LoadLocalisation.hpp" - -#include -#include -#include -#include - -using namespace godot; -using namespace OpenVic; - -LoadLocalisation* LoadLocalisation::singleton = nullptr; - -void LoadLocalisation::_bind_methods() { - ClassDB::bind_method(D_METHOD("load_file", "file_path", "locale"), &LoadLocalisation::load_file); - ClassDB::bind_method(D_METHOD("load_locale_dir", "dir_path", "locale"), &LoadLocalisation::load_locale_dir); - ClassDB::bind_method(D_METHOD("load_localisation_dir", "dir_path"), &LoadLocalisation::load_localisation_dir); -} - -LoadLocalisation* LoadLocalisation::get_singleton() { - return singleton; -} - -LoadLocalisation::LoadLocalisation() { - ERR_FAIL_COND(singleton != nullptr); - singleton = this; -} - -LoadLocalisation::~LoadLocalisation() { - ERR_FAIL_COND(singleton != this); - singleton = nullptr; -} - -Error LoadLocalisation::_load_file_into_translation(String const& file_path, Ref translation) { - const Ref file = FileAccess::open(file_path, FileAccess::ModeFlags::READ); - Error err = FileAccess::get_open_error(); - if (err != OK || file.is_null()) { - UtilityFunctions::push_error("Failed to load localisation file: ", file_path); - return err == OK ? FAILED : err; - } - int line_number = 0; - while (!file->eof_reached()) { - static const String delimeter = ";"; - const PackedStringArray line = file->get_csv_line(delimeter); - line_number++; - if (line.size() < 2 || line[0].is_empty() || line[1].is_empty()) { - if (!line[0].is_empty()) { - UtilityFunctions::push_warning("Key \"", line[0], "\" missing value on line ", line_number, " in file: ", file_path); - err = FAILED; - } else if (line.size() >= 2 && !line[1].is_empty()) { - UtilityFunctions::push_warning("Value \"", line[1], "\" missing key on line ", line_number, " in file: ", file_path); - err = FAILED; - } - continue; - } - translation->add_message(line[0], line[1].c_unescape()); - } - return err; -} - -Ref LoadLocalisation::_get_translation(String const& locale) { - TranslationServer* server = TranslationServer::get_singleton(); - if (server == nullptr) { - UtilityFunctions::push_error("Failed to get TranslationServer singleton"); - return nullptr; - } - Ref translation = server->get_translation_object(locale); - if (translation.is_null() || translation->get_locale() != locale) { - translation.instantiate(); - translation->set_locale(locale); - server->add_translation(translation); - } - return translation; -} - -Error LoadLocalisation::load_file(String const& file_path, String const& locale) { - return _load_file_into_translation(file_path, _get_translation(locale)); -} - -/* REQUIREMENTS - * FS-18, FS-24, FS-25 - */ -Error LoadLocalisation::load_locale_dir(String const& dir_path, String const& locale) { - if (!DirAccess::dir_exists_absolute(dir_path)) { - UtilityFunctions::push_error("Locale directory does not exist: ", dir_path); - return FAILED; - } - /* This will add the locale to the list of loaded locales even if it has no - * localisation files - this is useful for testing other aspects of localisation - * such as number formatting and text direction. To disable this behaviour and - * only show non-empty localisations, move the `_get_translation` call to after - * the `files.size()` check. - */ - const Ref translation = _get_translation(locale); - const PackedStringArray files = DirAccess::get_files_at(dir_path); - if (files.size() < 1) { - UtilityFunctions::push_error("Locale directory does not contain any files: ", dir_path); - return FAILED; - } - Error err = OK; - for (String const& file_name : files) { - if (file_name.get_extension().to_lower() == "csv") { - if (_load_file_into_translation(dir_path.path_join(file_name), translation) != OK) - err = FAILED; - } - } - return err; -} - -/* REQUIREMENTS - * FS-23 - */ -Error LoadLocalisation::load_localisation_dir(String const& dir_path) { - if (!DirAccess::dir_exists_absolute(dir_path)) { - UtilityFunctions::push_error("Localisation directory does not exist: ", dir_path); - return FAILED; - } - PackedStringArray const dirs = DirAccess::get_directories_at(dir_path); - if (dirs.size() < 1) { - UtilityFunctions::push_error("Localisation directory does not contain any sub-directories: ", dir_path); - return FAILED; - } - TranslationServer* server = TranslationServer::get_singleton(); - if (server == nullptr) { - UtilityFunctions::push_error("Failed to get TranslationServer singleton"); - return FAILED; - } - Error err = OK; - for (String const& locale_name : dirs) { - if (locale_name != server->standardize_locale(locale_name)) - UtilityFunctions::push_error("Invalid locale directory name: ", locale_name); - else if (load_locale_dir(dir_path.path_join(locale_name), locale_name) == OK) - continue; - err = FAILED; - } - return err; -} diff --git a/extension/src/LoadLocalisation.hpp b/extension/src/LoadLocalisation.hpp deleted file mode 100644 index 04ec5c7..0000000 --- a/extension/src/LoadLocalisation.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -namespace OpenVic { - class LoadLocalisation : public godot::Object { - - GDCLASS(LoadLocalisation, godot::Object) - - static LoadLocalisation* singleton; - - godot::Error _load_file_into_translation(godot::String const& file_path, godot::Ref translation); - godot::Ref _get_translation(godot::String const& locale); - - protected: - static void _bind_methods(); - - public: - static LoadLocalisation* get_singleton(); - - LoadLocalisation(); - ~LoadLocalisation(); - - godot::Error load_file(godot::String const& file_path, godot::String const& locale); - godot::Error load_locale_dir(godot::String const& dir_path, godot::String const& locale); - godot::Error load_localisation_dir(godot::String const& dir_path); - }; -} diff --git a/extension/src/MapMesh.cpp b/extension/src/MapMesh.cpp deleted file mode 100644 index 269360a..0000000 --- a/extension/src/MapMesh.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "MapMesh.hpp" - -#include - -using namespace godot; -using namespace OpenVic; - -void MapMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_aspect_ratio", "ratio"), &MapMesh::set_aspect_ratio); - ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &MapMesh::get_aspect_ratio); - - ClassDB::bind_method(D_METHOD("set_repeat_proportion", "proportion"), &MapMesh::set_repeat_proportion); - ClassDB::bind_method(D_METHOD("get_repeat_proportion"), &MapMesh::get_repeat_proportion); - - ClassDB::bind_method(D_METHOD("set_subdivide_width", "divisions"), &MapMesh::set_subdivide_width); - ClassDB::bind_method(D_METHOD("get_subdivide_width"), &MapMesh::get_subdivide_width); - - ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &MapMesh::set_subdivide_depth); - ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &MapMesh::get_subdivide_depth); - - ClassDB::bind_method(D_METHOD("get_core_aabb"), &MapMesh::get_core_aabb); - ClassDB::bind_method(D_METHOD("is_valid_uv_coord"), &MapMesh::is_valid_uv_coord); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "aspect_ratio", PROPERTY_HINT_NONE, "suffix:m"), "set_aspect_ratio", "get_aspect_ratio"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "repeat_proportion", PROPERTY_HINT_NONE, "suffix:m"), "set_repeat_proportion", "get_repeat_proportion"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); -} - -void MapMesh::_request_update() { - // Hack to trigger _update_lightmap_size and _request_update in PrimitiveMesh - set_add_uv2(get_add_uv2()); -} - -void MapMesh::set_aspect_ratio(const float ratio) { - aspect_ratio = ratio; - _request_update(); -} - -float MapMesh::get_aspect_ratio() const { - return aspect_ratio; -} - -void MapMesh::set_repeat_proportion(const float proportion) { - repeat_proportion = proportion; - _request_update(); -} - -float MapMesh::get_repeat_proportion() const { - return repeat_proportion; -} - -void MapMesh::set_subdivide_width(const int32_t divisions) { - subdivide_w = divisions > 0 ? divisions : 0; - _request_update(); -} - -int32_t MapMesh::get_subdivide_width() const { - return subdivide_w; -} - -void MapMesh::set_subdivide_depth(const int32_t divisions) { - subdivide_d = divisions > 0 ? divisions : 0; - _request_update(); -} - -int32_t MapMesh::get_subdivide_depth() const { - return subdivide_d; -} - -AABB MapMesh::get_core_aabb() const { - const Vector3 size { aspect_ratio, 0.0f, 1.0f }; - return AABB { size * -0.5f, size }; -} - -bool MapMesh::is_valid_uv_coord(godot::Vector2 const& uv) const { - return 0.0f <= uv.y && uv.y <= 1.0f; -} - -Array MapMesh::_create_mesh_array() const { - Array arr; - arr.resize(Mesh::ARRAY_MAX); - - const int32_t vertex_count = (subdivide_w + 2) * (subdivide_d + 2); - const int32_t indice_count = (subdivide_w + 1) * (subdivide_d + 1) * 6; - - PackedVector3Array points; - PackedVector3Array normals; - PackedFloat32Array tangents; - 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); - - static const Vector3 normal { 0.0f, 1.0f, 0.0f }; - const Size2 uv_size { 1.0f + 2.0f * repeat_proportion, 1.0f }; - const Size2 size { aspect_ratio * uv_size.x, uv_size.y }, start_pos = size * -0.5f; - - int32_t point_index = 0, thisrow = 0, prevrow = 0, indice_index = 0; - Vector2 subdivide_step { 1.0f / (subdivide_w + 1.0f), 1.0f / (subdivide_d + 1.0f) }; - Vector3 point { 0.0f, 0.0f, start_pos.y }; - Vector2 point_step = subdivide_step * size; - Vector2 uv {}, uv_step = subdivide_step * uv_size; - - for (int32_t j = 0; j <= subdivide_d + 1; ++j) { - point.x = start_pos.x; - uv.x = -repeat_proportion; - - for (int32_t i = 0; i <= subdivide_w + 1; ++i) { - points[point_index] = point; - normals[point_index] = normal; - tangents[point_index * 4 + 0] = 1.0f; - tangents[point_index * 4 + 1] = 0.0f; - tangents[point_index * 4 + 2] = 0.0f; - tangents[point_index * 4 + 3] = 1.0f; - uvs[point_index] = uv; - point_index++; - - if (i > 0 && j > 0) { - indices[indice_index + 0] = prevrow + i - 1; - indices[indice_index + 1] = prevrow + i; - indices[indice_index + 2] = thisrow + i - 1; - indices[indice_index + 3] = prevrow + i; - indices[indice_index + 4] = thisrow + i; - indices[indice_index + 5] = thisrow + i - 1; - indice_index += 6; - } - - point.x += point_step.x; - uv.x += uv_step.x; - } - - point.z += point_step.y; - uv.y += uv_step.y; - prevrow = thisrow; - 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; - - return arr; -} diff --git a/extension/src/MapMesh.hpp b/extension/src/MapMesh.hpp deleted file mode 100644 index 38b208c..0000000 --- a/extension/src/MapMesh.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -namespace OpenVic { - class MapMesh : public godot::PrimitiveMesh { - GDCLASS(MapMesh, godot::PrimitiveMesh) - - float aspect_ratio = 2.0f, repeat_proportion = 0.5f; - int32_t subdivide_w = 0, subdivide_d = 0; - - protected: - static void _bind_methods(); - void _request_update(); - - public: - void set_aspect_ratio(const float ratio); - float get_aspect_ratio() const; - - void set_repeat_proportion(const float proportion); - float get_repeat_proportion() const; - - void set_subdivide_width(const int32_t divisions); - int32_t get_subdivide_width() const; - - void set_subdivide_depth(const int32_t divisions); - int32_t get_subdivide_depth() const; - - godot::AABB get_core_aabb() const; - bool is_valid_uv_coord(godot::Vector2 const& uv) const; - - godot::Array _create_mesh_array() const override; - }; -} diff --git a/extension/src/Utilities.cpp b/extension/src/Utilities.cpp deleted file mode 100644 index 4ca6855..0000000 --- a/extension/src/Utilities.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "Utilities.hpp" - -#include - -#include -#include - -using namespace godot; -using namespace OpenVic; - -Ref OpenVic::load_godot_image(String const& path) { - if (path.begins_with("res://")) { - ResourceLoader* loader = ResourceLoader::get_singleton(); - return loader ? loader->load(path) : nullptr; - } else { - 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, - 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(x) / static_cast(size), - static_cast(y) / static_cast(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 deleted file mode 100644 index afd65c0..0000000 --- a/extension/src/Utilities.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include - -#include "openvic-simulation/types/Colour.hpp" - -#define ERR(x) ((x) ? OK : FAILED) - -namespace OpenVic { - - inline std::string godot_to_std_string(godot::String const& str) { - return str.ascii().get_data(); - } - - inline godot::String std_to_godot_string(std::string const& str) { - 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 load_godot_image(godot::String const& path); - - void draw_pie_chart(godot::Ref 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); -} diff --git a/extension/src/openvic-extension/Checksum.hpp b/extension/src/openvic-extension/Checksum.hpp new file mode 100644 index 0000000..b12a9cd --- /dev/null +++ b/extension/src/openvic-extension/Checksum.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace OpenVic { + class Checksum : public godot::Object { + GDCLASS(Checksum, godot::Object) + + // BEGIN BOILERPLATE + inline static Checksum* _checksum = nullptr; + + protected: + static void _bind_methods() { + godot::ClassDB::bind_method(godot::D_METHOD("get_checksum_text"), &Checksum::get_checksum_text); + } + + public: + inline static Checksum* get_singleton() { return _checksum; } + + inline Checksum() { + ERR_FAIL_COND(_checksum != nullptr); + _checksum = this; + } + inline ~Checksum() { + ERR_FAIL_COND(_checksum != this); + _checksum = nullptr; + } + // END BOILERPLATE + + /* REQUIREMENTS: + * DAT-8 + */ + inline godot::String get_checksum_text() { + return godot::String("1234abcd"); + } + }; +} diff --git a/extension/src/openvic-extension/GameSingleton.cpp b/extension/src/openvic-extension/GameSingleton.cpp new file mode 100644 index 0000000..a164b23 --- /dev/null +++ b/extension/src/openvic-extension/GameSingleton.cpp @@ -0,0 +1,395 @@ +#include "GameSingleton.hpp" + +#include + +#include + +#include "openvic-extension/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +TerrainVariant::TerrainVariant(const std::string_view new_identfier, + colour_t new_colour, Ref const& new_image) + : HasIdentifierAndColour { new_identfier, new_colour, true }, + image { new_image } {} + +Ref TerrainVariant::get_image() const { + return image; +} + +GameSingleton* GameSingleton::singleton = nullptr; + +void GameSingleton::_bind_methods() { + ClassDB::bind_static_method("GameSingleton", D_METHOD("setup_logger"), &GameSingleton::setup_logger); + ClassDB::bind_method(D_METHOD("load_defines_compatibility_mode", "file_paths"), &GameSingleton::load_defines_compatibility_mode); + ClassDB::bind_method(D_METHOD("lookup_file", "path"), &GameSingleton::lookup_file); + ClassDB::bind_method(D_METHOD("setup_game"), &GameSingleton::setup_game); + + ClassDB::bind_method(D_METHOD("get_province_index_from_uv_coords", "coords"), &GameSingleton::get_province_index_from_uv_coords); + ClassDB::bind_method(D_METHOD("get_province_info_from_index", "index"), &GameSingleton::get_province_info_from_index); + ClassDB::bind_method(D_METHOD("get_width"), &GameSingleton::get_width); + ClassDB::bind_method(D_METHOD("get_height"), &GameSingleton::get_height); + ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &GameSingleton::get_aspect_ratio); + ClassDB::bind_method(D_METHOD("get_terrain_texture"), &GameSingleton::get_terrain_texture); + ClassDB::bind_method(D_METHOD("get_province_shape_image_subdivisions"), &GameSingleton::get_province_shape_image_subdivisions); + ClassDB::bind_method(D_METHOD("get_province_shape_texture"), &GameSingleton::get_province_shape_texture); + ClassDB::bind_method(D_METHOD("get_province_colour_texture"), &GameSingleton::get_province_colour_texture); + + ClassDB::bind_method(D_METHOD("get_mapmode_count"), &GameSingleton::get_mapmode_count); + ClassDB::bind_method(D_METHOD("get_mapmode_identifier", "index"), &GameSingleton::get_mapmode_identifier); + ClassDB::bind_method(D_METHOD("set_mapmode", "identifier"), &GameSingleton::set_mapmode); + ClassDB::bind_method(D_METHOD("get_selected_province_index"), &GameSingleton::get_selected_province_index); + ClassDB::bind_method(D_METHOD("set_selected_province", "index"), &GameSingleton::set_selected_province); + + ClassDB::bind_method(D_METHOD("expand_building", "province_index", "building_type_identifier"), &GameSingleton::expand_building); + ClassDB::bind_method(D_METHOD("get_good_icon_texture", "identifier"), &GameSingleton::get_good_icon_texture); + + ClassDB::bind_method(D_METHOD("set_paused", "paused"), &GameSingleton::set_paused); + ClassDB::bind_method(D_METHOD("toggle_paused"), &GameSingleton::toggle_paused); + ClassDB::bind_method(D_METHOD("is_paused"), &GameSingleton::is_paused); + ClassDB::bind_method(D_METHOD("increase_speed"), &GameSingleton::increase_speed); + ClassDB::bind_method(D_METHOD("decrease_speed"), &GameSingleton::decrease_speed); + ClassDB::bind_method(D_METHOD("can_increase_speed"), &GameSingleton::can_increase_speed); + ClassDB::bind_method(D_METHOD("can_decrease_speed"), &GameSingleton::can_decrease_speed); + ClassDB::bind_method(D_METHOD("get_longform_date"), &GameSingleton::get_longform_date); + ClassDB::bind_method(D_METHOD("try_tick"), &GameSingleton::try_tick); + + ADD_SIGNAL(MethodInfo("state_updated")); + ADD_SIGNAL(MethodInfo("province_selected", PropertyInfo(Variant::INT, "index"))); + + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_province_info_province_key"), &GameSingleton::get_province_info_province_key); + 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); + + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_building_key"), &GameSingleton::get_building_info_building_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_level_key"), &GameSingleton::get_building_info_level_key); + ClassDB::bind_static_method("GameSingleton", D_METHOD("get_building_info_expansion_state_key"), &GameSingleton::get_building_info_expansion_state_key); + 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, + 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() { + return singleton; +} + +void GameSingleton::_on_state_updated() { + _update_colour_image(); + emit_signal("state_updated"); +} + +/* REQUIREMENTS: + * MAP-21, MAP-23, MAP-25, MAP-32, MAP-33, MAP-34 + */ +GameSingleton::GameSingleton() : game_manager { [this]() { _on_state_updated(); } }, + terrain_variants { "terrain variants" } { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; +} + +void GameSingleton::setup_logger() { + Logger::set_info_func([](std::string&& str) { UtilityFunctions::print(std_to_godot_string(str)); }); + Logger::set_warning_func([](std::string&& str) { UtilityFunctions::push_warning(std_to_godot_string(str)); }); + Logger::set_error_func([](std::string&& str) { UtilityFunctions::push_error(std_to_godot_string(str)); }); +} + +GameSingleton::~GameSingleton() { + ERR_FAIL_COND(singleton != this); + singleton = nullptr; +} + +Error GameSingleton::setup_game() { + bool ret = game_manager.setup(); + ret &= dataloader.load_pop_history(game_manager, "history/pops/" + game_manager.get_today().to_string()); + return ERR(ret); +} + +int32_t GameSingleton::get_province_index_from_uv_coords(Vector2 const& coords) const { + const size_t x_mod_w = UtilityFunctions::fposmod(coords.x, 1.0f) * get_width(); + const size_t y_mod_h = UtilityFunctions::fposmod(coords.y, 1.0f) * get_height(); + return game_manager.map.get_province_index_at(x_mod_w, y_mod_h); +} + +StringName const& GameSingleton::get_province_info_province_key() { + static const StringName key = "province"; + return key; +} +StringName const& GameSingleton::get_province_info_region_key() { + static const StringName key = "region"; + return key; +} +StringName const& GameSingleton::get_province_info_life_rating_key() { + static const StringName key = "life_rating"; + return key; +} +StringName const& GameSingleton::get_province_info_total_population_key() { + static const StringName key = "total_population"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_types_key() { + static const StringName key = "pop_types"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_ideologies_key() { + static const StringName key = "pop_ideologies"; + return key; +} +StringName const& GameSingleton::get_province_info_pop_cultures_key() { + static const StringName key = "pop_cultures"; + return key; +} +StringName const& GameSingleton::get_province_info_rgo_key() { + static const StringName key = "rgo"; + return key; +} +StringName const& GameSingleton::get_province_info_buildings_key() { + static const StringName key = "buildings"; + return key; +} + +StringName const& GameSingleton::get_building_info_building_key() { + static const StringName key = "building"; + return key; +} +StringName const& GameSingleton::get_building_info_level_key() { + static const StringName key = "level"; + return key; +} +StringName const& GameSingleton::get_building_info_expansion_state_key() { + static const StringName key = "expansion_state"; + return key; +} +StringName const& GameSingleton::get_building_info_start_date_key() { + static const StringName key = "start_date"; + return key; +} +StringName const& GameSingleton::get_building_info_end_date_key() { + static const StringName key = "end_date"; + return key; +} +StringName const& GameSingleton::get_building_info_expansion_progress_key() { + static const StringName key = "expansion_progress"; + return key; +} + +StringName const& GameSingleton::get_piechart_info_size_key() { + static const StringName key = "size"; + return key; +} +StringName const& GameSingleton::get_piechart_info_colour_key() { + static const StringName key = "colour"; + return key; +} + +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 {}; + Dictionary ret; + + ret[get_province_info_province_key()] = std_to_godot_string(province->get_identifier()); + + Region const* region = province->get_region(); + if (region != nullptr) ret[get_province_info_region_key()] = std_to_godot_string(region->get_identifier()); + + Good const* rgo = province->get_rgo(); + if (rgo != nullptr) ret[get_province_info_rgo_key()] = std_to_godot_string(rgo->get_identifier()); + + 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 const& buildings = province->get_buildings(); + if (!buildings.empty()) { + Array buildings_array; + buildings_array.resize(buildings.size()); + for (size_t idx = 0; idx < buildings.size(); ++idx) { + Building const& building = buildings[idx]; + + Dictionary building_dict; + building_dict[get_building_info_building_key()] = std_to_godot_string(building.get_identifier()); + building_dict[get_building_info_level_key()] = static_cast(building.get_level()); + building_dict[get_building_info_expansion_state_key()] = static_cast(building.get_expansion_state()); + building_dict[get_building_info_start_date_key()] = std_to_godot_string(building.get_start_date().to_string()); + building_dict[get_building_info_end_date_key()] = std_to_godot_string(building.get_end_date().to_string()); + building_dict[get_building_info_expansion_progress_key()] = building.get_expansion_progress(); + + buildings_array[idx] = building_dict; + } + ret[get_province_info_buildings_key()] = buildings_array; + } + return ret; +} + +int32_t GameSingleton::get_width() const { + return game_manager.map.get_width(); +} + +int32_t GameSingleton::get_height() const { + return game_manager.map.get_height(); +} + +float GameSingleton::get_aspect_ratio() const { + return static_cast(get_width()) / static_cast(get_height()); +} + +Ref GameSingleton::get_terrain_texture() const { + return terrain_texture; +} + +Vector2i GameSingleton::get_province_shape_image_subdivisions() const { + return image_subdivisions; +} + +Ref GameSingleton::get_province_shape_texture() const { + return province_shape_texture; +} + +Ref GameSingleton::get_province_colour_texture() const { + return province_colour_texture; +} + +Error GameSingleton::_update_colour_image() { + static PackedByteArray colour_data_array; + static constexpr int64_t colour_data_array_size = (static_cast(Province::MAX_INDEX) + 1) * Map::MAPMODE_COLOUR_SIZE; + colour_data_array.resize(colour_data_array_size); + + Error err = OK; + if (!game_manager.map.generate_mapmode_colours(mapmode_index, colour_data_array.ptrw())) + err = FAILED; + + static constexpr int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * 4); + if (province_colour_image.is_null()) { + province_colour_image.instantiate(); + ERR_FAIL_NULL_V_EDMSG(province_colour_image, FAILED, + "Failed to create province colour image"); + } + province_colour_image->set_data(PROVINCE_INDEX_SQRT, PROVINCE_INDEX_SQRT, + false, Image::FORMAT_RGBA8, colour_data_array); + if (province_colour_texture.is_null()) { + province_colour_texture = ImageTexture::create_from_image(province_colour_image); + ERR_FAIL_NULL_V_EDMSG(province_colour_texture, FAILED, + "Failed to create province colour texture"); + } else province_colour_texture->update(province_colour_image); + return err; +} + +int32_t GameSingleton::get_mapmode_count() const { + return game_manager.map.get_mapmode_count(); +} + +String GameSingleton::get_mapmode_identifier(int32_t index) const { + Mapmode const* mapmode = game_manager.map.get_mapmode_by_index(index); + if (mapmode != nullptr) return std_to_godot_string(mapmode->get_identifier()); + return String {}; +} + +Error GameSingleton::set_mapmode(String const& identifier) { + Mapmode const* mapmode = game_manager.map.get_mapmode_by_identifier(godot_to_std_string(identifier)); + if (mapmode == nullptr) { + UtilityFunctions::push_error("Failed to set mapmode to: ", identifier); + return FAILED; + } + mapmode_index = mapmode->get_index(); + _update_colour_image(); + return OK; +} + +int32_t GameSingleton::get_selected_province_index() const { + return game_manager.map.get_selected_province_index(); +} + +void GameSingleton::set_selected_province(int32_t index) { + game_manager.map.set_selected_province(index); + _update_colour_image(); + emit_signal("province_selected", index); +} + +Error GameSingleton::expand_building(int32_t province_index, String const& building_type_identifier) { + if (!game_manager.expand_building(province_index, godot_to_std_string(building_type_identifier))) { + UtilityFunctions::push_error("Failed to expand ", building_type_identifier, " at province index ", province_index); + return FAILED; + } + return OK; +} + +Ref GameSingleton::get_good_icon_texture(String const& identifier) const { + return good_icons.get(identifier, {}); +} + +void GameSingleton::set_paused(bool paused) { + game_manager.clock.isPaused = paused; +} + +void GameSingleton::toggle_paused() { + game_manager.clock.isPaused = !game_manager.clock.isPaused; +} + +bool GameSingleton::is_paused() const { + return game_manager.clock.isPaused; +} + +void GameSingleton::increase_speed() { + game_manager.clock.increaseSimulationSpeed(); +} + +void GameSingleton::decrease_speed() { + game_manager.clock.decreaseSimulationSpeed(); +} + +bool GameSingleton::can_increase_speed() const { + return game_manager.clock.canIncreaseSimulationSpeed(); +} + +bool GameSingleton::can_decrease_speed() const { + return game_manager.clock.canDecreaseSimulationSpeed(); +} + +String GameSingleton::get_longform_date() const { + return std_to_godot_string(game_manager.get_today().to_string()); +} + +void GameSingleton::try_tick() { + game_manager.clock.conditionallyAdvanceGame(); +} diff --git a/extension/src/openvic-extension/GameSingleton.hpp b/extension/src/openvic-extension/GameSingleton.hpp new file mode 100644 index 0000000..bd6b73c --- /dev/null +++ b/extension/src/openvic-extension/GameSingleton.hpp @@ -0,0 +1,158 @@ +#pragma once + +#include +#include + +#include +#include + +namespace OpenVic { + + struct TerrainVariant : HasIdentifierAndColour { + friend class GameSingleton; + + private: + const godot::Ref image; + + TerrainVariant(const std::string_view new_identfier, colour_t new_colour, + godot::Ref const& new_image); + + public: + static constexpr size_t MAX_TERRIN_VARIANT_COUNT = 1 << (8 * sizeof(Map::terrain_t)); + + TerrainVariant(TerrainVariant&&) = default; + + godot::Ref get_image() const; + }; + + class GameSingleton : public godot::Object { + GDCLASS(GameSingleton, godot::Object) + + static GameSingleton* singleton; + + GameManager game_manager; + Dataloader dataloader; + + godot::Vector2i image_subdivisions; + godot::Ref province_shape_texture; + godot::Ref province_colour_image; + godot::Ref province_colour_texture; + Mapmode::index_t mapmode_index = 0; + IdentifierRegistry terrain_variants; + Map::terrain_variant_map_t terrain_variant_map; + godot::Ref terrain_texture; + godot::Dictionary good_icons; + + godot::Error _generate_terrain_texture_array(); + godot::Error _load_map_images(godot::String const& province_image_path, godot::String const& terrain_image_path, bool flip_vertical = false); + + godot::Error _load_terrain_variants_compatibility_mode(godot::String const& terrain_image_path, godot::String const& terrain_texturesheet_path); + + /* Generate the province_colour_texture from the current mapmode. + */ + 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 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(); + ~GameSingleton(); + + static void setup_logger(); + + /* Load the game's defines in compatiblity mode from the filepath + * pointing to the defines folder. + */ + godot::Error load_defines_compatibility_mode(godot::PackedStringArray const& file_paths); + + godot::String lookup_file(godot::String const& path) const; + + /* Post-load/restart game setup - reset the game to post-load state + * and (re)generate starting data, e.g. buildings. + */ + godot::Error setup_game(); + + int32_t get_province_index_from_uv_coords(godot::Vector2 const& coords) const; + + static godot::StringName const& get_province_info_province_key(); + static godot::StringName const& get_province_info_region_key(); + static godot::StringName const& get_province_info_life_rating_key(); + static godot::StringName const& get_province_info_total_population_key(); + static godot::StringName const& get_province_info_pop_types_key(); + static godot::StringName const& get_province_info_pop_ideologies_key(); + static godot::StringName const& get_province_info_pop_cultures_key(); + static godot::StringName const& get_province_info_rgo_key(); + static godot::StringName const& get_province_info_buildings_key(); + + static godot::StringName const& get_building_info_building_key(); + static godot::StringName const& get_building_info_level_key(); + static godot::StringName const& get_building_info_expansion_state_key(); + static godot::StringName const& get_building_info_start_date_key(); + static godot::StringName const& get_building_info_end_date_key(); + static godot::StringName const& get_building_info_expansion_progress_key(); + + static godot::StringName const& get_piechart_info_size_key(); + static godot::StringName const& get_piechart_info_colour_key(); + + /* Get info to display in Province Overview Panel, packaged in + * a Dictionary using the StringNames above as keys. + */ + godot::Dictionary get_province_info_from_index(int32_t index) const; + + int32_t get_width() const; + int32_t get_height() const; + float get_aspect_ratio() const; + + /* The cosmetic terrain textures stored in a Texture2DArray. + */ + godot::Ref get_terrain_texture() const; + + /* Number of (vertical, horizontal) subdivisions the province shape image + * was split into when making the province_shape_texture to ensure no + * piece had a dimension greater than 16383. + */ + godot::Vector2i get_province_shape_image_subdivisions() const; + + /* The map, encoded in RGB8 with RG representing province index and B representing terrain texture. + * To support a wider range of GPUs, the image is divided so that no piece has a dimension + * greater than 16383 and the pieces are stored in a Texture2DArray. + */ + godot::Ref get_province_shape_texture() const; + + /* The colour each province should be tinted, arranged in + * index order into a 256x256 RGB8 texture. + */ + godot::Ref get_province_colour_texture() const; + + int32_t get_mapmode_count() const; + godot::String get_mapmode_identifier(int32_t index) const; + godot::Error set_mapmode(godot::String const& identifier); + int32_t get_selected_province_index() const; + void set_selected_province(int32_t index); + + godot::Error expand_building(int32_t province_index, godot::String const& building_type_identifier); + godot::Ref get_good_icon_texture(godot::String const& identifier) const; + + void set_paused(bool paused); + void toggle_paused(); + bool is_paused() const; + void increase_speed(); + void decrease_speed(); + bool can_increase_speed() const; + bool can_decrease_speed() const; + godot::String get_longform_date() const; + void try_tick(); + }; +} diff --git a/extension/src/openvic-extension/LoadGameCompatibility.cpp b/extension/src/openvic-extension/LoadGameCompatibility.cpp new file mode 100644 index 0000000..e8e3314 --- /dev/null +++ b/extension/src/openvic-extension/LoadGameCompatibility.cpp @@ -0,0 +1,110 @@ +#include "GameSingleton.hpp" + +#include +#include + +#include + +#include "openvic-extension/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +Error GameSingleton::_load_terrain_variants_compatibility_mode(String const& terrain_image_path, String const& terrain_texturesheet_path) { + // Read BMP's palette to determine terrain variant colours which texture they're associated with + BMP bmp; + if (!(bmp.open(godot_to_std_string(terrain_image_path).c_str()) && bmp.read_header() && bmp.read_palette())) { + UtilityFunctions::push_error("Failed to read BMP palette from compatibility mode terrain image: ", terrain_image_path); + return FAILED; + } + std::vector const& palette = bmp.get_palette(); + static constexpr int32_t SHEET_DIMS = 8, PALETTE_SIZE = SHEET_DIMS * SHEET_DIMS; + if (palette.size() == 0 || palette.size() < PALETTE_SIZE) { + UtilityFunctions::push_error("Invalid BMP palette size for terrain image: ", static_cast(palette.size()), " (expected ", PALETTE_SIZE, ")"); + return FAILED; + } + + // Load the terrain texture sheet and prepare to slice it up + Ref terrain_sheet = load_godot_image(terrain_texturesheet_path); + if (terrain_sheet.is_null()) { + UtilityFunctions::push_error("Failed to load terrain texture sheet: ", terrain_texturesheet_path); + return FAILED; + } + terrain_sheet->flip_y(); + const int32_t sheet_width = terrain_sheet->get_width(), sheet_height = terrain_sheet->get_height(); + if (sheet_width < 1 || sheet_width % SHEET_DIMS != 0 || sheet_width != sheet_height) { + UtilityFunctions::push_error("Invalid terrain texture sheet dims: ", sheet_width, "x", sheet_height, " (must be square with dims positive multiples of ", SHEET_DIMS, ")"); + return FAILED; + } + const int32_t slice_size = sheet_width / SHEET_DIMS; + + { + static constexpr colour_t TERRAIN_WATER_INDEX_COLOUR = 0xFFFFFF; + Ref water_image = Image::create(slice_size, slice_size, false, terrain_sheet->get_format()); + ERR_FAIL_NULL_V_EDMSG(water_image, FAILED, "Failed to create water terrain image"); + water_image->fill({ 0.1f, 0.1f, 0.5f }); + terrain_variants.add_item({ "terrain_water", TERRAIN_WATER_INDEX_COLOUR, water_image }); + } + Error err = OK; + for (int32_t idx = 0; idx < PALETTE_SIZE; ++idx) { + const Rect2i slice { (idx % SHEET_DIMS) * slice_size, (7 - (idx / SHEET_DIMS)) * slice_size, slice_size, slice_size }; + const Ref 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; + continue; + } + if (!terrain_variants.add_item({ "terrain_" + std::to_string(idx), palette[idx], terrain_image })) err = FAILED; + } + terrain_variants.lock(); + if (_generate_terrain_texture_array() != OK) return FAILED; + return err; +} + +Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& file_paths) { + static const fs::path province_image_file = "map/provinces.bmp"; + static const fs::path terrain_image_file = "map/terrain.bmp"; + static const fs::path terrain_texture_file = "map/terrain/texturesheet.tga"; + + Dataloader::path_vector_t roots; + for (String const& path : file_paths) { + roots.push_back(godot_to_std_string(path)); + } + + Error err = OK; + + if (!dataloader.set_roots(roots)) { + Logger::error("Failed to set dataloader roots!"); + err = FAILED; + } + + if (!dataloader.load_defines(game_manager)) { + UtilityFunctions::push_error("Failed to load defines!"); + err = FAILED; + } + + game_manager.map.lock_regions(); + if (_load_terrain_variants_compatibility_mode( + std_to_godot_string(dataloader.lookup_file(terrain_image_file).string()), + std_to_godot_string(dataloader.lookup_file(terrain_texture_file).string()) + ) != OK) { + UtilityFunctions::push_error("Failed to load terrain variants!"); + err = FAILED; + } + if (_load_map_images( + std_to_godot_string(dataloader.lookup_file(province_image_file).string()), + std_to_godot_string(dataloader.lookup_file(terrain_image_file).string()), + true) != OK) { + UtilityFunctions::push_error("Failed to load map images!"); + err = FAILED; + } + if (!game_manager.load_hardcoded_defines()) { + UtilityFunctions::push_error("Failed to hardcoded defines!"); + err = FAILED; + } + return err; +} + +String GameSingleton::lookup_file(String const& path) const { + return std_to_godot_string(dataloader.lookup_file(godot_to_std_string(path)).string()); +} diff --git a/extension/src/openvic-extension/LoadGameOpenVic.cpp b/extension/src/openvic-extension/LoadGameOpenVic.cpp new file mode 100644 index 0000000..87c66da --- /dev/null +++ b/extension/src/openvic-extension/LoadGameOpenVic.cpp @@ -0,0 +1,132 @@ +#include "GameSingleton.hpp" + +#include +#include +#include + +#include "openvic-extension/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +Error GameSingleton::_generate_terrain_texture_array() { + Error err = OK; + if (terrain_variants.size() == 0) { + UtilityFunctions::push_error("Failed to load terrain textures!"); + return FAILED; + } + // TerrainVariant count is limited by the data type representing it in the map image + if (terrain_variants.size() > TerrainVariant::MAX_TERRIN_VARIANT_COUNT) { + UtilityFunctions::push_error("Too many terrain textures - all after the first ", + static_cast(TerrainVariant::MAX_TERRIN_VARIANT_COUNT), " will be ignored"); + err = FAILED; + } + + Array terrain_images; + for (size_t i = 0; i < terrain_variants.size() && i < TerrainVariant::MAX_TERRIN_VARIANT_COUNT; ++i) { + TerrainVariant const& var = *terrain_variants.get_item_by_index(i); + terrain_variant_map[var.get_colour()] = i; + terrain_images.append(var.get_image()); + } + + terrain_texture.instantiate(); + if (terrain_texture->create_from_images(terrain_images) != OK) { + UtilityFunctions::push_error("Failed to create terrain texture array!"); + return FAILED; + } + return err; +} + +Error GameSingleton::_load_map_images(String const& province_image_path, String const& terrain_image_path, bool flip_vertical) { + if (province_shape_texture.is_valid()) { + UtilityFunctions::push_error("Map images have already been loaded, cannot load: ", province_image_path, " and ", terrain_image_path); + return FAILED; + } + + // Load images + Ref province_image = load_godot_image(province_image_path); + if (province_image.is_null()) { + UtilityFunctions::push_error("Failed to load province image: ", province_image_path); + return FAILED; + } + Ref terrain_image = load_godot_image(terrain_image_path); + if (terrain_image.is_null()) { + UtilityFunctions::push_error("Failed to load terrain image: ", terrain_image_path); + return FAILED; + } + + if (flip_vertical) { + province_image->flip_y(); + terrain_image->flip_y(); + } + + // Validate dimensions and format + Error err = OK; + const Vector2i province_dims = province_image->get_size(), terrain_dims = terrain_image->get_size(); + if (province_dims.x < 1 || province_dims.y < 1) { + UtilityFunctions::push_error("Invalid dimensions (", province_dims.x, "x", province_dims.y, ") for province image: ", province_image_path); + err = FAILED; + } + if (province_dims != terrain_dims) { + UtilityFunctions::push_error("Invalid dimensions (", terrain_dims.x, "x", terrain_dims.y, ") for terrain image: ", + terrain_image_path, " (must match province image: (", province_dims.x, "x", province_dims.x, "))"); + err = FAILED; + } + static constexpr Image::Format expected_format = Image::FORMAT_RGB8; + if (province_image->get_format() == Image::FORMAT_RGBA8) province_image->convert(expected_format); + if (terrain_image->get_format() == Image::FORMAT_RGBA8) terrain_image->convert(expected_format); + if (province_image->get_format() != expected_format) { + UtilityFunctions::push_error("Invalid format (", province_image->get_format(), ", should be ", expected_format, ") for province image: ", province_image_path); + err = FAILED; + } + if (terrain_image->get_format() != expected_format) { + UtilityFunctions::push_error("Invalid format (", terrain_image->get_format(), ", should be ", expected_format, ") for terrain image: ", terrain_image_path); + err = FAILED; + } + if (err != OK) return err; + + // 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, + false /* <-- whether to print detailed map errors or not (specific missing/unrecognised colours) */ + )) 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 + for (int i = 0; i < 2; ++i) + for (image_subdivisions[i] = 1; province_dims[i] / image_subdivisions[i] > GPU_DIM_LIMIT || + province_dims[i] % image_subdivisions[i] != 0; ++image_subdivisions[i]); + + Map::shape_pixel_t const* province_shape_data = game_manager.map.get_province_shape_image().data(); + const Vector2i divided_dims = province_dims / image_subdivisions; + Array province_shape_images; + province_shape_images.resize(image_subdivisions.x * image_subdivisions.y); + 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), + province_shape_data + (v * divided_dims.y + y) * province_dims.x + u * divided_dims.x, + divided_dims.x * sizeof(Map::shape_pixel_t)); + + const Ref province_shape_subimage = Image::create_from_data(divided_dims.x, divided_dims.y, false, Image::FORMAT_RGB8, index_data_array); + if (province_shape_subimage.is_null()) { + UtilityFunctions::push_error("Failed to create province shape image (", u, ", ", v, ")"); + err = FAILED; + } + province_shape_images[u + v * image_subdivisions.x] = province_shape_subimage; + } + } + + province_shape_texture.instantiate(); + if (province_shape_texture->create_from_images(province_shape_images) != OK) { + UtilityFunctions::push_error("Failed to create terrain texture array!"); + err = FAILED; + } + + if (_update_colour_image() != OK) err = FAILED; + + return err; +} diff --git a/extension/src/openvic-extension/LoadLocalisation.cpp b/extension/src/openvic-extension/LoadLocalisation.cpp new file mode 100644 index 0000000..dc7702c --- /dev/null +++ b/extension/src/openvic-extension/LoadLocalisation.cpp @@ -0,0 +1,136 @@ +#include "LoadLocalisation.hpp" + +#include +#include +#include +#include + +using namespace godot; +using namespace OpenVic; + +LoadLocalisation* LoadLocalisation::singleton = nullptr; + +void LoadLocalisation::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_file", "file_path", "locale"), &LoadLocalisation::load_file); + ClassDB::bind_method(D_METHOD("load_locale_dir", "dir_path", "locale"), &LoadLocalisation::load_locale_dir); + ClassDB::bind_method(D_METHOD("load_localisation_dir", "dir_path"), &LoadLocalisation::load_localisation_dir); +} + +LoadLocalisation* LoadLocalisation::get_singleton() { + return singleton; +} + +LoadLocalisation::LoadLocalisation() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; +} + +LoadLocalisation::~LoadLocalisation() { + ERR_FAIL_COND(singleton != this); + singleton = nullptr; +} + +Error LoadLocalisation::_load_file_into_translation(String const& file_path, Ref translation) { + const Ref file = FileAccess::open(file_path, FileAccess::ModeFlags::READ); + Error err = FileAccess::get_open_error(); + if (err != OK || file.is_null()) { + UtilityFunctions::push_error("Failed to load localisation file: ", file_path); + return err == OK ? FAILED : err; + } + int line_number = 0; + while (!file->eof_reached()) { + static const String delimeter = ";"; + const PackedStringArray line = file->get_csv_line(delimeter); + line_number++; + if (line.size() < 2 || line[0].is_empty() || line[1].is_empty()) { + if (!line[0].is_empty()) { + UtilityFunctions::push_warning("Key \"", line[0], "\" missing value on line ", line_number, " in file: ", file_path); + err = FAILED; + } else if (line.size() >= 2 && !line[1].is_empty()) { + UtilityFunctions::push_warning("Value \"", line[1], "\" missing key on line ", line_number, " in file: ", file_path); + err = FAILED; + } + continue; + } + translation->add_message(line[0], line[1].c_unescape()); + } + return err; +} + +Ref LoadLocalisation::_get_translation(String const& locale) { + TranslationServer* server = TranslationServer::get_singleton(); + if (server == nullptr) { + UtilityFunctions::push_error("Failed to get TranslationServer singleton"); + return nullptr; + } + Ref translation = server->get_translation_object(locale); + if (translation.is_null() || translation->get_locale() != locale) { + translation.instantiate(); + translation->set_locale(locale); + server->add_translation(translation); + } + return translation; +} + +Error LoadLocalisation::load_file(String const& file_path, String const& locale) { + return _load_file_into_translation(file_path, _get_translation(locale)); +} + +/* REQUIREMENTS + * FS-18, FS-24, FS-25 + */ +Error LoadLocalisation::load_locale_dir(String const& dir_path, String const& locale) { + if (!DirAccess::dir_exists_absolute(dir_path)) { + UtilityFunctions::push_error("Locale directory does not exist: ", dir_path); + return FAILED; + } + /* This will add the locale to the list of loaded locales even if it has no + * localisation files - this is useful for testing other aspects of localisation + * such as number formatting and text direction. To disable this behaviour and + * only show non-empty localisations, move the `_get_translation` call to after + * the `files.size()` check. + */ + const Ref translation = _get_translation(locale); + const PackedStringArray files = DirAccess::get_files_at(dir_path); + if (files.size() < 1) { + UtilityFunctions::push_error("Locale directory does not contain any files: ", dir_path); + return FAILED; + } + Error err = OK; + for (String const& file_name : files) { + if (file_name.get_extension().to_lower() == "csv") { + if (_load_file_into_translation(dir_path.path_join(file_name), translation) != OK) + err = FAILED; + } + } + return err; +} + +/* REQUIREMENTS + * FS-23 + */ +Error LoadLocalisation::load_localisation_dir(String const& dir_path) { + if (!DirAccess::dir_exists_absolute(dir_path)) { + UtilityFunctions::push_error("Localisation directory does not exist: ", dir_path); + return FAILED; + } + PackedStringArray const dirs = DirAccess::get_directories_at(dir_path); + if (dirs.size() < 1) { + UtilityFunctions::push_error("Localisation directory does not contain any sub-directories: ", dir_path); + return FAILED; + } + TranslationServer* server = TranslationServer::get_singleton(); + if (server == nullptr) { + UtilityFunctions::push_error("Failed to get TranslationServer singleton"); + return FAILED; + } + Error err = OK; + for (String const& locale_name : dirs) { + if (locale_name != server->standardize_locale(locale_name)) + UtilityFunctions::push_error("Invalid locale directory name: ", locale_name); + else if (load_locale_dir(dir_path.path_join(locale_name), locale_name) == OK) + continue; + err = FAILED; + } + return err; +} diff --git a/extension/src/openvic-extension/LoadLocalisation.hpp b/extension/src/openvic-extension/LoadLocalisation.hpp new file mode 100644 index 0000000..04ec5c7 --- /dev/null +++ b/extension/src/openvic-extension/LoadLocalisation.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace OpenVic { + class LoadLocalisation : public godot::Object { + + GDCLASS(LoadLocalisation, godot::Object) + + static LoadLocalisation* singleton; + + godot::Error _load_file_into_translation(godot::String const& file_path, godot::Ref translation); + godot::Ref _get_translation(godot::String const& locale); + + protected: + static void _bind_methods(); + + public: + static LoadLocalisation* get_singleton(); + + LoadLocalisation(); + ~LoadLocalisation(); + + godot::Error load_file(godot::String const& file_path, godot::String const& locale); + godot::Error load_locale_dir(godot::String const& dir_path, godot::String const& locale); + godot::Error load_localisation_dir(godot::String const& dir_path); + }; +} diff --git a/extension/src/openvic-extension/MapMesh.cpp b/extension/src/openvic-extension/MapMesh.cpp new file mode 100644 index 0000000..269360a --- /dev/null +++ b/extension/src/openvic-extension/MapMesh.cpp @@ -0,0 +1,150 @@ +#include "MapMesh.hpp" + +#include + +using namespace godot; +using namespace OpenVic; + +void MapMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_aspect_ratio", "ratio"), &MapMesh::set_aspect_ratio); + ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &MapMesh::get_aspect_ratio); + + ClassDB::bind_method(D_METHOD("set_repeat_proportion", "proportion"), &MapMesh::set_repeat_proportion); + ClassDB::bind_method(D_METHOD("get_repeat_proportion"), &MapMesh::get_repeat_proportion); + + ClassDB::bind_method(D_METHOD("set_subdivide_width", "divisions"), &MapMesh::set_subdivide_width); + ClassDB::bind_method(D_METHOD("get_subdivide_width"), &MapMesh::get_subdivide_width); + + ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &MapMesh::set_subdivide_depth); + ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &MapMesh::get_subdivide_depth); + + ClassDB::bind_method(D_METHOD("get_core_aabb"), &MapMesh::get_core_aabb); + ClassDB::bind_method(D_METHOD("is_valid_uv_coord"), &MapMesh::is_valid_uv_coord); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "aspect_ratio", PROPERTY_HINT_NONE, "suffix:m"), "set_aspect_ratio", "get_aspect_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "repeat_proportion", PROPERTY_HINT_NONE, "suffix:m"), "set_repeat_proportion", "get_repeat_proportion"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); +} + +void MapMesh::_request_update() { + // Hack to trigger _update_lightmap_size and _request_update in PrimitiveMesh + set_add_uv2(get_add_uv2()); +} + +void MapMesh::set_aspect_ratio(const float ratio) { + aspect_ratio = ratio; + _request_update(); +} + +float MapMesh::get_aspect_ratio() const { + return aspect_ratio; +} + +void MapMesh::set_repeat_proportion(const float proportion) { + repeat_proportion = proportion; + _request_update(); +} + +float MapMesh::get_repeat_proportion() const { + return repeat_proportion; +} + +void MapMesh::set_subdivide_width(const int32_t divisions) { + subdivide_w = divisions > 0 ? divisions : 0; + _request_update(); +} + +int32_t MapMesh::get_subdivide_width() const { + return subdivide_w; +} + +void MapMesh::set_subdivide_depth(const int32_t divisions) { + subdivide_d = divisions > 0 ? divisions : 0; + _request_update(); +} + +int32_t MapMesh::get_subdivide_depth() const { + return subdivide_d; +} + +AABB MapMesh::get_core_aabb() const { + const Vector3 size { aspect_ratio, 0.0f, 1.0f }; + return AABB { size * -0.5f, size }; +} + +bool MapMesh::is_valid_uv_coord(godot::Vector2 const& uv) const { + return 0.0f <= uv.y && uv.y <= 1.0f; +} + +Array MapMesh::_create_mesh_array() const { + Array arr; + arr.resize(Mesh::ARRAY_MAX); + + const int32_t vertex_count = (subdivide_w + 2) * (subdivide_d + 2); + const int32_t indice_count = (subdivide_w + 1) * (subdivide_d + 1) * 6; + + PackedVector3Array points; + PackedVector3Array normals; + PackedFloat32Array tangents; + 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); + + static const Vector3 normal { 0.0f, 1.0f, 0.0f }; + const Size2 uv_size { 1.0f + 2.0f * repeat_proportion, 1.0f }; + const Size2 size { aspect_ratio * uv_size.x, uv_size.y }, start_pos = size * -0.5f; + + int32_t point_index = 0, thisrow = 0, prevrow = 0, indice_index = 0; + Vector2 subdivide_step { 1.0f / (subdivide_w + 1.0f), 1.0f / (subdivide_d + 1.0f) }; + Vector3 point { 0.0f, 0.0f, start_pos.y }; + Vector2 point_step = subdivide_step * size; + Vector2 uv {}, uv_step = subdivide_step * uv_size; + + for (int32_t j = 0; j <= subdivide_d + 1; ++j) { + point.x = start_pos.x; + uv.x = -repeat_proportion; + + for (int32_t i = 0; i <= subdivide_w + 1; ++i) { + points[point_index] = point; + normals[point_index] = normal; + tangents[point_index * 4 + 0] = 1.0f; + tangents[point_index * 4 + 1] = 0.0f; + tangents[point_index * 4 + 2] = 0.0f; + tangents[point_index * 4 + 3] = 1.0f; + uvs[point_index] = uv; + point_index++; + + if (i > 0 && j > 0) { + indices[indice_index + 0] = prevrow + i - 1; + indices[indice_index + 1] = prevrow + i; + indices[indice_index + 2] = thisrow + i - 1; + indices[indice_index + 3] = prevrow + i; + indices[indice_index + 4] = thisrow + i; + indices[indice_index + 5] = thisrow + i - 1; + indice_index += 6; + } + + point.x += point_step.x; + uv.x += uv_step.x; + } + + point.z += point_step.y; + uv.y += uv_step.y; + prevrow = thisrow; + 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; + + return arr; +} diff --git a/extension/src/openvic-extension/MapMesh.hpp b/extension/src/openvic-extension/MapMesh.hpp new file mode 100644 index 0000000..38b208c --- /dev/null +++ b/extension/src/openvic-extension/MapMesh.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace OpenVic { + class MapMesh : public godot::PrimitiveMesh { + GDCLASS(MapMesh, godot::PrimitiveMesh) + + float aspect_ratio = 2.0f, repeat_proportion = 0.5f; + int32_t subdivide_w = 0, subdivide_d = 0; + + protected: + static void _bind_methods(); + void _request_update(); + + public: + void set_aspect_ratio(const float ratio); + float get_aspect_ratio() const; + + void set_repeat_proportion(const float proportion); + float get_repeat_proportion() const; + + void set_subdivide_width(const int32_t divisions); + int32_t get_subdivide_width() const; + + void set_subdivide_depth(const int32_t divisions); + int32_t get_subdivide_depth() const; + + godot::AABB get_core_aabb() const; + bool is_valid_uv_coord(godot::Vector2 const& uv) const; + + godot::Array _create_mesh_array() const override; + }; +} diff --git a/extension/src/openvic-extension/Utilities.cpp b/extension/src/openvic-extension/Utilities.cpp new file mode 100644 index 0000000..4ca6855 --- /dev/null +++ b/extension/src/openvic-extension/Utilities.cpp @@ -0,0 +1,106 @@ +#include "Utilities.hpp" + +#include + +#include +#include + +using namespace godot; +using namespace OpenVic; + +Ref OpenVic::load_godot_image(String const& path) { + if (path.begins_with("res://")) { + ResourceLoader* loader = ResourceLoader::get_singleton(); + return loader ? loader->load(path) : nullptr; + } else { + 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, + 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(x) / static_cast(size), + static_cast(y) / static_cast(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/openvic-extension/Utilities.hpp b/extension/src/openvic-extension/Utilities.hpp new file mode 100644 index 0000000..32e7cb5 --- /dev/null +++ b/extension/src/openvic-extension/Utilities.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#define ERR(x) ((x) ? OK : FAILED) + +namespace OpenVic { + + inline std::string godot_to_std_string(godot::String const& str) { + return str.ascii().get_data(); + } + + inline godot::String std_to_godot_string(std::string const& str) { + 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 load_godot_image(godot::String const& path); + + void draw_pie_chart(godot::Ref 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); +} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp new file mode 100644 index 0000000..273bb85 --- /dev/null +++ b/extension/src/openvic-extension/register_types.cpp @@ -0,0 +1,63 @@ +#include "register_types.hpp" + +#include + +#include "openvic-extension/Checksum.hpp" +#include "openvic-extension/GameSingleton.hpp" +#include "openvic-extension/LoadLocalisation.hpp" +#include "openvic-extension/MapMesh.hpp" + +using namespace godot; +using namespace OpenVic; + +static Checksum* _checksum; +static LoadLocalisation* _load_localisation; +static GameSingleton* _map_singleton; + +void initialize_openvic_types(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + ClassDB::register_class(); + _checksum = memnew(Checksum); + Engine::get_singleton()->register_singleton("Checksum", Checksum::get_singleton()); + + ClassDB::register_class(); + _load_localisation = memnew(LoadLocalisation); + Engine::get_singleton()->register_singleton("LoadLocalisation", LoadLocalisation::get_singleton()); + + ClassDB::register_class(); + _map_singleton = memnew(GameSingleton); + Engine::get_singleton()->register_singleton("GameSingleton", GameSingleton::get_singleton()); + + ClassDB::register_class(); +} + +void uninitialize_openvic_types(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + Engine::get_singleton()->unregister_singleton("Checksum"); + memdelete(_checksum); + + Engine::get_singleton()->unregister_singleton("LoadLocalisation"); + memdelete(_load_localisation); + + Engine::get_singleton()->unregister_singleton("GameSingleton"); + memdelete(_map_singleton); +} + +extern "C" { + // Initialization. + GDExtensionBool GDE_EXPORT openvic_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization* r_initialization) { + GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + + init_obj.register_initializer(initialize_openvic_types); + init_obj.register_terminator(uninitialize_openvic_types); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); + } +} diff --git a/extension/src/openvic-extension/register_types.hpp b/extension/src/openvic-extension/register_types.hpp new file mode 100644 index 0000000..dd24689 --- /dev/null +++ b/extension/src/openvic-extension/register_types.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include + +void initialize_openvic_types(godot::ModuleInitializationLevel); +void uninitialize_openvic_types(godot::ModuleInitializationLevel); diff --git a/extension/src/register_types.cpp b/extension/src/register_types.cpp deleted file mode 100644 index 92c25a4..0000000 --- a/extension/src/register_types.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "register_types.h" - -#include - -#include "Checksum.hpp" -#include "GameSingleton.hpp" -#include "LoadLocalisation.hpp" -#include "MapMesh.hpp" - -using namespace godot; -using namespace OpenVic; - -static Checksum* _checksum; -static LoadLocalisation* _load_localisation; -static GameSingleton* _map_singleton; - -void initialize_openvic_types(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { - return; - } - - ClassDB::register_class(); - _checksum = memnew(Checksum); - Engine::get_singleton()->register_singleton("Checksum", Checksum::get_singleton()); - - ClassDB::register_class(); - _load_localisation = memnew(LoadLocalisation); - Engine::get_singleton()->register_singleton("LoadLocalisation", LoadLocalisation::get_singleton()); - - ClassDB::register_class(); - _map_singleton = memnew(GameSingleton); - Engine::get_singleton()->register_singleton("GameSingleton", GameSingleton::get_singleton()); - - ClassDB::register_class(); -} - -void uninitialize_openvic_types(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { - return; - } - - Engine::get_singleton()->unregister_singleton("Checksum"); - memdelete(_checksum); - - Engine::get_singleton()->unregister_singleton("LoadLocalisation"); - memdelete(_load_localisation); - - Engine::get_singleton()->unregister_singleton("GameSingleton"); - memdelete(_map_singleton); -} - -extern "C" { - // Initialization. - GDExtensionBool GDE_EXPORT openvic_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization* r_initialization) { - GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); - - init_obj.register_initializer(initialize_openvic_types); - init_obj.register_terminator(uninitialize_openvic_types); - init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); - - return init_obj.init(); - } -} diff --git a/extension/src/register_types.h b/extension/src/register_types.h deleted file mode 100644 index dd24689..0000000 --- a/extension/src/register_types.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include - -void initialize_openvic_types(godot::ModuleInitializationLevel); -void uninitialize_openvic_types(godot::ModuleInitializationLevel); diff --git a/game/src/Game/GameSession/MapControlPanel/MapControlPanel.gd b/game/src/Game/GameSession/MapControlPanel/MapControlPanel.gd index 350c1a8..6cdbf28 100644 --- a/game/src/Game/GameSession/MapControlPanel/MapControlPanel.gd +++ b/game/src/Game/GameSession/MapControlPanel/MapControlPanel.gd @@ -11,7 +11,7 @@ signal zoom_out_button_pressed var _mapmode_button_group : ButtonGroup # REQUIREMENTS: -# * UI-550, UI-552, UI-554, UI-561, UI-562 +# * UI-550, UI-552, UI-554, UI-561, UI-562, UI-563 func _add_mapmode_button(identifier : String) -> void: var button := Button.new() button.text = identifier @@ -37,7 +37,7 @@ func _on_game_session_menu_button_pressed() -> void: # REQUIREMENTS: # * SS-76 -# * UIFUN-129, UIFUN-131, UIFUN-133, UIFUN-140, UIFUN-141 +# * UIFUN-129, UIFUN-131, UIFUN-133, UIFUN-140, UIFUN-141, UIFUN-142 func _mapmode_pressed(button : BaseButton) -> void: GameSingleton.set_mapmode(button.tooltip_text) diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn index 7e49ac8..7a982e9 100644 --- a/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn +++ b/game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn @@ -93,15 +93,18 @@ layout_mode = 2 mouse_filter = 1 [node name="PopStats" type="HBoxContainer" parent="PanelList/InteractList"] +editor_description = "UI-124" layout_mode = 2 [node name="PopTypeChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")] +editor_description = "UI-125" layout_mode = 2 [node name="PopIdeologyChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")] layout_mode = 2 [node name="PopCultureChart" parent="PanelList/InteractList/PopStats" instance=ExtResource("2_3oytt")] +editor_description = "UI-127" layout_mode = 2 [node name="HSeparator3" type="HSeparator" parent="PanelList/InteractList"] diff --git a/game/src/Game/LoadingScreen.tscn b/game/src/Game/LoadingScreen.tscn index 5ae1cb9..01b6856 100644 --- a/game/src/Game/LoadingScreen.tscn +++ b/game/src/Game/LoadingScreen.tscn @@ -59,6 +59,7 @@ _data = { } [node name="LoadingScreen" type="Control" node_paths=PackedStringArray("progress_bar", "quote_label", "animation_player")] +editor_description = "UI-24" layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 -- cgit v1.2.3-56-ga3b1