aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Gone2Daly <71726742+Gone2Daly@users.noreply.github.com>2023-04-07 18:10:49 +0200
committer Spartan322 <Megacake1234@gmail.com>2023-04-14 17:22:22 +0200
commit7241811bd8c9493b7b6c6480e8d63a5fb7f38e4e (patch)
treeb5a33a733398b511eb0aa7781860c8accbadd2a0
parentc0d76b78d3762e6eec3ed1c62618be84c5b7559b (diff)
Add minimap for terrain map
With accurate viewport shape display on minimap With Mapmode management With Region mapmode With Province mapmode With Index mapmode With Minimap single-click movement With Minimap drag-click movement
-rw-r--r--extension/src/Checksum.hpp8
-rw-r--r--extension/src/LoadLocalisation.hpp1
-rw-r--r--extension/src/MapMesh.cpp1
-rw-r--r--extension/src/MapSingleton.cpp359
-rw-r--r--extension/src/MapSingleton.hpp24
-rw-r--r--extension/src/Simulation.hpp6
-rw-r--r--extension/src/TestSingleton.hpp1
-rw-r--r--extension/src/openvic2/Map.cpp283
-rw-r--r--extension/src/openvic2/Map.hpp78
-rw-r--r--extension/src/openvic2/Province.cpp40
-rw-r--r--extension/src/openvic2/Region.cpp26
-rw-r--r--extension/src/openvic2/Types.hpp7
-rw-r--r--extension/src/register_types.cpp7
-rw-r--r--extension/src/register_types.h2
-rw-r--r--game/art/terrain/terrain.png (renamed from game/common/map/terrain/terrain.png)bin27542091 -> 27542091 bytes
-rw-r--r--game/art/terrain/terrain.png.import (renamed from game/common/map/terrain/terrain.png.import)8
-rw-r--r--game/art/ui/minimap.pngbin0 -> 122017 bytes
-rw-r--r--game/art/ui/minimap.png.import34
-rw-r--r--game/art/ui/minimap_frame.pngbin0 -> 1043 bytes
-rw-r--r--game/art/ui/minimap_frame.png.import34
-rw-r--r--game/common/map/regions.json6
-rw-r--r--game/localisation/en_GB/mapmodes.csv5
-rw-r--r--game/localisation/en_GB/mapmodes.csv.import3
-rw-r--r--game/src/Autoload/Events.gd5
-rw-r--r--game/src/GameSession/GameSession.tscn9
-rw-r--r--game/src/GameSession/MapControlPanel.gd40
-rw-r--r--game/src/GameSession/MapControlPanel.tscn36
-rw-r--r--game/src/GameSession/MapView.gd160
-rw-r--r--game/src/GameSession/MapView.tscn10
-rw-r--r--game/src/GameSession/Minimap.gd89
-rw-r--r--game/src/GameSession/ProvinceOverviewPanel.tscn2
-rw-r--r--game/src/GameSession/TerrainMap.gdshader13
32 files changed, 1028 insertions, 269 deletions
diff --git a/extension/src/Checksum.hpp b/extension/src/Checksum.hpp
index ebdbd43..717910e 100644
--- a/extension/src/Checksum.hpp
+++ b/extension/src/Checksum.hpp
@@ -1,15 +1,13 @@
#pragma once
-#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/core/class_db.hpp>
-#include <godot_cpp/variant/utility_functions.hpp>
namespace OpenVic2 {
class Checksum : public godot::Object {
GDCLASS(Checksum, godot::Object)
//BEGIN BOILERPLATE
- static Checksum* _checksum;
+ inline static Checksum* _checksum = nullptr;
protected:
static void _bind_methods() {
@@ -33,6 +31,4 @@ namespace OpenVic2 {
return godot::String("1234abcd");
}
};
-
- Checksum* Checksum::_checksum = nullptr;
-} \ No newline at end of file
+}
diff --git a/extension/src/LoadLocalisation.hpp b/extension/src/LoadLocalisation.hpp
index f54a025..49c0313 100644
--- a/extension/src/LoadLocalisation.hpp
+++ b/extension/src/LoadLocalisation.hpp
@@ -1,6 +1,5 @@
#pragma once
-#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/classes/translation.hpp>
namespace OpenVic2 {
diff --git a/extension/src/MapMesh.cpp b/extension/src/MapMesh.cpp
index f0fc819..91c7611 100644
--- a/extension/src/MapMesh.cpp
+++ b/extension/src/MapMesh.cpp
@@ -111,7 +111,6 @@ Array MapMesh::_create_mesh_array() const {
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;
diff --git a/extension/src/MapSingleton.cpp b/extension/src/MapSingleton.cpp
index 0f5fe7c..73cf522 100644
--- a/extension/src/MapSingleton.cpp
+++ b/extension/src/MapSingleton.cpp
@@ -11,21 +11,50 @@ 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_province_index_from_uv_coords", "coords"), &MapSingleton::get_province_index_from_uv_coords);
+ ClassDB::bind_method(D_METHOD("get_province_identifier_from_uv_coords", "coords"), &MapSingleton::get_province_identifier_from_uv_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);
+
+ ClassDB::bind_method(D_METHOD("update_colour_image"), &MapSingleton::update_colour_image);
+ ClassDB::bind_method(D_METHOD("get_mapmode_count"), &MapSingleton::get_mapmode_count);
+ ClassDB::bind_method(D_METHOD("get_mapmode_identifier", "index"), &MapSingleton::get_mapmode_identifier);
+ ClassDB::bind_method(D_METHOD("set_mapmode", "identifier"), &MapSingleton::set_mapmode);
}
MapSingleton* MapSingleton::get_singleton() {
return singleton;
}
+/* REQUIREMENTS:
+ * MAP-21, MAP-25
+ */
MapSingleton::MapSingleton() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
+
+ using mapmode_t = std::pair<std::string, Mapmode::colour_func_t>;
+ const std::vector<mapmode_t> mapmodes = {
+ { "mapmode_province", [](Map const&, Province const& province) -> Province::colour_t { return province.get_colour(); } },
+ { "mapmode_region", [](Map const&, Province const& province) -> Province::colour_t {
+ Region const* region = province.get_region();
+ if (region != nullptr) return region->get_provinces().front()->get_colour();
+ return province.get_colour();
+ } },
+ { "mapmode_index", [](Map const& map, Province const& province) -> Province::colour_t {
+ const uint8_t f = float(province.get_index()) / float(map.get_province_count()) * 255.0f;
+ return (f << 16) | (f << 8) | f;
+ } }
+ };
+ std::string error_message = "";
+ for (mapmode_t mapmode : mapmodes)
+ if (map.add_mapmode(mapmode.first, mapmode.second, error_message) != SUCCESS)
+ UtilityFunctions::push_error(error_message.c_str());
}
MapSingleton::~MapSingleton() {
@@ -33,172 +62,171 @@ MapSingleton::~MapSingleton() {
singleton = nullptr;
}
-Error MapSingleton::load_province_identifier_file(String const& file_path) {
- UtilityFunctions::print("Loading identifier file: ", file_path);
+Error MapSingleton::parse_json_dictionary_file(String const& file_description, String const& file_path,
+ String const& identifier_prefix, parse_json_entry_func_t parse_entry) const {
+ UtilityFunctions::print("Loading ", file_description, " 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);
+ UtilityFunctions::push_error("Failed to load ", file_description, " file: ", file_path);
return err == OK ? FAILED : err;
}
- String json_string = file->get_as_text();
+ const 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,
+ if (err != OK) {
+ 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;
}
- Variant json_var = json->get_data();
- Variant::Type type = json_var.get_type();
+ const Variant json_var = json->get_data();
+ const Variant::Type type = json_var.get_type();
if (type != Variant::DICTIONARY) {
- UtilityFunctions::push_error("Invalid province identifier JSON: root has type ",
+ UtilityFunctions::push_error("Invalid ", file_description, " 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];
+ 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 province identifier with colour: ", colour_var);
+ UtilityFunctions::push_error("Empty identifier in ", file_description, " file with entry: ", entry);
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;
+ if (!identifier.begins_with(identifier_prefix))
+ UtilityFunctions::push_warning("Identifier in ", file_description, " file missing \"", identifier_prefix, "\" prefix: ", identifier);
+ if (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;
}
- }
- } 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;
+ 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;
}
}
- 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;
+ } 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) != SUCCESS) {
+ 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](String const& identifier, Variant const& entry) -> Error {
+ return this->_parse_province_identifier_entry(identifier, entry);
+ });
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::_parse_region_entry(String const& identifier, Variant const& entry) {
+ Error err = OK;
+ Variant::Type type = entry.get_type();
+ std::vector<std::string> 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) != SUCCESS) {
+ 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](String const& identifier, Variant const& entry) -> Error {
+ return this->_parse_region_entry(identifier, entry);
+ });
+ map.lock_regions();
+ return err;
}
Error MapSingleton::load_province_shape_file(String const& file_path) {
- if (province_shape_image.is_valid()) {
+ if (province_index_image.is_valid()) {
UtilityFunctions::push_error("Province shape file has already been loaded, cannot load: ", file_path);
return FAILED;
}
+ Ref<Image> province_shape_image;
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();
+ int32_t width = province_shape_image->get_width();
+ int32_t 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();
+ 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;
- }
+ if (err != OK) 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;
- }
+ std::string error_message = "";
+ if (map.generate_province_index_image(width, height, province_shape_image->get_data().ptr(), error_message) != SUCCESS) {
+ UtilityFunctions::push_error(error_message.c_str());
+ err = FAILED;
}
- 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;
- }
- }
+ PackedByteArray index_data_array;
+ index_data_array.resize(width * height * sizeof(Province::index_t));
+ memcpy(index_data_array.ptrw(), map.get_province_index_image().data(), index_data_array.size());
province_index_image = Image::create_from_data(width, height, false, Image::FORMAT_RG8, index_data_array);
if (province_index_image.is_null()) {
@@ -206,45 +234,51 @@ Error MapSingleton::load_province_shape_file(String const& file_path) {
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;
- }
+ if (update_colour_image() != OK) err = FAILED;
return err;
}
-String MapSingleton::get_province_identifier_from_pixel_coords(Vector2i const& coords) {
+Province* MapSingleton::get_province_from_uv_coords(godot::Vector2 const& coords) {
+ if (province_index_image.is_valid()) {
+ const PackedByteArray index_data_array = province_index_image->get_data();
+ Province::index_t const* index_data = reinterpret_cast<Province::index_t const*>(index_data_array.ptr());
+ const int32_t x_mod_w = UtilityFunctions::fposmod(coords.x, 1.0f) * get_width();
+ const int32_t y_mod_h = UtilityFunctions::fposmod(coords.y, 1.0f) * get_height();
+ return map.get_province_by_index(index_data[x_mod_w + y_mod_h * get_width()]);
+ }
+ return nullptr;
+}
+
+Province const* MapSingleton::get_province_from_uv_coords(godot::Vector2 const& coords) const {
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();
+ Province::index_t const* index_data = reinterpret_cast<Province::index_t const*>(index_data_array.ptr());
+ const int32_t x_mod_w = UtilityFunctions::fposmod(coords.x, 1.0f) * get_width();
+ const int32_t y_mod_h = UtilityFunctions::fposmod(coords.y, 1.0f) * get_height();
+ return map.get_province_by_index(index_data[x_mod_w + y_mod_h * get_width()]);
}
+ return nullptr;
+}
+
+int32_t MapSingleton::get_province_index_from_uv_coords(Vector2 const& coords) const {
+ Province const* province = get_province_from_uv_coords(coords);
+ if (province != nullptr) return province->get_index();
+ return Province::NULL_INDEX;
+}
+
+String MapSingleton::get_province_identifier_from_uv_coords(Vector2 const& coords) const {
+ Province const* province = get_province_from_uv_coords(coords);
+ if (province != nullptr) return province->get_identifier().c_str();
return String{};
}
int32_t MapSingleton::get_width() const {
- return width;
+ return map.get_width();
}
int32_t MapSingleton::get_height() const {
- return height;
+ return map.get_height();
}
Ref<Image> MapSingleton::get_province_index_image() const {
@@ -254,3 +288,48 @@ Ref<Image> MapSingleton::get_province_index_image() const {
Ref<Image> MapSingleton::get_province_colour_image() const {
return province_colour_image;
}
+
+Error MapSingleton::update_colour_image() {
+ static PackedByteArray colour_data_array;
+ static const int64_t colour_data_array_size = (Province::MAX_INDEX + 1) * 3;
+ colour_data_array.resize(colour_data_array_size);
+
+ Error err = OK;
+ std::string error_message = "";
+ if (map.generate_mapmode_colours(mapmode_index, colour_data_array.ptrw(), error_message) != SUCCESS) {
+ UtilityFunctions::push_error(error_message.c_str());
+ err = FAILED;
+ }
+
+ static const int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * 4);
+ if (province_colour_image.is_null())
+ province_colour_image.instantiate();
+ province_colour_image->set_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 update province colour image");
+ return FAILED;
+ }
+ return err;
+}
+
+int32_t MapSingleton::get_mapmode_count() const {
+ return map.get_mapmode_count();
+}
+
+String MapSingleton::get_mapmode_identifier(int32_t index) const {
+ Mapmode const* mapmode = map.get_mapmode_by_index(index);
+ if (mapmode != nullptr) return mapmode->get_identifier().c_str();
+ return String{};
+}
+
+Error MapSingleton::set_mapmode(godot::String const& identifier) {
+ Mapmode const* mapmode = map.get_mapmode_by_identifier(identifier.utf8().get_data());
+ if (mapmode != nullptr) {
+ mapmode_index = mapmode->get_index();
+ return OK;
+ } else {
+ UtilityFunctions::push_error("Failed to set mapmode to: ", identifier);
+ return FAILED;
+ }
+}
diff --git a/extension/src/MapSingleton.hpp b/extension/src/MapSingleton.hpp
index 71761cd..9205e92 100644
--- a/extension/src/MapSingleton.hpp
+++ b/extension/src/MapSingleton.hpp
@@ -1,19 +1,27 @@
#pragma once
+#include <functional>
+
#include <godot_cpp/classes/image.hpp>
+
#include "openvic2/Map.hpp"
namespace OpenVic2 {
class MapSingleton : public godot::Object {
+ using parse_json_entry_func_t = std::function<godot::Error (godot::String const&, godot::Variant const&)>;
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;
+ godot::Ref<godot::Image> province_index_image, province_colour_image;
Map map;
+ Mapmode::index_t mapmode_index = 0;
+ godot::Error parse_json_dictionary_file(godot::String const& file_description, godot::String const& file_path,
+ godot::String const& identifier_prefix, parse_json_entry_func_t parse_entry) const;
+ godot::Error _parse_province_identifier_entry(godot::String const& identifier, godot::Variant const& entry);
+ godot::Error _parse_region_entry(godot::String const& identifier, godot::Variant const& entry);
protected:
static void _bind_methods();
@@ -24,11 +32,21 @@ namespace OpenVic2 {
~MapSingleton();
godot::Error load_province_identifier_file(godot::String const& file_path);
+ godot::Error load_region_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);
+
+ Province* get_province_from_uv_coords(godot::Vector2 const& coords);
+ Province const* get_province_from_uv_coords(godot::Vector2 const& coords) const;
+ int32_t get_province_index_from_uv_coords(godot::Vector2 const& coords) const;
+ godot::String get_province_identifier_from_uv_coords(godot::Vector2 const& coords) const;
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;
+
+ godot::Error update_colour_image();
+ int32_t get_mapmode_count() const;
+ godot::String get_mapmode_identifier(int32_t index) const;
+ godot::Error set_mapmode(godot::String const& identifier);
};
}
diff --git a/extension/src/Simulation.hpp b/extension/src/Simulation.hpp
index 8959310..b84016b 100644
--- a/extension/src/Simulation.hpp
+++ b/extension/src/Simulation.hpp
@@ -1,8 +1,6 @@
#pragma once
-#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/core/class_db.hpp>
-#include <godot_cpp/variant/utility_functions.hpp>
#include <vector>
namespace OpenVic2 {
@@ -11,7 +9,7 @@ namespace OpenVic2 {
std::vector<uint64_t> exampleProvinces;
//BEGIN BOILERPLATE
- static Simulation* _simulation;
+ inline static Simulation* _simulation = nullptr;
protected:
static void _bind_methods() {
@@ -47,6 +45,4 @@ namespace OpenVic2 {
return exampleProvinces[provinceID];
}
};
-
- Simulation* Simulation::_simulation = nullptr;
}
diff --git a/extension/src/TestSingleton.hpp b/extension/src/TestSingleton.hpp
index d140516..1261573 100644
--- a/extension/src/TestSingleton.hpp
+++ b/extension/src/TestSingleton.hpp
@@ -1,7 +1,6 @@
#pragma once
#include <godot_cpp/classes/object.hpp>
-#include <godot_cpp/core/class_db.hpp>
namespace OpenVic2 {
class TestSingleton : public godot::Object
diff --git a/extension/src/openvic2/Map.cpp b/extension/src/openvic2/Map.cpp
index d980b88..1654189 100644
--- a/extension/src/openvic2/Map.cpp
+++ b/extension/src/openvic2/Map.cpp
@@ -1,73 +1,126 @@
#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);
-}
+static const std::string SEPARATOR = "\n - ";
-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();
+Mapmode::Mapmode(index_t new_index, std::string const& new_identifier, colour_func_t new_colour_func)
+ : index(new_index), identifier(new_identifier), colour_func(new_colour_func) {
+ assert(!identifier.empty());
+ assert(colour_func != nullptr);
}
-Province::index_t Province::get_index() const {
+Mapmode::index_t Mapmode::get_index() const {
return index;
}
-std::string const& Province::get_identifier() const {
+std::string const& Mapmode::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) + ")";
+Mapmode::colour_func_t Mapmode::get_colour_func() const {
+ return colour_func;
}
-bool Map::add_province(std::string const& identifier, Province::colour_t colour, std::string& error_message) {
+return_t 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;
+ return FAILURE;
}
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;
+ return FAILURE;
+ }
+ if (identifier.empty()) {
+ error_message = "Empty province identifier for colour " + Province::colour_to_hex_string(colour);
+ return FAILURE;
}
if (colour == Province::NULL_COLOUR || colour > Province::MAX_COLOUR) {
error_message = "Invalid province colour: " + Province::colour_to_hex_string(colour);
- return false;
+ return FAILURE;
}
Province new_province{ static_cast<Province::index_t>(provinces.size() + 1), identifier, colour };
for (Province const& province : provinces) {
- if (province.identifier == identifier) {
+ if (province.get_identifier() == identifier) {
error_message = "Duplicate province identifiers: " + province.to_string() + " and " + new_province.to_string();
- return false;
+ return FAILURE;
}
- if (province.colour == colour) {
+ if (province.get_colour() == colour) {
error_message = "Duplicate province colours: " + province.to_string() + " and " + new_province.to_string();
- return false;
+ return FAILURE;
}
}
provinces.push_back(new_province);
error_message = "Added province: " + new_province.to_string();
- return true;
+ return SUCCESS;
}
void Map::lock_provinces() {
provinces_locked = true;
}
+return_t Map::add_region(std::string const& identifier, std::vector<std::string> const& province_identifiers, std::string& error_message) {
+ if (regions_locked) {
+ error_message = "The map's region list has already been locked!";
+ return FAILURE;
+ }
+ if (identifier.empty()) {
+ error_message = "Empty region identifier!";
+ return FAILURE;
+ }
+ if (provinces.empty()) {
+ error_message = "Empty province list for region " + identifier;
+ return FAILURE;
+ }
+ Region new_region{ identifier };
+ error_message = "Error message for region: " + identifier;
+ for (std::string const& province_identifier : province_identifiers) {
+ Province* province = get_province_by_identifier(province_identifier);
+ if (province != nullptr) {
+ if (new_region.contains_province(province))
+ error_message += SEPARATOR + "Duplicate province identifier " + province_identifier;
+ else {
+ size_t other_region_index = reinterpret_cast<size_t>(province->get_region());
+ if (other_region_index != 0) {
+ other_region_index--;
+ error_message += SEPARATOR + "Province " + province_identifier + " is already part of ";
+ if (other_region_index < regions.size())
+ error_message += regions[other_region_index].get_identifier();
+ else
+ error_message += "an unknown region with index " + std::to_string(other_region_index);
+ } else new_region.provinces.push_back(province);
+ }
+ } else error_message += SEPARATOR + "Invalid province identifier " + province_identifier;
+ }
+ if (!new_region.get_province_count()) {
+ error_message += SEPARATOR + "No valid provinces in region's list";
+ return FAILURE;
+ }
+ for (Region const& region : regions) {
+ if (region.get_identifier() == identifier) {
+ error_message += SEPARATOR + "Duplicate region identifiers: " + region.get_identifier() + " and " + identifier;
+ return FAILURE;
+ }
+ }
+ regions.push_back(new_region);
+ error_message += SEPARATOR + "Added region: " + identifier;
+ // Used to detect provinces listed in multiple regions, will
+ // be corrected once regions is stable (i.e. lock_regions).
+ Region* tmp_region_index = reinterpret_cast<Region*>(regions.size());
+ for (Province* province : new_region.get_provinces())
+ province->region = tmp_region_index;
+ return SUCCESS;
+}
+
+void Map::lock_regions() {
+ regions_locked = true;
+ for (Region& region : regions)
+ for (Province* province : region.get_provinces())
+ province->region = &region;
+}
+
size_t Map::get_province_count() const {
return provinces.size();
}
@@ -76,14 +129,178 @@ Province* Map::get_province_by_index(Province::index_t index) {
return index != Province::NULL_INDEX && index <= provinces.size() ? &provinces[index - 1] : nullptr;
}
+Province const* Map::get_province_by_index(Province::index_t index) const {
+ 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;
+ if (!identifier.empty())
+ for (Province& province : provinces)
+ if (province.get_identifier() == identifier) return &province;
+ return nullptr;
+}
+
+Province const* Map::get_province_by_identifier(std::string const& identifier) const {
+ if (!identifier.empty())
+ for (Province const& province : provinces)
+ if (province.get_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;
+ if (colour != Province::NULL_COLOUR)
+ for (Province& province : provinces)
+ if (province.get_colour() == colour) return &province;
+ return nullptr;
+}
+
+Province const* Map::get_province_by_colour(Province::colour_t colour) const {
+ if (colour != Province::NULL_COLOUR)
+ for (Province const& province : provinces)
+ if (province.get_colour() == colour) return &province;
+ return nullptr;
+}
+
+static Province::colour_t colour_at(uint8_t const* colour_data, int32_t idx) {
+ return (colour_data[idx * 3] << 16) | (colour_data[idx * 3 + 1] << 8) | colour_data[idx * 3 + 2];
+}
+
+return_t Map::generate_province_index_image(size_t new_width, size_t new_height, uint8_t const* colour_data, std::string& error_message) {
+ if (!province_index_image.empty()) {
+ error_message = "Province index image has already been generated!";
+ return FAILURE;
+ }
+ if (!provinces_locked) {
+ error_message = "Province index image cannot be generated until after provinces are locked!";
+ return FAILURE;
+ }
+ if (new_width < 1 || new_height < 1) {
+ error_message = "Invalid province image dimensions: " + std::to_string(new_width) + "x" + std::to_string(new_height);
+ return FAILURE;
+ }
+ if (colour_data == nullptr) {
+ error_message = "Province colour data pointer is null!";
+ return FAILURE;
+ }
+ width = new_width;
+ height = new_height;
+ province_index_image.resize(width * height);
+
+ std::vector<bool> province_checklist(provinces.size());
+ return_t ret = SUCCESS;
+
+ error_message = "Error message for province index image generation:";
+
+ 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(colour_data, idx);
+ if (colour == Province::NULL_COLOUR) {
+ province_index_image[idx] = Province::NULL_INDEX;
+ continue;
+ }
+ if (x > 0) {
+ const int32_t jdx = idx - 1;
+ if (colour_at(colour_data, jdx) == colour) {
+ province_index_image[idx] = province_index_image[jdx];
+ continue;
+ }
+ }
+ if (y > 0) {
+ const int32_t jdx = idx - width;
+ if (colour_at(colour_data, jdx) == colour) {
+ province_index_image[idx] = province_index_image[jdx];
+ continue;
+ }
+ }
+ Province const* province = get_province_by_colour(colour);
+ if (province != nullptr) {
+ const Province::index_t index = province->get_index();
+ province_index_image[idx] = index;
+ province_checklist[index - 1] = true;
+ continue;
+ }
+ error_message += SEPARATOR + "Unrecognised province colour " + Province::colour_to_hex_string(colour) + " at (" + std::to_string(x) + ", " + std::to_string(x) + ")";
+ ret = FAILURE;
+ province_index_image[idx] = Province::NULL_INDEX;
+ }
+ }
+
+ for (size_t idx = 0; idx < province_checklist.size(); ++idx) {
+ if (!province_checklist[idx]) {
+ error_message += SEPARATOR + "Province missing from shape image: " + provinces[idx].to_string();
+ ret = FAILURE;
+ }
+ }
+
+ error_message += SEPARATOR + "Generated province index image";
+ return ret;
+}
+
+size_t Map::get_width() const {
+ return width;
+}
+
+size_t Map::get_height() const {
+ return height;
+}
+
+std::vector<Province::index_t> const& Map::get_province_index_image() const {
+ return province_index_image;
+}
+
+return_t Map::add_mapmode(std::string const& identifier, Mapmode::colour_func_t colour_func, std::string& error_message) {
+ if (identifier.empty()) {
+ error_message = "Empty mapmode identifier!";
+ return FAILURE;
+ }
+ if (colour_func == nullptr) {
+ error_message = "Mapmode colour function is null for identifier: " + identifier;
+ return FAILURE;
+ }
+ Mapmode new_mapmode{ mapmodes.size(), identifier, colour_func };
+ for (Mapmode const& mapmode : mapmodes) {
+ if (mapmode.get_identifier() == identifier) {
+ error_message = "Duplicate mapmode identifiers: " + mapmode.get_identifier() + " and " + identifier;
+ return FAILURE;
+ }
+ }
+ mapmodes.push_back(new_mapmode);
+ error_message = "Added mapmode: " + identifier;
+ return SUCCESS;
+}
+
+size_t Map::get_mapmode_count() const {
+ return mapmodes.size();
+}
+
+Mapmode const* Map::get_mapmode_by_index(size_t index) const {
+ return index < mapmodes.size() ? &mapmodes[index] : nullptr;
+}
+
+Mapmode const* Map::get_mapmode_by_identifier(std::string const& identifier) const {
+ if (!identifier.empty())
+ for (Mapmode const& mapmode : mapmodes)
+ if (mapmode.get_identifier() == identifier) return &mapmode;
return nullptr;
}
+
+return_t Map::generate_mapmode_colours(Mapmode::index_t index, uint8_t* target, std::string& error_message) const {
+ if (target == nullptr) {
+ error_message = "Mapmode colour target pointer is null!";
+ return FAILURE;
+ }
+ if (index >= mapmodes.size()) {
+ error_message = "Invalid mapmode index: " + std::to_string(index);
+ return FAILURE;
+ }
+ Mapmode const& mapmode = mapmodes[index];
+ target += 3; // Skip past Province::NULL_INDEX
+ for (Province const& province : provinces) {
+ const Province::colour_t colour = mapmode.get_colour_func()(*this, province);
+ *target++ = (colour >> 16) & 0xFF;
+ *target++ = (colour >> 8) & 0xFF;
+ *target++ = colour & 0xFF;
+ }
+ return SUCCESS;
+}
diff --git a/extension/src/openvic2/Map.hpp b/extension/src/openvic2/Map.hpp
index 365d78b..42963c9 100644
--- a/extension/src/openvic2/Map.hpp
+++ b/extension/src/openvic2/Map.hpp
@@ -3,43 +3,111 @@
#include <string>
#include <cstdint>
#include <vector>
+#include <functional>
+
+#include "Types.hpp"
namespace OpenVic2 {
+ struct Region;
+ struct Map;
+ /* REQUIREMENTS:
+ * MAP-43, MAP-47
+ */
struct Province {
+ friend struct Map;
+
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;
+ Region* region = nullptr;
- Province(index_t index, std::string const& identifier, colour_t colour);
+ Province(index_t new_index, std::string const& new_identifier, colour_t new_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;
+ Region* get_region() const;
std::string to_string() const;
};
+ /* REQUIREMENTS:
+ * MAP-6, MAP-44, MAP-48
+ */
+ struct Region {
+ friend struct Map;
+ private:
+ std::string identifier;
+ std::vector<Province*> provinces;
+
+ Region(std::string const& new_identifier);
+ public:
+ std::string const& get_identifier() const;
+ size_t get_province_count() const;
+ bool contains_province(Province const* province) const;
+ std::vector<Province*> const& get_provinces() const;
+ };
+
+ struct Mapmode {
+ friend struct Map;
+
+ using colour_func_t = std::function<Province::colour_t (Map const&, Province const&)>;
+ using index_t = size_t;
+ private:
+ index_t index;
+ std::string identifier;
+ colour_func_t colour_func;
+
+ Mapmode(index_t new_index, std::string const& new_identifier, colour_func_t new_colour_func);
+ public:
+ index_t get_index() const;
+ std::string const& get_identifier() const;
+ colour_func_t get_colour_func() const;
+ };
+
+ /* REQUIREMENTS:
+ * MAP-4
+ */
struct Map {
private:
std::vector<Province> provinces;
- bool provinces_locked = false;
+ std::vector<Region> regions;
+ bool provinces_locked = false, regions_locked = false;
+ size_t width = 0, height = 0;
+ std::vector<Province::index_t> province_index_image;
+ std::vector<Mapmode> mapmodes;
public:
- bool add_province(std::string const& identifier, Province::colour_t colour, std::string& error_message);
+ return_t add_province(std::string const& identifier, Province::colour_t colour, std::string& error_message);
void lock_provinces();
+ return_t add_region(std::string const& identifier, std::vector<std::string> const& province_identifiers, std::string& error_message);
+ void lock_regions();
size_t get_province_count() const;
Province* get_province_by_index(Province::index_t index);
+ Province const* get_province_by_index(Province::index_t index) const;
Province* get_province_by_identifier(std::string const& identifier);
+ Province const* get_province_by_identifier(std::string const& identifier) const;
Province* get_province_by_colour(Province::colour_t colour);
- };
+ Province const* get_province_by_colour(Province::colour_t colour) const;
+
+ return_t generate_province_index_image(size_t new_width, size_t new_height, uint8_t const* colour_data, std::string& error_message);
+ size_t get_width() const;
+ size_t get_height() const;
+ std::vector<Province::index_t> const& get_province_index_image() const;
+ return_t add_mapmode(std::string const& identifier, Mapmode::colour_func_t colour_func, std::string& error_message);
+ size_t get_mapmode_count() const;
+ Mapmode const* get_mapmode_by_index(Mapmode::index_t index) const;
+ Mapmode const* get_mapmode_by_identifier(std::string const& identifier) const;
+ return_t generate_mapmode_colours(Mapmode::index_t index, uint8_t* target, std::string& error_message) const;
+ };
}
diff --git a/extension/src/openvic2/Province.cpp b/extension/src/openvic2/Province.cpp
new file mode 100644
index 0000000..49f1b0e
--- /dev/null
+++ b/extension/src/openvic2/Province.cpp
@@ -0,0 +1,40 @@
+#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;
+}
+
+Region* Province::get_region() const {
+ return region;
+}
+
+std::string Province::to_string() const {
+ return "(#" + std::to_string(index) + ", " + identifier + ", 0x" + colour_to_hex_string(colour) + ")";
+}
diff --git a/extension/src/openvic2/Region.cpp b/extension/src/openvic2/Region.cpp
new file mode 100644
index 0000000..6ee05f5
--- /dev/null
+++ b/extension/src/openvic2/Region.cpp
@@ -0,0 +1,26 @@
+#include "Map.hpp"
+
+#include <cassert>
+#include <algorithm>
+
+using namespace OpenVic2;
+
+Region::Region(std::string const& new_identifier) : identifier(new_identifier) {
+ assert(!identifier.empty());
+}
+
+std::string const& Region::get_identifier() const {
+ return identifier;
+}
+
+size_t Region::get_province_count() const {
+ return provinces.size();
+}
+
+bool Region::contains_province(Province const* province) const {
+ return province && std::find(provinces.begin(), provinces.end(), province) != provinces.end();
+}
+
+std::vector<Province*> const& Region::get_provinces() const {
+ return provinces;
+}
diff --git a/extension/src/openvic2/Types.hpp b/extension/src/openvic2/Types.hpp
new file mode 100644
index 0000000..0fb1c8b
--- /dev/null
+++ b/extension/src/openvic2/Types.hpp
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace OpenVic2 {
+ using return_t = bool;
+ // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`.
+ static const return_t SUCCESS = false, FAILURE = true;
+}
diff --git a/extension/src/register_types.cpp b/extension/src/register_types.cpp
index 16e59b2..9fd934e 100644
--- a/extension/src/register_types.cpp
+++ b/extension/src/register_types.cpp
@@ -1,8 +1,5 @@
#include "register_types.h"
-#include <gdextension_interface.h>
-#include <godot_cpp/core/class_db.hpp>
-#include <godot_cpp/core/defs.hpp>
-#include <godot_cpp/godot.hpp>
+
#include <godot_cpp/classes/engine.hpp>
#include "TestSingleton.hpp"
@@ -74,7 +71,7 @@ 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(GDExtensionInterface const* 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);
diff --git a/extension/src/register_types.h b/extension/src/register_types.h
index 860359f..1bc96f8 100644
--- a/extension/src/register_types.h
+++ b/extension/src/register_types.h
@@ -1,6 +1,6 @@
#pragma once
-#include <godot_cpp/core/class_db.hpp>
+#include <godot_cpp/godot.hpp>
void initialize_openvic2_types(godot::ModuleInitializationLevel);
void uninitialize_openvic2_types(godot::ModuleInitializationLevel); \ No newline at end of file
diff --git a/game/common/map/terrain/terrain.png b/game/art/terrain/terrain.png
index 6d36dee..6d36dee 100644
--- a/game/common/map/terrain/terrain.png
+++ b/game/art/terrain/terrain.png
Binary files differ
diff --git a/game/common/map/terrain/terrain.png.import b/game/art/terrain/terrain.png.import
index 56ba5dc..d7a078f 100644
--- a/game/common/map/terrain/terrain.png.import
+++ b/game/art/terrain/terrain.png.import
@@ -3,15 +3,15 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://cmw0pvjthnn8c"
-path="res://.godot/imported/terrain.png-c443b8a709ca7acc4b3bb3df1d3095a9.ctex"
+path="res://.godot/imported/terrain.png-06c63c2b87b3131a2067f668f87a9d67.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://common/map/terrain/terrain.png"
-dest_files=["res://.godot/imported/terrain.png-c443b8a709ca7acc4b3bb3df1d3095a9.ctex"]
+source_file="res://art/terrain/terrain.png"
+dest_files=["res://.godot/imported/terrain.png-06c63c2b87b3131a2067f668f87a9d67.ctex"]
[params]
@@ -25,7 +25,7 @@ mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
-process/fix_alpha_border=true
+process/fix_alpha_border=false
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
diff --git a/game/art/ui/minimap.png b/game/art/ui/minimap.png
new file mode 100644
index 0000000..9922b28
--- /dev/null
+++ b/game/art/ui/minimap.png
Binary files differ
diff --git a/game/art/ui/minimap.png.import b/game/art/ui/minimap.png.import
new file mode 100644
index 0000000..36f22e7
--- /dev/null
+++ b/game/art/ui/minimap.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c0sm1jfu4kyv3"
+path="res://.godot/imported/minimap.png-05ee44469856e77fd05107fe9e259e25.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://art/ui/minimap.png"
+dest_files=["res://.godot/imported/minimap.png-05ee44469856e77fd05107fe9e259e25.ctex"]
+
+[params]
+
+compress/mode=3
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=false
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/game/art/ui/minimap_frame.png b/game/art/ui/minimap_frame.png
new file mode 100644
index 0000000..21aa2ce
--- /dev/null
+++ b/game/art/ui/minimap_frame.png
Binary files differ
diff --git a/game/art/ui/minimap_frame.png.import b/game/art/ui/minimap_frame.png.import
new file mode 100644
index 0000000..3d2aeee
--- /dev/null
+++ b/game/art/ui/minimap_frame.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vr1hq2stk8ny"
+path="res://.godot/imported/minimap_frame.png-3668393435d2582fb1dc8a9598573e80.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://art/ui/minimap_frame.png"
+dest_files=["res://.godot/imported/minimap_frame.png-3668393435d2582fb1dc8a9598573e80.ctex"]
+
+[params]
+
+compress/mode=3
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=2
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=false
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/game/common/map/regions.json b/game/common/map/regions.json
new file mode 100644
index 0000000..bbeeb56
--- /dev/null
+++ b/game/common/map/regions.json
@@ -0,0 +1,6 @@
+{
+ "region_europe": ["prov_britain", "prov_ireland", "prov_iceland"],
+ "region_america": ["prov_cuba"],
+ "region_africa": ["prov_madagascar"],
+ "region_asia": ["prov_ceylon", "prov_formosa"]
+}
diff --git a/game/localisation/en_GB/mapmodes.csv b/game/localisation/en_GB/mapmodes.csv
new file mode 100644
index 0000000..c5b1bb0
--- /dev/null
+++ b/game/localisation/en_GB/mapmodes.csv
@@ -0,0 +1,5 @@
+
+,, Test Mapmodes
+mapmode_province,Province Mapmode
+mapmode_region,Region Mapmode
+mapmode_index,Index Mapmode
diff --git a/game/localisation/en_GB/mapmodes.csv.import b/game/localisation/en_GB/mapmodes.csv.import
new file mode 100644
index 0000000..8dd0c09
--- /dev/null
+++ b/game/localisation/en_GB/mapmodes.csv.import
@@ -0,0 +1,3 @@
+[remap]
+
+importer="keep"
diff --git a/game/src/Autoload/Events.gd b/game/src/Autoload/Events.gd
index 040cb06..f4aac70 100644
--- a/game/src/Autoload/Events.gd
+++ b/game/src/Autoload/Events.gd
@@ -4,10 +4,15 @@ var Options = preload("Events/Options.gd").new()
var Localisation = preload("Events/Localisation.gd").new()
const _province_identifier_file : String = "res://common/map/provinces.json"
+const _region_file : String = "res://common/map/regions.json"
const _province_shape_file : String = "res://common/map/provinces.png"
+# REQUIREMENTS
+# * FS-333, FS-335, FS-341
func _ready():
if MapSingleton.load_province_identifier_file(_province_identifier_file) != OK:
push_error("Failed to load province identifiers")
+ if MapSingleton.load_region_file(_region_file) != OK:
+ push_error("Failed to load regions")
if MapSingleton.load_province_shape_file(_province_shape_file) != OK:
push_error("Failed to load province shapes")
diff --git a/game/src/GameSession/GameSession.tscn b/game/src/GameSession/GameSession.tscn
index 390040e..e270f8a 100644
--- a/game/src/GameSession/GameSession.tscn
+++ b/game/src/GameSession/GameSession.tscn
@@ -7,7 +7,7 @@
[ext_resource type="PackedScene" uid="uid://byq323jbel48u" path="res://src/GameSession/ProvinceOverviewPanel.tscn" id="5_osjnn"]
[node name="GameSession" type="Node" node_paths=PackedStringArray("_game_session_menu")]
-editor_description = "SS-102"
+editor_description = "SS-102, UI-546"
script = ExtResource("1_eklvp")
_game_session_menu = NodePath("GameSessionMenu")
@@ -29,6 +29,8 @@ anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
+offset_left = -350.0
+offset_top = -210.0
grow_horizontal = 0
grow_vertical = 0
@@ -39,6 +41,11 @@ anchor_right = 0.15625
offset_top = 0.0
offset_right = 0.0
+[connection signal="map_view_camera_changed" from="MapView" to="MapControlPanel" method="_on_map_view_camera_changed"]
[connection signal="province_selected" from="MapView" to="ProvinceOverviewPanel" method="_on_province_selected"]
[connection signal="close_button_pressed" from="GameSessionMenu" to="." method="_on_game_session_menu_close_button_pressed"]
[connection signal="game_session_menu_button_pressed" from="MapControlPanel" to="." method="_on_game_session_menu_button_pressed"]
+[connection signal="mapmode_changed" from="MapControlPanel" to="MapView" method="_update_colour_texture"]
+[connection signal="minimap_clicked" from="MapControlPanel" to="MapView" method="_on_minimap_clicked"]
+[connection signal="mouse_entered" from="MapControlPanel" to="MapView" method="_on_mouse_exited_viewport"]
+[connection signal="mouse_exited" from="MapControlPanel" to="MapView" method="_on_mouse_entered_viewport"]
diff --git a/game/src/GameSession/MapControlPanel.gd b/game/src/GameSession/MapControlPanel.gd
index ad56536..693890f 100644
--- a/game/src/GameSession/MapControlPanel.gd
+++ b/game/src/GameSession/MapControlPanel.gd
@@ -1,8 +1,46 @@
extends PanelContainer
signal game_session_menu_button_pressed
+signal mapmode_changed
+signal map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2)
+signal minimap_clicked(pos_clicked : Vector2)
+
+@export var _mapmodes_grid : GridContainer
+
+var _mapmode_button_group : ButtonGroup
+
+# REQUIREMENTS:
+# * UI-550, UI-554
+func _add_mapmode_button(identifier : String) -> void:
+ var button := Button.new()
+ button.text = identifier
+ button.tooltip_text = identifier
+ button.toggle_mode = true
+ button.button_group = _mapmode_button_group
+ _mapmodes_grid.add_child(button)
+ if _mapmode_button_group.get_pressed_button() == null:
+ button.button_pressed = true
+
+func _ready():
+ _mapmode_button_group = ButtonGroup.new()
+ _mapmode_button_group.pressed.connect(_mapmode_pressed)
+ for index in MapSingleton.get_mapmode_count():
+ _add_mapmode_button(MapSingleton.get_mapmode_identifier(index))
# REQUIREMENTS:
# * UIFUN-10
-func _on_game_session_menu_button_pressed():
+func _on_game_session_menu_button_pressed() -> void:
game_session_menu_button_pressed.emit()
+
+# REQUIREMENTS:
+# * SS-76
+# * UIFUN-129, UIFUN-133
+func _mapmode_pressed(button : BaseButton) -> void:
+ MapSingleton.set_mapmode(button.tooltip_text)
+ mapmode_changed.emit()
+
+func _on_map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2) -> void:
+ map_view_camera_changed.emit(near_left, far_left, far_right, near_right)
+
+func _on_minimap_clicked(pos_clicked : Vector2) -> void:
+ minimap_clicked.emit(pos_clicked)
diff --git a/game/src/GameSession/MapControlPanel.tscn b/game/src/GameSession/MapControlPanel.tscn
index 2a0c971..70d7eab 100644
--- a/game/src/GameSession/MapControlPanel.tscn
+++ b/game/src/GameSession/MapControlPanel.tscn
@@ -1,6 +1,9 @@
-[gd_scene load_steps=4 format=3 uid="uid://g524p8lr574w"]
+[gd_scene load_steps=7 format=3 uid="uid://g524p8lr574w"]
[ext_resource type="Script" path="res://src/GameSession/MapControlPanel.gd" id="1_ign64"]
+[ext_resource type="Texture2D" uid="uid://c0sm1jfu4kyv3" path="res://art/ui/minimap.png" id="2_r613r"]
+[ext_resource type="Script" path="res://src/GameSession/Minimap.gd" id="3_s4dml"]
+[ext_resource type="Texture2D" uid="uid://vr1hq2stk8ny" path="res://art/ui/minimap_frame.png" id="4_f1exl"]
[sub_resource type="InputEventAction" id="InputEventAction_5nck3"]
action = &"ui_cancel"
@@ -8,23 +11,42 @@ action = &"ui_cancel"
[sub_resource type="Shortcut" id="Shortcut_fc1tk"]
events = [SubResource("InputEventAction_5nck3")]
-[node name="PanelContainer" type="PanelContainer"]
+[node name="MapControlPanel" type="PanelContainer" node_paths=PackedStringArray("_mapmodes_grid")]
editor_description = "SS-103"
+mouse_filter = 1
script = ExtResource("1_ign64")
+_mapmodes_grid = NodePath("HBoxContainer/VBoxContainer/MapmodesGrid")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
+alignment = 2
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
layout_mode = 2
-[node name="MapmodesPlaceholder" type="Label" parent="HBoxContainer/VBoxContainer"]
+[node name="MapmodesGrid" type="GridContainer" parent="HBoxContainer/VBoxContainer"]
+editor_description = "UI-750"
layout_mode = 2
-text = "MAPMODES"
+columns = 11
-[node name="MinimapPlaceholder" type="Label" parent="HBoxContainer/VBoxContainer"]
+[node name="Minimap" type="PanelContainer" parent="HBoxContainer/VBoxContainer"]
+editor_description = "UI-549"
layout_mode = 2
-text = "MINIMAP"
+mouse_filter = 1
+
+[node name="TextureRect" type="TextureRect" parent="HBoxContainer/VBoxContainer/Minimap"]
+editor_description = "UI-751, FS-338"
+layout_mode = 2
+texture = ExtResource("2_r613r")
+
+[node name="ViewportQuad" type="Control" parent="HBoxContainer/VBoxContainer/Minimap"]
+layout_mode = 2
+mouse_filter = 2
+script = ExtResource("3_s4dml")
+
+[node name="Frame" type="NinePatchRect" parent="HBoxContainer/VBoxContainer/Minimap"]
+layout_mode = 2
+texture = ExtResource("4_f1exl")
[node name="AuxiliaryPanel" type="VBoxContainer" parent="HBoxContainer"]
editor_description = "UI-761"
@@ -36,4 +58,6 @@ layout_mode = 2
shortcut = SubResource("Shortcut_fc1tk")
text = "ESC"
+[connection signal="map_view_camera_changed" from="." to="HBoxContainer/VBoxContainer/Minimap/ViewportQuad" method="_on_map_view_camera_changed"]
+[connection signal="minimap_clicked" from="HBoxContainer/VBoxContainer/Minimap/ViewportQuad" to="." method="_on_minimap_clicked"]
[connection signal="pressed" from="HBoxContainer/AuxiliaryPanel/GameSessionMenuButton" to="." method="_on_game_session_menu_button_pressed"]
diff --git a/game/src/GameSession/MapView.gd b/game/src/GameSession/MapView.gd
index faf90e8..54d8df8 100644
--- a/game/src/GameSession/MapView.gd
+++ b/game/src/GameSession/MapView.gd
@@ -1,6 +1,7 @@
extends Node3D
signal province_selected(identifier : String)
+signal map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2)
const _action_north : StringName = &"map_north"
const _action_east : StringName = &"map_east"
@@ -13,17 +14,19 @@ const _action_click : StringName = &"map_click"
const _shader_param_province_index : StringName = &"province_index_tex"
const _shader_param_province_colour : StringName = &"province_colour_tex"
-const _shader_param_hover_pos : StringName = &"hover_pos"
-const _shader_param_selected_pos : StringName = &"selected_pos"
+const _shader_param_hover_index : StringName = &"hover_index"
+const _shader_param_selected_index : StringName = &"selected_index"
@export var _camera : Camera3D
@export var _cardinal_move_speed : float = 1.0
-@export var _edge_move_threshold: float = 0.15
+@export var _edge_move_threshold: float = 0.02
@export var _edge_move_speed: float = 2.5
var _drag_anchor : Vector2
var _drag_active : bool = false
+var _mouse_over_viewport : bool = false
+
@export var _zoom_target_min : float = 0.2
@export var _zoom_target_max : float = 5.0
@export var _zoom_target_step : float = 0.1
@@ -38,38 +41,33 @@ var _map_mesh : MapMesh
var _map_shader_material : ShaderMaterial
var _map_image_size : Vector2
var _map_province_index_image : Image
+var _map_province_colour_image : Image
+var _map_province_colour_texture : ImageTexture
var _map_mesh_corner : Vector2
var _map_mesh_dims : Vector2
var _mouse_pos_viewport : Vector2 = Vector2(0.5, 0.5)
var _mouse_pos_map : Vector2 = Vector2(0.5, 0.5)
+var _viewport_dims : Vector2 = Vector2(1, 1)
+# ??? Strange Godot/GDExtension Bug ???
+# Upon first opening a clone of this repo with the Godot Editor,
+# if MapSingleton.get_province_index_image is called before MapMesh
+# is referenced in the script below, then the editor will crash due
+# to a failed HashMap lookup. I'm not sure if this is a bug in the
+# editor, GDExtension, my own extension, or a combination of them.
+# This was an absolute pain to track down. --- hop311
func _ready():
if _camera == null:
push_error("MapView's _camera variable hasn't been set!")
return
+ _zoom_target = _camera.position.y
if _map_mesh_instance == null:
push_error("MapView's _map_mesh variable hasn't been set!")
return
- if not _map_mesh_instance.mesh is MapMesh:
- push_error("Invalid map mesh class: ", _map_mesh_instance.mesh.get_class(), "(expected MapMesh)")
- return
- _map_mesh = _map_mesh_instance.mesh
-
- # Set map mesh size and get bounds
- _map_image_size = Vector2(Vector2i(MapSingleton.get_width(), MapSingleton.get_height()))
- _map_mesh.aspect_ratio = _map_image_size.x / _map_image_size.y
- var map_mesh_aabb := _map_mesh.get_core_aabb() * _map_mesh_instance.transform
- _map_mesh_corner = Vector2(
- min(map_mesh_aabb.position.x, map_mesh_aabb.end.x),
- min(map_mesh_aabb.position.z, map_mesh_aabb.end.z)
- )
- _map_mesh_dims = abs(Vector2(
- map_mesh_aabb.position.x - map_mesh_aabb.end.x,
- map_mesh_aabb.position.z - map_mesh_aabb.end.z
- ))
- var map_material = _map_mesh_instance.get_active_material(0)
+ # Shader Material
+ var map_material := _map_mesh_instance.get_active_material(0)
if map_material == null:
push_error("Map mesh is missing material!")
return
@@ -77,6 +75,7 @@ func _ready():
push_error("Invalid map mesh material class: ", map_material.get_class())
return
_map_shader_material = map_material
+
# Province index texture
_map_province_index_image = MapSingleton.get_province_index_image()
if _map_province_index_image == null:
@@ -84,22 +83,72 @@ func _ready():
return
var province_index_texture := ImageTexture.create_from_image(_map_province_index_image)
_map_shader_material.set_shader_parameter(_shader_param_province_index, province_index_texture)
+
# Province colour texture
- var province_colour_image = MapSingleton.get_province_colour_image()
- if province_colour_image == null:
+ _map_province_colour_image = MapSingleton.get_province_colour_image()
+ if _map_province_colour_image == null:
push_error("Failed to get province colour image!")
return
- var province_colour_texture := ImageTexture.create_from_image(province_colour_image)
- _map_shader_material.set_shader_parameter(_shader_param_province_colour, province_colour_texture)
+ _map_province_colour_texture = ImageTexture.create_from_image(_map_province_colour_image)
+ _map_shader_material.set_shader_parameter(_shader_param_province_colour, _map_province_colour_texture)
+
+ if not _map_mesh_instance.mesh is MapMesh:
+ push_error("Invalid map mesh class: ", _map_mesh_instance.mesh.get_class(), "(expected MapMesh)")
+ return
+ _map_mesh = _map_mesh_instance.mesh
+
+ # Set map mesh size and get bounds
+ _map_image_size = Vector2(Vector2i(MapSingleton.get_width(), MapSingleton.get_height()))
+ _map_mesh.aspect_ratio = _map_image_size.x / _map_image_size.y
+ var map_mesh_aabb := _map_mesh.get_core_aabb() * _map_mesh_instance.transform
+ _map_mesh_corner = Vector2(
+ min(map_mesh_aabb.position.x, map_mesh_aabb.end.x),
+ min(map_mesh_aabb.position.z, map_mesh_aabb.end.z)
+ )
+ _map_mesh_dims = abs(Vector2(
+ map_mesh_aabb.position.x - map_mesh_aabb.end.x,
+ map_mesh_aabb.position.z - map_mesh_aabb.end.z
+ ))
+
+func _notification(what : int):
+ match what:
+ NOTIFICATION_WM_MOUSE_ENTER: # Mouse inside window
+ _on_mouse_entered_viewport()
+ NOTIFICATION_WM_MOUSE_EXIT: # Mouse out of window
+ _on_mouse_exited_viewport()
+
+func _update_colour_texture() -> void:
+ MapSingleton.update_colour_image()
+ _map_province_colour_texture.update(_map_province_colour_image)
+
+func _world_to_map_coords(pos : Vector3) -> Vector2:
+ return (Vector2(pos.x, pos.z) - _map_mesh_corner) / _map_mesh_dims
+
+func _viewport_to_map_coords(pos_viewport : Vector2) -> Vector2:
+ var ray_origin := _camera.project_ray_origin(pos_viewport)
+ var ray_normal := _camera.project_ray_normal(pos_viewport)
+ # Plane with normal (0,1,0) facing upwards, at a distance 0 from the origin
+ var intersection = Plane(0, 1, 0, 0).intersects_ray(ray_origin, ray_normal)
+ if typeof(intersection) == TYPE_VECTOR3:
+ return _world_to_map_coords(intersection as Vector3)
+ else:
+ # Normals parallel to the xz-plane could cause null intersections,
+ # but the camera's orientation should prevent such normals
+ push_error("Invalid intersection: ", intersection)
+ return Vector2(0.5, 0.5)
+# REQUIREMENTS
+# * SS-31
func _unhandled_input(event : InputEvent):
if event.is_action_pressed(_action_click):
# Check if the mouse is outside of bounds
if _map_mesh.is_valid_uv_coord(_mouse_pos_map):
- _map_shader_material.set_shader_parameter(_shader_param_selected_pos, _mouse_pos_map)
- var mouse_pixel_pos := Vector2i(_mouse_pos_map * _map_image_size)
- var province_identifier := MapSingleton.get_province_identifier_from_pixel_coords(mouse_pixel_pos)
+ var selected_index := MapSingleton.get_province_index_from_uv_coords(_mouse_pos_map)
+ _map_shader_material.set_shader_parameter(_shader_param_selected_index, selected_index)
+ var province_identifier := MapSingleton.get_province_identifier_from_uv_coords(_mouse_pos_map)
province_selected.emit(province_identifier)
+ else:
+ print("Clicked outside the map!")
elif event.is_action_pressed(_action_drag):
if _drag_active:
push_warning("Drag being activated while already active!")
@@ -116,6 +165,7 @@ func _unhandled_input(event : InputEvent):
func _physics_process(delta : float):
_mouse_pos_viewport = get_viewport().get_mouse_position()
+ _viewport_dims = Vector2(Resolution.get_current_resolution())
# Process movement
_movement_process(delta)
# Keep within map bounds
@@ -124,9 +174,13 @@ func _physics_process(delta : float):
_zoom_process(delta)
# Orient based on height
_update_orientation()
+ # Update viewport on minimap
+ _update_minimap_viewport()
# Calculate where the mouse lies on the map
_update_mouse_map_position()
+# REQUIREMENTS
+# * UIFUN-124
func _movement_process(delta : float) -> void:
var direction : Vector2
if _drag_active:
@@ -137,13 +191,18 @@ func _movement_process(delta : float) -> void:
direction *= _camera.position.y * delta
_camera.position += Vector3(direction.x, 0, direction.y)
+# REQUIREMENTS
+# * UIFUN-125
func _edge_scrolling_vector() -> Vector2:
- var viewport_dims := Vector2(Resolution.get_current_resolution())
- var mouse_vector := _mouse_pos_viewport / viewport_dims - Vector2(0.5, 0.5);
- if pow(mouse_vector.x, 4) + pow(mouse_vector.y, 4) < pow(0.5 - _edge_move_threshold, 4):
+ if not _mouse_over_viewport:
+ return Vector2()
+ var mouse_vector := _mouse_pos_viewport / _viewport_dims - Vector2(0.5, 0.5)
+ if abs(mouse_vector.x) < 0.5 - _edge_move_threshold and abs(mouse_vector.y) < 0.5 - _edge_move_threshold:
mouse_vector *= 0
return mouse_vector * _edge_move_speed
+# REQUIREMENTS
+# * SS-75
func _cardinal_movement_vector() -> Vector2:
var move := Vector2(
float(Input.is_action_pressed(_action_east)) - float(Input.is_action_pressed(_action_west)),
@@ -155,6 +214,9 @@ func _clamp_over_map() -> void:
_camera.position.x = _map_mesh_corner.x + fposmod(_camera.position.x - _map_mesh_corner.x, _map_mesh_dims.x)
_camera.position.z = clamp(_camera.position.z, _map_mesh_corner.y, _map_mesh_corner.y + _map_mesh_dims.y)
+# REQUIREMENTS
+# * SS-74
+# * UIFUN-123
func _zoom_process(delta : float) -> void:
var height := _camera.position.y
var zoom := _zoom_target - height
@@ -169,17 +231,27 @@ func _update_orientation() -> void:
var dir := Vector3(0, -1, -exp(-_camera.position.y * 2.0 + 0.5))
_camera.look_at(_camera.position + dir)
+func _update_minimap_viewport() -> void:
+ var near_left := _viewport_to_map_coords(Vector2(0, _viewport_dims.y))
+ var far_left := _viewport_to_map_coords(Vector2(0, 0))
+ var far_right := _viewport_to_map_coords(Vector2(_viewport_dims.x, 0))
+ var near_right := _viewport_to_map_coords(_viewport_dims)
+ map_view_camera_changed.emit(near_left, far_left, far_right, near_right)
+
func _update_mouse_map_position() -> void:
- var ray_origin := _camera.project_ray_origin(_mouse_pos_viewport)
- var ray_normal := _camera.project_ray_normal(_mouse_pos_viewport)
- # Plane with normal (0,1,0) facing upwards, at a distance 0 from the origin
- var intersection = Plane(0, 1, 0, 0).intersects_ray(ray_origin, ray_normal)
- if typeof(intersection) == TYPE_VECTOR3:
- var intersection_vec := intersection as Vector3
- # This loops both horizontally (good) and vertically (bad)
- _mouse_pos_map = (Vector2(intersection_vec.x, intersection_vec.z) - _map_mesh_corner) / _map_mesh_dims
- _map_shader_material.set_shader_parameter(_shader_param_hover_pos, _mouse_pos_map)
- else:
- # Normals parallel to the xz-plane could cause null intersections,
- # but the camera's orientation should prevent such normals
- push_error("Invalid intersection: ", intersection)
+ _mouse_pos_map = _viewport_to_map_coords(_mouse_pos_viewport)
+ var hover_index := MapSingleton.get_province_index_from_uv_coords(_mouse_pos_map)
+ if _mouse_over_viewport:
+ _map_shader_material.set_shader_parameter(_shader_param_hover_index, hover_index)
+
+func _on_mouse_entered_viewport():
+ _mouse_over_viewport = true
+
+func _on_mouse_exited_viewport():
+ _mouse_over_viewport = false
+
+func _on_minimap_clicked(pos_clicked : Vector2):
+ pos_clicked *= _map_mesh_dims
+ _camera.position.x = pos_clicked.x
+ _camera.position.z = pos_clicked.y
+ _clamp_over_map() \ No newline at end of file
diff --git a/game/src/GameSession/MapView.tscn b/game/src/GameSession/MapView.tscn
index 4650acb..93fc162 100644
--- a/game/src/GameSession/MapView.tscn
+++ b/game/src/GameSession/MapView.tscn
@@ -2,26 +2,28 @@
[ext_resource type="Script" path="res://src/GameSession/MapView.gd" id="1_exccw"]
[ext_resource type="Shader" path="res://src/GameSession/TerrainMap.gdshader" id="1_upocn"]
-[ext_resource type="Texture2D" uid="uid://cmw0pvjthnn8c" path="res://common/map/terrain/terrain.png" id="3_l8pnf"]
+[ext_resource type="Texture2D" uid="uid://cmw0pvjthnn8c" path="res://art/terrain/terrain.png" id="3_l8pnf"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_tayeg"]
render_priority = 0
shader = ExtResource("1_upocn")
-shader_parameter/hover_pos = Vector2(0.5, 0.5)
-shader_parameter/selected_pos = Vector2(0.5, 0.5)
+shader_parameter/hover_index = null
+shader_parameter/selected_index = null
shader_parameter/terrain_tex = ExtResource("3_l8pnf")
[sub_resource type="MapMesh" id="MapMesh_3gtsd"]
[node name="MapView" type="Node3D" node_paths=PackedStringArray("_camera", "_map_mesh_instance")]
+editor_description = "SS-73"
script = ExtResource("1_exccw")
_camera = NodePath("MapCamera")
_map_mesh_instance = NodePath("MapMeshInstance")
[node name="MapCamera" type="Camera3D" parent="."]
-transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 1, 1)
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0.25, 1.5, -2.75)
[node name="MapMeshInstance" type="MeshInstance3D" parent="."]
+editor_description = "FS-343"
transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0)
material_override = SubResource("ShaderMaterial_tayeg")
mesh = SubResource("MapMesh_3gtsd")
diff --git a/game/src/GameSession/Minimap.gd b/game/src/GameSession/Minimap.gd
new file mode 100644
index 0000000..25c7cac
--- /dev/null
+++ b/game/src/GameSession/Minimap.gd
@@ -0,0 +1,89 @@
+extends Control
+
+signal minimap_clicked(pos_clicked : Vector2)
+
+const _action_click : StringName = &"map_click"
+
+var _viewport_points : PackedVector2Array
+
+# REQUIREMENTS
+# * SS-80
+# * UI-752
+func _draw() -> void:
+ if _viewport_points.size() > 1:
+ draw_multiline(_viewport_points, Color.WHITE, -1)
+
+# REQUIREMENTS
+# * SS-81
+# * UIFUN-127
+func _unhandled_input(event : InputEvent):
+ if event is InputEventMouse and Input.is_action_pressed(_action_click):
+ var pos_clicked := get_local_mouse_position() / size - Vector2(0.5, 0.5)
+ if abs(pos_clicked.x) < 0.5 and abs(pos_clicked.y) < 0.5:
+ minimap_clicked.emit(pos_clicked)
+
+# Returns the point on the line going through p and q with the specific x coord
+func _intersect_x(p : Vector2, q : Vector2, x : float) -> Vector2:
+ if p.x == q.x:
+ return Vector2(x, 0.5 * (p.y + q.y))
+ var t := (x - q.x) / (p.x - q.x)
+ return q + t * (p - q)
+
+# Returns the point on the line going through p and q with the specific y coord
+func _intersect_y(p : Vector2, q : Vector2, y : float) -> Vector2:
+ if p.y == q.y:
+ return Vector2(0.5 * (p.x + q.x), y)
+ var t := (y - q.y) / (p.y - q.y)
+ return q + t * (p - q)
+
+const _one_x := Vector2(1, 0)
+
+func _add_line_looped_over_x(left : Vector2, right : Vector2) -> void:
+ if left.x < 0:
+ if right.x < 0:
+ _viewport_points.push_back(left + _one_x)
+ _viewport_points.push_back(right + _one_x)
+ else:
+ var mid_point := _intersect_x(left, right, 0)
+ _viewport_points.push_back(mid_point)
+ _viewport_points.push_back(right)
+ mid_point.x = 1
+ _viewport_points.push_back(left + _one_x)
+ _viewport_points.push_back(mid_point)
+ elif right.x > 1:
+ if left.x > 1:
+ _viewport_points.push_back(left - _one_x)
+ _viewport_points.push_back(right - _one_x)
+ else:
+ var mid_point := _intersect_x(left, right, 1)
+ _viewport_points.push_back(left)
+ _viewport_points.push_back(mid_point)
+ mid_point.x = 0
+ _viewport_points.push_back(mid_point)
+ _viewport_points.push_back(right - _one_x)
+ else:
+ _viewport_points.push_back(left)
+ _viewport_points.push_back(right)
+
+# This can break if the viewport is rotated too far!
+func _on_map_view_camera_changed(near_left : Vector2, far_left : Vector2, far_right : Vector2, near_right : Vector2) -> void:
+ # Bound far y coords
+ if far_left.y < 0:
+ far_left = _intersect_y(near_left, far_left, 0)
+ if far_right.y < 0:
+ far_right = _intersect_y(near_right, far_right, 0)
+ # Bound near y coords
+ if near_left.y > 1:
+ near_left = _intersect_y(near_left, far_left, 1)
+ if near_right.y > 1:
+ near_right = _intersect_y(near_right, far_right, 1)
+
+ _viewport_points.clear()
+ _add_line_looped_over_x(near_left, near_right)
+ _add_line_looped_over_x(far_left, far_right)
+ _add_line_looped_over_x(far_left, near_left)
+ _add_line_looped_over_x(near_right, far_right)
+
+ for i in _viewport_points.size():
+ _viewport_points[i] *= size
+ queue_redraw()
diff --git a/game/src/GameSession/ProvinceOverviewPanel.tscn b/game/src/GameSession/ProvinceOverviewPanel.tscn
index e21b1c3..2df95fb 100644
--- a/game/src/GameSession/ProvinceOverviewPanel.tscn
+++ b/game/src/GameSession/ProvinceOverviewPanel.tscn
@@ -3,6 +3,7 @@
[ext_resource type="Script" path="res://src/GameSession/ProvinceOverviewPanel.gd" id="1_3n8k5"]
[node name="ProvinceOverviewPanel" type="Panel" node_paths=PackedStringArray("_province_name_label")]
+editor_description = "UI-56"
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
@@ -23,6 +24,7 @@ offset_right = -10.0
offset_bottom = -5.0
[node name="ProvinceName" type="Label" parent="VBoxContainer"]
+editor_description = "UI-57"
layout_mode = 2
text = "PROVINCE_NAME"
vertical_alignment = 1
diff --git a/game/src/GameSession/TerrainMap.gdshader b/game/src/GameSession/TerrainMap.gdshader
index a6774fd..7aca0f9 100644
--- a/game/src/GameSession/TerrainMap.gdshader
+++ b/game/src/GameSession/TerrainMap.gdshader
@@ -8,10 +8,10 @@ uniform sampler2D terrain_tex: source_color, repeat_enable, filter_linear;
uniform sampler2D province_index_tex : repeat_enable, filter_nearest;
// Province colour texture
uniform sampler2D province_colour_tex: source_color, repeat_enable, filter_nearest;
-// Position of the mouse over the map mesh in UV coords
-uniform vec2 hover_pos;
-// Position in UV coords of a pixel belonging to the currently selected province
-uniform vec2 selected_pos;
+// Index of the mouse over the map mesh
+uniform uint hover_index;
+// Index of the currently selected province
+uniform uint selected_index;
uvec2 vec2_to_uvec2(vec2 v) {
return uvec2(v * 255.0);
@@ -31,13 +31,10 @@ uint read_uint16(sampler2D tex, vec2 uv) {
void fragment() {
uvec2 prov_idx_split = read_uvec2(province_index_tex, UV);
-
uint prov_index = uvec2_to_uint(prov_idx_split);
- uint hover_index = read_uint16(province_index_tex, hover_pos);
- uint selected_index = read_uint16(province_index_tex, selected_pos);
// Boost prov_colour's contribution if it matches hover_colour or selected_colour
- float mix_val = float(prov_index == hover_index) * 0.3 + float(prov_index == selected_index) * 0.5;
+ float mix_val = 0.3 + float(prov_index == hover_index) * 0.3 + float(prov_index == selected_index) * 0.3;
// Don't mix if the province index is 0
mix_val *= float(prov_index != 0u);