Skip to content

Commit

Permalink
bevy_eventlistener: Intents are EntityEvents, behaviors are `Li…
Browse files Browse the repository at this point in the history
…stener`s
  • Loading branch information
abesto committed Dec 9, 2023
1 parent 3118eea commit 8bc7403
Show file tree
Hide file tree
Showing 32 changed files with 265 additions and 194 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ edition = "2021"
[dependencies]
bevy = "0.12.1"
bevy_ascii_terminal = "0.14.0"
bevy_eventlistener = "0.6.1"
bevy_prng = { version = "0.2.0", features = ["wyrand"] }
bevy_rand = "0.4.0"
bracket-noise = "0.8.7"
impl-trait-for-tuples = "0.2.2"
itertools = "0.12.0"
paste = "1.0.14"
pathfinding = "4.4.0"
Expand Down
3 changes: 3 additions & 0 deletions src/behaviors/actor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use super::{attack::AttackBehavior, movement::MovementBehavior, wait::WaitBehavior};

pub type ActorBehavior = (MovementBehavior, AttackBehavior, WaitBehavior);
38 changes: 38 additions & 0 deletions src/behaviors/attack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;
use bevy_prng::WyRand;
use bevy_rand::resource::GlobalEntropy;
use rand::seq::IteratorRandom;

use crate::{
behavior,
components::{name::Name, position::Position},
events::{intents::attack::AttackIntent, took_turn::TookTurn},
};

fn attack(
q_actor: Query<(&Name, &Position)>,
attack: Listener<AttackIntent>,
q_target: Query<(&Name, &Position)>, // TODO: replace by collision system caching positions
mut ev_took_turn: EventWriter<TookTurn>,
mut rng: ResMut<GlobalEntropy<WyRand>>,
) {
let attacker = attack.listener();
let attack_direction = attack.vector;
let Ok((attacker_name, attacker_position)) = q_actor.get(attacker) else {
return;
};
let attack_location = (attacker_position.xy.as_ivec2() + attack_direction).as_uvec2();

if let Some((target_name, _)) = q_target
.iter()
.filter(|(_, position)| position.xy == attack_location)
.choose(&mut *rng)
{
println!("{} attacks {}", attacker_name, target_name);
}

ev_took_turn.send(attacker.into());
}

behavior!(attack);
33 changes: 33 additions & 0 deletions src/behaviors/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use impl_trait_for_tuples::impl_for_tuples;

pub mod actor;
pub mod attack;
pub mod movement;
pub mod wait;

pub trait Behavior {
fn behavior() -> Self;
}

