diff options
author | Alexander Foremny <aforemny@posteo.de> | 2024-03-22 15:53:45 +0100 |
---|---|---|
committer | Alexander Foremny <aforemny@posteo.de> | 2024-03-22 16:42:41 +0100 |
commit | 7f962b034f888135ce618e03bea40a08b57a68c7 (patch) | |
tree | c9038a12b2712690a2e96a526814dc2ba1c0e9e4 | |
parent | 2d8740b1e29fc356fbe88cea21059ec83d8c0cf0 (diff) |
feat: nexuses
-rw-r--r-- | src/server.rs | 182 | ||||
-rw-r--r-- | src/shared.rs | 2 | ||||
-rw-r--r-- | src/shared/champion.rs | 14 | ||||
-rw-r--r-- | src/shared/faction.rs | 16 | ||||
-rw-r--r-- | src/shared/minion.rs | 7 | ||||
-rw-r--r-- | src/shared/nexus.rs | 77 | ||||
-rw-r--r-- | src/shared/shape.rs | 4 | ||||
-rw-r--r-- | src/shared/tower.rs | 7 |
8 files changed, 270 insertions, 39 deletions
diff --git a/src/server.rs b/src/server.rs index fa761fa..b09e96b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,11 +7,13 @@ use crate::shared::area_of_effect::*; use crate::shared::buffs::*; use crate::shared::champion::*; use crate::shared::cooldown::*; +use crate::shared::faction::*; use crate::shared::health::*; use crate::shared::health_event::*; use crate::shared::immovable::*; use crate::shared::imperative::*; use crate::shared::minion::*; +use crate::shared::nexus::*; use crate::shared::player::*; use crate::shared::projectile::*; use crate::shared::shape::*; @@ -94,42 +96,66 @@ impl Plugin for ServerPlugin { .add_systems(FixedUpdate, player_input) .add_systems( FixedUpdate, - (tower_ai, minion_ai).before(imperative_attack_approach), + (tower_ai, (minion_ai_attack, minion_ai_walk).chain()) + .before(imperative_attack_approach), ) - .add_systems(FixedUpdate, minion_despawn); + .add_systems(FixedUpdate, minion_despawn) + .add_systems(FixedUpdate, (nexus_tick, nexus_spawn_minions)); } } fn setup(mut commands: Commands, mut entity_map: ResMut<EntityMap>) { { - 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 client_id = 1; + let entity = commands.spawn(TowerBundle::new( + client_id, + Vec2::new(0., 100.), + Faction::Blue, + )); + entity_map.0.insert(client_id, entity.id()); } { - let entity_id = 2; + let client_id = 2; let entity = commands.spawn(TowerBundle::new( - entity_id, + client_id, Vec2::new(0., -100.), - Color::BLUE, + Faction::Red, )); - entity_map.0.insert(entity_id, entity.id()); + entity_map.0.insert(client_id, entity.id()); } - for entity_id in 3..=8 { + for client_id in 3..=8 { let entity = commands.spawn(MinionBundle::new( - entity_id, - Vec2::new((entity_id - 2) as f32 * 25., 0.), - Color::BLUE, + client_id, + Vec2::new((client_id - 2) as f32 * 25., 0.), + Faction::Blue, )); - entity_map.0.insert(entity_id, entity.id()); + entity_map.0.insert(client_id, entity.id()); } - for entity_id in 9..=14 { + for client_id in 9..=14 { let entity = commands.spawn(MinionBundle::new( - entity_id, - Vec2::new((entity_id - 8) as f32 * -25., 0.), - Color::RED, + client_id, + Vec2::new((client_id - 8) as f32 * -25., 0.), + Faction::Red, )); - entity_map.0.insert(entity_id, entity.id()); + entity_map.0.insert(client_id, entity.id()); + } + { + let client_id = 15; + let entity = commands.spawn(NexusBundle::new( + client_id, + Vec2::new(100., 100.0), + Faction::Blue, + )); + entity_map.0.insert(client_id, entity.id()); + } + { + let client_id = 16; + let entity = commands.spawn(NexusBundle::new( + client_id, + Vec2::new(-100., -100.0), + Faction::Red, + )); + entity_map.0.insert(client_id, entity.id()); } } @@ -698,14 +724,26 @@ fn tower_ai( &mut Imperative, &PlayerPosition, &EffectiveStats, + &Faction, )>, - targets: Query<(&PlayerId, &PlayerPosition, &Shape), Without<Tower>>, + targets: Query<(&PlayerId, &PlayerPosition, &Shape, &Faction), Without<Tower>>, ) { - for (mut tower_tower, mut tower_imperative, tower_player_position, tower_effective_stats) in - towers.iter_mut() + for ( + mut tower_tower, + mut tower_imperative, + tower_player_position, + tower_effective_stats, + tower_faction, + ) in towers.iter_mut() { let mut closest_target = None; - for (target_player_id, target_player_position, target_shape) in targets.iter() { + for (target_player_id, target_player_position, target_shape, target_faction) in + targets.iter() + { + if target_faction == tower_faction { + continue; + } + let target_in_range = tower_player_position.0.distance(target_player_position.0) < tower_effective_stats.0.attack_range + target_shape.radius; @@ -737,19 +775,46 @@ fn tower_ai( } } -fn minion_ai( - mut minions: Query< - (&PlayerId, &mut Imperative, &PlayerPosition, &EffectiveStats), - With<Minion>, - >, - targets: Query<(&PlayerId, &PlayerPosition, &Shape)>, +fn minion_ai_walk( + mut minions: Query<(&mut Imperative, &PlayerPosition, &Faction), With<Minion>>, + nexuses: Query<(&PlayerPosition, &Faction), With<Nexus>>, ) { - for (minion_player_id, mut minion_imperative, minion_player_position, minion_effective_stats) in + for (mut minion_imperative, minion_player_position, minion_faction) in minions.iter_mut() { + if *minion_imperative != Imperative::Idle { + continue; + } + let mut closest_target = None; + for (nexus_player_position, nexus_faction) in nexuses.iter() { + if nexus_faction == minion_faction { + continue; + } + let target_distance = minion_player_position.0.distance(nexus_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((nexus_player_position.0, target_distance)); + } + } + let Some((target_player_position, _)) = closest_target else { + *minion_imperative = Imperative::Idle; + continue; + }; + *minion_imperative = Imperative::WalkTo(target_player_position); + } +} +fn minion_ai_attack( + mut minions: Query<(&mut Imperative, &PlayerPosition, &EffectiveStats, &Faction), With<Minion>>, + targets: Query<(&PlayerId, &PlayerPosition, &Shape, &Faction)>, +) { + for (mut minion_imperative, minion_player_position, minion_effective_stats, minion_faction) 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 { + for (target_player_id, target_player_position, target_shape, target_faction) in + targets.iter() + { + if target_faction == minion_faction { continue; } let target_in_range = minion_player_position.0.distance(target_player_position.0) @@ -773,10 +838,57 @@ fn minion_ai( } } -fn minion_despawn(minions: Query<(Entity, &Health), With<Minion>>, mut commands: Commands) { - for (minion_entity, minion_health) in minions.iter() { +fn minion_despawn( + minions: Query<(Entity, &PlayerId, &Health), With<Minion>>, + mut commands: Commands, + mut entity_map: ResMut<EntityMap>, +) { + for (minion_entity, minion_player_id, minion_health) in minions.iter() { if minion_health.health <= 0. { - commands.entity(minion_entity).despawn() + commands.entity(minion_entity).despawn(); + entity_map.0.remove(&minion_player_id.0); + } + } +} + +fn nexus_spawn_minions( + nexuses: Query<(&Nexus, &PlayerPosition, &Faction)>, + mut commands: Commands, + mut entity_map: ResMut<EntityMap>, +) { + for (nexus_nexus, nexus_player_position, nexus_faction) in nexuses.iter() { + if nexus_nexus.spawn_minions.just_finished() { + let client_ids = generate_client_ids(5, &entity_map); + for client_id in client_ids { + let entity = commands.spawn(MinionBundle::new( + client_id, + nexus_player_position.0, + *nexus_faction, + )); + entity_map.0.insert(client_id, entity.id()); + } + } + } +} + +fn generate_client_ids(n: u64, entity_map: &ResMut<EntityMap>) -> Vec<u64> { + let mut rng = rand::thread_rng(); + let mut k = 0; + let mut client_ids = vec![]; + while k < n { + let client_id = rng.gen(); + if entity_map.0.contains_key(&client_id) { + continue; } + client_ids.push(client_id); + k += 1; + } + client_ids +} + +fn nexus_tick(mut nexuses: Query<&mut Nexus>, time: Res<Time>) { + let dt = time.delta(); + for mut nexus in nexuses.iter_mut() { + nexus.spawn_minions.tick(dt); } } diff --git a/src/shared.rs b/src/shared.rs index 629ad7b..a4ed8fc 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -15,11 +15,13 @@ pub mod area_of_effect; pub mod buffs; pub mod champion; pub mod cooldown; +pub mod faction; pub mod health; pub mod health_event; pub mod immovable; pub mod imperative; pub mod minion; +pub mod nexus; pub mod player; pub mod projectile; pub mod shape; diff --git a/src/shared/champion.rs b/src/shared/champion.rs index 711e520..64fc32e 100644 --- a/src/shared/champion.rs +++ b/src/shared/champion.rs @@ -11,6 +11,7 @@ pub enum Champion { Ranged, Tower, Minion, + Nexus, } impl Default for Champion { @@ -28,6 +29,7 @@ impl FromStr for Champion { "meele" => Ok(Champion::Meele), "tower" => Ok(Champion::Tower), "minion" => Ok(Champion::Minion), + "nexus" => Ok(Champion::Nexus), _ => Err(format!("unknown champion: {}", s)), } } @@ -60,6 +62,12 @@ impl Champion { max_health: 50., movement_speed: 60., }), + Champion::Nexus => BaseStats(Stats { + attack_range: 0., + attack_speed: 0., + max_health: 2000., + movement_speed: 0., + }), } } @@ -113,6 +121,9 @@ impl Champion { Champion::Minion => match ability_slot { _ => Ability::Targeted(TargetedAbility::RangedAttack(RangedAttack { damage: 2. })), }, + Champion::Nexus => match ability_slot { + _ => Ability::Targeted(TargetedAbility::MeeleAttack(MeeleAttack { damage: 0. })), + }, } } @@ -130,6 +141,9 @@ impl Champion { Champion::Minion => { BaseCooldown([1., 1., 1., 1., 1., 1., 1.].map(Duration::from_secs_f32)) } + Champion::Nexus => { + BaseCooldown([0., 0., 0., 0., 0., 0., 0.].map(Duration::from_secs_f32)) + } } } } diff --git a/src/shared/faction.rs b/src/shared/faction.rs new file mode 100644 index 0000000..4ff4a02 --- /dev/null +++ b/src/shared/faction.rs @@ -0,0 +1,16 @@ +use crate::shared::*; + +#[derive(Component, Clone, Copy, PartialEq, Eq)] +pub enum Faction { + Red, + Blue, +} + +impl Faction { + pub fn to_color(self) -> Color { + match self { + Faction::Red => Color::RED, + Faction::Blue => Color::BLUE, + } + } +} diff --git a/src/shared/minion.rs b/src/shared/minion.rs index 5d41f7a..4aa824e 100644 --- a/src/shared/minion.rs +++ b/src/shared/minion.rs @@ -1,5 +1,6 @@ use crate::shared::activation::*; use crate::shared::buffs::*; +use crate::shared::faction::*; use crate::shared::player::*; use crate::shared::shape::*; use crate::shared::stats::*; @@ -19,11 +20,12 @@ pub struct MinionBundle { activation: Activation, shape: Shape, minion: Minion, + faction: Faction, replicate: Replicate, } impl MinionBundle { - pub fn new(id: ClientId, position: Vec2, color: Color) -> Self { + pub fn new(id: ClientId, position: Vec2, faction: Faction) -> Self { let mut replicate = Replicate { replication_group: ReplicationGroup::default().set_priority(10.), ..Default::default() @@ -38,7 +40,7 @@ impl MinionBundle { MinionBundle { id: PlayerId(id), position: PlayerPosition(position), - color: PlayerColor(color), + color: PlayerColor(faction.to_color()), imperative: Imperative::Idle, cooldown: Cooldown::default(), health: Health { @@ -51,6 +53,7 @@ impl MinionBundle { activation: Activation::default(), shape: Shape::minion(), minion: Minion, + faction, replicate, } } diff --git a/src/shared/nexus.rs b/src/shared/nexus.rs new file mode 100644 index 0000000..df91db9 --- /dev/null +++ b/src/shared/nexus.rs @@ -0,0 +1,77 @@ +use crate::shared::activation::*; +use crate::shared::buffs::*; +use crate::shared::faction::*; +use crate::shared::immovable::*; +use crate::shared::player::*; +use crate::shared::shape::*; +use crate::shared::stats::*; +use crate::shared::*; +use bevy::utils::Duration; + +#[derive(Bundle)] +pub struct NexusBundle { + id: PlayerId, + position: PlayerPosition, + color: PlayerColor, + imperative: Imperative, + cooldown: Cooldown, + health: Health, + champion: Champion, + effective_stats: EffectiveStats, + buffs: Buffs, + activation: Activation, + shape: Shape, + nexus: Nexus, + immovable: Immovable, + faction: Faction, + replicate: Replicate, +} + +impl NexusBundle { + pub fn new(id: ClientId, position: Vec2, faction: Faction) -> 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::Nexus; + let effective_stats = EffectiveStats(champion.base_stats().0); + NexusBundle { + id: PlayerId(id), + position: PlayerPosition(position), + color: PlayerColor(faction.to_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::nexus(), + nexus: Nexus::default(), + immovable: Immovable, + faction, + replicate, + } + } +} + +#[derive(Component)] +pub struct Nexus { + pub spawn_minions: Timer, +} + +impl Default for Nexus { + fn default() -> Self { + let mut spawn_minions = Timer::from_seconds(60., TimerMode::Repeating); + spawn_minions.set_elapsed(Duration::from_secs_f32(60.)); + Nexus { spawn_minions } + } +} diff --git a/src/shared/shape.rs b/src/shared/shape.rs index 6e11c56..d77403d 100644 --- a/src/shared/shape.rs +++ b/src/shared/shape.rs @@ -17,4 +17,8 @@ impl Shape { pub fn minion() -> Self { Shape { radius: 5. } } + + pub fn nexus() -> Self { + Shape { radius: 35. } + } } diff --git a/src/shared/tower.rs b/src/shared/tower.rs index 5b62b7a..a21048a 100644 --- a/src/shared/tower.rs +++ b/src/shared/tower.rs @@ -1,5 +1,6 @@ use crate::shared::activation::*; use crate::shared::buffs::*; +use crate::shared::faction::*; use crate::shared::immovable::*; use crate::shared::player::*; use crate::shared::shape::*; @@ -21,11 +22,12 @@ pub struct TowerBundle { shape: Shape, tower: Tower, immovable: Immovable, + faction: Faction, replicate: Replicate, } impl TowerBundle { - pub fn new(id: ClientId, position: Vec2, color: Color) -> Self { + pub fn new(id: ClientId, position: Vec2, faction: Faction) -> Self { let mut replicate = Replicate { replication_group: ReplicationGroup::default().set_priority(10.), ..Default::default() @@ -40,7 +42,7 @@ impl TowerBundle { TowerBundle { id: PlayerId(id), position: PlayerPosition(position), - color: PlayerColor(color), + color: PlayerColor(faction.to_color()), imperative: Imperative::Idle, cooldown: Cooldown::default(), health: Health { @@ -54,6 +56,7 @@ impl TowerBundle { shape: Shape::tower(), tower: Tower::default(), immovable: Immovable, + faction, replicate, } } |