mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added npc_info, action backtraces
This commit is contained in:
parent
ac83cfc4a3
commit
2b3f0737d0
@ -311,6 +311,7 @@ pub enum ServerChatCommand {
|
|||||||
Time,
|
Time,
|
||||||
Tp,
|
Tp,
|
||||||
TpNpc,
|
TpNpc,
|
||||||
|
NpcInfo,
|
||||||
Unban,
|
Unban,
|
||||||
Version,
|
Version,
|
||||||
Waypoint,
|
Waypoint,
|
||||||
@ -682,7 +683,12 @@ impl ServerChatCommand {
|
|||||||
),
|
),
|
||||||
ServerChatCommand::TpNpc => cmd(
|
ServerChatCommand::TpNpc => cmd(
|
||||||
vec![Integer("npc index", 0, Required)],
|
vec![Integer("npc index", 0, Required)],
|
||||||
"Teleport to a npc",
|
"Teleport to an rtsim npc",
|
||||||
|
Some(Moderator),
|
||||||
|
),
|
||||||
|
ServerChatCommand::NpcInfo => cmd(
|
||||||
|
vec![Integer("npc index", 0, Required)],
|
||||||
|
"Display information about an rtsim NPC",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ServerChatCommand::Unban => cmd(
|
ServerChatCommand::Unban => cmd(
|
||||||
@ -808,6 +814,7 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::Time => "time",
|
ServerChatCommand::Time => "time",
|
||||||
ServerChatCommand::Tp => "tp",
|
ServerChatCommand::Tp => "tp",
|
||||||
ServerChatCommand::TpNpc => "tp_npc",
|
ServerChatCommand::TpNpc => "tp_npc",
|
||||||
|
ServerChatCommand::NpcInfo => "npc_info",
|
||||||
ServerChatCommand::Unban => "unban",
|
ServerChatCommand::Unban => "unban",
|
||||||
ServerChatCommand::Version => "version",
|
ServerChatCommand::Version => "version",
|
||||||
ServerChatCommand::Waypoint => "waypoint",
|
ServerChatCommand::Waypoint => "waypoint",
|
||||||
|
@ -99,7 +99,7 @@ pub enum ChunkResource {
|
|||||||
Cotton,
|
Cotton,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum Profession {
|
pub enum Profession {
|
||||||
#[serde(rename = "0")]
|
#[serde(rename = "0")]
|
||||||
Farmer,
|
Farmer,
|
||||||
|
@ -67,6 +67,10 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// Like [`Action::is_same`], but allows for dynamic dispatch.
|
/// Like [`Action::is_same`], but allows for dynamic dispatch.
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool;
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool;
|
||||||
|
|
||||||
|
/// Generate a backtrace for the action. The action should recursively push
|
||||||
|
/// all of the tasks it is currently performing.
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>);
|
||||||
|
|
||||||
/// Reset the action to its initial state such that it can be repeated.
|
/// Reset the action to its initial state such that it can be repeated.
|
||||||
fn reset(&mut self);
|
fn reset(&mut self);
|
||||||
|
|
||||||
@ -159,6 +163,21 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
{
|
{
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add debugging information to the action that will be visible when using
|
||||||
|
/// the `/npc_info` command.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// goto(npc.home).debug(|| "Going home")
|
||||||
|
/// ```
|
||||||
|
fn debug<F, T>(self, mk_info: F) -> Debug<Self, F, T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Debug(self, mk_info, PhantomData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
||||||
@ -171,6 +190,8 @@ impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) { (**self).backtrace(bt) }
|
||||||
|
|
||||||
fn reset(&mut self) { (**self).reset(); }
|
fn reset(&mut self) { (**self).reset(); }
|
||||||
|
|
||||||
// TODO: Reset closure state?
|
// TODO: Reset closure state?
|
||||||
@ -191,6 +212,14 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'stati
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
|
if let Some(action) = &self.1 {
|
||||||
|
action.backtrace(bt);
|
||||||
|
} else {
|
||||||
|
bt.push("<thinking>".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn reset(&mut self) { self.1 = None; }
|
fn reset(&mut self) { self.1 = None; }
|
||||||
|
|
||||||
// TODO: Reset closure state?
|
// TODO: Reset closure state?
|
||||||
@ -231,6 +260,8 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'stati
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {}
|
||||||
|
|
||||||
fn reset(&mut self) {}
|
fn reset(&mut self) {}
|
||||||
|
|
||||||
// TODO: Reset closure state?
|
// TODO: Reset closure state?
|
||||||
@ -266,6 +297,8 @@ impl Action<()> for Finish {
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {}
|
||||||
|
|
||||||
fn reset(&mut self) {}
|
fn reset(&mut self) {}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> { ControlFlow::Break(()) }
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> { ControlFlow::Break(()) }
|
||||||
@ -324,6 +357,14 @@ impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Actio
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
|
if let Some(prev) = &self.prev {
|
||||||
|
prev.0.backtrace(bt);
|
||||||
|
} else {
|
||||||
|
bt.push("<thinking>".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn reset(&mut self) { self.prev = None; }
|
fn reset(&mut self) { self.prev = None; }
|
||||||
|
|
||||||
// TODO: Reset `next` too?
|
// TODO: Reset `next` too?
|
||||||
@ -435,6 +476,14 @@ impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
|
if self.a0_finished {
|
||||||
|
self.a1.backtrace(bt);
|
||||||
|
} else {
|
||||||
|
self.a0.backtrace(bt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.a0.reset();
|
self.a0.reset();
|
||||||
self.a0_finished = false;
|
self.a0_finished = false;
|
||||||
@ -463,6 +512,8 @@ impl<R: Send + Sync + 'static, A: Action<R>> Action<!> for Repeat<A, R> {
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<!>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<!>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
||||||
|
|
||||||
fn reset(&mut self) { self.0.reset(); }
|
fn reset(&mut self) { self.0.reset(); }
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<!> {
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<!> {
|
||||||
@ -489,6 +540,14 @@ impl<R: Send + Sync + 'static, I: Iterator<Item = A> + Clone + Send + Sync + 'st
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
|
if let Some(action) = &self.2 {
|
||||||
|
action.backtrace(bt);
|
||||||
|
} else {
|
||||||
|
bt.push("<thinking>".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.0 = self.1.clone();
|
self.0 = self.1.clone();
|
||||||
self.2 = None;
|
self.2 = None;
|
||||||
@ -551,6 +610,8 @@ impl<A: Action<R>, F: FnMut(&mut NpcCtx) -> bool + Send + Sync + 'static, R> Act
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<Option<R>>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<Option<R>>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
||||||
|
|
||||||
fn reset(&mut self) { self.0.reset(); }
|
fn reset(&mut self) { self.0.reset(); }
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<Option<R>> {
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<Option<R>> {
|
||||||
@ -575,9 +636,38 @@ impl<A: Action<R>, F: FnMut(R) -> R1 + Send + Sync + 'static, R: Send + Sync + '
|
|||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<R1>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
||||||
|
|
||||||
fn reset(&mut self) { self.0.reset(); }
|
fn reset(&mut self) { self.0.reset(); }
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
|
||||||
self.0.tick(ctx).map_break(&mut self.1)
|
self.0.tick(ctx).map_break(&mut self.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
/// See [`Action::debug`].
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Debug<A, F, T>(A, F, PhantomData<T>);
|
||||||
|
|
||||||
|
impl<
|
||||||
|
A: Action<R>,
|
||||||
|
F: Fn() -> T + Send + Sync + 'static,
|
||||||
|
R: Send + Sync + 'static,
|
||||||
|
T: Send + Sync + std::fmt::Display + 'static,
|
||||||
|
> Action<R> for Debug<A, F, T>
|
||||||
|
{
|
||||||
|
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
|
||||||
|
|
||||||
|
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
|
bt.push((self.1)().to_string());
|
||||||
|
self.0.backtrace(bt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) { self.0.reset(); }
|
||||||
|
|
||||||
|
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { self.0.tick(ctx) }
|
||||||
|
}
|
||||||
|
@ -22,7 +22,7 @@ use std::{
|
|||||||
use vek::*;
|
use vek::*;
|
||||||
use world::{civ::Track, site::Site as WorldSite, util::RandomPerm};
|
use world::{civ::Track, site::Site as WorldSite, util::RandomPerm};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
pub enum NpcMode {
|
pub enum NpcMode {
|
||||||
/// The NPC is unloaded and is being simulated via rtsim.
|
/// The NPC is unloaded and is being simulated via rtsim.
|
||||||
#[default]
|
#[default]
|
||||||
@ -53,7 +53,7 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Brain {
|
pub struct Brain {
|
||||||
pub(crate) action: Box<dyn Action<!>>,
|
pub action: Box<dyn Action<!>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -15,6 +15,7 @@ use common::{
|
|||||||
rtsim::{Profession, SiteId},
|
rtsim::{Profession, SiteId},
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
|
time::DayPeriod,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use fxhash::FxHasher64;
|
use fxhash::FxHasher64;
|
||||||
@ -29,7 +30,7 @@ use vek::*;
|
|||||||
use world::{
|
use world::{
|
||||||
civ::{self, Track},
|
civ::{self, Track},
|
||||||
site::{Site as WorldSite, SiteKind},
|
site::{Site as WorldSite, SiteKind},
|
||||||
site2::{self, TileKind},
|
site2::{self, PlotKind, TileKind},
|
||||||
IndexRef, World,
|
IndexRef, World,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -313,7 +314,7 @@ impl Rule for NpcAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn idle() -> impl Action { just(|ctx| *ctx.controller = Controller::idle()) }
|
fn idle() -> impl Action { just(|ctx| *ctx.controller = Controller::idle()).debug(|| "idle") }
|
||||||
|
|
||||||
/// Try to walk toward a 3D position without caring for obstacles.
|
/// Try to walk toward a 3D position without caring for obstacles.
|
||||||
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
|
fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
|
||||||
@ -342,6 +343,7 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32) -> impl Action {
|
|||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < GOAL_DIST.powi(2))
|
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < GOAL_DIST.powi(2))
|
||||||
|
.debug(move || format!("goto {}, {}, {}", wpos.x, wpos.y, wpos.z))
|
||||||
.map(|_| {})
|
.map(|_| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,12 +368,14 @@ fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
|||||||
if let Some(current_site) = ctx.npc.current_site
|
if let Some(current_site) = ctx.npc.current_site
|
||||||
&& let Some(tracks) = path_towns(current_site, tgt_site, sites, ctx.world)
|
&& let Some(tracks) = path_towns(current_site, tgt_site, sites, ctx.world)
|
||||||
{
|
{
|
||||||
|
let track_count = tracks.path.len();
|
||||||
// For every track in the path we discovered between the sites...
|
// For every track in the path we discovered between the sites...
|
||||||
seq(tracks
|
seq(tracks
|
||||||
.path
|
.path
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
// ...traverse the nodes of that path.
|
// ...traverse the nodes of that path.
|
||||||
.map(|(track_id, reversed)| now(move |ctx| {
|
.map(move |(i, (track_id, reversed))| now(move |ctx| {
|
||||||
let track_len = 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.
|
// Tracks can be traversed backward (i.e: from end to beginning). Account for this.
|
||||||
seq(if reversed {
|
seq(if reversed {
|
||||||
@ -379,7 +383,8 @@ fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
|||||||
} else {
|
} else {
|
||||||
itertools::Either::Right(0..track_len)
|
itertools::Either::Right(0..track_len)
|
||||||
}
|
}
|
||||||
.map(move |node_idx| now(move |ctx| {
|
.enumerate()
|
||||||
|
.map(move |(i, node_idx)| now(move |ctx| {
|
||||||
// Find the centre of the track node's chunk
|
// Find the centre of the track node's chunk
|
||||||
let node_chunk_wpos = TerrainChunkSize::center_wpos(ctx.world
|
let node_chunk_wpos = TerrainChunkSize::center_wpos(ctx.world
|
||||||
.civs()
|
.civs()
|
||||||
@ -395,8 +400,10 @@ fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
|||||||
|
|
||||||
// Walk toward the node
|
// Walk toward the node
|
||||||
goto_2d(node_wpos.as_(), 1.0)
|
goto_2d(node_wpos.as_(), 1.0)
|
||||||
|
.debug(move || format!("traversing track node ({}/{})", i + 1, track_len))
|
||||||
})))
|
})))
|
||||||
})))
|
})
|
||||||
|
.debug(move || format!("travel via track {:?} ({}/{})", track_id, i + 1, track_count))))
|
||||||
.boxed()
|
.boxed()
|
||||||
} else if let Some(site) = sites.get(tgt_site) {
|
} else if let Some(site) = sites.get(tgt_site) {
|
||||||
// If all else fails, just walk toward the target site in a straight line
|
// If all else fails, just walk toward the target site in a straight line
|
||||||
@ -406,12 +413,43 @@ fn travel_to_site(tgt_site: SiteId) -> impl Action {
|
|||||||
finish().boxed()
|
finish().boxed()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.debug(move || format!("travel_to_site {:?}", tgt_site))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seconds
|
// Seconds
|
||||||
fn timeout(ctx: &NpcCtx, time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
||||||
let end = ctx.time.0 + time;
|
let mut timeout = None;
|
||||||
move |ctx| ctx.time.0 > end
|
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adventure() -> impl Action {
|
||||||
|
now(|ctx| {
|
||||||
|
// Choose a random site that's fairly close by
|
||||||
|
if let Some(tgt_site) = 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)
|
||||||
|
})
|
||||||
|
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
|
||||||
|
.map(|(site_id, _)| site_id)
|
||||||
|
{
|
||||||
|
// Travel to the site
|
||||||
|
travel_to_site(tgt_site)
|
||||||
|
// Stop for a few minutes
|
||||||
|
.then(villager().stop_if(timeout(60.0 * 3.0)))
|
||||||
|
.map(|_| ())
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
finish().boxed()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn villager() -> impl Action {
|
fn villager() -> impl Action {
|
||||||
@ -419,7 +457,34 @@ fn villager() -> impl Action {
|
|||||||
if let Some(home) = ctx.npc.home {
|
if let Some(home) = ctx.npc.home {
|
||||||
if ctx.npc.current_site != Some(home) {
|
if ctx.npc.current_site != Some(home) {
|
||||||
// Travel home if we're not there already
|
// Travel home if we're not there already
|
||||||
important(travel_to_site(home))
|
urgent(travel_to_site(home).debug(move || format!("travel home")))
|
||||||
|
} else if DayPeriod::from(ctx.time_of_day.0).is_dark() {
|
||||||
|
important(now(move |ctx| {
|
||||||
|
if let Some(house_wpos) = ctx
|
||||||
|
.state
|
||||||
|
.data()
|
||||||
|
.sites
|
||||||
|
.get(home)
|
||||||
|
.and_then(|home| ctx.index.sites.get(home.world_site?).site2())
|
||||||
|
.and_then(|site2| {
|
||||||
|
// Find a house
|
||||||
|
let house = site2
|
||||||
|
.plots()
|
||||||
|
.filter(|p| matches!(p.kind(), PlotKind::House(_)))
|
||||||
|
.choose(&mut thread_rng())?;
|
||||||
|
Some(site2.tile_center_wpos(house.root_tile()).as_())
|
||||||
|
})
|
||||||
|
{
|
||||||
|
goto_2d(house_wpos, 0.5)
|
||||||
|
.debug(|| "walk to house")
|
||||||
|
.then(idle().repeat().debug(|| "wait in house"))
|
||||||
|
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
||||||
|
.map(|_| ())
|
||||||
|
.boxed()
|
||||||
|
} else {
|
||||||
|
finish().boxed()
|
||||||
|
}
|
||||||
|
}))
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
ctx.npc.profession,
|
ctx.npc.profession,
|
||||||
Some(Profession::Merchant | Profession::Blacksmith)
|
Some(Profession::Merchant | Profession::Blacksmith)
|
||||||
@ -440,11 +505,13 @@ fn villager() -> impl Action {
|
|||||||
{
|
{
|
||||||
// Walk to the plaza...
|
// Walk to the plaza...
|
||||||
goto_2d(plaza_wpos, 0.5)
|
goto_2d(plaza_wpos, 0.5)
|
||||||
|
.debug(|| "walk to plaza")
|
||||||
// ...then wait for some time before moving on
|
// ...then wait for some time before moving on
|
||||||
.then(now(|ctx| {
|
.then({
|
||||||
let wait_time = thread_rng().gen_range(10.0..30.0);
|
let wait_time = thread_rng().gen_range(10.0..30.0);
|
||||||
idle().repeat().stop_if(timeout(ctx, wait_time))
|
idle().repeat().stop_if(timeout(wait_time))
|
||||||
}))
|
.debug(|| "wait at plaza")
|
||||||
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.boxed()
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
@ -459,31 +526,13 @@ fn villager() -> impl Action {
|
|||||||
casual(finish()) // Nothing to do if we're homeless!
|
casual(finish()) // Nothing to do if we're homeless!
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.debug(move || format!("villager"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn think() -> impl Action {
|
fn think() -> impl Action {
|
||||||
choose(|ctx| {
|
choose(|ctx| {
|
||||||
if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) {
|
if matches!(ctx.npc.profession, Some(Profession::Adventurer(_))) {
|
||||||
// Choose a random site that's fairly close by
|
casual(adventure())
|
||||||
if let Some(tgt_site) = 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)
|
|
||||||
})
|
|
||||||
.min_by_key(|(_, site)| site.wpos.as_().distance(ctx.npc.wpos.xy()) as i32)
|
|
||||||
.map(|(site_id, _)| site_id)
|
|
||||||
{
|
|
||||||
casual(travel_to_site(tgt_site))
|
|
||||||
} else {
|
|
||||||
casual(finish())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
casual(villager())
|
casual(villager())
|
||||||
}
|
}
|
||||||
|
@ -185,6 +185,7 @@ fn do_command(
|
|||||||
ServerChatCommand::Time => handle_time,
|
ServerChatCommand::Time => handle_time,
|
||||||
ServerChatCommand::Tp => handle_tp,
|
ServerChatCommand::Tp => handle_tp,
|
||||||
ServerChatCommand::TpNpc => handle_tp_npc,
|
ServerChatCommand::TpNpc => handle_tp_npc,
|
||||||
|
ServerChatCommand::NpcInfo => handle_npc_info,
|
||||||
ServerChatCommand::Unban => handle_unban,
|
ServerChatCommand::Unban => handle_unban,
|
||||||
ServerChatCommand::Version => handle_version,
|
ServerChatCommand::Version => handle_version,
|
||||||
ServerChatCommand::Waypoint => handle_waypoint,
|
ServerChatCommand::Waypoint => handle_waypoint,
|
||||||
@ -1212,6 +1213,53 @@ fn handle_tp_npc(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_npc_info(
|
||||||
|
server: &mut Server,
|
||||||
|
client: EcsEntity,
|
||||||
|
target: EcsEntity,
|
||||||
|
args: Vec<String>,
|
||||||
|
action: &ServerChatCommand,
|
||||||
|
) -> CmdResult<()> {
|
||||||
|
use crate::rtsim2::RtSim;
|
||||||
|
if let Some(id) = parse_cmd_args!(args, u32) {
|
||||||
|
// TODO: Take some other identifier than an integer to this command.
|
||||||
|
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
||||||
|
let data = rtsim.state().data();
|
||||||
|
let npc = data
|
||||||
|
.npcs
|
||||||
|
.values()
|
||||||
|
.nth(id as usize)
|
||||||
|
.ok_or_else(|| format!("No NPC has index {}", id))?;
|
||||||
|
|
||||||
|
let mut info = String::new();
|
||||||
|
|
||||||
|
let _ = writeln!(&mut info, "-- General Information --");
|
||||||
|
let _ = writeln!(&mut info, "Seed: {}", npc.seed);
|
||||||
|
let _ = writeln!(&mut info, "Profession: {:?}", npc.profession);
|
||||||
|
let _ = writeln!(&mut info, "Home: {:?}", npc.home);
|
||||||
|
let _ = writeln!(&mut info, "Current mode: {:?}", npc.mode);
|
||||||
|
let _ = writeln!(&mut info, "-- Action State --");
|
||||||
|
if let Some(brain) = &npc.brain {
|
||||||
|
let mut bt = Vec::new();
|
||||||
|
brain.action.backtrace(&mut bt);
|
||||||
|
for (i, action) in bt.into_iter().enumerate() {
|
||||||
|
let _ = writeln!(&mut info, "[{}] {}", i, action);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = writeln!(&mut info, "<NPC has no brain>");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ServerGeneral::server_msg(ChatType::CommandInfo, info),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(action.help_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_spawn(
|
fn handle_spawn(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
|
Loading…
Reference in New Issue
Block a user