diff options
-rw-r--r-- | src/openvic-simulation/dataloader/NodeTools.hpp | 19 | ||||
-rw-r--r-- | src/openvic-simulation/map/Map.cpp | 64 | ||||
-rw-r--r-- | src/openvic-simulation/map/Map.hpp | 14 | ||||
-rw-r--r-- | src/openvic-simulation/map/Province.cpp | 93 | ||||
-rw-r--r-- | src/openvic-simulation/map/Province.hpp | 28 | ||||
-rw-r--r-- | src/openvic-simulation/types/IdentifierRegistry.hpp | 34 | ||||
-rw-r--r-- | src/openvic-simulation/types/Vector.hpp | 9 | ||||
-rw-r--r-- | src/openvic-simulation/types/fixed_point/FixedPoint.hpp | 4 |
8 files changed, 194 insertions, 71 deletions
diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp index c41c09e..0df057a 100644 --- a/src/openvic-simulation/dataloader/NodeTools.hpp +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -506,5 +506,24 @@ namespace OpenVic { return warn; }; } + + /* Often used for rotations which must be negated due to OpenVic's coordinate system being orientated + * oppositely to Vic2's. */ + template<typename T = fixed_point_t> + constexpr Callback<T> auto negate_callback(Callback<T> auto callback) { + return [callback](T val) -> bool { + return callback(-val); + }; + } + + /* Often used for map-space coordinates which must have their y-coordinate flipped due to OpenVic using the + * top-left of the map as the origin as opposed Vic2 using the bottom-left. */ + template<typename T> + constexpr Callback<vec2_t<T>> auto flip_y_callback(Callback<vec2_t<T>> auto callback, T height) { + return [callback, height](vec2_t<T> val) -> bool { + val.y = height - val.y; + return callback(val); + }; + } } } diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index 81a599e..afbcf62 100644 --- a/src/openvic-simulation/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -31,7 +31,7 @@ Mapmode::base_stripe_t Mapmode::get_base_stripe_colours(Map const& map, Province } Map::Map() - : width { 0 }, height { 0 }, max_provinces { Province::MAX_INDEX }, selected_province { nullptr }, + : dims { 0, 0 }, max_provinces { Province::MAX_INDEX }, selected_province { nullptr }, highest_province_population { 0 }, total_map_population { 0 } {} bool Map::add_province(std::string_view identifier, colour_t colour) { @@ -75,8 +75,8 @@ Province::distance_t Map::calculate_distance_between(Province const& from, Provi const fixed_point_t min_x = std::min( (to_pos.x - from_pos.x).abs(), std::min( - (to_pos.x - from_pos.x + width).abs(), - (to_pos.x - from_pos.x - width).abs() + (to_pos.x - from_pos.x + get_width()).abs(), + (to_pos.x - from_pos.x - get_width()).abs() ) ); @@ -327,13 +327,21 @@ Province::index_t Map::get_index_from_colour(colour_t colour) const { return Province::NULL_INDEX; } -Province::index_t Map::get_province_index_at(size_t x, size_t y) const { - if (x < width && y < height) { - return province_shape_image[x + y * width].index; +Province::index_t Map::get_province_index_at(ivec2_t pos) const { + if (pos.nonnegative() && pos.less_than(dims)) { + return province_shape_image[get_pixel_index_from_pos(pos)].index; } return Province::NULL_INDEX; } +Province* Map::get_province_at(ivec2_t pos) { + return get_province_by_index(get_province_index_at(pos)); +} + +Province const* Map::get_province_at(ivec2_t pos) const { + return get_province_by_index(get_province_index_at(pos)); +} + bool Map::set_max_provinces(Province::index_t new_max_provinces) { if (new_max_provinces <= Province::NULL_INDEX) { Logger::error( @@ -568,8 +576,8 @@ bool Map::load_province_definitions(std::vector<LineObject> const& lines) { } bool Map::load_province_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) { - return expect_province_dictionary([&building_type_manager](Province& province, ast::NodeCPtr node) -> bool { - return province.load_positions(building_type_manager, node); + return expect_province_dictionary([this, &building_type_manager](Province& province, ast::NodeCPtr node) -> bool { + return province.load_positions(*this, building_type_manager, node); })(root); } @@ -665,9 +673,9 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain return false; } - width = province_bmp.get_width(); - height = province_bmp.get_height(); - province_shape_image.resize(width * height); + dims.x = province_bmp.get_width(); + dims.y = province_bmp.get_height(); + province_shape_image.resize(dims.x * dims.y); uint8_t const* province_data = province_bmp.get_pixel_data().data(); uint8_t const* terrain_data = terrain_bmp.get_pixel_data().data(); @@ -680,13 +688,13 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain std::vector<fixed_point_t> pixels_per_province(provinces.size()); std::vector<fvec2_t> pixel_position_sum_per_province(provinces.size()); - for (int32_t y = 0; y < height; ++y) { - for (int32_t x = 0; x < width; ++x) { - const size_t pixel_index = x + y * width; + for (ivec2_t pos {}; pos.y < get_height(); ++pos.y) { + for (pos.x = 0; pos.x < get_width(); ++pos.x) { + const size_t pixel_index = get_pixel_index_from_pos(pos); const colour_t province_colour = colour_at(province_data, pixel_index); Province::index_t province_index = Province::NULL_INDEX; - if (x > 0) { + if (pos.x > 0) { const size_t jdx = pixel_index - 1; if (colour_at(province_data, jdx) == province_colour) { province_index = province_shape_image[jdx].index; @@ -694,8 +702,8 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain } } - if (y > 0) { - const size_t jdx = pixel_index - width; + if (pos.y > 0) { + const size_t jdx = pixel_index - get_width(); if (colour_at(province_data, jdx) == province_colour) { province_index = province_shape_image[jdx].index; goto index_found; @@ -708,7 +716,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain unrecognised_province_colours.insert(province_colour); if (detailed_errors) { Logger::warning( - "Unrecognised province colour ", province_colour, " at (", x, ", ", y, ")" + "Unrecognised province colour ", province_colour, " at ", pos ); } } @@ -719,7 +727,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain if (province_index != Province::NULL_INDEX) { const Province::index_t array_index = province_index - 1; pixels_per_province[array_index]++; - pixel_position_sum_per_province[array_index] += static_cast<fvec2_t>(ivec2_t { x, y }); + pixel_position_sum_per_province[array_index] += static_cast<fvec2_t>(pos); } const TerrainTypeMapping::index_t terrain = terrain_data[pixel_index]; @@ -755,7 +763,7 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain province->on_map = pixel_count > 0; if (province->on_map) { - province->positions.centre = pixel_position_sum_per_province[array_index] / pixel_count; + province->centre = pixel_position_sum_per_province[array_index] / pixel_count; } else { if (detailed_errors) { Logger::warning("Province missing from shape image: ", province->to_string()); @@ -776,23 +784,21 @@ bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain bool Map::_generate_standard_province_adjacencies() { bool changed = false; - 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); + const auto generate_adjacency = [this](Province* current, ivec2_t pos) -> bool { + Province* neighbour = get_province_at(pos); if (neighbour != nullptr) { return add_standard_adjacency(*current, *neighbour); } return false; }; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; ++x) { - Province* cur = get_province_by_index(province_shape_image[x + y * width].index); + for (ivec2_t pos {}; pos.y < get_height(); ++pos.y) { + for (pos.x = 0; pos.x < get_width(); ++pos.x) { + Province* cur = get_province_at(pos); if (cur != nullptr) { - changed |= generate_adjacency(cur, (x + 1) % width, y); - if (y + 1 < height) { - changed |= generate_adjacency(cur, x, y + 1); - } + changed |= generate_adjacency(cur, { (pos.x + 1) % get_width(), pos.y }); + changed |= generate_adjacency(cur, { pos.x, pos.y + 1 }); } } } diff --git a/src/openvic-simulation/map/Map.hpp b/src/openvic-simulation/map/Map.hpp index 807945a..2a3f224 100644 --- a/src/openvic-simulation/map/Map.hpp +++ b/src/openvic-simulation/map/Map.hpp @@ -69,8 +69,7 @@ namespace OpenVic { ProvinceSet water_provinces; TerrainTypeManager PROPERTY_REF(terrain_type_manager); - int32_t PROPERTY(width); - int32_t PROPERTY(height); + ivec2_t PROPERTY(dims); std::vector<shape_pixel_t> PROPERTY(province_shape_image); colour_index_map_t colour_index_map; @@ -84,9 +83,16 @@ namespace OpenVic { StateManager PROPERTY_REF(state_manager); + inline constexpr int32_t get_pixel_index_from_pos(ivec2_t pos) const { + return pos.x + pos.y * dims.x; + } + public: Map(); + inline constexpr int32_t get_width() const { return dims.x; } + inline constexpr int32_t get_height() const { return dims.y; } + bool add_province(std::string_view identifier, colour_t colour); IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS_CUSTOM_INDEX_OFFSET(province, 1); @@ -110,7 +116,9 @@ namespace OpenVic { bool set_water_province_list(std::vector<std::string_view> const& list); void lock_water_provinces(); - Province::index_t get_province_index_at(size_t x, size_t y) const; + Province::index_t get_province_index_at(ivec2_t pos) const; + Province* get_province_at(ivec2_t pos); + Province const* get_province_at(ivec2_t pos) const; bool set_max_provinces(Province::index_t new_max_provinces); void set_selected_province(Province::index_t index); Province* get_selected_province(); diff --git a/src/openvic-simulation/map/Province.cpp b/src/openvic-simulation/map/Province.cpp index eb23759..89ddd39 100644 --- a/src/openvic-simulation/map/Province.cpp +++ b/src/openvic-simulation/map/Province.cpp @@ -1,6 +1,7 @@ #include "Province.hpp" #include "openvic-simulation/history/ProvinceHistory.hpp" +#include "openvic-simulation/map/Map.hpp" #include "openvic-simulation/military/UnitInstance.hpp" using namespace OpenVic; @@ -10,9 +11,10 @@ Province::Province( std::string_view new_identifier, colour_t new_colour, index_t new_index ) : HasIdentifierAndColour { new_identifier, new_colour, true }, index { new_index }, region { nullptr }, climate { nullptr }, continent { 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 } { + port { false }, port_adjacent_province { nullptr }, 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); } @@ -26,33 +28,68 @@ std::string Province::to_string() const { return stream.str(); } -bool Province::load_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) { +bool Province::load_positions(Map const& map, BuildingTypeManager const& building_type_manager, ast::NodeCPtr root) { + const fixed_point_t map_height = map.get_height(); + 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_position", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.text), map_height)), + "text_rotation", ZERO_OR_ONE, + expect_fixed_point(negate_callback<fixed_point_t>(assign_variable_callback(positions.text_rotation))), "text_scale", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(positions.text_scale)), - "unit", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.unit)), - "town", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.city)), - "city", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.city)), - "factory", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.factory)), - "building_construction", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.building_construction)), - "military_construction", ZERO_OR_ONE, expect_fvec2(assign_variable_callback(positions.military_construction)), + + "unit", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.unit), map_height)), + "town", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.city), map_height)), + "city", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.city), map_height)), + "factory", ZERO_OR_ONE, expect_fvec2(flip_y_callback(assign_variable_callback(positions.factory), map_height)), + + "building_construction", ZERO_OR_ONE, + expect_fvec2(flip_y_callback(assign_variable_callback(positions.building_construction), map_height)), + "military_construction", ZERO_OR_ONE, + expect_fvec2(flip_y_callback(assign_variable_callback(positions.military_construction), map_height)), + "building_position", ZERO_OR_ONE, building_type_manager.expect_building_type_dictionary_reserve_length( positions.building_position, - [this](BuildingType const& type, ast::NodeCPtr value) -> bool { - return expect_fvec2(map_callback(positions.building_position, &type))(value); + [this, map_height](BuildingType const& type, ast::NodeCPtr value) -> bool { + return expect_fvec2(flip_y_callback(map_callback(positions.building_position, &type), map_height))(value); } ), "building_rotation", ZERO_OR_ONE, building_type_manager.expect_building_type_decimal_map( - move_variable_callback(positions.building_rotation) + move_variable_callback(positions.building_rotation), std::negate {} ), + /* the below are esoteric clausewitz leftovers that either have no impact or whose functionality is lost to time */ "spawn_railway_track", ZERO_OR_ONE, success_callback, "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()); + if (coastal) { + fvec2_t const* port_position = get_building_position(building_type_manager.get_port_building_type()); + if (port_position != nullptr) { + const fixed_point_t rotation = get_building_rotation(building_type_manager.get_port_building_type()); + + /* At 0 rotation the port faces west, as rotation increases the port rotates anti-clockwise. */ + const fvec2_t port_dir { -rotation.cos(), rotation.sin() }; + const ivec2_t port_facing_position = static_cast<ivec2_t>(*port_position + port_dir / 4); + + Province const* province = map.get_province_at(port_facing_position); + + if (province != nullptr) { + if (province->is_water() && is_adjacent_to(province)) { + port = true; + port_adjacent_province = province; + } else { + /* Expected provinces with invalid ports: 39, 296, 1047, 1406, 2044 */ + Logger::warning( + "Invalid port for province ", get_identifier(), ": facing province ", province, + " which has: water = ", province->is_water(), ", adjacent = ", is_adjacent_to(province) + ); + } + } else { + Logger::warning("Invalid port for province ", get_identifier(), ": facing null province!"); + } + } + } return ret; } @@ -66,6 +103,28 @@ bool Province::expand_building(size_t building_index) { return building->expand(); } +fvec2_t const* Province::get_building_position(BuildingType const* building_type) const { + if (building_type != nullptr) { + const decltype(positions.building_position)::const_iterator it = positions.building_position.find(building_type); + + if (it != positions.building_position.end()) { + return &it->second; + } + } + return nullptr; +} + +fixed_point_t Province::get_building_rotation(BuildingType const* building_type) const { + if (building_type != nullptr) { + const decltype(positions.building_rotation)::const_iterator it = positions.building_rotation.find(building_type); + + if (it != positions.building_rotation.end()) { + return it->second; + } + } + return 0; +} + void Province::_add_pop(Pop pop) { pop.set_location(this); pops.push_back(std::move(pop)); @@ -179,7 +238,7 @@ bool Province::has_adjacency_going_through(Province const* province) const { } fvec2_t Province::get_unit_position() const { - return positions.unit.value_or(positions.centre); + return positions.unit.value_or(centre); } bool Province::add_army(ArmyInstance& army) { diff --git a/src/openvic-simulation/map/Province.hpp b/src/openvic-simulation/map/Province.hpp index c4785dd..cfe5ed6 100644 --- a/src/openvic-simulation/map/Province.hpp +++ b/src/openvic-simulation/map/Province.hpp @@ -68,20 +68,17 @@ namespace OpenVic { }; struct province_positions_t { - /* Calculated average */ - fvec2_t centre; - /* Province name placement */ - fvec2_t text; - fixed_point_t text_rotation; - fixed_point_t text_scale; + std::optional<fvec2_t> text; + std::optional<fvec2_t> text_rotation; + std::optional<fvec2_t> text_scale; /* Model positions */ std::optional<fvec2_t> unit; - fvec2_t city; - fvec2_t factory; - fvec2_t building_construction; - fvec2_t military_construction; + std::optional<fvec2_t> city; + std::optional<fvec2_t> factory; + std::optional<fvec2_t> building_construction; + std::optional<fvec2_t> military_construction; ordered_map<BuildingType const*, fvec2_t> building_position; fixed_point_map_t<BuildingType const*> building_rotation; }; @@ -99,11 +96,14 @@ namespace OpenVic { bool PROPERTY_CUSTOM_PREFIX(water, is); bool PROPERTY_CUSTOM_PREFIX(coastal, is); bool PROPERTY_CUSTOM_PREFIX(port, has); + Province const* PROPERTY(port_adjacent_province); /* Terrain type calculated from terrain image */ TerrainType const* PROPERTY(default_terrain_type); std::vector<adjacency_t> PROPERTY(adjacencies); - province_positions_t PROPERTY(positions); + /* Calculated mean pixel position. */ + fvec2_t PROPERTY(centre); + province_positions_t positions; /* Mutable attributes (reset before loading history) */ TerrainType const* PROPERTY(terrain_type); @@ -138,9 +138,13 @@ namespace OpenVic { bool operator==(Province const& other) const; std::string to_string() const; - bool load_positions(BuildingTypeManager const& building_type_manager, ast::NodeCPtr root); + /* The positions' y coordinates need to be inverted. */ + bool load_positions(Map const& map, BuildingTypeManager const& building_type_manager, ast::NodeCPtr root); bool expand_building(size_t building_index); + /* This returns a pointer to the position of the specified building type, or nullptr if none exists. */ + fvec2_t const* get_building_position(BuildingType const* building_type) const; + fixed_point_t get_building_rotation(BuildingType const* building_type) const; bool add_pop(Pop&& pop); bool add_pop_vec(std::vector<Pop> const& pop_vec); diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index 393ea37..c1fef9d 100644 --- a/src/openvic-simulation/types/IdentifierRegistry.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -421,18 +421,30 @@ namespace OpenVic { return identifiers; } + /* Parses a dictionary with item keys and decimal number values (in the form of fixed point values), + * with the resulting map move-returned via `callback`. The values can be transformed by providing + * a fixed point to fixed point function fixed_point_functor, which will be applied to ever parsed value. */ + template<NodeTools::FunctorConvertible<fixed_point_t, fixed_point_t> FixedPointFunctor = std::identity> constexpr NodeTools::NodeCallback auto expect_item_decimal_map( - NodeTools::Callback<fixed_point_map_t<external_value_type const*>&&> auto callback + NodeTools::Callback<fixed_point_map_t<external_value_type const*>&&> auto callback, + FixedPointFunctor fixed_point_functor = {} ) const { - return [this, callback](ast::NodeCPtr node) -> bool { + return [this, callback, fixed_point_functor](ast::NodeCPtr node) -> bool { fixed_point_map_t<external_value_type const*> map; - bool ret = expect_item_dictionary([&map](external_value_type const& key, ast::NodeCPtr value) -> bool { - fixed_point_t val; - const bool ret = NodeTools::expect_fixed_point(NodeTools::assign_variable_callback(val))(value); - map.emplace(&key, std::move(val)); - return ret; - })(node); + + bool ret = expect_item_dictionary( + [&map, fixed_point_functor](external_value_type const& key, ast::NodeCPtr value) -> bool { + return NodeTools::expect_fixed_point( + [&map, fixed_point_functor, &key](fixed_point_t val) -> bool { + map.emplace(&key, fixed_point_functor(val)); + return true; + } + )(value); + } + )(node); + ret &= callback(std::move(map)); + return ret; }; } @@ -525,10 +537,12 @@ public: \ std::vector<std::string_view> get_##singular##_identifiers() const { \ return registry.get_item_identifiers(); \ } \ + template<NodeTools::FunctorConvertible<fixed_point_t, fixed_point_t> FixedPointFunctor = std::identity> \ constexpr NodeTools::NodeCallback auto expect_##singular##_decimal_map( \ - NodeTools::Callback<fixed_point_map_t<decltype(registry)::external_value_type const*>&&> auto callback \ + NodeTools::Callback<fixed_point_map_t<decltype(registry)::external_value_type const*>&&> auto callback, \ + FixedPointFunctor fixed_point_functor = {} \ ) const { \ - return registry.expect_item_decimal_map(callback); \ + return registry.expect_item_decimal_map(callback, fixed_point_functor); \ } \ IDENTIFIER_REGISTRY_INTERNAL_SHARED(singular, plural, registry, index_offset, const) \ private: diff --git a/src/openvic-simulation/types/Vector.hpp b/src/openvic-simulation/types/Vector.hpp index 6897835..7ed952a 100644 --- a/src/openvic-simulation/types/Vector.hpp +++ b/src/openvic-simulation/types/Vector.hpp @@ -34,6 +34,15 @@ namespace OpenVic { return x * x + y * y; } + constexpr bool nonnegative() const { + return x >= 0 && y >= 0; + } + /* Checks that both coordinates are less than their corresponding values in the argument vector. + * This is intended for checking coordinates lie within certain bounds, it is not suitable for sorting vectors. */ + constexpr bool less_than(vec2_t const& vec) const { + return x < vec.x && y < vec.y; + } + constexpr T* data() { return reinterpret_cast<T*>(this); } diff --git a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp index 84d2b70..7752226 100644 --- a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp +++ b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp @@ -235,6 +235,10 @@ namespace OpenVic { return !negative ? result : -result; } + constexpr fixed_point_t cos() const { + return (*this + pi_half()).sin(); + } + constexpr bool is_negative() const { return value < 0; } |