diff options
author | Alexander Foremny <aforemny@posteo.de> | 2024-03-21 15:36:05 +0100 |
---|---|---|
committer | Alexander Foremny <aforemny@posteo.de> | 2024-03-22 03:53:08 +0100 |
commit | 91419fb01ef5dcdc06d9f6774d16d3ccca1e4b57 (patch) | |
tree | a302f2ca0d110579f468b2144b81e24f3aa113a3 /src | |
parent | 445a51c344ecea346051cf59d03b95c98bb28e75 (diff) |
feat: towers
Diffstat (limited to 'src')
-rw-r--r-- | src/client.rs | 123 | ||||
-rw-r--r-- | src/protocol.rs | 2 | ||||
-rw-r--r-- | src/server.rs | 87 | ||||
-rw-r--r-- | src/shared.rs | 4 | ||||
-rw-r--r-- | src/shared/ability.rs | 301 | ||||
-rw-r--r-- | src/shared/champion.rs | 21 | ||||
-rw-r--r-- | src/shared/immovable.rs | 4 | ||||
-rw-r--r-- | src/shared/player.rs | 3 | ||||
-rw-r--r-- | src/shared/shape.rs | 16 | ||||
-rw-r--r-- | src/shared/tower.rs | 65 |
10 files changed, 430 insertions, 196 deletions
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<SocketAddr>, client_id: u64, @@ -193,12 +190,12 @@ fn render_players( mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>, mut meshes: ResMut<Assets<Mesh>>, - players: Query<(Entity, &PlayerPosition, &PlayerColor), Added<PlayerId>>, + players: Query<(Entity, &PlayerPosition, &PlayerColor, &Shape), Added<PlayerId>>, ) { - 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<Pl } fn buffer_input( - players: Query<(&PlayerId, &PlayerPosition)>, + players: Query<(&PlayerId, &PlayerPosition, &Shape)>, mut attack: ResMut<Attack>, cameras: Query<(&Camera, &GlobalTransform)>, client_id: Res<ClientId>, @@ -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<ClientId>, - 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<ClientId>, - 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<ClientId>, + players: &Query<(&PlayerId, Entity)>, +) -> Option<Entity> { + for (id, entity) in players.iter() { + if id.0 == client_id.0 { + return Some(entity); + } + } + None +} + fn player_position( client_id: &Res<ClientId>, - players: &Query<(&PlayerId, &PlayerPosition)>, + players: &Query<(&PlayerId, &PlayerPosition, &Shape)>, ) -> Option<PlayerPosition> { - 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<ClientId>, players: &Query<(&PlayerId, &Shape)>) -> Option<Shape> { + 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<MessageEvent<HealthChanged>>, - 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<PlayerPosition> { - 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<EntityMap>) { - 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<EntityMap>, - mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats)>, + mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats), Without<Immovable>>, mut positions: Query<&mut PlayerPosition>, + shapes: Query<&Shape>, time: Res<Time>, ) { for (id, mut imperative, effective_stats) in players.iter_mut() { @@ -237,8 +244,12 @@ fn imperative_attack_approach( *imperative = Imperative::Idle; continue; }; + let Ok(target_shape) = shapes.get(*target_entity) else { + *imperative = Imperative::Idle; + continue; + }; let distance = target_position.0.distance(position.0); - if distance > effective_stats.0.attack_range { + if distance > effective_stats.0.attack_range + target_shape.radius { let (new_position, _) = move_to_target( position.0, target_position.0, @@ -258,6 +269,7 @@ fn imperative_attack_attack( mut cooldowns: Query<&mut Cooldown>, mut players: Query<(&PlayerId, &mut Imperative, &EffectiveStats)>, mut positions: Query<&mut PlayerPosition>, + shapes: Query<&Shape>, champions: Query<&Champion>, ) { for (id, mut imperative, effective_stats) in players.iter_mut() { @@ -285,8 +297,12 @@ fn imperative_attack_attack( *imperative = Imperative::Idle; continue; }; + let Ok(target_shape) = shapes.get(*target_entity) else { + *imperative = Imperative::Idle; + continue; + }; let distance = target_position.0.distance(position.0); - if distance <= effective_stats.0.attack_range { + if distance <= effective_stats.0.attack_range + target_shape.radius { let Ok(mut cooldown) = cooldowns.get_mut(*entity) else { *imperative = Imperative::Idle; continue; @@ -413,19 +429,19 @@ fn projectile_despawn( mut healths: Query<&mut Health>, player_positions: Query<&PlayerPosition>, projectiles: Query<(Entity, &mut Projectile)>, - projectile_targets: Query<(&PlayerId, &PlayerPosition)>, + projectile_targets: Query<(&PlayerId, &PlayerPosition, &Shape)>, ) { for (entity, projectile) in projectiles.iter() { 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() { + for (player, position, player_shape) in projectile_targets.iter() { if *player == projectile.source_player { continue; } let distance = free_projectile.position.distance(position.0); - if distance > PLAYER_RADIUS { + if distance > player_shape.radius { continue; } match maybe_target_distance { @@ -629,20 +645,65 @@ fn area_of_effect_despawn(area_of_effects: Query<(Entity, &AreaOfEffect)>, mut c } fn area_of_effect_activate( - players: Query<(&PlayerId, &PlayerPosition)>, + players: Query<(&PlayerId, &PlayerPosition, &Shape)>, area_of_effects: Query<&AreaOfEffect>, mut commands: Commands, ) { for area_of_effect in area_of_effects.iter() { - for (player_id, player_position) in players.iter() { + for (player_id, player_position, player_shape) in players.iter() { if *player_id == area_of_effect.source_player { continue; } if area_of_effect.position.distance(player_position.0) - < area_of_effect.radius + PLAYER_RADIUS + < area_of_effect.radius + player_shape.radius { area_of_effect.activate()(&mut commands, area_of_effect.source_player, *player_id); } } } } + +fn tower_ai( + mut towers: Query<( + &mut Tower, + &mut Imperative, + &PlayerPosition, + &EffectiveStats, + )>, + targets: Query<(&PlayerId, &PlayerPosition, &Shape), Without<Tower>>, +) { + for (mut tower_tower, mut tower_imperative, tower_player_position, tower_effective_stats) in + towers.iter_mut() + { + let mut closest_target = None; + for (target_player_id, target_player_position, target_shape) in targets.iter() { + let target_in_range = tower_player_position.0.distance(target_player_position.0) + < tower_effective_stats.0.attack_range + target_shape.radius; + + if !target_in_range { + continue; + } + + let target_distance = tower_player_position.0.distance(target_player_position.0); + if tower_tower.last_target_player_id == Some(*target_player_id) { + closest_target = Some((target_player_id, target_distance)); + break; + } + + let target_is_closer = closest_target + .map(|(_, closest_target_distance)| target_distance < closest_target_distance) + .unwrap_or(true); + if target_is_closer { + closest_target = Some((target_player_id, target_distance)); + } + } + let Some((target_player_id, _)) = closest_target else { + *tower_imperative = Imperative::Idle; + tower_tower.last_target_player_id = None; + continue; + }; + + *tower_imperative = Imperative::AttackTarget(AbilitySlot::A, *target_player_id); + tower_tower.last_target_player_id = Some(*target_player_id); + } +} diff --git a/src/shared.rs b/src/shared.rs index 8ec056d..3310c8c 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -17,14 +17,16 @@ pub mod champion; pub mod cooldown; pub mod health; pub mod health_event; +pub mod immovable; pub mod imperative; pub mod player; pub mod projectile; +pub mod shape; pub mod stats; +pub mod tower; 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/ability.rs b/src/shared/ability.rs index b7b3664..4723e59 100644 --- a/src/shared/ability.rs +++ b/src/shared/ability.rs @@ -3,6 +3,7 @@ use crate::shared::area_of_effect::*; use crate::shared::buffs::*; use crate::shared::player::*; use crate::shared::projectile::*; +use crate::shared::shape::*; use crate::shared::*; use bevy::ecs::system::*; use bevy::utils::Duration; @@ -248,79 +249,61 @@ fn dash_activation(dash: Dash) -> DirectionalAbilityActivation { move |commands: &mut Commands, source_player: PlayerId, direction: Vec2| { commands.add(move |world: &mut World| { world.run_system_once( - move |players: Query<(Entity, &PlayerId)>, + move |entity_map: Res<EntityMap>, mut imperatives: Query<&mut Imperative>, mut set: ParamSet<( Query<&mut PlayerPosition>, - Query<(&PlayerId, &PlayerPosition)>, + Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, )>, mut commands: Commands| { - let Some(source_entity) = ({ - let mut source_entity = None; - for (entity, player_id) in players.iter() { - if *player_id != source_player { - continue; - } - source_entity = Some(entity); - break; - } - source_entity - }) else { + let Some(source_entity) = entity_map.0.get(&source_player.0) else { return; }; - let Some(source_position) = ({ - let positions = set.p0(); - if let Ok(position) = positions.get(source_entity) { - Some(*position) - } else { - None - } - }) else { - return; - }; - - let DashCollisionResult { + let Some(DashCollisionResult { dash_end, dash_collision, - } = { + }) = ({ let dash_targets = set.p1(); dash_collision( - source_player, - source_position.0, + *source_entity, direction.normalize_or_zero(), dash.max_distance, &dash_targets, ) + }) + else { + return; }; let mut positions = set.p0(); - if let Ok(mut position) = positions.get_mut(source_entity) { + if let Ok(mut position) = positions.get_mut(*source_entity) { position.0 = dash_end; } if let Some(DashCollision { - collision_id, - collision_position, + collision_player_id, + collision_player_position, + .. }) = dash_collision { commands.spawn(AreaOfEffectBundle::new(AreaOfEffect { - position: collision_position, - radius: 1.5 * PLAYER_RADIUS, + position: collision_player_position.0, + radius: 1.5 * Shape::player().radius, duration: None, source_player, area_of_effect_type: AreaOfEffectType::Slow, })); commands.spawn(ProjectileBundle::new(Projectile { type_: ProjectileType::Instant(InstantProjectile { - target_player: collision_id, + target_player: collision_player_id, }), source_player, damage: dash.damage, })); - if let Ok(mut imperative) = imperatives.get_mut(source_entity) { + if let Ok(mut imperative) = imperatives.get_mut(*source_entity) { *imperative = - Imperative::AttackTarget(AbilitySlot::A, collision_id); + Imperative::AttackTarget(AbilitySlot::A, collision_player_id); } } }, @@ -335,61 +318,52 @@ pub struct DashCollisionResult { pub dash_collision: Option<DashCollision>, } +impl DashCollisionResult { + fn from_player_player_collision_result( + player_player_collision_result: PlayerPlayerCollisionResult, + ) -> Self { + DashCollisionResult { + dash_end: player_player_collision_result.final_position, + dash_collision: player_player_collision_result.collision.map( + |player_player_collision| { + DashCollision::from_player_player_collision(player_player_collision) + }, + ), + } + } +} + pub struct DashCollision { - pub collision_id: PlayerId, - pub collision_position: Vec2, + pub collision_entity: Entity, + pub collision_player_id: PlayerId, + pub collision_player_position: PlayerPosition, + pub collision_shape: Shape, +} + +impl DashCollision { + fn from_player_player_collision(player_player_collision: PlayerPlayerCollision) -> Self { + DashCollision { + collision_entity: player_player_collision.collision_entity, + collision_player_id: player_player_collision.collision_player_id, + collision_player_position: player_player_collision.collision_player_position, + collision_shape: player_player_collision.collision_shape, + } + } } pub fn dash_collision( - source_id: PlayerId, - dash_start: Vec2, + source_entity: Entity, dash_direction: Vec2, dash_max_distance: f32, - player_positions: &Query<(&PlayerId, &PlayerPosition)>, -) -> DashCollisionResult { - let mut dash_collision = dash_max_distance * dash_direction; - let mut collision_id = None; - let mut collision_position = None; - for (player_id, position) in player_positions.iter() { - if *player_id == source_id { - continue; - } - - let player_position = position.0 - dash_start; - let player_projection = player_position.project_onto(dash_collision); - let player_rejection = player_position - player_projection; - let scalar_factor = player_projection.dot(dash_collision).signum() - * player_projection.length() - / dash_collision.length(); - - if scalar_factor < 0. || scalar_factor > 1.0 { - continue; - } - - if player_rejection.length() < 2. * PLAYER_RADIUS { - dash_collision = player_projection; - collision_id = Some(*player_id); - collision_position = Some(position.0); - } - } - - if let (Some(collision_id), Some(collision_position)) = (collision_id, collision_position) { - let dash_end = dash_start - + (dash_collision.length() - 2. * PLAYER_RADIUS) * dash_collision.normalize_or_zero(); - DashCollisionResult { - dash_end, - dash_collision: Some(DashCollision { - collision_id, - collision_position, - }), - } - } else { - let dash_end = dash_start + dash_max_distance * dash_direction; - DashCollisionResult { - dash_end, - dash_collision: None, - } - } + dash_targets: &Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, +) -> Option<DashCollisionResult> { + player_player_collision( + source_entity, + dash_direction, + dash_max_distance, + dash_targets, + ) + .map(DashCollisionResult::from_player_player_collision_result) } fn pull_activation(pull: Pull) -> DirectionalAbilityActivation { @@ -401,7 +375,7 @@ fn pull_activation(pull: Pull) -> DirectionalAbilityActivation { mut imperatives: Query<&mut Imperative>, mut set: ParamSet<( Query<&mut PlayerPosition>, - Query<(&PlayerId, &PlayerPosition)>, + Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, )>, mut commands: Commands| { let Some(source_entity) = ({ @@ -418,26 +392,14 @@ fn pull_activation(pull: Pull) -> DirectionalAbilityActivation { return; }; - let Some(source_position) = ({ - let positions = set.p0(); - if let Ok(position) = positions.get(source_entity) { - Some(*position) - } else { - None - } - }) else { - return; - }; - - let Some(PullCollision { + let Some(PullCollisionResult { pull_end, - collision_id, + collision_player_id, .. }) = ({ let pull_targets = set.p1(); pull_collision( - source_player, - source_position.0, + source_entity, direction.normalize_or_zero(), pull.max_distance, &pull_targets, @@ -450,7 +412,7 @@ fn pull_activation(pull: Pull) -> DirectionalAbilityActivation { let Some(target_entity) = ({ let mut target_entity = None; for (entity, player_id) in players.iter() { - if *player_id != collision_id { + if *player_id != collision_player_id { continue; } target_entity = Some(entity); @@ -468,13 +430,14 @@ fn pull_activation(pull: Pull) -> DirectionalAbilityActivation { commands.spawn(ProjectileBundle::new(Projectile { type_: ProjectileType::Instant(InstantProjectile { - target_player: collision_id, + target_player: collision_player_id, }), source_player, damage: pull.damage, })); if let Ok(mut imperative) = imperatives.get_mut(source_entity) { - *imperative = Imperative::AttackTarget(AbilitySlot::A, collision_id); + *imperative = + Imperative::AttackTarget(AbilitySlot::A, collision_player_id); } }, ) @@ -483,56 +446,122 @@ fn pull_activation(pull: Pull) -> DirectionalAbilityActivation { ) } -pub struct PullCollision { +#[derive(Clone, Copy)] +pub struct PullCollisionResult { pub pull_end: Vec2, - pub collision_id: PlayerId, - pub collision_position: Vec2, + pub collision_entity: Entity, + pub collision_player_id: PlayerId, + pub collision_player_position: PlayerPosition, + pub collision_shape: Shape, +} + +impl PullCollisionResult { + fn from_player_player_collision_result( + player_player_collision_result: PlayerPlayerCollisionResult, + ) -> Option<Self> { + player_player_collision_result + .collision + .map(|player_player_collision| PullCollisionResult { + pull_end: player_player_collision.collision_player_position.0 + - (player_player_collision_result.final_position + - player_player_collision_result.source_player_position.0), + collision_entity: player_player_collision.collision_entity, + collision_player_id: player_player_collision.collision_player_id, + collision_player_position: player_player_collision.collision_player_position, + collision_shape: player_player_collision.collision_shape, + }) + } } pub fn pull_collision( - source_id: PlayerId, - pull_start: Vec2, + source_entity: Entity, pull_direction: Vec2, pull_max_distance: f32, - player_positions: &Query<(&PlayerId, &PlayerPosition)>, -) -> Option<PullCollision> { - let mut pull_collision = pull_max_distance * pull_direction; - let mut pull_player_id = None; - let mut pull_player_position = None; - for (player_id, position) in player_positions.iter() { - if *player_id == source_id { + pull_targets: &Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, +) -> Option<PullCollisionResult> { + player_player_collision( + source_entity, + pull_direction, + pull_max_distance, + pull_targets, + ) + .and_then(PullCollisionResult::from_player_player_collision_result) +} + +pub struct PlayerPlayerCollisionResult { + pub final_position: Vec2, + pub source_entity: Entity, + pub source_player_id: PlayerId, + pub source_player_position: PlayerPosition, + pub source_shape: Shape, + pub collision: Option<PlayerPlayerCollision>, +} + +pub struct PlayerPlayerCollision { + pub collision_entity: Entity, + pub collision_player_id: PlayerId, + pub collision_player_position: PlayerPosition, + pub collision_shape: Shape, +} + +pub fn player_player_collision( + source_entity: Entity, + direction: Vec2, + max_distance: f32, + targets: &Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, +) -> Option<PlayerPlayerCollisionResult> { + let Ok((_, source_player_id, source_player_position, source_shape)) = + targets.get(source_entity) + else { + return None; + }; + let start = source_player_position.0; + + let mut result = PlayerPlayerCollisionResult { + final_position: max_distance * direction, + source_entity, + source_player_id: *source_player_id, + source_player_position: *source_player_position, + source_shape: *source_shape, + collision: None, + }; + for (entity, player_id, position, shape) in targets.iter() { + if entity == source_entity { continue; } - let player_position = position.0 - pull_start; - let player_projection = player_position.project_onto(pull_collision); - let player_rejection = player_position - player_projection; - let scalar_factor = player_projection.dot(pull_collision).signum() - * player_projection.length() - / pull_collision.length(); + let target_position = position.0 - start; + let target_projection = target_position.project_onto(result.final_position); + let target_rejection = target_position - target_projection; + let scalar_factor = target_projection.dot(result.final_position).signum() + * target_projection.length() + / result.final_position.length(); if scalar_factor < 0. || scalar_factor > 1.0 { continue; } - if player_rejection.length() < 2. * PLAYER_RADIUS { - pull_player_id = Some(player_id); - pull_player_position = Some(position.0); - pull_collision = player_projection; + if target_rejection.length() < source_shape.radius + shape.radius { + result.final_position = target_projection; + result.collision = Some(PlayerPlayerCollision { + collision_entity: entity, + collision_player_id: *player_id, + collision_player_position: *position, + collision_shape: *shape, + }); } } - if let (Some(target_id), Some(target_position)) = (pull_player_id, pull_player_position) { - let pull_direction = pull_start - target_position; - let pull_end = target_position - + (pull_direction.length() - 2. * PLAYER_RADIUS) * pull_direction.normalize_or_zero(); - Some(PullCollision { - pull_end, - collision_id: *target_id, - collision_position: target_position, - }) + if let Some(ref mut collision) = result.collision { + result.final_position = start + + (result.final_position.length() + - source_shape.radius + - collision.collision_shape.radius) + * result.final_position.normalize_or_zero(); + Some(result) } else { - None + result.final_position += start; + Some(result) } } diff --git a/src/shared/champion.rs b/src/shared/champion.rs index f48faa1..1667672 100644 --- a/src/shared/champion.rs +++ b/src/shared/champion.rs @@ -1,4 +1,5 @@ use crate::shared::ability::*; +use crate::shared::shape::*; use crate::shared::stats::*; use crate::shared::*; use bevy::utils::*; @@ -8,6 +9,7 @@ use std::str::FromStr; pub enum Champion { Meele, Ranged, + Tower, } impl Default for Champion { @@ -23,6 +25,7 @@ impl FromStr for Champion { match s { "ranged" => Ok(Champion::Ranged), "meele" => Ok(Champion::Meele), + "tower" => Ok(Champion::Tower), _ => Err(format!("unknown champion: {}", s)), } } @@ -32,17 +35,23 @@ impl Champion { pub fn base_stats(self) -> BaseStats { match self { Champion::Meele => BaseStats(Stats { - attack_range: 25., + attack_range: Shape::player().radius, attack_speed: 0.75, max_health: 150., movement_speed: 75., }), Champion::Ranged => BaseStats(Stats { - attack_range: 60., + attack_range: 50., attack_speed: 1.5, max_health: 100., movement_speed: 85., }), + Champion::Tower => BaseStats(Stats { + attack_range: 100., + attack_speed: 4.5, + max_health: 500., + movement_speed: 0., + }), } } @@ -88,6 +97,11 @@ impl Champion { } _ => Ability::Targeted(TargetedAbility::RangedAttack(RangedAttack { damage: 8. })), }, + Champion::Tower => match ability_slot { + _ => { + Ability::Targeted(TargetedAbility::RangedAttack(RangedAttack { damage: 100. })) + } + }, } } @@ -99,6 +113,9 @@ impl Champion { Champion::Ranged => { BaseCooldown([0., 10., 10., 15., 35., 50., 50.].map(Duration::from_secs_f32)) } + Champion::Tower => { + BaseCooldown([10., 10., 10., 10., 10., 10., 10.].map(Duration::from_secs_f32)) + } } } } diff --git a/src/shared/immovable.rs b/src/shared/immovable.rs new file mode 100644 index 0000000..ef4b3f0 --- /dev/null +++ b/src/shared/immovable.rs @@ -0,0 +1,4 @@ +use crate::shared::*; + +#[derive(Component)] +pub struct Immovable; diff --git a/src/shared/player.rs b/src/shared/player.rs index 3805969..59e8853 100644 --- a/src/shared/player.rs +++ b/src/shared/player.rs @@ -1,5 +1,6 @@ use crate::shared::activation::*; use crate::shared::buffs::*; +use crate::shared::shape::*; use crate::shared::stats::*; use crate::shared::*; @@ -15,6 +16,7 @@ pub struct PlayerBundle { effective_stats: EffectiveStats, buffs: Buffs, activation: Activation, + shape: Shape, replicate: Replicate, } @@ -45,6 +47,7 @@ impl PlayerBundle { effective_stats, buffs: Buffs::default(), activation: Activation::default(), + shape: Shape::player(), replicate, } } diff --git a/src/shared/shape.rs b/src/shared/shape.rs new file mode 100644 index 0000000..7423375 --- /dev/null +++ b/src/shared/shape.rs @@ -0,0 +1,16 @@ +use crate::shared::*; + +#[derive(Component, Message, Clone, Copy, Serialize, Deserialize, Debug, PartialEq)] +pub struct Shape { + pub radius: f32, +} + +impl Shape { + pub fn player() -> Self { + Shape { radius: 10. } + } + + pub fn tower() -> Self { + Shape { radius: 25. } + } +} diff --git a/src/shared/tower.rs b/src/shared/tower.rs new file mode 100644 index 0000000..5b62b7a --- /dev/null +++ b/src/shared/tower.rs @@ -0,0 +1,65 @@ +use crate::shared::activation::*; +use crate::shared::buffs::*; +use crate::shared::immovable::*; +use crate::shared::player::*; +use crate::shared::shape::*; +use crate::shared::stats::*; +use crate::shared::*; + +#[derive(Bundle)] +pub struct TowerBundle { + id: PlayerId, + position: PlayerPosition, + color: PlayerColor, + imperative: Imperative, + cooldown: Cooldown, + health: Health, + champion: Champion, + effective_stats: EffectiveStats, + buffs: Buffs, + activation: Activation, + shape: Shape, + tower: Tower, + immovable: Immovable, + replicate: Replicate, +} + +impl TowerBundle { + pub fn new(id: ClientId, position: Vec2, color: Color) -> Self { + let mut replicate = Replicate { + replication_group: ReplicationGroup::default().set_priority(10.), + ..Default::default() + }; + replicate.enable_replicate_once::<PlayerId>(); + replicate.enable_replicate_once::<PlayerColor>(); + replicate.target::<Champion>(NetworkTarget::Single(id)); + replicate.target::<Cooldown>(NetworkTarget::Single(id)); + replicate.target::<EffectiveStats>(NetworkTarget::Single(id)); + let champion = Champion::Tower; + let effective_stats = EffectiveStats(champion.base_stats().0); + TowerBundle { + id: PlayerId(id), + position: PlayerPosition(position), + color: PlayerColor(color), + imperative: Imperative::Idle, + cooldown: Cooldown::default(), + health: Health { + health: effective_stats.0.max_health, + shield: 0., + }, + champion, + effective_stats, + buffs: Buffs::default(), + activation: Activation::default(), + shape: Shape::tower(), + tower: Tower::default(), + immovable: Immovable, + replicate, + } + } +} + +#[derive(Component, Default)] +pub struct Tower { + pub last_target_player_id: Option<PlayerId>, +} |