aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-21 15:36:05 +0100
committerLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-22 03:53:08 +0100
commit91419fb01ef5dcdc06d9f6774d16d3ccca1e4b57 (patch)
treea302f2ca0d110579f468b2144b81e24f3aa113a3 /src
parent445a51c344ecea346051cf59d03b95c98bb28e75 (diff)
feat: towers
Diffstat (limited to 'src')
-rw-r--r--src/client.rs123
-rw-r--r--src/protocol.rs2
-rw-r--r--src/server.rs87
-rw-r--r--src/shared.rs4
-rw-r--r--src/shared/ability.rs301
-rw-r--r--src/shared/champion.rs21
-rw-r--r--src/shared/immovable.rs4
-rw-r--r--src/shared/player.rs3
-rw-r--r--src/shared/shape.rs16
-rw-r--r--src/shared/tower.rs65
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>,
+}