#include "enemy_system.hpp" #include "beat_system.hpp" #include "combat_system.hpp" #include "movement_comp.hpp" #include "player_comp.hpp" #include "rigid_body_comp.hpp" #include namespace phase_shifter::gameplay { using namespace mirrage::ecs; Enemy_system::Enemy_system(mirrage::util::Message_bus& bus, Entity_manager& entity_manager, const Beat_system& beat_system) : _bus(bus), _entity_manager(entity_manager), _beat_system(beat_system) { _entity_manager.register_component_type(); _entity_manager.register_component_type(); _entity_manager.register_component_type(); _entity_manager.register_component_type(); _entity_manager.register_component_type(); _entity_manager.register_component_type(); } void Enemy_system::update(mirrage::util::Time dt) { auto beat = _beat_system.beat_state(); despawn_distant_bullets(); do_movement(beat); do_shooting(beat); do_bullet_hit_detection(); } void Enemy_system::do_movement(Beat_state& beat) { do_fixed_path_movement(beat); do_cont_path_movement(beat); do_hunting_movement(beat); } void Enemy_system::do_fixed_path_movement(Beat_state& beat) { for(auto&& [movement, fixed_path] : _entity_manager.list()) { if(beat.beat) { if(fixed_path.wait_beats == 0 && !movement.move) { float direction = fixed_path.next_direction(); if(direction < 360) { float rad_direction = direction * mirrage::util::PI / 180.f; movement.aim.x = std::sin(rad_direction); movement.aim.y = -std::cos(rad_direction); movement.move = true; } fixed_path.wait_beats = fixed_path.pause_between_steps; } else { fixed_path.wait_beats--; } } } } void Enemy_system::do_cont_path_movement(Beat_state& beat) { for(auto&& [movement, cont_path] : _entity_manager.list()) { if(beat.beat) { if(cont_path.wait_beats == 0 && !movement.move) { float rad_direction = cont_path.next_direction() * mirrage::util::PI / 180.f; movement.aim.x = std::sin(rad_direction); movement.aim.y = -std::cos(rad_direction); movement.move = true; cont_path.wait_beats = cont_path.pause_between_steps; } else { cont_path.wait_beats--; } } } } void Enemy_system::do_hunting_movement(Beat_state& beat) { for(auto&& [movement, follow_target, transform] : _entity_manager.list()) { if(beat.beat) { if(follow_target.wait_beats == 0 && !movement.move) { auto target_facet = _entity_manager.get(follow_target.target); if(target_facet.is_some()) { auto target_transform = target_facet.get_or_throw().get(); if(target_transform.is_some()) { auto& target_position = target_transform.get_or_throw().position; auto& my_position = transform.position; movement.aim = glm::vec2( {target_position.x - my_position.x, target_position.z - my_position.z}); movement.move = true; } } follow_target.wait_beats = follow_target.pause_between_steps; } else { follow_target.wait_beats--; } } } } void Enemy_system::do_shooting(Beat_state& beat) { for(auto&& [entity, shooting, transform] : _entity_manager.list()) { auto my_position = transform.position; bool shoot = false; for(auto&& [player, player_transform] : _entity_manager.list()) { if(glm::length(glm::vec2{player_transform.position.x, player_transform.position.z} - glm::vec2{my_position.x, my_position.z}) <= trigger_distance_shooting) { shoot = true; } } glm::vec3 closest_target_pos(0.f); float closest_dist = 999999999.f; for(auto&& [target, target_comp, target_transform] : _entity_manager.list()) { auto target_position = target_transform.position; auto dist = glm::length(glm::vec2(target_position.x, target_position.z) - glm::vec2(my_position.x, my_position.z)); if(dist < closest_dist) { closest_dist = dist; closest_target_pos = target_position; } } glm::vec2 t_direction{closest_target_pos.x - my_position.x, closest_target_pos.z - my_position.z}; shooting.target_direction = std::acos(-t_direction.y / glm::length(t_direction)) * 180.f / mirrage::util::PI; if(t_direction.x < 0) { shooting.target_direction *= -1; } if(shooting.attack_radius >= 0 && closest_dist <= shooting.attack_radius) { shooting.idle = false; } else { shooting.idle = true; } auto orientation = shooting.target_direction; if(shooting.idle) { orientation = shooting.default_orientation + shooting.rotation; } auto rad_orientation = orientation * mirrage::util::PI / 180.f; if(beat.beat) { if(shooting.idle && shooting.rotate) { shooting.do_rotation(); shooting.rotate = false; } if(shooting.wait_beats == 0) { auto bullet_pattern = shooting.next_pattern(); auto spawn_position = transform.position + shooting.spawn_offset * glm::vec3(std::sin(rad_orientation), 0, -std::cos(rad_orientation)) + glm::vec3{0.f, 1.f, 0.f}; if(shoot) { for(auto bullet : bullet_pattern.bullets) { auto bullet_direction = orientation + bullet.direction; auto rad_bullet_direction = bullet_direction * mirrage::util::PI / 180.f; _entity_manager.entity_builder("bullet") .position(spawn_position) .rotation(glm::rotation({0, 0, 1}, glm::vec3(std::sin(rad_bullet_direction), 0, -std::cos(rad_bullet_direction)))) .post_create([=](auto entity) { entity.process([=](Continuous_path_comp& cont_path) { cont_path.direction = bullet_direction; cont_path.curvature = bullet.curvature; }); entity.process( [=](Movement_comp& move, Continuous_path_comp& cont_path) { float rad_direction = cont_path.direction * mirrage::util::PI / 180.f; move.aim.x = std::sin(rad_direction); move.aim.y = -std::cos(rad_direction); }); }) .create(); } } shooting.wait_beats = shooting.pause_between_shots; if(std::abs(shooting.rotation_per_step) > 0.0001f) { shooting.rotate = true; } } else { shooting.wait_beats--; } } transform.orientation = glm::rotation( {0, 0, 1}, glm::vec3(std::sin(rad_orientation), 0, -std::cos(rad_orientation))); entity.process([&](Movement_comp& movement) { movement.want_orientation = false; }); } } void Enemy_system::do_bullet_hit_detection() { for(auto&& [bullet_handle, bullet, bullet_transform, bullet_body, bullet_movement] : _entity_manager.list()) { for(auto&& [player_handle, player, player_transform, player_body, player_movement] : _entity_manager.list()) { glm::vec2 bullet_pos{bullet_transform.position.x, bullet_transform.position.z}; glm::vec2 last_bullet_pos = bullet_movement.last_position; glm::vec2 player_pos{player_transform.position.x, player_transform.position.z}; glm::vec2 last_player_pos = player_movement.last_position; if(!bullet_movement.moved) { last_bullet_pos = bullet_pos; } if(!player_movement.moved) { last_player_pos = player_pos; } if(intersect(Stadium{last_bullet_pos, bullet_pos, bullet_body.radius}, Stadium{last_player_pos, player_pos, player_body.radius})) { _entity_manager.erase(bullet_handle); _bus.send(player_handle); _bus.send(0.3f, 0.3f); } } } } void Enemy_system::despawn_distant_bullets() { for(auto&& [bullet_handle, bullet, bullet_transform] : _entity_manager.list()) { bool despawn_bullet = true; for (auto&& [player, player_transform] : _entity_manager.list()) { if(glm::length(glm::vec2{bullet_transform.position.x, bullet_transform.position.z} - glm::vec2{player_transform.position.x, player_transform.position.z}) < trigger_distance_despawn_bullets) { despawn_bullet = false; } } if (despawn_bullet) { _entity_manager.erase(bullet_handle); } } } bool Enemy_system::intersect(const Stadium& stad1, const Stadium& stad2) { glm::vec2 direction1 = stad1.point2 - stad1.point1; glm::vec2 direction2 = stad2.point2 - stad2.point1; // check for intersection of the line segments float temp = -direction2.x * direction1.y + direction1.x * direction2.y; if (temp != 0) { float s = (-direction1.y * (stad1.point1.x - stad2.point1.x) + direction1.x * (stad1.point1.y - stad2.point1.y)) / temp; float t = (-direction2.y * (stad1.point1.x - stad2.point1.x) + direction2.x * (stad1.point1.y - stad2.point1.y)) / temp; if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { return true; } } // check the distance from each end point to the other line segment float comb_radius = stad1.radius + stad2.radius; float length2 = glm::length2(direction1); if(length2 <= 0.001f) { if(glm::distance(stad2.point1, stad1.point1) < comb_radius || glm::distance(stad2.point2, stad1.point1) < comb_radius) { return true; } } float lambda = std::clamp(dot(stad2.point1 - stad1.point1, direction1), 0.f, 1.f); if(glm::distance(stad2.point1, stad1.point1 + lambda * direction1) < comb_radius) { return true; } lambda = std::clamp(dot(stad2.point2 - stad1.point1, direction1), 0.f, 1.f); if(glm::distance(stad2.point2, stad1.point1 + lambda * direction1) < comb_radius) { return true; } length2 = glm::length2(direction2); if(length2 <= 0.001f) { if(glm::distance(stad1.point1, stad2.point1) < comb_radius || glm::distance(stad1.point2, stad2.point1) < comb_radius) { return true; } } lambda = std::clamp(dot(stad1.point1 - stad2.point1, direction2), 0.f, 1.f); if(glm::distance(stad1.point1, stad2.point1 + lambda * direction2) < comb_radius) { return true; } lambda = std::clamp(dot(stad1.point2 - stad2.point1, direction2), 0.f, 1.f); if(glm::distance(stad1.point2, stad2.point1 + lambda * direction2) < comb_radius) { return true; } return false; } float Enemy_system::dot(const glm::vec2& a, const glm::vec2& b) { return a.x * b.x + a.y * b.y; } } // namespace phase_shifter::gameplay