From 683fa5d9d654061c0ff1ed761dec79a1ecc561f5 Mon Sep 17 00:00:00 2001 From: Idhrendur Date: Sun, 21 Jan 2024 15:31:22 -0800 Subject: [PATCH] Fix army unit conversion (#559) * Add ability to import a military formation. * Add ability to import all formations. * Minor fixes * Actually import military formations. * Put military formations in their owning countries, not in the overall world. * Extract battalion-finding code to its own function. * Update test save * Add method to convert military formations to batallions. * Add mappings for formations * Formatting. * Separate army and navy military formations. * Convert navies. * Fix army locations. * Set fleet names. * Fix broken test * Fix another test * Fix another test. * Disable converting new-style fleets for the moment. * Remove a stray comment. * Update cmake * Rename file * Formatting --- CMakeLists.txt | 4 + Vic3ToHoI4Tests.vcxproj | 2 + Vic3ToHoI4Tests.vcxproj.filters | 9 + Vic3ToHoI4lib.vcxproj | 5 + Vic3ToHoI4lib.vcxproj.filters | 23 +- data/configurables/task_force_templates.txt | 295 +++++++++++++++++- data/configurables/unit_mappings.txt | 66 ++++ .../vic3_world/world/test_save.vic3 | 35 +++ .../hoi4_countries_converter_tests.cpp | 13 +- .../countries/hoi4_country_converter.cpp | 155 ++++++--- .../hoi4_country_converter_tests.cpp | 20 +- src/hoi4_world/military/task_force.h | 1 + src/mappers/unit/unit_mapper.cpp | 40 ++- src/mappers/unit/unit_mapper.h | 11 +- src/out_hoi4/countries/out_country.cpp | 10 +- src/out_hoi4/countries/out_country_tests.cpp | 44 ++- src/vic3_world/countries/vic3_country.h | 25 +- src/vic3_world/military/military_formation.h | 31 ++ .../military/military_formation_importer.cpp | 80 +++++ .../military/military_formation_importer.h | 37 +++ .../military_formation_importer_tests.cpp | 110 +++++++ .../military/military_formations_importer.cpp | 30 ++ .../military/military_formations_importer.h | 22 ++ .../military_formations_importer_tests.cpp | 117 +++++++ src/vic3_world/world/vic3_world_importer.cpp | 55 ++++ .../world/vic3_world_importer_tests.cpp | 47 ++- 26 files changed, 1194 insertions(+), 93 deletions(-) create mode 100644 src/vic3_world/military/military_formation.h create mode 100644 src/vic3_world/military/military_formation_importer.cpp create mode 100644 src/vic3_world/military/military_formation_importer.h create mode 100644 src/vic3_world/military/military_formation_importer_tests.cpp create mode 100644 src/vic3_world/military/military_formations_importer.cpp create mode 100644 src/vic3_world/military/military_formations_importer.h create mode 100644 src/vic3_world/military/military_formations_importer_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7116fb50..8485a44d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,8 @@ set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/institutions/institut set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/interest_groups/interest_group_importer.cpp") set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/interest_groups/interest_groups_importer.cpp") set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/laws/laws_importer.cpp") +set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/military/military_formations_importer.cpp") +set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/military/military_formation_importer.cpp") set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/pacts/pact_importer.cpp") set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/pacts/pacts_importer.cpp") set(CONVERTER_SOURCES ${CONVERTER_SOURCES} "src/vic3_world/provinces/vic3_province_definitions.cpp") @@ -400,6 +402,8 @@ set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/institutions/institutions_impor set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/interest_groups/interest_groups_importer_tests.cpp") set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/interest_groups/interest_group_importer_tests.cpp") set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/laws/laws_importer_tests.cpp") +set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/military/military_formations_importer_tests.cpp") +set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/military/military_formation_importer_tests.cpp") set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/pacts/pacts_importer_tests.cpp") set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/pacts/pact_importer_tests.cpp") set(TEST_SOURCES ${TEST_SOURCES} "src/vic3_world/pacts/pact_test.cpp") diff --git a/Vic3ToHoI4Tests.vcxproj b/Vic3ToHoI4Tests.vcxproj index 7a03203d..a5ce388b 100644 --- a/Vic3ToHoI4Tests.vcxproj +++ b/Vic3ToHoI4Tests.vcxproj @@ -114,6 +114,8 @@ + + diff --git a/Vic3ToHoI4Tests.vcxproj.filters b/Vic3ToHoI4Tests.vcxproj.filters index 64c8fdec..5ae3c62c 100644 --- a/Vic3ToHoI4Tests.vcxproj.filters +++ b/Vic3ToHoI4Tests.vcxproj.filters @@ -180,6 +180,9 @@ {22bef3c9-5e92-4aa9-8e42-801243baefc1} + + {7b80163d-64f7-4031-959c-c160201e684e} + @@ -557,6 +560,12 @@ src\out_hoi4\diplomacy + + src\vic3_world\military + + + src\vic3_world\military + diff --git a/Vic3ToHoI4lib.vcxproj b/Vic3ToHoI4lib.vcxproj index 8d8733d0..c710d178 100644 --- a/Vic3ToHoI4lib.vcxproj +++ b/Vic3ToHoI4lib.vcxproj @@ -164,6 +164,9 @@ + + + @@ -296,6 +299,8 @@ + + diff --git a/Vic3ToHoI4lib.vcxproj.filters b/Vic3ToHoI4lib.vcxproj.filters index 7c6084d1..b419752b 100644 --- a/Vic3ToHoI4lib.vcxproj.filters +++ b/Vic3ToHoI4lib.vcxproj.filters @@ -198,6 +198,9 @@ {f81bdcd3-9fdd-4a81-b245-ab3bae1d4db4} + + {5c7d0789-b5d6-46fe-906f-5e9065413240} + @@ -488,9 +491,6 @@ src\mappers\unit - - src\mappers\unit - src\mappers\ideology @@ -736,6 +736,15 @@ src\vic3_world\wars + + src\vic3_world\military + + + src\vic3_world\military + + + src\vic3_world\military + @@ -1137,10 +1146,16 @@ src\vic3_world\wars + + src\vic3_world\military + + + src\vic3_world\military + external\rakaly - + \ No newline at end of file diff --git a/data/configurables/task_force_templates.txt b/data/configurables/task_force_templates.txt index d2c070fa..62cff21b 100644 --- a/data/configurables/task_force_templates.txt +++ b/data/configurables/task_force_templates.txt @@ -1,3 +1,296 @@ +# Modern battleship group. +task_force = { + cost = { + combat_unit_type_battleship = 20 + combat_unit_type_carrier = 20 + } + ship = { + name = "Battleship" + definition = battleship + equipment = ship_hull_heavy_2 + legacy_equipment = battleship_2 + version = "1936 Battleship" + } + ship = { + name = "Heavy Cruiser" + definition = heavy_cruiser + equipment = ship_hull_cruiser_2 + legacy_equipment = heavy_cruiser_2 + version = "1936 Heavy Cruiser" + } + ship = { + name = "Light Cruiser" + definition = light_cruiser + equipment = ship_hull_cruiser_2 + legacy_equipment = light_cruiser_2 + version = "1936 Light Cruiser" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } +} + +# Heavy cruiser backup. +task_force = { + cost = { + combat_unit_type_battleship = 10 + combat_unit_type_carrier = 10 + } + ship = { + name = "Heavy Cruiser" + definition = heavy_cruiser + equipment = ship_hull_cruiser_2 + legacy_equipment = heavy_cruiser_2 + version = "1936 Heavy Cruiser" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } +} + +# Older battleship group. +task_force = { + cost = { + combat_unit_type_dreadnought = 20 + } + ship = { + name = "Battleship" + definition = battleship + equipment = ship_hull_heavy_1 + legacy_equipment = battleship_1 + version = "Early Battleship" + } + ship = { + name = "Heavy Cruiser" + definition = heavy_cruiser + equipment = ship_hull_cruiser_1 + legacy_equipment = heavy_cruiser_1 + version = "Early Heavy Cruiser" + } + ship = { + name = "Light Cruiser" + definition = light_cruiser + equipment = ship_hull_cruiser_1 + legacy_equipment = light_cruiser_1 + version = "Early Light Cruiser" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } +} + +# Heavy cruiser backup. +task_force = { + cost = { + combat_unit_type_dreadnought = 10 + } + ship = { + name = "Heavy Cruiser" + definition = heavy_cruiser + equipment = ship_hull_cruiser_1 + legacy_equipment = heavy_cruiser_1 + version = "Early Heavy Cruiser" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } +} + +# Modern destroyer flotilla. +task_force = { + cost = { + combat_unit_type_carrier = 10 + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_2 + legacy_equipment = destroyer_2 + version = "1936 Destroyer" + } +} + +# Older destroyers. +task_force = { + cost = { + combat_unit_type_destroyer = 10 + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } + ship = { + name = "Destroyer" + definition = destroyer + equipment = ship_hull_light_1 + legacy_equipment = destroyer_1 + version = "Early Destroyer" + } +} + + +# More modern submarines. +task_force = { + cost = { + combat_unit_type_submarine = 10 + } + ship = { + name = "Submarine" + definition = submarine + equipment = ship_hull_submarine_2 + legacy_equipment = submarine_2 + version = "1936 Submarine" + } + ship = { + name = "Submarine" + definition = submarine + equipment = ship_hull_submarine_2 + legacy_equipment = submarine_2 + version = "1936 Submarine" + } + ship = { + name = "Submarine" + definition = submarine + equipment = ship_hull_submarine_2 + legacy_equipment = submarine_2 + version = "1936 Submarine" + } + ship = { + name = "Submarine" + definition = submarine + equipment = ship_hull_submarine_2 + legacy_equipment = submarine_2 + version = "1936 Submarine" + } +} + + +### Pre 1.5 + # Modern battleship group. task_force = { cost = { @@ -362,4 +655,4 @@ task_force = { legacy_equipment = submarine_1 version = "Early Submarine" } -} +} \ No newline at end of file diff --git a/data/configurables/unit_mappings.txt b/data/configurables/unit_mappings.txt index 85211213..90832a49 100644 --- a/data/configurables/unit_mappings.txt +++ b/data/configurables/unit_mappings.txt @@ -1,5 +1,71 @@ ## Army units. +# Napoleonic infantry doesn't convert. +combat_unit_type_irregular_infantry = {} +combat_unit_type_line_infantry = {} + +# Rifled muskets become bad light infantry. +combat_unit_type_skirmish_infantry = { + infantry = 0.4 + cavalry = 0.05 +} + +# Machine guns improve equipment and shoot cavalry. +combat_unit_type_trench_infantry = { + infantry = 0.45 + equipment_scale = 25 +} + +# Great War infantry has actual guns. +combat_unit_type_squad_infantry = { + infantry = 0.35 + artillery_brigade = 0.1 +} +# Early thirties infantry is mechanised. +combat_unit_type_mechanized_infantry = { + infantry = 0.35 + motorized = 0.1 +} + +pm_no_artillery = {} +combat_unit_type_cannon_artillery = {} +combat_unit_type_mobile_artillery = { + artillery_brigade = 0.05 +} +combat_unit_type_shrapnel_artillery = { + artillery_brigade = 0.1 +} +combat_unit_type_siege_artillery = { + artillery = 0.05 + artillery_brigade = 0.15 +} +combat_unit_type_heavy_tank = { + motorized = 0.1 + light_tank = 0.2 +} + +combat_unit_type_hussars = { + cavalry = 0.2 +} +combat_unit_type_dragoons = { + cavalry = 0.3 +} +combat_unit_type_cuirassiers = { + cavalry = 0.3 +} +combat_unit_type_lancers = { + cavalry = 0.4 +} +combat_unit_type_light_tanks = { + motorized = 0.1 + light_tank = 0.1 +} + +pm_no_organization = {} +pm_general_training = {} + +## Pre-1.5 conversions + # 100 max-modernized Vicky barracks levels convert to: # 45 infantry # 10 motorized diff --git a/data/test_files/vic3_world/world/test_save.vic3 b/data/test_files/vic3_world/world/test_save.vic3 index 40dd5b84..fe2a749c 100644 --- a/data/test_files/vic3_world/world/test_save.vic3 +++ b/data/test_files/vic3_world/world/test_save.vic3 @@ -89,6 +89,41 @@ interest_groups={ } } } +military_formation_manager={ + database={ +1234={ + country=1 + type=army + name="Formation Name" + ordinal_number=2 + position={ 4743.02784 910.80912 } + organization=25 + current_location={ + type=hq + identity=50331767 + } + target_location={ + type=hq + identity=50331767 + } + building_to_expected_units_map={ +8061={ + unit_types_num_list={ +combat_unit_type_shrapnel_artillery=2 combat_unit_type_dragoons=3 combat_unit_type_trench_infantry=8 combat_unit_type_siege_artillery=2 } + } 11850={ + unit_types_num_list={ +combat_unit_type_skirmish_infantry=8 } + } 50334733={ + unit_types_num_list={ +combat_unit_type_trench_infantry=16 } + } } +} +5678={ + country=3 + type=fleet +} + } +} country_rankings={ average_prestige=170 highest_prestige=5544 diff --git a/src/hoi4_world/countries/hoi4_countries_converter_tests.cpp b/src/hoi4_world/countries/hoi4_countries_converter_tests.cpp index c7fcaade..3a94789a 100644 --- a/src/hoi4_world/countries/hoi4_countries_converter_tests.cpp +++ b/src/hoi4_world/countries/hoi4_countries_converter_tests.cpp @@ -167,6 +167,7 @@ TEST(Hoi4worldCountriesCountriesConverter, CountriesAreConverted) }; const std::vector expected_task_forces = { TaskForce{ + .name = "1. Fleet", .ships = {Ship("Test Ship 1", "test_ship", "mtg_test_ship", "legacy_test_ship", "Test Ship Variant One")}, .location = 10, }, @@ -179,7 +180,8 @@ TEST(Hoi4worldCountriesCountriesConverter, CountriesAreConverted) EXPECT_THAT(countries, testing::ElementsAre(testing::Pair("TAG", - Country(CountryOptions{.tag = "TAG", + Country(CountryOptions{ + .tag = "TAG", .color = commonItems::Color{std::array{1, 2, 3}}, .capital_state = 10, .ideology = "neutrality", @@ -195,9 +197,11 @@ TEST(Hoi4worldCountriesCountriesConverter, CountriesAreConverted) .starting_research_slots = 3, .units = {}, .convoys = 100, - .task_forces = expected_task_forces})), + .task_forces = expected_task_forces, + })), testing::Pair("TWO", - Country(CountryOptions{.tag = "TWO", + Country(CountryOptions{ + .tag = "TWO", .color = commonItems::Color{std::array{2, 4, 6}}, .capital_state = 20, .ideology = "democratic", @@ -212,7 +216,8 @@ TEST(Hoi4worldCountriesCountriesConverter, CountriesAreConverted) .starting_research_slots = 3, .units = {}, .convoys = 11, - .task_forces = {}})))); + .task_forces = {}, + })))); } } // namespace hoi4 diff --git a/src/hoi4_world/countries/hoi4_country_converter.cpp b/src/hoi4_world/countries/hoi4_country_converter.cpp index b7332110..9504c17a 100644 --- a/src/hoi4_world/countries/hoi4_country_converter.cpp +++ b/src/hoi4_world/countries/hoi4_country_converter.cpp @@ -284,7 +284,9 @@ std::map makeNavalBaseMap(const std::vector& states) return naval_base_locations; } + std::vector ConvertNavies(const std::string& tag, + const std::map& naval_formations, const vic3::Buildings& buildings, const std::vector& task_force_templates, const std::vector& active_ship_variants, @@ -299,6 +301,7 @@ std::vector ConvertNavies(const std::string& tag, extractActiveItems(active_legacy_ship_variants, active_variants); const auto naval_base_locations = makeNavalBaseMap(states.states); + int num_fleets = 1; for (const auto& [vic3_id, hoi4_id]: states.vic3_state_ids_to_hoi4_state_ids) { const auto itr = states.hoi4_state_ids_to_owner.find(hoi4_id); @@ -310,14 +313,14 @@ std::vector ConvertNavies(const std::string& tag, { continue; } - const auto navalbase = buildings.GetBuildingInState(vic3_id, vic3::BuildingType::NavalBase); - if (!navalbase.has_value()) + const auto naval_base = buildings.GetBuildingInState(vic3_id, vic3::BuildingType::NavalBase); + if (!naval_base.has_value()) { continue; } - for (const auto& pm: navalbase->GetProductionMethods()) + for (const auto& pm: naval_base->GetProductionMethods()) { - pm_amounts[pm] += navalbase->GetStaffingLevel(); + pm_amounts[pm] += naval_base->GetStaffingLevel(); } if (!naval_base_locations.contains(hoi4_id)) @@ -325,7 +328,9 @@ std::vector ConvertNavies(const std::string& tag, continue; } - hoi4::TaskForce task_force{.ships = {}, .location = naval_base_locations.at(hoi4_id)}; + hoi4::TaskForce task_force{.name = fmt::format("{}. Fleet", num_fleets), + .ships = {}, + .location = naval_base_locations.at(hoi4_id)}; for (const auto& tmpl: task_force_templates) { if (!tmpl.AllVariantsActive(active_variants)) @@ -337,33 +342,61 @@ std::vector ConvertNavies(const std::string& tag, if (!task_force.ships.empty()) { forces.push_back(task_force); - } - } + ++num_fleets; + } + } + + // Disable converting navies until naval bases can be converted + // Without naval bases set, navies crash hoi4 + // for (const vic3::MilitaryFormation& naval_formation: naval_formations | std::views::values) + //{ + // for (const auto& [ship_type, number]: naval_formation.units) + // { + // pm_amounts[ship_type] += number; + // } + + // hoi4::TaskForce task_force; + // if (naval_formation.name) + // { + // task_force.name = *naval_formation.name; + // } + // else if (naval_formation.ordinal_number) + // { + // task_force.name = fmt::format("{}. Fleet", *naval_formation.ordinal_number); + // } + // else + // { + // task_force.name = fmt::format("{}. Fleet", num_fleets); + // } + + // for (const auto& task_force_template: task_force_templates) + // { + // if (!task_force_template.AllVariantsActive(active_variants)) + // { + // continue; + // } + // task_force_template.AddShipsIfPossible(task_force.ships, ship_names, pm_amounts); + // } + // if (!task_force.ships.empty()) + // { + // forces.push_back(task_force); + // ++num_fleets; + // } + //} + return forces; } -std::vector ConvertArmies(const std::string& tag, - const mappers::UnitMapper& unit_mapper, - const vic3::Buildings& buildings, - const std::vector& division_templates, +std::vector DetermineBattalions(const std::string& tag, + const std::map& military_formations, const hoi4::States& states, - const std::optional& capital_state) + const vic3::Buildings& buildings, + const mappers::UnitMapper& unit_mapper) { std::vector battalions; - std::vector units; - int default_location = 11666; // Vienna. - if (capital_state.has_value()) - { - auto cap = *capital_state; - if (cap < states.states.size()) - { - if (!states.states[cap].GetProvinces().empty()) - { - default_location = *states.states[cap].GetProvinces().begin(); - } - } - } + + // Pre 1.5 for (const auto& [vic3_id, hoi4_id]: states.vic3_state_ids_to_hoi4_state_ids) { const auto itr = states.hoi4_state_ids_to_owner.find(hoi4_id); @@ -380,30 +413,70 @@ std::vector ConvertArmies(const std::string& tag, { continue; } - auto current = unit_mapper.MakeBattalions(barracks->GetProductionMethods(), barracks->GetStaffingLevel()); - const auto& provs = states.states[hoi4_id - 1].GetProvinces(); - auto pitr = provs.begin(); + auto current = + unit_mapper.MakeBattalions(barracks->GetProductionMethods(), static_cast(barracks->GetStaffingLevel())); + const auto& provinces = states.states[hoi4_id - 1].GetProvinces(); + auto province_itr = provinces.begin(); for (auto& b: current) { - b.SetLocation(*pitr); - if (pitr++ == provs.end()) + b.SetLocation(*province_itr); + if (province_itr++ == provinces.end()) { - pitr = provs.begin(); + province_itr = provinces.begin(); } } battalions.insert(battalions.end(), current.begin(), current.end()); } - if (battalions.empty()) + for (const auto& formation: military_formations | std::views::values) { - return units; + auto current = unit_mapper.MakeBattalions(formation); + battalions.insert(battalions.end(), current.begin(), current.end()); } - // Sort by decreasing equipment. - std::sort(battalions.begin(), battalions.end(), [](const hoi4::Battalion& one, const hoi4::Battalion& two) { + std::ranges::sort(battalions, [](const hoi4::Battalion& one, const hoi4::Battalion& two) { return one.GetEquipmentScale() > two.GetEquipmentScale(); }); + return battalions; +} + + +std::vector ConvertArmies(const std::string& tag, + const mappers::UnitMapper& unit_mapper, + const std::map& military_formations, + const vic3::Buildings& buildings, + const std::vector& division_templates, + const hoi4::States& states, + const std::optional& capital_state) +{ + std::vector units; + + std::vector battalions = + DetermineBattalions(tag, military_formations, states, buildings, unit_mapper); + if (battalions.empty()) + { + return units; + } + + int default_location = 11666; // Vienna. + if (capital_state.has_value()) + { + if (const int capital_number = *capital_state; capital_number < states.states.size()) + { + const hoi4::State& capital = states.states[capital_number - 1]; + const std::map& victory_points = capital.GetVictoryPoints(); + if (!victory_points.empty()) + { + default_location = victory_points.begin()->first; + } + else + { + default_location = *capital.GetProvinces().begin(); + } + } + } + while (!battalions.empty()) { std::optional unit; @@ -422,7 +495,7 @@ std::vector ConvertArmies(const std::string& tag, { break; } - }; + } return units; } @@ -743,9 +816,15 @@ std::optional hoi4::ConvertCountry(const vic3::World& source_worl const std::vector& active_plane_variants = DetermineActiveVariants(all_plane_variants, technologies); const std::vector& active_tank_variants = DetermineActiveVariants(all_tank_variants, technologies); - auto units = - ConvertArmies(*tag, unit_mapper, source_world.GetBuildings(), division_templates, states, capital_state); + auto units = ConvertArmies(*tag, + unit_mapper, + source_country.GetArmyFormations(), + source_world.GetBuildings(), + division_templates, + states, + capital_state); auto task_forces = ConvertNavies(*tag, + source_country.GetNavyFormations(), source_world.GetBuildings(), task_force_templates, active_ship_variants, diff --git a/src/hoi4_world/countries/hoi4_country_converter_tests.cpp b/src/hoi4_world/countries/hoi4_country_converter_tests.cpp index 36ba0086..a46d28c3 100644 --- a/src/hoi4_world/countries/hoi4_country_converter_tests.cpp +++ b/src/hoi4_world/countries/hoi4_country_converter_tests.cpp @@ -2440,17 +2440,23 @@ TEST(Hoi4worldCountriesCountryConverter, NaviesConvert) dummy_characters, dummy_culture_queues); - ASSERT_TRUE(country_one.has_value()); - ASSERT_TRUE(country_two.has_value()); - EXPECT_THAT(country_one->GetTaskForces(), + EXPECT_THAT(country_one.value_or(Country({})).GetTaskForces(), testing::UnorderedElementsAre(TaskForce{ - .ships = {Ship("Cruiser 1", "basic_ship", "mtg_basic_ship", "legacy_basic_ship", "Basic Ship"), - Ship("Cruiser 2", "basic_ship", "mtg_basic_ship", "legacy_basic_ship", "Basic Ship")}, + .name = "1. Fleet", + .ships = + { + Ship("Cruiser 1", "basic_ship", "mtg_basic_ship", "legacy_basic_ship", "Basic Ship"), + Ship("Cruiser 2", "basic_ship", "mtg_basic_ship", "legacy_basic_ship", "Basic Ship"), + }, .location = 1, })); - EXPECT_THAT(country_two->GetTaskForces(), + EXPECT_THAT(country_two.value_or(Country({})).GetTaskForces(), testing::UnorderedElementsAre(TaskForce{ - .ships = {Ship("Battleship 1", "1936_ship", "mtg_1936_ship", "legacy_1936_ship", "1936 Ship")}, + .name = "1. Fleet", + .ships = + { + Ship("Battleship 1", "1936_ship", "mtg_1936_ship", "legacy_1936_ship", "1936 Ship"), + }, .location = 3, })); } diff --git a/src/hoi4_world/military/task_force.h b/src/hoi4_world/military/task_force.h index 605598a9..2b0271f4 100644 --- a/src/hoi4_world/military/task_force.h +++ b/src/hoi4_world/military/task_force.h @@ -11,6 +11,7 @@ namespace hoi4 struct TaskForce { + std::string name; std::vector ships; int location; diff --git a/src/mappers/unit/unit_mapper.cpp b/src/mappers/unit/unit_mapper.cpp index d2a612b3..18751917 100644 --- a/src/mappers/unit/unit_mapper.cpp +++ b/src/mappers/unit/unit_mapper.cpp @@ -5,21 +5,26 @@ #include "external/fmt/include/fmt/format.h" + + std::set mappers::UnitMapper::warned_; + namespace { void WarnForMissingMapping(const std::string& pm, std::set& warned) { - if (warned.find(pm) == warned.end()) + if (warned.contains(pm)) { Log(LogLevel::Warning) << fmt::format("Missing unit mapping rule for {}", pm); } warned.insert(pm); } + } // namespace + std::vector mappers::UnitMapper::MakeBattalions(const std::vector& methods, int scale) const { @@ -37,14 +42,43 @@ std::vector mappers::UnitMapper::MakeBattalions(const std::vect equip += itr->second.equipment; for (const auto& [ut, str]: itr->second.units) { - current[ut] += str * scale; + current[ut] += str * static_cast(scale); } } std::vector units; for (const auto& [ut, str]: current) { - units.emplace_back(hoi4::Battalion(ut, equip, str)); + units.emplace_back(ut, equip, str); } return units; } + + +std::vector mappers::UnitMapper::MakeBattalions(const vic3::MilitaryFormation& formation) const +{ + int equip = 0; + BattalionMap current; + for (const auto& [unit_type, amount]: formation.units) + { + const auto& itr = templates_.find(unit_type); + if (itr == templates_.end()) + { + WarnForMissingMapping(unit_type, warned_); + continue; + } + + equip += itr->second.equipment; + for (const auto& [ut, str]: itr->second.units) + { + current[ut] += str * static_cast(amount); + } + } + + std::vector units; + for (const auto& [ut, str]: current) + { + units.emplace_back(ut, equip, str); + } + return units; +} \ No newline at end of file diff --git a/src/mappers/unit/unit_mapper.h b/src/mappers/unit/unit_mapper.h index a682f1ca..c353d9e9 100644 --- a/src/mappers/unit/unit_mapper.h +++ b/src/mappers/unit/unit_mapper.h @@ -6,18 +6,23 @@ #include #include "src/hoi4_world/military/battalion.h" +#include "src/vic3_world/military/military_formation.h" + + namespace mappers { using BattalionMap = std::map; + struct BattalionTemplate { int equipment; BattalionMap units; }; + using TemplateMap = std::map; class UnitMapper @@ -25,7 +30,8 @@ class UnitMapper public: explicit UnitMapper(TemplateMap& templates): templates_(std::move(templates)) {} - std::vector MakeBattalions(const std::vector& methods, int scale) const; + [[nodiscard]] std::vector MakeBattalions(const std::vector& methods, int scale) const; + [[nodiscard]] std::vector MakeBattalions(const vic3::MilitaryFormation& formation) const; private: TemplateMap templates_; @@ -35,4 +41,5 @@ class UnitMapper } // namespace mappers -#endif // SRC_MAPPERS_UNIT_UNITMAPPER_H + +#endif // SRC_MAPPERS_UNIT_UNITMAPPER_H \ No newline at end of file diff --git a/src/out_hoi4/countries/out_country.cpp b/src/out_hoi4/countries/out_country.cpp index 66d5f139..935e0c71 100644 --- a/src/out_hoi4/countries/out_country.cpp +++ b/src/out_hoi4/countries/out_country.cpp @@ -26,11 +26,11 @@ constexpr std::string_view kShip = " version_name = \"{}\" }} }} }}\n"; constexpr std::string_view kNavyHeader = "\tfleet = {{\n" - "\t\tname = \"{0} Fleet\"\n" - "\t\tnaval_base = {0}\n" + "\t\tname = \"{0}\"\n" + "\t\tnaval_base = {1}\n" "\t\ttask_force = {{\n" - "\t\t\tname = \"{0} Squadron\"\n" - "\t\t\tlocation = {0}\n"; + "\t\t\tname = \"{0}\"\n" + "\t\t\tlocation = {1}\n"; constexpr std::string_view kNavyFooter = "\t\t}\n" "\t}\n"; @@ -295,7 +295,7 @@ void outputNavy(std::ofstream& navy, for (const auto& task_force: task_forces) { - auto header = fmt::format(kNavyHeader, task_force.location); + const std::string header = fmt::format(kNavyHeader, task_force.name, task_force.location); navy << header; legacy << header; for (const auto& ship: task_force.ships) diff --git a/src/out_hoi4/countries/out_country_tests.cpp b/src/out_hoi4/countries/out_country_tests.cpp index 8a72dc94..88a79212 100644 --- a/src/out_hoi4/countries/out_country_tests.cpp +++ b/src/out_hoi4/countries/out_country_tests.cpp @@ -810,18 +810,30 @@ TEST(Outhoi4CountriesOutcountryTests, ShipsAreOutputInBothFormats) .tag = "TAG", .task_forces = { - {.ships = {hoi4::Ship("Test Ship", - "test_ship_type", - "mtg_equipment_template", - "legacy_equipment_template", - "Test Class")}, - .location = 123}, - {.ships = {hoi4::Ship("Test Ship 2", - "another_ship_type", - "mtg_equipment_template_2", - "legacy_equipment_template_2", - "Another Class")}, - .location = 456}, + { + .name = "123 Fleet", + .ships = + { + hoi4::Ship("Test Ship", + "test_ship_type", + "mtg_equipment_template", + "legacy_equipment_template", + "Test Class"), + }, + .location = 123, + }, + { + .name = "456 Fleet", + .ships = + { + hoi4::Ship("Test Ship 2", + "another_ship_type", + "mtg_equipment_template_2", + "legacy_equipment_template_2", + "Another Class"), + }, + .location = 456, + }, }, }); OutputCountryNavy("ShipsAreOutputInBothFormats", country); @@ -842,7 +854,7 @@ TEST(Outhoi4CountriesOutcountryTests, ShipsAreOutputInBothFormats) "\t\tname = \"123 Fleet\"\n" "\t\tnaval_base = 123\n" "\t\ttask_force = {\n" - "\t\t\tname = \"123 Squadron\"\n" + "\t\t\tname = \"123 Fleet\"\n" "\t\t\tlocation = 123\n" "\t\t\tship = {" " name = \"Test Ship\"" @@ -857,7 +869,7 @@ TEST(Outhoi4CountriesOutcountryTests, ShipsAreOutputInBothFormats) "\t\tname = \"456 Fleet\"\n" "\t\tnaval_base = 456\n" "\t\ttask_force = {\n" - "\t\t\tname = \"456 Squadron\"\n" + "\t\t\tname = \"456 Fleet\"\n" "\t\t\tlocation = 456\n" "\t\t\tship = {" " name = \"Test Ship 2\"" @@ -888,7 +900,7 @@ TEST(Outhoi4CountriesOutcountryTests, ShipsAreOutputInBothFormats) "\t\tname = \"123 Fleet\"\n" "\t\tnaval_base = 123\n" "\t\ttask_force = {\n" - "\t\t\tname = \"123 Squadron\"\n" + "\t\t\tname = \"123 Fleet\"\n" "\t\t\tlocation = 123\n" "\t\t\tship = {" " name = \"Test Ship\"" @@ -903,7 +915,7 @@ TEST(Outhoi4CountriesOutcountryTests, ShipsAreOutputInBothFormats) "\t\tname = \"456 Fleet\"\n" "\t\tnaval_base = 456\n" "\t\ttask_force = {\n" - "\t\t\tname = \"456 Squadron\"\n" + "\t\t\tname = \"456 Fleet\"\n" "\t\t\tlocation = 456\n" "\t\t\tship = {" " name = \"Test Ship 2\"" diff --git a/src/vic3_world/countries/vic3_country.h b/src/vic3_world/countries/vic3_country.h index b616e558..db4478e7 100644 --- a/src/vic3_world/countries/vic3_country.h +++ b/src/vic3_world/countries/vic3_country.h @@ -8,12 +8,17 @@ #include "external/commonItems/Color.h" #include "external/commonItems/Date.h" #include "src/vic3_world/institutions/institution.h" +#include "src/vic3_world/military/military_formation.h" + + namespace vic3 { + // need to extra-forward-declare this because World uses Country, and Country uses World class World; + /// /// tax level, salary level, etc. /// @@ -26,6 +31,7 @@ enum class BudgetLevel VeryHigh }; + struct CountryOptions { int number = 0; @@ -49,8 +55,11 @@ struct CountryOptions BudgetLevel tax_level; BudgetLevel salary_level; BudgetLevel mil_salary_level; + std::map army_formations; + std::map navy_formations; }; + enum class RankCategory { GreatPower, @@ -85,7 +94,9 @@ class Country legitimacy_(options.legitimacy), tax_level_(options.tax_level), salary_level_(options.salary_level), - mil_salary_level_(options.mil_salary_level) + mil_salary_level_(options.mil_salary_level), + army_formations_(options.army_formations), + navy_formations_(options.navy_formations) { } @@ -113,6 +124,8 @@ class Country [[nodiscard]] BudgetLevel GetTaxLevel() const { return tax_level_; } [[nodiscard]] BudgetLevel GetGovernmentSalaryLevel() const { return salary_level_; } [[nodiscard]] BudgetLevel GetMilitarySalaryLevel() const { return mil_salary_level_; } + [[nodiscard]] const std::map& GetArmyFormations() const { return army_formations_; } + [[nodiscard]] const std::map& GetNavyFormations() const { return navy_formations_; } void SetActiveLaws(std::set active_laws) { active_laws_ = std::move(active_laws); } void SetLastElection(date last_election) { last_election_ = last_election; } @@ -121,6 +134,14 @@ class Country void AddInterestGroupId(int ig_id) { ig_ids_.push_back(ig_id); } void AddPuppet(int puppet) { puppets_.insert(puppet); } void AddOverlord(int overlord) { overlord_ = overlord; } + void SetArmyFormations(const std::map& military_formations) + { + army_formations_ = military_formations; + } + void SetNavyFormations(const std::map& military_formations) + { + navy_formations_ = military_formations; + } [[nodiscard]] std::set GetAcquiredTechnologies(const vic3::World& world) const; [[nodiscard]] RankCategory GetCountryRankCategory(const vic3::World& world) const; @@ -150,6 +171,8 @@ class Country BudgetLevel tax_level_; BudgetLevel salary_level_; BudgetLevel mil_salary_level_; + std::map army_formations_; + std::map navy_formations_; }; } // namespace vic3 diff --git a/src/vic3_world/military/military_formation.h b/src/vic3_world/military/military_formation.h new file mode 100644 index 00000000..587c16f4 --- /dev/null +++ b/src/vic3_world/military/military_formation.h @@ -0,0 +1,31 @@ +#ifndef SRC_VIC3WORLD_MILITARY_MILITARYFORMATION_H +#define SRC_VIC3WORLD_MILITARY_MILITARYFORMATION_H + + + +namespace vic3 +{ + +enum class MilitaryFormationType +{ + kArmy, + kFleet +}; + + +struct MilitaryFormation +{ + int country; + MilitaryFormationType type; + std::optional name; + std::optional ordinal_number; + std::map units; + + std::strong_ordering operator<=>(const MilitaryFormation&) const = default; +}; + +} // namespace vic3 + + + +#endif // SRC_VIC3WORLD_MILITARY_MILITARYFORMATION_H \ No newline at end of file diff --git a/src/vic3_world/military/military_formation_importer.cpp b/src/vic3_world/military/military_formation_importer.cpp new file mode 100644 index 00000000..ff61c14b --- /dev/null +++ b/src/vic3_world/military/military_formation_importer.cpp @@ -0,0 +1,80 @@ +#include "src/vic3_world/military/military_formation_importer.h" + +#include "external/commonItems/CommonRegexes.h" +#include "external/commonItems/ParserHelpers.h" + + + +vic3::MilitaryFormationImporter::MilitaryFormationImporter() +{ + military_formation_parser_.registerKeyword("country", [this](std::istream& input) { + country_ = commonItems::getInt(input); + }); + military_formation_parser_.registerKeyword("type", [this](std::istream& input) { + const std::string type_string = commonItems::getString(input); + if (type_string == "army") + { + type_ = MilitaryFormationType::kArmy; + } + else if (type_string == "fleet") + { + type_ = MilitaryFormationType::kFleet; + } + }); + military_formation_parser_.registerKeyword("name", [this](std::istream& input) { + const std::string possible_name = commonItems::getString(input); + if (!possible_name.empty()) + { + name_ = possible_name; + } + }); + military_formation_parser_.registerKeyword("ordinal_number", [this](std::istream& input) { + ordinal_number_ = commonItems::getInt(input); + }); + military_formation_parser_.registerKeyword("building_to_expected_units_map", [this](std::istream& input) { + building_to_expected_unit_parser_.parseStream(input); + }); + military_formation_parser_.IgnoreUnregisteredItems(); + + building_to_expected_unit_parser_.registerRegex(commonItems::integerRegex, + [this](const std::string& _, std::istream& input) { + building_parser_.parseStream(input); + }); + + building_parser_.registerKeyword("unit_types_num_list", [this](std::istream& input) { + for (const auto& [unit_type, number_string]: commonItems::assignments{input}.getAssignments()) + { + try + { + units_[unit_type] += std::stoi(number_string); + } + catch (...) + { + continue; + } + } + }); +} + + +std::optional vic3::MilitaryFormationImporter::ImportMilitaryFormation(std::istream& input) +{ + country_.reset(); + type_ = MilitaryFormationType::kArmy; + name_.reset(); + ordinal_number_.reset(); + units_.clear(); + + military_formation_parser_.parseStream(input); + + if (country_.has_value()) + { + return MilitaryFormation{.country = *country_, + .type = type_, + .name = name_, + .ordinal_number = ordinal_number_, + .units = units_}; + } + + return std::nullopt; +} \ No newline at end of file diff --git a/src/vic3_world/military/military_formation_importer.h b/src/vic3_world/military/military_formation_importer.h new file mode 100644 index 00000000..98aa0e47 --- /dev/null +++ b/src/vic3_world/military/military_formation_importer.h @@ -0,0 +1,37 @@ +#ifndef SRC_VIC3WORLD_MILITARY_MILITARYFORMATIONIMPORTER_H +#define SRC_VIC3WORLD_MILITARY_MILITARYFORMATIONIMPORTER_H + + + +#include "external/commonItems/Parser.h" +#include "src/vic3_world/military/military_formation.h" + + + +namespace vic3 +{ + +class MilitaryFormationImporter +{ + public: + MilitaryFormationImporter(); + std::optional ImportMilitaryFormation(std::istream& input); + + private: + std::optional country_; + MilitaryFormationType type_; + std::optional name_; + std::optional ordinal_number_; + std::map units_; + + commonItems::parser military_formation_parser_; + commonItems::parser building_to_expected_unit_parser_; + commonItems::parser building_parser_; + commonItems::parser unit_types_num_list_parser_; +}; + +} // namespace vic3 + + + +#endif // SRC_VIC3WORLD_MILITARY_MILITARYFORMATIONIMPORTER_H \ No newline at end of file diff --git a/src/vic3_world/military/military_formation_importer_tests.cpp b/src/vic3_world/military/military_formation_importer_tests.cpp new file mode 100644 index 00000000..6d0d4a31 --- /dev/null +++ b/src/vic3_world/military/military_formation_importer_tests.cpp @@ -0,0 +1,110 @@ +#include +#include + +#include "external/commonItems/external/googletest/googlemock/include/gmock/gmock-matchers.h" +#include "external/commonItems/external/googletest/googletest/include/gtest/gtest.h" +#include "src/vic3_world/military/military_formation_importer.h" + +namespace vic3 +{ + +TEST(Vic3worldMilitaryMilitaryFormationImporter, NoCountryNumberMeanNoImport) +{ + MilitaryFormationImporter importer; + + std::stringstream input; + const std::optional military_formation = importer.ImportMilitaryFormation(input); + + EXPECT_FALSE(military_formation.has_value()); +} + + +TEST(Vic3worldMilitaryMilitaryFormationImporter, DefaultsAreDefaulted) +{ + MilitaryFormationImporter importer; + + std::stringstream input; + input << "={\n"; + input << " country=12345\n"; + input << "}\n"; + const std::optional military_formation = importer.ImportMilitaryFormation(input); + + EXPECT_TRUE(military_formation.has_value()); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).type, MilitaryFormationType::kArmy); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).country, 12345); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).name, std::nullopt); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).ordinal_number, std::nullopt); + EXPECT_TRUE(military_formation.value_or(MilitaryFormation{}).units.empty()); +} + + +TEST(Vic3worldMilitaryMilitaryFormationImporter, ItemsCanBeImported) +{ + MilitaryFormationImporter importer; + + std::stringstream input; + input << "={\n"; + input << " country=12345\n"; + input << " type=army\n"; + input << " name=\"Formation Name\"\n"; + input << " ordinal_number=2\n"; + // input << " position={ 4743.02784 910.80912 }\n"; // import this eventually + // input << " organization=25\n"; // import this eventually + // input << " current_location={\n"; // import this eventually + // input << " type=hq\n"; + // input << " identity=50331767\n"; + // input << " }\n"; + // input << " target_location={\n"; // import this eventually + // input << " type=hq\n"; + // input << " identity=50331767\n"; + // input << " }\n"; + // input << " travel_progress={\n"; // import this eventually + input << " building_to_expected_units_map={\n"; + input << "8061={\n"; + input << " unit_types_num_list={\n"; + input << "combat_unit_type_shrapnel_artillery=2 combat_unit_type_dragoons=3 combat_unit_type_trench_infantry=8 " + "combat_unit_type_siege_artillery=2 }\n"; + input << " } 11850={\n"; + input << " unit_types_num_list={\n"; + input << "combat_unit_type_skirmish_infantry=8 }\n"; + input << " } 50334733={\n"; + input << " unit_types_num_list={\n"; + input << "combat_unit_type_trench_infantry=16 }\n"; + input << " } }\n"; + input << "}\n"; + const std::optional military_formation = importer.ImportMilitaryFormation(input); + + std::map expected_units{ + {"combat_unit_type_shrapnel_artillery", 2}, + {"combat_unit_type_dragoons", 3}, + {"combat_unit_type_trench_infantry", 24}, + {"combat_unit_type_siege_artillery", 2}, + {"combat_unit_type_skirmish_infantry", 8}, + }; + + EXPECT_TRUE(military_formation.has_value()); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).country, 12345); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).type, MilitaryFormationType::kArmy); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).name, std::make_optional("Formation Name")); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).ordinal_number, std::make_optional(2)); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).units, expected_units); +} + + +TEST(Vic3worldMilitaryMilitaryFormationImporter, TypeCanBeFleet) +{ + MilitaryFormationImporter importer; + + std::stringstream input; + input << "={\n"; + input << " country=12345\n"; + input << " type=fleet\n"; + input << "}\n"; + const std::optional military_formation = importer.ImportMilitaryFormation(input); + + EXPECT_TRUE(military_formation.has_value()); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).country, 12345); + EXPECT_EQ(military_formation.value_or(MilitaryFormation{}).type, MilitaryFormationType::kFleet); +} + +} // namespace vic3 diff --git a/src/vic3_world/military/military_formations_importer.cpp b/src/vic3_world/military/military_formations_importer.cpp new file mode 100644 index 00000000..e05dce23 --- /dev/null +++ b/src/vic3_world/military/military_formations_importer.cpp @@ -0,0 +1,30 @@ +#include "src/vic3_world/military/military_formations_importer.h" + +#include "src/vic3_world/database/database_parser.h" + + + +std::map vic3::ImportMilitaryFormations(std::istream& input) +{ + std::map military_formations; + + MilitaryFormationImporter formation_importer; + + const auto& parser_function = [&formation_importer, &military_formations](const std::string& number_string, + std::istream& input_stream) { + const int formation_number = std::stoi(number_string); + if (std::optional formation = formation_importer.ImportMilitaryFormation(input_stream); + formation.has_value()) + { + military_formations.emplace(formation_number, *formation); + } + }; + + DatabaseParser parser(parser_function); + parser.registerKeyword("dead", commonItems::ignoreItem); + parser.registerKeyword("front_to_distribution_data_map", commonItems::ignoreItem); + parser.registerKeyword("front_to_distribution_counter_map", commonItems::ignoreItem); + parser.parseStream(input); + + return military_formations; +} \ No newline at end of file diff --git a/src/vic3_world/military/military_formations_importer.h b/src/vic3_world/military/military_formations_importer.h new file mode 100644 index 00000000..2bb712db --- /dev/null +++ b/src/vic3_world/military/military_formations_importer.h @@ -0,0 +1,22 @@ +#ifndef SRC_VIC3WORLD_MILITARY_MILITARYFORMATIONSIMPORTER_H +#define SRC_VIC3WORLD_MILITARY_MILITARYFORMATIONSIMPORTER_H + + +#include +#include + +#include "src/vic3_world/military/military_formation.h" +#include "src/vic3_world/military/military_formation_importer.h" + + + +namespace vic3 +{ + +std::map ImportMilitaryFormations(std::istream& input); + +} // namespace vic3 + + + +#endif // SRC_VIC3WORLD_MILITARY_MILITARYFORMATIONSIMPORTER_H \ No newline at end of file diff --git a/src/vic3_world/military/military_formations_importer_tests.cpp b/src/vic3_world/military/military_formations_importer_tests.cpp new file mode 100644 index 00000000..439fd9fa --- /dev/null +++ b/src/vic3_world/military/military_formations_importer_tests.cpp @@ -0,0 +1,117 @@ +#include +#include + +#include "external/commonItems/external/googletest/googlemock/include/gmock/gmock-matchers.h" +#include "external/commonItems/external/googletest/googletest/include/gtest/gtest.h" +#include "src/vic3_world/military/military_formations_importer.h" + + + +namespace vic3 +{ + +TEST(Vic3worldMilitaryMilitaryFormationsImporter, EmptyInputMeansNoFormations) +{ + std::stringstream input; + input << "={\n"; + input << "\tdatabase={\n"; + input << "\t}\n"; + input << "}\n"; + const std::map military_formations = ImportMilitaryFormations(input); + + EXPECT_TRUE(military_formations.empty()); +} + + +TEST(Vic3worldMilitaryMilitaryFormationsImporter, ExtrasAreSkipped) +{ + std::stringstream input; + input << "={\n"; + input << "\tdatabase={\n"; + input << "\t}\n"; + input << "\tdead={\n"; + input << "\t}\n"; + input << "\tfront_to_distribution_data_map={\n"; + input << "\t}\n"; + input << "\tfront_to_distribution_counter_map={\n"; + input << "\t}\n"; + input << "}\n"; + + std::stringstream log; + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(log.rdbuf()); + + [[maybe_unused]] const std::map military_formations = ImportMilitaryFormations(input); + + std::cout.rdbuf(cout_buffer); + + EXPECT_TRUE(log.str().empty()); +} + + +TEST(Vic3worldMilitaryMilitaryFormationsImporter, FormationsCanBeInput) +{ + std::stringstream input; + input << "={\n"; + input << "\tdatabase={\n"; + input << "1234={\n"; + input << " country=12345\n"; + input << " type=army\n"; + input << " name=\"Formation Name\"\n"; + input << " ordinal_number=2\n"; + // input << " position={ 4743.02784 910.80912 }\n"; // import this eventually + // input << " organization=25\n"; // import this eventually + // input << " current_location={\n"; // import this eventually + // input << " type=hq\n"; + // input << " identity=50331767\n"; + // input << " }\n"; + // input << " target_location={\n"; // import this eventually + // input << " type=hq\n"; + // input << " identity=50331767\n"; + // input << " }\n"; + // input << " travel_progress={\n"; // import this eventually + input << " building_to_expected_units_map={\n"; + input << "8061={\n"; + input << " unit_types_num_list={\n"; + input << "combat_unit_type_shrapnel_artillery=2 combat_unit_type_dragoons=3 combat_unit_type_trench_infantry=8 " + "combat_unit_type_siege_artillery=2 }\n"; + input << " } 11850={\n"; + input << " unit_types_num_list={\n"; + input << "combat_unit_type_skirmish_infantry=8 }\n"; + input << " } 50334733={\n"; + input << " unit_types_num_list={\n"; + input << "combat_unit_type_trench_infantry=16 }\n"; + input << " } }\n"; + input << "}\n"; + input << "5678={\n"; + input << " country=12345\n"; + input << " type=fleet\n"; + input << "}\n"; + input << "\t}\n"; + input << "}\n"; + const std::map military_formations = ImportMilitaryFormations(input); + + EXPECT_THAT(military_formations, + testing::UnorderedElementsAre(testing::Pair(1234, + MilitaryFormation{ + .country = 12345, + .type = MilitaryFormationType::kArmy, + .name = "Formation Name", + .ordinal_number = 2, + .units = + { + {"combat_unit_type_shrapnel_artillery", 2}, + {"combat_unit_type_dragoons", 3}, + {"combat_unit_type_trench_infantry", 24}, + {"combat_unit_type_siege_artillery", 2}, + {"combat_unit_type_skirmish_infantry", 8}, + }, + }), + testing::Pair(5678, + MilitaryFormation{ + .country = 12345, + .type = MilitaryFormationType::kFleet, + }))); +} + +} // namespace vic3 \ No newline at end of file diff --git a/src/vic3_world/world/vic3_world_importer.cpp b/src/vic3_world/world/vic3_world_importer.cpp index 55c8a9bd..406fcec7 100644 --- a/src/vic3_world/world/vic3_world_importer.cpp +++ b/src/vic3_world/world/vic3_world_importer.cpp @@ -32,6 +32,7 @@ #include "src/vic3_world/institutions/institutions_importer.h" #include "src/vic3_world/interest_groups/interest_groups_importer.h" #include "src/vic3_world/laws/laws_importer.h" +#include "src/vic3_world/military/military_formations_importer.h" #include "src/vic3_world/pacts/pacts_importer.h" #include "src/vic3_world/provinces/vic3_province_definitions.h" #include "src/vic3_world/provinces/vic3_province_definitions_loader.h" @@ -224,6 +225,54 @@ void AssignCharactersToCountries(const std::map& character } +void AssignMilitaryFormationsToCountries(const std::map& military_formations, + std::map& countries) +{ + std::map> army_formations_by_country; + std::map> navy_formations_by_country; + for (const auto& [formation_number, formation]: military_formations) + { + if (formation.type == vic3::MilitaryFormationType::kArmy) + { + auto [iterator, success] = army_formations_by_country.emplace(formation.country, + std::map{{formation_number, formation}}); + if (!success) + { + iterator->second.emplace(formation_number, formation); + } + } + else + { + auto [iterator, success] = navy_formations_by_country.emplace(formation.country, + std::map{{formation_number, formation}}); + if (!success) + { + iterator->second.emplace(formation_number, formation); + } + } + } + + for (const auto& [country_number, army_formations]: army_formations_by_country) + { + auto country = countries.find(country_number); + if (country == countries.end()) + { + Log(LogLevel::Warning) << fmt::format("Could not find country {} to assign army formations.", country_number); + } + country->second.SetArmyFormations(army_formations); + } + for (const auto& [country_number, navy_formations]: navy_formations_by_country) + { + auto country = countries.find(country_number); + if (country == countries.end()) + { + Log(LogLevel::Warning) << fmt::format("Could not find country {} to assign navy formations.", country_number); + } + country->second.SetNavyFormations(navy_formations); + } +} + + std::map MapCountryTagsToId(std::map& countries) { std::map tag_to_id_map; @@ -316,6 +365,7 @@ vic3::World vic3::ImportWorld(const configuration::Configuration& configuration) const std::map color_definitions = ImportCountryColorDefinitions(mod_filesystem); std::map cultures; std::map> country_character_map; + std::map military_formations; commonItems::parser save_parser; save_parser.registerKeyword("playthrough_id", [&world_options](std::istream& input_stream) { @@ -361,6 +411,9 @@ vic3::World vic3::ImportWorld(const configuration::Configuration& configuration) save_parser.registerKeyword("building_manager", [&world_options](std::istream& input_stream) { world_options.buildings = ImportBuildings(input_stream); }); + save_parser.registerKeyword("military_formation_manager", [&military_formations](std::istream& input_stream) { + military_formations = ImportMilitaryFormations(input_stream); + }); save_parser.registerKeyword("election_manager", [&world_options](std::istream& input_stream) { for (const auto& [country_number, last_election]: ImportElections(input_stream)) { @@ -417,6 +470,8 @@ vic3::World vic3::ImportWorld(const configuration::Configuration& configuration) ProgressManager::AddProgress(1); AssignCharactersToCountries(world_options.characters, country_character_map, world_options.countries); ProgressManager::AddProgress(1); + AssignMilitaryFormationsToCountries(military_formations, world_options.countries); + ProgressManager::AddProgress(1); vic3::IdeologiesImporter ideologies_importer; world_options.ideologies = ideologies_importer.ImportIdeologies(mod_filesystem); ProgressManager::AddProgress(1); diff --git a/src/vic3_world/world/vic3_world_importer_tests.cpp b/src/vic3_world/world/vic3_world_importer_tests.cpp index 856b6d35..2e1a3520 100644 --- a/src/vic3_world/world/vic3_world_importer_tests.cpp +++ b/src/vic3_world/world/vic3_world_importer_tests.cpp @@ -49,18 +49,36 @@ TEST(Vic3worldWorldVic3worldimporter, WorldCanBeImported) EXPECT_THAT(world.GetCountries(), testing::UnorderedElementsAre(testing::Pair(1, - Country({ - .number = 1, - .tag = "TAG", - .color = commonItems::Color(std::array{1, 2, 3}), - .active_laws = {"law_monarchy"}, - .primary_culture_ids = {0}, - .primary_cultures = {"welsh"}, - .head_of_state_id = 1, - .character_ids = {1, 2, 4}, - .ig_ids = {1, 2}, - .puppets = {3}, - })), + Country( + { + .number = 1, + .tag = "TAG", + .color = commonItems::Color(std::array{1, 2, 3}), + .active_laws = {"law_monarchy"}, + .primary_culture_ids = {0}, + .primary_cultures = {"welsh"}, + .head_of_state_id = 1, + .character_ids = {1, 2, 4}, + .ig_ids = {1, 2}, + .puppets = {3}, + .army_formations = + { + {1234, + MilitaryFormation{ + .country = 1, + .type = MilitaryFormationType::kArmy, + .name = "Formation Name", + .ordinal_number = 2, + .units = + { + {"combat_unit_type_shrapnel_artillery", 2}, + {"combat_unit_type_dragoons", 3}, + {"combat_unit_type_trench_infantry", 24}, + {"combat_unit_type_siege_artillery", 2}, + {"combat_unit_type_skirmish_infantry", 8}, + }, + }}}, + })), testing::Pair(3, Country({ .number = 3, @@ -72,6 +90,11 @@ TEST(Vic3worldWorldVic3worldimporter, WorldCanBeImported) .character_ids = {5}, .ig_ids = {3}, .overlord = 1, + .navy_formations = {{5678, + MilitaryFormation{ + .country = 3, + .type = MilitaryFormationType::kFleet, + }}}, })))); EXPECT_THAT(world.GetStates(), testing::UnorderedElementsAre(testing::Pair(0, State({.provinces = {1, 2, 3}})),