mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added rtsim_npc, made herbalists gather ingredients
This commit is contained in:
parent
1e70ccfb8d
commit
b402e450cf
@ -310,10 +310,11 @@ pub enum ServerChatCommand {
|
|||||||
Tell,
|
Tell,
|
||||||
Time,
|
Time,
|
||||||
Tp,
|
Tp,
|
||||||
RtsimTp,
|
|
||||||
RtsimInfo,
|
|
||||||
RtsimPurge,
|
|
||||||
RtsimChunk,
|
RtsimChunk,
|
||||||
|
RtsimInfo,
|
||||||
|
RtsimNpc,
|
||||||
|
RtsimPurge,
|
||||||
|
RtsimTp,
|
||||||
Unban,
|
Unban,
|
||||||
Version,
|
Version,
|
||||||
Waypoint,
|
Waypoint,
|
||||||
@ -693,6 +694,11 @@ impl ServerChatCommand {
|
|||||||
"Display information about an rtsim NPC",
|
"Display information about an rtsim NPC",
|
||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
|
ServerChatCommand::RtsimNpc => cmd(
|
||||||
|
vec![Any("query", Required)],
|
||||||
|
"List rtsim NPCs that fit a given query (e.g: simulated,merchant)",
|
||||||
|
Some(Moderator),
|
||||||
|
),
|
||||||
ServerChatCommand::RtsimPurge => cmd(
|
ServerChatCommand::RtsimPurge => cmd(
|
||||||
vec![Boolean(
|
vec![Boolean(
|
||||||
"whether purging of rtsim data should occur on next startup",
|
"whether purging of rtsim data should occur on next startup",
|
||||||
@ -836,6 +842,7 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::Tp => "tp",
|
ServerChatCommand::Tp => "tp",
|
||||||
ServerChatCommand::RtsimTp => "rtsim_tp",
|
ServerChatCommand::RtsimTp => "rtsim_tp",
|
||||||
ServerChatCommand::RtsimInfo => "rtsim_info",
|
ServerChatCommand::RtsimInfo => "rtsim_info",
|
||||||
|
ServerChatCommand::RtsimNpc => "rtsim_npc",
|
||||||
ServerChatCommand::RtsimPurge => "rtsim_purge",
|
ServerChatCommand::RtsimPurge => "rtsim_purge",
|
||||||
ServerChatCommand::RtsimChunk => "rtsim_chunk",
|
ServerChatCommand::RtsimChunk => "rtsim_chunk",
|
||||||
ServerChatCommand::Unban => "unban",
|
ServerChatCommand::Unban => "unban",
|
||||||
|
@ -192,43 +192,30 @@ impl Default for Personality {
|
|||||||
/// into the game as a physical entity or not). Agent code should attempt to act
|
/// into the game as a physical entity or not). Agent code should attempt to act
|
||||||
/// upon its instructions where reasonable although deviations for various
|
/// upon its instructions where reasonable although deviations for various
|
||||||
/// reasons (obstacle avoidance, counter-attacking, etc.) are expected.
|
/// reasons (obstacle avoidance, counter-attacking, etc.) are expected.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct RtSimController {
|
pub struct RtSimController {
|
||||||
/// When this field is `Some(..)`, the agent should attempt to make progress
|
pub activity: Option<NpcActivity>,
|
||||||
/// toward the given location, accounting for obstacles and other
|
pub actions: VecDeque<NpcAction>,
|
||||||
/// high-priority situations like being attacked.
|
|
||||||
pub travel_to: Option<Vec3<f32>>,
|
|
||||||
pub personality: Personality,
|
pub personality: Personality,
|
||||||
pub heading_to: Option<String>,
|
pub heading_to: Option<String>,
|
||||||
/// Proportion of full speed to move
|
|
||||||
pub speed_factor: f32,
|
|
||||||
pub actions: VecDeque<NpcAction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RtSimController {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
travel_to: None,
|
|
||||||
personality: Personality::default(),
|
|
||||||
heading_to: None,
|
|
||||||
speed_factor: 1.0,
|
|
||||||
actions: VecDeque::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtSimController {
|
impl RtSimController {
|
||||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
travel_to: Some(pos),
|
activity: Some(NpcActivity::Goto(pos, 0.5)),
|
||||||
personality: Personality::default(),
|
..Default::default()
|
||||||
heading_to: None,
|
|
||||||
speed_factor: 0.5,
|
|
||||||
actions: VecDeque::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum NpcActivity {
|
||||||
|
/// (travel_to, speed_factor)
|
||||||
|
Goto(Vec3<f32>, f32),
|
||||||
|
Gather(&'static [ChunkResource]),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum NpcAction {
|
pub enum NpcAction {
|
||||||
Greet(Actor),
|
Greet(Actor),
|
||||||
|
@ -86,6 +86,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// // Walk toward an enemy NPC and, once done, attack the enemy NPC
|
/// // Walk toward an enemy NPC and, once done, attack the enemy NPC
|
||||||
/// goto(enemy_npc).then(attack(enemy_npc))
|
/// goto(enemy_npc).then(attack(enemy_npc))
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
fn then<A1: Action<R1>, R1>(self, other: A1) -> Then<Self, A1, R>
|
fn then<A1: Action<R1>, R1>(self, other: A1) -> Then<Self, A1, R>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -106,6 +107,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// // Endlessly collect flax from the environment
|
/// // Endlessly collect flax from the environment
|
||||||
/// find_and_collect(ChunkResource::Flax).repeat()
|
/// find_and_collect(ChunkResource::Flax).repeat()
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
fn repeat<R1>(self) -> Repeat<Self, R1>
|
fn repeat<R1>(self) -> Repeat<Self, R1>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -121,6 +123,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// // Keep going on adventures until your 111th birthday
|
/// // Keep going on adventures until your 111th birthday
|
||||||
/// go_on_an_adventure().repeat().stop_if(|ctx| ctx.npc.age > 111.0)
|
/// go_on_an_adventure().repeat().stop_if(|ctx| ctx.npc.age > 111.0)
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
fn stop_if<F: FnMut(&mut NpcCtx) -> bool>(self, f: F) -> StopIf<Self, F>
|
fn stop_if<F: FnMut(&mut NpcCtx) -> bool>(self, f: F) -> StopIf<Self, F>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -129,6 +132,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Map the completion value of this action to something else.
|
/// Map the completion value of this action to something else.
|
||||||
|
#[must_use]
|
||||||
fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R>
|
fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -157,6 +161,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// go_on_an_adventure().boxed()
|
/// go_on_an_adventure().boxed()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
fn boxed(self) -> Box<dyn Action<R>>
|
fn boxed(self) -> Box<dyn Action<R>>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -172,6 +177,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// goto(npc.home).debug(|| "Going home")
|
/// goto(npc.home).debug(|| "Going home")
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
fn debug<F, T>(self, mk_info: F) -> Debug<Self, F, T>
|
fn debug<F, T>(self, mk_info: F) -> Debug<Self, F, T>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@ -412,6 +418,7 @@ impl Action<()> for Finish {
|
|||||||
/// }
|
/// }
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
pub fn finish() -> Finish { Finish }
|
pub fn finish() -> Finish { Finish }
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
@ -425,12 +432,15 @@ pub const CASUAL: Priority = 2;
|
|||||||
pub struct Node<R>(Box<dyn Action<R>>, Priority);
|
pub struct Node<R>(Box<dyn Action<R>>, Priority);
|
||||||
|
|
||||||
/// Perform an action with [`URGENT`] priority (see [`choose`]).
|
/// Perform an action with [`URGENT`] priority (see [`choose`]).
|
||||||
|
#[must_use]
|
||||||
pub fn urgent<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), URGENT) }
|
pub fn urgent<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), URGENT) }
|
||||||
|
|
||||||
/// Perform an action with [`IMPORTANT`] priority (see [`choose`]).
|
/// Perform an action with [`IMPORTANT`] priority (see [`choose`]).
|
||||||
|
#[must_use]
|
||||||
pub fn important<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), IMPORTANT) }
|
pub fn important<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), IMPORTANT) }
|
||||||
|
|
||||||
/// Perform an action with [`CASUAL`] priority (see [`choose`]).
|
/// Perform an action with [`CASUAL`] priority (see [`choose`]).
|
||||||
|
#[must_use]
|
||||||
pub fn casual<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), CASUAL) }
|
pub fn casual<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), CASUAL) }
|
||||||
|
|
||||||
/// See [`choose`] and [`watch`].
|
/// See [`choose`] and [`watch`].
|
||||||
@ -501,6 +511,7 @@ impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Actio
|
|||||||
/// }
|
/// }
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
pub fn choose<R: 'static, F>(f: F) -> impl Action<R>
|
pub fn choose<R: 'static, F>(f: F) -> impl Action<R>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
|
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
|
||||||
@ -535,6 +546,7 @@ where
|
|||||||
/// }
|
/// }
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
pub fn watch<R: 'static, F>(f: F) -> impl Action<R>
|
pub fn watch<R: 'static, F>(f: F) -> impl Action<R>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
|
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
|
||||||
@ -679,6 +691,7 @@ impl<R: Send + Sync + 'static, I: Iterator<Item = A> + Clone + Send + Sync + 'st
|
|||||||
/// .into_iter()
|
/// .into_iter()
|
||||||
/// .map(|enemy| attack(enemy)))
|
/// .map(|enemy| attack(enemy)))
|
||||||
/// ```
|
/// ```
|
||||||
|
#[must_use]
|
||||||
pub fn seq<I, A, R>(iter: I) -> Sequence<I, A, R>
|
pub fn seq<I, A, R>(iter: I) -> Sequence<I, A, R>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = A> + Clone,
|
I: Iterator<Item = A> + Clone,
|
||||||
|
@ -3,7 +3,9 @@ pub use common::rtsim::{NpcId, Profession};
|
|||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{Actor, FactionId, NpcAction, Personality, SiteId, VehicleId},
|
rtsim::{
|
||||||
|
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, SiteId, VehicleId,
|
||||||
|
},
|
||||||
store::Id,
|
store::Id,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
@ -46,15 +48,18 @@ pub struct PathingMemory {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
pub actions: Vec<NpcAction>,
|
pub actions: Vec<NpcAction>,
|
||||||
/// (wpos, speed_factor)
|
pub activity: Option<NpcActivity>,
|
||||||
pub goto: Option<(Vec3<f32>, f32)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub fn do_idle(&mut self) { self.goto = None; }
|
pub fn do_idle(&mut self) { self.activity = None; }
|
||||||
|
|
||||||
pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
|
pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
|
||||||
self.goto = Some((wpos, speed_factor));
|
self.activity = Some(NpcActivity::Goto(wpos, speed_factor));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_gather(&mut self, resources: &'static [ChunkResource]) {
|
||||||
|
self.activity = Some(NpcActivity::Gather(resources));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_greet(&mut self, actor: Actor) { self.actions.push(NpcAction::Greet(actor)); }
|
pub fn do_greet(&mut self, actor: Actor) { self.actions.push(NpcAction::Greet(actor)); }
|
||||||
|
@ -12,7 +12,8 @@ use crate::{
|
|||||||
use common::{
|
use common::{
|
||||||
astar::{Astar, PathResult},
|
astar::{Astar, PathResult},
|
||||||
path::Path,
|
path::Path,
|
||||||
rtsim::{Profession, SiteId},
|
rtsim::{ChunkResource, Profession, SiteId},
|
||||||
|
spiral::Spiral2d,
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::{SiteKindMeta, TerrainChunkSize},
|
terrain::{SiteKindMeta, TerrainChunkSize},
|
||||||
time::DayPeriod,
|
time::DayPeriod,
|
||||||
@ -514,8 +515,22 @@ fn adventure() -> impl Action {
|
|||||||
.debug(move || "adventure")
|
.debug(move || "adventure")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gather_ingredients() -> impl Action {
|
||||||
|
just(|ctx| {
|
||||||
|
ctx.controller.do_gather(
|
||||||
|
&[
|
||||||
|
ChunkResource::Fruit,
|
||||||
|
ChunkResource::Mushroom,
|
||||||
|
ChunkResource::Plant,
|
||||||
|
][..],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.debug(|| "gather ingredients")
|
||||||
|
}
|
||||||
|
|
||||||
fn villager(visiting_site: SiteId) -> impl Action {
|
fn villager(visiting_site: SiteId) -> impl Action {
|
||||||
choose(move |ctx| {
|
choose(move |ctx| {
|
||||||
|
/*
|
||||||
if ctx
|
if ctx
|
||||||
.state
|
.state
|
||||||
.data()
|
.data()
|
||||||
@ -523,23 +538,24 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.get(visiting_site)
|
.get(visiting_site)
|
||||||
.map_or(true, |s| s.world_site.is_none())
|
.map_or(true, |s| s.world_site.is_none())
|
||||||
{
|
{
|
||||||
casual(
|
return casual(idle()
|
||||||
idle().debug(|| "idling (visiting site does not exist, perhaps it's stale data?)"),
|
.debug(|| "idling (visiting site does not exist, perhaps it's stale data?)"));
|
||||||
)
|
|
||||||
} else if ctx.npc.current_site != Some(visiting_site) {
|
} else if ctx.npc.current_site != Some(visiting_site) {
|
||||||
let npc_home = ctx.npc.home;
|
let npc_home = ctx.npc.home;
|
||||||
// Travel to the site we're supposed to be in
|
// Travel to the site we're supposed to be in
|
||||||
urgent(travel_to_site(visiting_site).debug(move || {
|
return urgent(travel_to_site(visiting_site).debug(move || {
|
||||||
if npc_home == Some(visiting_site) {
|
if npc_home == Some(visiting_site) {
|
||||||
"travel home".to_string()
|
"travel home".to_string()
|
||||||
} else {
|
} else {
|
||||||
"travel to visiting site".to_string()
|
"travel to visiting site".to_string()
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
} else if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
} else
|
||||||
|
*/
|
||||||
|
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
||||||
&& !matches!(ctx.npc.profession, Some(Profession::Guard))
|
&& !matches!(ctx.npc.profession, Some(Profession::Guard))
|
||||||
{
|
{
|
||||||
important(
|
return important(
|
||||||
now(move |ctx| {
|
now(move |ctx| {
|
||||||
if let Some(house_wpos) = ctx
|
if let Some(house_wpos) = ctx
|
||||||
.state
|
.state
|
||||||
@ -567,8 +583,34 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.debug(|| "find somewhere to sleep"),
|
.debug(|| "find somewhere to sleep"),
|
||||||
)
|
);
|
||||||
} else {
|
// Villagers with roles should perform those roles
|
||||||
|
} else if matches!(ctx.npc.profession, Some(Profession::Herbalist)) {
|
||||||
|
let chunk_pos = ctx.npc.wpos.xy().as_() / TerrainChunkSize::RECT_SIZE.as_();
|
||||||
|
if let Some(tree_chunk) = Spiral2d::new()
|
||||||
|
.skip(thread_rng().gen_range(1..=8))
|
||||||
|
.take(49)
|
||||||
|
.map(|rpos| chunk_pos + rpos)
|
||||||
|
.find(|cpos| {
|
||||||
|
ctx.world
|
||||||
|
.sim()
|
||||||
|
.get(*cpos)
|
||||||
|
.map_or(false, |c| c.tree_density > 0.75)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return important(
|
||||||
|
travel_to_point(TerrainChunkSize::center_wpos(tree_chunk).as_())
|
||||||
|
.debug(|| "walk to forest")
|
||||||
|
.then({
|
||||||
|
let wait_time = thread_rng().gen_range(10.0..30.0);
|
||||||
|
gather_ingredients().repeat().stop_if(timeout(wait_time))
|
||||||
|
})
|
||||||
|
.map(|_| ()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing else needs doing, walk between plazas and socialize
|
||||||
casual(now(move |ctx| {
|
casual(now(move |ctx| {
|
||||||
// Choose a plaza in the site we're visiting to walk to
|
// Choose a plaza in the site we're visiting to walk to
|
||||||
if let Some(plaza_wpos) = ctx
|
if let Some(plaza_wpos) = ctx
|
||||||
@ -587,7 +629,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.debug(|| "walk to plaza")
|
.debug(|| "walk to plaza")
|
||||||
// ...then wait for some time before moving on
|
// ...then wait for some time before moving on
|
||||||
.then({
|
.then({
|
||||||
let wait_time = thread_rng().gen_range(10.0..30.0);
|
let wait_time = thread_rng().gen_range(30.0..90.0);
|
||||||
socialize().repeat().stop_if(timeout(wait_time))
|
socialize().repeat().stop_if(timeout(wait_time))
|
||||||
.debug(|| "wait at plaza")
|
.debug(|| "wait at plaza")
|
||||||
})
|
})
|
||||||
@ -598,7 +640,6 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
finish().boxed()
|
finish().boxed()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.debug(move || format!("villager at site {:?}", visiting_site))
|
.debug(move || format!("villager at site {:?}", visiting_site))
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{Actor, NpcAction, Personality},
|
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
@ -179,13 +179,14 @@ impl Rule for SimulateNpcs {
|
|||||||
|
|
||||||
// Simulate the NPC's movement and interactions
|
// Simulate the NPC's movement and interactions
|
||||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
if matches!(npc.mode, SimulationMode::Simulated) {
|
||||||
// Move NPCs if they have a target destination
|
|
||||||
if let Some((target, speed_factor)) = npc.controller.goto {
|
|
||||||
// Simulate NPC movement when riding
|
// Simulate NPC movement when riding
|
||||||
if let Some(riding) = &npc.riding {
|
if let Some(riding) = &npc.riding {
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
||||||
|
match npc.controller.activity {
|
||||||
// If steering, the NPC controls the vehicle's motion
|
// If steering, the NPC controls the vehicle's motion
|
||||||
if riding.steering {
|
Some(NpcActivity::Goto(target, speed_factor))
|
||||||
|
if riding.steering =>
|
||||||
|
{
|
||||||
let diff = target.xy() - vehicle.wpos.xy();
|
let diff = target.xy() - vehicle.wpos.xy();
|
||||||
let dist2 = diff.magnitude_squared();
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
@ -243,6 +244,10 @@ impl Rule for SimulateNpcs {
|
|||||||
vehicle.wpos = wpos;
|
vehicle.wpos = wpos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// When riding, other actions are disabled
|
||||||
|
Some(NpcActivity::Goto(_, _) | NpcActivity::Gather(_)) => {},
|
||||||
|
None => {},
|
||||||
}
|
}
|
||||||
npc.wpos = vehicle.wpos;
|
npc.wpos = vehicle.wpos;
|
||||||
} else {
|
} else {
|
||||||
@ -251,16 +256,27 @@ impl Rule for SimulateNpcs {
|
|||||||
}
|
}
|
||||||
// If not riding, we assume they're just walking
|
// If not riding, we assume they're just walking
|
||||||
} else {
|
} else {
|
||||||
|
match npc.controller.activity {
|
||||||
|
// Move NPCs if they have a target destination
|
||||||
|
Some(NpcActivity::Goto(target, speed_factor)) => {
|
||||||
let diff = target.xy() - npc.wpos.xy();
|
let diff = target.xy() - npc.wpos.xy();
|
||||||
let dist2 = diff.magnitude_squared();
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
if dist2 > 0.5f32.powi(2) {
|
||||||
npc.wpos += (diff
|
npc.wpos += (diff
|
||||||
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
* (npc.body.max_speed_approx()
|
||||||
|
* speed_factor
|
||||||
|
* ctx.event.dt
|
||||||
/ dist2.sqrt())
|
/ dist2.sqrt())
|
||||||
.min(1.0))
|
.min(1.0))
|
||||||
.with_z(0.0);
|
.with_z(0.0);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Some(NpcActivity::Gather(_)) => {
|
||||||
|
// TODO: Maybe they should walk around randomly
|
||||||
|
// when gathering resources?
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ use common::{
|
|||||||
effect::{BuffEffect, Effect},
|
effect::{BuffEffect, Effect},
|
||||||
event::{Emitter, ServerEvent},
|
event::{Emitter, ServerEvent},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
|
rtsim::NpcActivity,
|
||||||
states::basic_beam,
|
states::basic_beam,
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
time::DayPeriod,
|
time::DayPeriod,
|
||||||
@ -212,7 +213,8 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
agent.action_state.timers[ActionTimers::TimerIdle as usize] = 0.0;
|
agent.action_state.timers[ActionTimers::TimerIdle as usize] = 0.0;
|
||||||
if let Some(travel_to) = &agent.rtsim_controller.travel_to {
|
match agent.rtsim_controller.activity {
|
||||||
|
Some(NpcActivity::Goto(travel_to, speed_factor)) => {
|
||||||
// If it has an rtsim destination and can fly, then it should.
|
// If it has an rtsim destination and can fly, then it should.
|
||||||
// If it is flying and bumps something above it, then it should move down.
|
// If it is flying and bumps something above it, then it should move down.
|
||||||
if self.traversal_config.can_fly
|
if self.traversal_config.can_fly
|
||||||
@ -233,7 +235,7 @@ impl<'a> AgentData<'a> {
|
|||||||
.terrain
|
.terrain
|
||||||
.try_find_space(travel_to.as_())
|
.try_find_space(travel_to.as_())
|
||||||
.map(|pos| pos.as_())
|
.map(|pos| pos.as_())
|
||||||
.unwrap_or(*travel_to);
|
.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,
|
||||||
@ -247,7 +249,7 @@ impl<'a> AgentData<'a> {
|
|||||||
) {
|
) {
|
||||||
controller.inputs.move_dir =
|
controller.inputs.move_dir =
|
||||||
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
|
||||||
* speed.min(agent.rtsim_controller.speed_factor);
|
* speed.min(speed_factor);
|
||||||
self.jump_if(bearing.z > 1.5 || self.traversal_config.can_fly, controller);
|
self.jump_if(bearing.z > 1.5 || self.traversal_config.can_fly, controller);
|
||||||
controller.inputs.climb = Some(comp::Climb::Up);
|
controller.inputs.climb = Some(comp::Climb::Up);
|
||||||
//.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid().is_some());
|
//.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid().is_some());
|
||||||
@ -260,7 +262,8 @@ impl<'a> AgentData<'a> {
|
|||||||
.ray(
|
.ray(
|
||||||
self.pos.0 + Vec3::unit_z(),
|
self.pos.0 + Vec3::unit_z(),
|
||||||
self.pos.0
|
self.pos.0
|
||||||
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 80.0
|
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y)
|
||||||
|
* 80.0
|
||||||
+ Vec3::unit_z(),
|
+ Vec3::unit_z(),
|
||||||
)
|
)
|
||||||
.until(Block::is_solid)
|
.until(Block::is_solid)
|
||||||
@ -291,8 +294,8 @@ impl<'a> AgentData<'a> {
|
|||||||
for i in 0..=NUM_RAYS {
|
for i in 0..=NUM_RAYS {
|
||||||
let magnitude = self.body.map_or(20.0, |b| b.flying_height());
|
let magnitude = self.body.map_or(20.0, |b| b.flying_height());
|
||||||
// Lerp between a line straight ahead and straight down to detect a
|
// Lerp between a line straight ahead and straight down to detect a
|
||||||
// wedge of obstacles we might fly into (inclusive so that both vectors
|
// wedge of obstacles we might fly into (inclusive so that both
|
||||||
// are sampled)
|
// vectors are sampled)
|
||||||
if let Some(dir) = Lerp::lerp(
|
if let Some(dir) = Lerp::lerp(
|
||||||
-Vec3::unit_z(),
|
-Vec3::unit_z(),
|
||||||
Vec3::new(bearing.x, bearing.y, 0.0),
|
Vec3::new(bearing.x, bearing.y, 0.0),
|
||||||
@ -334,7 +337,12 @@ impl<'a> AgentData<'a> {
|
|||||||
controller.push_action(ControlAction::Unwield);
|
controller.push_action(ControlAction::Unwield);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
|
Some(NpcActivity::Gather(resources)) => {
|
||||||
|
// TODO: Implement
|
||||||
|
controller.push_action(ControlAction::Dance);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
// Bats should fly
|
// Bats should fly
|
||||||
// Use a proportional controller as the bouncing effect mimics bat flight
|
// Use a proportional controller as the bouncing effect mimics bat flight
|
||||||
if self.traversal_config.can_fly
|
if self.traversal_config.can_fly
|
||||||
@ -447,6 +455,7 @@ impl<'a> AgentData<'a> {
|
|||||||
if rng.gen::<f32>() < 0.0035 {
|
if rng.gen::<f32>() < 0.0035 {
|
||||||
controller.push_action(ControlAction::Sit);
|
controller.push_action(ControlAction::Sit);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +185,7 @@ fn do_command(
|
|||||||
ServerChatCommand::Tp => handle_tp,
|
ServerChatCommand::Tp => handle_tp,
|
||||||
ServerChatCommand::RtsimTp => handle_rtsim_tp,
|
ServerChatCommand::RtsimTp => handle_rtsim_tp,
|
||||||
ServerChatCommand::RtsimInfo => handle_rtsim_info,
|
ServerChatCommand::RtsimInfo => handle_rtsim_info,
|
||||||
|
ServerChatCommand::RtsimNpc => handle_rtsim_npc,
|
||||||
ServerChatCommand::RtsimPurge => handle_rtsim_purge,
|
ServerChatCommand::RtsimPurge => handle_rtsim_purge,
|
||||||
ServerChatCommand::RtsimChunk => handle_rtsim_chunk,
|
ServerChatCommand::RtsimChunk => handle_rtsim_chunk,
|
||||||
ServerChatCommand::Unban => handle_unban,
|
ServerChatCommand::Unban => handle_unban,
|
||||||
@ -1263,6 +1264,61 @@ fn handle_rtsim_info(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_rtsim_npc(
|
||||||
|
server: &mut Server,
|
||||||
|
client: EcsEntity,
|
||||||
|
_target: EcsEntity,
|
||||||
|
args: Vec<String>,
|
||||||
|
action: &ServerChatCommand,
|
||||||
|
) -> CmdResult<()> {
|
||||||
|
use crate::rtsim::RtSim;
|
||||||
|
if let Some(query) = parse_cmd_args!(args, String) {
|
||||||
|
let terms = query
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.trim().to_lowercase())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
||||||
|
let data = rtsim.state().data();
|
||||||
|
let npcs = data
|
||||||
|
.npcs
|
||||||
|
.values()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(idx, npc)| {
|
||||||
|
let tags = [
|
||||||
|
npc.profession
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| format!("{:?}", p))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
format!("{:?}", npc.mode),
|
||||||
|
format!("{}", idx),
|
||||||
|
];
|
||||||
|
terms
|
||||||
|
.iter()
|
||||||
|
.all(|term| tags.iter().any(|tag| term.eq_ignore_ascii_case(tag.trim())))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut info = String::new();
|
||||||
|
|
||||||
|
let _ = writeln!(&mut info, "-- NPCs matching [{}] --", terms.join(", "));
|
||||||
|
for (idx, _) in &npcs {
|
||||||
|
let _ = write!(&mut info, "{}, ", idx);
|
||||||
|
}
|
||||||
|
let _ = writeln!(&mut info, "");
|
||||||
|
let _ = writeln!(&mut info, "Matched {} NPCs.", npcs.len());
|
||||||
|
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ServerGeneral::server_msg(ChatType::CommandInfo, info),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(action.help_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_rtsim_purge(
|
fn handle_rtsim_purge(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
|
@ -366,13 +366,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Update entity state
|
// Update entity state
|
||||||
if let Some(agent) = agent {
|
if let Some(agent) = agent {
|
||||||
agent.rtsim_controller.personality = npc.personality;
|
agent.rtsim_controller.personality = npc.personality;
|
||||||
if let Some((wpos, speed_factor)) = npc.controller.goto {
|
agent.rtsim_controller.activity = npc.controller.activity;
|
||||||
agent.rtsim_controller.travel_to = Some(wpos);
|
|
||||||
agent.rtsim_controller.speed_factor = speed_factor;
|
|
||||||
} else {
|
|
||||||
agent.rtsim_controller.travel_to = None;
|
|
||||||
agent.rtsim_controller.speed_factor = 1.0;
|
|
||||||
}
|
|
||||||
agent
|
agent
|
||||||
.rtsim_controller
|
.rtsim_controller
|
||||||
.actions
|
.actions
|
||||||
|
@ -495,6 +495,7 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
|
|||||||
bdata
|
bdata
|
||||||
.agent_data
|
.agent_data
|
||||||
.chat_npc("npc-speech-villager", &mut bdata.event_emitter);
|
.chat_npc("npc-speech-villager", &mut bdata.event_emitter);
|
||||||
|
// Start a timer so that they eventually stop interacting
|
||||||
bdata
|
bdata
|
||||||
.agent
|
.agent
|
||||||
.timer
|
.timer
|
||||||
|
Loading…
Reference in New Issue
Block a user