diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client.rs | 83 | ||||
-rw-r--r-- | src/protocol.rs | 4 | ||||
-rw-r--r-- | src/server.rs | 153 | ||||
-rw-r--r-- | src/shared.rs | 3 | ||||
-rw-r--r-- | src/shared/ability.rs | 8 | ||||
-rw-r--r-- | src/shared/activation.rs | 16 | ||||
-rw-r--r-- | src/shared/buffs.rs | 7 | ||||
-rw-r--r-- | src/shared/champion.rs | 15 | ||||
-rw-r--r-- | src/shared/imperative.rs | 2 | ||||
-rw-r--r-- | src/shared/player.rs | 15 | ||||
-rw-r--r-- | src/shared/stats.rs | 22 |
11 files changed, 249 insertions, 79 deletions
diff --git a/src/client.rs b/src/client.rs index 7ccced7..3679b85 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,7 @@ use crate::client::network::*; use crate::protocol::*; use crate::shared::ability::*; +use crate::shared::activation::*; use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; @@ -8,6 +9,7 @@ use crate::shared::health_event::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::stats::*; use crate::shared::*; use bevy::input::keyboard::*; use bevy::input::mouse::MouseButton; @@ -255,7 +257,8 @@ fn buffer_input( ) { if mouse_input.just_pressed(MouseButton::Left) { match attack.0 { - Some(attack_key) => match champion.0.to_ability(attack_key) { + Some(ability_slot) => match champion.0.to_ability(ability_slot) { + Ability::Activated(_) => {} Ability::Directional(_) => { let Some(world_position) = cursor_world_position(&windows, &cameras) else { return; @@ -267,7 +270,8 @@ fn buffer_input( return; }; client.add_input(Inputs::Imperative(Imperative::AttackDirection( - attack_key, direction, + ability_slot, + direction, ))); attack.0 = None; } @@ -278,7 +282,7 @@ fn buffer_input( return; }; client.add_input(Inputs::Imperative(Imperative::AttackTarget( - attack_key, + ability_slot, target_player, ))); attack.0 = None; @@ -299,26 +303,45 @@ fn buffer_input( } } } + } else { + client.add_input(Inputs::None); } } -fn choose_attack(keyboard_input: Res<ButtonInput<KeyCode>>, mut attack_key: ResMut<Attack>) { +fn choose_attack( + champion: Res<MyChampion>, + keyboard_input: Res<ButtonInput<KeyCode>>, + mut attack: ResMut<Attack>, + mut client: ClientMut, +) { if keyboard_input.just_pressed(KeyCode::KeyA) { - attack_key.0 = Some(AbilitySlot::A); + attack.0 = Some(AbilitySlot::A); } else if keyboard_input.just_pressed(KeyCode::KeyQ) { - attack_key.0 = Some(AbilitySlot::Q); + attack.0 = Some(AbilitySlot::Q); } else if keyboard_input.just_pressed(KeyCode::KeyW) { - attack_key.0 = Some(AbilitySlot::W); + attack.0 = Some(AbilitySlot::W); } else if keyboard_input.just_pressed(KeyCode::KeyE) { - attack_key.0 = Some(AbilitySlot::E); + attack.0 = Some(AbilitySlot::E); } else if keyboard_input.just_pressed(KeyCode::KeyR) { - attack_key.0 = Some(AbilitySlot::R); + attack.0 = Some(AbilitySlot::R); } else if keyboard_input.just_pressed(KeyCode::KeyF) { - attack_key.0 = Some(AbilitySlot::F); + attack.0 = Some(AbilitySlot::F); } else if keyboard_input.just_pressed(KeyCode::KeyG) { - attack_key.0 = Some(AbilitySlot::G); + attack.0 = Some(AbilitySlot::G); } else if keyboard_input.just_pressed(KeyCode::Escape) { - attack_key.0 = None; + attack.0 = None; + } else if keyboard_input.just_pressed(KeyCode::ShiftLeft) { + attack.0 = None; + } + match attack.0 { + Some(ability_slot) => match champion.0.to_ability(ability_slot) { + Ability::Activated(_) => { + client.add_input(Inputs::Activation(Activation::Activate(ability_slot))); + attack.0 = None; + } + _ => {} + }, + None => {} } } @@ -383,6 +406,7 @@ fn gizmos_attack_indicator( attack: Res<Attack>, cameras: Query<(&Camera, &GlobalTransform)>, client_id: Res<ClientId>, + effective_statses: Query<(&PlayerId, &EffectiveStats)>, hoverables: Query<(&PlayerId, &PlayerPosition)>, mut gizmos: Gizmos, player_champions: Query<(&PlayerId, &Champion)>, @@ -395,19 +419,16 @@ fn gizmos_attack_indicator( let Some(champion) = player_champion(&client_id, &player_champions) else { return; }; - let Some(attack_key) = attack.0.or_else(|| { + let Some(ability_slot) = attack.0.or_else(|| { hovered_other_player(&cameras, &client_id, &hoverables, &windows).map(|_| AbilitySlot::A) }) else { return; }; - match champion.to_ability(attack_key) { - Ability::Targeted(_) => { - gizmos.circle_2d( - position.0, - Stats::from_champion(champion).attack_range, - Color::YELLOW, - ); - } + let Some(effective_stats) = player_effective_stats(&client_id, &effective_statses) else { + return; + }; + match champion.to_ability(ability_slot) { + Ability::Activated(_) => {} Ability::Directional(_) => { let Some(world_position) = cursor_world_position(&windows, &cameras) else { return; @@ -417,6 +438,9 @@ fn gizmos_attack_indicator( }; gizmos.arrow_2d(position.0, position.0 + 75. * direction, Color::YELLOW); } + Ability::Targeted(_) => { + gizmos.circle_2d(position.0, effective_stats.0.attack_range, Color::YELLOW); + } } } @@ -432,6 +456,18 @@ fn player_position( None } +fn player_effective_stats( + client_id: &Res<ClientId>, + players: &Query<(&PlayerId, &EffectiveStats)>, +) -> Option<EffectiveStats> { + for (id, effective_stats) in players.iter() { + if id.0 == client_id.0 { + return Some(*effective_stats); + } + } + None +} + fn player_champion( client_id: &Res<ClientId>, players: &Query<(&PlayerId, &Champion)>, @@ -506,11 +542,8 @@ fn hotbar_hotbar_display( } fn hotbar_hotbar_highlight(attack: Res<Attack>, mut hotbars: Query<(&Hotbar, &mut Text)>) { - let Some(ability_slot) = attack.0 else { - return; - }; for (hotbar, mut text) in hotbars.iter_mut() { - let is_highlighted = ability_slot == hotbar.0; + let is_highlighted = attack.0 == Some(hotbar.0); if text.sections.len() <= 0 { continue; } diff --git a/src/protocol.rs b/src/protocol.rs index 1b7eaa0..f8f1f62 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,3 +1,4 @@ +use crate::shared::activation::*; use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; @@ -5,6 +6,7 @@ use crate::shared::health_event::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::stats::*; use bevy::prelude::*; use lightyear::prelude::*; use serde::Deserialize; @@ -13,6 +15,7 @@ use serde::Serialize; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum Inputs { Imperative(Imperative), + Activation(Activation), None, } impl UserAction for Inputs {} @@ -38,6 +41,7 @@ pub enum Components { Cooldown(Cooldown), Health(Health), Champion(Champion), + EffectiveStats(EffectiveStats), } #[derive(Channel)] diff --git a/src/server.rs b/src/server.rs index 038f854..0b15c5f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,8 @@ use crate::protocol::*; use crate::server::network::*; use crate::shared::ability::*; +use crate::shared::activation::*; +use crate::shared::buffs::*; use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; @@ -8,6 +10,7 @@ use crate::shared::health_event::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::stats::*; use crate::shared::*; use bevy::prelude::*; use bevy::utils::HashMap; @@ -53,13 +56,15 @@ impl Plugin for ServerPlugin { .add_systems(Update, (connects, disconnects)) .add_systems(Update, receive_message) .add_systems(FixedUpdate, timers_tick) + .add_systems(FixedUpdate, effective_stats) .add_systems(FixedUpdate, health_regen.after(timers_tick)) .add_systems( FixedUpdate, ( (imperative_attack_approach, imperative_attack_attack) .chain() - .after(cooldown_decrement), + .after(activation), + activation.after(cooldown_decrement), imperative_walk_to, ) .after(player_input), @@ -73,6 +78,7 @@ impl Plugin for ServerPlugin { .chain(), ) .add_systems(FixedUpdate, cooldown_decrement) + .add_systems(FixedUpdate, (buffs_despawn, buffs_tick).chain()) .add_systems(FixedUpdate, player_input); } } @@ -110,7 +116,7 @@ fn receive_message( let client_id = event.context(); let SelectChampion(champion) = event.message(); let Some(entity) = entity_map.0.get(client_id) else { - return; + continue; }; commands.entity(*entity).insert(*champion); } @@ -133,6 +139,7 @@ fn player_input( entity_map: Res<EntityMap>, mut input_reader: EventReader<server::InputEvent<Inputs>>, mut imperatives: Query<&mut Imperative>, + mut activations: Query<&mut Activation>, ) { for input in input_reader.read() { let client_id = input.context(); @@ -144,28 +151,31 @@ fn player_input( *imperative = *new_imperative; } } - _ => {} + Inputs::Activation(new_activation) => { + if let Ok(mut activation) = activations.get_mut(*entity_id) { + *activation = *new_activation; + } + } + Inputs::None => {} } } } } } -const MOVEMENT_SPEED: f32 = 80.; - fn imperative_walk_to( - mut players: Query<(Entity, &mut Imperative)>, + mut players: Query<(Entity, &mut Imperative, &EffectiveStats)>, mut positions: Query<&mut PlayerPosition>, time: Res<Time>, ) { - for (entity, mut imperative) in players.iter_mut() { + for (entity, mut imperative, effective_stats) in players.iter_mut() { match *imperative { Imperative::WalkTo(target_position) => { if let Ok(mut position) = positions.get_mut(entity) { let (new_position, target_reached) = move_to_target( position.0, target_position, - MOVEMENT_SPEED * time.delta().as_secs_f32(), + effective_stats.0.movement_speed * time.delta().as_secs_f32(), ); position.0 = new_position; if target_reached { @@ -197,38 +207,33 @@ fn move_along_direction(position: Vec2, direction: Vec2, distance: f32) -> Vec2 fn imperative_attack_approach( entity_map: Res<EntityMap>, - mut players: Query<(&PlayerId, &mut Imperative)>, + mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats)>, mut positions: Query<&mut PlayerPosition>, - champions: Query<&Champion>, time: Res<Time>, ) { - for (id, mut imperative) in players.iter_mut() { + for (id, mut imperative, effective_stats) in players.iter_mut() { match *imperative { Imperative::AttackTarget(_, target_player) => { let Some(entity) = entity_map.0.get(&id.0) else { *imperative = Imperative::Idle; - return; - }; - let Ok(champion) = champions.get(*entity) else { - *imperative = Imperative::Idle; - return; + continue; }; let Some(target_entity) = entity_map.0.get(&target_player.0) else { *imperative = Imperative::Idle; - return; + continue; }; let Ok([mut position, target_position]) = positions.get_many_mut([*entity, *target_entity]) else { *imperative = Imperative::Idle; - return; + continue; }; let distance = target_position.0.distance(position.0); - if distance > Stats::from_champion(*champion).attack_range { + if distance > effective_stats.0.attack_range { let (new_position, _) = move_to_target( position.0, target_position.0, - MOVEMENT_SPEED * time.delta().as_secs_f32(), + effective_stats.0.movement_speed * time.delta().as_secs_f32(), ); position.0 = new_position; } @@ -242,40 +247,40 @@ fn imperative_attack_attack( entity_map: Res<EntityMap>, mut commands: Commands, mut cooldowns: Query<&mut Cooldown>, - mut players: Query<(&PlayerId, &mut Imperative)>, + mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats)>, mut positions: Query<&mut PlayerPosition>, champions: Query<&Champion>, ) { - for (id, mut imperative) in players.iter_mut() { + for (id, mut imperative, effective_stats) in players.iter_mut() { let Some(entity) = entity_map.0.get(&id.0) else { *imperative = Imperative::Idle; - return; + continue; }; let Ok(champion) = champions.get(*entity) else { *imperative = Imperative::Idle; - return; + continue; }; match *imperative { Imperative::AttackTarget(ability_slot, target_player) => { let Ability::Targeted(ability) = champion.to_ability(ability_slot) else { *imperative = Imperative::Idle; - return; + continue; }; let Some(target_entity) = entity_map.0.get(&target_player.0) else { *imperative = Imperative::Idle; - return; + continue; }; let Ok([position, target_position]) = positions.get_many_mut([*entity, *target_entity]) else { *imperative = Imperative::Idle; - return; + continue; }; let distance = target_position.0.distance(position.0); - if distance <= Stats::from_champion(*champion).attack_range { + if distance <= effective_stats.0.attack_range { let Ok(mut cooldown) = cooldowns.get_mut(*entity) else { *imperative = Imperative::Idle; - return; + continue; }; let base_cooldown = BaseCooldown::from_champion(*champion); if cooldown.0[ability_slot].is_zero() { @@ -285,21 +290,24 @@ fn imperative_attack_attack( position.0, target_player, ))); + if ability_slot != AbilitySlot::A { + *imperative = Imperative::Idle; + } } } } Imperative::AttackDirection(ability_slot, direction) => { let Ability::Directional(ability) = champion.to_ability(ability_slot) else { *imperative = Imperative::Idle; - return; + continue; }; let Ok(position) = positions.get_mut(*entity) else { *imperative = Imperative::Idle; - return; + continue; }; let Ok(mut cooldown) = cooldowns.get_mut(*entity) else { *imperative = Imperative::Idle; - return; + continue; }; let base_cooldown = BaseCooldown::from_champion(*champion); if cooldown.0[ability_slot].is_zero() { @@ -307,6 +315,7 @@ fn imperative_attack_attack( commands.spawn(ProjectileBundle::new( ability.to_projectile(*id, position.0, direction), )); + *imperative = Imperative::Idle; } } _ => {} @@ -314,6 +323,46 @@ fn imperative_attack_attack( } } +fn activation( + entity_map: Res<EntityMap>, + mut cooldowns: Query<&mut Cooldown>, + mut players: Query<(&PlayerId, &mut Activation, &mut Buffs)>, + champions: Query<&Champion>, +) { + for (id, mut activation, mut buffs) in players.iter_mut() { + let Some(entity) = entity_map.0.get(&id.0) else { + *activation = Activation::None; + continue; + }; + let Ok(champion) = champions.get(*entity) else { + *activation = Activation::None; + continue; + }; + let Activation::Activate(ability_slot) = *activation else { + *activation = Activation::None; + continue; + }; + let Ability::Activated(ability) = champion.to_ability(ability_slot) else { + *activation = Activation::None; + continue; + }; + let Ok(mut cooldown) = cooldowns.get_mut(*entity) else { + *activation = Activation::None; + continue; + }; + match ability { + ActivatedAbility::Speed => { + let base_cooldown = BaseCooldown::from_champion(*champion); + if cooldown.0[ability_slot].is_zero() { + cooldown.0[ability_slot] = base_cooldown.0[ability_slot]; + buffs.speed = Some(Timer::from_seconds(2., TimerMode::Once)); + *activation = Activation::None; + } + } + } + } +} + const PROJECTILE_SPEED: f32 = 150.; fn projectile_move( @@ -460,3 +509,43 @@ fn health_regen(health_regen_timer: Res<HealthRegenTimer>, mut healths: Query<&m } } } + +fn effective_stats(mut effective_statses: Query<(&Champion, &mut EffectiveStats, &Buffs)>) { + for (champion, mut effective_stats, buffs) in effective_statses.iter_mut() { + let mut stats = BaseStats::from_champion(*champion).0; + if buffs.slow.is_some() { + stats.movement_speed *= 0.85; + } + if buffs.speed.is_some() { + stats.movement_speed *= 1.25; + } + effective_stats.0 = stats; + } +} + +fn buffs_tick(mut buffses: Query<&mut Buffs>, time: Res<Time>) { + let dt = time.delta(); + for mut buffs in buffses.iter_mut() { + if let Some(ref mut timer) = &mut buffs.slow { + timer.tick(dt); + } + if let Some(ref mut timer) = &mut buffs.speed { + timer.tick(dt); + } + } +} + +fn buffs_despawn(mut buffses: Query<&mut Buffs>) { + for mut buffs in buffses.iter_mut() { + if let Some(timer) = &buffs.slow { + if timer.finished() { + buffs.slow = None; + } + } + if let Some(timer) = &buffs.speed { + if timer.finished() { + buffs.speed = None; + } + } + } +} diff --git a/src/shared.rs b/src/shared.rs index a06ff3e..678c592 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -10,6 +10,8 @@ use serde::Serialize; use std::default::Default; pub mod ability; +pub mod activation; +pub mod buffs; pub mod champion; pub mod cooldown; pub mod health; @@ -17,6 +19,7 @@ pub mod health_event; pub mod imperative; pub mod player; pub mod projectile; +pub mod stats; 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, diff --git a/src/shared/ability.rs b/src/shared/ability.rs index cab7ae6..e776015 100644 --- a/src/shared/ability.rs +++ b/src/shared/ability.rs @@ -6,8 +6,9 @@ use std::ops::*; #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum Ability { - Targeted(TargetedAbility), + Activated(ActivatedAbility), Directional(DirectionalAbility), + Targeted(TargetedAbility), } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] @@ -42,6 +43,11 @@ impl TargetedAbility { } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] +pub enum ActivatedAbility { + Speed, +} + +#[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum DirectionalAbility { Spear, } diff --git a/src/shared/activation.rs b/src/shared/activation.rs new file mode 100644 index 0000000..3ca6956 --- /dev/null +++ b/src/shared/activation.rs @@ -0,0 +1,16 @@ +use crate::shared::ability::*; +use crate::shared::*; +use serde::Deserialize; +use serde::Serialize; + +#[derive(Component, Message, Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] +pub enum Activation { + Activate(AbilitySlot), + None, +} + +impl Default for Activation { + fn default() -> Self { + Activation::None + } +} diff --git a/src/shared/buffs.rs b/src/shared/buffs.rs new file mode 100644 index 0000000..4fe033d --- /dev/null +++ b/src/shared/buffs.rs @@ -0,0 +1,7 @@ +use crate::shared::*; + +#[derive(Clone, Component, Default, Debug)] +pub struct Buffs { + pub slow: Option<Timer>, + pub speed: Option<Timer>, +} diff --git a/src/shared/champion.rs b/src/shared/champion.rs index 28d1ff2..c32d3d5 100644 --- a/src/shared/champion.rs +++ b/src/shared/champion.rs @@ -26,28 +26,17 @@ impl FromStr for Champion { } } -pub struct Stats { - pub attack_range: f32, -} - -impl Stats { - pub fn from_champion(champion: Champion) -> Self { - match champion { - Champion::Meele => Stats { attack_range: 25. }, - Champion::Ranged => Stats { attack_range: 60. }, - } - } -} - impl Champion { pub fn to_ability(self, ability_slot: AbilitySlot) -> Ability { match self { Champion::Meele => match ability_slot { AbilitySlot::Q => Ability::Directional(DirectionalAbility::Spear), + AbilitySlot::G => Ability::Activated(ActivatedAbility::Speed), _ => Ability::Targeted(TargetedAbility::MeeleAttack), }, Champion::Ranged => match ability_slot { AbilitySlot::Q => Ability::Directional(DirectionalAbility::Spear), + AbilitySlot::G => Ability::Activated(ActivatedAbility::Speed), _ => Ability::Targeted(TargetedAbility::RangedAttack), }, } diff --git a/src/shared/imperative.rs b/src/shared/imperative.rs index 44a0e43..4291368 100644 --- a/src/shared/imperative.rs +++ b/src/shared/imperative.rs @@ -8,6 +8,6 @@ use serde::Serialize; pub enum Imperative { Idle, WalkTo(Vec2), - AttackTarget(AbilitySlot, PlayerId), AttackDirection(AbilitySlot, Vec2), + AttackTarget(AbilitySlot, PlayerId), } diff --git a/src/shared/player.rs b/src/shared/player.rs index a886499..4f571d8 100644 --- a/src/shared/player.rs +++ b/src/shared/player.rs @@ -1,3 +1,6 @@ +use crate::shared::activation::*; +use crate::shared::buffs::*; +use crate::shared::stats::*; use crate::shared::*; #[derive(Bundle)] @@ -9,6 +12,9 @@ pub struct PlayerBundle { cooldown: Cooldown, health: Health, champion: Champion, + effective_stats: EffectiveStats, + buffs: Buffs, + activation: Activation, replicate: Replicate, } @@ -20,8 +26,10 @@ impl PlayerBundle { }; replicate.enable_replicate_once::<PlayerId>(); replicate.enable_replicate_once::<PlayerColor>(); - replicate.target::<Cooldown>(NetworkTarget::Single(id)); replicate.target::<Champion>(NetworkTarget::Single(id)); + replicate.target::<Cooldown>(NetworkTarget::Single(id)); + replicate.target::<EffectiveStats>(NetworkTarget::Single(id)); + let champion = Champion::default(); PlayerBundle { id: PlayerId(id), position: PlayerPosition(position), @@ -29,7 +37,10 @@ impl PlayerBundle { imperative: Imperative::Idle, cooldown: Cooldown::default(), health: Health::default(), - champion: Champion::default(), + champion, + effective_stats: EffectiveStats(BaseStats::from_champion(champion).0), + buffs: Buffs::default(), + activation: Activation::default(), replicate, } } diff --git a/src/shared/stats.rs b/src/shared/stats.rs index 278a19f..749453c 100644 --- a/src/shared/stats.rs +++ b/src/shared/stats.rs @@ -1,16 +1,28 @@ -use crate::shared::champion::*; use crate::shared::*; -#[derive(Component, Message, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Copy, Serialize, Deserialize, PartialEq)] pub struct Stats { pub attack_range: f32, + pub movement_speed: f32, } -impl Stats { +#[derive(Component, Message, Clone, Copy, Serialize, Deserialize, PartialEq)] +pub struct BaseStats(pub Stats); + +#[derive(Component, Message, Clone, Copy, Serialize, Deserialize, PartialEq)] +pub struct EffectiveStats(pub Stats); + +impl BaseStats { pub fn from_champion(champion: Champion) -> Self { match champion { - Champion::Meele => Stats { attack_range: 35. }, - Champion::Ranged => Stats { attack_range: 60. }, + Champion::Meele => BaseStats(Stats { + attack_range: 25., + movement_speed: 75., + }), + Champion::Ranged => BaseStats(Stats { + attack_range: 60., + movement_speed: 85., + }), } } } |