Overhauled rtsim2 pathfinding with TravelTo

This commit is contained in:
Joshua Barretto 2022-09-05 15:03:21 +01:00
parent 1b439d0897
commit 7e9474ab70
7 changed files with 366 additions and 287 deletions

View File

@ -10,9 +10,9 @@ use rand::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap; use slotmap::HopSlotMap;
use std::{ use std::{
collections::VecDeque,
ops::{Deref, DerefMut, ControlFlow},
any::Any, any::Any,
collections::VecDeque,
ops::{ControlFlow, Deref, DerefMut},
}; };
use vek::*; use vek::*;
use world::{civ::Track, site::Site as WorldSite, util::RandomPerm}; use world::{civ::Track, site::Site as WorldSite, util::RandomPerm};
@ -57,31 +57,37 @@ pub trait Task: PartialEq + Clone + Send + Sync + 'static {
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State; fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State;
fn run<'a>(&self, state: &mut Self::State, ctx: &Self::Ctx<'a>, controller: &mut Controller) -> ControlFlow<()>; fn run<'a>(
&self,
state: &mut Self::State,
ctx: &Self::Ctx<'a>,
controller: &mut Controller,
) -> ControlFlow<()>;
fn then<B: Task>(self, other: B) -> Then<Self, B> { fn then<B: Task>(self, other: B) -> Then<Self, B> { Then(self, other) }
Then(self, other)
}
fn repeat(self) -> Repeat<Self> { fn repeat(self) -> Repeat<Self> { Repeat(self) }
Repeat(self)
}
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Then<A, B>(A, B); pub struct Then<A, B>(A, B);
impl<A: Task, B> Task for Then<A, B> impl<A: Task, B> Task for Then<A, B>
where B: for<'a> Task<Ctx<'a> = A::Ctx<'a>> where
B: for<'a> Task<Ctx<'a> = A::Ctx<'a>>,
{ {
type State = Result<A::State, B::State>; // TODO: Use `Either` instead // TODO: Use `Either` instead
type Ctx<'a> = A::Ctx<'a>; type Ctx<'a> = A::Ctx<'a>;
type State = Result<A::State, B::State>;
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { Ok(self.0.begin(ctx)) }
Ok(self.0.begin(ctx))
}
fn run<'a>(&self, state: &mut Self::State, ctx: &Self::Ctx<'a>, controller: &mut Controller) -> ControlFlow<()> { fn run<'a>(
&self,
state: &mut Self::State,
ctx: &Self::Ctx<'a>,
controller: &mut Controller,
) -> ControlFlow<()> {
match state { match state {
Ok(a_state) => { Ok(a_state) => {
self.0.run(a_state, ctx, controller)?; self.0.run(a_state, ctx, controller)?;
@ -97,14 +103,17 @@ impl<A: Task, B> Task for Then<A, B>
pub struct Repeat<A>(A); pub struct Repeat<A>(A);
impl<A: Task> Task for Repeat<A> { impl<A: Task> Task for Repeat<A> {
type State = A::State;
type Ctx<'a> = A::Ctx<'a>; type Ctx<'a> = A::Ctx<'a>;
type State = A::State;
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { self.0.begin(ctx) }
self.0.begin(ctx)
}
fn run<'a>(&self, state: &mut Self::State, ctx: &Self::Ctx<'a>, controller: &mut Controller) -> ControlFlow<()> { fn run<'a>(
&self,
state: &mut Self::State,
ctx: &Self::Ctx<'a>,
controller: &mut Controller,
) -> ControlFlow<()> {
self.0.run(state, ctx, controller)?; self.0.run(state, ctx, controller)?;
*state = self.0.begin(ctx); *state = self.0.begin(ctx);
CONTINUE CONTINUE
@ -120,13 +129,12 @@ impl TaskState {
) -> ControlFlow<()> { ) -> ControlFlow<()> {
type StateOf<T> = (T, <T as Task>::State); type StateOf<T> = (T, <T as Task>::State);
let mut state = if let Some(state) = self.state let mut state = if let Some(state) = self.state.take().and_then(|state| {
.take() state
.and_then(|state| state
.downcast::<StateOf<T>>() .downcast::<StateOf<T>>()
.ok() .ok()
.filter(|state| state.0 == task)) .filter(|state| state.0 == task)
{ }) {
state state
} else { } else {
let mut state = task.begin(ctx); let mut state = task.begin(ctx);
@ -157,7 +165,6 @@ pub struct Npc {
pub faction: Option<FactionId>, pub faction: Option<FactionId>,
// Unpersisted state // Unpersisted state
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub current_site: Option<SiteId>, pub current_site: Option<SiteId>,

View File

@ -77,13 +77,13 @@ impl Data {
Npc::new(rng.gen(), rand_wpos(&mut rng)) Npc::new(rng.gen(), rand_wpos(&mut rng))
.with_faction(site.faction) .with_faction(site.faction)
.with_home(site_id) .with_home(site_id)
.with_profession(match rng.gen_range(0..17) { .with_profession(match rng.gen_range(0..20) {
// 0 => Profession::Hunter, 0 => Profession::Hunter,
// 1 => Profession::Blacksmith, 1 => Profession::Blacksmith,
// 2 => Profession::Chef, 2 => Profession::Chef,
// 3 => Profession::Alchemist, 3 => Profession::Alchemist,
// 5..=10 => Profession::Farmer, 5..=10 => Profession::Farmer,
// 11..=15 => Profession::Guard, 11..=15 => Profession::Guard,
_ => Profession::Adventurer(rng.gen_range(0..=3)), _ => Profession::Adventurer(rng.gen_range(0..=3)),
}), }),
); );

View File

@ -1,24 +1,35 @@
use crate::data::{FactionId, Site}; use crate::data::{FactionId, Site};
use common::store::Id; use common::store::Id;
use vek::*; use vek::*;
use world::{site::Site as WorldSite, IndexRef, World}; use world::{
site::{Site as WorldSite, SiteKind},
IndexRef, World,
};
impl Site { impl Site {
pub fn generate( pub fn generate(
world_site: Id<WorldSite>, world_site_id: Id<WorldSite>,
world: &World, world: &World,
index: IndexRef, index: IndexRef,
nearby_factions: &[(Vec2<i32>, FactionId)], nearby_factions: &[(Vec2<i32>, FactionId)],
) -> Self { ) -> Self {
let wpos = index.sites.get(world_site).get_origin(); let world_site = index.sites.get(world_site_id);
let wpos = world_site.get_origin();
Self { Self {
wpos, wpos,
world_site: Some(world_site), world_site: Some(world_site_id),
faction: nearby_factions faction: if matches!(
.iter() &world_site.kind,
.min_by_key(|(faction_wpos, _)| faction_wpos.distance_squared(wpos)) SiteKind::Refactor(_) | SiteKind::CliffTown(_) | SiteKind::DesertCity(_)
.map(|(_, faction)| *faction), ) {
nearby_factions
.iter()
.min_by_key(|(faction_wpos, _)| faction_wpos.distance_squared(wpos))
.map(|(_, faction)| *faction)
} else {
None
},
} }
} }
} }

View File

@ -1,4 +1,9 @@
#![feature(explicit_generic_args_with_impl_trait, generic_associated_types, never_type, try_blocks)] #![feature(
explicit_generic_args_with_impl_trait,
generic_associated_types,
never_type,
try_blocks
)]
pub mod data; pub mod data;
pub mod event; pub mod event;

View File

@ -1,9 +1,12 @@
use std::{collections::VecDeque, hash::BuildHasherDefault}; use std::{collections::VecDeque, hash::BuildHasherDefault};
use crate::{ use crate::{
data::{npc::{PathData, PathingMemory, Npc, Task, TaskState, Controller, CONTINUE, FINISH}, Sites}, data::{
npc::{Controller, Npc, NpcId, PathData, PathingMemory, Task, TaskState, CONTINUE, FINISH},
Sites,
},
event::OnTick, event::OnTick,
RtState, Rule, RuleError, EventCtx, EventCtx, RtState, Rule, RuleError,
}; };
use common::{ use common::{
astar::{Astar, PathResult}, astar::{Astar, PathResult},
@ -16,6 +19,11 @@ use common::{
use fxhash::FxHasher64; use fxhash::FxHasher64;
use itertools::Itertools; use itertools::Itertools;
use rand::prelude::*; use rand::prelude::*;
use std::{
any::{Any, TypeId},
marker::PhantomData,
ops::ControlFlow,
};
use vek::*; use vek::*;
use world::{ use world::{
civ::{self, Track}, civ::{self, Track},
@ -23,24 +31,9 @@ use world::{
site2::{self, TileKind}, site2::{self, TileKind},
IndexRef, World, IndexRef, World,
}; };
use std::{
ops::ControlFlow,
marker::PhantomData,
any::{Any, TypeId},
};
pub struct NpcAi; pub struct NpcAi;
const NEIGHBOURS: &[Vec2<i32>] = &[
Vec2::new(1, 0),
Vec2::new(0, 1),
Vec2::new(-1, 0),
Vec2::new(0, -1),
Vec2::new(1, 1),
Vec2::new(-1, 1),
Vec2::new(-1, -1),
Vec2::new(1, -1),
];
const CARDINALS: &[Vec2<i32>] = &[ const CARDINALS: &[Vec2<i32>] = &[
Vec2::new(1, 0), Vec2::new(1, 0),
Vec2::new(0, 1), Vec2::new(0, 1),
@ -51,7 +44,7 @@ const CARDINALS: &[Vec2<i32>] = &[
fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> { fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> {
let heuristic = |tile: &Vec2<i32>| tile.as_::<f32>().distance(end.as_()); let heuristic = |tile: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
let mut astar = Astar::new( let mut astar = Astar::new(
250, 1000,
start, start,
&heuristic, &heuristic,
BuildHasherDefault::<FxHasher64>::default(), BuildHasherDefault::<FxHasher64>::default(),
@ -63,9 +56,9 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
let b_tile = site.tiles.get(*b); let b_tile = site.tiles.get(*b);
let terrain = match &b_tile.kind { let terrain = match &b_tile.kind {
TileKind::Empty => 5.0, TileKind::Empty => 3.0,
TileKind::Hazard(_) => 20.0, TileKind::Hazard(_) => 50.0,
TileKind::Field => 12.0, TileKind::Field => 8.0,
TileKind::Plaza | TileKind::Road { .. } => 1.0, TileKind::Plaza | TileKind::Road { .. } => 1.0,
TileKind::Building TileKind::Building
@ -74,7 +67,7 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
| TileKind::Tower(_) | TileKind::Tower(_)
| TileKind::Keep(_) | TileKind::Keep(_)
| TileKind::Gate | TileKind::Gate
| TileKind::GnarlingFortification => 20.0, | TileKind::GnarlingFortification => 5.0,
}; };
let is_door_tile = |plot: Id<site2::Plot>, tile: Vec2<i32>| match site.plot(plot).kind() { let is_door_tile = |plot: Id<site2::Plot>, tile: Vec2<i32>| match site.plot(plot).kind() {
site2::PlotKind::House(house) => house.door_tile == tile, site2::PlotKind::House(house) => house.door_tile == tile,
@ -85,27 +78,27 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
a_tile a_tile
.plot .plot
.and_then(|plot| is_door_tile(plot, *a).then(|| 1.0)) .and_then(|plot| is_door_tile(plot, *a).then(|| 1.0))
.unwrap_or(f32::INFINITY) .unwrap_or(10000.0)
} else if b_tile.is_building() && a_tile.is_road() { } else if b_tile.is_building() && a_tile.is_road() {
b_tile b_tile
.plot .plot
.and_then(|plot| is_door_tile(plot, *b).then(|| 1.0)) .and_then(|plot| is_door_tile(plot, *b).then(|| 1.0))
.unwrap_or(f32::INFINITY) .unwrap_or(10000.0)
} else if (a_tile.is_building() || b_tile.is_building()) && a_tile.plot != b_tile.plot { } else if (a_tile.is_building() || b_tile.is_building()) && a_tile.plot != b_tile.plot {
f32::INFINITY 10000.0
} else { } else {
1.0 1.0
}; };
distance * terrain * building distance * terrain + building
}; };
astar.poll( astar.poll(
250, 1000,
heuristic, heuristic,
|&tile| NEIGHBOURS.iter().map(move |c| tile + *c), |&tile| CARDINALS.iter().map(move |c| tile + *c),
transition, transition,
|tile| *tile == end, |tile| *tile == end || site.tiles.get_known(*tile).is_none(),
) )
} }
@ -183,20 +176,20 @@ fn path_town(
} }
// We pop the first element of the path // We pop the first element of the path
fn pop_first<T>(mut queue: VecDeque<T>) -> VecDeque<T> { // fn pop_first<T>(mut queue: VecDeque<T>) -> VecDeque<T> {
queue.pop_front(); // queue.pop_front();
queue // queue
} // }
match path_in_site(start, end, site) { match path_in_site(start, end, site) {
PathResult::Path(p) => Some(PathData { PathResult::Path(p) => Some(PathData {
end, end,
path: pop_first(p.nodes.into()), path: p.nodes.into(), //pop_first(p.nodes.into()),
repoll: false, repoll: false,
}), }),
PathResult::Exhausted(p) => Some(PathData { PathResult::Exhausted(p) => Some(PathData {
end, end,
path: pop_first(p.nodes.into()), path: p.nodes.into(), //pop_first(p.nodes.into()),
repoll: true, repoll: true,
}), }),
PathResult::None(_) | PathResult::Pending => None, PathResult::None(_) | PathResult::Pending => None,
@ -244,7 +237,10 @@ impl Rule for NpcAi {
let npc_ids = ctx.state.data().npcs.keys().collect::<Vec<_>>(); let npc_ids = ctx.state.data().npcs.keys().collect::<Vec<_>>();
for npc_id in npc_ids { for npc_id in npc_ids {
let mut task_state = ctx.state.data_mut().npcs[npc_id].task_state.take().unwrap_or_default(); let mut task_state = ctx.state.data_mut().npcs[npc_id]
.task_state
.take()
.unwrap_or_default();
let (controller, task_state) = { let (controller, task_state) = {
let data = &*ctx.state.data(); let data = &*ctx.state.data();
@ -256,40 +252,81 @@ impl Rule for NpcAi {
if matches!(npc.profession, Some(Profession::Adventurer(_))) { if matches!(npc.profession, Some(Profession::Adventurer(_))) {
if let Some(home) = npc.home { if let Some(home) = npc.home {
// Travel between random nearby sites // Travel between random nearby sites
let task = generate(move |(npc, ctx): &(&Npc, &EventCtx<_, _>)| { let task = generate(
let tgt_site = ctx.state.data().sites move |(_, npc, ctx): &(NpcId, &Npc, &EventCtx<_, _>)| {
.iter() // Choose a random site that's fairly close by
.filter(|(site_id, site)| npc let tgt_site = ctx
.current_site .state
.map_or(true, |cs| *site_id != cs) && thread_rng().gen_bool(0.25)) .data()
.min_by_key(|(_, site)| site.wpos.as_().distance(npc.wpos.xy()) as i32) .sites
.map(|(site_id, _)| site_id) .iter()
.unwrap_or(home); .filter(|(site_id, site)| {
site.faction.is_some()
&& npc
.current_site
.map_or(true, |cs| *site_id != cs)
&& thread_rng().gen_bool(0.25)
})
.min_by_key(|(_, site)| {
site.wpos.as_().distance(npc.wpos.xy()) as i32
})
.map(|(site_id, _)| site_id)
.unwrap_or(home);
TravelToSite(tgt_site) let wpos = ctx
}) .state
.repeat(); .data()
.sites
.get(tgt_site)
.map_or(npc.wpos.xy(), |site| site.wpos.as_());
task_state.perform(task, &(&*npc, &ctx), &mut controller)?; TravelTo {
wpos,
use_paths: true,
}
},
)
.repeat();
task_state.perform(
task,
&(npc_id, &*npc, &ctx),
&mut controller,
)?;
} }
} else { } else {
controller.goto = None;
// // Choose a random plaza in the npcs home site (which should be the // // Choose a random plaza in the npcs home site (which should be the
// // current here) to go to. // // current here) to go to.
// if let Some(home_id) = let task =
// data.sites.get(home_id).and_then(|site| site.world_site) generate(move |(_, npc, ctx): &(NpcId, &Npc, &EventCtx<_, _>)| {
// { let data = ctx.state.data();
// npc.pathing.intrasite_path = let site2 =
// path_town(npc.wpos, home_id, ctx.index, |site| { npc.home.and_then(|home| data.sites.get(home)).and_then(
// Some( |home| match &ctx.index.sites.get(home.world_site?).kind
// site.plots {
// [site.plazas().choose(&mut dynamic_rng)?] SiteKind::Refactor(site2)
// .root_tile(), | SiteKind::CliffTown(site2)
// ) | SiteKind::DesertCity(site2) => Some(site2),
// }) _ => None,
// .map(|path| (path, home_id)); },
// } );
let wpos = site2
.and_then(|site2| {
let plaza = &site2.plots
[site2.plazas().choose(&mut thread_rng())?];
Some(site2.tile_center_wpos(plaza.root_tile()).as_())
})
.unwrap_or(npc.wpos.xy());
TravelTo {
wpos,
use_paths: true,
}
})
.repeat();
task_state.perform(task, &(npc_id, &*npc, &ctx), &mut controller)?;
} }
}; };
@ -315,10 +352,11 @@ impl<F, T> PartialEq for Generate<F, T> {
pub fn generate<F, T>(f: F) -> Generate<F, T> { Generate(f, PhantomData) } pub fn generate<F, T>(f: F) -> Generate<F, T> { Generate(f, PhantomData) }
impl<F, T: Task> Task for Generate<F, T> impl<F, T: Task> Task for Generate<F, T>
where F: Clone + Send + Sync + 'static + for<'a> Fn(&T::Ctx<'a>) -> T where
F: Clone + Send + Sync + 'static + for<'a> Fn(&T::Ctx<'a>) -> T,
{ {
type State = (T::State, T);
type Ctx<'a> = T::Ctx<'a>; type Ctx<'a> = T::Ctx<'a>;
type State = (T::State, T);
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State {
let task = (self.0)(ctx); let task = (self.0)(ctx);
@ -336,30 +374,53 @@ impl<F, T: Task> Task for Generate<F, T>
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
struct Goto(Vec2<f32>, f32); pub struct Goto {
wpos: Vec2<f32>,
speed_factor: f32,
finish_dist: f32,
}
pub fn goto(wpos: Vec2<f32>) -> Goto {
Goto {
wpos,
speed_factor: 1.0,
finish_dist: 1.0,
}
}
impl Task for Goto { impl Task for Goto {
type State = (Vec2<f32>, f32);
type Ctx<'a> = (&'a Npc, &'a EventCtx<'a, NpcAi, OnTick>); type Ctx<'a> = (&'a Npc, &'a EventCtx<'a, NpcAi, OnTick>);
type State = ();
fn begin<'a>(&self, (_npc, _ctx): &Self::Ctx<'a>) -> Self::State { (self.0, self.1) } fn begin<'a>(&self, (_npc, _ctx): &Self::Ctx<'a>) -> Self::State {}
fn run<'a>( fn run<'a>(
&self, &self,
(tgt, speed_factor): &mut Self::State, (): &mut Self::State,
(npc, ctx): &Self::Ctx<'a>, (npc, ctx): &Self::Ctx<'a>,
controller: &mut Controller, controller: &mut Controller,
) -> ControlFlow<()> { ) -> ControlFlow<()> {
if npc.wpos.xy().distance_squared(*tgt) < 2f32.powi(2) { if npc.wpos.xy().distance_squared(self.wpos) < self.finish_dist.powi(2) {
controller.goto = None; controller.goto = None;
FINISH FINISH
} else { } else {
let dist = npc.wpos.xy().distance(*tgt); let dist = npc.wpos.xy().distance(self.wpos);
let step = dist.min(32.0); let step = dist.min(32.0);
let next_tgt = npc.wpos.xy() + (*tgt - npc.wpos.xy()) / dist * step; let next_tgt = npc.wpos.xy() + (self.wpos - npc.wpos.xy()) / dist * step;
if npc.goto.map_or(true, |(tgt, _)| tgt.xy().distance_squared(next_tgt) > (step * 0.5).powi(2)) || npc.wpos.xy().distance_squared(next_tgt) < (step * 0.5).powi(2) { if npc.goto.map_or(true, |(tgt, _)| {
controller.goto = Some((next_tgt.with_z(ctx.world.sim().get_alt_approx(next_tgt.map(|e| e as i32)).unwrap_or(0.0)), *speed_factor)); tgt.xy().distance_squared(next_tgt) > (step * 0.5).powi(2)
}) || npc.wpos.xy().distance_squared(next_tgt) < (step * 0.5).powi(2)
{
controller.goto = Some((
next_tgt.with_z(
ctx.world
.sim()
.get_alt_approx(next_tgt.map(|e| e as i32))
.unwrap_or(0.0),
),
self.speed_factor,
));
} }
CONTINUE CONTINUE
} }
@ -367,181 +428,172 @@ impl Task for Goto {
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
struct TravelToSite(SiteId); pub struct TravelTo {
wpos: Vec2<f32>,
use_paths: bool,
}
impl Task for TravelToSite { pub enum TravelStage {
type State = (PathingMemory, TaskState); Goto(Vec2<f32>),
type Ctx<'a> = (&'a Npc, &'a EventCtx<'a, NpcAi, OnTick>); SiteToSite {
path: PathData<(Id<Track>, bool), SiteId>,
progress: usize,
},
IntraSite {
path: PathData<Vec2<i32>, Vec2<i32>>,
site: Id<WorldSite>,
},
}
fn begin<'a>(&self, (npc, ctx): &Self::Ctx<'a>) -> Self::State { impl Task for TravelTo {
(PathingMemory::default(), TaskState::default()) type Ctx<'a> = (NpcId, &'a Npc, &'a EventCtx<'a, NpcAi, OnTick>);
type State = (VecDeque<TravelStage>, TaskState);
fn begin<'a>(&self, (_npc_id, npc, ctx): &Self::Ctx<'a>) -> Self::State {
if self.use_paths {
let a = npc.wpos.xy();
let b = self.wpos;
let data = ctx.state.data();
let nearest_in_dir = |wpos: Vec2<f32>, end: Vec2<f32>| {
let dist = wpos.distance(end);
data.sites
.iter()
// TODO: faction.is_some() is currently used as a proxy for whether the site likely has paths, don't do this
.filter(|(site_id, site)| site.faction.is_some() && end.distance(site.wpos.as_()) < dist * 1.2)
.min_by_key(|(_, site)| site.wpos.as_().distance(wpos) as i32)
};
if let Some((site_a, site_b)) = nearest_in_dir(a, b).zip(nearest_in_dir(b, a)) {
if site_a.0 != site_b.0 {
if let Some((path, progress)) =
path_towns(site_a.0, site_b.0, &ctx.state.data().sites, ctx.world)
{
return (
[
TravelStage::Goto(site_a.1.wpos.as_()),
TravelStage::SiteToSite { path, progress },
TravelStage::Goto(b),
]
.into_iter()
.collect(),
TaskState::default(),
);
}
}
}
}
(
[TravelStage::Goto(self.wpos)].into_iter().collect(),
TaskState::default(),
)
} }
fn run<'a>( fn run<'a>(
&self, &self,
(pathing, task_state): &mut Self::State, (stages, task_state): &mut Self::State,
(npc, ctx): &Self::Ctx<'a>, (npc_id, npc, ctx): &Self::Ctx<'a>,
controller: &mut Controller, controller: &mut Controller,
) -> ControlFlow<()> { ) -> ControlFlow<()> {
if let Some(current_site) = npc.current_site { let get_site2 = |site| match &ctx.index.sites.get(site).kind {
if pathing.intersite_path.is_none() { SiteKind::Refactor(site2)
pathing.intersite_path = path_towns( | SiteKind::CliffTown(site2)
current_site, | SiteKind::DesertCity(site2) => Some(site2),
self.0, _ => None,
&ctx.state.data().sites,
ctx.world,
);
if pathing.intersite_path.is_none() {
return FINISH;
}
}
}
if let Some((ref mut path, site)) = pathing.intrasite_path {
// If the npc walking in a site and want to reroll (because the path was
// exhausted.) to try to find a complete path.
if path.repoll {
pathing.intrasite_path =
path_town(npc.wpos, site, ctx.index, |_| Some(path.end))
.map(|path| (path, site));
}
}
if let Some((ref mut path, site)) = pathing.intrasite_path {
if let Some(next_tile) = path.path.front() {
match &ctx.index.sites.get(site).kind {
SiteKind::Refactor(site)
| SiteKind::CliffTown(site)
| SiteKind::DesertCity(site) => {
// Set the target to the next node in the path.
let wpos = site.tile_center_wpos(*next_tile);
task_state.perform(Goto(wpos.map(|e| e as f32 + 0.5), 1.0), &(npc, ctx), controller)?;
path.path.pop_front();
return CONTINUE;
},
_ => {},
}
} else {
// If the path is empty, we're done.
pathing.intrasite_path = None;
}
}
if let Some((path, progress)) = {
if let Some((path, progress)) = &mut pathing.intersite_path {
if let Some((track_id, _)) = path.path.front() {
let track = ctx.world.civs().tracks.get(*track_id);
if *progress >= track.path().len() {
if path.repoll {
// Repoll if last path wasn't complete.
pathing.intersite_path = path_towns(
npc.current_site.unwrap(),
path.end,
&ctx.state.data().sites,
ctx.world,
);
} else {
// Otherwise just take the next in the calculated path.
path.path.pop_front();
*progress = 0;
}
}
}
}
&mut pathing.intersite_path
} {
if let Some((track_id, reversed)) = path.path.front() {
let track = ctx.world.civs().tracks.get(*track_id);
let get_progress = |progress: usize| {
if *reversed {
track.path().len().wrapping_sub(progress + 1)
} else {
progress
}
};
let transform_path_pos = |chunk_pos| {
let chunk_wpos = TerrainChunkSize::center_wpos(chunk_pos);
if let Some(pathdata) =
ctx.world.sim().get_nearest_path(chunk_wpos)
{
pathdata.1.map(|e| e as i32)
} else {
chunk_wpos
}
};
// Loop through and skip nodes that are inside a site, and use intra
// site path finding there instead.
let walk_path = if let Some(chunk_pos) =
track.path().nodes.get(get_progress(*progress))
{
if let Some((wpos, site_id, site)) =
ctx.world.sim().get(*chunk_pos).and_then(|chunk| {
let site_id = *chunk.sites.first()?;
let wpos = transform_path_pos(*chunk_pos);
match &ctx.index.sites.get(site_id).kind {
SiteKind::Refactor(site)
| SiteKind::CliffTown(site)
| SiteKind::DesertCity(site) => {
Some((wpos, site_id, site))
},
_ => None,
}
})
{
if pathing.intrasite_path.is_none() {
let end = site.wpos_tile_pos(wpos);
pathing.intrasite_path =
path_town(npc.wpos, site_id, ctx.index, |_| {
Some(end)
})
.map(|path| (path, site_id));
}
if site.wpos_tile(wpos).is_obstacle() {
*progress += 1;
pathing.intrasite_path = None;
false
} else {
true
}
} else {
true
}
} else {
false
};
if walk_path {
// Find the next wpos on the path.
// NOTE: Consider not having this big gap between current
// position and next. For better path finding. Maybe that would
// mean having a float for progress.
let wpos = transform_path_pos(
track.path().nodes[get_progress(*progress)],
);
task_state.perform(Goto(wpos.map(|e| e as f32 + 0.5), 0.8), &(npc, ctx), controller)?;
*progress += 1;
return CONTINUE;
}
} else {
pathing.intersite_path = None;
}
}
let world_site = |site_id: SiteId| {
let id = ctx.state.data().sites.get(site_id).and_then(|site| site.world_site)?;
ctx.world.civs().sites.recreate_id(id.id())
}; };
if let Some(site_wpos) = world_site(self.0) if let Some(stage) = stages.front_mut() {
.map(|home| TerrainChunkSize::center_wpos(ctx.world.civs().sites.get(home).center)) match stage {
{ TravelStage::Goto(wpos) => {
if site_wpos.map(|e| e as f32 + 0.5).distance_squared(npc.wpos.xy()) < 16f32.powi(2) { task_state.perform(goto(*wpos), &(npc, ctx), controller)?;
FINISH stages.pop_front();
} else { },
task_state.perform(Goto(site_wpos.map(|e| e as f32 + 0.5), 0.8), &(npc, ctx), controller) TravelStage::IntraSite { path, site } => {
if npc
.current_site
.and_then(|site| ctx.state.data().sites.get(site)?.world_site)
== Some(*site)
{
if let Some(next_tile) = path.path.front() {
task_state.perform(
Goto {
wpos: get_site2(*site)
.expect(
"intrasite path should only be started on a site2 site",
)
.tile_center_wpos(*next_tile)
.as_()
+ 0.5,
speed_factor: 0.6,
finish_dist: 1.0,
},
&(npc, ctx),
controller,
)?;
path.path.pop_front();
return CONTINUE;
}
}
task_state.perform(goto(self.wpos), &(npc, ctx), controller)?;
stages.pop_front();
},
TravelStage::SiteToSite { path, progress } => {
if let Some((track_id, reversed)) = path.path.front() {
let track = ctx.world.civs().tracks.get(*track_id);
if *progress >= track.path().len() {
// We finished this track section, move to the next one
path.path.pop_front();
*progress = 0;
} else {
let next_node_idx = if *reversed {
track.path().len().saturating_sub(*progress + 1)
} else {
*progress
};
let next_node = track.path().nodes[next_node_idx];
let transform_path_pos = |chunk_pos| {
let chunk_wpos = TerrainChunkSize::center_wpos(chunk_pos);
if let Some(pathdata) = ctx.world.sim().get_nearest_path(chunk_wpos)
{
pathdata.1.map(|e| e as i32)
} else {
chunk_wpos
}
};
task_state.perform(
Goto {
wpos: transform_path_pos(next_node).as_() + 0.5,
speed_factor: 1.0,
finish_dist: 10.0,
},
&(npc, ctx),
controller,
)?;
*progress += 1;
}
} else {
stages.pop_front();
}
},
} }
if !matches!(stages.front(), Some(TravelStage::IntraSite { .. })) {
let data = ctx.state.data();
if let Some((site2, site)) = npc
.current_site
.and_then(|current_site| data.sites.get(current_site))
.and_then(|site| site.world_site)
.and_then(|site| Some((get_site2(site)?, site)))
{
let end = site2.wpos_tile_pos(self.wpos.as_());
if let Some(path) = path_town(npc.wpos, site, ctx.index, |_| Some(end)) {
stages.push_front(TravelStage::IntraSite { path, site });
}
}
}
CONTINUE
} else { } else {
FINISH FINISH
} }

View File

@ -262,11 +262,13 @@ impl<'a> System<'a> for Sys {
agent.rtsim_controller.speed_factor = npc.goto.map_or(1.0, |(_, sf)| sf); agent.rtsim_controller.speed_factor = npc.goto.map_or(1.0, |(_, sf)| sf);
// TODO: // TODO:
// agent.rtsim_controller.heading_to = // agent.rtsim_controller.heading_to =
// npc.pathing.intersite_path.as_ref().and_then(|(path, _)| { // npc.pathing.intersite_path.as_ref().
// and_then(|(path, _)| {
// Some( // Some(
// index // index
// .sites // .sites
// .get(data.sites.get(path.end)?.world_site?) //
// .get(data.sites.get(path.end)?.world_site?)
// .name() // .name()
// .to_string(), // .to_string(),
// ) // )

View File

@ -25,9 +25,7 @@ impl Default for TileGrid {
} }
impl TileGrid { impl TileGrid {
pub fn get(&self, tpos: Vec2<i32>) -> &Tile { pub fn get_known(&self, tpos: Vec2<i32>) -> Option<&Tile> {
static EMPTY: Tile = Tile::empty();
let tpos = tpos + TILE_RADIUS as i32; let tpos = tpos + TILE_RADIUS as i32;
self.zones self.zones
.get(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32))) .get(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
@ -36,7 +34,11 @@ impl TileGrid {
.get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32))) .get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
}) })
.and_then(|tile| tile.as_ref()) .and_then(|tile| tile.as_ref())
.unwrap_or(&EMPTY) }
pub fn get(&self, tpos: Vec2<i32>) -> &Tile {
static EMPTY: Tile = Tile::empty();
self.get_known(tpos).unwrap_or(&EMPTY)
} }
// WILL NOT EXPAND BOUNDS! // WILL NOT EXPAND BOUNDS!