Added sequence combinator, NPC site-site pathfinding

This commit is contained in:
Joshua Barretto 2023-01-05 01:50:51 +00:00
parent b2f92e4a6c
commit 9413a56c13
3 changed files with 158 additions and 33 deletions

View File

@ -109,6 +109,28 @@ pub trait Action<R = ()>: Any + Send + Sync {
{
Map(self, f, PhantomData)
}
fn boxed(self) -> Box<dyn Action<R>>
where
Self: Sized,
{
Box::new(self)
}
}
impl<R: 'static> Action<R> for Box<dyn Action<R>> {
fn is_same(&self, other: &Self) -> bool { (**self).dyn_is_same(other) }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool {
match (other as &dyn Any).downcast_ref::<Self>() {
Some(other) => self.is_same(other),
None => false,
}
}
fn reset(&mut self) { (**self).reset(); }
// TODO: Reset closure state?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { (**self).tick(ctx) }
}
// Now
@ -164,6 +186,23 @@ where
Just(f, PhantomData)
}
// Finish
#[derive(Copy, Clone)]
pub struct Finish;
impl Action<()> for Finish {
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) {}
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> { ControlFlow::Break(()) }
}
pub fn finish() -> Finish { Finish }
// Tree
pub type Priority = usize;
@ -293,6 +332,49 @@ impl<R: Send + Sync + 'static, A: Action<R>> Action<!> for Repeat<A, R> {
}
}
// Sequence
#[derive(Copy, Clone)]
pub struct Sequence<I, A, R = ()>(I, I, Option<A>, PhantomData<R>);
impl<R: Send + Sync + 'static, I: Iterator<Item = A> + Clone + Send + Sync + 'static, A: Action<R>>
Action<()> for Sequence<I, A, R>
{
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) {
self.0 = self.1.clone();
self.2 = None;
}
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> {
let item = if let Some(prev) = &mut self.2 {
prev
} else {
match self.0.next() {
Some(next) => self.2.insert(next),
None => return ControlFlow::Break(()),
}
};
if let ControlFlow::Break(_) = item.tick(ctx) {
self.2 = None;
}
ControlFlow::Continue(())
}
}
pub fn seq<I, A, R>(iter: I) -> Sequence<I, A, R>
where
I: Iterator<Item = A> + Clone,
A: Action<R>,
{
Sequence(iter.clone(), iter, None, PhantomData)
}
// StopIf
#[derive(Copy, Clone)]

View File

@ -6,7 +6,8 @@
generators,
trait_alias,
trait_upcasting,
control_flow_enum
control_flow_enum,
let_chains
)]
pub mod data;

View File

