Improved AI code structure, added docs

This commit is contained in:
Joshua Barretto 2023-01-05 03:08:00 +00:00
parent 9413a56c13
commit 8f7b11f12f
4 changed files with 689 additions and 915 deletions

583
rtsim/src/ai/mod.rs Normal file
View File

@ -0,0 +1,583 @@
use crate::{
data::npc::{Controller, Npc, NpcId},
RtState,
};
use common::resources::{Time, TimeOfDay};
use std::{any::Any, marker::PhantomData, ops::ControlFlow};
use world::{IndexRef, World};
/// The context provided to an [`Action`] while it is being performed. It should
/// be possible to access any and all important information about the game world
/// through this struct.
pub struct NpcCtx<'a> {
pub state: &'a RtState,
pub world: &'a World,
pub index: IndexRef<'a>,
pub time_of_day: TimeOfDay,
pub time: Time,
pub npc_id: NpcId,
pub npc: &'a Npc,
pub controller: &'a mut Controller,
}
/// A trait that describes 'actions': long-running tasks performed by rtsim
/// NPCs. These can be as simple as walking in a straight line between two
/// locations or as complex as taking part in an adventure with players or
/// performing an entire daily work schedule.
///
/// Actions are built up from smaller sub-actions via the combinator methods
/// defined on this trait, and with the standalone functions in this module.
/// Using these combinators, in a similar manner to using the [`Iterator`] API,
/// it is possible to construct arbitrarily complex actions including behaviour
/// trees (see [`choose`] and [`watch`]) and other forms of moment-by-moment
/// decision-making.
///
/// On completion, actions may produce a value, denoted by the type parameter
/// `R`. For example, an action may communicate whether it was successful or
/// unsuccessful through this completion value.
///
/// You should not need to implement this trait yourself when writing AI code.
/// If you find yourself wanting to implement it, please discuss with the core
/// dev team first.
pub trait Action<R = ()>: Any + Send + Sync {
/// Returns `true` if the action should be considered the 'same' (i.e:
/// achieving the same objective) as another. In general, the AI system
/// will try to avoid switching (and therefore restarting) tasks when the
/// new task is the 'same' as the old one.
// TODO: Figure out a way to compare actions based on their 'intention': i.e:
// two pathing actions should be considered equivalent if their destination
// is the same regardless of the progress they've each made.
fn is_same(&self, other: &Self) -> bool
where
Self: Sized;
/// Like [`Action::is_same`], but allows for dynamic dispatch.
fn dyn_is_same_sized(&self, other: &dyn Action<R>) -> bool
where
Self: Sized,
{
match (other as &dyn Any).downcast_ref::<Self>() {
Some(other) => self.is_same(other),
None => false,
}
}
/// Like [`Action::is_same`], but allows for dynamic dispatch.
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool;
/// Reset the action to its initial state such that it can be repeated.
fn reset(&mut self);
/// Perform the action for the current tick.
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R>;
/// Create an action that chains together two sub-actions, one after the
/// other.
///
/// # Example
///
/// ```ignore
/// // Walk toward an enemy NPC and, once done, attack the enemy NPC
/// goto(enemy_npc).then(attack(enemy_npc))
/// ```
fn then<A1: Action<R1>, R1>(self, other: A1) -> Then<Self, A1, R>
where
Self: Sized,
{
Then {
a0: self,
a0_finished: false,
a1: other,
phantom: PhantomData,
}
}
/// Create an action that repeats a sub-action indefinitely.
///
/// # Example
///
/// ```ignore
/// // Endlessly collect flax from the environment
/// find_and_collect(ChunkResource::Flax).repeat()
/// ```
fn repeat<R1>(self) -> Repeat<Self, R1>
where
Self: Sized,
{
Repeat(self, PhantomData)
}
/// Stop the sub-action suddenly if a condition is reached.
///
/// # Example
///
/// ```ignore
/// // Keep going on adventures until your 111th birthday
/// go_on_an_adventure().repeat().stop_if(|ctx| ctx.npc.age > 111.0)
/// ```
fn stop_if<F: FnMut(&mut NpcCtx) -> bool>(self, f: F) -> StopIf<Self, F>
where
Self: Sized,
{
StopIf(self, f)
}
/// Map the completion value of this action to something else.
fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R>
where
Self: Sized,
{
Map(self, f, PhantomData)
}
/// Box the action. Often used to perform type erasure, such as when you
/// want to return one of many actions (each with different types) from
/// the same function.
///
/// # Example
///
/// ```ignore
/// // Error! Type mismatch between branches
/// if npc.is_too_tired() {
/// goto(npc.home)
/// } else {
/// go_on_an_adventure()
/// }
///
/// // All fine
/// if npc.is_too_tired() {
/// goto(npc.home).boxed()
/// } else {
/// go_on_an_adventure().boxed()
/// }
/// ```
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
/// See [`now`].
#[derive(Copy, Clone)]
pub struct Now<F, A>(F, Option<A>);
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'static, A: Action<R>>
Action<R> for Now<F, A>
{
// TODO: This doesn't compare?!
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.1 = None; }
// TODO: Reset closure state?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
(self.1.get_or_insert_with(|| (self.0)(ctx))).tick(ctx)
}
}
/// Start a new action based on the state of the world (`ctx`) at the moment the
/// action is started.
///
/// If you're in a situation where you suddenly find yourself needing `ctx`, you
/// probably want to use this.
///
/// # Example
///
/// ```ignore
/// // An action that makes an NPC immediately travel to its *current* home
/// now(|ctx| goto(ctx.npc.home))
/// ```
pub fn now<F, A>(f: F) -> Now<F, A>
where
F: FnMut(&mut NpcCtx) -> A,
{
Now(f, None)
}
// Just
/// See [`just`].
#[derive(Copy, Clone)]
pub struct Just<F, R = ()>(F, PhantomData<R>);
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static> Action<R>
for Just<F, R>
{
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) {}
// TODO: Reset closure state?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { ControlFlow::Break((self.0)(ctx)) }
}
/// An action that executes some code just once when performed.
///
/// If you want to execute this code on every tick, consider combining it with
/// [`Action::repeat`].
///
/// # Example
///
/// ```ignore
/// // Make the current NPC say 'Hello, world!' exactly once
/// just(|ctx| ctx.controller.say("Hello, world!"))
/// ```
pub fn just<F, R: Send + Sync + 'static>(mut f: F) -> Just<F, R>
where
F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static,
{
Just(f, PhantomData)
}
// Finish
/// See [`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(()) }
}
/// An action that immediately finishes without doing anything.
///
/// This action is useless by itself, but becomes useful when combined with
/// actions that make decisions.
///
/// # Example
///
/// ```ignore
/// now(|ctx| {
/// if ctx.npc.is_tired() {
/// sleep().boxed() // If we're tired, sleep
/// } else if ctx.npc.is_hungry() {
/// eat().boxed() // If we're hungry, eat
/// } else {
/// finish().boxed() // Otherwise, do nothing
/// }
/// })
/// ```
pub fn finish() -> Finish { Finish }
// Tree
pub type Priority = usize;
pub const URGENT: Priority = 0;
pub const IMPORTANT: Priority = 1;
pub const CASUAL: Priority = 2;
pub struct Node<R>(Box<dyn Action<R>>, Priority);
/// Perform an action with [`URGENT`] priority (see [`choose`]).
pub fn urgent<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), URGENT) }
/// Perform an action with [`IMPORTANT`] priority (see [`choose`]).
pub fn important<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), IMPORTANT) }
/// Perform an action with [`CASUAL`] priority (see [`choose`]).
pub fn casual<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), CASUAL) }
/// See [`choose`] and [`watch`].
pub struct Tree<F, R> {
next: F,
prev: Option<Node<R>>,
interrupt: bool,
}
impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Action<R>
for Tree<F, R>
{
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.prev = None; }
// TODO: Reset `next` too?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
let new = (self.next)(ctx);
let prev = match &mut self.prev {
Some(prev) if prev.1 <= new.1 && (prev.0.dyn_is_same(&*new.0) || !self.interrupt) => {
prev
},
_ => self.prev.insert(new),
};
match prev.0.tick(ctx) {
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
ControlFlow::Break(r) => {
self.prev = None;
ControlFlow::Break(r)
},
}
}
}
/// An action that allows implementing a decision tree, with action
/// prioritisation.
///
/// The inner function will be run every tick to decide on an action. When an
/// action is chosen, it will be performed until completed *UNLESS* an action
/// with a more urgent priority is chosen in a subsequent tick. [`choose`] tries
/// to commit to actions when it can: only more urgent actions will interrupt an
/// action that's currently being performed. If you want something that's more
/// eager to switch actions, see [`watch`].
///
/// # Example
///
/// ```ignore
/// choose(|ctx| {
/// if ctx.npc.is_being_attacked() {
/// urgent(combat()) // If we're in danger, do something!
/// } else if ctx.npc.is_hungry() {
/// important(eat()) // If we're hungry, eat
/// } else {
/// casual(idle()) // Otherwise, do nothing
/// }
/// })
/// ```
pub fn choose<R: 'static, F>(f: F) -> impl Action<R>
where
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
{
Tree {
next: f,
prev: None,
interrupt: false,
}
}
/// An action that allows implementing a decision tree, with action
/// prioritisation.
///
/// The inner function will be run every tick to decide on an action. When an
/// action is chosen, it will be performed until completed unless a different
/// action is chosen in a subsequent tick. [`watch`] is very unfocussed and will
/// happily switch between actions rapidly between ticks if conditions change.
/// If you want something that tends to commit to actions until they are
/// completed, see [`choose`].
///
/// # Example
///
/// ```ignore
/// choose(|ctx| {
/// if ctx.npc.is_being_attacked() {
/// urgent(combat()) // If we're in danger, do something!
/// } else if ctx.npc.is_hungry() {
/// important(eat()) // If we're hungry, eat
/// } else {
/// casual(idle()) // Otherwise, do nothing
/// }
/// })
/// ```
pub fn watch<R: 'static, F>(f: F) -> impl Action<R>
where
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
{
Tree {
next: f,
prev: None,
interrupt: true,
}
}
// Then
/// See [`Action::then`].
#[derive(Copy, Clone)]
pub struct Then<A0, A1, R0> {
a0: A0,
a0_finished: bool,
a1: A1,
phantom: PhantomData<R0>,
}
impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync + 'static>
Action<R1> for Then<A0, A1, R0>
{
fn is_same(&self, other: &Self) -> bool {
self.a0.is_same(&other.a0) && self.a1.is_same(&other.a1)
}
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) {
self.a0.reset();
self.a0_finished = false;
self.a1.reset();
}
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
if !self.a0_finished {
match self.a0.tick(ctx) {
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
ControlFlow::Break(_) => self.a0_finished = true,
}
}
self.a1.tick(ctx)
}
}
// Repeat
/// See [`Action::repeat`].
#[derive(Copy, Clone)]
pub struct Repeat<A, R = ()>(A, PhantomData<R>);
impl<R: Send + Sync + 'static, A: Action<R>> Action<!> for Repeat<A, R> {
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
fn dyn_is_same(&self, other: &dyn Action<!>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.0.reset(); }
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<!> {
match self.0.tick(ctx) {
ControlFlow::Continue(()) => ControlFlow::Continue(()),
ControlFlow::Break(_) => {
self.0.reset();
ControlFlow::Continue(())
},
}
}
}
// Sequence
/// See [`seq`].
#[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(())
}
}
/// An action that consumes and performs an iterator of actions in sequence, one
/// after another.
///
/// # Example
///
/// ```ignore
/// // A list of enemies we should attack in turn
/// let enemies = vec![
/// ugly_goblin,
/// stinky_troll,
/// rude_dwarf,
/// ];
///
/// // Attack each enemy, one after another
/// seq(enemies
/// .into_iter()
/// .map(|enemy| attack(enemy)))
/// ```
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
/// See [`Action::stop_if`].
#[derive(Copy, Clone)]
pub struct StopIf<A, F>(A, F);
impl<A: Action<R>, F: FnMut(&mut NpcCtx) -> bool + Send + Sync + 'static, R> Action<Option<R>>
for StopIf<A, F>
{
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
fn dyn_is_same(&self, other: &dyn Action<Option<R>>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.0.reset(); }
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<Option<R>> {
if (self.1)(ctx) {
ControlFlow::Break(None)
} else {
self.0.tick(ctx).map_break(Some)
}
}
}
// Map
/// See [`Action::map`].
#[derive(Copy, Clone)]
pub struct Map<A, F, R>(A, F, PhantomData<R>);
impl<A: Action<R>, F: FnMut(R) -> R1 + Send + Sync + 'static, R: Send + Sync + 'static, R1>
Action<R1> for Map<A, F, R>
{
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.0.reset(); }
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
self.0.tick(ctx).map_break(&mut self.1)
}
}

