diff options
author | Alexander Foremny <aforemny@posteo.de> | 2024-03-15 16:07:58 +0100 |
---|---|---|
committer | Alexander Foremny <aforemny@posteo.de> | 2024-03-15 16:08:00 +0100 |
commit | c71366c2e0a9f0b454957feeb87b51f6b27d54bd (patch) | |
tree | 843e35d09a27275babf4008b58ef72302c18c4ff | |
parent | fdee6aa8cf2c51d5004d914458ff661da366e883 (diff) |
feat: add champions
Select champions using the `--champion` flag. Valid values are `ranged`
and `meele` (default).
-rw-r--r-- | src/client.rs | 45 | ||||
-rw-r--r-- | src/main.rs | 5 | ||||
-rw-r--r-- | src/protocol.rs | 8 | ||||
-rw-r--r-- | src/server.rs | 95 | ||||
-rw-r--r-- | src/shared.rs | 4 | ||||
-rw-r--r-- | src/shared/champion.rs | 39 | ||||
-rw-r--r-- | src/shared/projectile.rs | 2 | ||||
-rw-r--r-- | src/shared/stats.rs | 16 |
8 files changed, 169 insertions, 45 deletions
diff --git a/src/client.rs b/src/client.rs index 174d615..fff18af 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use crate::client::network::*; use crate::protocol::*; +use crate::shared::champion::*; use crate::shared::health::*; use crate::shared::imperative::*; use crate::shared::projectile::*; @@ -17,13 +18,19 @@ const PLAYER_RADIUS: f32 = 10.; const PLAYER_HOVER_INDICATOR_RADIUS: f32 = 13.; const PLAYER_HOVER_RADIUS: f32 = 20.; -pub fn main(server_addr: Option<SocketAddr>, client_id: u64, transport: TransportConfig) { +pub fn main( + server_addr: Option<SocketAddr>, + client_id: u64, + transport: TransportConfig, + champion: Champion, +) { App::new() .add_plugins(DefaultPlugins) .add_plugins(ClientPlugin { server_addr, client_id, transport, + champion, }) .run(); } @@ -31,15 +38,20 @@ pub fn main(server_addr: Option<SocketAddr>, client_id: u64, transport: Transpor #[derive(Resource)] struct ClientId(pub u64); +#[derive(Resource)] +struct MyChampion(pub Champion); + struct ClientPlugin { pub server_addr: Option<SocketAddr>, pub client_id: u64, pub transport: TransportConfig, + pub champion: Champion, } impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { app.insert_resource(ClientId(self.client_id)) + .insert_resource(MyChampion(self.champion)) .add_plugins(NetworkPlugin { server_addr: self.server_addr.clone(), client_id: self.client_id, @@ -63,9 +75,12 @@ impl Plugin for ClientPlugin { } } -fn setup(mut client: ClientMut, mut commands: Commands) { +fn setup(mut client: ClientMut, mut commands: Commands, champion: Res<MyChampion>) { commands.spawn(Camera2dBundle::default()); client.connect().unwrap(); + client + .send_message::<Channel1, SelectChampion>(SelectChampion(champion.0)) + .unwrap(); } fn render_players( @@ -200,14 +215,22 @@ fn gizmos_attack_indicator( client_id: Res<ClientId>, hoverables: Query<(&PlayerId, &PlayerPosition)>, mut gizmos: Gizmos, - players: Query<(&PlayerId, &PlayerPosition)>, + player_positions: Query<(&PlayerId, &PlayerPosition)>, + player_champions: Query<(&PlayerId, &Champion)>, windows: Query<&Window>, ) { - let Some(position) = player_position(&client_id, &players) else { + let Some(position) = player_position(&client_id, &player_positions) else { + return; + }; + let Some(champion) = player_champion(&client_id, &player_champions) else { return; }; if hovered_other_player(&cameras, &client_id, &hoverables, &windows).is_some() { - gizmos.circle_2d(position.0, ATTACK_RANGE, Color::YELLOW); + gizmos.circle_2d( + position.0, + Stats::from_champion(champion).attack_range, + Color::YELLOW, + ); } } @@ -223,6 +246,18 @@ fn player_position( None } +fn player_champion( + client_id: &Res<ClientId>, + players: &Query<(&PlayerId, &Champion)>, +) -> Option<Champion> { + for (id, champion) in players.iter() { + if id.0 == client_id.0 { + return Some(*champion); + } + } + None +} + const HEALTH_OFFSET: f32 = 4.; fn render_health(players: Query<(&Health, &PlayerPosition)>, mut gizmos: Gizmos) { diff --git a/src/main.rs b/src/main.rs index b615ddc..2326310 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use crate::shared::champion::*; use clap::Parser; use lightyear::transport::io::TransportConfig; use rand::Rng; @@ -17,6 +18,8 @@ struct Cli { connect_to: Option<SocketAddr>, #[arg(long, num_args = 0)] server: bool, + #[arg(long, default_value = "meele")] + champion: Champion, } fn main() { @@ -38,6 +41,7 @@ fn main() { Some(server_addr), client_id as u64, TransportConfig::UdpSocket(client_addr), + cli.champion, ); } else { let (from_server_send, from_server_recv) = crossbeam_channel::unbounded(); @@ -57,6 +61,7 @@ fn main() { recv: from_server_recv, send: to_server_send, }, + cli.champion, ); } } diff --git a/src/protocol.rs b/src/protocol.rs index 3d4567d..7ec3dcb 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,3 +1,4 @@ +use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; use crate::shared::imperative::*; @@ -16,11 +17,11 @@ pub enum Inputs { impl UserAction for Inputs {} #[derive(Message, Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct Message1(pub usize); +pub struct SelectChampion(pub Champion); #[message_protocol(protocol = "MyProtocol")] pub enum Messages { - Message1(Message1), + SelectChampion(SelectChampion), } #[component_protocol(protocol = "MyProtocol")] @@ -33,10 +34,11 @@ pub enum Components { ProjectilePosition(ProjectilePosition), Cooldown(Cooldown), Health(Health), + Champion(Champion), } #[derive(Channel)] -struct Channel1; +pub struct Channel1; protocolize! { Self = MyProtocol, diff --git a/src/server.rs b/src/server.rs index fab529f..1b9b7ef 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,6 @@ use crate::protocol::*; use crate::server::network::*; +use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; use crate::shared::imperative::*; @@ -9,6 +10,7 @@ use bevy::prelude::*; use bevy::utils::Duration; use bevy::utils::HashMap; use lightyear::prelude::*; +use lightyear::server::events::MessageEvent; use rand::Rng; mod network; @@ -47,6 +49,7 @@ impl Plugin for ServerPlugin { }) .add_systems(Startup, setup) .add_systems(Update, (connects, disconnects)) + .add_systems(Update, receive_message) .add_systems(Update, timers_ticket) .add_systems(Update, health_regen.after(timers_ticket)) .add_systems( @@ -94,6 +97,21 @@ fn connects( } } +fn receive_message( + entity_map: Res<EntityMap>, + mut commands: Commands, + mut reader: EventReader<MessageEvent<SelectChampion>>, +) { + for event in reader.read() { + let client_id = event.context(); + let SelectChampion(champion) = event.message(); + let Some(entity) = entity_map.0.get(client_id) else { + return; + }; + commands.entity(*entity).insert(*champion); + } +} + fn disconnects( mut commands: Commands, mut disconnects: EventReader<server::DisconnectEvent>, @@ -175,47 +193,54 @@ fn imperative_attack( mut cooldowns: Query<&mut Cooldown>, mut players: Query<(&PlayerId, &mut Imperative)>, mut positions: Query<&mut PlayerPosition>, + champions: Query<&Champion>, time: Res<Time>, ) { 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 Some(target_entity) = entity_map.0.get(&target_player.0) { - if let Ok([mut position, target_position]) = - positions.get_many_mut([*entity, *target_entity]) - { - let distance = target_position.0.distance(position.0); - if distance > ATTACK_RANGE { - let (new_position, _) = move_to_target( - position.0, - target_position.0, - MOVEMENT_SPEED * time.delta().as_secs_f32(), - ); - position.0 = new_position; - } else { - 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, - damage: 4., - }, - position: ProjectilePosition(position.0), - replicate: Replicate::default(), - }); - } - } else { - } - *imperative = Imperative::Idle; - } - } else { - *imperative = Imperative::Idle; - } - } else { + 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; + }; + let Some(target_entity) = entity_map.0.get(&target_player.0) else { + *imperative = Imperative::Idle; + return; + }; + let Ok([mut position, target_position]) = + positions.get_many_mut([*entity, *target_entity]) + else { + *imperative = Imperative::Idle; + return; + }; + let distance = target_position.0.distance(position.0); + if distance > Stats::from_champion(*champion).attack_range { + let (new_position, _) = move_to_target( + position.0, + target_position.0, + MOVEMENT_SPEED * time.delta().as_secs_f32(), + ); + position.0 = new_position; + } else { + 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 { + target_player, + source_player: *id, + damage: 4., + }, + position: ProjectilePosition(position.0), + replicate: Replicate::default(), + }); } } } diff --git a/src/shared.rs b/src/shared.rs index 92b6a5b..16cea60 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,4 +1,5 @@ use crate::protocol::Replicate; +use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; use crate::shared::imperative::*; @@ -8,6 +9,7 @@ use serde::Deserialize; use serde::Serialize; use std::default::Default; +pub mod champion; pub mod cooldown; pub mod health; pub mod imperative; @@ -28,6 +30,7 @@ pub struct PlayerBundle { imperative: Imperative, cooldown: Cooldown, health: Health, + champion: Champion, } #[derive(Component, Message, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] @@ -49,6 +52,7 @@ impl PlayerBundle { imperative: Imperative::Idle, cooldown: Cooldown::default(), health: Health::default(), + champion: Champion::default(), } } } diff --git a/src/shared/champion.rs b/src/shared/champion.rs new file mode 100644 index 0000000..ec27c62 --- /dev/null +++ b/src/shared/champion.rs @@ -0,0 +1,39 @@ +use crate::shared::*; +use std::str::FromStr; + +#[derive(Component, Message, Clone, Copy, Serialize, Deserialize, PartialEq, Debug)] +pub enum Champion { + Meele, + Ranged, +} + +impl Default for Champion { + fn default() -> Champion { + Champion::Meele + } +} + +impl FromStr for Champion { + type Err = String; + + fn from_str(s: &str) -> Result<Champion, String> { + match s { + "ranged" => Ok(Champion::Ranged), + "meele" => Ok(Champion::Meele), + _ => Err(format!("unknown champion: {}", s)), + } + } +} + +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. }, + } + } +} diff --git a/src/shared/projectile.rs b/src/shared/projectile.rs index c42173a..2e9c6c3 100644 --- a/src/shared/projectile.rs +++ b/src/shared/projectile.rs @@ -1,7 +1,5 @@ use crate::shared::*; -pub const ATTACK_RANGE: f32 = 60.; - #[derive(Bundle)] pub struct ProjectileBundle { pub projectile: Projectile, diff --git a/src/shared/stats.rs b/src/shared/stats.rs new file mode 100644 index 0000000..278a19f --- /dev/null +++ b/src/shared/stats.rs @@ -0,0 +1,16 @@ +use crate::shared::champion::*; +use crate::shared::*; + +#[derive(Component, Message, Clone, Serialize, Deserialize, PartialEq)] +pub struct Stats { + pub attack_range: f32, +} + +impl Stats { + pub fn from_champion(champion: Champion) -> Self { + match champion { + Champion::Meele => Stats { attack_range: 35. }, + Champion::Ranged => Stats { attack_range: 60. }, + } + } +} |