diff options
Diffstat (limited to 'extension')
-rw-r--r-- | extension/src/LoadLocalisation.cpp | 8 | ||||
-rw-r--r-- | extension/src/LoadLocalisation.hpp | 8 | ||||
-rw-r--r-- | extension/src/MapMesh.cpp | 151 | ||||
-rw-r--r-- | extension/src/MapMesh.hpp | 34 | ||||
-rw-r--r-- | extension/src/MapSingleton.cpp | 256 | ||||
-rw-r--r-- | extension/src/MapSingleton.hpp | 34 | ||||
-rw-r--r-- | extension/src/TestSingleton.cpp | 4 | ||||
-rw-r--r-- | extension/src/TestSingleton.hpp | 4 | ||||
-rw-r--r-- | extension/src/openvic2/Map.cpp | 89 | ||||
-rw-r--r-- | extension/src/openvic2/Map.hpp | 45 | ||||
-rw-r--r-- | extension/src/register_types.cpp | 20 |
11 files changed, 635 insertions, 18 deletions
diff --git a/extension/src/LoadLocalisation.cpp b/extension/src/LoadLocalisation.cpp index c95c08b..8698bb2 100644 --- a/extension/src/LoadLocalisation.cpp +++ b/extension/src/LoadLocalisation.cpp @@ -8,7 +8,7 @@ using namespace godot; using namespace OpenVic2; -LoadLocalisation *LoadLocalisation::singleton = nullptr; +LoadLocalisation* LoadLocalisation::singleton = nullptr; void LoadLocalisation::_bind_methods() { ClassDB::bind_method(D_METHOD("load_file", "file_path", "locale"), &LoadLocalisation::load_file); @@ -16,7 +16,7 @@ void LoadLocalisation::_bind_methods() { ClassDB::bind_method(D_METHOD("load_localisation_dir", "dir_path"), &LoadLocalisation::load_localisation_dir); } -LoadLocalisation *LoadLocalisation::get_singleton() { +LoadLocalisation* LoadLocalisation::get_singleton() { return singleton; } @@ -54,7 +54,7 @@ Error LoadLocalisation::_load_file_into_translation(String const& file_path, Ref } Ref<Translation> LoadLocalisation::_get_translation(String const& locale) { - TranslationServer *server = TranslationServer::get_singleton(); + TranslationServer* server = TranslationServer::get_singleton(); Ref<Translation> translation = server->get_translation_object(locale); if (translation.is_null() || translation->get_locale() != locale) { translation.instantiate(); @@ -93,7 +93,7 @@ Error LoadLocalisation::load_locale_dir(String const& dir_path, String const& lo */ Error LoadLocalisation::load_localisation_dir(String const& dir_path) { if (DirAccess::dir_exists_absolute(dir_path)) { - TranslationServer *server = TranslationServer::get_singleton(); + TranslationServer* server = TranslationServer::get_singleton(); Error err = OK; for (String const& locale_name : DirAccess::get_directories_at(dir_path)) { if (locale_name == server->standardize_locale(locale_name)) { diff --git a/extension/src/LoadLocalisation.hpp b/extension/src/LoadLocalisation.hpp index 90f3158..f54a025 100644 --- a/extension/src/LoadLocalisation.hpp +++ b/extension/src/LoadLocalisation.hpp @@ -4,11 +4,11 @@ #include <godot_cpp/classes/translation.hpp> namespace OpenVic2 { - class LoadLocalisation : public godot::Object - { + class LoadLocalisation : public godot::Object { + GDCLASS(LoadLocalisation, godot::Object) - static LoadLocalisation *singleton; + static LoadLocalisation* singleton; godot::Error _load_file_into_translation(godot::String const& file_path, godot::Ref<godot::Translation> translation); godot::Ref<godot::Translation> _get_translation(godot::String const& locale); @@ -17,7 +17,7 @@ namespace OpenVic2 { static void _bind_methods(); public: - static LoadLocalisation *get_singleton(); + static LoadLocalisation* get_singleton(); LoadLocalisation(); ~LoadLocalisation(); diff --git a/extension/src/MapMesh.cpp b/extension/src/MapMesh.cpp new file mode 100644 index 0000000..f0fc819 --- /dev/null +++ b/extension/src/MapMesh.cpp @@ -0,0 +1,151 @@ +#include "MapMesh.hpp" + +#include <godot_cpp/templates/vector.hpp> + +using namespace godot; +using namespace OpenVic2; + +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 int divisions) { + subdivide_w = divisions > 0 ? divisions : 0; + _request_update(); +} + +int MapMesh::get_subdivide_width() const { + return subdivide_w; +} + +void MapMesh::set_subdivide_depth(const int divisions) { + subdivide_d = divisions > 0 ? divisions : 0; + _request_update(); +} + +int 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 int vertex_count = (subdivide_w + 2) * (subdivide_d + 2); + const int 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; + + int 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 (int j = 0; j <= subdivide_d + 1; ++j) { + point.x = start_pos.x; + uv.x = -repeat_proportion; + + for (int 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 new file mode 100644 index 0000000..d8727cf --- /dev/null +++ b/extension/src/MapMesh.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <godot_cpp/classes/primitive_mesh.hpp> + +namespace OpenVic2 { + class MapMesh : public godot::PrimitiveMesh { + GDCLASS(MapMesh, godot::PrimitiveMesh) + + float aspect_ratio = 2.0f, repeat_proportion = 0.5f; + int 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 int divisions); + int get_subdivide_width() const; + + void set_subdivide_depth(const int divisions); + int 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/MapSingleton.cpp b/extension/src/MapSingleton.cpp new file mode 100644 index 0000000..0f5fe7c --- /dev/null +++ b/extension/src/MapSingleton.cpp @@ -0,0 +1,256 @@ +#include "MapSingleton.hpp" + +#include <godot_cpp/variant/utility_functions.hpp> +#include <godot_cpp/classes/file_access.hpp> +#include <godot_cpp/classes/json.hpp> + +using namespace godot; +using namespace OpenVic2; + +MapSingleton* MapSingleton::singleton = nullptr; + +void MapSingleton::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_province_identifier_file", "file_path"), &MapSingleton::load_province_identifier_file); + ClassDB::bind_method(D_METHOD("load_province_shape_file", "file_path"), &MapSingleton::load_province_shape_file); + ClassDB::bind_method(D_METHOD("get_province_identifier_from_pixel_coords", "coords"), &MapSingleton::get_province_identifier_from_pixel_coords); + ClassDB::bind_method(D_METHOD("get_width"), &MapSingleton::get_width); + ClassDB::bind_method(D_METHOD("get_height"), &MapSingleton::get_height); + ClassDB::bind_method(D_METHOD("get_province_index_image"), &MapSingleton::get_province_index_image); + ClassDB::bind_method(D_METHOD("get_province_colour_image"), &MapSingleton::get_province_colour_image); +} + +MapSingleton* MapSingleton::get_singleton() { + return singleton; +} + +MapSingleton::MapSingleton() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; +} + +MapSingleton::~MapSingleton() { + ERR_FAIL_COND(singleton != this); + singleton = nullptr; +} + +Error MapSingleton::load_province_identifier_file(String const& file_path) { + UtilityFunctions::print("Loading identifier file: ", file_path); + Ref<FileAccess> 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 province identifier file: ", file_path); + return err == OK ? FAILED : err; + } + String json_string = file->get_as_text(); + Ref<JSON> json; + json.instantiate(); + err = json->parse(json_string); + if (err) { + UtilityFunctions::push_error("Failed to parse province identifier file as JSON: ", file_path, + "\nError at line ", json->get_error_line(), ": ", json->get_error_message()); + return err; + } + Variant json_var = json->get_data(); + Variant::Type type = json_var.get_type(); + if (type != Variant::DICTIONARY) { + UtilityFunctions::push_error("Invalid province identifier JSON: root has type ", + Variant::get_type_name(type), " (expected Dictionary)"); + return FAILED; + } + Dictionary prov_dict = json_var; + Array prov_identifiers = prov_dict.keys(); + for (int idx = 0; idx < prov_identifiers.size(); ++idx) { + String const& identifier = prov_identifiers[idx]; + Variant const& colour_var = prov_dict[identifier]; + if (identifier.is_empty()) { + UtilityFunctions::push_error("Empty province identifier with colour: ", colour_var); + err = FAILED; + continue; + } + static const String prov_prefix = "prov_"; + if (!identifier.begins_with(prov_prefix)) + UtilityFunctions::push_warning("Province identifier missing prefix: ", identifier); + type = colour_var.get_type(); + Province::colour_t colour = Province::NULL_COLOUR; + if (type == Variant::ARRAY) { + Array colour_array = colour_var; + if (colour_array.size() == 3) { + for (int jdx = 0; jdx < 3; ++jdx) { + Variant var = colour_array[jdx]; + if (var.get_type() != Variant::FLOAT) { + colour = Province::NULL_COLOUR; + break; + } + double colour_double = var; + if (std::trunc(colour_double) != colour_double) { + colour = Province::NULL_COLOUR; + break; + } + int64_t colour_int = static_cast<int64_t>(colour_double); + if (colour_int < 0 || colour_int > 255) { + colour = Province::NULL_COLOUR; + break; + } + colour = (colour << 8) | colour_int; + } + } + } else if (type == Variant::STRING) { + String colour_string = colour_var; + if (colour_string.is_valid_hex_number()) { + int64_t colour_int = colour_string.hex_to_int(); + if (0 <= colour_int && colour_int <= 0xFFFFFF) + colour = colour_int; + } + } + if (colour == Province::NULL_COLOUR) { + UtilityFunctions::push_error("Invalid province identifier colour for ", identifier, ": ", colour_var); + err = FAILED; + continue; + } + std::string error_message; + if (!map.add_province(identifier.utf8().get_data(), colour, error_message)) { + UtilityFunctions::push_error(error_message.c_str()); + err = FAILED; + } + } + map.lock_provinces(); + return err; +} + +static Province::colour_t colour_at(PackedByteArray const& colour_data_array, int32_t idx) { + return (colour_data_array[idx * 3] << 16) | (colour_data_array[idx * 3 + 1] << 8) | colour_data_array[idx * 3 + 2]; +} + +Error MapSingleton::load_province_shape_file(String const& file_path) { + if (province_shape_image.is_valid()) { + UtilityFunctions::push_error("Province shape file has already been loaded, cannot load: ", file_path); + return FAILED; + } + province_shape_image.instantiate(); + Error err = province_shape_image->load(file_path); + if (err != OK) { + UtilityFunctions::push_error("Failed to load province shape file: ", file_path); + province_shape_image.unref(); + return err; + } + width = province_shape_image->get_width(); + height = province_shape_image->get_height(); + if (width < 1 || height < 1) { + UtilityFunctions::push_error("Invalid dimensions (", width, "x", height, ") for province shape file: ", file_path); + err = FAILED; + } + static const Image::Format expected_format = Image::FORMAT_RGB8; + Image::Format format = province_shape_image->get_format(); + if (format != expected_format) { + UtilityFunctions::push_error("Invalid format (", format, ", should be ", expected_format, ") for province shape file: ", file_path); + err = FAILED; + } + if (err) { + province_shape_image.unref(); + return err; + } + + std::vector<bool> province_checklist(map.get_province_count()); + + PackedByteArray shape_data_array = province_shape_image->get_data(), index_data_array; + index_data_array.resize(width * height * sizeof(Province::index_t)); + Province::index_t* index_data = reinterpret_cast<Province::index_t*>(index_data_array.ptrw()); + + for (int32_t y = 0; y < height; ++y) { + for (int32_t x = 0; x < width; ++x) { + const int32_t idx = x + y * width; + const Province::colour_t colour = colour_at(shape_data_array, idx); + if (colour == Province::NULL_COLOUR) { + index_data[idx] = Province::NULL_INDEX; + continue; + } + if (x > 0) { + const int32_t jdx = idx - 1; + if (colour_at(shape_data_array, jdx) == colour) { + index_data[idx] = index_data[jdx]; + continue; + } + } + if (y > 0) { + const int32_t jdx = idx - width; + if (colour_at(shape_data_array, jdx) == colour) { + index_data[idx] = index_data[jdx]; + continue; + } + } + const Province* province = map.get_province_by_colour(colour); + if (province) { + Province::index_t index = province->get_index(); + index_data[idx] = index; + province_checklist[index - 1] = true; + continue; + } + UtilityFunctions::push_error("Unrecognised province colour ", Province::colour_to_hex_string(colour).c_str(), " at (", x, ", ", y, ")"); + err = FAILED; + index_data[idx] = Province::NULL_INDEX; + } + } + + for (size_t idx = 0; idx < province_checklist.size(); ++idx) { + if (!province_checklist[idx]) { + Province* province = map.get_province_by_index(idx + 1); + if (province) UtilityFunctions::push_error("Province missing from shape image: ", province->to_string().c_str()); + else UtilityFunctions::push_error("Province missing for index: ", static_cast<int32_t>(idx + 1)); + err = FAILED; + } + } + + province_index_image = Image::create_from_data(width, height, false, Image::FORMAT_RG8, index_data_array); + if (province_index_image.is_null()) { + UtilityFunctions::push_error("Failed to create province ID image"); + err = FAILED; + } + + PackedByteArray colour_data_array; + colour_data_array.resize((Province::MAX_INDEX + 1) * 3); + for (size_t idx = 1; idx <= map.get_province_count(); ++idx) { + const Province* province = map.get_province_by_index(idx); + if (province) { + const Province::colour_t colour = province->get_colour(); + colour_data_array[3 * idx + 0] = (colour >> 16) & 0xFF; + colour_data_array[3 * idx + 1] = (colour >> 8) & 0xFF; + colour_data_array[3 * idx + 2] = colour & 0xFF; + } else UtilityFunctions::push_error("Missing province at index ", static_cast<int32_t>(idx)); + } + static const int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * 4); + province_colour_image = Image::create_from_data(PROVINCE_INDEX_SQRT, PROVINCE_INDEX_SQRT, false, Image::FORMAT_RGB8, colour_data_array); + if (province_colour_image.is_null()) { + UtilityFunctions::push_error("Failed to create province colour image"); + err = FAILED; + } + + return err; +} + +String MapSingleton::get_province_identifier_from_pixel_coords(Vector2i const& coords) { + if (province_index_image.is_valid()) { + const PackedByteArray index_data_array = province_index_image->get_data(); + const Province::index_t* index_data = reinterpret_cast<const Province::index_t*>(index_data_array.ptr()); + const int32_t x_mod_w = UtilityFunctions::posmod(coords.x, width); + const int32_t y_mod_h = UtilityFunctions::posmod(coords.y, height); + const Province* province = map.get_province_by_index(index_data[x_mod_w + y_mod_h * width]); + if (province) return province->get_identifier().c_str(); + } + return String{}; +} + +int32_t MapSingleton::get_width() const { + return width; +} + +int32_t MapSingleton::get_height() const { + return height; +} + +Ref<Image> MapSingleton::get_province_index_image() const { + return province_index_image; +} + +Ref<Image> MapSingleton::get_province_colour_image() const { + return province_colour_image; +} diff --git a/extension/src/MapSingleton.hpp b/extension/src/MapSingleton.hpp new file mode 100644 index 0000000..71761cd --- /dev/null +++ b/extension/src/MapSingleton.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <godot_cpp/classes/image.hpp> +#include "openvic2/Map.hpp" + +namespace OpenVic2 { + class MapSingleton : public godot::Object { + + GDCLASS(MapSingleton, godot::Object) + + static MapSingleton* singleton; + + godot::Ref<godot::Image> province_shape_image, province_index_image, province_colour_image; + int32_t width = 0, height = 0; + Map map; + + protected: + static void _bind_methods(); + + public: + static MapSingleton* get_singleton(); + + MapSingleton(); + ~MapSingleton(); + + godot::Error load_province_identifier_file(godot::String const& file_path); + godot::Error load_province_shape_file(godot::String const& file_path); + godot::String get_province_identifier_from_pixel_coords(godot::Vector2i const& coords); + int32_t get_width() const; + int32_t get_height() const; + godot::Ref<godot::Image> get_province_index_image() const; + godot::Ref<godot::Image> get_province_colour_image() const; + }; +} diff --git a/extension/src/TestSingleton.cpp b/extension/src/TestSingleton.cpp index 0855a30..d9e2b03 100644 --- a/extension/src/TestSingleton.cpp +++ b/extension/src/TestSingleton.cpp @@ -6,14 +6,14 @@ using namespace godot; using namespace OpenVic2; -TestSingleton *TestSingleton::singleton = nullptr; +TestSingleton* TestSingleton::singleton = nullptr; void TestSingleton::_bind_methods() { ClassDB::bind_method(D_METHOD("hello_singleton"), &TestSingleton::hello_singleton); } -TestSingleton *TestSingleton::get_singleton() +TestSingleton* TestSingleton::get_singleton() { return singleton; } diff --git a/extension/src/TestSingleton.hpp b/extension/src/TestSingleton.hpp index de27589..d140516 100644 --- a/extension/src/TestSingleton.hpp +++ b/extension/src/TestSingleton.hpp @@ -8,13 +8,13 @@ namespace OpenVic2 { { GDCLASS(TestSingleton, godot::Object) - static TestSingleton *singleton; + static TestSingleton* singleton; protected: static void _bind_methods(); public: - static TestSingleton *get_singleton(); + static TestSingleton* get_singleton(); TestSingleton(); ~TestSingleton(); diff --git a/extension/src/openvic2/Map.cpp b/extension/src/openvic2/Map.cpp new file mode 100644 index 0000000..d980b88 --- /dev/null +++ b/extension/src/openvic2/Map.cpp @@ -0,0 +1,89 @@ +#include "Map.hpp" + +#include <cassert> +#include <sstream> +#include <iomanip> + +using namespace OpenVic2; + +Province::Province(index_t new_index, std::string const& new_identifier, colour_t new_colour) : + index(new_index), identifier(new_identifier), colour(new_colour) { + assert(index != NULL_INDEX); + assert(!identifier.empty()); + assert(colour != NULL_COLOUR); +} + +std::string Province::colour_to_hex_string(colour_t colour) { + std::ostringstream stream; + stream << std::hex << std::setfill('0') << std::setw(6) << colour; + return stream.str(); +} + +Province::index_t Province::get_index() const { + return index; +} + +std::string const& Province::get_identifier() const { + return identifier; +} + +Province::colour_t Province::get_colour() const { + return colour; +} + +std::string Province::to_string() const { + return "(#" + std::to_string(index) + ", " + identifier + ", 0x" + colour_to_hex_string(colour) + ")"; +} + +bool Map::add_province(std::string const& identifier, Province::colour_t colour, std::string& error_message) { + if (provinces_locked) { + error_message = "The map's province list has already been locked!"; + return false; + } + if (provinces.size() >= Province::MAX_INDEX) { + error_message = "The map's province list is full - there can be at most " + std::to_string(Province::MAX_INDEX) + " provinces"; + return false; + } + if (colour == Province::NULL_COLOUR || colour > Province::MAX_COLOUR) { + error_message = "Invalid province colour: " + Province::colour_to_hex_string(colour); + return false; + } + Province new_province{ static_cast<Province::index_t>(provinces.size() + 1), identifier, colour }; + for (Province const& province : provinces) { + if (province.identifier == identifier) { + error_message = "Duplicate province identifiers: " + province.to_string() + " and " + new_province.to_string(); + return false; + } + if (province.colour == colour) { + error_message = "Duplicate province colours: " + province.to_string() + " and " + new_province.to_string(); + return false; + } + } + provinces.push_back(new_province); + error_message = "Added province: " + new_province.to_string(); + return true; +} + +void Map::lock_provinces() { + provinces_locked = true; +} + +size_t Map::get_province_count() const { + return provinces.size(); +} + +Province* Map::get_province_by_index(Province::index_t index) { + return index != Province::NULL_INDEX && index <= provinces.size() ? &provinces[index - 1] : nullptr; +} + +Province* Map::get_province_by_identifier(std::string const& identifier) { + for (Province& province : provinces) + if (province.identifier == identifier) return &province; + return nullptr; +} + +Province* Map::get_province_by_colour(Province::colour_t colour) { + for (Province& province : provinces) + if (province.colour == colour) return &province; + return nullptr; +} diff --git a/extension/src/openvic2/Map.hpp b/extension/src/openvic2/Map.hpp new file mode 100644 index 0000000..365d78b --- /dev/null +++ b/extension/src/openvic2/Map.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include <string> +#include <cstdint> +#include <vector> + +namespace OpenVic2 { + + struct Province { + using colour_t = uint32_t; + using index_t = uint16_t; + friend struct Map; + static const colour_t NULL_COLOUR = 0, MAX_COLOUR = 0xFFFFFF; + static const index_t NULL_INDEX = 0, MAX_INDEX = 0xFFFF; + private: + index_t index; + std::string identifier; + colour_t colour; + + Province(index_t index, std::string const& identifier, colour_t colour); + public: + static std::string colour_to_hex_string(colour_t colour); + + index_t get_index() const; + std::string const& get_identifier() const; + colour_t get_colour() const; + std::string to_string() const; + }; + + struct Map { + private: + std::vector<Province> provinces; + bool provinces_locked = false; + + public: + bool add_province(std::string const& identifier, Province::colour_t colour, std::string& error_message); + void lock_provinces(); + size_t get_province_count() const; + + Province* get_province_by_index(Province::index_t index); + Province* get_province_by_identifier(std::string const& identifier); + Province* get_province_by_colour(Province::colour_t colour); + }; + +} diff --git a/extension/src/register_types.cpp b/extension/src/register_types.cpp index d1613a5..16e59b2 100644 --- a/extension/src/register_types.cpp +++ b/extension/src/register_types.cpp @@ -9,6 +9,8 @@ #include "Simulation.hpp" #include "Checksum.hpp" #include "LoadLocalisation.hpp" +#include "MapSingleton.hpp" +#include "MapMesh.hpp" using namespace godot; using namespace OpenVic2; @@ -17,9 +19,9 @@ static TestSingleton* _test_singleton; static Simulation* _simulation; static Checksum* _checksum; static LoadLocalisation* _load_localisation; +static MapSingleton* _map_singleton; -void initialize_openvic2_types(ModuleInitializationLevel p_level) -{ +void initialize_openvic2_types(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } @@ -40,6 +42,11 @@ void initialize_openvic2_types(ModuleInitializationLevel p_level) _load_localisation = memnew(LoadLocalisation); Engine::get_singleton()->register_singleton("LoadLocalisation", LoadLocalisation::get_singleton()); + ClassDB::register_class<MapSingleton>(); + _map_singleton = memnew(MapSingleton); + Engine::get_singleton()->register_singleton("MapSingleton", MapSingleton::get_singleton()); + + ClassDB::register_class<MapMesh>(); } void uninitialize_openvic2_types(ModuleInitializationLevel p_level) { @@ -58,15 +65,16 @@ void uninitialize_openvic2_types(ModuleInitializationLevel p_level) { Engine::get_singleton()->unregister_singleton("LoadLocalisation"); memdelete(_load_localisation); + + Engine::get_singleton()->unregister_singleton("MapSingleton"); + memdelete(_map_singleton); } -extern "C" -{ +extern "C" { // Initialization. - GDExtensionBool GDE_EXPORT openvic2_library_init(const GDExtensionInterface *p_interface, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) - { + GDExtensionBool GDE_EXPORT openvic2_library_init(const GDExtensionInterface* p_interface, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization* r_initialization) { GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization); init_obj.register_initializer(initialize_openvic2_types); |