diff options
-rw-r--r-- | src/client.rs | 89 | ||||
-rw-r--r-- | src/protocol.rs | 1 | ||||
-rw-r--r-- | src/server.rs | 174 | ||||
-rw-r--r-- | src/shared.rs | 1 | ||||
-rw-r--r-- | src/shared/imperative.rs | 3 | ||||
-rw-r--r-- | src/shared/projectile.rs | 29 |
6 files changed, 223 insertions, 74 deletions
diff --git a/src/client.rs b/src/client.rs index b2db950..7f70549 100644 --- a/src/client.rs +++ b/src/client.rs @@ -15,7 +15,6 @@ use std::net::SocketAddr; mod network; -const PLAYER_RADIUS: f32 = 10.; const PLAYER_HOVER_INDICATOR_RADIUS: f32 = 13.; const PLAYER_HOVER_RADIUS: f32 = 20.; @@ -111,23 +110,35 @@ fn render_projectiles( mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>, mut meshes: ResMut<Assets<Mesh>>, - projectiles: Query<(Entity, &ProjectilePosition), Added<Projectile>>, + projectiles: Query<(Entity, &Projectile), Added<Projectile>>, ) { - for (entity, position) in projectiles.iter() { + for (entity, projectile) in projectiles.iter() { + let Some(position) = (match projectile.type_ { + ProjectileType::Free(FreeProjectile { position, .. }) => Some(position), + ProjectileType::Instant(InstantProjectile { .. }) => None, + ProjectileType::Targeted(TargetedProjectile { position, .. }) => Some(position), + }) else { + continue; + }; commands.entity(entity).insert(MaterialMesh2dBundle { mesh: Mesh2dHandle(meshes.add(Circle { radius: 2. })), material: materials.add(Color::RED), - transform: Transform::from_xyz(position.0.x, position.0.y, 1.), + transform: Transform::from_xyz(position.x, position.y, 1.), ..Default::default() }); } } -fn move_projectiles( - mut projectiles: Query<(&mut Transform, &ProjectilePosition), Changed<ProjectilePosition>>, -) { - for (mut transform, projectile_position) in projectiles.iter_mut() { - transform.translation = Vec3::new(projectile_position.0.x, projectile_position.0.y, 0.); +fn move_projectiles(mut projectiles: Query<(&mut Transform, &Projectile), Changed<Projectile>>) { + for (mut transform, projectile) in projectiles.iter_mut() { + let Some(position) = (match projectile.type_ { + ProjectileType::Free(FreeProjectile { position, .. }) => Some(position), + ProjectileType::Instant(InstantProjectile { .. }) => None, + ProjectileType::Targeted(TargetedProjectile { position, .. }) => Some(position), + }) else { + continue; + }; + transform.translation = Vec3::new(position.x, position.y, 0.); } } @@ -138,46 +149,66 @@ fn move_players(mut players: Query<(&mut Transform, &PlayerPosition), Changed<Pl } fn buffer_input( - attackables: Query<(&PlayerId, &PlayerPosition)>, + players: Query<(&PlayerId, &PlayerPosition)>, + mut attack: ResMut<Attack>, cameras: Query<(&Camera, &GlobalTransform)>, client_id: Res<ClientId>, mouse_input: Res<ButtonInput<MouseButton>>, mut client: ClientMut, windows: Query<&Window>, - attack: Res<Attack>, ) { if mouse_input.just_pressed(MouseButton::Left) { - if let Some((target_player, _)) = - hovered_other_player(&cameras, &client_id, &attackables, &windows) - { - client.add_input(Inputs::Imperative(Imperative::Attack( - attack.0.unwrap_or(AttackKey::A), - target_player, - ))); - } else { - if let Some(world_position) = cursor_world_position(&windows, &cameras) { - client.add_input(Inputs::Imperative(Imperative::WalkTo(world_position))); + match attack.0 { + Some(AttackKey::Q) => { + let Some(world_position) = cursor_world_position(&windows, &cameras) else { + return; + }; + let Some(position) = player_position(&client_id, &players) else { + return; + }; + let Some(direction) = (world_position - position.0).try_normalize() else { + return; + }; + client.add_input(Inputs::Imperative(Imperative::AttackDirection( + AttackKey::Q, + direction, + ))); + attack.0 = None; + } + _ => { + if let Some((target_player, _)) = + hovered_other_player(&cameras, &client_id, &players, &windows) + { + client.add_input(Inputs::Imperative(Imperative::AttackTarget( + attack.0.unwrap_or(AttackKey::A), + target_player, + ))); + } else { + if let Some(world_position) = cursor_world_position(&windows, &cameras) { + client.add_input(Inputs::Imperative(Imperative::WalkTo(world_position))); + } + } } } } } fn choose_attack(keyboard_input: Res<ButtonInput<KeyCode>>, mut attack_key: ResMut<Attack>) { - if keyboard_input.pressed(KeyCode::KeyA) { + if keyboard_input.just_pressed(KeyCode::KeyA) { attack_key.0 = Some(AttackKey::A); - } else if keyboard_input.pressed(KeyCode::KeyQ) { + } else if keyboard_input.just_pressed(KeyCode::KeyQ) { attack_key.0 = Some(AttackKey::Q); - } else if keyboard_input.pressed(KeyCode::KeyW) { + } else if keyboard_input.just_pressed(KeyCode::KeyW) { attack_key.0 = Some(AttackKey::W); - } else if keyboard_input.pressed(KeyCode::KeyE) { + } else if keyboard_input.just_pressed(KeyCode::KeyE) { attack_key.0 = Some(AttackKey::E); - } else if keyboard_input.pressed(KeyCode::KeyR) { + } else if keyboard_input.just_pressed(KeyCode::KeyR) { attack_key.0 = Some(AttackKey::R); - } else if keyboard_input.pressed(KeyCode::KeyD) { + } else if keyboard_input.just_pressed(KeyCode::KeyD) { attack_key.0 = Some(AttackKey::D); - } else if keyboard_input.pressed(KeyCode::KeyF) { + } else if keyboard_input.just_pressed(KeyCode::KeyF) { attack_key.0 = Some(AttackKey::F); - } else if keyboard_input.pressed(KeyCode::Escape) { + } else if keyboard_input.just_pressed(KeyCode::Escape) { attack_key.0 = None; } } diff --git a/src/protocol.rs b/src/protocol.rs index 7ec3dcb..70f4f64 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -31,7 +31,6 @@ pub enum Components { PlayerPosition(PlayerPosition), PlayerColor(PlayerColor), Projectile(Projectile), - ProjectilePosition(ProjectilePosition), Cooldown(Cooldown), Health(Health), Champion(Champion), diff --git a/src/server.rs b/src/server.rs index e3d5a72..b2f22b3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -189,6 +189,10 @@ fn move_to_target(position: Vec2, target_position: Vec2, max_distance: f32) -> ( } } +fn move_along_direction(position: Vec2, direction: Vec2, distance: f32) -> Vec2 { + position + distance * direction +} + fn imperative_attack_approach( entity_map: Res<EntityMap>, mut players: Query<(&PlayerId, &mut Imperative)>, @@ -198,7 +202,7 @@ fn imperative_attack_approach( ) { for (id, mut imperative) in players.iter_mut() { match *imperative { - Imperative::Attack(AttackKey::A, target_player) => { + Imperative::AttackTarget(_, target_player) => { let Some(entity) = entity_map.0.get(&id.0) else { *imperative = Imperative::Idle; return; @@ -242,7 +246,7 @@ fn imperative_attack_attack( ) { for (id, mut imperative) in players.iter_mut() { match *imperative { - Imperative::Attack(AttackKey::A, target_player) => { + Imperative::AttackTarget(_, target_player) => { let Some(entity) = entity_map.0.get(&id.0) else { *imperative = Imperative::Idle; return; @@ -269,24 +273,54 @@ fn imperative_attack_attack( }; if cooldown.a_cooldown.is_zero() { cooldown.a_cooldown = Duration::from_secs_f32(1.5); - let instant = *champion == Champion::Meele; + let projectile_type = if *champion == Champion::Meele { + ProjectileType::Instant(InstantProjectile { target_player }) + } else { + ProjectileType::Targeted(TargetedProjectile { + target_player, + position: position.0, + }) + }; commands.spawn(ProjectileBundle { projectile: Projectile { - target_player, source_player: *id, damage: 4., - instant, + type_: projectile_type, }, - position: ProjectilePosition(if instant { - target_position.0 - } else { - position.0 - }), replicate: Replicate::default(), }); } } } + Imperative::AttackDirection(_, direction) => { + let Some(entity) = entity_map.0.get(&id.0) else { + *imperative = Imperative::Idle; + return; + }; + let Ok(position) = positions.get_mut(*entity) else { + *imperative = Imperative::Idle; + return; + }; + let Ok(mut cooldown) = cooldowns.get_mut(*entity) else { + *imperative = Imperative::Idle; + return; + }; + if cooldown.a_cooldown.is_zero() { + cooldown.a_cooldown = Duration::from_secs_f32(1.5); + commands.spawn(ProjectileBundle { + projectile: Projectile { + source_player: *id, + damage: 4., + type_: ProjectileType::Free(FreeProjectile { + position: position.0, + direction, + starting_position: position.0, + }), + }, + replicate: Replicate::default(), + }); + } + } _ => {} } } @@ -296,24 +330,40 @@ const PROJECTILE_SPEED: f32 = 150.; fn projectile_move( entity_map: Res<EntityMap>, - mut projectile_positions: Query<&mut ProjectilePosition>, player_positions: Query<&PlayerPosition>, - projectiles: Query<(Entity, &mut Projectile)>, + mut projectiles: Query<&mut Projectile>, time: Res<Time>, ) { - for (entity, projectile) in projectiles.iter() { - if let Some(target_entity) = entity_map.0.get(&projectile.target_player.0) { - if let Ok(mut position) = projectile_positions.get_mut(entity) { - if let Ok(target_position) = player_positions.get(*target_entity) { - let (new_position, _) = move_to_target( - position.0, - target_position.0, - PROJECTILE_SPEED * time.delta().as_secs_f32(), - ); - position.0 = new_position; + for mut projectile in projectiles.iter_mut() { + let new_type = match projectile.type_.clone() { + ProjectileType::Free(mut free_projectile) => { + let new_position = move_along_direction( + free_projectile.position, + free_projectile.direction, + PROJECTILE_SPEED * time.delta().as_secs_f32(), + ); + free_projectile.position = new_position; + ProjectileType::Free(free_projectile) + } + ProjectileType::Instant(instant_projectile) => { + ProjectileType::Instant(instant_projectile) + } + ProjectileType::Targeted(mut targeted_projectile) => { + if let Some(target_entity) = entity_map.0.get(&targeted_projectile.target_player.0) + { + if let Ok(target_position) = player_positions.get(*target_entity) { + let (new_position, _) = move_to_target( + targeted_projectile.position.clone(), + target_position.0, + PROJECTILE_SPEED * time.delta().as_secs_f32(), + ); + targeted_projectile.position = new_position; + } } + ProjectileType::Targeted(targeted_projectile) } - } + }; + projectile.type_ = new_type; } } @@ -322,25 +372,71 @@ fn projectile_despawn( mut commands: Commands, mut healths: Query<&mut Health>, player_positions: Query<&PlayerPosition>, - projectile_positions: Query<&ProjectilePosition>, + projectile_targets: Query<(&PlayerId, &PlayerPosition)>, projectiles: Query<(Entity, &mut Projectile)>, ) { for (entity, projectile) in projectiles.iter() { - let Some(target_entity) = entity_map.0.get(&projectile.target_player.0) else { - commands.entity(entity).despawn(); - return; - }; - let Ok(position) = projectile_positions.get(entity) else { - commands.entity(entity).despawn(); - return; - }; - let Ok(target_position) = player_positions.get(*target_entity) else { - commands.entity(entity).despawn(); - return; - }; - if position.0.distance(target_position.0) <= f32::EPSILON { - if let Ok(mut health) = healths.get_mut(*target_entity) { - health.0 = (health.0 - projectile.damage).max(0.); + let (despawn, maybe_target_player): (bool, Option<PlayerId>) = (|| match &projectile.type_ { + ProjectileType::Free(free_projectile) => { + let mut maybe_target_player = None; + let mut maybe_target_distance = None; + for (player, position) in projectile_targets.iter() { + if *player == projectile.source_player { + continue; + } + let distance = free_projectile.position.distance(position.0); + if distance > PLAYER_RADIUS { + continue; + } + match maybe_target_distance { + Some(old_distance) => { + if distance < old_distance { + maybe_target_player = Some(player); + maybe_target_distance = Some(distance); + } + } + None => { + maybe_target_player = Some(player); + maybe_target_distance = Some(distance); + } + } + } + if let Some(target_player) = maybe_target_player { + (true, Some(*target_player)) + } else { + ( + free_projectile + .position + .distance(free_projectile.starting_position) + >= 10000.0, + None, + ) + } + } + ProjectileType::Instant(instant_projectile) => { + (true, Some(instant_projectile.target_player)) + } + ProjectileType::Targeted(targeted_projectile) => { + let Some(target_entity) = entity_map.0.get(&targeted_projectile.target_player.0) + else { + return (true, None); + }; + let Ok(target_position) = player_positions.get(*target_entity) else { + return (true, None); + }; + ( + targeted_projectile.position.distance(target_position.0) <= f32::EPSILON, + Some(targeted_projectile.target_player), + ) + } + })(); + if despawn { + if let Some(target_player) = maybe_target_player { + if let Some(target_entity) = entity_map.0.get(&target_player.0) { + if let Ok(mut health) = healths.get_mut(*target_entity) { + health.0 = (health.0 - projectile.damage).max(0.); + } + } } commands.entity(entity).despawn(); } diff --git a/src/shared.rs b/src/shared.rs index 16cea60..a7a2d16 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -18,6 +18,7 @@ pub mod projectile; pub const KEY: [u8; 32] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; +pub const PLAYER_RADIUS: f32 = 10.; pub const PROTOCOL_ID: u64 = 0; pub const SERVER_PORT: u16 = 16384; diff --git a/src/shared/imperative.rs b/src/shared/imperative.rs index 256ea1a..61a4762 100644 --- a/src/shared/imperative.rs +++ b/src/shared/imperative.rs @@ -6,7 +6,8 @@ use serde::Serialize; pub enum Imperative { Idle, WalkTo(Vec2), - Attack(AttackKey, PlayerId), + AttackTarget(AttackKey, PlayerId), + AttackDirection(AttackKey, Vec2), } #[derive(Resource, Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] diff --git a/src/shared/projectile.rs b/src/shared/projectile.rs index 16a064a..f944e8a 100644 --- a/src/shared/projectile.rs +++ b/src/shared/projectile.rs @@ -3,17 +3,38 @@ use crate::shared::*; #[derive(Bundle)] pub struct ProjectileBundle { pub projectile: Projectile, - pub position: ProjectilePosition, pub replicate: Replicate, } #[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] + pub struct Projectile { - pub target_player: PlayerId, + pub type_: ProjectileType, pub source_player: PlayerId, pub damage: f32, - pub instant: bool, } #[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct ProjectilePosition(pub Vec2); +pub enum ProjectileType { + Free(FreeProjectile), + Instant(InstantProjectile), + Targeted(TargetedProjectile), +} + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct FreeProjectile { + pub position: Vec2, + pub direction: Vec2, + pub starting_position: Vec2, +} + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct InstantProjectile { + pub target_player: PlayerId, +} + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct TargetedProjectile { + pub target_player: PlayerId, + pub position: Vec2, +} |