View File

@ -1,4 +1,4 @@
use crate::rule::npc_ai::NpcCtx;
use crate::ai::{Action, NpcCtx};
pub use common::rtsim::{NpcId, Profession};
use common::{
comp,
@ -11,10 +11,8 @@ use rand::prelude::*;
use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap;
use std::{
any::{Any, TypeId},
collections::VecDeque,
marker::PhantomData,
ops::{ControlFlow, Deref, DerefMut, Generator, GeneratorState},
ops::{Deref, DerefMut, Generator, GeneratorState},
pin::Pin,
sync::{
atomic::{AtomicPtr, Ordering},
@ -54,369 +52,6 @@ impl Controller {
pub fn idle() -> Self { Self { goto: None } }
}
pub trait Action<R = ()>: Any + Send + Sync {
/// Returns `true` if the action should be considered the 'same' (i.e:
/// achieving the same objective) as another. In general, the AI system
/// will try to avoid switching (and therefore restarting) tasks when the
/// new task is the 'same' as the old one.
// TODO: Figure out a way to compare actions based on their 'intention': i.e:
// two pathing actions should be considered equivalent if their destination
// is the same regardless of the progress they've each made.
fn is_same(&self, other: &Self) -> bool
where
Self: Sized;
fn dyn_is_same_sized(&self, other: &dyn Action<R>) -> bool
where
Self: Sized,
{
match (other as &dyn Any).downcast_ref::<Self>() {
Some(other) => self.is_same(other),
None => false,
}
}
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool;
// Reset the action to its initial state so it can be restarted
fn reset(&mut self);
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R>;
fn then<A1: Action<R1>, R1>(self, other: A1) -> Then<Self, A1, R>
where
Self: Sized,
{
Then {
a0: self,
a0_finished: false,
a1: other,
phantom: PhantomData,
}
}
fn repeat<R1>(self) -> Repeat<Self, R1>
where
Self: Sized,
{
Repeat(self, PhantomData)
}
fn stop_if<F: FnMut(&mut NpcCtx) -> bool>(self, f: F) -> StopIf<Self, F>
where
Self: Sized,
{
StopIf(self, f)
}
fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R>
where
Self: Sized,
{
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
#[derive(Copy, Clone)]
pub struct Now<F, A>(F, Option<A>);
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'static, A: Action<R>>
Action<R> for Now<F, A>
{
// TODO: This doesn't compare?!
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.1 = None; }
// TODO: Reset closure state?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
(self.1.get_or_insert_with(|| (self.0)(ctx))).tick(ctx)
}
}
pub fn now<F, A>(f: F) -> Now<F, A>
where
F: FnMut(&mut NpcCtx) -> A,
{
Now(f, None)
}
// Just
#[derive(Copy, Clone)]
pub struct Just<F, R = ()>(F, PhantomData<R>);
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static> Action<R>
for Just<F, R>
{
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) {}
// TODO: Reset closure state?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { ControlFlow::Break((self.0)(ctx)) }
}
pub fn just<F, R: Send + Sync + 'static>(mut f: F) -> Just<F, R>
where
F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static,
{
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;
const URGENT: Priority = 0;
const CASUAL: Priority = 1;
pub struct Node<R>(Box<dyn Action<R>>, Priority);
pub fn urgent<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), URGENT) }
pub fn casual<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), CASUAL) }
pub struct Tree<F, R> {
next: F,
prev: Option<Node<R>>,
interrupt: bool,
}
impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Action<R>
for Tree<F, R>
{
fn is_same(&self, other: &Self) -> bool { true }
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.prev = None; }
// TODO: Reset `next` too?
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
let new = (self.next)(ctx);
let prev = match &mut self.prev {
Some(prev) if prev.1 <= new.1 && (prev.0.dyn_is_same(&*new.0) || !self.interrupt) => {
prev
},
_ => self.prev.insert(new),
};
match prev.0.tick(ctx) {
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
ControlFlow::Break(r) => {
self.prev = None;
ControlFlow::Break(r)
},
}
}
}
pub fn choose<R: 'static, F>(f: F) -> impl Action<R>
where
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
{
Tree {
next: f,
prev: None,
interrupt: false,
}
}
pub fn watch<R: 'static, F>(f: F) -> impl Action<R>
where
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
{
Tree {
next: f,
prev: None,
interrupt: true,
}
}
// Then
#[derive(Copy, Clone)]
pub struct Then<A0, A1, R0> {
a0: A0,
a0_finished: bool,
a1: A1,
phantom: PhantomData<R0>,
}
impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync + 'static>
Action<R1> for Then<A0, A1, R0>
{
fn is_same(&self, other: &Self) -> bool {
self.a0.is_same(&other.a0) && self.a1.is_same(&other.a1)
}
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) {
self.a0.reset();
self.a0_finished = false;
self.a1.reset();
}
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
if !self.a0_finished {
match self.a0.tick(ctx) {
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
ControlFlow::Break(_) => self.a0_finished = true,
}
}
self.a1.tick(ctx)
}
}
// Repeat
#[derive(Copy, Clone)]
pub struct Repeat<A, R = ()>(A, PhantomData<R>);
impl<R: Send + Sync + 'static, A: Action<R>> Action<!> for Repeat<A, R> {
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
fn dyn_is_same(&self, other: &dyn Action<!>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.0.reset(); }
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<!> {
match self.0.tick(ctx) {
ControlFlow::Continue(()) => ControlFlow::Continue(()),
ControlFlow::Break(_) => {
self.0.reset();
ControlFlow::Continue(())
},
}
}
}
// 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)]
pub struct StopIf<A, F>(A, F);
impl<A: Action<R>, F: FnMut(&mut NpcCtx) -> bool + Send + Sync + 'static, R> Action<Option<R>>
for StopIf<A, F>
{
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
fn dyn_is_same(&self, other: &dyn Action<Option<R>>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.0.reset(); }
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<Option<R>> {
if (self.1)(ctx) {
ControlFlow::Break(None)
} else {
self.0.tick(ctx).map_break(Some)
}
}
}
// Map
#[derive(Copy, Clone)]
pub struct Map<A, F, R>(A, F, PhantomData<R>);
impl<A: Action<R>, F: FnMut(R) -> R1 + Send + Sync + 'static, R: Send + Sync + 'static, R1>
Action<R1> for Map<A, F, R>
{
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
fn reset(&mut self) { self.0.reset(); }
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
self.0.tick(ctx).map_break(&mut self.1)
}
}
pub struct Brain {
pub(crate) action: Box<dyn Action<!>>,
}

