aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.rs44
-rw-r--r--src/protocol.rs5
-rw-r--r--src/server.rs174
-rw-r--r--src/shared.rs8
-rw-r--r--src/shared/cooldown.rs15
-rw-r--r--src/shared/imperative.rs3
-rw-r--r--src/shared/projectile.rs17
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);