diff options
-rw-r--r-- | src/client.rs | 44 | ||||
-rw-r--r-- | src/protocol.rs | 5 | ||||
-rw-r--r-- | src/server.rs | 174 | ||||
-rw-r--r-- | src/shared.rs | 8 | ||||
-rw-r--r-- | src/shared/cooldown.rs | 15 | ||||
-rw-r--r-- | src/shared/imperative.rs | 3 | ||||
-rw-r--r-- | src/shared/projectile.rs | 17 |
7 files changed, 243 insertions, 23 deletions
diff --git a/src/client.rs b/src/client.rs index bac597e..7dbe7fa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,7 @@ use crate::client::network::*; use crate::protocol::*; use crate::shared::imperative::*; +use crate::shared::projectile::*; use crate::shared::*; use bevy::input::mouse::MouseButton; use bevy::prelude::*; @@ -27,8 +28,10 @@ impl Plugin for ClientPlugin { transport: self.transport.clone(), }) .add_systems(Startup, setup) - .add_systems(Update, spawn_players) + .add_systems(Update, render_players) + .add_systems(Update, render_projectiles) .add_systems(Update, move_players) + .add_systems(Update, move_projectiles) .add_systems( FixedPreUpdate, buffer_input.in_set(InputSystemSet::BufferInputs), @@ -41,7 +44,7 @@ fn setup(mut client: ClientMut, mut commands: Commands) { client.connect().unwrap(); } -fn spawn_players( +fn render_players( mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>, mut meshes: ResMut<Assets<Mesh>>, @@ -57,6 +60,30 @@ fn spawn_players( } } +fn render_projectiles( + mut commands: Commands, + mut materials: ResMut<Assets<ColorMaterial>>, + mut meshes: ResMut<Assets<Mesh>>, + projectiles: Query<(Entity, &ProjectilePosition), Added<Projectile>>, +) { + for (entity, position) in projectiles.iter() { + 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.), + ..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_players(mut players: Query<(&mut Transform, &PlayerPosition), Changed<PlayerPosition>>) { for (mut transform, player_position) in players.iter_mut() { transform.translation = Vec3::new(player_position.0.x, player_position.0.y, 0.); @@ -64,6 +91,7 @@ fn move_players(mut players: Query<(&mut Transform, &PlayerPosition), Changed<Pl } fn buffer_input( + attackables: Query<(&PlayerId, &PlayerPosition)>, cameras: Query<(&Camera, &GlobalTransform)>, mouse_input: Res<ButtonInput<MouseButton>>, mut client: ClientMut, @@ -76,7 +104,17 @@ fn buffer_input( if let Some(world_position) = camera.viewport_to_world_2d(camera_transform, cursor_position) { - client.add_input(Inputs::Imperative(Imperative::WalkTo(world_position))); + let mut attackable_player = None; + for (player, position) in attackables.iter() { + if position.0.distance(world_position) < 20. { + attackable_player = Some(player); + } + } + if let Some(target_player) = attackable_player { + client.add_input(Inputs::Imperative(Imperative::Attack(*target_player))); + } else { + client.add_input(Inputs::Imperative(Imperative::WalkTo(world_position))); + } } } } diff --git a/src/protocol.rs b/src/protocol.rs index 86d1fa0..7672489 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,4 +1,6 @@ +use crate::shared::cooldown::*; use crate::shared::imperative::*; +use crate::shared::projectile::*; use crate::shared::*; use bevy::prelude::*; use lightyear::prelude::*; @@ -26,6 +28,9 @@ pub enum Components { PlayerId(PlayerId), PlayerPosition(PlayerPosition), PlayerColor(PlayerColor), + Projectile(Projectile), + ProjectilePosition(ProjectilePosition), + Cooldown(Cooldown), } #[derive(Channel)] diff --git a/src/server.rs b/src/server.rs index 188aad1..52daf1a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,11 @@ use crate::protocol::*; use crate::server::network::*; +use crate::shared::cooldown::*; use crate::shared::imperative::*; +use crate::shared::projectile::*; use crate::shared::*; use bevy::prelude::*; +use bevy::utils::Duration; use bevy::utils::HashMap; use lightyear::prelude::*; use rand::Rng; @@ -29,22 +32,43 @@ impl Plugin for ServerPlugin { .add_plugins(NetworkPlugin { transport: self.transport.clone(), }) - .add_systems(Update, connections) - .add_systems(FixedUpdate, player_input) - .add_systems(Update, imperative); + .add_systems(Startup, setup) + .add_systems(Update, (connects, disconnects)) + .add_systems( + Update, + ( + imperative_attack.after(cooldown_decrement), + imperative_walk_to, + ) + .after(player_input), + ) + .add_systems( + Update, + ( + projectile_move.after(imperative_walk_to), + projectile_despawn, + ) + .chain(), + ) + .add_systems(Update, cooldown_decrement) + .add_systems(FixedUpdate, player_input); } } -fn connections( +fn setup(mut commands: Commands, mut entity_map: ResMut<EntityMap>) { + let client_id = 1; + let entity = commands.spawn(PlayerBundle::new(1, Vec2::ZERO, Color::GRAY)); + entity_map.0.insert(client_id, entity.id()); +} + +fn connects( mut commands: Commands, mut connects: EventReader<server::ConnectEvent>, - mut disconnects: EventReader<server::DisconnectEvent>, mut entity_map: ResMut<EntityMap>, ) { let mut rng = rand::thread_rng(); for connection in connects.read() { let client_id = connection.context(); - info!("connected: {:?}", client_id); let position = Vec2::new( 50. * rng.gen_range(-2..=2) as f32, 50. * rng.gen_range(-2..=2) as f32, @@ -53,9 +77,15 @@ fn connections( let entity = commands.spawn(PlayerBundle::new(*client_id, position, color)); entity_map.0.insert(*client_id, entity.id()); } +} + +fn disconnects( + mut commands: Commands, + mut disconnects: EventReader<server::DisconnectEvent>, + mut entity_map: ResMut<EntityMap>, +) { for connection in disconnects.read() { let client_id = connection.context(); - info!("disconnected: {:?}", client_id); if let Some(entity_id) = entity_map.0.remove(client_id) { commands.entity(entity_id).despawn(); } @@ -86,22 +116,130 @@ fn player_input( const MOVEMENT_SPEED: f32 = 80.; -fn imperative(mut players: Query<(&mut Imperative, &mut PlayerPosition)>, time: Res<Time>) { - for (mut imperative, mut position) in players.iter_mut() { +fn imperative_walk_to( + mut players: Query<(Entity, &mut Imperative)>, + mut positions: Query<&mut PlayerPosition>, + time: Res<Time>, +) { + for (entity, mut imperative) in players.iter_mut() { match *imperative { - Imperative::Idle => {} - Imperative::WalkTo(target) => { - let distance = (target - position.0).length(); - let direction = (target - position.0).normalize_or_zero(); - let new_position = position.0 - + f32::min(MOVEMENT_SPEED * time.delta().as_secs_f32(), distance) * direction; - if position.0.distance(new_position) < f32::EPSILON { - position.0 = target; + Imperative::WalkTo(target_position) => { + if let Ok(mut position) = positions.get_mut(entity) { + let distance = (target_position - position.0).length(); + let direction = (target_position - position.0).normalize_or_zero(); + let new_position = position.0 + + f32::min(MOVEMENT_SPEED * time.delta().as_secs_f32(), distance) + * direction; + if position.0.distance(new_position) < f32::EPSILON { + position.0 = target_position; + *imperative = Imperative::Idle; + } else { + position.0 = new_position; + } + } else { *imperative = Imperative::Idle; + } + } + _ => {} + } + } +} + +fn imperative_attack( + entity_map: Res<EntityMap>, + mut commands: Commands, + mut cooldowns: Query<&mut Cooldown>, + mut players: Query<(&PlayerId, &mut Imperative)>, + positions: Query<&PlayerPosition>, +) { + for (id, mut imperative) in players.iter_mut() { + match *imperative { + Imperative::Attack(target_player) => { + if let Some(entity) = entity_map.0.get(&id.0) { + if let Ok(position) = positions.get(*entity) { + if let Ok(mut cooldown) = cooldowns.get_mut(*entity) { + if cooldown.a_cooldown.is_zero() { + cooldown.a_cooldown = Duration::from_secs_f32(1.5); + commands.spawn(ProjectileBundle { + projectile: Projectile { + target_player, + source_player: *id, + }, + position: ProjectilePosition(position.0), + replicate: Replicate::default(), + }); + } + } else { + *imperative = Imperative::Idle; + } + } else { + *imperative = Imperative::Idle; + } } else { - position.0 = new_position; + *imperative = Imperative::Idle; + } + } + _ => {} + } + } +} + +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)>, + 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 distance = (target_position.0 - position.0).length(); + let direction = (target_position.0 - position.0).normalize_or_zero(); + let new_position = position.0 + + f32::min(PROJECTILE_SPEED * time.delta().as_secs_f32(), distance) + * direction; + if position.0.distance(new_position) < f32::EPSILON { + position.0 = target_position.0; + } else { + position.0 = new_position; + } + } + } + } + } +} + +fn projectile_despawn( + entity_map: Res<EntityMap>, + mut commands: Commands, + projectile_positions: Query<&ProjectilePosition>, + player_positions: Query<&PlayerPosition>, + projectiles: Query<(Entity, &mut Projectile)>, +) { + for (entity, projectile) in projectiles.iter() { + if let Some(target_entity) = entity_map.0.get(&projectile.target_player.0) { + if let Ok(position) = projectile_positions.get(entity) { + if let Ok(target_position) = player_positions.get(*target_entity) { + if position.0.distance(target_position.0) < f32::EPSILON { + commands.entity(entity).despawn(); + } } } } } } + +fn cooldown_decrement(mut cooldowns: Query<&mut Cooldown>, time: Res<Time>) { + for mut cooldown in cooldowns.iter_mut() { + cooldown.a_cooldown = cooldown.a_cooldown.saturating_sub(time.delta()); + cooldown.q_cooldown = cooldown.q_cooldown.saturating_sub(time.delta()); + cooldown.w_cooldown = cooldown.w_cooldown.saturating_sub(time.delta()); + cooldown.e_cooldown = cooldown.e_cooldown.saturating_sub(time.delta()); + cooldown.d_cooldown = cooldown.d_cooldown.saturating_sub(time.delta()); + cooldown.f_cooldown = cooldown.f_cooldown.saturating_sub(time.delta()); + } +} diff --git a/src/shared.rs b/src/shared.rs index 5d6cbed..146e343 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,11 +1,15 @@ use crate::protocol::Replicate; +use crate::shared::cooldown::*; use crate::shared::imperative::*; use bevy::prelude::*; use lightyear::prelude::*; use serde::Deserialize; use serde::Serialize; +use std::default::Default; +pub mod cooldown; pub mod imperative; +pub mod projectile; pub const CLIENT_ID: u64 = 0; pub const KEY: [u8; 32] = [ @@ -21,9 +25,10 @@ pub struct PlayerBundle { color: PlayerColor, replicate: Replicate, imperative: Imperative, + cooldown: Cooldown, } -#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Component, Message, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] pub struct PlayerId(pub ClientId); #[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -40,6 +45,7 @@ impl PlayerBundle { color: PlayerColor(color), replicate: Replicate::default(), imperative: Imperative::Idle, + cooldown: Cooldown::default(), } } } diff --git a/src/shared/cooldown.rs b/src/shared/cooldown.rs new file mode 100644 index 0000000..52f41de --- /dev/null +++ b/src/shared/cooldown.rs @@ -0,0 +1,15 @@ +use crate::shared::*; +use bevy::utils::Duration; +use serde::Deserialize; +use serde::Serialize; +use std::default::Default; + +#[derive(Component, Message, Serialize, Deserialize, Clone, Copy, PartialEq, Debug, Default)] +pub struct Cooldown { + pub a_cooldown: Duration, + pub q_cooldown: Duration, + pub w_cooldown: Duration, + pub e_cooldown: Duration, + pub d_cooldown: Duration, + pub f_cooldown: Duration, +} diff --git a/src/shared/imperative.rs b/src/shared/imperative.rs index 1370ddb..d9d727f 100644 --- a/src/shared/imperative.rs +++ b/src/shared/imperative.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use crate::shared::*; use serde::Deserialize; use serde::Serialize; @@ -6,4 +6,5 @@ use serde::Serialize; pub enum Imperative { Idle, WalkTo(Vec2), + Attack(PlayerId), } diff --git a/src/shared/projectile.rs b/src/shared/projectile.rs new file mode 100644 index 0000000..59dec30 --- /dev/null +++ b/src/shared/projectile.rs @@ -0,0 +1,17 @@ +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 source_player: PlayerId, +} + +#[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ProjectilePosition(pub Vec2); |