use bevy::log::LogPlugin;
use bevy::prelude::*;
use bevy::utils::tracing::{debug, trace};
use big_brain::prelude::*;
#[derive(Component, Debug, Copy, Clone)]
pub struct Position {
pub position: Vec2,
}
#[derive(Component, Debug)]
pub struct WaterSource;
#[derive(Component, Debug)]
pub struct Thirst {
pub per_second: f32,
pub thirst: f32,
}
impl Thirst {
pub fn new(thirst: f32, per_second: f32) -> Self {
Self { thirst, per_second }
}
}
pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
for mut thirst in &mut thirsts {
thirst.thirst += thirst.per_second * time.delta_secs();
if thirst.thirst >= 100.0 {
thirst.thirst = 100.0;
}
trace!("Thirst: {}", thirst.thirst);
}
}
#[derive(Clone, Component, Debug, ActionBuilder)]
pub struct MoveToWaterSource {
speed: f32,
}
const MAX_DISTANCE: f32 = 0.1;
fn move_to_water_source_action_system(
time: Res<Time>,
waters: Query<&Position, With<WaterSource>>,
mut positions: Query<&mut Position, Without<WaterSource>>,
mut action_query: Query<(&Actor, &mut ActionState, &MoveToWaterSource, &ActionSpan)>,
) {
for (actor, mut action_state, move_to, span) in &mut action_query {
let _guard = span.span().enter();
match *action_state {
ActionState::Requested => {
debug!("Let's go find some water!");
*action_state = ActionState::Executing;
}
ActionState::Executing => {
let mut actor_position = positions.get_mut(actor.0).expect("actor has no position");
trace!("Actor position: {:?}", actor_position.position);
let closest_water_source = find_closest_water_source(&waters, &actor_position);
let delta = closest_water_source.position - actor_position.position;
let distance = delta.length();
trace!("Distance: {}", distance);
if distance > MAX_DISTANCE {
trace!("Stepping closer.");
let step_size = time.delta_secs() * move_to.speed;
let step = delta.normalize() * step_size.min(distance);
actor_position.position += step;
} else {
debug!("We got there!");
*action_state = ActionState::Success;
}
}
ActionState::Cancelled => {
*action_state = ActionState::Failure;
}
_ => {}
}
}
}
fn find_closest_water_source(
waters: &Query<&Position, With<WaterSource>>,
actor_position: &Position,
) -> Position {
*(waters
.iter()
.min_by(|a, b| {
let da = (a.position - actor_position.position).length_squared();
let db = (b.position - actor_position.position).length_squared();
da.partial_cmp(&db).unwrap()
})
.expect("no water sources"))
}
#[derive(Clone, Component, Debug, ActionBuilder)]
pub struct Drink {
per_second: f32,
}
fn drink_action_system(
time: Res<Time>,
mut thirsts: Query<(&Position, &mut Thirst), Without<WaterSource>>,
waters: Query<&Position, With<WaterSource>>,
mut query: Query<(&Actor, &mut ActionState, &Drink, &ActionSpan)>,
) {
for (Actor(actor), mut state, drink, span) in &mut query {
let _guard = span.span().enter();
let (actor_position, mut thirst) = thirsts.get_mut(*actor).expect("actor has no thirst");
match *state {
ActionState::Requested => {
debug!("Drinking the water.");
*state = ActionState::Executing;
}
ActionState::Executing => {
let closest_water_source = find_closest_water_source(&waters, actor_position);
let distance = (closest_water_source.position - actor_position.position).length();
if distance < MAX_DISTANCE {
trace!("Drinking!");
thirst.thirst -= drink.per_second * time.delta_secs();
if thirst.thirst <= 0.0 {
thirst.thirst = 0.0;
*state = ActionState::Success;
}
} else {
debug!("We're too far away!");
*state = ActionState::Failure;
}
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
}
}
}
#[derive(Clone, Component, Debug, ScorerBuilder)]
pub struct Thirsty;
pub fn thirsty_scorer_system(
thirsts: Query<&Thirst>,
mut query: Query<(&Actor, &mut Score), With<Thirsty>>,
) {
for (Actor(actor), mut score) in &mut query {
if let Ok(thirst) = thirsts.get(*actor) {
score.set(thirst.thirst / 100.);
}
}
}
pub fn init_entities(mut cmd: Commands) {
cmd.spawn((
WaterSource,
Position {
position: Vec2::new(10.0, 10.0),
},
));
cmd.spawn((
WaterSource,
Position {
position: Vec2::new(-10.0, 0.0),
},
));
let move_and_drink = Steps::build()
.label("MoveAndDrink")
.step(MoveToWaterSource { speed: 1.0 })
.step(Drink { per_second: 10.0 });
let thinker = Thinker::build()
.label("ThirstyThinker")
.picker(FirstToScore { threshold: 0.8 })
.when(Thirsty, move_and_drink);
cmd.spawn((
Thirst::new(75.0, 2.0),
Position {
position: Vec2::new(0.0, 0.0),
},
thinker,
));
}
fn main() {
App::new()
.add_plugins(MinimalPlugins)
.add_plugins(LogPlugin {
filter: "big_brain=debug,sequence=debug".to_string(),
..default()
})
.add_plugins(BigBrainPlugin::new(PreUpdate))
.add_systems(Startup, init_entities)
.add_systems(Update, thirst_system)
.add_systems(
PreUpdate,
(drink_action_system, move_to_water_source_action_system).in_set(BigBrainSet::Actions),
)
.add_systems(First, thirsty_scorer_system)
.run();
}