#[impl_for_tuples(1, 16)]
impl Behavior for BehaviorIdentifier {
fn behavior() -> Self {
for_tuples!( ( #( (BehaviorIdentifier::behavior()) ),* ) )
}
}

// If I was an adult, I'd implement this as a derive macro
#[macro_export]
macro_rules! behavior {
($n:ident) => {
paste::paste! {
pub type [< $n:camel Behavior >] = On< [<$n:camel Intent >] >;

impl $crate::behaviors::Behavior for [< $n:camel Behavior >] {
fn behavior() -> Self {
On::< [< $n:camel Intent >] >::run( [< $n >] )
}
}
}
};
}
28 changes: 28 additions & 0 deletions src/behaviors/movement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;

use crate::{
behavior,
components::{energy::Active, position::Position},
events::{intents::movement::MovementIntent, took_turn::TookTurn},
plugins::map::resources::Map,
};

fn movement(
mut query: Query<&mut Position, With<Active>>,
map: Res<Map>,
intent: Listener<MovementIntent>,
mut ev_took_turn: EventWriter<TookTurn>,
) {
let entity = intent.listener();
let Ok(mut position) = query.get_mut(entity) else {
return;
};
let new_position = map.iclamp(&(position.xy.as_ivec2() + intent.vector));
if map.is_walkable(&new_position) {
position.xy = new_position;
}
ev_took_turn.send(entity.into());
}

behavior!(movement);
13 changes: 13 additions & 0 deletions src/behaviors/wait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;

use crate::{
behavior,
events::{intents::wait::WaitIntent, took_turn::TookTurn},
};

fn wait(wait: Listener<WaitIntent>, mut ev_took_turn: EventWriter<TookTurn>) {
ev_took_turn.send(wait.listener().into());
}

behavior!(wait);
21 changes: 14 additions & 7 deletions src/plugins/spawner/bundles/dog.rs → src/bundles/dog.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
use bevy::{ecs::bundle::Bundle, render::color::Color};
use bevy_ascii_terminal::TileFormatter;

use crate::components::{
ai::Ai,
energy::Energy,
name::Name,
position::Position,
renderable::{RenderLayer, Renderable},
use crate::{
behaviors::{actor::ActorBehavior, Behavior},
components::{
ai::Ai,
energy::Energy,
name::Name,
position::Position,
renderable::{RenderLayer, Renderable},
},
};

type Behaviors = ActorBehavior;

#[derive(Bundle)]
pub struct DogBundle {
pub renderable: Renderable,
pub position: Position,
pub energy: Energy,
pub ai: Ai,
pub name: Name,
pub behaviors: Behaviors,
}

impl Default for DogBundle {
fn default() -> Self {
DogBundle {
renderable: Renderable::new('d'.fg(Color::WHITE), RenderLayer::Monsters),
position: Position::default(),
energy: Energy::new(1),
ai: Ai::Dog,
name: Name::new("Fluffy"),
behaviors: Behaviors::behavior(),
position: Position::default(),
}
}
}
21 changes: 14 additions & 7 deletions src/plugins/spawner/bundles/goblin.rs → src/bundles/goblin.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
use bevy::{ecs::bundle::Bundle, render::color::Color};
use bevy_ascii_terminal::TileFormatter;

use crate::components::{
ai::Ai,
energy::Energy,
name::Name,
position::Position,
renderable::{RenderLayer, Renderable},
use crate::{
behaviors::{actor::ActorBehavior, Behavior},
components::{
ai::Ai,
energy::Energy,
name::Name,
position::Position,
renderable::{RenderLayer, Renderable},
},
};

#[derive(Bundle, Clone)]
type Behaviors = ActorBehavior;

#[derive(Bundle)]
pub struct GoblinBundle {
pub renderable: Renderable,
pub position: Position,
pub energy: Energy,
pub ai: Ai,
pub name: Name,
pub behaviors: Behaviors,
}

impl Default for GoblinBundle {
Expand All @@ -26,6 +32,7 @@ impl Default for GoblinBundle {
energy: Energy::new(2),
ai: Ai::Monster,
name: Name::new("goblin"),
behaviors: Behaviors::behavior(),
}
}
}
File renamed without changes.
21 changes: 14 additions & 7 deletions src/plugins/spawner/bundles/orc.rs → src/bundles/orc.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
use bevy::{ecs::bundle::Bundle, render::color::Color};
use bevy_ascii_terminal::TileFormatter;

use crate::components::{
ai::Ai,
energy::Energy,
name::Name,
position::Position,
renderable::{RenderLayer, Renderable},
use crate::{
behaviors::{actor::ActorBehavior, Behavior},
components::{
ai::Ai,
energy::Energy,
name::Name,
position::Position,
renderable::{RenderLayer, Renderable},
},
};

#[derive(Bundle, Clone)]
type Behaviors = ActorBehavior;

#[derive(Bundle)]
pub struct OrcBundle {
pub renderable: Renderable,
pub position: Position,
pub energy: Energy,
pub ai: Ai,
pub name: Name,
pub behaviors: Behaviors,
}

impl Default for OrcBundle {
Expand All @@ -26,6 +32,7 @@ impl Default for OrcBundle {
energy: Energy::new(2),
ai: Ai::Monster,
name: Name::new("orc"),
behaviors: Behaviors::behavior(),
}
}
}
19 changes: 13 additions & 6 deletions src/plugins/spawner/bundles/player.rs → src/bundles/player.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
use bevy::{ecs::bundle::Bundle, render::color::Color};
use bevy_ascii_terminal::TileFormatter;

use crate::components::{
energy::Energy,
name::Name,
player::Player,
position::Position,
renderable::{RenderLayer, Renderable},
use crate::{
behaviors::{actor::ActorBehavior, Behavior},
components::{
energy::Energy,
name::Name,
player::Player,
position::Position,
renderable::{RenderLayer, Renderable},
},
};

type Behaviors = ActorBehavior;

#[derive(Bundle)]
pub struct PlayerBundle {
pub player: Player,
pub renderable: Renderable,
pub position: Position,
pub energy: Energy,
pub name: Name,
pub behaviors: Behaviors,
}

impl Default for PlayerBundle {
Expand All @@ -26,6 +32,7 @@ impl Default for PlayerBundle {
position: Position::default(),
energy: Energy::new(1000),
name: Name::new("Player"),
behaviors: Behaviors::behavior(),
}
}
}
4 changes: 0 additions & 4 deletions src/components/intents/attack.rs

This file was deleted.

4 changes: 0 additions & 4 deletions src/components/intents/movement.rs

This file was deleted.

4 changes: 0 additions & 4 deletions src/components/intents/wait.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub mod ai;
pub mod energy;
pub mod intents;
pub mod name;
pub mod player;
pub mod position;
Expand Down
6 changes: 6 additions & 0 deletions src/components/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ impl Name {
}
}
}

impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
10 changes: 10 additions & 0 deletions src/events/intents/attack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;

#[derive(Clone, Event, EntityEvent)]
pub struct AttackIntent {
// This reads weird, watch out: it's the target of the event, but the attacker in the attack.
#[target]
pub actor: Entity,
pub vector: IVec2,
}
File renamed without changes.
9 changes: 9 additions & 0 deletions src/events/intents/movement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;

#[derive(Clone, Event, EntityEvent)]
pub struct MovementIntent {
#[target]
pub actor: Entity,
pub vector: IVec2,
}
8 changes: 8 additions & 0 deletions src/events/intents/wait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;

#[derive(Clone, Event, EntityEvent)]
pub struct WaitIntent {
#[target]
pub actor: Entity,
}
14 changes: 14 additions & 0 deletions src/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
use bevy::prelude::*;
use bevy_eventlistener::prelude::*;

use crate::events::intents::{attack::AttackIntent, wait::WaitIntent};

use self::intents::movement::MovementIntent;

pub mod intents;
pub mod took_turn;

// short for "event listener plugins"
macro_rules! elp {
($app:ident, $($events:ident),+ $(,)?) => {
$($app.add_plugins(EventListenerPlugin::<$events>::default());)+
};
}

pub struct EventsPlugin;

impl Plugin for EventsPlugin {
fn build(&self, app: &mut App) {
app.add_event::<took_turn::TookTurn>();
elp!(app, MovementIntent, AttackIntent, WaitIntent);
}
}

0 comments on commit 8bc7403

Please sign in to comment.