| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- #include "production_system.h"
- #include "../core/component.h"
- #include "../core/ownership_constants.h"
- #include "../core/world.h"
- #include "../game_config.h"
- #include "../map/map_transformer.h"
- #include "../map/terrain_service.h"
- #include "../units/factory.h"
- #include "../units/troop_config.h"
- #include "building_collision_registry.h"
- #include "command_service.h"
- #include "nation_registry.h"
- #include "pathfinding.h"
- #include "troop_profile_service.h"
- #include "units/spawn_type.h"
- #include "units/unit.h"
- #include <cmath>
- #include <limits>
- #include <qvectornd.h>
- namespace Game::Systems {
- namespace {
- void apply_production_profile(Engine::Core::ProductionComponent *prod,
- Game::Systems::NationID nation_id,
- Game::Units::TroopType troop_type) {
- if (prod == nullptr) {
- return;
- }
- const auto profile =
- TroopProfileService::instance().get_profile(nation_id, troop_type);
- prod->build_time = profile.production.build_time;
- prod->villager_cost = profile.production.cost;
- }
- auto resolve_nation_id(const Engine::Core::UnitComponent *unit,
- int owner_id) -> Game::Systems::NationID {
- auto ®istry = NationRegistry::instance();
- if (const auto *nation = registry.get_nation_for_player(owner_id)) {
- return nation->id;
- }
- return registry.default_nation_id();
- }
- auto compute_builder_exit_position(
- float center_x, float center_z, const QVector3D &builder_pos,
- float unit_radius, const std::string &building_type) -> QVector3D {
- auto const size = BuildingCollisionRegistry::get_building_size(building_type);
- float const half_width = size.width * 0.5F;
- float const half_depth = size.depth * 0.5F;
- float const clearance = unit_radius + 0.25F;
- float dir_x = builder_pos.x() - center_x;
- float dir_z = builder_pos.z() - center_z;
- float const len_sq = dir_x * dir_x + dir_z * dir_z;
- if (len_sq < 0.0001F) {
- dir_x = 1.0F;
- dir_z = 0.0F;
- } else {
- float const len = std::sqrt(len_sq);
- dir_x /= len;
- dir_z /= len;
- }
- float const abs_x = std::fabs(dir_x);
- float const abs_z = std::fabs(dir_z);
- float const sx = (abs_x > 0.0001F) ? (half_width + clearance) / abs_x
- : std::numeric_limits<float>::infinity();
- float const sz = (abs_z > 0.0001F) ? (half_depth + clearance) / abs_z
- : std::numeric_limits<float>::infinity();
- float const scale = std::min(sx, sz);
- float const fallback_scale = std::max(half_width, half_depth) + clearance;
- float const final_scale =
- std::isfinite(scale) && scale > 0.0F ? scale : fallback_scale;
- return {center_x + dir_x * final_scale, builder_pos.y(),
- center_z + dir_z * final_scale};
- }
- auto find_guaranteed_valid_exit(float exit_x, float exit_z,
- float unit_radius) -> QVector3D {
- Pathfinding *pathfinder = CommandService::get_pathfinder();
- if (pathfinder == nullptr) {
- return {exit_x, 0.0F, exit_z};
- }
- auto &terrain_service = Game::Map::TerrainService::instance();
- Point const exit_grid = CommandService::world_to_grid(exit_x, exit_z);
- bool is_valid = pathfinder->is_walkable_with_radius(exit_grid.x, exit_grid.y,
- unit_radius);
- if (is_valid && terrain_service.is_initialized()) {
- is_valid = terrain_service.is_walkable(exit_grid.x, exit_grid.y);
- }
- if (is_valid) {
- return {exit_x, 0.0F, exit_z};
- }
- constexpr int k_max_search_radius = 50;
- Point safe_grid = exit_grid;
- for (int radius = 1; radius <= k_max_search_radius; ++radius) {
- for (int dy = -radius; dy <= radius; ++dy) {
- for (int dx = -radius; dx <= radius; ++dx) {
- if (std::abs(dx) != radius && std::abs(dy) != radius) {
- continue;
- }
- int const check_x = exit_grid.x + dx;
- int const check_y = exit_grid.y + dy;
- bool valid =
- pathfinder->is_walkable_with_radius(check_x, check_y, unit_radius);
- if (valid && terrain_service.is_initialized()) {
- valid = terrain_service.is_walkable(check_x, check_y);
- }
- if (valid) {
- safe_grid = {check_x, check_y};
- return CommandService::grid_to_world(safe_grid);
- }
- }
- }
- }
- return CommandService::grid_to_world(safe_grid);
- }
- void activate_bypass_movement(Engine::Core::BuilderProductionComponent *builder,
- float target_x, float target_z) {
- if (builder == nullptr) {
- return;
- }
- builder->bypass_movement_active = true;
- builder->bypass_target_x = target_x;
- builder->bypass_target_z = target_z;
- }
- } // namespace
- void ProductionSystem::update(Engine::Core::World *world, float delta_time) {
- if (world == nullptr) {
- return;
- }
- auto entities = world->get_entities_with<Engine::Core::ProductionComponent>();
- for (auto *e : entities) {
- auto *prod = e->get_component<Engine::Core::ProductionComponent>();
- if (prod == nullptr) {
- continue;
- }
- auto *unit_comp = e->get_component<Engine::Core::UnitComponent>();
- if ((unit_comp != nullptr) &&
- Game::Core::isNeutralOwner(unit_comp->owner_id)) {
- continue;
- }
- if (!prod->in_progress) {
- continue;
- }
- const int owner_id = (unit_comp != nullptr) ? unit_comp->owner_id : -1;
- const auto nation_id = resolve_nation_id(unit_comp, owner_id);
- const auto current_profile = TroopProfileService::instance().get_profile(
- nation_id, prod->product_type);
- int const individuals_per_unit = current_profile.individuals_per_unit;
- int const production_cost = current_profile.production.cost;
- if (prod->produced_count + production_cost > prod->max_units) {
- prod->in_progress = false;
- continue;
- }
- prod->time_remaining -= delta_time;
- if (prod->time_remaining <= 0.0F) {
- auto *t = e->get_component<Engine::Core::TransformComponent>();
- auto *u = e->get_component<Engine::Core::UnitComponent>();
- if ((t != nullptr) && (u != nullptr)) {
- int const current_troops =
- Engine::Core::World::count_troops_for_player(u->owner_id);
- int const max_troops =
- Game::GameConfig::instance().get_max_troops_per_player();
- if (current_troops + production_cost > max_troops) {
- prod->in_progress = false;
- prod->time_remaining = 0.0F;
- continue;
- }
- float const exit_offset = 2.5F + 0.2F * float(prod->produced_count % 5);
- float const exit_angle = 0.5F * float(prod->produced_count % 8);
- QVector3D const exit_pos =
- QVector3D(t->position.x + exit_offset * std::cos(exit_angle), 0.0F,
- t->position.z + exit_offset * std::sin(exit_angle));
- auto reg = Game::Map::MapTransformer::getFactoryRegistry();
- if (reg) {
- Game::Units::SpawnParams sp;
- sp.position = exit_pos;
- sp.player_id = u->owner_id;
- sp.spawn_type =
- Game::Units::spawn_typeFromTroopType(prod->product_type);
- sp.ai_controlled =
- e->has_component<Engine::Core::AIControlledComponent>();
- sp.nation_id = nation_id;
- sp.is_initial_spawn = false;
- auto unit = reg->create(sp.spawn_type, *world, sp);
- if (unit && prod->rally_set) {
- unit->move_to(prod->rally_x, prod->rally_z);
- }
- }
- prod->produced_count += production_cost;
- }
- prod->in_progress = false;
- prod->time_remaining = 0.0F;
- if (!prod->production_queue.empty()) {
- prod->product_type = prod->production_queue.front();
- prod->production_queue.erase(prod->production_queue.begin());
- apply_production_profile(prod, nation_id, prod->product_type);
- prod->time_remaining = prod->build_time;
- prod->in_progress = true;
- }
- }
- }
- constexpr float CONSTRUCTION_ARRIVAL_DISTANCE_SQ = 4.0F;
- constexpr float MAX_CONSTRUCTION_DISTANCE_SQ = 9.0F;
- auto builder_entities =
- world->get_entities_with<Engine::Core::BuilderProductionComponent>();
- for (auto *e : builder_entities) {
- auto *builder_prod =
- e->get_component<Engine::Core::BuilderProductionComponent>();
- if (builder_prod == nullptr) {
- continue;
- }
- if (builder_prod->is_placement_preview) {
- continue;
- }
- auto *transform = e->get_component<Engine::Core::TransformComponent>();
- auto *movement = e->get_component<Engine::Core::MovementComponent>();
- if (builder_prod->has_construction_site &&
- !builder_prod->at_construction_site) {
- if (transform != nullptr) {
- float dx = builder_prod->construction_site_x - transform->position.x;
- float dz = builder_prod->construction_site_z - transform->position.z;
- float dist_sq = dx * dx + dz * dz;
- if (dist_sq < CONSTRUCTION_ARRIVAL_DISTANCE_SQ) {
- builder_prod->at_construction_site = true;
- builder_prod->in_progress = true;
- builder_prod->bypass_movement_active = false;
- transform->position.x = builder_prod->construction_site_x;
- transform->position.z = builder_prod->construction_site_z;
- if (movement != nullptr) {
- movement->goal_x = builder_prod->construction_site_x;
- movement->goal_y = builder_prod->construction_site_z;
- movement->target_x = builder_prod->construction_site_x;
- movement->target_y = builder_prod->construction_site_z;
- movement->has_target = false;
- movement->clear_path();
- movement->vx = 0.0F;
- movement->vz = 0.0F;
- }
- } else {
- if (!builder_prod->bypass_movement_active) {
- activate_bypass_movement(builder_prod,
- builder_prod->construction_site_x,
- builder_prod->construction_site_z);
- }
- }
- }
- continue;
- }
- if (!builder_prod->in_progress) {
- continue;
- }
- if (builder_prod->at_construction_site && transform != nullptr) {
- float dx = builder_prod->construction_site_x - transform->position.x;
- float dz = builder_prod->construction_site_z - transform->position.z;
- float dist_sq = dx * dx + dz * dz;
- if (dist_sq > MAX_CONSTRUCTION_DISTANCE_SQ) {
- builder_prod->has_construction_site = false;
- builder_prod->at_construction_site = false;
- builder_prod->in_progress = false;
- builder_prod->construction_complete = false;
- builder_prod->time_remaining = 0.0F;
- continue;
- }
- }
- builder_prod->time_remaining -= delta_time;
- if (builder_prod->time_remaining <= 0.0F) {
- auto *t = e->get_component<Engine::Core::TransformComponent>();
- auto *u = e->get_component<Engine::Core::UnitComponent>();
- if ((t != nullptr) && (u != nullptr)) {
- auto reg = Game::Map::MapTransformer::getFactoryRegistry();
- if (reg) {
- Game::Units::SpawnParams sp;
- if (builder_prod->has_construction_site) {
- sp.position =
- QVector3D(builder_prod->construction_site_x, t->position.y,
- builder_prod->construction_site_z);
- } else {
- sp.position =
- QVector3D(t->position.x, t->position.y, t->position.z);
- }
- sp.player_id = u->owner_id;
- sp.ai_controlled =
- e->has_component<Engine::Core::AIControlledComponent>();
- sp.nation_id = u->nation_id;
- sp.is_initial_spawn = false;
- if (builder_prod->product_type == "catapult") {
- sp.spawn_type = Game::Units::SpawnType::Catapult;
- } else if (builder_prod->product_type == "ballista") {
- sp.spawn_type = Game::Units::SpawnType::Ballista;
- } else if (builder_prod->product_type == "defense_tower") {
- sp.spawn_type = Game::Units::SpawnType::DefenseTower;
- } else if (builder_prod->product_type == "home") {
- sp.spawn_type = Game::Units::SpawnType::Home;
- } else {
- builder_prod->in_progress = false;
- builder_prod->time_remaining = 0.0F;
- builder_prod->has_construction_site = false;
- builder_prod->at_construction_site = false;
- continue;
- }
- reg->create(sp.spawn_type, *world, sp);
- if (builder_prod->has_construction_site && movement != nullptr &&
- t != nullptr) {
- float const unit_radius =
- CommandService::get_unit_radius(*world, e->get_id());
- QVector3D const preferred_exit = compute_builder_exit_position(
- builder_prod->construction_site_x,
- builder_prod->construction_site_z,
- QVector3D(t->position.x, t->position.y, t->position.z),
- unit_radius, builder_prod->product_type);
- QVector3D const safe_exit = find_guaranteed_valid_exit(
- preferred_exit.x(), preferred_exit.z(), unit_radius);
- activate_bypass_movement(builder_prod, safe_exit.x(),
- safe_exit.z());
- movement->goal_x = safe_exit.x();
- movement->goal_y = safe_exit.z();
- movement->target_x = safe_exit.x();
- movement->target_y = safe_exit.z();
- }
- }
- }
- builder_prod->in_progress = false;
- builder_prod->time_remaining = 0.0F;
- builder_prod->construction_complete = true;
- builder_prod->has_construction_site = false;
- builder_prod->at_construction_site = false;
- }
- }
- }
- } // namespace Game::Systems
|