Added interrupt_with combinator, guard patrol patterns

This commit is contained in:
Joshua Barretto 2023-04-05 21:45:10 +01:00
parent a7a08763f2
commit ce5ef481e1
3 changed files with 124 additions and 11 deletions

View File

@ -134,6 +134,36 @@ pub trait Action<R = ()>: Any + Send + Sync {
StopIf(self, f.clone(), f) StopIf(self, f.clone(), f)
} }
/// Pause an action to possibly perform another action.
///
/// # Example
///
/// ```ignore
/// // Keep going on adventures until your 111th birthday
/// walk_to_the_shops()
/// .interrupt_with(|ctx| if ctx.npc.is_hungry() {
/// Some(eat_food())
/// } else {
/// None
/// })
/// ```
#[must_use]
fn interrupt_with<A1: Action<R1>, R1, F: FnMut(&mut NpcCtx) -> Option<A1> + Clone>(
self,
f: F,
) -> InterruptWith<Self, F, A1, R1>
where
Self: Sized,
{
InterruptWith {
a0: self,
f: f.clone(),
f2: f,
a1: None,
phantom: PhantomData,
}
}
/// Map the completion value of this action to something else. /// Map the completion value of this action to something else.
#[must_use] #[must_use]
fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R> fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R>
@ -606,6 +636,62 @@ impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync
} }
} }
// InterruptWith
/// See [`Action::then`].
#[derive(Copy, Clone)]
pub struct InterruptWith<A0, F, A1, R1> {
a0: A0,
f: F,
f2: F,
a1: Option<A1>,
phantom: PhantomData<R1>,
}
impl<
A0: Action<R0>,
A1: Action<R1>,
F: FnMut(&mut NpcCtx) -> Option<A1> + Clone + Send + Sync + 'static,
R0: Send + Sync + 'static,
R1: Send + Sync + 'static,
> Action<R0> for InterruptWith<A0, F, A1, R1>
{
fn is_same(&self, other: &Self) -> bool { self.a0.is_same(&other.a0) }
fn dyn_is_same(&self, other: &dyn Action<R0>) -> bool { self.dyn_is_same_sized(other) }
fn backtrace(&self, bt: &mut Vec<String>) {
if let Some(a1) = &self.a1 {
// TODO: Find a way to represent interrupts in backtraces
bt.push("<interrupted>".to_string());
a1.backtrace(bt);
} else {
self.a0.backtrace(bt);
}
}
fn reset(&mut self) {
self.a0.reset();
self.f = self.f2.clone();
self.a1 = None;
}
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R0> {
if let Some(new_a1) = (self.f)(ctx) {
self.a1 = Some(new_a1);
}
if let Some(a1) = &mut self.a1 {
match a1.tick(ctx) {
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
ControlFlow::Break(_) => self.a1 = None,
}
}
self.a0.tick(ctx)
}
}
// Repeat // Repeat
/// See [`Action::repeat`]. /// See [`Action::repeat`].

View File

@ -560,6 +560,18 @@ fn find_forest(ctx: &mut NpcCtx) -> Option<Vec2<f32>> {
.map(|chunk| TerrainChunkSize::center_wpos(chunk).as_()) .map(|chunk| TerrainChunkSize::center_wpos(chunk).as_())
} }
fn choose_plaza(ctx: &mut NpcCtx, site: SiteId) -> Option<Vec2<f32>> {
ctx.state
.data()
.sites
.get(site)
.and_then(|site| ctx.index.sites.get(site.world_site?).site2())
.and_then(|site2| {
let plaza = &site2.plots[site2.plazas().choose(&mut ctx.rng)?];
Some(site2.tile_center_wpos(plaza.root_tile()).as_())
})
}
fn villager(visiting_site: SiteId) -> impl Action { fn villager(visiting_site: SiteId) -> impl Action {
choose(move |ctx| { choose(move |ctx| {
/* /*
@ -645,6 +657,29 @@ fn villager(visiting_site: SiteId) -> impl Action {
.map(|_| ()), .map(|_| ()),
); );
} }
} else if matches!(ctx.npc.profession, Some(Profession::Guard)) && ctx.rng.gen_bool(0.5) {
if let Some(plaza_wpos) = choose_plaza(ctx, visiting_site) {
return important(
travel_to_point(plaza_wpos, 0.45)
.debug(|| "patrol")
.interrupt_with(|ctx| {
if ctx.rng.gen_bool(0.0003) {
let phrase = *[
"My brother's out fighting ogres. What do I get? Guard duty...",
"Just one more patrol, then I can head home",
"No bandits are going to get past me",
]
.iter()
.choose(&mut ctx.rng)
.unwrap(); // Can't fail
Some(just(move |ctx| ctx.controller.say(None, phrase)))
} else {
None
}
})
.map(|_| ()),
);
}
} else if matches!(ctx.npc.profession, Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8) } else if matches!(ctx.npc.profession, Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8)
{ {
return casual( return casual(
@ -688,17 +723,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
// If nothing else needs doing, walk between plazas and socialize // If nothing else needs doing, walk between plazas and socialize
casual(now(move |ctx| { casual(now(move |ctx| {
// Choose a plaza in the site we're visiting to walk to // Choose a plaza in the site we're visiting to walk to
if let Some(plaza_wpos) = ctx if let Some(plaza_wpos) = choose_plaza(ctx, visiting_site) {
.state
.data()
.sites
.get(visiting_site)
.and_then(|site| ctx.index.sites.get(site.world_site?).site2())
.and_then(|site2| {
let plaza = &site2.plots[site2.plazas().choose(&mut ctx.rng)?];
Some(site2.tile_center_wpos(plaza.root_tile()).as_())
})
{
// Walk to the plaza... // Walk to the plaza...
Either::Left(travel_to_point(plaza_wpos, 0.5) Either::Left(travel_to_point(plaza_wpos, 0.5)
.debug(|| "walk to plaza")) .debug(|| "walk to plaza"))

View File

@ -464,6 +464,8 @@ fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
false, false,
owner_pos, owner_pos,
)); ));
// Always become aware of our owner no matter what
bdata.agent.awareness.set_maximally_aware();
} }
} }
} }