diff --git a/src/openvic-simulation/military/MilitaryComponents.hpp b/src/openvic-simulation/military/MilitaryComponents.hpp new file mode 100644 index 000000000..2feb14f0e --- /dev/null +++ b/src/openvic-simulation/military/MilitaryComponents.hpp @@ -0,0 +1,602 @@ +#pragma once + +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/types/UnitBranchType.hpp" +#include "openvic-simulation/types/Date.hpp" +#include "openvic-simulation/core/memory/Vector.hpp" +#include "openvic-simulation/core/memory/String.hpp" +#include "openvic-simulation/ecs/EntityID.hpp" + +namespace OpenVic { + struct UnitType; + struct RegimentType; + struct ShipType; + struct LeaderTrait; + struct ProvinceInstance; + struct CountryInstance; + struct Pop; + + enum class unit_category_t : uint8_t { + INVALID_UNIT_CATEGORY, + INFANTRY, + CAVALRY, + SUPPORT, + SPECIAL, + BIG_SHIP, + LIGHT_SHIP, + TRANSPORT + }; +} + +namespace OpenVic::ecs { + + using icon_t = uint32_t; + + struct UnitTypeStats { + fixed_point_t max_strength; + fixed_point_t default_organization; + fixed_point_t max_speed; + fixed_point_t supply_consumption; + unit_category_t unit_category; + bool starts_unlocked; + Timespan build_time; + }; + + struct UnitTypeCosts { + memory::vector> build_cost; + memory::vector> supply_cost; + }; + + struct UnitTypeTerrainModifiers { + memory::vector> terrain_modifiers; + }; + + struct UnitTypeDisplay { + icon_t icon; + uint32_t priority; + bool has_floating_flag; + fixed_point_t weighted_value; + + memory::string sprite; + memory::string move_sound; + memory::string select_sound; + } + + struct RegimentTypeStats { + regiment_allowed_cultures_t allowed_cultures; + const fixed_point_t reconnaissance; + const fixed_point_t attack; + const fixed_point_t defence; + const fixed_point_t discipline; + const fixed_point_t support; + const fixed_point_t maneuver; + const fixed_point_t siege; + }; + + struct RegimentTypeDisplay { + memory::string sprite_override; + memory::string sprite_mount; + memory::string sprite_mount_attach_node; + }; + + struct ShipTypeStats { + bool can_sail; + bool is_transport; + bool is_capital; + bool can_build_overseas; + + fixed_point_t hull; + fixed_point_t gun_power; + fixed_point_t fire_range; + fixed_point_t evasion; + fixed_point_t torpedo_attack; + fixed_point_t colonial_points; + fixed_point_t supply_consumption_score; + + uint32_t min_port_level; + int32_t limit_per_port; + }; + + struct ShipTypeDisplay { + icon_t icon; + }; + + struct UnitStats { + fixed_point_t organization; + fixed_point_t max_organization; + fixed_point_t strength; + }; + + struct UnitIdentity { + memory::string name; + EntityID unit_type_id; + }; + + struct RegimentPopLink { + EntityID pop = INVALID_ENTITY_ID; + bool mobilized; + }; + + struct IsNaval {}; + struct IsLand {}; + + struct GroupMember { + EntityID group = INVALID_ENTITY_ID; + }; + + struct GroupIdentity { + memory::string name; + }; + + struct GroupLocation { + EntityID province = INVALID_ENTITY_ID; + }; + + struct GroupCountry { + EntityID country = INVALID_ENTITY_ID; + }; + + struct GroupLeader { + EntityID leader = INVALID_ENTITY_ID; + }; + + struct GroupStats { + //use total to make it distinct from something like UnitStats + fixed_point_t total_max_organization; + fixed_point_t total_organization; + fixed_point_t total_max_strength; + fixed_point_t total_strength; + }; + +#ifdef NOTYET + struct NavyStats { + //maybe navies need different stats in the future? + fixed_point_t total_max_organization; + fixed_point_t total_organization; + fixed_point_t total_max_strength; + fixed_point_t total_strength; + }; + + struct ArmyStats { + //maybe armies need different stats in the future? + fixed_point_t total_max_organization; + fixed_point_t total_organization; + fixed_point_t total_max_strength; + fixed_point_t total_strength; + }; +#endif + + struct GroupMovement { + memory::vector path; + fixed_point_t movement_progress; + }; + + + //used for checking and applying modifiers per terrain performantly + struct GroupTerrainState { + bool dirty; + fixed_point_t cached_modifier = fixed_point_t::_0; + }; + + struct ArmyExtra { + EntityID transport_navy = INVALID_ENTITY_ID; + uint8_t dig_in_level; + bool is_exiled; + }; + + struct NavyExtra { + memory::vector carried_armies; + }; + + struct InCombat {}; + struct Moving {}; + + struct LeaderIdentity { + memory::string name; + uint32_t personality; + uint32_t background; + unit_branch_t branch; + //date + fixed_point_t prestige; + memory::string picture; + }; + + struct LeaderCountry { + EntityID country = INVALID_ENTITY_ID; + }; + + struct LeaderUsability { + bool usable; + }; + + //back reference to the leader's army + //pre attached at creation so lookups are just plainly read + //substitute for a longer search algorithm + struct LeaderGroup { + EntityID group = INVALID_ENTITY_ID; + }; + + struct UnitDeployment { + memory::string name; + province_index_t index; + regiment_type_index_t type; + }; + + //TODO IMPLEMENT THE METHODS! + + EntityID create_army(CommandBuffer& cmd, memory::string name, EntityID location, EntityID country) { + return cmd.create_entity( + GroupIdentity{ std::move(name) }, + GroupLocation{ location }, + GroupCountry{ country }, + GroupLeader{ INVALID_ENTITY_ID }, + GroupStats{}, + GroupMovement{}, + GroupTerrainState{ false }, + ArmyExtra{ INVALID_ENTITY_ID, 0, false } + ); + } + + EntityID create_navy(CommandBuffer& cmd, memory::string name, EntityID location, EntityID country) { + return cmd.create_entity( + GroupIdentity{ std::move(name) }, + GroupLocation{ location }, + GroupCountry{ country }, + GroupLeader{ INVALID_ENTITY_ID }, + GroupStats{}, + GroupMovement{}, + GroupTerrainState{ false }, + NavyExtra{} + ); + } + + EntityID create_regiment(CommandBuffer& cmd, World const& world, EntityID unit_type_entity, EntityID army_id, memory::string name) { + UnitTypeStats const* type_stats = world.get_component(unit_type_entity); + + return cmd.create_entity( + UnitIdentity{ std::move(name), unit_type_entity }, + RegimentPopLink{ INVALID_ENTITY_ID, false }, + UnitStats{ + .organization = type_stats->default_organization; + .max_organization = types_stats->default_organization; + .strength = types_stats->max_organization; + }, + GroupMember{ army_id }, + IsLand{} + ); + } + + EntityID create_ship(CommandBuffer& cmd, World const& world, EntityID unit_type_entity, EntityID ship_id, memory::string name) { + UnitTypeStats const* type_stats = world.get_component(unit_type_entity); + + return cmd.create_entity( + UnitIdentity{ std::move(name), unit_type_entity }, + RegimentPopLink{ INVALID_ENTITY_ID, false }, + UnitStats{ + .organization = type_stats->default_organization; + .max_organization = types_stats->default_organization; + .strength = types_stats->max_organization; + }, + GroupMember{ ship_id }, + IsNaval{} + ); + } + + + EntityID get_leader_army(World const& world, EntityID leader_id) { + LeaderGroup const* group = world.get_component(leader_id); + return group != nullptr ? group->group : INVALID_ENTITY_ID; + } + + void assign_leader(World& world, EntityID leader_id, EntityID army_id) { + LeaderGroup* leader_group = world.get_component(leader_id); + if (leader_group == nullptr) { + return; + } + + if (leader_group->group.is_valid() && world.is_alive(leader_group->group)) { + if (GroupLeader* old_group = world.get_component(leader_group->group)) { + old_group->leader = INVALID_ENTITY_ID; + } + } + + leader_group->group = army_id; + + if (GroupLeader* new_group = world.get_component(army_id)) { + new_group->leader = leader_id; + } + } + + void create_land_leader(CommandBuffer& cmd, World const& world, EntityID country, uint32_t personality, uint32_t background, memory::string name /* maybe creation_date??? */) { + //TODO: get last names of accepted culture, then assign randomly + //TODO: update prestige somehow + //TODO: do personalities and background generation somehow + //TODO: do pictures + name = "Ben"; //:) + return cmd.create_entity( + LeaderIdentity{ + .name = name; + .prestige = 0; + .personality = 0; + .background = 0; + .picture = ""; + }, + LeaderCountry{ country }, + LeaderGroup{ INVALID_ENTITY_ID }, + IsLand{} + ); + } + + void create_naval_leader(CommandBuffer& cmd, World const& world, EntityID country, uint32_t personality, uint32_t background, memory::string name /* maybe creation_date??? */) { //TODO: get last names of accepted culture, then assign randomly + //TODO: update prestige somehow + //TODO: do personalities and background generation somehow + //TODO: do pictures + name = "Ben"; //:) + return cmd.create_entity( + LeaderIdentity{ + .name = name; + .prestige = 0; + .personality = 0; + .background = 0; + .picture = ""; + }, + LeaderCountry{ country }, + LeaderGroup{ INVALID_ENTITY_ID }, + IsNaval{} + ); + } + + + EntityID create_unit_type_entity(World& world, ParsedUnitTypeDef const& def) { + if (def.is_naval) { + return world.create_entity( + def.stats, + def.costs, + UnitTypeTerrainModifiers{}, + def.display, + def.ship_stats, + def.ship_display, + IsNaval{} + ); + } + return world.create_entity( + def.stats, + def.costs, + UnitTypeTerrainModifiers{}, + def.display, + def.regiment_stats, + def.regiment_display, + IsLand{} + ); + } + + void apply_terrain_modifiers(World& world, EntityID unit_type_entity, ParsedUnitTypeDef const& def, memory::unordered_map const& terrain_name_to_entity) { + memory::vector> modifiers; + for (auto const& [terrain_name, value] : def.terrain_modifiers_by_name) { + auto it = terrain_name_to_entity.find(terrain_name); + if (it != terrain_name_to_entity.end()) { + modifiers.emplace_back(it->second, value); + } + } + set_unit_terrain_modifiers(world, unit_type_entity, std::move(modifiers)); + } + + bool load_unit_types(World& world, ovdl::v2script::Parser const& parser, memory::unordered_map const& terrain_name_to_entity) { + memory::vector defs; + bool ret = parse_unit_type_definitions_file(parser, defs); + + for (ParsedUnitTypeDef const& def : defs) { + EntityID unit_type_entity = create_unit_type_entity(world, def); + apply_terrain_modifiers(world, unit_type_entity, def, terrain_name_to_entity); + } + + return ret; + } + + void tick_military(World& world) { + recompute_group_stats(world); + tick_military_terrain(world); + } + + struct ParsedUnitTypeDef { + memory::string identifier; + bool is_naval; + UnitTypeStats stats; + UnitTypeCosts costs; + UnitTypeDisplay display; + RegimentTypeStats regiment_stats; + RegimentTypeDisplay regiment_display; + ShipTypeStats ship_stats; + ShipTypeDisplay ship_display; + memory::vector> terrain_modifiers_by_name; + }; + + bool parse_unit_type_definitions_file(ovdl::v2script::Parser const& parser, memory::vector& out_defs); + + + struct GroupStatsAccum { + fixed_point_t organization = fixed_point_t::_0; + fixed_point_t max_organization = fixed_point_t::_0; + fixed_point_t strength = fixed_point_t::_0; + fixed_point_t max_strength = fixed_point_t::_0; + }; + + void recompute_group_stats(World& world) { + std::unordered_map totals; + + world.for_each_chunk( + [&](ChunkView view) { + UnitStats const* OV_RESTRICT stats = view.array(); + UnitIdentity const* OV_RESTRICT identity = view.array(); + GroupMember const* OV_RESTRICT members = view.array(); + + for (std::size_t i = 0; i < view.count(); ++i) { + GroupStatsAccum& accum = totals[members[i].group.to_uint64()]; + accum.organization += stats[i].organization; + accum.max_organization += stats[i].max_organization; + accum.strength += stats[i].strength; + + if (UnitTypeStats const* type_stats = world.get_component(identity[i].unit_type)) { + accum.max_strength += type_stats->max_strength; + } + } + } + ); + + world.for_each_with_entity([&](EntityID id, GroupStats& stats) { + auto const it = totals.find(id.to_uint64()); + GroupStatsAccum const accum = it != totals.end() ? it->second : GroupStatsAccum{}; + stats.total_organization = accum.organization; + stats.total_max_organization = accum.max_organization; + stats.total_strength = accum.strength; + stats.total_max_strength = accum.max_strength; + }); + } + + void on_group_arrives(World& world, EntityID group_id, EntityID new_province) { + if (GroupTerrainState* state = world.get_component(group_id)) + state->dirty = true; + } + + inline fixed_point_t get_unit_terrain_modifier(World const& world, EntityID unit_type, EntityID terrain) { + UnitTypeTerrainModifiers const* modifiers = world.get_component(unit_type); + if (modifiers == nullptr) { + return fixed_point_t::_0; + } + + for (std::pair const& entry : modifiers->terrain_modifiers) { + if (entry.first == terrain) { + return entry.second; + } + } + + return fixed_point_t::_0; + } + + inline fixed_point_t get_regiment_terrain_modifier(World const& world, EntityID regiment, EntityID terrain) { + UnitIdentity const* identity = world.get_component(regiment); + if (identity == nullptr) { + return fixed_point_t::_0; + } + + return get_unit_terrain_modifier(world, identity->unit_type, terrain); + } + + void recompute_terrain_modifiers(World& world) { + std::unordered_map totals; + + world.for_each_chunk([&](ChunkView view) { + UnitIdentity const* OV_RESTRICT identity = view.array(); + GroupMember const* OV_RESTRICT members = view.array(); + + for (std::size_t i = 0; i < view.count(); ++i) { + GroupLocation const* loc = world.get_component(members[i].group); + if (loc == nullptr) { + continue; + } + totals[members[i].group.to_uint64()] += get_unit_terrain_modifier(world, identity[i].unit_type_id, loc->province); + } + }); + + world.for_each_with_entity([&](EntityID group_id, GroupTerrainState& state) { + if (!state.dirty) { + return; + } + auto it = totals.find(group_id.to_uint64()); + state.cached_modifier = it != totals.end() ? it->second : fixed_point_t::_0; + state.dirty = false; + }); + } + + + //ecs_checksums + inline uint64_t ecs_checksum(UnitIdentity const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.name.size(), seed); + h = fnv1a_64_bytes(value.name.data(), value.name.size(), h); + return fold_uint64(value.unit_type.to_uint64(), h); + } + + inline uint64_t ecs_checksum(RegimentPopLink const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.pop.to_uint64(), seed); + return fold_uint64(value.mobilized ? 1u : 0u, h); + } + + inline uint64_t ecs_checksum(GroupIdentity const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.name.size(), seed); + return fnv1a_64_bytes(value.name.data(), value.name.size(), h); + } + + inline uint64_t ecs_checksum(GroupMovement const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.path.size(), seed); + for (EntityID const& eid : value.path) { + h = fold_uint64(eid.to_uint64(), h); + } + return checksum_value(value.movement_progress, h); + } + + inline uint64_t ecs_checksum(ArmyExtra const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.transport_navy.to_uint64(), seed); + h = fold_uint64(value.dig_in_level, h); + return fold_uint64(value.is_exiled ? 1u : 0u, h); + } + + inline uint64_t ecs_checksum(NavyExtra const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.carried_armies.size(), seed); + for (EntityID const& eid : value.carried_armies) { + h = fold_uint64(eid.to_uint64(), h); + } + return h; + } + + inline uint64_t ecs_checksum(LeaderIdentity const& value, uint64_t seed) { + uint64_t h = fold_uint64(value.name.size(), seed); + h = fnv1a_64_bytes(value.name.data(), value.name.size(), h); + h = fold_uint64(value.personality, h); + h = fold_uint64(value.background, h); + h = checksum_value(value.prestige, h); + h = fold_uint64(value.picture.size(), h); + return fnv1a_64_bytes(value.picture.data(), value.picture.size(), h); + } +} + +ECS_COMPONENT(OpenVic::ecs::UnitStats, "OpenVic::UnitStats") +ECS_COMPONENT(OpenVic::ecs::UnitIdentity, "OpenVic::UnitIdentity") +ECS_COMPONENT(OpenVic::ecs::RegimentPopLink, "OpenVic::RegimentPopLink") +ECS_COMPONENT(OpenVic::ecs::IsNaval, "OpenVic::IsNaval") +ECS_COMPONENT(OpenVic::ecs::IsLand, "OpenVic::IsLand") +ECS_COMPONENT(OpenVic::ecs::GroupMember, "OpenVic::GroupMember") +ECS_COMPONENT(OpenVic::ecs::GroupIdentity, "OpenVic::GroupIdentity") +ECS_COMPONENT(OpenVic::ecs::GroupLocation, "OpenVic::GroupLocation") +ECS_COMPONENT(OpenVic::ecs::GroupCountry, "OpenVic::GroupCountry") +ECS_COMPONENT(OpenVic::ecs::GroupLeader, "OpenVic::GroupLeader") +ECS_COMPONENT(OpenVic::ecs::GroupStats, "OpenVic::GroupStats") +ECS_COMPONENT(OpenVic::ecs::GroupMovement, "OpenVic::GroupMovement") +ECS_COMPONENT(OpenVic::ecs::GroupTerrainState, "OpenVic::GroupTerrainState") +ECS_COMPONENT(OpenVic::ecs::ArmyExtra, "OpenVic::ArmyExtra") +ECS_COMPONENT(OpenVic::ecs::NavyExtra, "OpenVic::NavyExtra") +ECS_COMPONENT(OpenVic::ecs::InCombat, "OpenVic::InCombat") +ECS_COMPONENT(OpenVic::ecs::Moving, "OpenVic::Moving") +ECS_COMPONENT(OpenVic::ecs::LeaderIdentity, "OpenVic::LeaderIdentity") +ECS_COMPONENT(OpenVic::ecs::LeaderCountry, "OpenVic::LeaderCountry") +ECS_COMPONENT(OpenVic::ecs::LeaderUsability, "OpenVic::LeaderUsability") +ECS_COMPONENT(OpenVic::ecs::LeaderGroup, "OpenVic::LeaderGroup") +ECS_COMPONENT(OpenVic::ecs::UnitDeployment, "OpenVic::UnitDeployment") + +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +static_assert(OpenVic::ecs::is_checksummable_v); +