From ee9562c767631fbec6b444cf18f2435d24848e93 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 20 Nov 2023 01:23:08 -0600 Subject: Additional adjacency loading --- src/openvic-simulation/GameManager.cpp | 37 +++ src/openvic-simulation/dataloader/Dataloader.cpp | 34 +-- src/openvic-simulation/economy/BuildingType.cpp | 16 +- src/openvic-simulation/economy/BuildingType.hpp | 5 +- src/openvic-simulation/history/ProvinceHistory.cpp | 2 +- src/openvic-simulation/map/Map.cpp | 186 ++++++++------- src/openvic-simulation/map/Map.hpp | 25 +-- src/openvic-simulation/map/Province.cpp | 250 ++++++++++++++++++--- src/openvic-simulation/map/Province.hpp | 69 ++++-- src/openvic-simulation/types/Vector.hpp | 10 + 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 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 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 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 province_checklist(provinces.size()); std::vector> terrain_type_pixels_list(provinces.size()); + bool ret = true; std::unordered_set unrecognised_province_colours; - + std::vector pixels_per_province(provinces.size()); - std::vector x_sum_per_province(provinces.size()); - std::vector y_sum_per_province(provinces.size()); + std::vector 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(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 const& terrain_type_pixels = terrain_type_pixels_list[array_index]; + const fixed_point_map_const_iterator_t 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 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 const& additional_adjacencies) { - bool ret = _generate_province_adjacencies(); - // TODO - DEV TASK: read additional adjacencies +bool Map::generate_and_load_province_adjacencies(std::vector 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 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::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::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 province_shape_image; + int32_t PROPERTY(width); + int32_t PROPERTY(height); + std::vector 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 const& get_province_shape_image() const; - bool add_region(std::string_view identifier, std::vector 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 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 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::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::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::get_adjacencies_going_through(Province const* province) const { + std::vector 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(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(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(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 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::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 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 PROPERTY(cores); @@ -90,13 +120,12 @@ namespace OpenVic { fixed_point_map_t PROPERTY(culture_distribution); fixed_point_map_t 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 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 + constexpr explicit operator vec2_t() { + return { static_cast(x), static_cast(y) }; + } + constexpr vec2_t abs() const { return { x >= 0 ? x : -x, y >= 0 ? y : -y }; } -- cgit v1.2.3-56-ga3b1