diff options
author | Hop311 <hop3114@gmail.com> | 2023-09-24 23:42:48 +0200 |
---|---|---|
committer | Hop311 <hop3114@gmail.com> | 2023-09-25 00:12:36 +0200 |
commit | bbfa8faf5337ebdff60ef2106074417aa628eca1 (patch) | |
tree | 99604a6bc9a99f1c68232cfff84f01192991f1a9 | |
parent | 3714db86f7c52674e044566096f389660a67a039 (diff) |
Adding terrain image/type loading
-rw-r--r-- | src/openvic-simulation/GameManager.cpp | 8 | ||||
-rw-r--r-- | src/openvic-simulation/GameManager.hpp | 3 | ||||
-rw-r--r-- | src/openvic-simulation/Modifier.cpp | 41 | ||||
-rw-r--r-- | src/openvic-simulation/Modifier.hpp | 7 | ||||
-rw-r--r-- | src/openvic-simulation/dataloader/Dataloader.cpp | 19 | ||||
-rw-r--r-- | src/openvic-simulation/economy/Good.cpp | 4 | ||||
-rw-r--r-- | src/openvic-simulation/economy/ProductionType.cpp | 4 | ||||
-rw-r--r-- | src/openvic-simulation/map/Map.cpp | 239 | ||||
-rw-r--r-- | src/openvic-simulation/map/Map.hpp | 24 | ||||
-rw-r--r-- | src/openvic-simulation/map/TerrainType.cpp | 190 | ||||
-rw-r--r-- | src/openvic-simulation/map/TerrainType.hpp | 68 | ||||
-rw-r--r-- | src/openvic-simulation/types/IdentifierRegistry.hpp | 1 | ||||
-rw-r--r-- | src/openvic-simulation/utility/BMP.cpp | 93 | ||||
-rw-r--r-- | src/openvic-simulation/utility/BMP.hpp | 17 |
14 files changed, 564 insertions, 154 deletions
diff --git a/src/openvic-simulation/GameManager.cpp b/src/openvic-simulation/GameManager.cpp index 249808d..d969132 100644 --- a/src/openvic-simulation/GameManager.cpp +++ b/src/openvic-simulation/GameManager.cpp @@ -70,6 +70,14 @@ UnitManager const& GameManager::get_unit_manager() const { return unit_manager; } +ModifierManager& GameManager::get_modifier_manager() { + return modifier_manager; +} + +ModifierManager const& GameManager::get_modifier_manager() const{ + return modifier_manager; +} + GameAdvancementHook& GameManager::get_clock() { return clock; } diff --git a/src/openvic-simulation/GameManager.hpp b/src/openvic-simulation/GameManager.hpp index eb73331..d3f5656 100644 --- a/src/openvic-simulation/GameManager.hpp +++ b/src/openvic-simulation/GameManager.hpp @@ -24,6 +24,7 @@ namespace OpenVic { IssueManager issue_manager; ProductionTypeManager production_type_manager; UnitManager unit_manager; + ModifierManager modifier_manager; GameAdvancementHook clock; time_t session_start; /* SS-54, as well as allowing time-tracking */ @@ -54,6 +55,8 @@ namespace OpenVic { ProductionTypeManager const& get_production_type_manager() const; UnitManager& get_unit_manager(); UnitManager const& get_unit_manager() const; + ModifierManager& get_modifier_manager(); + ModifierManager const& get_modifier_manager() const; GameAdvancementHook& get_clock(); GameAdvancementHook const& get_clock() const; diff --git a/src/openvic-simulation/Modifier.cpp b/src/openvic-simulation/Modifier.cpp index 7871692..9fa0691 100644 --- a/src/openvic-simulation/Modifier.cpp +++ b/src/openvic-simulation/Modifier.cpp @@ -15,6 +15,9 @@ ModifierValue::ModifierValue(effect_map_t&& new_values) : values { std::move(new ModifierValue::ModifierValue(ModifierValue const&) = default; ModifierValue::ModifierValue(ModifierValue&&) = default; +ModifierValue& ModifierValue::operator=(ModifierValue const&) = default; +ModifierValue& ModifierValue::operator=(ModifierValue&&) = default; + void ModifierValue::trim() { std::erase_if(values, [](effect_map_t::value_type const& value) -> bool { return value.second == fixed_point_t::_0(); @@ -111,19 +114,39 @@ bool ModifierManager::add_modifier(const std::string_view identifier, ModifierVa return modifiers.add_item({ identifier, std::move(values), icon }); } -node_callback_t ModifierManager::expect_modifier_value(callback_t<ModifierValue&&> callback) const { - return [this, callback](ast::NodeCPtr root) -> bool { +bool ModifierManager::setup_modifier_effects() { + bool ret = true; + + ret &= add_modifier_effect("movement_cost", false); + ret &= add_modifier_effect("farm_rgo_size", true); + ret &= add_modifier_effect("farm_rgo_eff", true); + ret &= add_modifier_effect("mine_rgo_size", true); + ret &= add_modifier_effect("mine_rgo_eff", true); + ret &= add_modifier_effect("min_build_railroad", false); + ret &= add_modifier_effect("supply_limit", true); + ret &= add_modifier_effect("combat_width", false); + ret &= add_modifier_effect("defence", true); + + modifier_effects.lock(); + return ret; +} + +node_callback_t ModifierManager::expect_modifier_value(callback_t<ModifierValue&&> callback, key_value_callback_t default_callback) const { + return [this, callback, default_callback](ast::NodeCPtr root) -> bool { ModifierValue modifier; bool ret = expect_dictionary( - [this, &modifier](std::string_view key, ast::NodeCPtr value) -> bool { + [this, &modifier, default_callback](std::string_view key, ast::NodeCPtr value) -> bool { ModifierEffect const* effect = get_modifier_effect_by_identifier(key); if (effect != nullptr) { - return expect_fixed_point( - assign_variable_callback(modifier.values[effect]) - )(value); + if (modifier.values.find(effect) == modifier.values.end()) { + return expect_fixed_point( + assign_variable_callback(modifier.values[effect]) + )(value); + } + Logger::error("Duplicate modifier effect: ", key); + return false; } - Logger::error("Invalid modifier effect: ", key); - return false; + return default_callback(key, value); } )(root); ret &= callback(std::move(modifier)); @@ -138,4 +161,4 @@ namespace OpenVic { //so the compiler shuts up } return stream; } -}
\ No newline at end of file +} diff --git a/src/openvic-simulation/Modifier.hpp b/src/openvic-simulation/Modifier.hpp index 6db2c0c..ea302b2 100644 --- a/src/openvic-simulation/Modifier.hpp +++ b/src/openvic-simulation/Modifier.hpp @@ -37,6 +37,9 @@ namespace OpenVic { ModifierValue(ModifierValue const&); ModifierValue(ModifierValue&&); + ModifierValue& operator=(ModifierValue const&); + ModifierValue& operator=(ModifierValue&&); + /* Removes effect entries with a value of zero. */ void trim(); size_t get_effect_count() const; @@ -98,6 +101,8 @@ namespace OpenVic { bool add_modifier(const std::string_view identifier, ModifierValue&& values, Modifier::icon_t icon); IDENTIFIER_REGISTRY_ACCESSORS(Modifier, modifier) - NodeTools::node_callback_t expect_modifier_value(NodeTools::callback_t<ModifierValue&&> callback) const; + bool setup_modifier_effects(); + + NodeTools::node_callback_t expect_modifier_value(NodeTools::callback_t<ModifierValue&&> callback, NodeTools::key_value_callback_t default_callback) const; }; } diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index 6396967..530aa76 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -255,6 +255,21 @@ bool Dataloader::_load_map_dir(GameManager& game_manager, fs::path const& map_di } map.lock_water_provinces(); + if (!map.get_terrain_type_manager().load_terrain_types(game_manager.get_modifier_manager(), _parse_defines(lookup_file(map_directory / terrain_definition)).get_file_node())) { + Logger::error("Failed to load terrain types!"); + ret = false; + } + + if (!map.load_map_images(lookup_file(map_directory / provinces), lookup_file(map_directory / terrain), false)) { + Logger::error("Failed to load map images!"); + ret = false; + } + + if (!map.generate_and_load_province_adjacencies(_parse_csv(lookup_file(map_directory / adjacencies)).get_lines())) { + Logger::error("Failed to generate and load province adjacencies!"); + ret = false; + } + return ret; } @@ -272,6 +287,10 @@ bool Dataloader::load_defines(GameManager& game_manager) const { bool ret = true; + if (!game_manager.get_modifier_manager().setup_modifier_effects()) { + Logger::error("Failed to set up modifier effects!"); + ret = false; + } if (!game_manager.get_good_manager().load_goods_file(_parse_defines(lookup_file(goods_file)).get_file_node())) { Logger::error("Failed to load goods!"); ret = false; diff --git a/src/openvic-simulation/economy/Good.cpp b/src/openvic-simulation/economy/Good.cpp index f1da8bd..8a04e39 100644 --- a/src/openvic-simulation/economy/Good.cpp +++ b/src/openvic-simulation/economy/Good.cpp @@ -112,8 +112,8 @@ bool GoodManager::load_goods_file(ast::NodeCPtr root) { [this, good_category](std::string_view key, ast::NodeCPtr value) -> bool { colour_t colour = NULL_COLOUR; Good::price_t base_price; - bool available_from_start, tradeable = true; - bool money, overseas_penalty = false; + bool available_from_start = true, tradeable = true; + bool money = false, overseas_penalty = false; bool ret = expect_dictionary_keys( "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), diff --git a/src/openvic-simulation/economy/ProductionType.cpp b/src/openvic-simulation/economy/ProductionType.cpp index 4ea7967..40ac2a3 100644 --- a/src/openvic-simulation/economy/ProductionType.cpp +++ b/src/openvic-simulation/economy/ProductionType.cpp @@ -264,10 +264,12 @@ bool ProductionTypeManager::load_production_types_file(GoodManager& good_manager } } - return ret & PARSE_NODE(node) & add_production_type( + ret &= PARSE_NODE(node); + ret &= add_production_type( key, owner, employees, type, workforce, input_goods, output_goods, value, bonuses, efficiency, coastal, farm, mine, good_manager ); + return ret; } )(root); diff --git a/src/openvic-simulation/map/Map.cpp b/src/openvic-simulation/map/Map.cpp index 6cafb57..203e100 100644 --- a/src/openvic-simulation/map/Map.cpp +++ b/src/openvic-simulation/map/Map.cpp @@ -4,6 +4,7 @@ #include <unordered_set> #include "openvic-simulation/economy/Good.hpp" +#include "openvic-simulation/utility/BMP.hpp" #include "openvic-simulation/utility/Logger.hpp" using namespace OpenVic; @@ -182,116 +183,6 @@ Province const* Map::get_selected_province() const { return get_province_by_index(get_selected_province_index()); } -static colour_t colour_at(uint8_t const* colour_data, int32_t idx) { - idx *= 3; - return (colour_data[idx] << 16) | (colour_data[idx + 1] << 8) | colour_data[idx + 2]; -} - -bool Map::generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, - uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors) { - if (!province_shape_image.empty()) { - Logger::error("Province index image has already been generated!"); - return false; - } - if (!provinces.is_locked()) { - Logger::error("Province index image cannot be generated until after provinces are locked!"); - return false; - } - if (new_width < 1 || new_height < 1) { - Logger::error("Invalid province image dimensions: ", new_width, "x", new_height); - return false; - } - if (colour_data == nullptr) { - Logger::error("Province colour data pointer is null!"); - return false; - } - if (terrain_data == nullptr) { - Logger::error("Province terrain data pointer is null!"); - return false; - } - width = new_width; - height = new_height; - province_shape_image.resize(width * height); - - std::vector<bool> province_checklist(provinces.size()); - bool ret = true; - std::unordered_set<colour_t> unrecognised_province_colours, unrecognised_terrain_colours; - - for (int32_t y = 0; y < height; ++y) { - for (int32_t x = 0; x < width; ++x) { - const int32_t idx = x + y * width; - - const colour_t terrain_colour = colour_at(terrain_data, idx); - const terrain_variant_map_t::const_iterator it = terrain_variant_map.find(terrain_colour); - if (it != terrain_variant_map.end()) province_shape_image[idx].terrain = it->second; - else { - if (unrecognised_terrain_colours.find(terrain_colour) == unrecognised_terrain_colours.end()) { - unrecognised_terrain_colours.insert(terrain_colour); - if (detailed_errors) { - Logger::warning("Unrecognised terrain colour ", colour_to_hex_string(terrain_colour), - " at (", x, ", ", y, ")"); - } - } - province_shape_image[idx].terrain = 0; - } - - const colour_t province_colour = colour_at(colour_data, idx); - if (x > 0) { - const int32_t jdx = idx - 1; - if (colour_at(colour_data, jdx) == province_colour) { - province_shape_image[idx].index = province_shape_image[jdx].index; - continue; - } - } - if (y > 0) { - const int32_t jdx = idx - width; - if (colour_at(colour_data, jdx) == province_colour) { - province_shape_image[idx].index = province_shape_image[jdx].index; - continue; - } - } - const Province::index_t index = get_index_from_colour(province_colour); - if (index != Province::NULL_INDEX) { - province_checklist[index - 1] = true; - province_shape_image[idx].index = index; - continue; - } - if (unrecognised_province_colours.find(province_colour) == unrecognised_province_colours.end()) { - unrecognised_province_colours.insert(province_colour); - if (detailed_errors) { - Logger::warning("Unrecognised province colour ", colour_to_hex_string(province_colour), - " at (", x, ", ", y, ")"); - } - } - province_shape_image[idx].index = Province::NULL_INDEX; - } - } - if (!unrecognised_province_colours.empty()) { - Logger::warning("Province image contains ", unrecognised_province_colours.size(), " unrecognised province colours"); - } - if (!unrecognised_terrain_colours.empty()) { - Logger::warning("Terrain image contains ", unrecognised_terrain_colours.size(), " unrecognised terrain colours"); - } - - size_t missing = 0; - for (size_t idx = 0; idx < province_checklist.size(); ++idx) { - if (!province_checklist[idx]) { - if (detailed_errors) { - Logger::error("Province missing from shape image: ", provinces.get_item_by_index(idx)->to_string()); - } - missing++; - } - } - if (missing > 0) { - Logger::error("Province image is missing ", missing, " province colours"); - ret = false; - } - - ret &= _generate_province_adjacencies(); - - return ret; -} - size_t Map::get_width() const { return width; } @@ -304,6 +195,14 @@ std::vector<Map::shape_pixel_t> const& Map::get_province_shape_image() const { return province_shape_image; } +TerrainTypeManager& Map::get_terrain_type_manager() { + return terrain_type_manager; +} + +TerrainTypeManager const& Map::get_terrain_type_manager() const { + return terrain_type_manager; +} + bool Map::add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func) { if (identifier.empty()) { Logger::error("Invalid mapmode identifier - empty!"); @@ -443,7 +342,7 @@ bool Map::load_province_definitions(std::vector<LineObject> const& lines) { [this, &ret](LineObject const& line) -> void { const std::string_view identifier = line.get_value_for(0); if (!identifier.empty()) { - colour_t colour; + colour_t colour = NULL_COLOUR; if (!parse_province_colour(colour, { line.get_value_for(1), line.get_value_for(2), line.get_value_for(3) } )) { @@ -504,6 +403,118 @@ bool Map::load_region_file(ast::NodeCPtr root) { return ret; } +static constexpr colour_t colour_at(uint8_t const* colour_data, int32_t idx) { + idx *= 3; + return (colour_data[idx + 2] << 16) | (colour_data[idx + 1] << 8) | colour_data[idx]; +} + +bool Map::load_map_images(fs::path const& province_path, fs::path const& terrain_path, bool detailed_errors) { + if (!provinces.is_locked()) { + Logger::error("Province index image cannot be generated until after provinces are locked!"); + return false; + } + if (!terrain_type_manager.terrain_type_mappings_are_locked()) { + Logger::error("Province index image cannot be generated until after terrain type mappings are locked!"); + return false; + } + + BMP province_bmp; + if (!(province_bmp.open(province_path) && province_bmp.read_header() && province_bmp.read_pixel_data())) { + Logger::error("Failed to read BMP for compatibility mode province image: ", province_path); + return false; + } + static constexpr uint16_t expected_province_bpp = 24; + if (province_bmp.get_bits_per_pixel() != expected_province_bpp) { + Logger::error("Invalid province BMP bits per pixel: ", province_bmp.get_bits_per_pixel(), " (expected ", expected_province_bpp, ")"); + return false; + } + + BMP terrain_bmp; + if (!(terrain_bmp.open(terrain_path) && terrain_bmp.read_header() && terrain_bmp.read_pixel_data())) { + Logger::error("Failed to read BMP for compatibility mode terrain image: ", terrain_path); + return false; + } + static constexpr uint16_t expected_terrain_bpp = 8; + if (terrain_bmp.get_bits_per_pixel() != expected_terrain_bpp) { + Logger::error("Invalid terrain BMP bits per pixel: ", terrain_bmp.get_bits_per_pixel(), " (expected ", expected_terrain_bpp, ")"); + return false; + } + + if (province_bmp.get_width() != terrain_bmp.get_width() || province_bmp.get_height() != terrain_bmp.get_height()) { + Logger::error("Mismatched province and terrain BMP dims: ", province_bmp.get_width(), "x", province_bmp.get_height(), " vs ", terrain_bmp.get_width(), "x", terrain_bmp.get_height()); + return false; + } + + width = province_bmp.get_width(); + height = province_bmp.get_height(); + province_shape_image.resize(width * height); + + 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()); + bool ret = true; + std::unordered_set<colour_t> unrecognised_province_colours; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + const size_t idx = x + y * width; + + province_shape_image[idx].terrain = terrain_data[idx] < terrain_type_manager.get_terrain_texture_limit() ? terrain_data[idx] + 1 : 0; + + const colour_t province_colour = colour_at(province_data, idx); + if (x > 0) { + const size_t jdx = idx - 1; + if (colour_at(province_data, jdx) == province_colour) { + province_shape_image[idx].index = province_shape_image[jdx].index; + continue; + } + } + if (y > 0) { + const size_t jdx = idx - width; + if (colour_at(province_data, jdx) == province_colour) { + province_shape_image[idx].index = province_shape_image[jdx].index; + continue; + } + } + const Province::index_t index = get_index_from_colour(province_colour); + if (index != Province::NULL_INDEX) { + province_checklist[index - 1] = true; + province_shape_image[idx].index = index; + continue; + } + if (unrecognised_province_colours.find(province_colour) == unrecognised_province_colours.end()) { + unrecognised_province_colours.insert(province_colour); + if (detailed_errors) { + Logger::warning("Unrecognised province colour ", colour_to_hex_string(province_colour), + " at (", x, ", ", y, ")"); + } + } + province_shape_image[idx].index = Province::NULL_INDEX; + } + } + + if (!unrecognised_province_colours.empty()) { + Logger::warning("Province image contains ", unrecognised_province_colours.size(), " unrecognised province colours"); + } + + size_t missing = 0; + for (size_t idx = 0; idx < province_checklist.size(); ++idx) { + if (!province_checklist[idx]) { + if (detailed_errors) { + Logger::error("Province missing from shape image: ", provinces.get_item_by_index(idx)->to_string()); + } + missing++; + } + } + if (missing > 0) { + Logger::error("Province image is missing ", missing, " province colours"); + ret = false; + } + + return ret; +} + /* REQUIREMENTS: * MAP-19, MAP-84 */ @@ -531,3 +542,9 @@ 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 - read additional adjacencies + return ret; +} diff --git a/src/openvic-simulation/map/Map.hpp b/src/openvic-simulation/map/Map.hpp index f81b9c1..99c0bce 100644 --- a/src/openvic-simulation/map/Map.hpp +++ b/src/openvic-simulation/map/Map.hpp @@ -1,12 +1,15 @@ #pragma once +#include <filesystem> #include <functional> #include <openvic-dataloader/csv/LineObject.hpp> #include "openvic-simulation/map/Region.hpp" +#include "openvic-simulation/map/TerrainType.hpp" namespace OpenVic { + namespace fs = std::filesystem; struct Mapmode : HasIdentifier { friend struct Map; @@ -35,14 +38,12 @@ namespace OpenVic { * MAP-4 */ struct Map { - using terrain_t = uint8_t; - using terrain_variant_map_t = std::map<colour_t, terrain_t>; #pragma pack(push, 1) /* Used to represent tightly packed 3-byte integer pixel information. */ struct shape_pixel_t { Province::index_t index; - terrain_t terrain; + TerrainTypeMapping::index_t terrain; }; #pragma pack(pop) private: @@ -52,13 +53,14 @@ namespace OpenVic { IdentifierRegistry<Region> regions; IdentifierRegistry<Mapmode> mapmodes; ProvinceSet water_provinces; + TerrainTypeManager terrain_type_manager; size_t width = 0, height = 0; std::vector<shape_pixel_t> 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 get_index_from_colour(colour_t colour) const; @@ -70,12 +72,10 @@ namespace OpenVic { bool add_province(const std::string_view identifier, colour_t colour); IDENTIFIER_REGISTRY_ACCESSORS(Province, province) IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS(Province, province) + bool set_water_province(const std::string_view identifier); bool set_water_province_list(std::vector<std::string_view> const& list); void lock_water_provinces(); - bool add_region(const std::string_view identifier, std::vector<std::string_view> const& province_identifiers); - IDENTIFIER_REGISTRY_ACCESSORS(Region, region) - IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS(Region, region) Province* get_province_by_index(Province::index_t index); Province const* get_province_by_index(Province::index_t index) const; @@ -86,11 +86,15 @@ namespace OpenVic { Province::index_t get_selected_province_index() const; Province const* get_selected_province() const; - bool generate_province_shape_image(size_t new_width, size_t new_height, uint8_t const* colour_data, - uint8_t const* terrain_data, terrain_variant_map_t const& terrain_variant_map, bool detailed_errors); size_t get_width() const; size_t get_height() const; std::vector<shape_pixel_t> const& get_province_shape_image() const; + TerrainTypeManager& get_terrain_type_manager(); + TerrainTypeManager const& get_terrain_type_manager() const; + + bool add_region(const std::string_view identifier, std::vector<std::string_view> const& province_identifiers); + IDENTIFIER_REGISTRY_ACCESSORS(Region, region) + IDENTIFIER_REGISTRY_NON_CONST_ACCESSORS(Region, region) bool add_mapmode(const std::string_view identifier, Mapmode::colour_func_t colour_func); IDENTIFIER_REGISTRY_ACCESSORS(Mapmode, mapmode) @@ -111,5 +115,7 @@ namespace OpenVic { bool load_province_definitions(std::vector<ovdl::csv::LineObject> const& lines); bool load_province_positions(BuildingManager const& building_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); + bool generate_and_load_province_adjacencies(std::vector<ovdl::csv::LineObject> const& additional_adjacencies); }; } diff --git a/src/openvic-simulation/map/TerrainType.cpp b/src/openvic-simulation/map/TerrainType.cpp new file mode 100644 index 0000000..8464421 --- /dev/null +++ b/src/openvic-simulation/map/TerrainType.cpp @@ -0,0 +1,190 @@ +#include "TerrainType.hpp" + +using namespace OpenVic; +using namespace OpenVic::NodeTools; + +TerrainType::TerrainType(const std::string_view new_identifier, colour_t new_colour, ModifierValue&& new_values, bool new_is_water) + : HasIdentifierAndColour { new_identifier, new_colour, true, false }, ModifierValue { std::move(new_values) }, is_water { new_is_water } {} + +bool TerrainType::get_is_water() const { + return is_water; +} + +TerrainTypeMapping::TerrainTypeMapping(const std::string_view new_identifier, TerrainType const& new_type, + std::vector<index_t>&& new_terrain_indicies, index_t new_priority, bool new_has_texture) + : HasIdentifier { new_identifier }, type { new_type }, terrain_indicies { std::move(new_terrain_indicies) }, + priority { new_priority }, has_texture { new_has_texture } {} + +TerrainType const& TerrainTypeMapping::get_type() const { + return type; +} + +std::vector<TerrainTypeMapping::index_t> const& TerrainTypeMapping::get_terrain_indicies() const { + return terrain_indicies; +} + +TerrainTypeMapping::index_t TerrainTypeMapping::get_priority() const { + return priority; +} + +bool TerrainTypeMapping::get_has_texture() const { + return has_texture; +} + +TerrainTypeManager::TerrainTypeManager() : terrain_types { "terrain types" }, terrain_type_mappings { "terrain type mappings" } {} + +bool TerrainTypeManager::add_terrain_type(const std::string_view identifier, colour_t colour, ModifierValue&& values, bool is_water) { + if (identifier.empty()) { + Logger::error("Invalid terrain type identifier - empty!"); + return false; + } + if (colour > MAX_COLOUR_RGB) { + Logger::error("Invalid terrain type colour for ", identifier, ": ", colour_to_hex_string(colour)); + return false; + } + return terrain_types.add_item({ identifier, colour, std::move(values), is_water }); +} + +bool TerrainTypeManager::add_terrain_type_mapping(const std::string_view identifier, TerrainType const* type, + std::vector<TerrainTypeMapping::index_t>&& terrain_indicies, TerrainTypeMapping::index_t priority, bool has_texture) { + if (!terrain_types.is_locked()) { + Logger::error("Cannot register terrain type mappings until terrain types are locked!"); + return false; + } + if (identifier.empty()) { + Logger::error("Invalid terrain type mapping identifier - empty!"); + return false; + } + if (type == nullptr) { + Logger::error("Null terrain type for mapping ", identifier); + return false; + } + return terrain_type_mappings.add_item({ identifier, *type, std::move(terrain_indicies), priority, has_texture }); +} + +bool TerrainTypeManager::_load_terrain_type_categories(ModifierManager const& modifier_manager, ast::NodeCPtr root) { + const bool ret = expect_dictionary_reserve_length(terrain_types, + [this, &modifier_manager](std::string_view type_key, ast::NodeCPtr type_node) -> bool { + ModifierValue values; + colour_t colour = NULL_COLOUR; + bool is_water = false; + bool has_colour = false, has_is_water = false; + bool ret = modifier_manager.expect_modifier_value(move_variable_callback(values), + [&colour, &has_colour, &is_water, &has_is_water](std::string_view key, ast::NodeCPtr value) -> bool { + if (key == "color") { + if (!has_colour) { + has_colour = true; + return expect_colour(assign_variable_callback(colour))(value); + } else { + Logger::error("Duplicate terrain type colour key!"); + return false; + } + } else if (key == "is_water") { + if (!has_is_water) { + has_is_water = true; + return expect_bool(assign_variable_callback(is_water))(value); + } else { + Logger::error("Duplicate terrain type is_water key!"); + return false; + } + } else { + Logger::error("Invalid terrain type entry key: ", key); + return false; + } + } + )(type_node); + if (!has_colour) { + Logger::error("Terrain type missing color key: ", type_key); + ret = false; + } + ret &= add_terrain_type(type_key, colour, std::move(values), is_water); + return ret; + } + )(root); + terrain_types.lock(); + return ret; +} + +bool TerrainTypeManager::_load_terrain_type_mapping(std::string_view mapping_key, ast::NodeCPtr mapping_value) { + TerrainType const* type = nullptr; + std::vector<TerrainTypeMapping::index_t> terrain_indicies; + TerrainTypeMapping::index_t priority = 0; + bool has_texture = true; + + bool ret = expect_dictionary_keys( + "type", ONE_EXACTLY, expect_terrain_type_identifier(assign_variable_callback_pointer(type)), + "color", ONE_EXACTLY, expect_list_reserve_length(terrain_indicies, expect_uint( + [&terrain_indicies](uint64_t val) -> bool { + if (val <= 1 << 8 * sizeof(TerrainTypeMapping::index_t)) { + TerrainTypeMapping::index_t index = val; + if (std::find(terrain_indicies.begin(), terrain_indicies.end(), index) == terrain_indicies.end()) { + terrain_indicies.push_back(val); + return true; + } + Logger::error("Repeat terrain type mapping index: ", val); + return false; + } + Logger::error("Index too big for terrain type mapping index: ", val); + return false; + } + )), + "priority", ZERO_OR_ONE, expect_uint(assign_variable_callback_uint("terrain type mapping priority", priority)), + "has_texture", ZERO_OR_ONE, expect_bool(assign_variable_callback(has_texture)) + )(mapping_value); + if (has_texture) { + if (++terrain_texture_count == terrain_texture_limit + 1) { + Logger::error("More terrain textures than limit!"); + ret = false; + } + } + ret &= add_terrain_type_mapping(mapping_key, type, std::move(terrain_indicies), priority, has_texture); + return true; +} + +TerrainTypeMapping::index_t TerrainTypeManager::get_terrain_texture_limit() const { + return terrain_texture_limit; +} + +bool TerrainTypeManager::load_terrain_types(ModifierManager const& modifier_manager, ast::NodeCPtr root) { + bool terrain = false, categories = false; + bool ret = expect_dictionary_and_length( + [this](size_t size) -> size_t { + terrain_type_mappings.reserve(size - 2); + return size; + }, + [this, &terrain, &categories, &modifier_manager](std::string_view key, ast::NodeCPtr value) -> bool { + if (key == "terrain") { + if (!terrain) { + terrain = true; + return expect_uint(assign_variable_callback_uint("terrain texture limit", terrain_texture_limit))(value); + } else { + Logger::error("Duplicate terrain key!"); + return false; + } + } else if (key == "categories") { + if (!categories) { + categories = true; + return _load_terrain_type_categories(modifier_manager, value); + } else { + Logger::error("Duplicate categories key!"); + return false; + } + } else if (terrain && categories) { + return _load_terrain_type_mapping(key, value); + } else { + Logger::error("Cannot define terrain type mapping before terrain and categories keys: ", key); + return false; + } + } + )(root); + if (!terrain) { + Logger::error("Missing expected key: \"terrain\""); + ret = false; + } + if (!categories) { + Logger::error("Missing expected key: \"categories\""); + ret = false; + } + terrain_type_mappings.lock(); + return ret; +} diff --git a/src/openvic-simulation/map/TerrainType.hpp b/src/openvic-simulation/map/TerrainType.hpp new file mode 100644 index 0000000..48b811a --- /dev/null +++ b/src/openvic-simulation/map/TerrainType.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "openvic-simulation/Modifier.hpp" + +namespace OpenVic { + struct TerrainTypeManager; + + struct TerrainType : HasIdentifierAndColour, ModifierValue { + friend struct TerrainTypeManager; + + private: + const bool is_water; + + TerrainType(const std::string_view new_identifier, colour_t new_colour, ModifierValue&& new_values, bool new_is_water); + + public: + TerrainType(TerrainType&&) = default; + + bool get_is_water() const; + }; + + struct TerrainTypeMapping : HasIdentifier { + friend struct TerrainTypeManager; + + using index_t = uint8_t; + + private: + TerrainType const& type; + const std::vector<index_t> terrain_indicies; + const index_t priority; + const bool has_texture; + + TerrainTypeMapping(const std::string_view new_identifier, TerrainType const& new_type, std::vector<index_t>&& new_terrain_indicies, index_t new_priority, bool new_has_texture); + + public: + TerrainTypeMapping(TerrainTypeMapping&&) = default; + + TerrainType const& get_type() const; + std::vector<index_t> const& get_terrain_indicies() const; + index_t get_priority() const; + bool get_has_texture() const; + }; + + struct TerrainTypeManager { + private: + IdentifierRegistry<TerrainType> terrain_types; + IdentifierRegistry<TerrainTypeMapping> terrain_type_mappings; + + TerrainTypeMapping::index_t terrain_texture_limit = 0, terrain_texture_count = 0; + + bool _load_terrain_type_categories(ModifierManager const& modifier_manager, ast::NodeCPtr root); + bool _load_terrain_type_mapping(std::string_view key, ast::NodeCPtr value); + + public: + TerrainTypeManager(); + + bool add_terrain_type(const std::string_view identifier, colour_t colour, ModifierValue&& values, bool is_water); + IDENTIFIER_REGISTRY_ACCESSORS(TerrainType, terrain_type) + + bool add_terrain_type_mapping(const std::string_view identifier, TerrainType const* type, + std::vector<TerrainTypeMapping::index_t>&& terrain_indicies, TerrainTypeMapping::index_t priority, bool has_texture); + IDENTIFIER_REGISTRY_ACCESSORS(TerrainTypeMapping, terrain_type_mapping) + + TerrainTypeMapping::index_t get_terrain_texture_limit() const; + + bool load_terrain_types(ModifierManager const& modifier_manager, ast::NodeCPtr root); + }; +} diff --git a/src/openvic-simulation/types/IdentifierRegistry.hpp b/src/openvic-simulation/types/IdentifierRegistry.hpp index 7b61cf8..41e4c6b 100644 --- a/src/openvic-simulation/types/IdentifierRegistry.hpp +++ b/src/openvic-simulation/types/IdentifierRegistry.hpp @@ -241,6 +241,7 @@ namespace OpenVic { #define IDENTIFIER_REGISTRY_ACCESSORS_CUSTOM_PLURAL(type, singular, plural) \ void lock_##plural() { plural.lock(); } \ + bool plural##_are_locked() const { return plural.is_locked(); } \ type const* get_##singular##_by_identifier(const std::string_view identifier) const { \ return plural.get_item_by_identifier(identifier); } \ size_t get_##singular##_count() const { \ diff --git a/src/openvic-simulation/utility/BMP.cpp b/src/openvic-simulation/utility/BMP.cpp index 531870b..2fa9417 100644 --- a/src/openvic-simulation/utility/BMP.cpp +++ b/src/openvic-simulation/utility/BMP.cpp @@ -11,13 +11,12 @@ BMP::~BMP() { close(); } -bool BMP::open(char const* filepath) { +bool BMP::open(fs::path const& filepath) { reset(); - errno = 0; - file = fopen(filepath, "rb"); - if (file == nullptr || errno != 0) { - Logger::error("Failed to open BMP file \"", filepath, "\" (errno = ", errno, ")"); - file = nullptr; + file.open(filepath, std::ios::binary); + if (file.fail()) { + Logger::error("Failed to open BMP file \"", filepath, "\""); + close(); return false; } return true; @@ -28,15 +27,17 @@ bool BMP::read_header() { Logger::error("BMP header already validated!"); return false; } - if (file == nullptr) { + if (!file.is_open()) { Logger::error("Cannot read BMP header before opening a file"); return false; } - if (fseek(file, 0, SEEK_SET) != 0) { + file.seekg(0, std::ios::beg); + if (file.fail()) { Logger::error("Failed to move to the beginning of the BMP file!"); return false; } - if (fread(&header, sizeof(header), 1, file) != 1) { + file.read(reinterpret_cast<char*>(&header), sizeof(header)); + if (file.fail()) { Logger::error("Failed to read BMP header!"); return false; } @@ -117,7 +118,11 @@ bool BMP::read_header() { } bool BMP::read_palette() { - if (file == nullptr) { + if (palette_read) { + Logger::error("BMP palette already read!"); + return false; + } + if (!file.is_open()) { Logger::error("Cannot read BMP palette before opening a file"); return false; } @@ -129,24 +134,25 @@ bool BMP::read_palette() { Logger::error("Cannot read BMP palette - header indicates this file doesn't have one"); return false; } - if (fseek(file, sizeof(header), SEEK_SET) != 0) { + file.seekg(sizeof(header), std::ios::beg); + if (file.fail()) { Logger::error("Failed to move to the palette in the BMP file!"); return false; } palette.resize(palette_size); - if (fread(palette.data(), palette_size * PALETTE_COLOUR_SIZE, 1, file) != 1) { + file.read(reinterpret_cast<char*>(palette.data()), palette_size * PALETTE_COLOUR_SIZE); + if (file.fail()) { Logger::error("Failed to read BMP header!"); palette.clear(); return false; } - return true; + palette_read = true; + return palette_read; } void BMP::close() { - if (file != nullptr) { - if (fclose(file) != 0) - Logger::error("Failed to close BMP!"); - file = nullptr; + if (file.is_open()) { + file.close(); } } @@ -156,8 +162,61 @@ void BMP::reset() { header_validated = false; palette_size = 0; palette.clear(); + pixel_data.clear(); +} + +int32_t BMP::get_width() const { + return header.width_px; +} + +int32_t BMP::get_height() const { + return header.height_px; +} + +uint16_t BMP::get_bits_per_pixel() const { + return header.bits_per_pixel; } std::vector<colour_t> const& BMP::get_palette() const { + if (!palette_read) { + Logger::warning("Trying to get BMP palette before loading"); + } return palette; } + +bool BMP::read_pixel_data() { + if (pixel_data_read) { + Logger::error("BMP pixel data already read!"); + return false; + } + if (!file.is_open()) { + Logger::error("Cannot read BMP pixel data before opening a file"); + return false; + } + if (!header_validated) { + Logger::error("Cannot read pixel data before BMP header is validated!"); + return false; + } + file.seekg(header.offset, std::ios::beg); + if (file.fail()) { + Logger::error("Failed to move to the pixel data in the BMP file!"); + return false; + } + const size_t pixel_data_size = get_width() * get_height() * header.bits_per_pixel / 8; + pixel_data.resize(pixel_data_size); + file.read(reinterpret_cast<char*>(pixel_data.data()), pixel_data_size); + if (file.fail()) { + Logger::error("Failed to read BMP pixel data!"); + pixel_data.clear(); + return false; + } + pixel_data_read = true; + return pixel_data_read; +} + +std::vector<uint8_t> const& BMP::get_pixel_data() const { + if (!pixel_data_read) { + Logger::warning("Trying to get BMP pixel data before loading"); + } + return pixel_data; +} diff --git a/src/openvic-simulation/utility/BMP.hpp b/src/openvic-simulation/utility/BMP.hpp index f04b41a..c08ac99 100644 --- a/src/openvic-simulation/utility/BMP.hpp +++ b/src/openvic-simulation/utility/BMP.hpp @@ -1,11 +1,14 @@ #pragma once -#include <cstdio> +#include <filesystem> +#include <fstream> #include <vector> #include "openvic-simulation/types/Colour.hpp" namespace OpenVic { + namespace fs = std::filesystem; + class BMP { #pragma pack(push) #pragma pack(1) @@ -29,10 +32,11 @@ namespace OpenVic { } header; #pragma pack(pop) - FILE* file = nullptr; - bool header_validated = false; + std::ifstream file; + bool header_validated = false, palette_read = false, pixel_data_read = false; uint32_t palette_size = 0; std::vector<colour_t> palette; + std::vector<uint8_t> pixel_data; public: static constexpr uint32_t PALETTE_COLOUR_SIZE = sizeof(colour_t); @@ -40,12 +44,17 @@ namespace OpenVic { BMP() = default; ~BMP(); - bool open(char const* filepath); + bool open(fs::path const& filepath); bool read_header(); bool read_palette(); + bool read_pixel_data(); void close(); void reset(); + int32_t get_width() const; + int32_t get_height() const; + uint16_t get_bits_per_pixel() const; std::vector<colour_t> const& get_palette() const; + std::vector<uint8_t> const& get_pixel_data() const; }; } |