use crate::server::entity_map::*; use crate::shared::area_of_effect::*; use crate::shared::buffs::*; use crate::shared::damage::*; use crate::shared::health::*; use crate::shared::imperative::*; use crate::shared::player::*; use crate::shared::projectile::*; use crate::shared::shape::*; use bevy::ecs::system::*; use bevy::prelude::*; use bevy::utils::*; use serde::*; use std::ops::*; #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum Ability { Activated(ActivatedAbility), Directional(DirectionalAbility), Targeted(TargetedAbility), } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum TargetedAbility { MeeleAttack(MeeleAttack), RangedAttack(RangedAttack), } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct MeeleAttack { pub damage: Damage, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct RangedAttack { pub damage: Damage, } impl TargetedAbility { pub fn to_projectile( self, source_player: PlayerId, position: Vec2, target_player: PlayerId, ) -> Projectile { match self { TargetedAbility::MeeleAttack(MeeleAttack { damage }) => Projectile { type_: ProjectileType::Instant(InstantProjectile { target_player }), source_player, damage, }, TargetedAbility::RangedAttack(RangedAttack { damage }) => Projectile { type_: ProjectileType::Targeted(TargetedProjectile { target_player, position, }), source_player, damage, }, } } } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum ActivatedAbility { Focus(Focus), Haste(Haste), Shield(Shield), Speed(Speed), } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Focus { pub duration: f32, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Haste { pub duration: f32, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Shield { pub duration: f32, pub blocked_damage: f32, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Speed { pub duration: f32, } pub type ActivatedAbilityActivation = Box ()>; impl ActivatedAbility { pub fn activate(self) -> ActivatedAbilityActivation { match self { ActivatedAbility::Focus(focus) => focus_activation(focus), ActivatedAbility::Haste(haste) => haste_activation(haste), ActivatedAbility::Shield(shield) => shield_activation(shield), ActivatedAbility::Speed(speed) => speed_activation(speed), } } } fn focus_activation(focus: Focus) -> ActivatedAbilityActivation { Box::new(move |commands: &mut Commands, source_id: PlayerId| { commands.add(move |world: &mut World| { world.run_system_once( move |entity_map: Res, mut buffses: Query<&mut Buffs>| { let Some(entity_id) = entity_map.0.get(&source_id.0) else { return; }; let Ok(mut buffs) = buffses.get_mut(*entity_id) else { return; }; buffs.haste = Some(focus.duration); buffs.speed = Some(focus.duration); }, ) }); }) } fn haste_activation(haste: Haste) -> ActivatedAbilityActivation { Box::new(move |commands: &mut Commands, source_id: PlayerId| { commands.add(move |world: &mut World| { world.run_system_once( move |entity_map: Res, mut buffses: Query<&mut Buffs>| { let Some(entity_id) = entity_map.0.get(&source_id.0) else { return; }; let Ok(mut buffs) = buffses.get_mut(*entity_id) else { return; }; buffs.haste = Some(haste.duration); }, ) }); }) } fn shield_activation(shield: Shield) -> ActivatedAbilityActivation { Box::new(move |commands: &mut Commands, source_id: PlayerId| { commands.add(move |world: &mut World| { world.run_system_once( move |entity_map: Res, mut healths: Query<&mut Health>, mut buffses: Query<&mut Buffs>| { let Some(entity_id) = entity_map.0.get(&source_id.0) else { return; }; let Ok(mut health) = healths.get_mut(*entity_id) else { return; }; let Ok(mut buffs) = buffses.get_mut(*entity_id) else { return; }; health.shield = health.shield + shield.blocked_damage; buffs.shield = Some(shield.duration); }, ) }); }) } fn speed_activation(speed: Speed) -> ActivatedAbilityActivation { Box::new(move |commands: &mut Commands, source_id: PlayerId| { commands.add(move |world: &mut World| { world.run_system_once( move |entity_map: Res, mut buffses: Query<&mut Buffs>| { let Some(entity_id) = entity_map.0.get(&source_id.0) else { return; }; let Ok(mut buffs) = buffses.get_mut(*entity_id) else { return; }; buffs.speed = Some(speed.duration); }, ) }); }) } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub enum DirectionalAbility { Dash(Dash), Flash(Flash), Pull(Pull), Spear(Spear), } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Dash { pub max_distance: f32, pub damage: Damage, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Flash { pub max_distance: f32, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Pull { pub max_distance: f32, pub damage: Damage, } #[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)] pub struct Spear { pub max_distance: f32, pub damage: Damage, } // `direction: Vec2` is NOT normalized (cf. `Flash`)! pub type DirectionalAbilityActivation = Box ()>; impl DirectionalAbility { pub fn activate(self) -> DirectionalAbilityActivation { match self { DirectionalAbility::Dash(dash) => dash_activation(dash), DirectionalAbility::Flash(flash) => flash_activation(flash), DirectionalAbility::Pull(pull) => pull_activation(pull), DirectionalAbility::Spear(spear) => spear_activation(spear), } } } fn flash_activation(flash: Flash) -> DirectionalAbilityActivation { Box::new( move |commands: &mut Commands, source_player: PlayerId, direction: Vec2| { commands.add(move |world: &mut World| { world.run_system_once( move |entity_map: Res, mut positions: Query<&mut PlayerPosition>| { let Some(entity_id) = entity_map.0.get(&source_player.0) else { return; }; let Ok(mut position) = positions.get_mut(*entity_id) else { return; }; position.0 += direction.length().min(flash.max_distance) * direction.normalize_or_zero(); }, ); }); }, ) } fn dash_activation(dash: Dash) -> DirectionalAbilityActivation { Box::new( move |commands: &mut Commands, source_player: PlayerId, direction: Vec2| { commands.add(move |world: &mut World| { world.run_system_once( move |entity_map: Res, mut imperatives: Query<&mut Imperative>, mut set: ParamSet<( Query<(&mut PlayerPosition, &PlayerColor)>, Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, )>, mut commands: Commands| { let Some(source_entity) = entity_map.0.get(&source_player.0) else { return; }; let Some(DashCollisionResult { dash_end, dash_collision, }) = ({ let dash_targets = set.p1(); dash_collision( *source_entity, direction.normalize_or_zero(), dash.max_distance, &dash_targets, ) }) else { return; }; let mut positions = set.p0(); let Ok((mut position, source_player_color)) = positions.get_mut(*source_entity) else { return; }; position.0 = dash_end; if let Some(DashCollision { collision_player_id, collision_player_position, .. }) = dash_collision { commands.spawn(AreaOfEffectBundle::new(AreaOfEffect { 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_player_id, }), source_player, damage: dash.damage, }, ProjectileColor(source_player_color.0), )); if let Ok(mut imperative) = imperatives.get_mut(*source_entity) { *imperative = Imperative::AttackTarget(AbilitySlot::A, collision_player_id); } } }, ) }); }, ) } pub struct DashCollisionResult { pub dash_end: Vec2, pub dash_collision: Option, } 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_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_entity: Entity, dash_direction: Vec2, dash_max_distance: f32, dash_targets: &Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, ) -> Option { 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 { Box::new( move |commands: &mut Commands, source_player: PlayerId, direction: Vec2| { commands.add(move |world: &mut World| { world.run_system_once( move |players: Query<(Entity, &PlayerId)>, mut imperatives: Query<&mut Imperative>, mut set: ParamSet<( Query<(&mut PlayerPosition, &PlayerColor)>, Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, )>, mut commands: Commands, entity_map: Res| { let Some(source_entity) = entity_map.0.get(&source_player.0) else { return; }; let Some(PullCollisionResult { pull_end, collision_player_id, .. }) = ({ let pull_targets = set.p1(); pull_collision( *source_entity, direction.normalize_or_zero(), pull.max_distance, &pull_targets, ) }) else { return; }; let Some(target_entity) = ({ let mut target_entity = None; for (entity, player_id) in players.iter() { if *player_id != collision_player_id { continue; } target_entity = Some(entity); break; } target_entity }) else { return; }; let mut positions = set.p0(); let Ok([(_, source_player_color), (mut position, _)]) = positions.get_many_mut([*source_entity, target_entity]) else { return; }; position.0 = pull_end; commands.spawn(ProjectileBundle::new( Projectile { type_: ProjectileType::Instant(InstantProjectile { target_player: collision_player_id, }), source_player, damage: pull.damage, }, ProjectileColor(source_player_color.0), )); if let Ok(mut imperative) = imperatives.get_mut(*source_entity) { *imperative = Imperative::AttackTarget(AbilitySlot::A, collision_player_id); } }, ) }); }, ) } #[derive(Clone, Copy)] pub struct PullCollisionResult { pub pull_end: 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 { 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_entity: Entity, pull_direction: Vec2, pull_max_distance: f32, pull_targets: &Query<(Entity, &PlayerId, &PlayerPosition, &Shape)>, ) -> Option { 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, } 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 { 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 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 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(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 { result.final_position += start; Some(result) } } fn spear_activation(spear: Spear) -> DirectionalAbilityActivation { Box::new( move |commands: &mut Commands, source_player: PlayerId, direction: Vec2| { commands.add(move |world: &mut World| { world.run_system_once( move |mut commands: Commands, players: Query<(&PlayerId, &PlayerPosition, &PlayerColor)>, entity_map: Res| { let Some(source_entity) = entity_map.0.get(&source_player.0) else { return; }; let Ok((_source_player_id, source_player_position, source_player_color)) = players.get(*source_entity) else { return; }; commands.spawn(ProjectileBundle::new( Projectile { type_: ProjectileType::Free(FreeProjectile { position: source_player_position.0, direction: direction.normalize_or_zero(), starting_position: source_player_position.0, max_distance: spear.max_distance, scale_damage: Some((0.75, 2.5)), }), source_player, damage: spear.damage, }, ProjectileColor(source_player_color.0), )); }, ) }); }, ) } #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] pub enum AbilitySlot { A, Q, W, E, R, F, G, } impl AbilitySlot { pub fn to_label(self) -> &'static str { match self { AbilitySlot::A => "A", AbilitySlot::Q => "Q", AbilitySlot::W => "W", AbilitySlot::E => "E", AbilitySlot::R => "R", AbilitySlot::F => "F", AbilitySlot::G => "G", } } pub fn all() -> Vec { vec![ AbilitySlot::A, AbilitySlot::Q, AbilitySlot::W, AbilitySlot::E, AbilitySlot::R, AbilitySlot::F, AbilitySlot::G, ] } } impl Index for [Duration; 7] { type Output = Duration; fn index(&self, ability_slot: AbilitySlot) -> &Self::Output { &self[ability_slot as usize] } } impl IndexMut for [Duration; 7] { fn index_mut(&mut self, ability_slot: AbilitySlot) -> &mut Self::Output { &mut self[ability_slot as usize] } }