Skip to content

Commit

Permalink
Implement simple attacking functionality
Browse files Browse the repository at this point in the history
Relates to #24.
  • Loading branch information
Indy2222 committed Jul 5, 2022
1 parent 37d42be commit 170389c
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/attacking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ de_objects = { path = "../objects", version = "0.1.0-dev" }
de_terrain = { path = "../terrain", version = "0.1.0-dev" }
de_index = { path = "../index", version = "0.1.0-dev" }
de_spawner = { path = "../spawner", version = "0.1.0-dev" }
de_behaviour = { path = "../behaviour", version = "0.1.0-dev" }

# Other
bevy = "0.7.0"
Expand Down
136 changes: 136 additions & 0 deletions crates/attacking/src/attack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::{cmp::Ordering, collections::BinaryHeap};

use bevy::prelude::*;
use de_behaviour::ChaseTarget;
use de_core::state::GameState;
use de_objects::LaserCannon;
use iyes_loopless::prelude::*;
use parry3d::query::Ray;

use crate::laser::LaserFireEvent;
use crate::{sightline::LineOfSight, AttackingLabels};

pub(crate) struct AttackPlugin;

impl Plugin for AttackPlugin {
fn build(&self, app: &mut App) {
app.add_system_set_to_stage(
CoreStage::Update,
SystemSet::new()
.with_system(
update
.run_in_state(GameState::Playing)
.label(AttackingLabels::Update),
)
.with_system(
aim_and_fire
.run_in_state(GameState::Playing)
.label(AttackingLabels::Aim)
.after(AttackingLabels::Update)
.before(AttackingLabels::Fire),
),
);
}
}

fn update(time: Res<Time>, mut cannons: Query<&mut LaserCannon>) {
for mut cannon in cannons.iter_mut() {
cannon.timer_mut().tick(time.delta());
}
}

fn aim_and_fire(
mut attackers: Query<(Entity, &GlobalTransform, &mut LaserCannon, &ChaseTarget)>,
targets: Query<&GlobalTransform>,
sightline: LineOfSight,
mut events: EventWriter<LaserFireEvent>,
) {
let attackers = attackers.iter_mut();
// The queue is used so that attacking has the same result as if it was
// done in real-time (unaffected by update frequency).
let mut fire_queue = BinaryHeap::new();

for (attacker, attacker_transform, mut cannon, target) in attackers {
let target_transform = match targets.get(target.entity()) {
Ok(transform) => transform,
Err(_) => continue,
};

let muzzle = attacker_transform.translation + cannon.muzzle();
// TODO do not aim at the object position but at center of its body
let target_position = target_transform.translation;
let to_target = (target_position - muzzle)
.try_normalize()
.expect("Attacker and target to close together");
let ray = Ray::new(muzzle.into(), to_target.into());

let observation = sightline.sight(&ray, cannon.range(), attacker);
if observation.entity().map_or(true, |e| e != target.entity()) {
cannon.timer_mut().reset();
} else if cannon.timer_mut().check_and_update() {
fire_queue.push(FireScheduleItem::new(attacker, ray, cannon.into_inner()));
}
}

while let Some(mut fire_schedule_item) = fire_queue.pop() {
if fire_schedule_item.fire(&mut events) {
fire_queue.push(fire_schedule_item);
}
}
}

struct FireScheduleItem<'a> {
attacker: Entity,
ray: Ray,
cannon: &'a mut LaserCannon,
}

impl<'a> FireScheduleItem<'a> {
fn new(attacker: Entity, ray: Ray, cannon: &'a mut LaserCannon) -> Self {
Self {
attacker,
ray,
cannon,
}
}

fn fire(&mut self, events: &mut EventWriter<LaserFireEvent>) -> bool {
events.send(LaserFireEvent::new(
self.attacker,
self.ray,
self.cannon.range(),
self.cannon.damage(),
));
self.cannon.timer_mut().check_and_update()
}
}

impl<'a> Ord for FireScheduleItem<'a> {
fn cmp(&self, other: &Self) -> Ordering {
let ordering = self.cannon.timer().cmp(other.cannon.timer());
if let Ordering::Equal = ordering {
// Make it more deterministic, objects with smaller coordinates
// have disadvantage.
self.ray
.origin
.partial_cmp(&other.ray.origin)
.unwrap_or(Ordering::Equal)
} else {
ordering
}
}
}

impl<'a> PartialOrd for FireScheduleItem<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<'a> PartialEq for FireScheduleItem<'a> {
fn eq(&self, other: &Self) -> bool {
self.ray.origin == other.ray.origin && self.cannon.timer() == other.cannon.timer()
}
}

impl<'a> Eq for FireScheduleItem<'a> {}
6 changes: 5 additions & 1 deletion crates/attacking/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
use attack::AttackPlugin;
use bevy::{
app::PluginGroupBuilder,
prelude::{PluginGroup, SystemLabel},
};
use laser::LaserPlugin;

mod attack;
mod laser;
mod sightline;

pub struct AttackingPluginGroup;

impl PluginGroup for AttackingPluginGroup {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(LaserPlugin);
group.add(LaserPlugin).add(AttackPlugin);
}
}

#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
enum AttackingLabels {
Update,
Aim,
Fire,
}
2 changes: 1 addition & 1 deletion crates/behaviour/src/chase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl ChaseTarget {
}
}

fn entity(&self) -> Entity {
pub fn entity(&self) -> Entity {
self.entity
}

Expand Down

0 comments on commit 170389c

Please sign in to comment.