From 00e60abe92d709565a660145f61772c65d003c6f Mon Sep 17 00:00:00 2001 From: Benjamin Lee McQueen Date: Wed, 8 Jul 2026 10:38:00 -0700 Subject: [PATCH 1/4] implement wip ecs-ported military header wip, not very close to finished yet --- MilitaryComponents.hpp | 497 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 MilitaryComponents.hpp diff --git a/MilitaryComponents.hpp b/MilitaryComponents.hpp new file mode 100644 index 000000000..1652fb750 --- /dev/null +++ b/MilitaryComponents.hpp @@ -0,0 +1,497 @@ +#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; + }; + + struct ArmyExtra { + EntityID transport_navy = INVALID_ENTITY_ID; + uint8_t dig_in_level; + bool is_exiled; + }; + + struct NavyExtra { + memory::vector carried_armies; + }; + + //maybe not even needed idk just putting it here + 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; + }; + + //only definitions done currently + //no methods + //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{}, + 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{}, + 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{} + ); + } + + 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; + }); + } + + 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); + } + + //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::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); + From 8175101e428bd783d88961371cba29f6dc9c81f2 Mon Sep 17 00:00:00 2001 From: Benjamin Lee McQueen Date: Wed, 8 Jul 2026 10:40:50 -0700 Subject: [PATCH 2/4] change file dir --- .../openvic-simulation/military/MilitaryComponents.hpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename MilitaryComponents.hpp => src/openvic-simulation/military/MilitaryComponents.hpp (100%) diff --git a/MilitaryComponents.hpp b/src/openvic-simulation/military/MilitaryComponents.hpp similarity index 100% rename from MilitaryComponents.hpp rename to src/openvic-simulation/military/MilitaryComponents.hpp From eb947083c8228aebeaacd8cac132fe6b4bf66f45 Mon Sep 17 00:00:00 2001 From: Benjamin Lee McQueen Date: Wed, 8 Jul 2026 23:11:19 -0700 Subject: [PATCH 3/4] add terrain modifier components and recompute pass --- .../military/MilitaryComponents.hpp | 797 +++++++++--------- 1 file changed, 417 insertions(+), 380 deletions(-) diff --git a/src/openvic-simulation/military/MilitaryComponents.hpp b/src/openvic-simulation/military/MilitaryComponents.hpp index 1652fb750..c1e153cc5 100644 --- a/src/openvic-simulation/military/MilitaryComponents.hpp +++ b/src/openvic-simulation/military/MilitaryComponents.hpp @@ -8,342 +8,346 @@ #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 - }; + 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; - }; + 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; - }; + 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; - }; - - struct ArmyExtra { - EntityID transport_navy = INVALID_ENTITY_ID; - uint8_t dig_in_level; - bool is_exiled; - }; - - struct NavyExtra { - memory::vector carried_armies; - }; - - //maybe not even needed idk just putting it here - 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; - }; - - //only definitions done currently - //no methods - //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{}, - 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{}, - 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{} - ); - } - - struct GroupStatsAccum { + 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{} + ); + } + + 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; @@ -381,11 +385,16 @@ namespace OpenVic::ecs { 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; + return fixed_point_t::_0; } for (std::pair const& entry : modifiers->terrain_modifiers) { @@ -405,55 +414,82 @@ namespace OpenVic::ecs { return get_unit_terrain_modifier(world, identity->unit_type, terrain); } - - //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); - } + + 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") @@ -468,6 +504,7 @@ 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") @@ -488,10 +525,10 @@ 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); - From a399fe95a01b6d59206b7301bbce9210854b666c Mon Sep 17 00:00:00 2001 From: Ben M Date: Thu, 9 Jul 2026 13:59:04 -0700 Subject: [PATCH 4/4] fix bugs and do more terrain stuff --- .../military/MilitaryComponents.hpp | 894 ++++++++++-------- 1 file changed, 481 insertions(+), 413 deletions(-) diff --git a/src/openvic-simulation/military/MilitaryComponents.hpp b/src/openvic-simulation/military/MilitaryComponents.hpp index c1e153cc5..2feb14f0e 100644 --- a/src/openvic-simulation/military/MilitaryComponents.hpp +++ b/src/openvic-simulation/military/MilitaryComponents.hpp @@ -8,346 +8,413 @@ #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 - }; + 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; - }; + 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; - }; + 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{} - ); - } - - struct GroupStatsAccum { + 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; @@ -386,15 +453,15 @@ namespace OpenVic::ecs { }); } - void on_group_arrives(World& world, EntityID group_id, EntityID new_province) { - if (GroupTerrainState* state = world.get_component(group_id)) - state->dirty = true; - } - + 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; + return fixed_point_t::_0; } for (std::pair const& entry : modifiers->terrain_modifiers) { @@ -415,81 +482,81 @@ namespace OpenVic::ecs { 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); - } + 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") @@ -532,3 +599,4 @@ 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); +