mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Incremental point traversal
This commit is contained in:
parent
bb96e92362
commit
191f362292
@ -194,10 +194,42 @@ impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
||||
|
||||
fn reset(&mut self) { (**self).reset(); }
|
||||
|
||||
// TODO: Reset closure state?
|
||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { (**self).tick(ctx) }
|
||||
}
|
||||
|
||||
impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for itertools::Either<A, B> {
|
||||
fn is_same(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(itertools::Either::Left(x), itertools::Either::Left(y)) => x.is_same(y),
|
||||
(itertools::Either::Right(x), itertools::Either::Right(y)) => x.is_same(y),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||
|
||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||
match self {
|
||||
itertools::Either::Left(x) => x.backtrace(bt),
|
||||
itertools::Either::Right(x) => x.backtrace(bt),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
match self {
|
||||
itertools::Either::Left(x) => x.reset(),
|
||||
itertools::Either::Right(x) => x.reset(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
||||
match self {
|
||||
itertools::Either::Left(x) => x.tick(ctx),
|
||||
itertools::Either::Right(x) => x.tick(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now
|
||||
|
||||
/// See [`now`].
|
||||
@ -220,6 +252,7 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'stati
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Reset closure?
|
||||
fn reset(&mut self) { self.1 = None; }
|
||||
|
||||
// TODO: Reset closure state?
|
||||
@ -247,6 +280,62 @@ where
|
||||
Now(f, None)
|
||||
}
|
||||
|
||||
// Until
|
||||
|
||||
/// See [`now`].
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Until<F, A, R>(F, Option<A>, PhantomData<R>);
|
||||
|
||||
impl<
|
||||
R: Send + Sync + 'static,
|
||||
F: FnMut(&mut NpcCtx) -> Option<A> + Send + Sync + 'static,
|
||||
A: Action<R>,
|
||||
> Action<()> for Until<F, A, R>
|
||||
{
|
||||
// TODO: This doesn't compare?!
|
||||
fn is_same(&self, other: &Self) -> bool { true }
|
||||
|
||||
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
||||
|
||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||
if let Some(action) = &self.1 {
|
||||
action.backtrace(bt);
|
||||
} else {
|
||||
bt.push("<thinking>".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Reset closure?
|
||||
fn reset(&mut self) { self.1 = None; }
|
||||
|
||||
// TODO: Reset closure state?
|
||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> {
|
||||
match &mut self.1 {
|
||||
Some(x) => match x.tick(ctx) {
|
||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
||||
ControlFlow::Break(_) => {
|
||||
self.1 = None;
|
||||
ControlFlow::Continue(())
|
||||
},
|
||||
},
|
||||
None => match (self.0)(ctx) {
|
||||
Some(x) => {
|
||||
self.1 = Some(x);
|
||||
ControlFlow::Continue(())
|
||||
},
|
||||
None => ControlFlow::Break(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn until<F, A, R>(f: F) -> Until<F, A, R>
|
||||
where
|
||||
F: FnMut(&mut NpcCtx) -> Option<A>,
|
||||
{
|
||||
Until(f, None, PhantomData)
|
||||
}
|
||||
|
||||
// Just
|
||||
|
||||
/// See [`just`].
|
||||
@ -262,6 +351,7 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'stati
|
||||
|
||||
fn backtrace(&self, bt: &mut Vec<String>) {}
|
||||
|
||||
// TODO: Reset closure?
|
||||
fn reset(&mut self) {}
|
||||
|
||||
// TODO: Reset closure state?
|
||||
|
@ -73,9 +73,9 @@ impl Data {
|
||||
// Spawn some test entities at the sites
|
||||
for (site_id, site) in this.sites.iter()
|
||||
// TODO: Stupid
|
||||
// .filter(|(_, site)| site.world_site.map_or(false, |ws|
|
||||
// matches!(&index.sites.get(ws).kind, SiteKind::Refactor(_)))) .skip(1)
|
||||
// .take(1)
|
||||
.filter(|(_, site)| site.world_site.map_or(false, |ws|
|
||||
matches!(&index.sites.get(ws).kind, SiteKind::Refactor(_)))) .skip(1)
|
||||
.take(1)
|
||||
{
|
||||
let Some(good_or_evil) = site
|
||||
.faction
|
||||
@ -90,7 +90,7 @@ impl Data {
|
||||
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
||||
};
|
||||
if good_or_evil {
|
||||
for _ in 0..64 {
|
||||
for _ in 0..32 {
|
||||
this.npcs.create(
|
||||
Npc::new(rng.gen(), rand_wpos(&mut rng))
|
||||
.with_faction(site.faction)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::{collections::VecDeque, hash::BuildHasherDefault};
|
||||
|
||||
use crate::{
|
||||
ai::{casual, choose, finish, important, just, now, seq, urgent, watch, Action, NpcCtx},
|
||||
ai::{casual, choose, finish, important, just, now, seq, until, urgent, watch, Action, NpcCtx},
|
||||
data::{
|
||||
npc::{Brain, Controller, Npc, NpcId, PathData, PathingMemory},
|
||||
Sites,
|
||||
@ -162,46 +162,31 @@ fn path_between_sites(
|
||||
})
|
||||
}
|
||||
|
||||
fn path_town(
|
||||
wpos: Vec3<f32>,
|
||||
fn path_site(
|
||||
start: Vec2<f32>,
|
||||
end: Vec2<f32>,
|
||||
site: Id<WorldSite>,
|
||||
index: IndexRef,
|
||||
end: impl FnOnce(&site2::Site) -> Option<Vec2<i32>>,
|
||||
) -> Option<PathData<Vec2<i32>, Vec2<i32>>> {
|
||||
match &index.sites.get(site).kind {
|
||||
SiteKind::Refactor(site) | SiteKind::CliffTown(site) | SiteKind::DesertCity(site) => {
|
||||
let start = site.wpos_tile_pos(wpos.xy().as_());
|
||||
) -> Option<Vec<Vec2<f32>>> {
|
||||
if let Some(site) = index.sites.get(site).site2() {
|
||||
let start = site.wpos_tile_pos(start.as_());
|
||||
|
||||
let end = end(site)?;
|
||||
let end = site.wpos_tile_pos(end.as_());
|
||||
|
||||
if start == end {
|
||||
return None;
|
||||
}
|
||||
let nodes = match path_in_site(start, end, site) {
|
||||
PathResult::Path(p) => p.nodes,
|
||||
PathResult::Exhausted(p) => p.nodes,
|
||||
PathResult::None(_) | PathResult::Pending => return None,
|
||||
};
|
||||
|
||||
// We pop the first element of the path
|
||||
// fn pop_first<T>(mut queue: VecDeque<T>) -> VecDeque<T> {
|
||||
// queue.pop_front();
|
||||
// queue
|
||||
// }
|
||||
|
||||
match path_in_site(start, end, site) {
|
||||
PathResult::Path(p) => Some(PathData {
|
||||
end,
|
||||
path: p.nodes.into(), //pop_first(p.nodes.into()),
|
||||
repoll: false,
|
||||
}),
|
||||
PathResult::Exhausted(p) => Some(PathData {
|
||||
end,
|
||||
path: p.nodes.into(), //pop_first(p.nodes.into()),
|
||||
repoll: true,
|
||||
}),
|
||||
PathResult::None(_) | PathResult::Pending => None,
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// No brain T_T
|
||||
None
|
||||
},
|
||||
Some(
|
||||
nodes
|
||||
.into_iter()
|
||||
.map(|tile| site.tile_center_wpos(tile).as_() + 0.5)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,6 +350,58 @@ fn goto_2d(wpos2d: Vec2<f32>, speed_factor: f32, goal_dist: f32) -> impl Action
|
||||
})
|
||||
}
|
||||
|
||||
fn traverse_points<F>(mut next_point: F) -> impl Action<()>
|
||||
where
|
||||
F: FnMut(&mut NpcCtx) -> Option<Vec2<f32>> + Send + Sync + 'static,
|
||||
{
|
||||
until(move |ctx| {
|
||||
let wpos = next_point(ctx)?;
|
||||
|
||||
let wpos_site = |wpos: Vec2<f32>| {
|
||||
ctx.world
|
||||
.sim()
|
||||
.get(wpos.as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
|
||||
.and_then(|chunk| chunk.sites.first().copied())
|
||||
};
|
||||
|
||||
// If we're traversing within a site, to intra-site pathfinding
|
||||
if let Some(site) = wpos_site(wpos) {
|
||||
let mut site_exit = wpos;
|
||||
while let Some(next) = next_point(ctx).filter(|next| wpos_site(*next) == Some(site)) {
|
||||
site_exit = next;
|
||||
}
|
||||
|
||||
// println!("[NPC {:?}] Pathing in site...", ctx.npc_id);
|
||||
if let Some(path) = path_site(wpos, site_exit, site, ctx.index) {
|
||||
// println!("[NPC {:?}] Found path of length {} from {:?} to {:?}!", ctx.npc_id,
|
||||
// path.len(), wpos, site_exit);
|
||||
Some(itertools::Either::Left(
|
||||
seq(path.into_iter().map(|wpos| goto_2d(wpos, 1.0, 8.0)))
|
||||
.then(goto_2d(site_exit, 1.0, 8.0)),
|
||||
))
|
||||
} else {
|
||||
// println!("[NPC {:?}] No path", ctx.npc_id);
|
||||
Some(itertools::Either::Right(goto_2d(site_exit, 1.0, 8.0)))
|
||||
}
|
||||
} else {
|
||||
Some(itertools::Either::Right(goto_2d(wpos, 1.0, 8.0)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to travel to a site. Where practical, paths will be taken.
|
||||
fn travel_to_point(wpos: Vec2<f32>) -> impl Action {
|
||||
now(move |ctx| {
|
||||
const WAYPOINT: f32 = 24.0;
|
||||
let start = ctx.npc.wpos.xy();
|
||||
let diff = wpos - start;
|
||||
let n = (diff.magnitude() / WAYPOINT).max(1.0);
|
||||
let mut points = (1..n as usize + 1).map(move |i| start + diff * (i as f32 / n));
|
||||
traverse_points(move |_| points.next())
|
||||
})
|
||||
.debug(|| "travel to point")
|
||||
}
|
||||
|
||||
/// Try to travel to a site. Where practical, paths will be taken.
|
||||
fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
||||
now(move |ctx| {
|
||||
@ -376,51 +413,83 @@ fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
||||
&& let Some(tracks) = path_towns(current_site, tgt_site, sites, ctx.world)
|
||||
{
|
||||
let track_count = tracks.path.len();
|
||||
// For every track in the path we discovered between the sites...
|
||||
seq(tracks
|
||||
.path
|
||||
|
||||
let mut nodes = tracks.path
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
// ...traverse the nodes of that path.
|
||||
.map(move |(i, (track_id, reversed))| now(move |ctx| {
|
||||
let track_len = ctx.world.civs().tracks.get(track_id).path().len();
|
||||
// Tracks can be traversed backward (i.e: from end to beginning). Account for this.
|
||||
seq(if reversed {
|
||||
itertools::Either::Left((0..track_len).rev())
|
||||
} else {
|
||||
itertools::Either::Right(0..track_len)
|
||||
}
|
||||
.enumerate()
|
||||
.map(move |(i, node_idx)| now(move |ctx| {
|
||||
// Find the centre of the track node's chunk
|
||||
let node_chunk_wpos = TerrainChunkSize::center_wpos(ctx.world
|
||||
.civs()
|
||||
.tracks
|
||||
.get(track_id)
|
||||
.path()
|
||||
.nodes[node_idx]);
|
||||
.flat_map(move |(track_id, reversed)| (0..)
|
||||
.map(move |node_idx| (node_idx, track_id, reversed)));
|
||||
|
||||
// Refine the node position a bit more based on local path information
|
||||
let node_wpos = ctx.world.sim()
|
||||
.get_nearest_path(node_chunk_wpos)
|
||||
.map_or(node_chunk_wpos, |(_, wpos, _, _)| wpos.as_());
|
||||
traverse_points(move |ctx| {
|
||||
let (node_idx, track_id, reversed) = nodes.next()?;
|
||||
let nodes = &ctx.world.civs().tracks.get(track_id).path().nodes;
|
||||
|
||||
// Walk toward the node
|
||||
goto_2d(node_wpos.as_(), 1.0, 8.0)
|
||||
.debug(move || format!("traversing track node ({}/{})", i + 1, track_len))
|
||||
})))
|
||||
})
|
||||
.debug(move || format!("travel via track {:?} ({}/{})", track_id, i + 1, track_count))))
|
||||
// Handle the case where we walk paths backward
|
||||
let idx = if reversed {
|
||||
nodes.len().checked_sub(node_idx + 1)
|
||||
} else {
|
||||
Some(node_idx)
|
||||
};
|
||||
|
||||
if let Some(node) = idx.and_then(|idx| nodes.get(idx)) {
|
||||
// Find the centre of the track node's chunk
|
||||
let node_chunk_wpos = TerrainChunkSize::center_wpos(*node);
|
||||
|
||||
// Refine the node position a bit more based on local path information
|
||||
Some(ctx.world.sim()
|
||||
.get_nearest_path(node_chunk_wpos)
|
||||
.map_or(node_chunk_wpos, |(_, wpos, _, _)| wpos.as_())
|
||||
.as_::<f32>())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
|
||||
// For every track in the path we discovered between the sites...
|
||||
// seq(tracks
|
||||
// .path
|
||||
// .into_iter()
|
||||
// .enumerate()
|
||||
// // ...traverse the nodes of that path.
|
||||
// .map(move |(i, (track_id, reversed))| now(move |ctx| {
|
||||
// let track_len = ctx.world.civs().tracks.get(track_id).path().len();
|
||||
// // Tracks can be traversed backward (i.e: from end to beginning). Account for this.
|
||||
// seq(if reversed {
|
||||
// itertools::Either::Left((0..track_len).rev())
|
||||
// } else {
|
||||
// itertools::Either::Right(0..track_len)
|
||||
// }
|
||||
// .enumerate()
|
||||
// .map(move |(i, node_idx)| now(move |ctx| {
|
||||
// // Find the centre of the track node's chunk
|
||||
// let node_chunk_wpos = TerrainChunkSize::center_wpos(ctx.world
|
||||
// .civs()
|
||||
// .tracks
|
||||
// .get(track_id)
|
||||
// .path()
|
||||
// .nodes[node_idx]);
|
||||
|
||||
// // Refine the node position a bit more based on local path information
|
||||
// let node_wpos = ctx.world.sim()
|
||||
// .get_nearest_path(node_chunk_wpos)
|
||||
// .map_or(node_chunk_wpos, |(_, wpos, _, _)| wpos.as_());
|
||||
|
||||
// // Walk toward the node
|
||||
// goto_2d(node_wpos.as_(), 1.0, 8.0)
|
||||
// .debug(move || format!("traversing track node ({}/{})", i + 1, track_len))
|
||||
// })))
|
||||
// })
|
||||
// .debug(move || format!("travel via track {:?} ({}/{})", track_id, i + 1, track_count))))
|
||||
// .boxed()
|
||||
} else if let Some(site) = sites.get(tgt_site) {
|
||||
// If all else fails, just walk toward the target site in a straight line
|
||||
goto_2d(site.wpos.map(|e| e as f32 + 0.5), 1.0, 8.0).boxed()
|
||||
travel_to_point(site.wpos.map(|e| e as f32 + 0.5)).boxed()
|
||||
} else {
|
||||
// If we can't find a way to get to the site at all, there's nothing more to be done
|
||||
finish().boxed()
|
||||
}
|
||||
})
|
||||
.debug(move || format!("travel_to_site {:?}", tgt_site))
|
||||
.debug(move || format!("travel_to_site {:?}", tgt_site))
|
||||
}
|
||||
|
||||
// Seconds
|
||||
@ -500,7 +569,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
||||
Some(site2.tile_center_wpos(house.root_tile()).as_())
|
||||
})
|
||||
{
|
||||
goto_2d(house_wpos, 0.5, 1.0)
|
||||
travel_to_point(house_wpos)
|
||||
.debug(|| "walk to house")
|
||||
.then(idle().repeat().debug(|| "wait in house"))
|
||||
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
||||
@ -527,7 +596,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
||||
})
|
||||
{
|
||||
// Walk to the plaza...
|
||||
goto_2d(plaza_wpos, 0.5, 8.0)
|
||||
travel_to_point(plaza_wpos)
|
||||
.debug(|| "walk to plaza")
|
||||
// ...then wait for some time before moving on
|
||||
.then({
|
||||
|
Loading…
Reference in New Issue
Block a user