aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-17 09:26:25 +0100
committerLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-17 13:12:19 +0100
commit601474aa84cfc071c724051ac121e3c9cc796577 (patch)
tree058728022e968100e5a2314d5cc2e16de0f3bfee /src
parent4f9957fc6b4bdb4c7430f878101b07051b40a2ff (diff)
feat: skillshots
Diffstat (limited to 'src')
-rw-r--r--src/client.rs89
-rw-r--r--src/protocol.rs1
-rw-r--r--src/server.rs174
-rw-r--r--src/shared.rs1
-rw-r--r--src/shared/imperative.rs3
-rw-r--r--src/shared/projectile.rs29
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,
+}