#include "MapSingleton.hpp" #include #include #include 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_region_file", "file_path"), &MapSingleton::load_region_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; } typedef Error (MapSingleton::* parse_json_entry_function)(String const& identifier, Variant const& entry); static Error parse_json_dictionary_file(String const& file_description, String const& file_path, String const& identifier_prefix, MapSingleton* map_singleton, parse_json_entry_function parse_entry) { UtilityFunctions::print("Loading ", file_description, " file: ", file_path); 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 ", file_description, " file: ", file_path); return err == OK ? FAILED : err; } const String json_string = file->get_as_text(); Ref json; json.instantiate(); err = json->parse(json_string); if (err) { UtilityFunctions::push_error("Failed to parse ", file_description, " file as JSON: ", file_path, "\nError at line ", json->get_error_line(), ": ", json->get_error_message()); return err; } const Variant json_var = json->get_data(); const Variant::Type type = json_var.get_type(); if (type != Variant::DICTIONARY) { UtilityFunctions::push_error("Invalid ", file_description, " JSON: root has type ", Variant::get_type_name(type), " (expected Dictionary)"); return FAILED; } const Dictionary dict = json_var; const Array identifiers = dict.keys(); for (int idx = 0; idx < identifiers.size(); ++idx) { String const& identifier = identifiers[idx]; Variant const& entry = dict[identifier]; if (identifier.is_empty()) { UtilityFunctions::push_error("Empty identifier in ", file_description, " file with entry: ", entry); err = FAILED; continue; } if (!identifier.begins_with(identifier_prefix)) UtilityFunctions::push_warning("Identifier in ", file_description, " file missing \"", identifier_prefix, "\" prefix: ", identifier); if ((map_singleton->*parse_entry)(identifier, entry) != OK) err = FAILED; } return err; } Error MapSingleton::_parse_province_identifier_entry(String const& identifier, Variant const& entry) { const Variant::Type type = entry.get_type(); Province::colour_t colour = Province::NULL_COLOUR; if (type == Variant::ARRAY) { const Array colour_array = entry; if (colour_array.size() == 3) { for (int jdx = 0; jdx < 3; ++jdx) { const 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(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 = entry; 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; } } else { UtilityFunctions::push_error("Invalid colour for province identifier \"", identifier, "\": ", entry); return FAILED; } std::string error_message; if (!map.add_province(identifier.utf8().get_data(), colour, error_message)) { UtilityFunctions::push_error(error_message.c_str()); return FAILED; } return OK; } Error MapSingleton::load_province_identifier_file(String const& file_path) { const Error err = parse_json_dictionary_file("province identifier", file_path, "prov_", this, &MapSingleton::_parse_province_identifier_entry); map.lock_provinces(); return err; } Error MapSingleton::_parse_region_entry(String const& identifier, Variant const& entry) { Error err = OK; Variant::Type type = entry.get_type(); std::vector province_identifiers; if (type == Variant::ARRAY) { const Array province_array = entry; for (int64_t idx = 0; idx < province_array.size(); ++idx) { const Variant province_var = province_array[idx]; type = province_var.get_type(); if (type == Variant::STRING) { String province_string = province_var; province_identifiers.push_back(province_string.utf8().get_data()); } else { UtilityFunctions::push_error("Invalid province identifier for region \"", identifier, "\": ", entry); err = FAILED; } } } std::string error_message; if (!map.add_region(identifier.utf8().get_data(), province_identifiers, error_message)) { UtilityFunctions::push_error(error_message.c_str()); return FAILED; } return err; } Error MapSingleton::load_region_file(String const& file_path) { const Error err = parse_json_dictionary_file("region", file_path, "region_", this, &MapSingleton::_parse_region_entry); map.lock_regions(); 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; const 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 province_checklist(map.get_province_count()); const PackedByteArray shape_data_array = province_shape_image->get_data(); PackedByteArray index_data_array; index_data_array.resize(width * height * sizeof(Province::index_t)); Province::index_t* index_data = reinterpret_cast(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; } } Province const* province = map.get_province_by_colour(colour); if (province) { const 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 const* 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(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) { Province const* province = map.get_province_by_index(idx); if (province) { Province::colour_t colour = colour = province->get_colour(); const Region* region = province->get_region(); if (region) colour = region->get_provinces().front()->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(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) const { if (province_index_image.is_valid()) { const PackedByteArray index_data_array = province_index_image->get_data(); Province::index_t const* index_data = reinterpret_cast(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); Province const* 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 MapSingleton::get_province_index_image() const { return province_index_image; } Ref MapSingleton::get_province_colour_image() const { return province_colour_image; }