diff --git a/assets/maps/map1.map b/assets/maps/map1.map index 87631e2..c272d08 100644 --- a/assets/maps/map1.map +++ b/assets/maps/map1.map @@ -1116,6 +1116,7 @@ "angles" "0 -90 0" "head" "field medic" "key" "fence_gate" +"disable_ai" "true" } // entity 3 { @@ -1128,6 +1129,7 @@ "origin" "3256 248 -232" "head" "field medic" "angles" "0 0 0" +"disable_ai" "true" } // entity 5 { diff --git a/src/ai/mod.rs b/src/ai/mod.rs index fe175e2..2cb82b6 100644 --- a/src/ai/mod.rs +++ b/src/ai/mod.rs @@ -6,19 +6,141 @@ use crate::{ aim::AimTarget, heads::ActiveHeads, heads_database::HeadsDatabase, + player::Player, }; #[derive(Component, Reflect)] #[reflect(Component)] pub struct Ai; +#[derive(Component, Reflect, Clone)] +#[reflect(Component)] +struct WaitForAnyPlayer; + +#[derive(Component, Reflect, Clone)] +#[reflect(Component)] +struct Engage(Entity); + +#[derive(Component, Reflect, Clone)] +#[reflect(Component)] +struct Reload; + pub fn plugin(app: &mut App) { - app.add_systems(Update, update.run_if(in_state(GameState::Playing))); + app.add_systems( + Update, + ( + engage_and_throw, + wait_for_player, + out_of_range, + detect_reload, + detect_reload_done, + rotate, + ) + .run_if(in_state(GameState::Playing)), + ); + app.add_systems(Update, on_ai_added); } -fn update( +fn on_ai_added(mut commands: Commands, query: Query>) { + for entity in query.iter() { + commands.entity(entity).insert(WaitForAnyPlayer); + } +} + +fn wait_for_player( mut commands: Commands, - mut query: Query<(&mut ActiveHeads, &AimTarget, &Transform), With>, + agents: Query>, + transform: Query<&Transform>, + players: Query>, +) { + for agent in agents.iter() { + if let Some(player) = in_range(50., agent, &players, &transform) { + info!("[{agent}] Engage: {player}"); + commands + .entity(agent) + .remove::() + .insert(Engage(player)); + } + } +} + +fn out_of_range( + mut commands: Commands, + agents: Query>, + transform: Query<&Transform>, + players: Query>, +) { + for agent in agents.iter() { + if in_range(100., agent, &players, &transform).is_none() { + info!("[{agent}] Player out of range"); + commands + .entity(agent) + .remove::() + .insert(WaitForAnyPlayer); + } + } +} + +fn detect_reload(mut commands: Commands, agents: Query<(Entity, &ActiveHeads), With>) { + for (e, head) in agents.iter() { + if head.reloading() { + info!("[{e}] Reload started"); + commands.entity(e).remove::().insert(Reload); + } + } +} + +fn detect_reload_done(mut commands: Commands, agents: Query<(Entity, &ActiveHeads), With>) { + for (e, head) in agents.iter() { + if !head.reloading() { + info!("[{e}] Reload done"); + commands + .entity(e) + .remove::() + .insert(WaitForAnyPlayer); + } + } +} + +fn in_range( + range: f32, + entity: Entity, + players: &Query<'_, '_, Entity, With>, + transform: &Query<'_, '_, &Transform>, +) -> Option { + let Ok(pos) = transform.get(entity).map(|t| t.translation) else { + return None; + }; + + players + .iter() + .filter_map(|p| transform.get(p).ok().map(|t| (p, *t))) + .find(|(_, t)| t.translation.distance(pos) < range) + .map(|(e, _)| e) +} + +fn rotate(agent: Query<(Entity, &Engage)>, mut transform: Query<&mut Transform>) { + for (agent, Engage(target)) in agent.iter() { + let Ok(target_pos) = transform.get(*target).map(|t| t.translation) else { + continue; + }; + let Ok(mut agent_transform) = transform.get_mut(agent) else { + continue; + }; + + // Get the direction vector from the current position to the target + let direction = (target_pos - agent_transform.translation).normalize(); + + // Project the direction onto the XZ plane by zeroing out the Y component + let xz_direction = Vec3::new(direction.x, 0.0, direction.z).normalize(); + + agent_transform.rotation = Quat::from_rotation_arc(Vec3::Z, xz_direction); + } +} + +fn engage_and_throw( + mut commands: Commands, + mut query: Query<(&mut ActiveHeads, &AimTarget, &Transform), With>, time: Res