aboutsummaryrefslogtreecommitdiff
path: root/src/openvic-simulation
diff options
context:
space:
mode:
author BuildTools <unconfigured@null.spigotmc.org>2023-11-20 08:23:08 +0100
committer hop311 <hop3114@gmail.com>2023-12-17 20:43:53 +0100
commitee9562c767631fbec6b444cf18f2435d24848e93 (patch)
tree8c5a770d6cf6d01c6b92247ae89522f9165967ef /src/openvic-simulation
parente6b455b8924948761dbd792756303efe18875c6d (diff)
Additional adjacency loading
Diffstat (limited to 'src/openvic-simulation')
-rw-r--r--src/openvic-simulation/GameManager.cpp37
-rw-r--r--src/openvic-simulation/dataloader/Dataloader.cpp34
-rw-r--r--src/openvic-simulation/economy/BuildingType.cpp16
-rw-r--r--src/openvic-simulation/economy/BuildingType.hpp5
-rw-r--r--src/openvic-simulation/history/ProvinceHistory.cpp2
-rw-r--r--src/openvic-simulation/map/Map.cpp186
-rw-r--r--src/openvic-simulation/map/Map.hpp25
-rw-r--r--src/openvic-simulation/map/Province.cpp250
-rw-r--r--src/openvic-simulation/map/Province.hpp69
-rw-r--r--src/openvic-simulation/types/Vector.hpp10
10 files changed, 476 insertions, 158 deletions
diff --git a/src/openvic-simulation/GameManager.cpp b/src/openvic-simulation/GameManager.cpp
index 5142c66..6c600ae 100644
--- a/src/openvic-simulation/GameManager.cpp
+++ b/src/openvic-simulation/GameManager.cpp
@@ -203,6 +203,43 @@ bool GameManager::load_hardcoded_defines() {
},
{
"mapmode_religion", shaded_mapmode(&Province::get_religion_distribution)
+ },
+ {
+ "mapmode_adjacencies", [](Map const& map, Province const& province) -> Mapmode::base_stripe_t {
+ Province const* selected_province = map.get_selected_province();
+ if (selected_province != nullptr) {
+ if (selected_province == &province) {
+ return make_solid_base_stripe(ALPHA_VALUE | 0xFFFFFF);
+ }
+ colour_t base = NULL_COLOUR, stripe = NULL_COLOUR;
+ Province::adjacency_t const* adj = selected_province->get_adjacency_to(&province);
+ if (adj != nullptr) {
+ using enum Province::adjacency_t::type_t;
+ switch (adj->get_type()) {
+ case LAND: base = 0x00FF00; break;
+ case WATER: base = 0x0000FF; break;
+ case COASTAL: base = 0xF9D199; break;
+ case IMPASSABLE: base = 0x8B4513; break;
+ case STRAIT: base = 0x00FFFF; break;
+ case CANAL: base = 0x888888; break;
+ default: base = 0xFF0000; break;
+ }
+ base |= ALPHA_VALUE;
+ stripe = base;
+ }
+ if (selected_province->has_adjacency_going_through(&province)) {
+ stripe = ALPHA_VALUE | 0xFFFF00;
+ }
+
+ return combine_base_stripe(base, stripe);
+ }
+ return NULL_COLOUR;
+ }
+ },
+ {
+ "mapmode_port", make_solid_base_stripe_func([](Map const& map, Province const& province) -> colour_t {
+ return province.has_port() ? ALPHA_VALUE | 0xFFFFFF : NULL_COLOUR;
+ })
}
};
diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp
index 97b5592..abaaf8b 100644
--- a/src/openvic-simulation/dataloader/Dataloader.cpp
+++ b/src/openvic-simulation/dataloader/Dataloader.cpp
@@ -585,15 +585,18 @@ bool Dataloader::_load_map_dir(GameManager& game_manager) const {
static constexpr std::string_view default_provinces = "provinces.bmp";
static constexpr std::string_view default_positions = "positions.txt";
static constexpr std::string_view default_terrain = "terrain.bmp";
- static constexpr std::string_view default_rivers = "rivers.bmp";
+ static constexpr std::string_view default_rivers = "rivers.bmp"; // TODO
static constexpr std::string_view default_terrain_definition = "terrain.txt";
- static constexpr std::string_view default_tree_definition = "trees.txt";
- static constexpr std::string_view default_continent = "continent.txt";
+ static constexpr std::string_view default_tree_definition = "trees.txt"; // TODO
+ static constexpr std::string_view default_continent = "continent.txt"; // TODO
static constexpr std::string_view default_adjacencies = "adjacencies.csv";
static constexpr std::string_view default_region = "region.txt";
- static constexpr std::string_view default_region_sea = "region_sea.txt";
- static constexpr std::string_view default_province_flag_sprite = "province_flag_sprites";
+ static constexpr std::string_view default_region_sea = "region_sea.txt"; // TODO
+ static constexpr std::string_view default_province_flag_sprite = "province_flag_sprites"; // TODO
+ static constexpr std::string_view climate_filename = "climate.txt"; // TODO
+
+ /* Parser stored so the filename string_views persist until the end of this function. */
const v2script::Parser parser = parse_defines(lookup_file(append_string_views(map_directory, defaults_filename)));
std::vector<std::string_view> water_province_identifiers;
@@ -645,14 +648,6 @@ bool Dataloader::_load_map_dir(GameManager& game_manager) const {
ret = false;
}
- if (!map.load_province_positions(
- game_manager.get_economy_manager().get_building_type_manager(),
- parse_defines(lookup_file(append_string_views(map_directory, positions))).get_file_node()
- )) {
- Logger::error("Failed to load province positions file!");
- ret = false;
- }
-
if (!map.load_region_file(parse_defines(lookup_file(append_string_views(map_directory, region))).get_file_node())) {
Logger::error("Failed to load region file!");
ret = false;
@@ -679,13 +674,24 @@ bool Dataloader::_load_map_dir(GameManager& game_manager) const {
ret = false;
}
- if (!map.generate_and_load_province_adjacencies(
+ if (map.generate_and_load_province_adjacencies(
parse_csv(lookup_file(append_string_views(map_directory, adjacencies))).get_lines()
)) {
+ Logger::info("Successfully generated and loaded province adjacencies!");
+ } else {
Logger::error("Failed to generate and load province adjacencies!");
ret = false;
}
+ /* Must be loaded after adjacencies so we know what provinces are coastal, and so can have a port */
+ if (!map.load_province_positions(
+ game_manager.get_economy_manager().get_building_type_manager(),
+ parse_defines(lookup_file(append_string_views(map_directory, positions))).get_file_node()
+ )) {
+ Logger::error("Failed to load province positions file!");
+ ret = false;
+ }
+
return ret;
}
diff --git a/src/openvic-simulation/economy/BuildingType.cpp b/src/openvic-simulation/economy/BuildingType.cpp
index a5a4fb0..3d44c8c 100644
--- a/src/openvic-simulation/economy/BuildingType.cpp
+++ b/src/openvic-simulation/economy/BuildingType.cpp
@@ -14,6 +14,8 @@ BuildingType::BuildingType(
colonial_range { colonial_range }, infrastructure { infrastructure }, spawn_railway_track { spawn_railway_track },
sail { sail }, steam { steam }, capital { capital }, port { port } {}
+BuildingTypeManager::BuildingTypeManager() : port_building_type { nullptr } {}
+
bool BuildingTypeManager::add_building_type(std::string_view identifier, ARGS) {
if (identifier.empty()) {
Logger::error("Invalid building identifier - empty!");
@@ -32,7 +34,7 @@ bool BuildingTypeManager::load_buildings_file(
GoodManager const& good_manager, ProductionTypeManager const& production_type_manager, ModifierManager& modifier_manager,
ast::NodeCPtr root
) {
- const bool ret = expect_dictionary_reserve_length(
+ bool ret = expect_dictionary_reserve_length(
building_types,
[this, &good_manager, &production_type_manager, &modifier_manager](std::string_view key, ast::NodeCPtr value) -> bool {
std::string_view type;
@@ -101,6 +103,18 @@ bool BuildingTypeManager::load_buildings_file(
modifier_manager.add_modifier_effect(
min_modifier_prefix.append(building_type.get_identifier()), false, ModifierEffect::format_t::INT
);
+
+ if (building_type.is_port()) {
+ if (port_building_type == nullptr) {
+ port_building_type = &building_type;
+ } else {
+ Logger::error(
+ "Building type ", building_type, " is marked as a port, but we are already using ", port_building_type,
+ " as the port building type!"
+ );
+ ret = false;
+ }
+ }
}
return ret;
diff --git a/src/openvic-simulation/economy/BuildingType.hpp b/src/openvic-simulation/economy/BuildingType.hpp
index a8f9d61..27cedfd 100644
--- a/src/openvic-simulation/economy/BuildingType.hpp
+++ b/src/openvic-simulation/economy/BuildingType.hpp
@@ -61,7 +61,7 @@ namespace OpenVic {
bool PROPERTY(sail); // only in clipper shipyard
bool PROPERTY(steam); // only in steamer shipyard
bool PROPERTY(capital); // only in naval base
- bool PROPERTY(port); // only in naval base
+ bool PROPERTY_CUSTOM_PREFIX(port, is); // only in naval base
BuildingType(std::string_view identifier, ARGS);
@@ -74,8 +74,11 @@ namespace OpenVic {
private:
IdentifierRegistry<BuildingType> IDENTIFIER_REGISTRY(building_type);
+ BuildingType const* PROPERTY(port_building_type);
public:
+ BuildingTypeManager();
+
bool add_building_type(std::string_view identifier, ARGS);
bool load_buildings_file(
diff --git a/src/openvic-simulation/history/ProvinceHistory.cpp b/src/openvic-simulation/history/ProvinceHistory.cpp
index 4e92300..db998b7 100644
--- a/src/openvic-simulation/history/ProvinceHistory.cpp
+++ b/src/openvic-simulation/history/ProvinceHistory.cpp
@@ -101,7 +101,7 @@ void ProvinceHistoryManager::lock_province_histories(Map const& map, bool detail
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.get_water()) {
+ if (!province.is_water()) {
if (detailed_errors) {
Logger::warning("Province history missing for province: ", province.get_identifier());
}
diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp
index 1e68cc6..ec4691d 100644
--- a/src/openvic-simulation/map/Map.cpp
+++ b/src/openvic-simulation/map/Map.cpp
@@ -25,6 +25,10 @@ Mapmode::base_stripe_t Mapmode::get_base_stripe_colours(Map const& map, Province
return colour_func ? colour_func(map, province) : NULL_COLOUR;
}
+Map::Map()
+ : width { 0 }, height { 0 }, max_provinces { Province::MAX_INDEX }, selected_province_index { Province::NULL_INDEX },
+ highest_province_population { 0 }, total_map_population { 0 } {}
+
bool Map::add_province(std::string_view identifier, colour_t colour) {
if (provinces.size() >= max_provinces) {
Logger::error(
@@ -69,7 +73,7 @@ bool Map::set_water_province(std::string_view identifier) {
Logger::error("Unrecognised water province identifier: ", identifier);
return false;
}
- if (province->get_water()) {
+ if (province->is_water()) {
Logger::warning("Province ", identifier, " is already a water province!");
return true;
}
@@ -172,41 +176,21 @@ bool Map::set_max_provinces(Province::index_t new_max_provinces) {
return true;
}
-Province::index_t Map::get_max_provinces() const {
- return max_provinces;
-}
-
void Map::set_selected_province(Province::index_t index) {
if (index > get_province_count()) {
Logger::error(
"Trying to set selected province to an invalid index ", index, " (max index is ", get_province_count(), ")"
);
- selected_province = Province::NULL_INDEX;
+ selected_province_index = Province::NULL_INDEX;
} else {
- selected_province = index;
+ selected_province_index = index;
}
}
-Province::index_t Map::get_selected_province_index() const {
- return selected_province;
-}
-
Province const* Map::get_selected_province() const {
return get_province_by_index(get_selected_province_index());
}
-size_t Map::get_width() const {
- return width;
-}
-
-size_t Map::get_height() const {
- return height;
-}
-
-std::vector<Map::shape_pixel_t> const& Map::get_province_shape_image() const {
- return province_shape_image;
-}
-
bool Map::add_mapmode(std::string_view identifier, Mapmode::colour_func_t colour_func) {
if (identifier.empty()) {
Logger::error("Invalid mapmode identifier - empty!");
@@ -265,10 +249,6 @@ void Map::update_highest_province_population() {
}
}
-Pop::pop_size_t Map::get_highest_province_population() const {
- return highest_province_population;
-}
-
void Map::update_total_map_population() {
total_map_population = 0;
for (Province const& province : provinces.get_items()) {
@@ -276,10 +256,6 @@ void Map::update_total_map_population() {
}
}
-Pop::pop_size_t Map::get_total_map_population() const {
- return total_map_population;
-}
-
bool Map::reset(BuildingTypeManager const& building_type_manager) {
bool ret = true;
for (Province& province : provinces.get_items()) {
@@ -291,7 +267,7 @@ bool Map::reset(BuildingTypeManager const& building_type_manager) {
bool Map::apply_history_to_provinces(ProvinceHistoryManager const& history_manager, Date date) {
bool ret = true;
for (Province& province : provinces.get_items()) {
- if (!province.get_water()) {
+ if (!province.is_water()) {
ProvinceHistoryMap const* history_map = history_manager.get_province_history(&province);
if (history_map != nullptr) {
ProvinceHistoryEntry const* pop_history_entry = nullptr;
@@ -496,46 +472,39 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain
uint8_t const* province_data = province_bmp.get_pixel_data().data();
uint8_t const* terrain_data = terrain_bmp.get_pixel_data().data();
- std::vector<bool> province_checklist(provinces.size());
std::vector<fixed_point_map_t<TerrainType const*>> terrain_type_pixels_list(provinces.size());
+
bool ret = true;
std::unordered_set<colour_t> unrecognised_province_colours;
-
+
std::vector<fixed_point_t> pixels_per_province(provinces.size());
- std::vector<fixed_point_t> x_sum_per_province(provinces.size());
- std::vector<fixed_point_t> y_sum_per_province(provinces.size());
+ std::vector<fvec2_t> pixel_position_sum_per_province(provinces.size());
- for (size_t y = 0; y < height; ++y) {
- for (size_t x = 0; x < width; ++x) {
+ for (int32_t y = 0; y < height; ++y) {
+ for (int32_t x = 0; x < width; ++x) {
const size_t pixel_index = x + y * width;
const colour_t province_colour = colour_at(province_data, pixel_index);
+ Province::index_t province_index = Province::NULL_INDEX;
if (x > 0) {
const size_t jdx = pixel_index - 1;
if (colour_at(province_data, jdx) == province_colour) {
- province_shape_image[pixel_index].index = province_shape_image[jdx].index;
- goto set_terrain;
+ province_index = province_shape_image[jdx].index;
+ goto index_found;
}
}
if (y > 0) {
const size_t jdx = pixel_index - width;
if (colour_at(province_data, jdx) == province_colour) {
- province_shape_image[pixel_index].index = province_shape_image[jdx].index;
- goto set_terrain;
+ province_index = province_shape_image[jdx].index;
+ goto index_found;
}
}
- {
- const Province::index_t province_index = get_index_from_colour(province_colour);
- if (province_index != Province::NULL_INDEX) {
- province_checklist[province_index - 1] = true;
- province_shape_image[pixel_index].index = province_index;
- goto set_terrain;
- }
- }
+ province_index = get_index_from_colour(province_colour);
- if (!unrecognised_province_colours.contains(province_colour)) {
+ if (province_index == Province::NULL_INDEX && !unrecognised_province_colours.contains(province_colour)) {
unrecognised_province_colours.insert(province_colour);
if (detailed_errors) {
Logger::warning(
@@ -544,27 +513,26 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain
}
}
- province_shape_image[pixel_index].index = Province::NULL_INDEX;
+ index_found:
+ province_shape_image[pixel_index].index = province_index;
- set_terrain:
- const Province::index_t province_index = province_shape_image[pixel_index].index;
if (province_index != Province::NULL_INDEX) {
- const uint16_t array_index = province_index - 1;
+ const Province::index_t array_index = province_index - 1;
pixels_per_province[array_index]++;
- x_sum_per_province[array_index] += x;
- y_sum_per_province[array_index] += y;
+ pixel_position_sum_per_province[array_index] += static_cast<fvec2_t>(ivec2_t { x, y });
}
const TerrainTypeMapping::index_t terrain = terrain_data[pixel_index];
TerrainTypeMapping const* mapping = terrain_type_manager.get_terrain_type_mapping_for(terrain);
if (mapping != nullptr) {
-
if (province_index != Province::NULL_INDEX) {
terrain_type_pixels_list[province_index - 1][&mapping->get_type()]++;
}
-
- province_shape_image[pixel_index].terrain =
- mapping->get_has_texture() && terrain < terrain_type_manager.get_terrain_texture_limit() ? terrain + 1 : 0;
+ if (mapping->get_has_texture() && terrain < terrain_type_manager.get_terrain_texture_limit()) {
+ province_shape_image[pixel_index].terrain = terrain + 1;
+ } else {
+ province_shape_image[pixel_index].terrain = 0;
+ }
} else {
province_shape_image[pixel_index].terrain = 0;
}
@@ -576,23 +544,19 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain
}
size_t missing = 0;
- for (size_t array_index = 0; array_index < province_checklist.size(); ++array_index) {
+ for (size_t array_index = 0; array_index < provinces.size(); ++array_index) {
Province* province = provinces.get_item_by_index(array_index);
+
+ fixed_point_map_t<TerrainType const*> const& terrain_type_pixels = terrain_type_pixels_list[array_index];
+ const fixed_point_map_const_iterator_t<TerrainType const*> largest = get_largest_item(terrain_type_pixels);
+ province->default_terrain_type = largest != terrain_type_pixels.end() ? largest->first : nullptr;
+
const fixed_point_t pixel_count = pixels_per_province[array_index];
- if(pixel_count > 0) {
- const fixed_point_t x = x_sum_per_province[array_index] / pixel_count;
- const fixed_point_t y = y_sum_per_province[array_index] / pixel_count;
- const fvec2_t center { x, y };
- province->positions.center = center;
- }
- else {
- Logger::warning("Province ",province->index," has no pixels");
- }
+ province->on_map = pixel_count > 0;
- const fixed_point_map_const_iterator_t<TerrainType const*> largest = get_largest_item(terrain_type_pixels_list[array_index]);
- province->default_terrain_type = largest != terrain_type_pixels_list[array_index].end() ? largest->first : nullptr;
- province->on_map = province_checklist[array_index];
- if (!province->on_map) {
+ if (province->on_map) {
+ province->positions.centre = pixel_position_sum_per_province[array_index] / pixel_count;
+ } else {
if (detailed_errors) {
Logger::warning("Province missing from shape image: ", province->to_string());
}
@@ -609,15 +573,13 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain
/* REQUIREMENTS:
* MAP-19, MAP-84
*/
-bool Map::_generate_province_adjacencies() {
+bool Map::_generate_standard_province_adjacencies() {
bool changed = false;
- auto generate_adjacency = [&](Province* current, size_t x, size_t y) -> bool {
+ const auto generate_adjacency = [this, &changed](Province* current, size_t x, size_t y) -> bool {
Province* neighbour = get_province_by_index(province_shape_image[x + y * width].index);
- if (neighbour != nullptr && current != neighbour) {
- const Province::distance_t distance = current->calculate_distance_to(neighbour);
- const Province::flags_t flags = 0;
- return current->add_adjacency(neighbour, distance, flags) | neighbour->add_adjacency(current, distance, flags);
+ if (neighbour != nullptr) {
+ return Province::add_standard_adjacency(*current, *neighbour);
}
return false;
};
@@ -638,8 +600,64 @@ bool Map::_generate_province_adjacencies() {
return changed;
}
-bool Map::generate_and_load_province_adjacencies(std::vector<ovdl::csv::LineObject> const& additional_adjacencies) {
- bool ret = _generate_province_adjacencies();
- // TODO - DEV TASK: read additional adjacencies
+bool Map::generate_and_load_province_adjacencies(std::vector<LineObject> const& additional_adjacencies) {
+ bool ret = _generate_standard_province_adjacencies();
+ if (!ret) {
+ Logger::error("Failed to generate standard province adjacencies!");
+ }
+ /* Skip first line containing column headers */
+ if (additional_adjacencies.size() <= 1) {
+ Logger::error("No entries in province adjacencies file!");
+ return false;
+ }
+ std::for_each(
+ additional_adjacencies.begin() + 1, additional_adjacencies.end(), [this, &ret](LineObject const& adjacency) -> void {
+ const std::string_view from_str = adjacency.get_value_for(0);
+ if (from_str.empty() || from_str.front() == '#') {
+ return;
+ }
+ Province* const from = get_province_by_identifier(from_str);
+ if (from == nullptr) {
+ Logger::error("Unrecognised adjacency from province identifier: \"", from_str, "\"");
+ ret = false;
+ return;
+ }
+
+ const std::string_view to_str = adjacency.get_value_for(1);
+ Province* const to = get_province_by_identifier(to_str);
+ if (to == nullptr) {
+ Logger::error("Unrecognised adjacency to province identifier: \"", to_str, "\"");
+ ret = false;
+ return;
+ }
+
+ using enum Province::adjacency_t::type_t;
+ static const string_map_t<Province::adjacency_t::type_t> type_map {
+ { "land", LAND }, { "sea", STRAIT }, { "impassable", IMPASSABLE }, { "canal", CANAL }
+ };
+ const std::string_view type_str = adjacency.get_value_for(2);
+ const string_map_t<Province::adjacency_t::type_t>::const_iterator it = type_map.find(type_str);
+ if (it == type_map.end()) {
+ Logger::error("Invalid adjacency type: \"", type_str, "\"");
+ ret = false;
+ return;
+ }
+ const Province::adjacency_t::type_t type = it->second;
+
+ Province const* const through = get_province_by_identifier(adjacency.get_value_for(3));
+
+ const std::string_view data_str = adjacency.get_value_for(4);
+ bool successful = false;
+ const uint64_t data_uint = StringUtils::string_to_uint64(data_str, &successful);
+ if (!successful || data_uint > std::numeric_limits<Province::adjacency_t::data_t>::max()) {
+ Logger::error("Invalid adjacency data: \"", data_str, "\"");
+ ret = false;
+ return;
+ }
+ const Province::adjacency_t::data_t data = data_uint;
+
+ ret &= Province::add_special_adjacency(*from, *to, type, through, data);
+ }
+ );
return ret;
}
diff --git a/src/openvic-simulation/map/Map.hpp b/src/openvic-simulation/map/Map.hpp
index 16d12a8..185e99e 100644
--- a/src/openvic-simulation/map/Map.hpp
+++ b/src/openvic-simulation/map/Map.hpp
@@ -59,20 +59,24 @@ namespace OpenVic {
ProvinceSet water_provinces;
TerrainTypeManager PROPERTY_REF(terrain_type_manager);
- size_t width = 0, height = 0;
- std::vector<shape_pixel_t> province_shape_image;
+ int32_t PROPERTY(width);
+ int32_t PROPERTY(height);
+ std::vector<shape_pixel_t> PROPERTY(province_shape_image);
colour_index_map_t colour_index_map;
- Province::index_t max_provinces = Province::MAX_INDEX;
- Province::index_t selected_province = Province::NULL_INDEX;
- Pop::pop_size_t highest_province_population, total_map_population;
+ Province::index_t PROPERTY(max_provinces);
+ Province::index_t PROPERTY(selected_province_index);
+ Pop::pop_size_t PROPERTY(highest_province_population)
+ Pop::pop_size_t PROPERTY(total_map_population);
Province::index_t get_index_from_colour(colour_t colour) const;
- bool _generate_province_adjacencies();
+ bool _generate_standard_province_adjacencies();
StateManager PROPERTY_REF(state_manager);
public:
+ Map();
+
bool add_province(std::string_view identifier, colour_t colour);
IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS_CUSTOM_INDEX_OFFSET(province, 1);
@@ -82,15 +86,9 @@ namespace OpenVic {
Province::index_t get_province_index_at(size_t x, size_t y) const;
bool set_max_provinces(Province::index_t new_max_provinces);
- Province::index_t get_max_provinces() const;
void set_selected_province(Province::index_t index);
- Province::index_t get_selected_province_index() const;
Province const* get_selected_province() const;
- size_t get_width() const;
- size_t get_height() const;
- std::vector<shape_pixel_t> const& get_province_shape_image() const;
-
bool add_region(std::string_view identifier, std::vector<std::string_view> const& province_identifiers);
IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS(region)
@@ -107,14 +105,13 @@ namespace OpenVic {
bool apply_history_to_provinces(ProvinceHistoryManager const& history_manager, Date date);
void update_highest_province_population();
- Pop::pop_size_t get_highest_province_population() const;
void update_total_map_population();
- Pop::pop_size_t get_total_map_population() const;
void update_state(Date today);
void tick(Date today);
bool load_province_definitions(std::vector<ovdl::csv::LineObject> const& lines);
+ /* Must be loaded after adjacencies so we know what provinces are coastal, and so can have a port */
bool load_province_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root);
bool load_region_file(ast::NodeCPtr root);
bool load_map_images(fs::path const& province_path, fs::path const& terrain_path, bool detailed_errors);
diff --git a/src/openvic-simulation/map/Province.cpp b/src/openvic-simulation/map/Province.cpp
index 717ef35..6f1a0f6 100644
--- a/src/openvic-simulation/map/Province.cpp
+++ b/src/openvic-simulation/map/Province.cpp
@@ -7,14 +7,18 @@ using namespace OpenVic::NodeTools;
Province::Province(
std::string_view new_identifier, colour_t new_colour, index_t new_index
-) : HasIdentifierAndColour { new_identifier, new_colour, true, false }, index { new_index },
- region { nullptr }, on_map { false }, has_region { false }, water { false }, default_terrain_type { nullptr },
- positions {}, terrain_type { nullptr }, life_rating { 0 }, colony_status { colony_status_t::STATE }, owner { nullptr },
- controller { nullptr }, slave { false }, crime { nullptr }, rgo { nullptr }, buildings { "buildings", false },
- total_population { 0 } {
+) : HasIdentifierAndColour { new_identifier, new_colour, true, false }, index { new_index }, region { nullptr },
+ on_map { false }, has_region { false }, water { false }, coastal { false }, port { false },
+ default_terrain_type { nullptr }, positions {}, terrain_type { nullptr }, life_rating { 0 },
+ colony_status { colony_status_t::STATE }, state { nullptr }, owner { nullptr }, controller { nullptr }, slave { false },
+ crime { nullptr }, rgo { nullptr }, buildings { "buildings", false }, total_population { 0 } {
assert(index != NULL_INDEX);
}
+bool Province::operator==(Province const& other) const {
+ return this == &other;
+}
+
std::string Province::to_string() const {
std::stringstream stream;
stream << "(#" << std::to_string(index) << ", " << get_identifier() << ", 0x" << colour_to_hex_string() << ")";
@@ -22,7 +26,7 @@ std::string Province::to_string() const {
}
bool Province::load_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) {
- return expect_dictionary_keys(
+ const bool ret = expect_dictionary_keys(
"text_position", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.text)),
"text_rotation", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(positions.text_rotation)),
"text_scale", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(positions.text_scale)),
@@ -48,6 +52,10 @@ bool Province::load_positions(BuildingTypeManager const& building_type_manager,
"railroad_visibility", ZERO_OR_ONE, success_callback,
"building_nudge", ZERO_OR_ONE, success_callback
)(root);
+
+ port = coastal && positions.building_position.contains(building_type_manager.get_port_building_type());
+
+ return ret;
}
bool Province::expand_building(std::string_view building_type_identifier) {
@@ -59,7 +67,7 @@ bool Province::expand_building(std::string_view building_type_identifier) {
}
bool Province::add_pop(Pop&& pop) {
- if (!get_water()) {
+ if (!is_water()) {
pops.push_back(std::move(pop));
return true;
} else {
@@ -69,7 +77,7 @@ bool Province::add_pop(Pop&& pop) {
}
bool Province::add_pop_vec(std::vector<Pop> const& pop_vec) {
- if (!get_water()) {
+ if (!is_water()) {
pops.reserve(pops.size() + pop_vec.size());
for (Pop const& pop : pop_vec) {
pops.push_back(pop);
@@ -116,57 +124,241 @@ void Province::tick(Date today) {
}
}
-Province::adjacency_t::adjacency_t(Province const* province, distance_t distance, flags_t flags)
- : province { province }, distance { distance }, flags { flags } {
- assert(province != nullptr);
+Province::adjacency_t::adjacency_t(
+ Province const* new_to, distance_t new_distance, type_t new_type, Province const* new_through, data_t new_data
+) : to { new_to }, distance { new_distance }, type { new_type }, through { new_through }, data { new_data } {}
+
+std::string_view Province::adjacency_t::get_type_name(type_t type) {
+ switch (type) {
+ case type_t::LAND: return "Land";
+ case type_t::WATER: return "Water";
+ case type_t::COASTAL: return "Coastal";
+ case type_t::IMPASSABLE: return "Impassable";
+ case type_t::STRAIT: return "Strait";
+ case type_t::CANAL: return "Canal";
+ default: return "Invalid Adjacency Type";
+ }
+}
+
+Province::adjacency_t* Province::get_adjacency_to(Province const* province) {
+ const std::vector<adjacency_t>::iterator it = std::find_if(adjacencies.begin(), adjacencies.end(),
+ [province](adjacency_t const& adj) -> bool { return adj.get_to() == province; }
+ );
+ if (it != adjacencies.end()) {
+ return &*it;
+ } else {
+ return nullptr;
+ }
+}
+
+Province::adjacency_t const* Province::get_adjacency_to(Province const* province) const {
+ const std::vector<adjacency_t>::const_iterator it = std::find_if(adjacencies.begin(), adjacencies.end(),
+ [province](adjacency_t const& adj) -> bool { return adj.get_to() == province; }
+ );
+ if (it != adjacencies.end()) {
+ return &*it;
+ } else {
+ return nullptr;
+ }
}
bool Province::is_adjacent_to(Province const* province) const {
- for (adjacency_t adj : adjacencies) {
- if (adj.province == province) {
- return true;
+ return province != nullptr && std::any_of(adjacencies.begin(), adjacencies.end(),
+ [province](adjacency_t const& adj) -> bool { return adj.get_to() == province; }
+ );
+}
+
+std::vector<Province::adjacency_t const*> Province::get_adjacencies_going_through(Province const* province) const {
+ std::vector<adjacency_t const*> ret;
+ for (adjacency_t const& adj : adjacencies) {
+ if (adj.get_through() == province) {
+ ret.push_back(&adj);
}
}
- return false;
+ return ret;
+}
+
+bool Province::has_adjacency_going_through(Province const* province) const {
+ return province != nullptr && std::any_of(adjacencies.begin(), adjacencies.end(),
+ [province](adjacency_t const& adj) -> bool { return adj.get_through() == province; }
+ );
}
-bool Province::add_adjacency(Province const* province, distance_t distance, flags_t flags) {
- if (province == nullptr) {
- Logger::error("Tried to create null adjacency province for province ", get_identifier(), "!");
+/* This is called for all adjacent pixel pairs and returns whether or not a new adjacency was add,
+ * hence the lack of error messages in the false return cases. */
+bool Province::add_standard_adjacency(Province& from, Province& to) {
+ if (from == to) {
return false;
}
- if (is_adjacent_to(province)) {
+
+ const bool from_needs_adjacency = !from.is_adjacent_to(&to);
+ const bool to_needs_adjacency = !to.is_adjacent_to(&from);
+
+ if (!from_needs_adjacency && !to_needs_adjacency) {
return false;
}
- adjacencies.push_back({ province, distance, flags });
+
+ const distance_t distance = calculate_distance_between(from, to);
+
+ using enum adjacency_t::type_t;
+
+ /* Default land-to-land adjacency */
+ adjacency_t::type_t type = LAND;
+ if (from.is_water() != to.is_water()) {
+ /* Land-to-water adjacency */
+ type = COASTAL;
+
+ /* Mark the land province as coastal */
+ from.coastal = !from.is_water();
+ to.coastal = !to.is_water();
+ } else if (from.is_water()) {
+ /* Water-to-water adjacency */
+ type = WATER;
+ }
+
+ if (from_needs_adjacency) {
+ from.adjacencies.emplace_back(&to, distance, type, nullptr, 0);
+ }
+ if (to_needs_adjacency) {
+ to.adjacencies.emplace_back(&from, distance, type, nullptr, 0);
+ }
return true;
}
+bool Province::add_special_adjacency(
+ Province& from, Province& to, adjacency_t::type_t type, Province const* through, adjacency_t::data_t data
+) {
+ if (from == to) {
+ Logger::error("Trying to add ", adjacency_t::get_type_name(type), " adjacency from province ", from, " to itself!");
+ return false;
+ }
+
+ using enum adjacency_t::type_t;
+
+ /* Check end points */
+ switch (type) {
+ case LAND:
+ case IMPASSABLE:
+ case STRAIT:
+ if (from.is_water() || to.is_water()) {
+ Logger::error(adjacency_t::get_type_name(type), " adjacency from ", from, " to ", to, " has water endpoint(s)!");
+ return false;
+ }
+ break;
+ case WATER:
+ case CANAL:
+ if (!from.is_water() || !to.is_water()) {
+ Logger::error(adjacency_t::get_type_name(type), " adjacency from ", from, " to ", to, " has land endpoint(s)!");
+ return false;
+ }
+ break;
+ case COASTAL:
+ if (from.is_water() == to.is_water()) {
+ Logger::error("Coastal adjacency from ", from, " to ", to, " has both land or water endpoints!");
+ return false;
+ }
+ break;
+ default:
+ Logger::error("Invalid adjacency type ", static_cast<uint32_t>(type));
+ return false;
+ }
+
+ /* Check through province */
+ if (type == STRAIT || type == CANAL) {
+ const bool water_expected = type == STRAIT;
+ if (through == nullptr || through->is_water() != water_expected) {
+ Logger::error(
+ adjacency_t::get_type_name(type), " adjacency from ", from, " to ", to, " has a ",
+ (through == nullptr ? "null" : water_expected ? "land" : "water"), " through province ", through
+ );
+ return false;
+ }
+ } else if (through != nullptr) {
+ Logger::warning(
+ adjacency_t::get_type_name(type), " adjacency from ", from, " to ", to, " has a non-null through province ",
+ through
+ );
+ through = nullptr;
+ }
+
+ /* Check canal data */
+ if (data != adjacency_t::NO_CANAL && type != CANAL) {
+ Logger::warning(
+ adjacency_t::get_type_name(type), " adjacency from ", from, " to ", to, " has invalid data ",
+ static_cast<uint32_t>(data)
+ );
+ data = adjacency_t::NO_CANAL;
+ }
+
+ const distance_t distance = calculate_distance_between(from, to);
+
+ const auto add_adjacency = [distance, type, through, data](Province& from, Province const& to) -> bool {
+ adjacency_t* existing_adjacency = from.get_adjacency_to(&to);
+ if (existing_adjacency != nullptr) {
+ if (type == existing_adjacency->get_type()) {
+ Logger::warning(
+ "Adjacency from ", from, " to ", to, " already has type ", adjacency_t::get_type_name(type), "!"
+ );
+ if (type != STRAIT && type != CANAL) {
+ /* Straits and canals might change through or data, otherwise we can exit early */
+ return true;
+ }
+ }
+ if (type != IMPASSABLE && type != STRAIT && type != CANAL) {
+ Logger::error(
+ "Provinces ", from, " and ", to, " already have an existing ",
+ adjacency_t::get_type_name(existing_adjacency->get_type()), " adjacency, cannot create a ",
+ adjacency_t::get_type_name(type), " adjacency!"
+ );
+ return false;
+ }
+ if (type != existing_adjacency->get_type() && existing_adjacency->get_type() != (type == CANAL ? WATER : LAND)) {
+ Logger::error(
+ "Cannot convert ", adjacency_t::get_type_name(existing_adjacency->get_type()), " adjacency from ", from,
+ " to ", to, " to type ", adjacency_t::get_type_name(type), "!"
+ );
+ return false;
+ }
+ *existing_adjacency = { &to, distance, type, through, data };
+ return true;
+ } else if (type == IMPASSABLE) {
+ Logger::warning(
+ "Provinces ", from, " and ", to, " do not have an existing adjacency to make impassable!"
+ );
+ return true;
+ } else {
+ from.adjacencies.emplace_back(&to, distance, type, through, data);
+ return true;
+ }
+ };
+
+ return add_adjacency(from, to) & add_adjacency(to, from);
+}
+
fvec2_t Province::get_unit_position() const {
- return positions.unit.value_or(positions.center);
+ return positions.unit.value_or(positions.centre);
}
-Province::distance_t Province::calculate_distance_to(Province const* province) const {
- const fvec2_t my_unit_position = get_unit_position();
- const fvec2_t other_unit_position = province->get_unit_position();
- const fvec2_t distance_vector = other_unit_position - my_unit_position;
- const fixed_point_t distance = distance_vector.length_squared();
- return static_cast<Province::distance_t>(distance);
+Province::distance_t Province::calculate_distance_between(Province const& from, Province const& to) {
+ const fvec2_t distance_vector = to.get_unit_position() - from.get_unit_position();
+ return distance_vector.length_squared(); // TODO - replace with length using deterministic fixed point square root
}
bool Province::reset(BuildingTypeManager const& building_type_manager) {
terrain_type = default_terrain_type;
life_rating = 0;
colony_status = colony_status_t::STATE;
+ state = nullptr;
owner = nullptr;
controller = nullptr;
cores.clear();
slave = false;
+ crime = nullptr;
rgo = nullptr;
buildings.reset();
bool ret = true;
- if (!get_water()) {
+ if (!is_water()) {
if (building_type_manager.building_types_are_locked()) {
for (BuildingType const& building_type : building_type_manager.get_building_types()) {
if (building_type.get_in_province()) {
@@ -232,6 +424,6 @@ bool Province::apply_history_to_province(ProvinceHistoryEntry const* entry) {
}
}
// TODO: load state buildings
- // TODO: party loyalties for each POP when implemented on POP side#
+ // TODO: party loyalties for each POP when implemented on POP side
return ret;
}
diff --git a/src/openvic-simulation/map/Province.hpp b/src/openvic-simulation/map/Province.hpp
index 2f8068e..844ca63 100644
--- a/src/openvic-simulation/map/Province.hpp
+++ b/src/openvic-simulation/map/Province.hpp
@@ -27,26 +27,52 @@ namespace OpenVic {
using index_t = uint16_t;
using life_rating_t = int8_t;
using distance_t = fixed_point_t;
- using flags_t = uint16_t;
enum struct colony_status_t : uint8_t { STATE, PROTECTORATE, COLONY };
struct adjacency_t {
- friend struct Province;
+ using data_t = uint8_t;
+ static constexpr data_t NO_CANAL = 0;
- private:
- Province const* const province;
- const distance_t PROPERTY(distance);
- flags_t PROPERTY(flags);
+ enum struct type_t : uint8_t {
+ LAND, /* Between two land provinces */
+ WATER, /* Between two water provinces */
+ COASTAL, /* Between a land province and a water province */
+ IMPASSABLE, /* Between two land provinces (non-traversable) */
+ STRAIT, /* Between two land provinces with a water through province */
+ CANAL /* Between two water provinces with a land through province */
+ };
+
+ /* Type display name used for logging */
+ static std::string_view get_type_name(type_t type);
- adjacency_t(Province const* province, distance_t distance, flags_t flags);
+ private:
+ Province const* PROPERTY(to);
+ Province const* PROPERTY(through);
+ distance_t PROPERTY(distance);
+ type_t PROPERTY(type);
+ data_t PROPERTY(data); // represents canal index, 0 for non-canal adjacencies
+
+ public:
+ adjacency_t(
+ Province const* new_to, distance_t new_distance, type_t new_type, Province const* new_through, data_t new_data
+ );
+ adjacency_t(adjacency_t const&) = delete;
+ adjacency_t(adjacency_t&&) = default;
+ adjacency_t& operator=(adjacency_t const&) = delete;
+ adjacency_t& operator=(adjacency_t&&) = default;
};
struct province_positions_t {
- fvec2_t center;
+ /* Calculated average */
+ fvec2_t centre;
+
+ /* Province name placement */
fvec2_t text;
fixed_point_t text_rotation;
fixed_point_t text_scale;
+
+ /* Model positions */
std::optional<fvec2_t> unit;
fvec2_t city;
fvec2_t factory;
@@ -59,21 +85,25 @@ namespace OpenVic {
static constexpr index_t NULL_INDEX = 0, MAX_INDEX = std::numeric_limits<index_t>::max();
private:
+ /* Immutable attributes (unchanged after initial game load) */
const index_t PROPERTY(index);
Region* PROPERTY(region);
- State const* PROPERTY_RW(state);
bool PROPERTY(on_map);
bool PROPERTY(has_region);
- bool PROPERTY(water);
+ bool PROPERTY_CUSTOM_PREFIX(water, is);
+ bool PROPERTY_CUSTOM_PREFIX(coastal, is);
+ bool PROPERTY_CUSTOM_PREFIX(port, has);
/* Terrain type calculated from terrain image */
TerrainType const* PROPERTY(default_terrain_type);
std::vector<adjacency_t> PROPERTY(adjacencies);
province_positions_t PROPERTY(positions);
+ /* Mutable attributes (reset before loading history) */
TerrainType const* PROPERTY(terrain_type);
life_rating_t PROPERTY(life_rating);
colony_status_t PROPERTY(colony_status);
+ State const* PROPERTY_RW(state);
Country const* PROPERTY(owner);
Country const* PROPERTY(controller);
std::vector<Country const*> PROPERTY(cores);
@@ -90,13 +120,12 @@ namespace OpenVic {
fixed_point_map_t<Culture const*> PROPERTY(culture_distribution);
fixed_point_map_t<Religion const*> PROPERTY(religion_distribution);
- fvec2_t get_unit_position() const;
-
Province(std::string_view new_identifier, colour_t new_colour, index_t new_index);
public:
Province(Province&&) = default;
+ bool operator==(Province const& other) const;
std::string to_string() const;
bool load_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root);
@@ -111,10 +140,22 @@ namespace OpenVic {
void update_state(Date today);
void tick(Date today);
+ private:
+ adjacency_t* get_adjacency_to(Province const* province);
+
+ public:
+ adjacency_t const* get_adjacency_to(Province const* province) const;
bool is_adjacent_to(Province const* province) const;
- bool add_adjacency(Province const* province, distance_t distance, flags_t flags);
+ std::vector<adjacency_t const*> get_adjacencies_going_through(Province const* province) const;
+ bool has_adjacency_going_through(Province const* province) const;
+
+ static bool add_standard_adjacency(Province& from, Province& to);
+ static bool add_special_adjacency(
+ Province& from, Province& to, adjacency_t::type_t type, Province const* through, adjacency_t::data_t data
+ );
- distance_t calculate_distance_to(Province const* province) const;
+ fvec2_t get_unit_position() const;
+ static distance_t calculate_distance_between(Province const& from, Province const& to);
bool reset(BuildingTypeManager const& building_type_manager);
bool apply_history_to_province(ProvinceHistoryEntry const* entry);
diff --git a/src/openvic-simulation/types/Vector.hpp b/src/openvic-simulation/types/Vector.hpp
index 327806d..89fe12f 100644
--- a/src/openvic-simulation/types/Vector.hpp
+++ b/src/openvic-simulation/types/Vector.hpp
@@ -14,6 +14,16 @@ namespace OpenVic {
constexpr vec2_t(T new_val) : x { new_val }, y { new_val } {}
constexpr vec2_t(T new_x, T new_y) : x { new_x }, y { new_y } {}
+ constexpr vec2_t(vec2_t const&) = default;
+ constexpr vec2_t(vec2_t&&) = default;
+ constexpr vec2_t& operator=(vec2_t const&) = default;
+ constexpr vec2_t& operator=(vec2_t&&) = default;
+
+ template<typename S>
+ constexpr explicit operator vec2_t<S>() {
+ return { static_cast<S>(x), static_cast<S>(y) };
+ }
+
constexpr vec2_t abs() const {
return { x >= 0 ? x : -x, y >= 0 ? y : -y };
}