diff options
-rw-r--r-- | src/server.rs | 87 | ||||
-rw-r--r-- | src/shared.rs | 1 | ||||
-rw-r--r-- | src/shared/champion.rs | 14 | ||||
-rw-r--r-- | src/shared/minion.rs | 60 | ||||
-rw-r--r-- | src/shared/shape.rs | 4 |
5 files changed, 159 insertions, 7 deletions
diff --git a/src/server.rs b/src/server.rs index 4da2253..fa761fa 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,6 +11,7 @@ use crate::shared::health::*; use crate::shared::health_event::*; use crate::shared::immovable::*; use crate::shared::imperative::*; +use crate::shared::minion::*; use crate::shared::player::*; use crate::shared::projectile::*; use crate::shared::shape::*; @@ -91,17 +92,45 @@ 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, tower_ai); + .add_systems( + FixedUpdate, + (tower_ai, minion_ai).before(imperative_attack_approach), + ) + .add_systems(FixedUpdate, minion_despawn); } } fn setup(mut commands: Commands, mut entity_map: ResMut<EntityMap>) { - let entity = commands.spawn(PlayerBundle::new(1, Vec2::ZERO, Color::GRAY)); - 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()); + { + let entity_id = 1; + let entity = commands.spawn(TowerBundle::new(entity_id, Vec2::new(0., 100.), Color::RED)); + entity_map.0.insert(entity_id, entity.id()); + } + { + let entity_id = 2; + let entity = commands.spawn(TowerBundle::new( + entity_id, + Vec2::new(0., -100.), + Color::BLUE, + )); + entity_map.0.insert(entity_id, entity.id()); + } + for entity_id in 3..=8 { + let entity = commands.spawn(MinionBundle::new( + entity_id, + Vec2::new((entity_id - 2) as f32 * 25., 0.), + Color::BLUE, + )); + entity_map.0.insert(entity_id, entity.id()); + } + for entity_id in 9..=14 { + let entity = commands.spawn(MinionBundle::new( + entity_id, + Vec2::new((entity_id - 8) as f32 * -25., 0.), + Color::RED, + )); + entity_map.0.insert(entity_id, entity.id()); + } } fn connects( @@ -707,3 +736,47 @@ fn tower_ai( tower_tower.last_target_player_id = Some(*target_player_id); } } + +fn minion_ai( + mut minions: Query< + (&PlayerId, &mut Imperative, &PlayerPosition, &EffectiveStats), + With<Minion>, + >, + targets: Query<(&PlayerId, &PlayerPosition, &Shape)>, +) { + for (minion_player_id, mut minion_imperative, minion_player_position, minion_effective_stats) in + minions.iter_mut() + { + let mut closest_target = None; + for (target_player_id, target_player_position, target_shape) in targets.iter() { + if target_player_id == minion_player_id { + continue; + } + let target_in_range = minion_player_position.0.distance(target_player_position.0) + < minion_effective_stats.0.attack_range + target_shape.radius; + if !target_in_range { + continue; + } + let target_distance = minion_player_position.0.distance(target_player_position.0); + 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 { + *minion_imperative = Imperative::Idle; + continue; + }; + *minion_imperative = Imperative::AttackTarget(AbilitySlot::A, *target_player_id); + } +} + +fn minion_despawn(minions: Query<(Entity, &Health), With<Minion>>, mut commands: Commands) { + for (minion_entity, minion_health) in minions.iter() { + if minion_health.health <= 0. { + commands.entity(minion_entity).despawn() + } + } +} diff --git a/src/shared.rs b/src/shared.rs index 3310c8c..629ad7b 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -19,6 +19,7 @@ pub mod health; pub mod health_event; pub mod immovable; pub mod imperative; +pub mod minion; pub mod player; pub mod projectile; pub mod shape; diff --git a/src/shared/champion.rs b/src/shared/champion.rs index 1667672..711e520 100644 --- a/src/shared/champion.rs +++ b/src/shared/champion.rs @@ -10,6 +10,7 @@ pub enum Champion { Meele, Ranged, Tower, + Minion, } impl Default for Champion { @@ -26,6 +27,7 @@ impl FromStr for Champion { "ranged" => Ok(Champion::Ranged), "meele" => Ok(Champion::Meele), "tower" => Ok(Champion::Tower), + "minion" => Ok(Champion::Minion), _ => Err(format!("unknown champion: {}", s)), } } @@ -52,6 +54,12 @@ impl Champion { max_health: 500., movement_speed: 0., }), + Champion::Minion => BaseStats(Stats { + attack_range: 30., + attack_speed: 1., + max_health: 50., + movement_speed: 60., + }), } } @@ -102,6 +110,9 @@ impl Champion { Ability::Targeted(TargetedAbility::RangedAttack(RangedAttack { damage: 100. })) } }, + Champion::Minion => match ability_slot { + _ => Ability::Targeted(TargetedAbility::RangedAttack(RangedAttack { damage: 2. })), + }, } } @@ -116,6 +127,9 @@ impl Champion { Champion::Tower => { BaseCooldown([10., 10., 10., 10., 10., 10., 10.].map(Duration::from_secs_f32)) } + Champion::Minion => { + BaseCooldown([1., 1., 1., 1., 1., 1., 1.].map(Duration::from_secs_f32)) + } } } } diff --git a/src/shared/minion.rs b/src/shared/minion.rs new file mode 100644 index 0000000..5d41f7a --- /dev/null +++ b/src/shared/minion.rs @@ -0,0 +1,60 @@ +use crate::shared::activation::*; +use crate::shared::buffs::*; +use crate::shared::player::*; +use crate::shared::shape::*; +use crate::shared::stats::*; +use crate::shared::*; + +#[derive(Bundle)] +pub struct MinionBundle { + id: PlayerId, + position: PlayerPosition, + color: PlayerColor, + imperative: Imperative, + cooldown: Cooldown, + health: Health, + champion: Champion, + effective_stats: EffectiveStats, + buffs: Buffs, + activation: Activation, + shape: Shape, + minion: Minion, + replicate: Replicate, +} + +impl MinionBundle { + 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::Minion; + let effective_stats = EffectiveStats(champion.base_stats().0); + MinionBundle { + 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::minion(), + minion: Minion, + replicate, + } + } +} + +#[derive(Component, Message, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub struct Minion; diff --git a/src/shared/shape.rs b/src/shared/shape.rs index 7423375..6e11c56 100644 --- a/src/shared/shape.rs +++ b/src/shared/shape.rs @@ -13,4 +13,8 @@ impl Shape { pub fn tower() -> Self { Shape { radius: 25. } } + + pub fn minion() -> Self { + Shape { radius: 5. } + } } |