View File

@ -10,6 +10,7 @@
let_chains
)]
pub mod ai;
pub mod data;
pub mod event;
pub mod gen;

View File

@ -1,11 +1,9 @@
use std::{collections::VecDeque, hash::BuildHasherDefault};
use crate::{
ai::{casual, choose, finish, just, now, seq, urgent, watch, Action, NpcCtx},
data::{
npc::{
casual, choose, finish, just, now, seq, urgent, watch, Action, Brain, Controller, Npc,
NpcId, PathData, PathingMemory,
},
npc::{Brain, Controller, Npc, NpcId, PathData, PathingMemory},
Sites,
},
event::OnTick,
@ -254,7 +252,11 @@ impl Rule for NpcAi {
let mut controller = Controller { goto: npc.goto };
brain.action.tick(&mut NpcCtx {
ctx: &ctx,
state: ctx.state,
world: ctx.world,
index: ctx.index,
time_of_day: ctx.event.time_of_day,
time: ctx.event.time,
npc,
npc_id,
controller: &mut controller,
@ -262,93 +264,46 @@ impl Rule for NpcAi {
/*
let action: ControlFlow<()> = try {
if matches!(npc.profession, Some(Profession::Adventurer(_))) {
if let Some(home) = npc.home {
// Travel between random nearby sites
let task = generate(
move |(_, npc, ctx): &(NpcId, &Npc, &EventCtx<_, _>)| {
// Choose a random site that's fairly close by
let tgt_site = ctx
.state
.data()
.sites
.iter()
.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);
brain.tick(&mut NpcData {
ctx: &ctx,
npc,
npc_id,
controller: &mut controller,
});
/*
// // Choose a random plaza in the npcs home site (which should be the
// // current here) to go to.
let task =
generate(move |(_, npc, ctx): &(NpcId, &Npc, &EventCtx<_, _>)| {
let data = ctx.state.data();
let site2 =
npc.home.and_then(|home| data.sites.get(home)).and_then(
|home| match &ctx.index.sites.get(home.world_site?).kind
{
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
},
);
let wpos = ctx
.state
.data()
.sites
.get(tgt_site)
.map_or(npc.wpos.xy(), |site| site.wpos.as_());
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();
TravelTo {
wpos,
use_paths: true,
}
})
.repeat();
task_state.perform(
task,
&(npc_id, &*npc, &ctx),
&mut controller,
)?;
}
} else {
brain.tick(&mut NpcData {
ctx: &ctx,
npc,
npc_id,
controller: &mut controller,
});
/*
// // Choose a random plaza in the npcs home site (which should be the
// // current here) to go to.
let task =
generate(move |(_, npc, ctx): &(NpcId, &Npc, &EventCtx<_, _>)| {
let data = ctx.state.data();
let site2 =
npc.home.and_then(|home| data.sites.get(home)).and_then(
|home| match &ctx.index.sites.get(home.world_site?).kind
{
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
},
);
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)?;
*/
}
task_state.perform(task, &(npc_id, &*npc, &ctx), &mut controller)?;
*/
};
*/
@ -364,23 +319,13 @@ impl Rule for NpcAi {
}
}
pub struct NpcCtx<'a> {
ctx: &'a EventCtx<'a, NpcAi, OnTick>,
npc_id: NpcId,
npc: &'a Npc,
controller: &'a mut Controller,
}
fn idle() -> impl Action { just(|ctx| *ctx.controller = Controller::idle()) }
fn pass() -> impl Action { just(|ctx| {}) }
fn idle_wait() -> impl Action {
just(|ctx| *ctx.controller = Controller::idle())
.repeat()
.map(|_| ())
}
fn move_toward(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
/// Try to walk toward a 3D position without caring for obstacles.
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
const STEP_DIST: f32 = 16.0;
const GOAL_DIST: f32 = 2.0;
just(move |ctx| {
let rpos = wpos - ctx.npc.wpos;
let len = rpos.magnitude();
@ -389,76 +334,78 @@ fn move_toward(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
speed_factor,
));
})
.repeat()
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < GOAL_DIST.powi(2))
.map(|_| {})
}
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
const MIN_DIST: f32 = 2.0;
move_toward(wpos, speed_factor)
.repeat()
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < MIN_DIST.powi(2))
.map(|_| {})
}
/// Try to walk toward a 2D position on the terrain without caring for
/// obstacles.
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),
);
let wpos = wpos2d.with_z(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 {
/// Try to travel to a site. Where practical, paths will be taken.
fn travel_to_site(tgt_site: SiteId) -> impl Action {
now(move |ctx| {
let sites = &ctx.ctx.state.data().sites;
let sites = &ctx.state.data().sites;
// If we can, try to find a path to the site via tracks
// If we're currently in a site, try to find a path to the target 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)
&& let Some((mut tracks, _)) = path_towns(current_site, tgt_site, sites, ctx.world)
{
// For every track in the path we discovered between the sites...
seq(tracks
.path
.into_iter()
// ...traverse the nodes of that path.
.map(|(track_id, reversed)| now(move |ctx| {
let track_len = ctx.ctx.world.civs().tracks.get(track_id).path().len();
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)
}
.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];
// 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]);
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_());
// 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_());
goto_2d(path_wpos2d.as_(), 1.0)
// Walk toward the node
goto_2d(node_wpos.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
// 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).boxed()
} else {
pass().boxed()
// If we can't find a way to get to the site at all, there's nothing more to be done
finish().boxed()
}
})
}
// Seconds
fn timeout(ctx: &NpcCtx, time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
let end = ctx.ctx.event.time.0 + time;
move |ctx| ctx.ctx.event.time.0 > end
let end = ctx.time.0 + time;
move |ctx| ctx.time.0 > end
}
fn think() -> impl Action {
@ -466,12 +413,13 @@ fn think() -> impl Action {
if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) {
// Choose a random site that's fairly close by
if let Some(tgt_site) = ctx
.ctx
.state
.data()
.sites
.iter()
.filter(|(site_id, site)| {
// TODO: faction.is_some() is used as a proxy for whether the site likely has
// paths, don't do this
site.faction.is_some()
&& ctx.npc.current_site.map_or(true, |cs| *site_id != cs)
&& thread_rng().gen_bool(0.25)
@ -479,429 +427,36 @@ fn think() -> impl Action {
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
.map(|(site_id, _)| site_id)
{
casual(path_to_site(tgt_site))
casual(travel_to_site(tgt_site))
} else {
casual(pass())
casual(finish())
}
} else if matches!(ctx.npc.profession, Some(Profession::Blacksmith)) {
casual(idle_wait())
casual(idle())
} 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_wait().stop_if(timeout(ctx, 5.0))))
.then(now(|ctx| idle().repeat().stop_if(timeout(ctx, 5.0))))
.map(|_| {}),
)
}
})
}
/*
#[derive(Clone)]
pub struct Generate<F, T>(F, PhantomData<T>);
impl<F, T> PartialEq for Generate<F, T> {
fn eq(&self, _: &Self) -> bool { true }
}
pub fn generate<F, T>(f: F) -> Generate<F, T> { Generate(f, PhantomData) }
impl<F, T: Task> Task for Generate<F, T>
where
F: Clone + Send + Sync + 'static + for<'a> Fn(&T::Ctx<'a>) -> T,
{
type Ctx<'a> = T::Ctx<'a>;
type State = (T::State, T);
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State {
let task = (self.0)(ctx);
(task.begin(ctx), task)
}
fn run<'a>(
&self,
(state, task): &mut Self::State,
ctx: &Self::Ctx<'a>,
controller: &mut Controller,
) -> ControlFlow<()> {
task.run(state, ctx, controller)
}
}
#[derive(Clone, PartialEq)]
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 {
type Ctx<'a> = (&'a Npc, &'a EventCtx<'a, NpcAi, OnTick>);
type State = ();
fn begin<'a>(&self, (_npc, _ctx): &Self::Ctx<'a>) -> Self::State {}
fn run<'a>(
&self,
(): &mut Self::State,
(npc, ctx): &Self::Ctx<'a>,
controller: &mut Controller,
) -> ControlFlow<()> {
if npc.wpos.xy().distance_squared(self.wpos) < self.finish_dist.powi(2) {
controller.goto = None;
FINISH
} else {
let dist = npc.wpos.xy().distance(self.wpos);
let step = dist.min(32.0);
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)
{
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
}
}
}
#[derive(Clone, PartialEq)]
pub struct TravelTo {
wpos: Vec2<f32>,
use_paths: bool,
}
pub enum TravelStage {
Goto(Vec2<f32>),
SiteToSite {
path: PathData<(Id<Track>, bool), SiteId>,
progress: usize,
},
IntraSite {
path: PathData<Vec2<i32>, Vec2<i32>>,
site: Id<WorldSite>,
},
}
impl Task for TravelTo {
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>(
&self,
(stages, task_state): &mut Self::State,
(npc_id, npc, ctx): &Self::Ctx<'a>,
controller: &mut Controller,
) -> ControlFlow<()> {
let get_site2 = |site| match &ctx.index.sites.get(site).kind {
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
};
if let Some(stage) = stages.front_mut() {
match stage {
TravelStage::Goto(wpos) => {
task_state.perform(goto(*wpos), &(npc, ctx), controller)?;
stages.pop_front();
},
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 {
FINISH
}
}
}
/*
let data = ctx.state.data();
let site2 =
npc.home.and_then(|home| data.sites.get(home)).and_then(
|home| match &ctx.index.sites.get(home.world_site?).kind
{
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
},
);
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,
}
*/
trait IsTask =
core::ops::Generator<Data<NpcData<'static>>, Yield = (), Return = ()> + Any + Send + Sync;
pub struct NpcData<'a> {
ctx: &'a EventCtx<'a, NpcAi, OnTick>,
npc_id: NpcId,
npc: &'a Npc,
controller: &'a mut Controller,
}
unsafe impl Context for NpcData<'static> {
type Ty<'a> = NpcData<'a>;
}
pub fn brain() -> Brain<NpcData<'static>> {
Brain::new(|mut data: Data<NpcData>| {
let mut task = TaskBox::<_, ()>::new(data.clone());
loop {
println!("Started");
while let ControlFlow::Break(end) = task.finish(0) {
yield end;
}
// Choose a new plaza in the NPC's home site to path towards
let path = data.with(|d| {
let data = d.ctx.state.data();
let current_site = data.sites.get(d.npc.current_site?)?;
let site2 = match &d.ctx.index.sites.get(current_site.world_site?).kind {
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
}?;
let plaza = &site2.plots[site2.plazas().choose(&mut thread_rng())?];
let end_wpos = site2.tile_center_wpos(plaza.root_tile());
if end_wpos.as_::<f32>().distance(d.npc.wpos.xy()) < 32.0 {
return None;
}
let start = site2.wpos_tile_pos(d.npc.wpos.xy().as_());
let end = site2.wpos_tile_pos(plaza.root_tile());
let path = match path_in_site(start, end, site2) {
PathResult::Path(path) => path,
_ => return None,
};
println!(
"CHOSE PATH, len = {}, start = {:?}, end = {:?}\nnpc = {:?}",
path.len(),
start,
end,
d.npc_id
);
Some((current_site.world_site?, path))
});
if let Some((site, path)) = path {
println!("Begin path");
task.perform(0, walk_path(site, path));
} else {
println!("No path, waiting...");
for _ in 0..100 {
yield ();
}
println!("Waited.");
}
}
})
}
fn walk_path(site: Id<WorldSite>, path: Path<Vec2<i32>>) -> impl IsTask {
move |mut data: Data<NpcData>| {
for tile in path {
println!("TILE");
let wpos = data.with(|d| {
match &d.ctx.index.sites.get(site).kind {
SiteKind::Refactor(site2)
| SiteKind::CliffTown(site2)
| SiteKind::DesertCity(site2) => Some(site2),
_ => None,
}
.expect("intrasite path should only be started on a site2 site")
.tile_center_wpos(tile)
.as_()
+ 0.5
});
println!(
"Walking to next tile... tile wpos = {:?} npc wpos = {:?}",
wpos,
data.with(|d| d.npc.wpos)
);
while data.with(|d| d.npc.wpos.xy().distance_squared(wpos) > 2.0) {
data.with(|d| {
d.controller.goto = Some((
wpos.with_z(
d.ctx
.world
.sim()
.get_alt_approx(wpos.map(|e| e as i32))
.unwrap_or(0.0),
),
1.0,
))
});
yield ();
}
}
println!("Waiting..");
for _ in 0..100 {
yield ();
}
println!("Waited.");
}
}
*/
// 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 }); }
// }
// }