mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Improved AI code structure, added docs
This commit is contained in:
parent
9413a56c13
commit
8f7b11f12f
583
rtsim/src/ai/mod.rs
Normal file
583
rtsim/src/ai/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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<!>>,
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
let_chains
|
||||
)]
|
||||
|
||||
pub mod ai;
|
||||
pub mod data;
|
||||
pub mod event;
|
||||
pub mod gen;
|
||||
|
@ -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 }); }
|
||||
// }
|
||||
// }
|
||||
|
Loading…
Reference in New Issue
Block a user