mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Switch to combinator-driven NPC AI API
This commit is contained in:
parent
acecc62d40
commit
b2f92e4a6c
@ -1,4 +1,4 @@
|
|||||||
use crate::rule::npc_ai;
|
use crate::rule::npc_ai::NpcCtx;
|
||||||
pub use common::rtsim::{NpcId, Profession};
|
pub use common::rtsim::{NpcId, Profession};
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
@ -50,221 +50,295 @@ pub struct Controller {
|
|||||||
pub goto: Option<(Vec3<f32>, f32)>,
|
pub goto: Option<(Vec3<f32>, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl Controller {
|
||||||
pub struct TaskState {
|
pub fn idle() -> Self { Self { goto: None } }
|
||||||
state: Option<Box<dyn Any + Send + Sync>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CONTINUE: ControlFlow<()> = ControlFlow::Break(());
|
pub trait Action<R = ()>: Any + Send + Sync {
|
||||||
pub const FINISH: ControlFlow<()> = ControlFlow::Continue(());
|
/// 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);
|
||||||
|
|
||||||
pub trait Task: PartialEq + Clone + Send + Sync + 'static {
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R>;
|
||||||
type State: Send + Sync;
|
|
||||||
type Ctx<'a>;
|
|
||||||
|
|
||||||
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State;
|
fn then<A1: Action<R1>, R1>(self, other: A1) -> Then<Self, A1, R>
|
||||||
|
where
|
||||||
fn run<'a>(
|
Self: Sized,
|
||||||
&self,
|
{
|
||||||
state: &mut Self::State,
|
Then {
|
||||||
ctx: &Self::Ctx<'a>,
|
a0: self,
|
||||||
controller: &mut Controller,
|
a0_finished: false,
|
||||||
) -> ControlFlow<()>;
|
a1: other,
|
||||||
|
phantom: PhantomData,
|
||||||
fn then<B: Task>(self, other: B) -> Then<Self, B> { Then(self, other) }
|
}
|
||||||
|
}
|
||||||
fn repeat(self) -> Repeat<Self> { Repeat(self) }
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
// Now
|
||||||
pub struct Then<A, B>(A, B);
|
|
||||||
|
|
||||||
impl<A: Task, B> Task for Then<A, B>
|
#[derive(Copy, Clone)]
|
||||||
where
|
pub struct Now<F, A>(F, Option<A>);
|
||||||
B: for<'a> Task<Ctx<'a> = A::Ctx<'a>>,
|
|
||||||
|
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'static, A: Action<R>>
|
||||||
|
Action<R> for Now<F, A>
|
||||||
{
|
{
|
||||||
// TODO: Use `Either` instead
|
// TODO: This doesn't compare?!
|
||||||
type Ctx<'a> = A::Ctx<'a>;
|
fn is_same(&self, other: &Self) -> bool { true }
|
||||||
type State = Result<A::State, B::State>;
|
|
||||||
|
|
||||||
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { Ok(self.0.begin(ctx)) }
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn run<'a>(
|
fn reset(&mut self) { self.1 = None; }
|
||||||
&self,
|
|
||||||
state: &mut Self::State,
|
// TODO: Reset closure state?
|
||||||
ctx: &Self::Ctx<'a>,
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
||||||
controller: &mut Controller,
|
(self.1.get_or_insert_with(|| (self.0)(ctx))).tick(ctx)
|
||||||
) -> ControlFlow<()> {
|
|
||||||
match state {
|
|
||||||
Ok(a_state) => {
|
|
||||||
self.0.run(a_state, ctx, controller)?;
|
|
||||||
*state = Err(self.1.begin(ctx));
|
|
||||||
CONTINUE
|
|
||||||
},
|
|
||||||
Err(b_state) => self.1.run(b_state, ctx, controller),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
pub fn now<F, A>(f: F) -> Now<F, A>
|
||||||
pub struct Repeat<A>(A);
|
where
|
||||||
|
F: FnMut(&mut NpcCtx) -> A,
|
||||||
impl<A: Task> Task for Repeat<A> {
|
{
|
||||||
type Ctx<'a> = A::Ctx<'a>;
|
Now(f, None)
|
||||||
type State = A::State;
|
|
||||||
|
|
||||||
fn begin<'a>(&self, ctx: &Self::Ctx<'a>) -> Self::State { self.0.begin(ctx) }
|
|
||||||
|
|
||||||
fn run<'a>(
|
|
||||||
&self,
|
|
||||||
state: &mut Self::State,
|
|
||||||
ctx: &Self::Ctx<'a>,
|
|
||||||
controller: &mut Controller,
|
|
||||||
) -> ControlFlow<()> {
|
|
||||||
self.0.run(state, ctx, controller)?;
|
|
||||||
*state = self.0.begin(ctx);
|
|
||||||
CONTINUE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskState {
|
// Just
|
||||||
pub fn perform<'a, T: Task>(
|
|
||||||
&mut self,
|
|
||||||
task: T,
|
|
||||||
ctx: &T::Ctx<'a>,
|
|
||||||
controller: &mut Controller,
|
|
||||||
) -> ControlFlow<()> {
|
|
||||||
type StateOf<T> = (T, <T as Task>::State);
|
|
||||||
|
|
||||||
let mut state = if let Some(state) = self.state.take().and_then(|state| {
|
#[derive(Copy, Clone)]
|
||||||
state
|
pub struct Just<F, R = ()>(F, PhantomData<R>);
|
||||||
.downcast::<StateOf<T>>()
|
|
||||||
.ok()
|
|
||||||
.filter(|state| state.0 == task)
|
|
||||||
}) {
|
|
||||||
state
|
|
||||||
} else {
|
|
||||||
let mut state = task.begin(ctx);
|
|
||||||
Box::new((task, state))
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = state.0.run(&mut state.1, ctx, controller);
|
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 }
|
||||||
|
|
||||||
self.state = if matches!(res, ControlFlow::Break(())) {
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
Some(state)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
res
|
fn reset(&mut self) {}
|
||||||
}
|
|
||||||
|
// TODO: Reset closure state?
|
||||||
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { ControlFlow::Break((self.0)(ctx)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe trait Context {
|
pub fn just<F, R: Send + Sync + 'static>(mut f: F) -> Just<F, R>
|
||||||
// TODO: Somehow we need to enforce this bound, I think?
|
where
|
||||||
// Hence, this trait is unsafe for now.
|
F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static,
|
||||||
type Ty<'a>; // where for<'a> Self::Ty<'a>: 'a;
|
{
|
||||||
|
Just(f, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Data<C: Context>(Arc<AtomicPtr<()>>, PhantomData<C>);
|
// Tree
|
||||||
|
|
||||||
impl<C: Context> Clone for Data<C> {
|
|
||||||
fn clone(&self) -> Self { Self(self.0.clone(), PhantomData) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Context> Data<C> {
|
|
||||||
pub fn with<R>(&mut self, f: impl FnOnce(&mut C::Ty<'_>) -> R) -> R {
|
|
||||||
let ptr = self.0.swap(std::ptr::null_mut(), Ordering::Acquire);
|
|
||||||
if ptr.is_null() {
|
|
||||||
panic!("Data pointer was null, you probably tried to access data recursively")
|
|
||||||
} else {
|
|
||||||
// Safety: We have exclusive access to the pointer within this scope.
|
|
||||||
// TODO: Do we need a panic guard here?
|
|
||||||
let r = f(unsafe { &mut *(ptr as *mut C::Ty<'_>) });
|
|
||||||
self.0.store(ptr, Ordering::Release);
|
|
||||||
r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Priority = usize;
|
pub type Priority = usize;
|
||||||
|
|
||||||
pub struct TaskBox<C: Context, A = ()> {
|
const URGENT: Priority = 0;
|
||||||
task: Option<(
|
const CASUAL: Priority = 1;
|
||||||
TypeId,
|
|
||||||
Box<dyn Generator<Data<C>, Yield = A, Return = ()> + Unpin + Send + Sync>,
|
pub struct Node<R>(Box<dyn Action<R>>, Priority);
|
||||||
Priority,
|
|
||||||
)>,
|
pub fn urgent<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), URGENT) }
|
||||||
data: Data<C>,
|
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<C: Context, A> TaskBox<C, A> {
|
impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Action<R>
|
||||||
pub fn new(data: Data<C>) -> Self { Self { task: None, data } }
|
for Tree<F, R>
|
||||||
|
{
|
||||||
|
fn is_same(&self, other: &Self) -> bool { true }
|
||||||
|
|
||||||
#[must_use]
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
pub fn finish(&mut self, prio: Priority) -> ControlFlow<A> {
|
|
||||||
if let Some((_, task, _)) = &mut self.task.as_mut().filter(|(_, _, p)| *p <= prio) {
|
|
||||||
match Pin::new(task).resume(self.data.clone()) {
|
|
||||||
GeneratorState::Yielded(action) => ControlFlow::Break(action),
|
|
||||||
GeneratorState::Complete(_) => {
|
|
||||||
self.task = None;
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
fn reset(&mut self) { self.prev = None; }
|
||||||
pub fn perform<T: Generator<Data<C>, Yield = A, Return = ()> + Unpin + Any + Send + Sync>(
|
|
||||||
&mut self,
|
// TODO: Reset `next` too?
|
||||||
prio: Priority,
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
||||||
task: T,
|
let new = (self.next)(ctx);
|
||||||
) -> ControlFlow<A> {
|
|
||||||
let ty = TypeId::of::<T>();
|
let prev = match &mut self.prev {
|
||||||
if self
|
Some(prev) if prev.1 <= new.1 && (prev.0.dyn_is_same(&*new.0) || !self.interrupt) => {
|
||||||
.task
|
prev
|
||||||
.as_mut()
|
},
|
||||||
.filter(|(ty1, _, _)| *ty1 == ty)
|
_ => self.prev.insert(new),
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
self.task = Some((ty, Box::new(task), prio));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.finish(prio)
|
match prev.0.tick(ctx) {
|
||||||
}
|
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
|
||||||
}
|
ControlFlow::Break(r) => {
|
||||||
|
self.prev = None;
|
||||||
pub struct Brain<C: Context, A = ()> {
|
ControlFlow::Break(r)
|
||||||
task: Box<dyn Generator<Data<C>, Yield = A, Return = !> + Unpin + Send + Sync>,
|
|
||||||
data: Data<C>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Context, A> Brain<C, A> {
|
|
||||||
pub fn new<T: Generator<Data<C>, Yield = A, Return = !> + Unpin + Any + Send + Sync>(
|
|
||||||
task: T,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
task: Box::new(task),
|
|
||||||
data: Data(Arc::new(AtomicPtr::new(std::ptr::null_mut())), PhantomData),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self, ctx_ref: &mut C::Ty<'_>) -> A {
|
|
||||||
self.data
|
|
||||||
.0
|
|
||||||
.store(ctx_ref as *mut C::Ty<'_> as *mut (), Ordering::SeqCst);
|
|
||||||
match Pin::new(&mut self.task).resume(self.data.clone()) {
|
|
||||||
GeneratorState::Yielded(action) => {
|
|
||||||
self.data.0.store(std::ptr::null_mut(), Ordering::Release);
|
|
||||||
action
|
|
||||||
},
|
},
|
||||||
GeneratorState::Complete(ret) => match ret {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<!>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Npc {
|
pub struct Npc {
|
||||||
// Persisted state
|
// Persisted state
|
||||||
@ -292,10 +366,7 @@ pub struct Npc {
|
|||||||
pub mode: NpcMode,
|
pub mode: NpcMode,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub task_state: Option<TaskState>,
|
pub brain: Option<Brain>,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
|
||||||
pub brain: Option<Brain<npc_ai::NpcData<'static>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Npc {
|
impl Clone for Npc {
|
||||||
@ -310,7 +381,6 @@ impl Clone for Npc {
|
|||||||
current_site: Default::default(),
|
current_site: Default::default(),
|
||||||
goto: Default::default(),
|
goto: Default::default(),
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
task_state: Default::default(),
|
|
||||||
brain: Default::default(),
|
brain: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,8 +400,7 @@ impl Npc {
|
|||||||
current_site: None,
|
current_site: None,
|
||||||
goto: None,
|
goto: None,
|
||||||
mode: NpcMode::Simulated,
|
mode: NpcMode::Simulated,
|
||||||
task_state: Default::default(),
|
brain: None,
|
||||||
brain: Some(npc_ai::brain()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,12 +72,12 @@ impl Data {
|
|||||||
.map(|e| e as f32 + 0.5)
|
.map(|e| e as f32 + 0.5)
|
||||||
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
||||||
};
|
};
|
||||||
for _ in 0..1 {
|
for _ in 0..10 {
|
||||||
this.npcs.create(
|
this.npcs.create(
|
||||||
Npc::new(rng.gen(), rand_wpos(&mut rng))
|
Npc::new(rng.gen(), rand_wpos(&mut rng))
|
||||||
.with_faction(site.faction)
|
.with_faction(site.faction)
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_profession(match 1/*rng.gen_range(0..20)*/ {
|
.with_profession(match rng.gen_range(0..20) {
|
||||||
0 => Profession::Hunter,
|
0 => Profession::Hunter,
|
||||||
1 => Profession::Blacksmith,
|
1 => Profession::Blacksmith,
|
||||||
2 => Profession::Chef,
|
2 => Profession::Chef,
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
try_blocks,
|
try_blocks,
|
||||||
generator_trait,
|
generator_trait,
|
||||||
generators,
|
generators,
|
||||||
trait_alias
|
trait_alias,
|
||||||
|
trait_upcasting,
|
||||||
|
control_flow_enum
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
@ -3,8 +3,8 @@ use std::{collections::VecDeque, hash::BuildHasherDefault};
|
|||||||
use crate::{
|
use crate::{
|
||||||
data::{
|
data::{
|
||||||
npc::{
|
npc::{
|
||||||
Brain, Context, Controller, Data, Npc, NpcId, PathData, PathingMemory, Task, TaskBox,
|
casual, choose, just, now, urgent, Action, Brain, Controller, Npc, NpcId, PathData,
|
||||||
TaskState, CONTINUE, FINISH,
|
PathingMemory,
|
||||||
},
|
},
|
||||||
Sites,
|
Sites,
|
||||||
},
|
},
|
||||||
@ -240,21 +240,27 @@ impl Rule for NpcAi {
|
|||||||
let npc_ids = ctx.state.data().npcs.keys().collect::<Vec<_>>();
|
let npc_ids = ctx.state.data().npcs.keys().collect::<Vec<_>>();
|
||||||
|
|
||||||
for npc_id in npc_ids {
|
for npc_id in npc_ids {
|
||||||
let mut task_state = ctx.state.data_mut().npcs[npc_id]
|
|
||||||
.task_state
|
|
||||||
.take()
|
|
||||||
.unwrap_or_default();
|
|
||||||
let mut brain = ctx.state.data_mut().npcs[npc_id]
|
let mut brain = ctx.state.data_mut().npcs[npc_id]
|
||||||
.brain
|
.brain
|
||||||
.take()
|
.take()
|
||||||
.unwrap_or_else(brain);
|
.unwrap_or_else(|| Brain {
|
||||||
|
action: Box::new(think().repeat()),
|
||||||
|
});
|
||||||
|
|
||||||
let (controller, task_state) = {
|
let controller = {
|
||||||
let data = &*ctx.state.data();
|
let data = &*ctx.state.data();
|
||||||
let npc = &data.npcs[npc_id];
|
let npc = &data.npcs[npc_id];
|
||||||
|
|
||||||
let mut controller = Controller { goto: npc.goto };
|
let mut controller = Controller { goto: npc.goto };
|
||||||
|
|
||||||
|
brain.action.tick(&mut NpcCtx {
|
||||||
|
ctx: &ctx,
|
||||||
|
npc,
|
||||||
|
npc_id,
|
||||||
|
controller: &mut controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
let action: ControlFlow<()> = try {
|
let action: ControlFlow<()> = try {
|
||||||
if matches!(npc.profession, Some(Profession::Adventurer(_))) {
|
if matches!(npc.profession, Some(Profession::Adventurer(_))) {
|
||||||
if let Some(home) = npc.home {
|
if let Some(home) = npc.home {
|
||||||
@ -344,12 +350,12 @@ impl Rule for NpcAi {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
(controller, task_state)
|
controller
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.state.data_mut().npcs[npc_id].goto = controller.goto;
|
ctx.state.data_mut().npcs[npc_id].goto = controller.goto;
|
||||||
ctx.state.data_mut().npcs[npc_id].task_state = Some(task_state);
|
|
||||||
ctx.state.data_mut().npcs[npc_id].brain = Some(brain);
|
ctx.state.data_mut().npcs[npc_id].brain = Some(brain);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -358,6 +364,99 @@ 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 + Clone { just(|ctx| *ctx.controller = Controller::idle()) }
|
||||||
|
|
||||||
|
fn move_toward(wpos: Vec3<f32>, speed_factor: f32) -> impl Action + Clone {
|
||||||
|
const STEP_DIST: f32 = 10.0;
|
||||||
|
just(move |ctx| {
|
||||||
|
let rpos = wpos - ctx.npc.wpos;
|
||||||
|
let len = rpos.magnitude();
|
||||||
|
ctx.controller.goto = Some((
|
||||||
|
ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST),
|
||||||
|
speed_factor,
|
||||||
|
));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action + Clone {
|
||||||
|
const MIN_DIST: f32 = 1.0;
|
||||||
|
|
||||||
|
move_toward(wpos, speed_factor)
|
||||||
|
.repeat()
|
||||||
|
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < MIN_DIST.powi(2))
|
||||||
|
.map(|_| {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn think() -> impl Action {
|
||||||
|
choose(|ctx| {
|
||||||
|
if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) {
|
||||||
|
// Choose a random site that's fairly close by
|
||||||
|
let site_wpos2d = ctx
|
||||||
|
.ctx
|
||||||
|
.state
|
||||||
|
.data()
|
||||||
|
.sites
|
||||||
|
.iter()
|
||||||
|
.filter(|(site_id, site)| {
|
||||||
|
site.faction.is_some()
|
||||||
|
&& ctx.npc.current_site.map_or(true, |cs| *site_id != cs)
|
||||||
|
&& thread_rng().gen_bool(0.25)
|
||||||
|
})
|
||||||
|
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
|
||||||
|
.map(|(site_id, _)| site_id)
|
||||||
|
.and_then(|tgt_site| {
|
||||||
|
ctx.ctx
|
||||||
|
.state
|
||||||
|
.data()
|
||||||
|
.sites
|
||||||
|
.get(tgt_site)
|
||||||
|
.map(|site| site.wpos)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(site_wpos2d) = site_wpos2d {
|
||||||
|
// Walk toward the site
|
||||||
|
casual(goto(
|
||||||
|
site_wpos2d.map(|e| e as f32 + 0.5).with_z(
|
||||||
|
ctx.ctx
|
||||||
|
.world
|
||||||
|
.sim()
|
||||||
|
.get_alt_approx(site_wpos2d.as_())
|
||||||
|
.unwrap_or(0.0),
|
||||||
|
),
|
||||||
|
1.0,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
casual(idle())
|
||||||
|
}
|
||||||
|
} else if matches!(ctx.npc.profession, Some(Profession::Blacksmith)) {
|
||||||
|
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().repeat().stop_if(timeout(ctx, 5.0))))
|
||||||
|
.map(|_| {}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Generate<F, T>(F, PhantomData<T>);
|
pub struct Generate<F, T>(F, PhantomData<T>);
|
||||||
|
|
||||||
@ -763,3 +862,4 @@ fn walk_path(site: Id<WorldSite>, path: Path<Vec2<i32>>) -> impl IsTask {
|
|||||||
println!("Waited.");
|
println!("Waited.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -229,11 +229,16 @@ impl<'a> AgentData<'a> {
|
|||||||
controller.push_cancel_input(InputKind::Fly)
|
controller.push_cancel_input(InputKind::Fly)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let chase_tgt = *travel_to/*read_data.terrain
|
||||||
|
.try_find_space(travel_to.as_())
|
||||||
|
.map(|pos| pos.as_())
|
||||||
|
.unwrap_or(*travel_to)*/;
|
||||||
|
|
||||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||||
&*read_data.terrain,
|
&*read_data.terrain,
|
||||||
self.pos.0,
|
self.pos.0,
|
||||||
self.vel.0,
|
self.vel.0,
|
||||||
*travel_to,
|
chase_tgt,
|
||||||
TraversalConfig {
|
TraversalConfig {
|
||||||
min_tgt_dist: 1.25,
|
min_tgt_dist: 1.25,
|
||||||
..self.traversal_config
|
..self.traversal_config
|
||||||
|
Loading…
Reference in New Issue
Block a user