aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-22 15:53:45 +0100
committerLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-22 16:42:41 +0100
commit7f962b034f888135ce618e03bea40a08b57a68c7 (patch)
treec9038a12b2712690a2e96a526814dc2ba1c0e9e4
parent2d8740b1e29fc356fbe88cea21059ec83d8c0cf0 (diff)
feat: nexuses
-rw-r--r--src/server.rs182
-rw-r--r--src/shared.rs2
-rw-r--r--src/shared/champion.rs14
-rw-r--r--src/shared/faction.rs16
-rw-r--r--src/shared/minion.rs7
-rw-r--r--src/shared/nexus.rs77
-rw-r--r--src/shared/shape.rs4
-rw-r--r--src/shared/tower.rs7
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,
}
}