From 91419fb01ef5dcdc06d9f6774d16d3ccca1e4b57 Mon Sep 17 00:00:00 2001 From: Alexander Foremny Date: Thu, 21 Mar 2024 15:36:05 +0100 Subject: feat: towers --- src/client.rs | 123 +++++++++++++------- src/protocol.rs | 2 + src/server.rs | 87 +++++++++++--- src/shared.rs | 4 +- src/shared/ability.rs | 301 ++++++++++++++++++++++++++---------------------- src/shared/champion.rs | 21 +++- src/shared/immovable.rs | 4 + src/shared/player.rs | 3 + src/shared/shape.rs | 16 +++ src/shared/tower.rs | 65 +++++++++++ 10 files changed, 430 insertions(+), 196 deletions(-) create mode 100644 src/shared/immovable.rs create mode 100644 src/shared/shape.rs create mode 100644 src/shared/tower.rs diff --git a/src/client.rs b/src/client.rs index ce48b4f..6fa6ff1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,8 +11,8 @@ use crate::shared::health_event::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::shape::*; use crate::shared::stats::*; -use crate::shared::*; use bevy::input::keyboard::*; use bevy::input::mouse::MouseButton; use bevy::prelude::*; @@ -24,9 +24,6 @@ use std::net::SocketAddr; mod network; -const PLAYER_HOVER_INDICATOR_RADIUS: f32 = 13.; -const PLAYER_HOVER_RADIUS: f32 = 20.; - pub fn main( server_addr: Option, client_id: u64, @@ -193,12 +190,12 @@ fn render_players( mut commands: Commands, mut materials: ResMut>, mut meshes: ResMut>, - players: Query<(Entity, &PlayerPosition, &PlayerColor), Added>, + players: Query<(Entity, &PlayerPosition, &PlayerColor, &Shape), Added>, ) { - for (entity, position, color) in players.iter() { + for (entity, position, color, shape) in players.iter() { commands.entity(entity).insert(MaterialMesh2dBundle { mesh: Mesh2dHandle(meshes.add(Circle { - radius: PLAYER_RADIUS, + radius: shape.radius, })), material: materials.add(color.0), transform: Transform::from_xyz(position.0.x, position.0.y, 0.), @@ -275,7 +272,7 @@ fn move_players(mut players: Query<(&mut Transform, &PlayerPosition), Changed, + players: Query<(&PlayerId, &PlayerPosition, &Shape)>, mut attack: ResMut, cameras: Query<(&Camera, &GlobalTransform)>, client_id: Res, @@ -303,7 +300,7 @@ fn buffer_input( attack.0 = None; } Ability::Targeted(_) => { - let Some((target_player, _)) = + let Some((target_player, _, _)) = hovered_other_player(&cameras, &client_id, &players, &windows) else { return; @@ -316,7 +313,7 @@ fn buffer_input( } }, None => { - if let Some((target_player, _)) = + if let Some((target_player, _, _)) = hovered_other_player(&cameras, &client_id, &players, &windows) { client.add_input(Inputs::Imperative(Imperative::AttackTarget( @@ -378,15 +375,16 @@ fn choose_attack( fn gizmos_hover_indicator( cameras: Query<(&Camera, &GlobalTransform)>, client_id: Res, - hoverables: Query<(&PlayerId, &PlayerPosition)>, + hoverables: Query<(&PlayerId, &PlayerPosition, &Shape)>, mut gizmos: Gizmos, windows: Query<&Window>, ) { - let Some((_, position)) = hovered_other_player(&cameras, &client_id, &hoverables, &windows) + let Some((_, position, shape)) = + hovered_other_player(&cameras, &client_id, &hoverables, &windows) else { return; }; - gizmos.circle_2d(position.0, PLAYER_HOVER_INDICATOR_RADIUS, Color::GREEN); + gizmos.circle_2d(position.0, shape.radius + 1., Color::GREEN); } fn cursor_world_position( @@ -409,22 +407,22 @@ fn world_to_viewport(cameras: &Query<(&Camera, &GlobalTransform)>, position: Vec fn hovered_other_player( cameras: &Query<(&Camera, &GlobalTransform)>, client_id: &Res, - hoverables: &Query<(&PlayerId, &PlayerPosition)>, + hoverables: &Query<(&PlayerId, &PlayerPosition, &Shape)>, windows: &Query<&Window>, -) -> Option<(PlayerId, PlayerPosition)> { +) -> Option<(PlayerId, PlayerPosition, Shape)> { let Some(world_position) = cursor_world_position(&windows, &cameras) else { return None; }; let mut hovered_player = None; let mut hovered_distance = None; - for (id, position) in hoverables.iter() { + for (id, position, shape) in hoverables.iter() { if id.0 == client_id.0 { continue; } let distance = position.0.distance(world_position); - if distance < PLAYER_HOVER_RADIUS { + if distance < shape.radius + 5. { if hovered_distance.map_or(true, |hovered_distance| distance < hovered_distance) { - hovered_player = Some((*id, *position)); + hovered_player = Some((*id, *position, *shape)); hovered_distance = Some(distance); } } @@ -439,15 +437,25 @@ fn gizmos_attack_indicator( effective_statses: Query<(&PlayerId, &EffectiveStats)>, mut gizmos: Gizmos, player_champions: Query<(&PlayerId, &Champion)>, - player_positions: Query<(&PlayerId, &PlayerPosition)>, + player_positions: Query<(&PlayerId, &PlayerPosition, &Shape)>, + player_entities: Query<(&PlayerId, Entity)>, + pull_targets: Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, + dash_targets: Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, + player_shapes: Query<(&PlayerId, &Shape)>, windows: Query<&Window>, ) { + let Some(entity) = player_entity(&client_id, &player_entities) else { + return; + }; let Some(position) = player_position(&client_id, &player_positions) else { return; }; let Some(champion) = player_champion(&client_id, &player_champions) else { return; }; + let Some(shape) = player_shape(&client_id, &player_shapes) else { + return; + }; let Some(ability_slot) = attack.0 else { return; }; @@ -463,13 +471,14 @@ fn gizmos_attack_indicator( let direction = world_position - position.0; match ability { DirectionalAbility::Dash(Dash { max_distance, .. }) => { - let DashCollisionResult { dash_end, .. } = dash_collision( - PlayerId(client_id.0), - position.0, + let Some(DashCollisionResult { dash_end, .. }) = dash_collision( + entity, direction.normalize_or_zero(), max_distance, - &player_positions, - ); + &dash_targets, + ) else { + return; + }; gizmos.arrow_2d(position.0, dash_end, Color::YELLOW); } DirectionalAbility::Flash(Flash { max_distance, .. }) => { @@ -481,27 +490,26 @@ fn gizmos_attack_indicator( ); } DirectionalAbility::Pull(Pull { max_distance, .. }) => { - let Some(PullCollision { + let Some(PullCollisionResult { pull_end, - collision_position, + collision_player_position, .. }) = pull_collision( - PlayerId(client_id.0), - position.0, + entity, direction.normalize_or_zero(), max_distance, - &player_positions, + &pull_targets, ) else { let pull_direction = -max_distance * direction.normalize_or_zero(); let pull_start = position.0 - pull_direction; let pull_end = pull_start - + (pull_direction.length() - 2. * PLAYER_RADIUS) + + (pull_direction.length() - 2. * shape.radius) * pull_direction.normalize_or_zero(); gizmos.arrow_2d(pull_start, pull_end, Color::YELLOW); return; }; - gizmos.arrow_2d(collision_position, pull_end, Color::YELLOW); + gizmos.arrow_2d(collision_player_position.0, pull_end, Color::YELLOW); } DirectionalAbility::Spear(Spear { max_distance, .. }) => { gizmos.arrow_2d( @@ -518,11 +526,23 @@ fn gizmos_attack_indicator( } } +fn player_entity( + client_id: &Res, + players: &Query<(&PlayerId, Entity)>, +) -> Option { + for (id, entity) in players.iter() { + if id.0 == client_id.0 { + return Some(entity); + } + } + None +} + fn player_position( client_id: &Res, - players: &Query<(&PlayerId, &PlayerPosition)>, + players: &Query<(&PlayerId, &PlayerPosition, &Shape)>, ) -> Option { - for (id, position) in players.iter() { + for (id, position, _) in players.iter() { if id.0 == client_id.0 { return Some(*position); } @@ -554,12 +574,24 @@ fn player_champion( None } +fn player_shape(client_id: &Res, players: &Query<(&PlayerId, &Shape)>) -> Option { + for (id, shape) in players.iter() { + if id.0 == client_id.0 { + return Some(*shape); + } + } + None +} + const HEALTH_OFFSET: f32 = 4.; -fn render_health(players: Query<(&Health, &PlayerPosition, &EffectiveStats)>, mut gizmos: Gizmos) { - for (health, position, effective_stats) in players.iter() { - let start = position.0 + Vec2::new(-PLAYER_RADIUS, PLAYER_RADIUS + HEALTH_OFFSET); - let end = position.0 + Vec2::new(PLAYER_RADIUS, PLAYER_RADIUS + HEALTH_OFFSET); +fn render_health( + players: Query<(&Health, &PlayerPosition, &EffectiveStats, &Shape)>, + mut gizmos: Gizmos, +) { + for (health, position, effective_stats, shape) in players.iter() { + let start = position.0 + Vec2::new(-shape.radius, shape.radius + HEALTH_OFFSET); + let end = position.0 + Vec2::new(shape.radius, shape.radius + HEALTH_OFFSET); let health_start = start; let health_end = start.lerp(end, health.health / effective_stats.0.max_health); let px_per_health = (end.x - start.x) / effective_stats.0.max_health; @@ -570,10 +602,13 @@ fn render_health(players: Query<(&Health, &PlayerPosition, &EffectiveStats)>, mu } } -fn render_buffs(players: Query<(&Buffs, &PlayerPosition)>, mut gizmos: Gizmos) { - for (buffs, position) in players.iter() { - let mut start = - position.0 + Vec2::new(-PLAYER_RADIUS + 1., PLAYER_RADIUS + HEALTH_OFFSET + 2.); +fn render_buffs(players: Query<(&Buffs, &PlayerPosition, &Shape)>, mut gizmos: Gizmos) { + for (buffs, position, player_shape) in players.iter() { + let mut start = position.0 + + Vec2::new( + -player_shape.radius + 1., + player_shape.radius + HEALTH_OFFSET + 2., + ); if buffs.haste.is_some() { gizmos.rect_2d(start, 0., Vec2::new(2., 2.), Color::RED); start = start + Vec2::new(3., 0.); @@ -668,7 +703,7 @@ fn health_indicator_spawn( cameras: Query<(&Camera, &GlobalTransform)>, mut commands: Commands, mut event_reader: EventReader>, - players: Query<(&PlayerId, &PlayerPosition)>, + players: Query<(&PlayerId, &PlayerPosition, &Shape)>, ) { for event in event_reader.read() { let HealthChanged(HealthEvent { @@ -712,9 +747,9 @@ fn health_indicator_spawn( fn any_player_position( player_id: PlayerId, - players: &Query<(&PlayerId, &PlayerPosition)>, + players: &Query<(&PlayerId, &PlayerPosition, &Shape)>, ) -> Option { - for (id, position) in players.iter() { + for (id, position, _) in players.iter() { if *id == player_id { return Some(*position); } diff --git a/src/protocol.rs b/src/protocol.rs index 2a5b846..c881428 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -8,6 +8,7 @@ use crate::shared::health_event::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::shape::*; use crate::shared::stats::*; use bevy::prelude::*; use lightyear::prelude::*; @@ -46,6 +47,7 @@ pub enum Components { EffectiveStats(EffectiveStats), AreaOfEffect(AreaOfEffect), Buffs(Buffs), + Shape(Shape), } #[derive(Channel)] diff --git a/src/server.rs b/src/server.rs index 5ac07c0..4da2253 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,11 +9,13 @@ use crate::shared::champion::*; use crate::shared::cooldown::*; use crate::shared::health::*; use crate::shared::health_event::*; +use crate::shared::immovable::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::shape::*; use crate::shared::stats::*; -use crate::shared::*; +use crate::shared::tower::*; use bevy::prelude::*; use bevy::utils::*; use lightyear::prelude::*; @@ -88,14 +90,18 @@ impl Plugin for ServerPlugin { ) .add_systems(FixedUpdate, cooldown_decrement) .add_systems(FixedUpdate, (buffs_despawn, buffs_tick).chain()) - .add_systems(FixedUpdate, player_input); + .add_systems(FixedUpdate, player_input) + .add_systems(FixedUpdate, tower_ai); } } fn setup(mut commands: Commands, mut entity_map: ResMut) { - let client_id = 1; let entity = commands.spawn(PlayerBundle::new(1, Vec2::ZERO, Color::GRAY)); - entity_map.0.insert(client_id, entity.id()); + entity_map.0.insert(1, entity.id()); + let entity = commands.spawn(TowerBundle::new(2, Vec2::new(0., 100.), Color::RED)); + entity_map.0.insert(2, entity.id()); + let entity = commands.spawn(TowerBundle::new(3, Vec2::new(0., -100.), Color::BLUE)); + entity_map.0.insert(3, entity.id()); } fn connects( @@ -216,8 +222,9 @@ fn move_along_direction(position: Vec2, direction: Vec2, distance: f32) -> Vec2 fn imperative_attack_approach( entity_map: Res, - mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats)>, + mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats), Without>, mut positions: Query<&mut PlayerPosition>, + shapes: Query<&Shape>, time: Res