@ -3,8 +3,8 @@ use std::{collections::VecDeque, hash::BuildHasherDefault};
use crate::{
data::{
npc::{
casual, choose, just, now, urgent, Action, Brain, Controller, Npc, NpcId, PathData,
PathingMemory,
casual, choose, finish, just, now, seq, urgent, watch, Action, Brain, Controller, Npc,
NpcId, PathData, PathingMemory,
},
Sites,
},
@ -371,10 +371,16 @@ pub struct NpcCtx<'a> {
controller: &'a mut Controller,
}
fn idle() -> impl Action + Clone { just(|ctx| *ctx.controller = Controller::idle()) }
fn pass() -> impl Action { just(|ctx| {}) }
fn move_toward(wpos: Vec3<f32>, speed_factor: f32) -> impl Action + Clone {
const STEP_DIST: f32 = 10.0;
fn idle_wait() -> impl Action {
just(|ctx| *ctx.controller = Controller::idle())
.repeat()
.map(|_| ())
}
fn move_toward(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
const STEP_DIST: f32 = 16.0;
just(move |ctx| {
let rpos = wpos - ctx.npc.wpos;
let len = rpos.magnitude();
@ -385,8 +391,8 @@ fn move_toward(wpos: Vec3<f32>, speed_factor: f32) -> impl Action + Clone {
})
}
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action + Clone {
const MIN_DIST: f32 = 1.0;
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
const MIN_DIST: f32 = 2.0;
move_toward(wpos, speed_factor)
.repeat()
@ -394,6 +400,61 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action + Clone {
.map(|_| {})
}
fn goto_2d(wpos2d: Vec2<f32>, speed_factor: f32) -> impl Action {
const MIN_DIST: f32 = 2.0;
now(move |ctx| {
let wpos = wpos2d.with_z(
ctx.ctx
.world
.sim()
.get_alt_approx(wpos2d.as_())
.unwrap_or(0.0),
);
goto(wpos, speed_factor)
})
}
fn path_to_site(tgt_site: SiteId) -> impl Action {
now(move |ctx| {
let sites = &ctx.ctx.state.data().sites;
// If we can, try to find a path to the site via tracks
if let Some(current_site) = ctx.npc.current_site
&& let Some((mut tracks, _)) = path_towns(current_site, tgt_site, sites, ctx.ctx.world)
{
seq(tracks
.path
.into_iter()
.map(|(track_id, reversed)| now(move |ctx| {
let track_len = ctx.ctx.world.civs().tracks.get(track_id).path().len();
seq(if reversed {
itertools::Either::Left((0..track_len).rev())
} else {
itertools::Either::Right(0..track_len)
}
.map(move |node_idx| now(move |ctx| {
let track = ctx.ctx.world.civs().tracks.get(track_id);
let next_node = track.path().nodes[node_idx];
let chunk_wpos = TerrainChunkSize::center_wpos(next_node);
let path_wpos2d = ctx.ctx.world.sim()
.get_nearest_path(chunk_wpos)
.map_or(chunk_wpos, |(_, wpos, _, _)| wpos.as_());
goto_2d(path_wpos2d.as_(), 1.0)
})))
})))
.boxed()
} else if let Some(site) = sites.get(tgt_site) {
// If all else fails, just walk toward the site in a straight line
goto_2d(site.wpos.map(|e| e as f32 + 0.5), 1.0).boxed()
} else {
pass().boxed()
}
})
}
// Seconds
fn timeout(ctx: &NpcCtx, time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
let end = ctx.ctx.event.time.0 + time;
@ -404,7 +465,7 @@ fn think() -> impl Action {
choose(|ctx| {
if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) {
// Choose a random site that's fairly close by
let site_wpos2d = ctx
if let Some(tgt_site) = ctx
.ctx
.state
.data()
@ -417,39 +478,20 @@ fn think() -> impl Action {
})
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
.map(|(site_id, _)| site_id)
.and_then(|tgt_site| {
ctx.ctx
.state
.data()
.sites
.get(tgt_site)
.map(|site| site.wpos)
});
if let Some(site_wpos2d) = site_wpos2d {
// Walk toward the site
casual(goto(
site_wpos2d.map(|e| e as f32 + 0.5).with_z(
ctx.ctx
.world
.sim()
.get_alt_approx(site_wpos2d.as_())
.unwrap_or(0.0),
),
1.0,
))
{
casual(path_to_site(tgt_site))
} else {
casual(idle())
casual(pass())
}
} else if matches!(ctx.npc.profession, Some(Profession::Blacksmith)) {
casual(idle())
casual(idle_wait())
} else {
casual(
now(|ctx| goto(ctx.npc.wpos + Vec3::unit_x() * 10.0, 1.0))
.then(now(|ctx| goto(ctx.npc.wpos - Vec3::unit_x() * 10.0, 1.0)))
.repeat()
.stop_if(timeout(ctx, 10.0))
.then(now(|ctx| idle().repeat().stop_if(timeout(ctx, 5.0))))
.then(now(|ctx| idle_wait().stop_if(timeout(ctx, 5.0))))
.map(|_| {}),
)
}