mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Various rtsim related stuff
This commit is contained in:
parent
272d57b4fa
commit
a884e0e058
@ -87,6 +87,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Underwater fall damage
|
- Underwater fall damage
|
||||||
- The scale component now behaves properly
|
- The scale component now behaves properly
|
||||||
- Multiple model support for dropped items (orichalcum armor)
|
- Multiple model support for dropped items (orichalcum armor)
|
||||||
|
- Made rtsim monsters not go into too deep water, and certainly not outside the map.
|
||||||
|
- Fixed bug where npcs would be dismounted from vehicles if loaded/unloaded in a certain order.
|
||||||
|
|
||||||
## [0.14.0] - 2023-01-07
|
## [0.14.0] - 2023-01-07
|
||||||
|
|
||||||
|
@ -269,6 +269,10 @@ npc-speech-witness_death =
|
|||||||
.a0 = No!
|
.a0 = No!
|
||||||
.a1 = This is terrible!
|
.a1 = This is terrible!
|
||||||
.a2 = Oh my goodness!
|
.a2 = Oh my goodness!
|
||||||
|
npc-speech-welcome-aboard =
|
||||||
|
.a0 = Welcome aboard!
|
||||||
|
.a1 = Can I see your ticket... just kidding it's free!
|
||||||
|
.a2 = Have a nice ride!
|
||||||
npc-speech-dir_north = north
|
npc-speech-dir_north = north
|
||||||
npc-speech-dir_north_east = north-east
|
npc-speech-dir_north_east = north-east
|
||||||
npc-speech-dir_east = east
|
npc-speech-dir_east = east
|
||||||
|
@ -2233,7 +2233,7 @@ impl Client {
|
|||||||
return Err(Error::Other("Failed to find entity from uid.".into()));
|
return Err(Error::Other("Failed to find entity from uid.".into()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ServerGeneral::TimeOfDay(time_of_day, calendar, new_time) => {
|
ServerGeneral::TimeOfDay(time_of_day, calendar, new_time, time_scale) => {
|
||||||
self.target_time_of_day = Some(time_of_day);
|
self.target_time_of_day = Some(time_of_day);
|
||||||
*self.state.ecs_mut().write_resource() = calendar;
|
*self.state.ecs_mut().write_resource() = calendar;
|
||||||
let mut time = self.state.ecs_mut().write_resource::<Time>();
|
let mut time = self.state.ecs_mut().write_resource::<Time>();
|
||||||
@ -2249,7 +2249,7 @@ impl Client {
|
|||||||
} else {
|
} else {
|
||||||
0.99
|
0.99
|
||||||
};
|
};
|
||||||
self.dt_adjustment = dt_adjustment;
|
self.dt_adjustment = dt_adjustment * time_scale.0;
|
||||||
},
|
},
|
||||||
ServerGeneral::EntitySync(entity_sync_package) => {
|
ServerGeneral::EntitySync(entity_sync_package) => {
|
||||||
self.state
|
self.state
|
||||||
|
@ -11,7 +11,7 @@ use common::{
|
|||||||
lod,
|
lod,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook},
|
recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook},
|
||||||
resources::{Time, TimeOfDay},
|
resources::{Time, TimeOfDay, TimeScale},
|
||||||
shared_server_config::ServerConstants,
|
shared_server_config::ServerConstants,
|
||||||
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||||
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, SitePrices, TradeId, TradeResult},
|
||||||
@ -195,7 +195,7 @@ pub enum ServerGeneral {
|
|||||||
ChatMsg(comp::ChatMsg),
|
ChatMsg(comp::ChatMsg),
|
||||||
ChatMode(comp::ChatMode),
|
ChatMode(comp::ChatMode),
|
||||||
SetPlayerEntity(Uid),
|
SetPlayerEntity(Uid),
|
||||||
TimeOfDay(TimeOfDay, Calendar, Time),
|
TimeOfDay(TimeOfDay, Calendar, Time, TimeScale),
|
||||||
EntitySync(sync::EntitySyncPackage),
|
EntitySync(sync::EntitySyncPackage),
|
||||||
CompSync(sync::CompSyncPackage<EcsCompPacket>, u64),
|
CompSync(sync::CompSyncPackage<EcsCompPacket>, u64),
|
||||||
CreateEntity(sync::EntityPackage<EcsCompPacket>),
|
CreateEntity(sync::EntityPackage<EcsCompPacket>),
|
||||||
@ -342,7 +342,7 @@ impl ServerMsg {
|
|||||||
| ServerGeneral::ChatMsg(_)
|
| ServerGeneral::ChatMsg(_)
|
||||||
| ServerGeneral::ChatMode(_)
|
| ServerGeneral::ChatMode(_)
|
||||||
| ServerGeneral::SetPlayerEntity(_)
|
| ServerGeneral::SetPlayerEntity(_)
|
||||||
| ServerGeneral::TimeOfDay(_, _, _)
|
| ServerGeneral::TimeOfDay(_, _, _, _)
|
||||||
| ServerGeneral::EntitySync(_)
|
| ServerGeneral::EntitySync(_)
|
||||||
| ServerGeneral::CompSync(_, _)
|
| ServerGeneral::CompSync(_, _)
|
||||||
| ServerGeneral::CreateEntity(_)
|
| ServerGeneral::CreateEntity(_)
|
||||||
|
@ -328,6 +328,7 @@ pub enum ServerChatCommand {
|
|||||||
Sudo,
|
Sudo,
|
||||||
Tell,
|
Tell,
|
||||||
Time,
|
Time,
|
||||||
|
TimeScale,
|
||||||
Tp,
|
Tp,
|
||||||
Unban,
|
Unban,
|
||||||
Version,
|
Version,
|
||||||
@ -722,6 +723,11 @@ impl ServerChatCommand {
|
|||||||
"Set the time of day",
|
"Set the time of day",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
|
ServerChatCommand::TimeScale => cmd(
|
||||||
|
vec![Float("time scale", 1.0, Optional)],
|
||||||
|
"Set scaling of delta time",
|
||||||
|
Some(Admin),
|
||||||
|
),
|
||||||
ServerChatCommand::Tp => cmd(
|
ServerChatCommand::Tp => cmd(
|
||||||
vec![
|
vec![
|
||||||
PlayerName(Optional),
|
PlayerName(Optional),
|
||||||
@ -894,6 +900,7 @@ impl ServerChatCommand {
|
|||||||
ServerChatCommand::Sudo => "sudo",
|
ServerChatCommand::Sudo => "sudo",
|
||||||
ServerChatCommand::Tell => "tell",
|
ServerChatCommand::Tell => "tell",
|
||||||
ServerChatCommand::Time => "time",
|
ServerChatCommand::Time => "time",
|
||||||
|
ServerChatCommand::TimeScale => "time_scale",
|
||||||
ServerChatCommand::Tp => "tp",
|
ServerChatCommand::Tp => "tp",
|
||||||
ServerChatCommand::RtsimTp => "rtsim_tp",
|
ServerChatCommand::RtsimTp => "rtsim_tp",
|
||||||
ServerChatCommand::RtsimInfo => "rtsim_info",
|
ServerChatCommand::RtsimInfo => "rtsim_info",
|
||||||
|
@ -103,6 +103,32 @@ pub struct AllBodies<BodyMeta, SpeciesMeta> {
|
|||||||
pub arthropod: BodyData<BodyMeta, arthropod::AllSpecies<SpeciesMeta>>,
|
pub arthropod: BodyData<BodyMeta, arthropod::AllSpecies<SpeciesMeta>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<BodyMeta, SpeciesMeta> AllBodies<BodyMeta, SpeciesMeta> {
|
||||||
|
/// Get species meta associated with the body.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the body doesn't have any associated meta, i.e ships,
|
||||||
|
/// objects, itemdrops.
|
||||||
|
pub fn get_species_meta<'a>(&'a self, body: &Body) -> Option<&'a SpeciesMeta> {
|
||||||
|
Some(match body {
|
||||||
|
Body::Humanoid(b) => &self.humanoid.species[&b.species],
|
||||||
|
Body::QuadrupedSmall(b) => &self.quadruped_small.species[&b.species],
|
||||||
|
Body::QuadrupedMedium(b) => &self.quadruped_medium.species[&b.species],
|
||||||
|
Body::BirdMedium(b) => &self.bird_medium.species[&b.species],
|
||||||
|
Body::BirdLarge(b) => &self.bird_large.species[&b.species],
|
||||||
|
Body::FishMedium(b) => &self.fish_medium.species[&b.species],
|
||||||
|
Body::Dragon(b) => &self.dragon.species[&b.species],
|
||||||
|
Body::FishSmall(b) => &self.fish_small.species[&b.species],
|
||||||
|
Body::BipedLarge(b) => &self.biped_large.species[&b.species],
|
||||||
|
Body::BipedSmall(b) => &self.biped_small.species[&b.species],
|
||||||
|
Body::Golem(b) => &self.golem.species[&b.species],
|
||||||
|
Body::Theropod(b) => &self.theropod.species[&b.species],
|
||||||
|
Body::QuadrupedLow(b) => &self.quadruped_low.species[&b.species],
|
||||||
|
Body::Arthropod(b) => &self.arthropod.species[&b.species],
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Can only retrieve body metadata by direct index.
|
/// Can only retrieve body metadata by direct index.
|
||||||
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
|
impl<BodyMeta, SpeciesMeta> core::ops::Index<NpcKind> for AllBodies<BodyMeta, SpeciesMeta> {
|
||||||
type Output = BodyMeta;
|
type Output = BodyMeta;
|
||||||
|
@ -99,6 +99,7 @@ impl ModularBase {
|
|||||||
hand_restriction.unwrap_or(Hands::One)
|
hand_restriction.unwrap_or(Hands::One)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
pub(super) fn kind(
|
pub(super) fn kind(
|
||||||
&self,
|
&self,
|
||||||
components: &[Item],
|
components: &[Item],
|
||||||
|
@ -173,18 +173,18 @@ impl Role for VolumeRider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
pub enum Volume {
|
pub enum Volume<E> {
|
||||||
Terrain,
|
Terrain,
|
||||||
Entity(Uid),
|
Entity(E),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
pub struct VolumePos {
|
pub struct VolumePos<E = Uid> {
|
||||||
pub kind: Volume,
|
pub kind: Volume<E>,
|
||||||
pub pos: Vec3<i32>,
|
pub pos: Vec3<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VolumePos {
|
impl<E> VolumePos<E> {
|
||||||
pub fn terrain(block_pos: Vec3<i32>) -> Self {
|
pub fn terrain(block_pos: Vec3<i32>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: Volume::Terrain,
|
kind: Volume::Terrain,
|
||||||
@ -192,12 +192,22 @@ impl VolumePos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entity(block_pos: Vec3<i32>, uid: Uid) -> Self {
|
pub fn entity(block_pos: Vec3<i32>, uid: E) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: Volume::Entity(uid),
|
kind: Volume::Entity(uid),
|
||||||
pos: block_pos,
|
pos: block_pos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_map_entity<U>(self, f: impl FnOnce(E) -> Option<U>) -> Option<VolumePos<U>> {
|
||||||
|
Some(VolumePos {
|
||||||
|
pos: self.pos,
|
||||||
|
kind: match self.kind {
|
||||||
|
Volume::Terrain => Volume::Terrain,
|
||||||
|
Volume::Entity(e) => Volume::Entity(f(e)?),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VolumePos {
|
impl VolumePos {
|
||||||
|
@ -13,6 +13,13 @@ pub struct TimeOfDay(pub f64);
|
|||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Time(pub f64);
|
pub struct Time(pub f64);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TimeScale(pub f64);
|
||||||
|
|
||||||
|
impl Default for TimeScale {
|
||||||
|
fn default() -> Self { Self(1.0) }
|
||||||
|
}
|
||||||
|
|
||||||
/// A resource that stores the time since the previous tick.
|
/// A resource that stores the time since the previous tick.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DeltaTime(pub f32);
|
pub struct DeltaTime(pub f32);
|
||||||
|
@ -15,7 +15,7 @@ use common::{
|
|||||||
region::RegionMap,
|
region::RegionMap,
|
||||||
resources::{
|
resources::{
|
||||||
DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings, Time,
|
DeltaTime, EntitiesDiedLastTick, GameMode, PlayerEntity, PlayerPhysicsSettings, Time,
|
||||||
TimeOfDay,
|
TimeOfDay, TimeScale,
|
||||||
},
|
},
|
||||||
shared_server_config::ServerConstants,
|
shared_server_config::ServerConstants,
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
@ -269,6 +269,7 @@ impl State {
|
|||||||
ecs.insert(Calendar::default());
|
ecs.insert(Calendar::default());
|
||||||
ecs.insert(WeatherGrid::new(Vec2::zero()));
|
ecs.insert(WeatherGrid::new(Vec2::zero()));
|
||||||
ecs.insert(Time(0.0));
|
ecs.insert(Time(0.0));
|
||||||
|
ecs.insert(TimeScale(1.0));
|
||||||
|
|
||||||
// Register unsynced resources used by the ECS.
|
// Register unsynced resources used by the ECS.
|
||||||
ecs.insert(DeltaTime(0.0));
|
ecs.insert(DeltaTime(0.0));
|
||||||
@ -644,14 +645,16 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Change the time accordingly.
|
// Change the time accordingly.
|
||||||
|
let time_scale = self.ecs.read_resource::<TimeScale>().0;
|
||||||
self.ecs.write_resource::<TimeOfDay>().0 +=
|
self.ecs.write_resource::<TimeOfDay>().0 +=
|
||||||
dt.as_secs_f64() * server_constants.day_cycle_coefficient;
|
dt.as_secs_f64() * server_constants.day_cycle_coefficient * time_scale;
|
||||||
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
|
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64() * time_scale;
|
||||||
|
|
||||||
// Update delta time.
|
// Update delta time.
|
||||||
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
|
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
|
||||||
// important physics events.
|
// important physics events.
|
||||||
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
|
self.ecs.write_resource::<DeltaTime>().0 =
|
||||||
|
(dt.as_secs_f32() * time_scale as f32).min(MAX_DELTA_TIME);
|
||||||
|
|
||||||
if update_terrain_and_regions {
|
if update_terrain_and_regions {
|
||||||
self.update_region_map();
|
self.update_region_map();
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
pub mod predicate;
|
||||||
|
|
||||||
|
use predicate::Predicate;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{
|
data::{
|
||||||
npc::{Controller, Npc, NpcId},
|
npc::{Controller, Npc, NpcId},
|
||||||
@ -15,6 +19,39 @@ use rand_chacha::ChaChaRng;
|
|||||||
use std::{any::Any, collections::VecDeque, marker::PhantomData, ops::ControlFlow};
|
use std::{any::Any, collections::VecDeque, marker::PhantomData, ops::ControlFlow};
|
||||||
use world::{IndexRef, World};
|
use world::{IndexRef, World};
|
||||||
|
|
||||||
|
pub trait State: Clone + Send + Sync + 'static {}
|
||||||
|
|
||||||
|
impl<T: Clone + Send + Sync + 'static> State for T {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Resettable<T> {
|
||||||
|
original: T,
|
||||||
|
current: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> From<T> for Resettable<T> {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
original: value.clone(),
|
||||||
|
current: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Resettable<T> {
|
||||||
|
fn reset(&mut self) { self.current = self.original.clone(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for Resettable<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.current }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::DerefMut for Resettable<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.current }
|
||||||
|
}
|
||||||
|
|
||||||
/// The context provided to an [`Action`] while it is being performed. It should
|
/// 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
|
/// be possible to access any and all important information about the game world
|
||||||
/// through this struct.
|
/// through this struct.
|
||||||
@ -33,6 +70,8 @@ pub struct NpcCtx<'a> {
|
|||||||
pub sentiments: &'a mut Sentiments,
|
pub sentiments: &'a mut Sentiments,
|
||||||
pub known_reports: &'a mut HashSet<ReportId>,
|
pub known_reports: &'a mut HashSet<ReportId>,
|
||||||
|
|
||||||
|
/// The delta time since this npcs ai was last ran.
|
||||||
|
pub dt: f32,
|
||||||
pub rng: ChaChaRng,
|
pub rng: ChaChaRng,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +94,7 @@ pub struct NpcCtx<'a> {
|
|||||||
/// You should not need to implement this trait yourself when writing AI code.
|
/// 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
|
/// If you find yourself wanting to implement it, please discuss with the core
|
||||||
/// dev team first.
|
/// dev team first.
|
||||||
pub trait Action<R = ()>: Any + Send + Sync {
|
pub trait Action<S = (), R = ()>: Any + Send + Sync {
|
||||||
/// Returns `true` if the action should be considered the 'same' (i.e:
|
/// Returns `true` if the action should be considered the 'same' (i.e:
|
||||||
/// achieving the same objective) as another. In general, the AI system
|
/// achieving the same objective) as another. In general, the AI system
|
||||||
/// will try to avoid switching (and therefore restarting) tasks when the
|
/// will try to avoid switching (and therefore restarting) tasks when the
|
||||||
@ -68,7 +107,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
/// Like [`Action::is_same`], but allows for dynamic dispatch.
|
/// Like [`Action::is_same`], but allows for dynamic dispatch.
|
||||||
fn dyn_is_same_sized(&self, other: &dyn Action<R>) -> bool
|
fn dyn_is_same_sized(&self, other: &dyn Action<S, R>) -> bool
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@ -79,7 +118,7 @@ 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<S, R>) -> bool;
|
||||||
|
|
||||||
/// Generate a backtrace for the action. The action should recursively push
|
/// Generate a backtrace for the action. The action should recursively push
|
||||||
/// all of the tasks it is currently performing.
|
/// all of the tasks it is currently performing.
|
||||||
@ -89,7 +128,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
fn reset(&mut self);
|
fn reset(&mut self);
|
||||||
|
|
||||||
/// Perform the action for the current tick.
|
/// Perform the action for the current tick.
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R>;
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R>;
|
||||||
|
|
||||||
/// Create an action that chains together two sub-actions, one after the
|
/// Create an action that chains together two sub-actions, one after the
|
||||||
/// other.
|
/// other.
|
||||||
@ -101,7 +140,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// goto(enemy_npc).then(attack(enemy_npc))
|
/// goto(enemy_npc).then(attack(enemy_npc))
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn then<A1: Action<R1>, R1>(self, other: A1) -> Then<Self, A1, R>
|
fn then<A1: Action<S, R1>, R1>(self, other: A1) -> Then<Self, A1, R>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@ -122,7 +161,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// find_and_collect(ChunkResource::Flax).repeat()
|
/// find_and_collect(ChunkResource::Flax).repeat()
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn repeat<R1>(self) -> Repeat<Self, R1>
|
fn repeat(self) -> Repeat<Self, R>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@ -138,11 +177,11 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// 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]
|
#[must_use]
|
||||||
fn stop_if<F: FnMut(&mut NpcCtx) -> bool + Clone>(self, f: F) -> StopIf<Self, F>
|
fn stop_if<P: Predicate + Clone>(self, p: P) -> StopIf<Self, P>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
StopIf(self, f.clone(), f)
|
StopIf(self, p.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pause an action to possibly perform another action.
|
/// Pause an action to possibly perform another action.
|
||||||
@ -159,7 +198,11 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn interrupt_with<A1: Action<R1>, R1, F: FnMut(&mut NpcCtx) -> Option<A1> + Clone>(
|
fn interrupt_with<
|
||||||
|
A1: Action<S, R1>,
|
||||||
|
R1,
|
||||||
|
F: Fn(&mut NpcCtx, &mut S) -> Option<A1> + Send + Sync + 'static,
|
||||||
|
>(
|
||||||
self,
|
self,
|
||||||
f: F,
|
f: F,
|
||||||
) -> InterruptWith<Self, F, A1, R1>
|
) -> InterruptWith<Self, F, A1, R1>
|
||||||
@ -168,8 +211,7 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
{
|
{
|
||||||
InterruptWith {
|
InterruptWith {
|
||||||
a0: self,
|
a0: self,
|
||||||
f: f.clone(),
|
f,
|
||||||
f2: f,
|
|
||||||
a1: None,
|
a1: None,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
}
|
}
|
||||||
@ -177,7 +219,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]
|
#[must_use]
|
||||||
fn map<F: FnMut(R) -> R1, R1>(self, f: F) -> Map<Self, F, R>
|
fn map<F: Fn(R, &mut S) -> R1, R1>(self, f: F) -> Map<Self, F, R>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@ -209,13 +251,54 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn boxed(self) -> Box<dyn Action<R>>
|
fn boxed(self) -> Box<dyn Action<S, R>>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
Box::new(self)
|
Box::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the state for child actions.
|
||||||
|
///
|
||||||
|
/// Note that state is reset when repeated.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// just(|_, state: &mut i32| *state += 2)
|
||||||
|
/// // Outputs 5
|
||||||
|
/// .then(just(|_, state: &mut i32| println!("{state}")))
|
||||||
|
/// .with_state(3)
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
fn with_state<S0>(self, s: S) -> WithState<Self, S, S0>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
S: Clone,
|
||||||
|
{
|
||||||
|
WithState(self, s.into(), PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map the current state for child actions, this map expects the return
|
||||||
|
/// value to have the same lifetime as the input state.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // Goes forward 5 steps
|
||||||
|
/// just(|_, state: &mut i32| go_forward(*state))
|
||||||
|
/// .map_state(|state: &mut (i32, i32)| &mut state.1)
|
||||||
|
/// .with_state((14, 5))
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
fn map_state<S0, F>(self, f: F) -> MapState<Self, F, S, S0>
|
||||||
|
where
|
||||||
|
F: Fn(&mut S0) -> &mut S,
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
MapState(self, f, PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add debugging information to the action that will be visible when using
|
/// Add debugging information to the action that will be visible when using
|
||||||
/// the `/npc_info` command.
|
/// the `/npc_info` command.
|
||||||
///
|
///
|
||||||
@ -249,10 +332,10 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
impl<S: State, R: 'static> Action<S, R> for Box<dyn Action<S, R>> {
|
||||||
fn is_same(&self, other: &Self) -> bool { (**self).dyn_is_same(other) }
|
fn is_same(&self, other: &Self) -> bool { (**self).dyn_is_same(other) }
|
||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool {
|
fn dyn_is_same(&self, other: &dyn Action<S, R>) -> bool {
|
||||||
match (other as &dyn Any).downcast_ref::<Self>() {
|
match (other as &dyn Any).downcast_ref::<Self>() {
|
||||||
Some(other) => self.is_same(other),
|
Some(other) => self.is_same(other),
|
||||||
None => false,
|
None => false,
|
||||||
@ -263,10 +346,12 @@ impl<R: 'static> Action<R> for Box<dyn Action<R>> {
|
|||||||
|
|
||||||
fn reset(&mut self) { (**self).reset(); }
|
fn reset(&mut self) { (**self).reset(); }
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { (**self).tick(ctx) }
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R> {
|
||||||
|
(**self).tick(ctx, state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for Either<A, B> {
|
impl<S: State, R: 'static, A: Action<S, R>, B: Action<S, R>> Action<S, R> for Either<A, B> {
|
||||||
fn is_same(&self, other: &Self) -> bool {
|
fn is_same(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Either::Left(x), Either::Left(y)) => x.is_same(y),
|
(Either::Left(x), Either::Left(y)) => x.is_same(y),
|
||||||
@ -275,7 +360,7 @@ impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for Either<A, B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<S, R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
match self {
|
match self {
|
||||||
@ -291,10 +376,10 @@ impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for Either<A, B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R> {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(x) => x.tick(ctx),
|
Either::Left(x) => x.tick(ctx, state),
|
||||||
Either::Right(x) => x.tick(ctx),
|
Either::Right(x) => x.tick(ctx, state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,13 +390,17 @@ impl<R: 'static, A: Action<R>, B: Action<R>> Action<R> for Either<A, B> {
|
|||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Now<F, A>(F, Option<A>);
|
pub struct Now<F, A>(F, Option<A>);
|
||||||
|
|
||||||
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'static, A: Action<R>>
|
impl<
|
||||||
Action<R> for Now<F, A>
|
S: State,
|
||||||
|
R: Send + Sync + 'static,
|
||||||
|
F: Fn(&mut NpcCtx, &mut S) -> A + Send + Sync + 'static,
|
||||||
|
A: Action<S, R>,
|
||||||
|
> Action<S, R> for Now<F, A>
|
||||||
{
|
{
|
||||||
// TODO: This doesn't compare?!
|
// TODO: This doesn't compare?!
|
||||||
fn is_same(&self, _other: &Self) -> bool { true }
|
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 dyn_is_same(&self, other: &dyn Action<S, R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
if let Some(action) = &self.1 {
|
if let Some(action) = &self.1 {
|
||||||
@ -321,12 +410,11 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'stati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reset closure?
|
|
||||||
fn reset(&mut self) { self.1 = None; }
|
fn reset(&mut self) { self.1 = None; }
|
||||||
|
|
||||||
// TODO: Reset closure state?
|
// TODO: Reset closure state?
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R> {
|
||||||
(self.1.get_or_insert_with(|| (self.0)(ctx))).tick(ctx)
|
(self.1.get_or_insert_with(|| (self.0)(ctx, state))).tick(ctx, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,9 +430,9 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> A + Send + Sync + 'stati
|
|||||||
/// // An action that makes an NPC immediately travel to its *current* home
|
/// // An action that makes an NPC immediately travel to its *current* home
|
||||||
/// now(|ctx| goto(ctx.npc.home))
|
/// now(|ctx| goto(ctx.npc.home))
|
||||||
/// ```
|
/// ```
|
||||||
pub fn now<F, A>(f: F) -> Now<F, A>
|
pub fn now<S, R, F, A: Action<S, R>>(f: F) -> Now<F, A>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> A,
|
F: Fn(&mut NpcCtx, &mut S) -> A + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Now(f, None)
|
Now(f, None)
|
||||||
}
|
}
|
||||||
@ -356,15 +444,16 @@ where
|
|||||||
pub struct Until<F, A, R>(F, Option<A>, PhantomData<R>);
|
pub struct Until<F, A, R>(F, Option<A>, PhantomData<R>);
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
|
S: State,
|
||||||
R: Send + Sync + 'static,
|
R: Send + Sync + 'static,
|
||||||
F: FnMut(&mut NpcCtx) -> Option<A> + Send + Sync + 'static,
|
F: Fn(&mut NpcCtx, &mut S) -> Option<A> + Send + Sync + 'static,
|
||||||
A: Action<R>,
|
A: Action<S, R>,
|
||||||
> Action<()> for Until<F, A, R>
|
> Action<S, ()> for Until<F, A, R>
|
||||||
{
|
{
|
||||||
// TODO: This doesn't compare?!
|
// TODO: This doesn't compare?!
|
||||||
fn is_same(&self, _other: &Self) -> bool { true }
|
fn is_same(&self, _other: &Self) -> bool { true }
|
||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<S, ()>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
if let Some(action) = &self.1 {
|
if let Some(action) = &self.1 {
|
||||||
@ -374,20 +463,18 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reset closure?
|
|
||||||
fn reset(&mut self) { self.1 = None; }
|
fn reset(&mut self) { self.1 = None; }
|
||||||
|
|
||||||
// TODO: Reset closure state?
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<()> {
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> {
|
|
||||||
match &mut self.1 {
|
match &mut self.1 {
|
||||||
Some(x) => match x.tick(ctx) {
|
Some(x) => match x.tick(ctx, state) {
|
||||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
||||||
ControlFlow::Break(_) => {
|
ControlFlow::Break(_) => {
|
||||||
self.1 = None;
|
self.1 = None;
|
||||||
ControlFlow::Continue(())
|
ControlFlow::Continue(())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
None => match (self.0)(ctx) {
|
None => match (self.0)(ctx, state) {
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
self.1 = Some(x);
|
self.1 = Some(x);
|
||||||
ControlFlow::Continue(())
|
ControlFlow::Continue(())
|
||||||
@ -398,9 +485,9 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn until<F, A, R>(f: F) -> Until<F, A, R>
|
pub fn until<S, F, A: Action<S, R>, R>(f: F) -> Until<F, A, R>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Option<A>,
|
F: Fn(&mut NpcCtx, &mut S) -> Option<A>,
|
||||||
{
|
{
|
||||||
Until(f, None, PhantomData)
|
Until(f, None, PhantomData)
|
||||||
}
|
}
|
||||||
@ -411,20 +498,20 @@ where
|
|||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Just<F, R = ()>(F, PhantomData<R>);
|
pub struct Just<F, R = ()>(F, PhantomData<R>);
|
||||||
|
|
||||||
impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static> Action<R>
|
impl<S: State, R: Send + Sync + 'static, F: Fn(&mut NpcCtx, &mut S) -> R + Send + Sync + 'static>
|
||||||
for Just<F, R>
|
Action<S, R> for Just<F, R>
|
||||||
{
|
{
|
||||||
fn is_same(&self, _other: &Self) -> bool { true }
|
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 dyn_is_same(&self, other: &dyn Action<S, R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, _bt: &mut Vec<String>) {}
|
fn backtrace(&self, _bt: &mut Vec<String>) {}
|
||||||
|
|
||||||
// TODO: Reset closure?
|
|
||||||
fn reset(&mut self) {}
|
fn reset(&mut self) {}
|
||||||
|
|
||||||
// TODO: Reset closure state?
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R> {
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { ControlFlow::Break((self.0)(ctx)) }
|
ControlFlow::Break((self.0)(ctx, state))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An action that executes some code just once when performed.
|
/// An action that executes some code just once when performed.
|
||||||
@ -438,9 +525,9 @@ impl<R: Send + Sync + 'static, F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'stati
|
|||||||
/// // Make the current NPC say 'Hello, world!' exactly once
|
/// // Make the current NPC say 'Hello, world!' exactly once
|
||||||
/// just(|ctx| ctx.controller.say("Hello, world!"))
|
/// just(|ctx| ctx.controller.say("Hello, world!"))
|
||||||
/// ```
|
/// ```
|
||||||
pub fn just<F, R: Send + Sync + 'static>(f: F) -> Just<F, R>
|
pub fn just<S: State, F, R: Send + Sync + 'static>(f: F) -> Just<F, R>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> R + Send + Sync + 'static,
|
F: Fn(&mut NpcCtx, &mut S) -> R + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Just(f, PhantomData)
|
Just(f, PhantomData)
|
||||||
}
|
}
|
||||||
@ -451,16 +538,18 @@ where
|
|||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Finish;
|
pub struct Finish;
|
||||||
|
|
||||||
impl Action<()> for Finish {
|
impl<S: State> Action<S, ()> for Finish {
|
||||||
fn is_same(&self, _other: &Self) -> bool { true }
|
fn is_same(&self, _other: &Self) -> bool { true }
|
||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<S, ()>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, _bt: &mut Vec<String>) {}
|
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, _state: &mut S) -> ControlFlow<()> {
|
||||||
|
ControlFlow::Break(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An action that immediately finishes without doing anything.
|
/// An action that immediately finishes without doing anything.
|
||||||
@ -492,33 +581,33 @@ pub const URGENT: Priority = 0;
|
|||||||
pub const IMPORTANT: Priority = 1;
|
pub const IMPORTANT: Priority = 1;
|
||||||
pub const CASUAL: Priority = 2;
|
pub const CASUAL: Priority = 2;
|
||||||
|
|
||||||
pub struct Node<R>(Box<dyn Action<R>>, Priority);
|
pub struct Node<S, R>(Box<dyn Action<S, R>>, Priority);
|
||||||
|
|
||||||
/// Perform an action with [`URGENT`] priority (see [`choose`]).
|
/// Perform an action with [`URGENT`] priority (see [`choose`]).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn urgent<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), URGENT) }
|
pub fn urgent<S, A: Action<S, R>, R>(a: A) -> Node<S, R> { Node(Box::new(a), URGENT) }
|
||||||
|
|
||||||
/// Perform an action with [`IMPORTANT`] priority (see [`choose`]).
|
/// Perform an action with [`IMPORTANT`] priority (see [`choose`]).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn important<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), IMPORTANT) }
|
pub fn important<S, A: Action<S, R>, R>(a: A) -> Node<S, R> { Node(Box::new(a), IMPORTANT) }
|
||||||
|
|
||||||
/// Perform an action with [`CASUAL`] priority (see [`choose`]).
|
/// Perform an action with [`CASUAL`] priority (see [`choose`]).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn casual<A: Action<R>, R>(a: A) -> Node<R> { Node(Box::new(a), CASUAL) }
|
pub fn casual<S, A: Action<S, R>, R>(a: A) -> Node<S, R> { Node(Box::new(a), CASUAL) }
|
||||||
|
|
||||||
/// See [`choose`] and [`watch`].
|
/// See [`choose`] and [`watch`].
|
||||||
pub struct Tree<F, R> {
|
pub struct Tree<S, F, R> {
|
||||||
next: F,
|
next: F,
|
||||||
prev: Option<Node<R>>,
|
prev: Option<Node<S, R>>,
|
||||||
interrupt: bool,
|
interrupt: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Action<R>
|
impl<S: State, F: Fn(&mut NpcCtx, &mut S) -> Node<S, R> + Send + Sync + 'static, R: 'static>
|
||||||
for Tree<F, R>
|
Action<S, R> for Tree<S, F, R>
|
||||||
{
|
{
|
||||||
fn is_same(&self, _other: &Self) -> bool { true }
|
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 dyn_is_same(&self, other: &dyn Action<S, R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
if let Some(prev) = &self.prev {
|
if let Some(prev) = &self.prev {
|
||||||
@ -530,9 +619,8 @@ impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Actio
|
|||||||
|
|
||||||
fn reset(&mut self) { self.prev = None; }
|
fn reset(&mut self) { self.prev = None; }
|
||||||
|
|
||||||
// TODO: Reset `next` too?
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R> {
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> {
|
let new = (self.next)(ctx, state);
|
||||||
let new = (self.next)(ctx);
|
|
||||||
|
|
||||||
let prev = match &mut self.prev {
|
let prev = match &mut self.prev {
|
||||||
Some(prev) if prev.1 <= new.1 && (prev.0.dyn_is_same(&*new.0) || !self.interrupt) => {
|
Some(prev) if prev.1 <= new.1 && (prev.0.dyn_is_same(&*new.0) || !self.interrupt) => {
|
||||||
@ -541,7 +629,7 @@ impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Actio
|
|||||||
_ => self.prev.insert(new),
|
_ => self.prev.insert(new),
|
||||||
};
|
};
|
||||||
|
|
||||||
match prev.0.tick(ctx) {
|
match prev.0.tick(ctx, state) {
|
||||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
||||||
ControlFlow::Break(r) => {
|
ControlFlow::Break(r) => {
|
||||||
self.prev = None;
|
self.prev = None;
|
||||||
@ -575,9 +663,9 @@ impl<F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static, R: 'static> Actio
|
|||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn choose<R: 'static, F>(f: F) -> impl Action<R>
|
pub fn choose<S: State, R: 'static, F>(f: F) -> impl Action<S, R>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
|
F: Fn(&mut NpcCtx, &mut S) -> Node<S, R> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Tree {
|
Tree {
|
||||||
next: f,
|
next: f,
|
||||||
@ -610,9 +698,9 @@ where
|
|||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn watch<R: 'static, F>(f: F) -> impl Action<R>
|
pub fn watch<S: State, R: 'static, F>(f: F) -> impl Action<S, R>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Node<R> + Send + Sync + 'static,
|
F: Fn(&mut NpcCtx, &mut S) -> Node<S, R> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Tree {
|
Tree {
|
||||||
next: f,
|
next: f,
|
||||||
@ -632,14 +720,19 @@ pub struct Then<A0, A1, R0> {
|
|||||||
phantom: PhantomData<R0>,
|
phantom: PhantomData<R0>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync + 'static>
|
impl<
|
||||||
Action<R1> for Then<A0, A1, R0>
|
S: State,
|
||||||
|
A0: Action<S, R0>,
|
||||||
|
A1: Action<S, R1>,
|
||||||
|
R0: Send + Sync + 'static,
|
||||||
|
R1: Send + Sync + 'static,
|
||||||
|
> Action<S, R1> for Then<A0, A1, R0>
|
||||||
{
|
{
|
||||||
fn is_same(&self, other: &Self) -> bool {
|
fn is_same(&self, other: &Self) -> bool {
|
||||||
self.a0.is_same(&other.a0) && self.a1.is_same(&other.a1)
|
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 dyn_is_same(&self, other: &dyn Action<S, R1>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
if self.a0_finished {
|
if self.a0_finished {
|
||||||
@ -655,14 +748,14 @@ impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync
|
|||||||
self.a1.reset();
|
self.a1.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R1> {
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R1> {
|
||||||
if !self.a0_finished {
|
if !self.a0_finished {
|
||||||
match self.a0.tick(ctx) {
|
match self.a0.tick(ctx, state) {
|
||||||
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
|
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
|
||||||
ControlFlow::Break(_) => self.a0_finished = true,
|
ControlFlow::Break(_) => self.a0_finished = true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.a1.tick(ctx)
|
self.a1.tick(ctx, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,22 +766,22 @@ impl<A0: Action<R0>, A1: Action<R1>, R0: Send + Sync + 'static, R1: Send + Sync
|
|||||||
pub struct InterruptWith<A0, F, A1, R1> {
|
pub struct InterruptWith<A0, F, A1, R1> {
|
||||||
a0: A0,
|
a0: A0,
|
||||||
f: F,
|
f: F,
|
||||||
f2: F,
|
|
||||||
a1: Option<A1>,
|
a1: Option<A1>,
|
||||||
phantom: PhantomData<R1>,
|
phantom: PhantomData<R1>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
A0: Action<R0>,
|
S: State,
|
||||||
A1: Action<R1>,
|
A0: Action<S, R0>,
|
||||||
F: FnMut(&mut NpcCtx) -> Option<A1> + Clone + Send + Sync + 'static,
|
A1: Action<S, R1>,
|
||||||
|
F: Fn(&mut NpcCtx, &mut S) -> Option<A1> + Send + Sync + 'static,
|
||||||
R0: Send + Sync + 'static,
|
R0: Send + Sync + 'static,
|
||||||
R1: Send + Sync + 'static,
|
R1: Send + Sync + 'static,
|
||||||
> Action<R0> for InterruptWith<A0, F, A1, R1>
|
> Action<S, R0> for InterruptWith<A0, F, A1, R1>
|
||||||
{
|
{
|
||||||
fn is_same(&self, other: &Self) -> bool { self.a0.is_same(&other.a0) }
|
fn is_same(&self, other: &Self) -> bool { self.a0.is_same(&other.a0) }
|
||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<R0>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<S, R0>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
if let Some(a1) = &self.a1 {
|
if let Some(a1) = &self.a1 {
|
||||||
@ -702,23 +795,22 @@ impl<
|
|||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.a0.reset();
|
self.a0.reset();
|
||||||
self.f = self.f2.clone();
|
|
||||||
self.a1 = None;
|
self.a1 = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R0> {
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R0> {
|
||||||
if let Some(new_a1) = (self.f)(ctx) {
|
if let Some(new_a1) = (self.f)(ctx, state) {
|
||||||
self.a1 = Some(new_a1);
|
self.a1 = Some(new_a1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(a1) = &mut self.a1 {
|
if let Some(a1) = &mut self.a1 {
|
||||||
match a1.tick(ctx) {
|
match a1.tick(ctx, state) {
|
||||||
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
|
ControlFlow::Continue(()) => return ControlFlow::Continue(()),
|
||||||
ControlFlow::Break(_) => self.a1 = None,
|
ControlFlow::Break(_) => self.a1 = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.a0.tick(ctx)
|
self.a0.tick(ctx, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,17 +820,17 @@ impl<
|
|||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Repeat<A, R = ()>(A, PhantomData<R>);
|
pub struct Repeat<A, R = ()>(A, PhantomData<R>);
|
||||||
|
|
||||||
impl<R: Send + Sync + 'static, A: Action<R>> Action<!> for Repeat<A, R> {
|
impl<S: State, R: Send + Sync + 'static, A: Action<S, R>> Action<S, !> for Repeat<A, R> {
|
||||||
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
|
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 dyn_is_same(&self, other: &dyn Action<S, !>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
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, state: &mut S) -> ControlFlow<!> {
|
||||||
match self.0.tick(ctx) {
|
match self.0.tick(ctx, state) {
|
||||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
||||||
ControlFlow::Break(_) => {
|
ControlFlow::Break(_) => {
|
||||||
self.0.reset();
|
self.0.reset();
|
||||||
@ -752,17 +844,21 @@ impl<R: Send + Sync + 'static, A: Action<R>> Action<!> for Repeat<A, R> {
|
|||||||
|
|
||||||
/// See [`seq`].
|
/// See [`seq`].
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Sequence<I, A, R = ()>(I, I, Option<A>, PhantomData<R>);
|
pub struct Sequence<I, A, R = ()>(Resettable<I>, Option<A>, PhantomData<R>);
|
||||||
|
|
||||||
impl<R: Send + Sync + 'static, I: Iterator<Item = A> + Clone + Send + Sync + 'static, A: Action<R>>
|
impl<
|
||||||
Action<()> for Sequence<I, A, R>
|
S: State,
|
||||||
|
R: Send + Sync + 'static,
|
||||||
|
I: Iterator<Item = A> + Clone + Send + Sync + 'static,
|
||||||
|
A: Action<S, R>,
|
||||||
|
> Action<S, ()> for Sequence<I, A, R>
|
||||||
{
|
{
|
||||||
fn is_same(&self, _other: &Self) -> bool { true }
|
fn is_same(&self, _other: &Self) -> bool { true }
|
||||||
|
|
||||||
fn dyn_is_same(&self, other: &dyn Action<()>) -> bool { self.dyn_is_same_sized(other) }
|
fn dyn_is_same(&self, other: &dyn Action<S, ()>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
if let Some(action) = &self.2 {
|
if let Some(action) = &self.1 {
|
||||||
action.backtrace(bt);
|
action.backtrace(bt);
|
||||||
} else {
|
} else {
|
||||||
bt.push("<thinking>".to_string());
|
bt.push("<thinking>".to_string());
|
||||||
@ -770,22 +866,22 @@ impl<R: Send + Sync + 'static, I: Iterator<Item = A> + Clone + Send + Sync + 'st
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.0 = self.1.clone();
|
self.0.reset();
|
||||||
self.2 = None;
|
self.1 = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<()> {
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<()> {
|
||||||
let item = if let Some(prev) = &mut self.2 {
|
let item = if let Some(prev) = &mut self.1 {
|
||||||
prev
|
prev
|
||||||
} else {
|
} else {
|
||||||
match self.0.next() {
|
match self.0.next() {
|
||||||
Some(next) => self.2.insert(next),
|
Some(next) => self.1.insert(next),
|
||||||
None => return ControlFlow::Break(()),
|
None => return ControlFlow::Break(()),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let ControlFlow::Break(_) = item.tick(ctx) {
|
if let ControlFlow::Break(_) = item.tick(ctx, state) {
|
||||||
self.2 = None;
|
self.1 = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlFlow::Continue(())
|
ControlFlow::Continue(())
|
||||||
@ -811,39 +907,41 @@ impl<R: Send + Sync + 'static, I: Iterator<Item = A> + Clone + Send + Sync + 'st
|
|||||||
/// .map(|enemy| attack(enemy)))
|
/// .map(|enemy| attack(enemy)))
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn seq<I, A, R>(iter: I) -> Sequence<I, A, R>
|
pub fn seq<S, I, A, R>(iter: I) -> Sequence<I, A, R>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = A> + Clone,
|
I: Iterator<Item = A> + Clone,
|
||||||
A: Action<R>,
|
A: Action<S, R>,
|
||||||
{
|
{
|
||||||
Sequence(iter.clone(), iter, None, PhantomData)
|
Sequence(iter.into(), None, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopIf
|
// StopIf
|
||||||
|
|
||||||
/// See [`Action::stop_if`].
|
/// See [`Action::stop_if`].
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct StopIf<A, F>(A, F, F);
|
pub struct StopIf<A, P>(A, Resettable<P>);
|
||||||
|
|
||||||
impl<A: Action<R>, F: FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync + 'static, R>
|
impl<S: State, A: Action<S, R>, P: Predicate + Clone + Send + Sync + 'static, R>
|
||||||
Action<Option<R>> for StopIf<A, F>
|
Action<S, Option<R>> for StopIf<A, P>
|
||||||
{
|
{
|
||||||
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
|
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 dyn_is_same(&self, other: &dyn Action<S, Option<R>>) -> bool {
|
||||||
|
self.dyn_is_same_sized(other)
|
||||||
|
}
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.0.reset();
|
self.0.reset();
|
||||||
self.1 = self.2.clone();
|
self.1.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<Option<R>> {
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<Option<R>> {
|
||||||
if (self.1)(ctx) {
|
if self.1.should(ctx) {
|
||||||
ControlFlow::Break(None)
|
ControlFlow::Break(None)
|
||||||
} else {
|
} else {
|
||||||
self.0.tick(ctx).map_break(Some)
|
self.0.tick(ctx, state).map_break(Some)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -854,19 +952,24 @@ impl<A: Action<R>, F: FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync + 'static
|
|||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Map<A, F, R>(A, F, PhantomData<R>);
|
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>
|
impl<
|
||||||
Action<R1> for Map<A, F, R>
|
S: State,
|
||||||
|
A: Action<S, R>,
|
||||||
|
F: Fn(R, &mut S) -> R1 + Send + Sync + 'static,
|
||||||
|
R: Send + Sync + 'static,
|
||||||
|
R1,
|
||||||
|
> Action<S, R1> for Map<A, F, R>
|
||||||
{
|
{
|
||||||
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
|
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 dyn_is_same(&self, other: &dyn Action<S, R1>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) { self.0.backtrace(bt); }
|
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, state: &mut S) -> ControlFlow<R1> {
|
||||||
self.0.tick(ctx).map_break(&mut self.1)
|
self.0.tick(ctx, state).map_break(|t| (self.1)(t, state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -877,15 +980,16 @@ impl<A: Action<R>, F: FnMut(R) -> R1 + Send + Sync + 'static, R: Send + Sync + '
|
|||||||
pub struct Debug<A, F, T>(A, F, PhantomData<T>);
|
pub struct Debug<A, F, T>(A, F, PhantomData<T>);
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
A: Action<R>,
|
S: 'static,
|
||||||
|
A: Action<S, R>,
|
||||||
F: Fn() -> T + Send + Sync + 'static,
|
F: Fn() -> T + Send + Sync + 'static,
|
||||||
R: Send + Sync + 'static,
|
R: Send + Sync + 'static,
|
||||||
T: Send + Sync + std::fmt::Display + 'static,
|
T: Send + Sync + std::fmt::Display + 'static,
|
||||||
> Action<R> for Debug<A, F, T>
|
> Action<S, R> for Debug<A, F, T>
|
||||||
{
|
{
|
||||||
fn is_same(&self, other: &Self) -> bool { self.0.is_same(&other.0) }
|
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 dyn_is_same(&self, other: &dyn Action<S, R>) -> bool { self.dyn_is_same_sized(other) }
|
||||||
|
|
||||||
fn backtrace(&self, bt: &mut Vec<String>) {
|
fn backtrace(&self, bt: &mut Vec<String>) {
|
||||||
bt.push((self.1)().to_string());
|
bt.push((self.1)().to_string());
|
||||||
@ -894,5 +998,56 @@ impl<
|
|||||||
|
|
||||||
fn reset(&mut self) { self.0.reset(); }
|
fn reset(&mut self) { self.0.reset(); }
|
||||||
|
|
||||||
fn tick(&mut self, ctx: &mut NpcCtx) -> ControlFlow<R> { self.0.tick(ctx) }
|
fn tick(&mut self, ctx: &mut NpcCtx, state: &mut S) -> ControlFlow<R> {
|
||||||
|
self.0.tick(ctx, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct WithState<A, S, S0>(A, Resettable<S>, PhantomData<S0>);
|
||||||
|
|
||||||
|
impl<S0: State, S: State, R, A: Action<S, R>> Action<S0, R> for WithState<A, S, S0> {
|
||||||
|
fn is_same(&self, other: &Self) -> bool
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.0.is_same(&other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_is_same(&self, other: &dyn Action<S0, 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();
|
||||||
|
self.1.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self, ctx: &mut NpcCtx, _state: &mut S0) -> ControlFlow<R> {
|
||||||
|
self.0.tick(ctx, &mut self.1.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct MapState<A, F, S, S0>(A, F, PhantomData<(S, S0)>);
|
||||||
|
|
||||||
|
impl<S0: State, S: State, R, A: Action<S, R>, F: Fn(&mut S0) -> &mut S + Send + Sync + 'static>
|
||||||
|
Action<S0, R> for MapState<A, F, S, S0>
|
||||||
|
{
|
||||||
|
fn is_same(&self, other: &Self) -> bool
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.0.is_same(&other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_is_same(&self, other: &dyn Action<S0, 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 tick(&mut self, ctx: &mut NpcCtx, state: &mut S0) -> ControlFlow<R> {
|
||||||
|
self.0.tick(ctx, (self.1)(state))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
93
rtsim/src/ai/predicate.rs
Normal file
93
rtsim/src/ai/predicate.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use super::NpcCtx;
|
||||||
|
|
||||||
|
pub trait Predicate: Sized + Clone {
|
||||||
|
fn should(&mut self, ctx: &mut NpcCtx) -> bool;
|
||||||
|
|
||||||
|
fn chance(self, chance: f32) -> Chance<Self> {
|
||||||
|
Chance {
|
||||||
|
predicate: self,
|
||||||
|
chance,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hint for when this will be true.
|
||||||
|
fn time_hint(&self) -> Option<f32> { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Yes;
|
||||||
|
|
||||||
|
impl Predicate for Yes {
|
||||||
|
fn should(&mut self, _ctx: &mut NpcCtx) -> bool { true }
|
||||||
|
|
||||||
|
fn time_hint(&self) -> Option<f32> { Some(0.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EveryRange {
|
||||||
|
next: Option<f32>,
|
||||||
|
sample: std::ops::Range<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn every_range(r: std::ops::Range<f32>) -> EveryRange {
|
||||||
|
EveryRange {
|
||||||
|
next: None,
|
||||||
|
sample: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Predicate for EveryRange {
|
||||||
|
fn should(&mut self, ctx: &mut NpcCtx) -> bool {
|
||||||
|
if let Some(ref mut next) = self.next {
|
||||||
|
*next -= ctx.dt;
|
||||||
|
if *next <= 0.0 {
|
||||||
|
*next += ctx.rng.gen_range(self.sample.clone());
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.next = Some(ctx.rng.gen_range(self.sample.clone()));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_hint(&self) -> Option<f32> { self.next }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Chance<P> {
|
||||||
|
predicate: P,
|
||||||
|
chance: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Predicate> Predicate for Chance<P> {
|
||||||
|
fn should(&mut self, ctx: &mut NpcCtx) -> bool {
|
||||||
|
self.predicate.should(ctx) && ctx.rng.gen_bool(self.chance as f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_hint(&self) -> Option<f32> { self.predicate.time_hint() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: Fn(&mut NpcCtx) -> bool + Clone> Predicate for F {
|
||||||
|
fn should(&mut self, ctx: &mut NpcCtx) -> bool { self(ctx) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seconds
|
||||||
|
pub fn timeout(time: f64) -> Timeout { Timeout { seconds_left: time } }
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Timeout {
|
||||||
|
seconds_left: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Predicate for Timeout {
|
||||||
|
fn should(&mut self, ctx: &mut NpcCtx) -> bool {
|
||||||
|
self.seconds_left -= ctx.dt as f64;
|
||||||
|
self.seconds_left <= 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_hint(&self) -> Option<f32> { Some(self.seconds_left.max(0.0) as f32) }
|
||||||
|
}
|
@ -29,7 +29,7 @@ use std::{
|
|||||||
/// Note that this number does *not* need incrementing on every change: most
|
/// Note that this number does *not* need incrementing on every change: most
|
||||||
/// field removals/additions are fine. This number should only be incremented
|
/// field removals/additions are fine. This number should only be incremented
|
||||||
/// when we wish to perform a *hard purge* of rtsim data.
|
/// when we wish to perform a *hard purge* of rtsim data.
|
||||||
pub const CURRENT_VERSION: u32 = 2;
|
pub const CURRENT_VERSION: u32 = 3;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
|
@ -86,7 +86,7 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Brain {
|
pub struct Brain {
|
||||||
pub action: Box<dyn Action<!>>,
|
pub action: Box<dyn Action<(), !>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crate::{RtState, Rule};
|
use crate::{RtState, Rule};
|
||||||
use common::{
|
use common::{
|
||||||
|
mounting::VolumePos,
|
||||||
resources::{Time, TimeOfDay},
|
resources::{Time, TimeOfDay},
|
||||||
rtsim::Actor,
|
rtsim::{Actor, VehicleId},
|
||||||
};
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use world::{IndexRef, World};
|
use world::{IndexRef, World};
|
||||||
@ -36,3 +37,10 @@ pub struct OnDeath {
|
|||||||
pub killer: Option<Actor>,
|
pub killer: Option<Actor>,
|
||||||
}
|
}
|
||||||
impl Event for OnDeath {}
|
impl Event for OnDeath {}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct OnMountVolume {
|
||||||
|
pub actor: Actor,
|
||||||
|
pub pos: VolumePos<VehicleId>,
|
||||||
|
}
|
||||||
|
impl Event for OnMountVolume {}
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
trait_upcasting,
|
trait_upcasting,
|
||||||
control_flow_enum,
|
control_flow_enum,
|
||||||
let_chains,
|
let_chains,
|
||||||
binary_heap_drain_sorted
|
binary_heap_drain_sorted,
|
||||||
|
fn_traits,
|
||||||
|
unboxed_closures,
|
||||||
|
tuple_trait
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod ai;
|
pub mod ai;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
use std::hash::BuildHasherDefault;
|
use std::{collections::VecDeque, hash::BuildHasherDefault};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ai::{casual, choose, finish, important, just, now, seq, until, Action, NpcCtx},
|
ai::{
|
||||||
|
casual, choose, finish, important, just, now,
|
||||||
|
predicate::{every_range, timeout, Chance, EveryRange, Predicate},
|
||||||
|
seq, until, Action, NpcCtx, State,
|
||||||
|
},
|
||||||
data::{
|
data::{
|
||||||
npc::{Brain, PathData, SimulationMode},
|
npc::{Brain, PathData, SimulationMode},
|
||||||
ReportKind, Sentiment, Sites,
|
ReportKind, Sentiment, Sites,
|
||||||
@ -52,6 +56,12 @@ const CARDINALS: &[Vec2<i32>] = &[
|
|||||||
Vec2::new(0, -1),
|
Vec2::new(0, -1),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct DefaultState {
|
||||||
|
socialize_timer: EveryRange,
|
||||||
|
move_home_timer: Chance<EveryRange>,
|
||||||
|
}
|
||||||
|
|
||||||
fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> {
|
fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathResult<Vec2<i32>> {
|
||||||
let heuristic = |tile: &Vec2<i32>, _: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
|
let heuristic = |tile: &Vec2<i32>, _: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
|
||||||
let mut astar = Astar::new(1000, start, BuildHasherDefault::<FxHasher64>::default());
|
let mut astar = Astar::new(1000, start, BuildHasherDefault::<FxHasher64>::default());
|
||||||
@ -222,7 +232,17 @@ fn path_towns(
|
|||||||
|
|
||||||
impl Rule for NpcAi {
|
impl Rule for NpcAi {
|
||||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||||
rtstate.bind::<Self, OnTick>(|ctx| {
|
// Keep track of the last `SIMULATED_TICK_SKIP` ticks, to know the deltatime
|
||||||
|
// since the last tick we ran the npc.
|
||||||
|
let mut last_ticks: VecDeque<_> = [1.0 / 30.0; SIMULATED_TICK_SKIP as usize]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
rtstate.bind::<Self, OnTick>(move |ctx| {
|
||||||
|
last_ticks.push_front(ctx.event.dt);
|
||||||
|
if last_ticks.len() >= SIMULATED_TICK_SKIP as usize {
|
||||||
|
last_ticks.pop_back();
|
||||||
|
}
|
||||||
// Temporarily take the brains of NPCs out of their heads to appease the borrow
|
// Temporarily take the brains of NPCs out of their heads to appease the borrow
|
||||||
// checker
|
// checker
|
||||||
let mut npc_data = {
|
let mut npc_data = {
|
||||||
@ -239,13 +259,20 @@ impl Rule for NpcAi {
|
|||||||
let sentiments = std::mem::take(&mut npc.sentiments);
|
let sentiments = std::mem::take(&mut npc.sentiments);
|
||||||
let known_reports = std::mem::take(&mut npc.known_reports);
|
let known_reports = std::mem::take(&mut npc.known_reports);
|
||||||
let brain = npc.brain.take().unwrap_or_else(|| Brain {
|
let brain = npc.brain.take().unwrap_or_else(|| Brain {
|
||||||
action: Box::new(think().repeat()),
|
action: Box::new(think().repeat().with_state(DefaultState {
|
||||||
|
socialize_timer: every_range(15.0..30.0),
|
||||||
|
move_home_timer: every_range(400.0..2000.0).chance(0.5),
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
(npc_id, controller, inbox, sentiments, known_reports, brain)
|
(npc_id, controller, inbox, sentiments, known_reports, brain)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The sum of the last `SIMULATED_TICK_SKIP` tick deltatimes is the deltatime since
|
||||||
|
// simulated npcs ran this tick had their ai ran.
|
||||||
|
let simulated_dt = last_ticks.iter().sum::<f32>();
|
||||||
|
|
||||||
// Do a little thinking
|
// Do a little thinking
|
||||||
{
|
{
|
||||||
let data = &*ctx.state.data();
|
let data = &*ctx.state.data();
|
||||||
@ -267,8 +294,13 @@ impl Rule for NpcAi {
|
|||||||
inbox,
|
inbox,
|
||||||
known_reports,
|
known_reports,
|
||||||
sentiments,
|
sentiments,
|
||||||
|
dt: if matches!(npc.mode, SimulationMode::Loaded) {
|
||||||
|
ctx.event.dt
|
||||||
|
} else {
|
||||||
|
simulated_dt
|
||||||
|
},
|
||||||
rng: ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>()),
|
rng: ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>()),
|
||||||
});
|
}, &mut ());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,20 +319,19 @@ impl Rule for NpcAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn idle() -> impl Action { just(|ctx| ctx.controller.do_idle()).debug(|| "idle") }
|
fn idle<S: State>() -> impl Action<S> { just(|ctx, _| ctx.controller.do_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, goal_dist: f32) -> impl Action {
|
fn goto<S: State>(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action<S> {
|
||||||
const STEP_DIST: f32 = 24.0;
|
const STEP_DIST: f32 = 24.0;
|
||||||
const WAYPOINT_DIST: f32 = 12.0;
|
const WAYPOINT_DIST: f32 = 12.0;
|
||||||
let mut waypoint = None;
|
|
||||||
|
|
||||||
just(move |ctx| {
|
just(move |ctx, waypoint: &mut Option<Vec3<f32>>| {
|
||||||
// If we're close to the next waypoint, complete it
|
// If we're close to the next waypoint, complete it
|
||||||
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
|
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
|
||||||
ctx.npc.wpos.xy().distance_squared(waypoint.xy()) < WAYPOINT_DIST.powi(2)
|
ctx.npc.wpos.xy().distance_squared(waypoint.xy()) < WAYPOINT_DIST.powi(2)
|
||||||
}) {
|
}) {
|
||||||
waypoint = None;
|
*waypoint = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next waypoint on the route toward the goal
|
// Get the next waypoint on the route toward the goal
|
||||||
@ -309,40 +340,36 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
|||||||
let len = rpos.magnitude();
|
let len = rpos.magnitude();
|
||||||
let wpos = ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST);
|
let wpos = ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST);
|
||||||
|
|
||||||
wpos.with_z(
|
wpos.with_z(ctx.world.sim().get_surface_alt_approx(wpos.xy().as_()))
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(wpos.xy().as_())
|
|
||||||
.unwrap_or(wpos.z),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.controller.do_goto(*waypoint, speed_factor);
|
ctx.controller.do_goto(*waypoint, speed_factor);
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2))
|
.stop_if(move |ctx: &mut NpcCtx| {
|
||||||
|
ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2)
|
||||||
|
})
|
||||||
|
.with_state(None)
|
||||||
.debug(move || format!("goto {}, {}, {}", wpos.x, wpos.y, wpos.z))
|
.debug(move || format!("goto {}, {}, {}", wpos.x, wpos.y, wpos.z))
|
||||||
.map(|_| {})
|
.map(|_, _| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to walk fly a 3D position following the terrain altitude at an offset
|
/// Try to walk fly a 3D position following the terrain altitude at an offset
|
||||||
/// without caring for obstacles.
|
/// without caring for obstacles.
|
||||||
fn goto_flying(
|
fn goto_flying<S: State>(
|
||||||
wpos: Vec3<f32>,
|
wpos: Vec3<f32>,
|
||||||
speed_factor: f32,
|
speed_factor: f32,
|
||||||
goal_dist: f32,
|
goal_dist: f32,
|
||||||
step_dist: f32,
|
step_dist: f32,
|
||||||
waypoint_dist: f32,
|
waypoint_dist: f32,
|
||||||
height_offset: f32,
|
height_offset: f32,
|
||||||
) -> impl Action {
|
) -> impl Action<S> {
|
||||||
let mut waypoint = None;
|
just(move |ctx, waypoint: &mut Option<Vec3<f32>>| {
|
||||||
|
|
||||||
just(move |ctx| {
|
|
||||||
// If we're close to the next waypoint, complete it
|
// If we're close to the next waypoint, complete it
|
||||||
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
|
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
|
||||||
ctx.npc.wpos.distance_squared(waypoint) < waypoint_dist.powi(2)
|
ctx.npc.wpos.distance_squared(waypoint) < waypoint_dist.powi(2)
|
||||||
}) {
|
}) {
|
||||||
waypoint = None;
|
*waypoint = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next waypoint on the route toward the goal
|
// Get the next waypoint on the route toward the goal
|
||||||
@ -351,43 +378,41 @@ fn goto_flying(
|
|||||||
let len = rpos.magnitude();
|
let len = rpos.magnitude();
|
||||||
let wpos = ctx.npc.wpos + (rpos / len) * len.min(step_dist);
|
let wpos = ctx.npc.wpos + (rpos / len) * len.min(step_dist);
|
||||||
|
|
||||||
wpos.with_z(
|
wpos.with_z(ctx.world.sim().get_surface_alt_approx(wpos.xy().as_()) + height_offset)
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(wpos.xy().as_())
|
|
||||||
.map(|alt| alt + height_offset)
|
|
||||||
.unwrap_or(wpos.z),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.controller.do_goto(*waypoint, speed_factor);
|
ctx.controller.do_goto(*waypoint, speed_factor);
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2))
|
.boxed()
|
||||||
|
.with_state(None)
|
||||||
|
.stop_if(move |ctx: &mut NpcCtx| {
|
||||||
|
ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2)
|
||||||
|
})
|
||||||
.debug(move || format!("goto {}, {}, {}", wpos.x, wpos.y, wpos.z))
|
.debug(move || format!("goto {}, {}, {}", wpos.x, wpos.y, wpos.z))
|
||||||
.map(|_| {})
|
.map(|_, _| {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to walk toward a 2D position on the terrain without caring for
|
/// Try to walk toward a 2D position on the surface without caring for
|
||||||
/// obstacles.
|
/// obstacles.
|
||||||
fn goto_2d(wpos2d: Vec2<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
fn goto_2d<S: State>(wpos2d: Vec2<f32>, speed_factor: f32, goal_dist: f32) -> impl Action<S> {
|
||||||
now(move |ctx| {
|
now(move |ctx, _| {
|
||||||
let wpos = wpos2d.with_z(ctx.world.sim().get_alt_approx(wpos2d.as_()).unwrap_or(0.0));
|
let wpos = wpos2d.with_z(ctx.world.sim().get_surface_alt_approx(wpos2d.as_()));
|
||||||
goto(wpos, speed_factor, goal_dist)
|
goto(wpos, speed_factor, goal_dist)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to fly toward a 2D position following the terrain altitude at an offset
|
/// Try to fly toward a 2D position following the terrain altitude at an offset
|
||||||
/// without caring for obstacles.
|
/// without caring for obstacles.
|
||||||
fn goto_2d_flying(
|
fn goto_2d_flying<S: State>(
|
||||||
wpos2d: Vec2<f32>,
|
wpos2d: Vec2<f32>,
|
||||||
speed_factor: f32,
|
speed_factor: f32,
|
||||||
goal_dist: f32,
|
goal_dist: f32,
|
||||||
step_dist: f32,
|
step_dist: f32,
|
||||||
waypoint_dist: f32,
|
waypoint_dist: f32,
|
||||||
height_offset: f32,
|
height_offset: f32,
|
||||||
) -> impl Action {
|
) -> impl Action<S> {
|
||||||
now(move |ctx| {
|
now(move |ctx, _| {
|
||||||
let wpos = wpos2d
|
let wpos = wpos2d
|
||||||
.with_z(ctx.world.sim().get_alt_approx(wpos2d.as_()).unwrap_or(0.0) + height_offset);
|
.with_z(ctx.world.sim().get_alt_approx(wpos2d.as_()).unwrap_or(0.0) + height_offset);
|
||||||
goto_flying(
|
goto_flying(
|
||||||
@ -401,11 +426,11 @@ fn goto_2d_flying(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn traverse_points<F>(mut next_point: F, speed_factor: f32) -> impl Action
|
fn traverse_points<S: State, F>(next_point: F, speed_factor: f32) -> impl Action<S>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Option<Vec2<f32>> + Send + Sync + 'static,
|
F: FnMut(&mut NpcCtx) -> Option<Vec2<f32>> + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
until(move |ctx| {
|
until(move |ctx, next_point: &mut F| {
|
||||||
let wpos = next_point(ctx)?;
|
let wpos = next_point(ctx)?;
|
||||||
|
|
||||||
let wpos_site = |wpos: Vec2<f32>| {
|
let wpos_site = |wpos: Vec2<f32>| {
|
||||||
@ -437,11 +462,12 @@ where
|
|||||||
Some(Either::Right(goto_2d(wpos, speed_factor, 8.0)))
|
Some(Either::Right(goto_2d(wpos, speed_factor, 8.0)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.with_state(next_point)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to travel to a site. Where practical, paths will be taken.
|
/// Try to travel to a site. Where practical, paths will be taken.
|
||||||
fn travel_to_point(wpos: Vec2<f32>, speed_factor: f32) -> impl Action {
|
fn travel_to_point<S: State>(wpos: Vec2<f32>, speed_factor: f32) -> impl Action<S> {
|
||||||
now(move |ctx| {
|
now(move |ctx, _| {
|
||||||
const WAYPOINT: f32 = 48.0;
|
const WAYPOINT: f32 = 48.0;
|
||||||
let start = ctx.npc.wpos.xy();
|
let start = ctx.npc.wpos.xy();
|
||||||
let diff = wpos - start;
|
let diff = wpos - start;
|
||||||
@ -453,8 +479,8 @@ fn travel_to_point(wpos: Vec2<f32>, speed_factor: f32) -> impl Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Try to travel to a site. Where practical, paths will be taken.
|
/// Try to travel to a site. Where practical, paths will be taken.
|
||||||
fn travel_to_site(tgt_site: SiteId, speed_factor: f32) -> impl Action {
|
fn travel_to_site<S: State>(tgt_site: SiteId, speed_factor: f32) -> impl Action<S> {
|
||||||
now(move |ctx| {
|
now(move |ctx, _| {
|
||||||
let sites = &ctx.state.data().sites;
|
let sites = &ctx.state.data().sites;
|
||||||
|
|
||||||
let site_wpos = sites.get(tgt_site).map(|site| site.wpos.as_());
|
let site_wpos = sites.get(tgt_site).map(|site| site.wpos.as_());
|
||||||
@ -540,20 +566,14 @@ fn travel_to_site(tgt_site: SiteId, speed_factor: f32) -> impl Action {
|
|||||||
finish().boxed()
|
finish().boxed()
|
||||||
}
|
}
|
||||||
// Stop the NPC early if we're near the site to prevent huddling around the centre
|
// Stop the NPC early if we're near the site to prevent huddling around the centre
|
||||||
.stop_if(move |ctx| site_wpos.map_or(false, |site_wpos| ctx.npc.wpos.xy().distance_squared(site_wpos) < 16f32.powi(2)))
|
.stop_if(move |ctx: &mut NpcCtx| site_wpos.map_or(false, |site_wpos| ctx.npc.wpos.xy().distance_squared(site_wpos) < 16f32.powi(2)))
|
||||||
})
|
})
|
||||||
.debug(move || format!("travel_to_site {:?}", tgt_site))
|
.debug(move || format!("travel_to_site {:?}", tgt_site))
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seconds
|
fn talk_to<S: State>(tgt: Actor, _subject: Option<Subject>) -> impl Action<S> {
|
||||||
fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
now(move |ctx, _| {
|
||||||
let mut timeout = None;
|
|
||||||
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn talk_to(tgt: Actor, _subject: Option<Subject>) -> impl Action {
|
|
||||||
now(move |ctx| {
|
|
||||||
if matches!(tgt, Actor::Npc(_)) && ctx.rng.gen_bool(0.2) {
|
if matches!(tgt, Actor::Npc(_)) && ctx.rng.gen_bool(0.2) {
|
||||||
// Cut off the conversation sometimes to avoid infinite conversations (but only
|
// Cut off the conversation sometimes to avoid infinite conversations (but only
|
||||||
// if the target is an NPC!) TODO: Don't special case this, have
|
// if the target is an NPC!) TODO: Don't special case this, have
|
||||||
@ -598,23 +618,23 @@ fn talk_to(tgt: Actor, _subject: Option<Subject>) -> impl Action {
|
|||||||
idle()
|
idle()
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(timeout(wait))
|
.stop_if(timeout(wait))
|
||||||
.then(just(move |ctx| ctx.controller.say(tgt, comment.clone())))
|
.then(just(move |ctx, _| ctx.controller.say(tgt, comment.clone())))
|
||||||
.r()
|
.r()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn socialize() -> impl Action {
|
fn socialize() -> impl Action<EveryRange> {
|
||||||
now(|ctx| {
|
now(move |ctx, socialize: &mut EveryRange| {
|
||||||
// Skip most socialising actions if we're not loaded
|
// Skip most socialising actions if we're not loaded
|
||||||
if matches!(ctx.npc.mode, SimulationMode::Loaded) && ctx.rng.gen_bool(0.002) {
|
if matches!(ctx.npc.mode, SimulationMode::Loaded) && socialize.should(ctx) {
|
||||||
// Sometimes dance
|
// Sometimes dance
|
||||||
if ctx.rng.gen_bool(0.15) {
|
if ctx.rng.gen_bool(0.15) {
|
||||||
return just(|ctx| ctx.controller.do_dance())
|
return just(|ctx, _| ctx.controller.do_dance())
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(timeout(6.0))
|
.stop_if(timeout(6.0))
|
||||||
.debug(|| "dancing")
|
.debug(|| "dancing")
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
.l()
|
.l()
|
||||||
.l();
|
.l();
|
||||||
// Talk to nearby NPCs
|
// Talk to nearby NPCs
|
||||||
@ -628,17 +648,16 @@ fn socialize() -> impl Action {
|
|||||||
return talk_to(other, None)
|
return talk_to(other, None)
|
||||||
// After talking, wait for a while
|
// After talking, wait for a while
|
||||||
.then(idle().repeat().stop_if(timeout(4.0)))
|
.then(idle().repeat().stop_if(timeout(4.0)))
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
.r().l();
|
.r().l();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idle().r()
|
idle().r()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adventure() -> impl Action {
|
fn adventure() -> impl Action<DefaultState> {
|
||||||
choose(|ctx| {
|
choose(|ctx, _| {
|
||||||
// Choose a random site that's fairly close by
|
// Choose a random site that's fairly close by
|
||||||
if let Some(tgt_site) = ctx
|
if let Some(tgt_site) = ctx
|
||||||
.state
|
.state
|
||||||
@ -670,11 +689,11 @@ fn adventure() -> impl Action {
|
|||||||
.map(|ws| ctx.index.sites.get(ws).name().to_string())
|
.map(|ws| ctx.index.sites.get(ws).name().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
// Travel to the site
|
// Travel to the site
|
||||||
important(just(move |ctx| ctx.controller.say(None, Content::localized_with_args("npc-speech-moving_on", [("site", site_name.clone())])))
|
important(just(move |ctx, _| ctx.controller.say(None, Content::localized_with_args("npc-speech-moving_on", [("site", site_name.clone())])))
|
||||||
.then(travel_to_site(tgt_site, 0.6))
|
.then(travel_to_site(tgt_site, 0.6))
|
||||||
// Stop for a few minutes
|
// Stop for a few minutes
|
||||||
.then(villager(tgt_site).repeat().stop_if(timeout(wait_time)))
|
.then(villager(tgt_site).repeat().stop_if(timeout(wait_time)))
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -684,8 +703,8 @@ fn adventure() -> impl Action {
|
|||||||
.debug(move || "adventure")
|
.debug(move || "adventure")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_ingredients() -> impl Action {
|
fn gather_ingredients<S: State>() -> impl Action<S> {
|
||||||
just(|ctx| {
|
just(|ctx, _| {
|
||||||
ctx.controller.do_gather(
|
ctx.controller.do_gather(
|
||||||
&[
|
&[
|
||||||
ChunkResource::Fruit,
|
ChunkResource::Fruit,
|
||||||
@ -697,8 +716,8 @@ fn gather_ingredients() -> impl Action {
|
|||||||
.debug(|| "gather ingredients")
|
.debug(|| "gather ingredients")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hunt_animals() -> impl Action {
|
fn hunt_animals<S: State>() -> impl Action<S> {
|
||||||
just(|ctx| ctx.controller.do_hunt_animals()).debug(|| "hunt_animals")
|
just(|ctx, _| ctx.controller.do_hunt_animals()).debug(|| "hunt_animals")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_forest(ctx: &mut NpcCtx) -> Option<Vec2<f32>> {
|
fn find_forest(ctx: &mut NpcCtx) -> Option<Vec2<f32>> {
|
||||||
@ -732,10 +751,10 @@ fn choose_plaza(ctx: &mut NpcCtx, site: SiteId) -> Option<Vec2<f32>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn villager(visiting_site: SiteId) -> impl Action {
|
fn villager(visiting_site: SiteId) -> impl Action<DefaultState> {
|
||||||
choose(move |ctx| {
|
choose(move |ctx, state: &mut DefaultState| {
|
||||||
// Consider moving home if the home site gets too full
|
// Consider moving home if the home site gets too full
|
||||||
if ctx.rng.gen_bool(0.0001)
|
if state.move_home_timer.should(ctx)
|
||||||
&& let Some(home) = ctx.npc.home
|
&& let Some(home) = ctx.npc.home
|
||||||
&& Some(home) == ctx.npc.current_site
|
&& Some(home) == ctx.npc.current_site
|
||||||
&& let Some(home_pop_ratio) = ctx.state.data().sites.get(home)
|
&& let Some(home_pop_ratio) = ctx.state.data().sites.get(home)
|
||||||
@ -769,20 +788,20 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
{
|
{
|
||||||
let site_name = ctx.state.data().sites[new_home].world_site
|
let site_name = ctx.state.data().sites[new_home].world_site
|
||||||
.map(|ws| ctx.index.sites.get(ws).name().to_string());
|
.map(|ws| ctx.index.sites.get(ws).name().to_string());
|
||||||
return important(just(move |ctx| {
|
return important(just(move |ctx, _| {
|
||||||
if let Some(site_name) = &site_name {
|
if let Some(site_name) = &site_name {
|
||||||
ctx.controller.say(None, Content::localized_with_args("npc-speech-migrating", [("site", site_name.clone())]))
|
ctx.controller.say(None, Content::localized_with_args("npc-speech-migrating", [("site", site_name.clone())]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(travel_to_site(new_home, 0.5))
|
.then(travel_to_site(new_home, 0.5))
|
||||||
.then(just(move |ctx| ctx.controller.set_new_home(new_home))));
|
.then(just(move |ctx, _| ctx.controller.set_new_home(new_home))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
if DayPeriod::from(ctx.time_of_day.0).is_dark()
|
||||||
&& !matches!(ctx.npc.profession(), Some(Profession::Guard))
|
&& !matches!(ctx.npc.profession(), Some(Profession::Guard))
|
||||||
{
|
{
|
||||||
return important(
|
return important(
|
||||||
now(move |ctx| {
|
now(move |ctx, _| {
|
||||||
if let Some(house_wpos) = ctx
|
if let Some(house_wpos) = ctx
|
||||||
.state
|
.state
|
||||||
.data()
|
.data()
|
||||||
@ -798,19 +817,19 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
Some(site2.tile_center_wpos(house.root_tile()).as_())
|
Some(site2.tile_center_wpos(house.root_tile()).as_())
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
just(|ctx| {
|
just(|ctx, _| {
|
||||||
ctx.controller
|
ctx.controller
|
||||||
.say(None, Content::localized("npc-speech-night_time"))
|
.say(None, Content::localized("npc-speech-night_time"))
|
||||||
})
|
})
|
||||||
.then(travel_to_point(house_wpos, 0.65))
|
.then(travel_to_point(house_wpos, 0.65))
|
||||||
.debug(|| "walk to house")
|
.debug(|| "walk to house")
|
||||||
.then(socialize().repeat().debug(|| "wait in house"))
|
.then(socialize().repeat().map_state(|state: &mut DefaultState| &mut state.socialize_timer).debug(|| "wait in house"))
|
||||||
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
.stop_if(|ctx: &mut NpcCtx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
||||||
.then(just(|ctx| {
|
.then(just(|ctx, _| {
|
||||||
ctx.controller
|
ctx.controller
|
||||||
.say(None, Content::localized("npc-speech-day_time"))
|
.say(None, Content::localized("npc-speech-day_time"))
|
||||||
}))
|
}))
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
.boxed()
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
finish().boxed()
|
finish().boxed()
|
||||||
@ -829,13 +848,13 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
let wait_time = ctx.rng.gen_range(10.0..30.0);
|
let wait_time = ctx.rng.gen_range(10.0..30.0);
|
||||||
gather_ingredients().repeat().stop_if(timeout(wait_time))
|
gather_ingredients().repeat().stop_if(timeout(wait_time))
|
||||||
})
|
})
|
||||||
.map(|_| ()),
|
.map(|_, _| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession(), Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
|
} else if matches!(ctx.npc.profession(), Some(Profession::Hunter)) && ctx.rng.gen_bool(0.8) {
|
||||||
if let Some(forest_wpos) = find_forest(ctx) {
|
if let Some(forest_wpos) = find_forest(ctx) {
|
||||||
return casual(
|
return casual(
|
||||||
just(|ctx| {
|
just(|ctx, _| {
|
||||||
ctx.controller
|
ctx.controller
|
||||||
.say(None, Content::localized("npc-speech-start_hunting"))
|
.say(None, Content::localized("npc-speech-start_hunting"))
|
||||||
})
|
})
|
||||||
@ -845,7 +864,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
let wait_time = ctx.rng.gen_range(30.0..60.0);
|
let wait_time = ctx.rng.gen_range(30.0..60.0);
|
||||||
hunt_animals().repeat().stop_if(timeout(wait_time))
|
hunt_animals().repeat().stop_if(timeout(wait_time))
|
||||||
})
|
})
|
||||||
.map(|_| ()),
|
.map(|_, _| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession(), Some(Profession::Guard)) && ctx.rng.gen_bool(0.7) {
|
} else if matches!(ctx.npc.profession(), Some(Profession::Guard)) && ctx.rng.gen_bool(0.7) {
|
||||||
@ -853,9 +872,9 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
return casual(
|
return casual(
|
||||||
travel_to_point(plaza_wpos, 0.4)
|
travel_to_point(plaza_wpos, 0.4)
|
||||||
.debug(|| "patrol")
|
.debug(|| "patrol")
|
||||||
.interrupt_with(|ctx| {
|
.interrupt_with(move |ctx, _| {
|
||||||
if ctx.rng.gen_bool(0.0003) {
|
if ctx.rng.gen_bool(0.0003) {
|
||||||
Some(just(move |ctx| {
|
Some(just(move |ctx, _| {
|
||||||
ctx.controller
|
ctx.controller
|
||||||
.say(None, Content::localized("npc-speech-guard_thought"))
|
.say(None, Content::localized("npc-speech-guard_thought"))
|
||||||
}))
|
}))
|
||||||
@ -863,13 +882,13 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|_| ()),
|
.map(|_, _| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if matches!(ctx.npc.profession(), Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8)
|
} else if matches!(ctx.npc.profession(), Some(Profession::Merchant)) && ctx.rng.gen_bool(0.8)
|
||||||
{
|
{
|
||||||
return casual(
|
return casual(
|
||||||
just(|ctx| {
|
just(|ctx, _| {
|
||||||
// Try to direct our speech at nearby actors, if there are any
|
// Try to direct our speech at nearby actors, if there are any
|
||||||
let (target, phrase) = if ctx.rng.gen_bool(0.3) && let Some(other) = ctx
|
let (target, phrase) = if ctx.rng.gen_bool(0.3) && let Some(other) = ctx
|
||||||
.state
|
.state
|
||||||
@ -890,12 +909,12 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(timeout(60.0))
|
.stop_if(timeout(60.0))
|
||||||
.debug(|| "sell wares")
|
.debug(|| "sell wares")
|
||||||
.map(|_| ()),
|
.map(|_, _| ()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If nothing else needs doing, walk between plazas and socialize
|
// 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) = choose_plaza(ctx, visiting_site) {
|
if let Some(plaza_wpos) = choose_plaza(ctx, visiting_site) {
|
||||||
// Walk to the plaza...
|
// Walk to the plaza...
|
||||||
@ -908,9 +927,10 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
// ...then socialize for some time before moving on
|
// ...then socialize for some time before moving on
|
||||||
.then(socialize()
|
.then(socialize()
|
||||||
.repeat()
|
.repeat()
|
||||||
|
.map_state(|state: &mut DefaultState| &mut state.socialize_timer)
|
||||||
.stop_if(timeout(ctx.rng.gen_range(30.0..90.0)))
|
.stop_if(timeout(ctx.rng.gen_range(30.0..90.0)))
|
||||||
.debug(|| "wait at plaza"))
|
.debug(|| "wait at plaza"))
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.debug(move || format!("villager at site {:?}", visiting_site))
|
.debug(move || format!("villager at site {:?}", visiting_site))
|
||||||
@ -937,9 +957,9 @@ fn follow(npc: NpcId, distance: f32) -> impl Action {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn pilot(ship: common::comp::ship::Body) -> impl Action {
|
fn pilot<S: State>(ship: common::comp::ship::Body) -> impl Action<S> {
|
||||||
// Travel between different towns in a straight line
|
// Travel between different towns in a straight line
|
||||||
now(move |ctx| {
|
now(move |ctx, _| {
|
||||||
let data = &*ctx.state.data();
|
let data = &*ctx.state.data();
|
||||||
let site = data
|
let site = data
|
||||||
.sites
|
.sites
|
||||||
@ -968,12 +988,12 @@ fn pilot(ship: common::comp::ship::Body) -> impl Action {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn captain() -> impl Action {
|
fn captain<S: State>() -> impl Action<S> {
|
||||||
// For now just randomly travel the sea
|
// For now just randomly travel the sea
|
||||||
now(|ctx| {
|
now(|ctx, _| {
|
||||||
let chunk = ctx.npc.wpos.xy().as_().wpos_to_cpos();
|
let chunk = ctx.npc.wpos.xy().as_().wpos_to_cpos();
|
||||||
if let Some(chunk) = NEIGHBORS
|
if let Some(chunk) = NEIGHBORS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -999,10 +1019,10 @@ fn captain() -> impl Action {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.map(|_| ())
|
.map(|_, _| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
fn check_inbox<S: State>(ctx: &mut NpcCtx) -> Option<impl Action<S>> {
|
||||||
loop {
|
loop {
|
||||||
match ctx.inbox.pop_front() {
|
match ctx.inbox.pop_front() {
|
||||||
Some(NpcInput::Report(report_id)) if !ctx.known_reports.contains(&report_id) => {
|
Some(NpcInput::Report(report_id)) if !ctx.known_reports.contains(&report_id) => {
|
||||||
@ -1047,8 +1067,10 @@ fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
|||||||
};
|
};
|
||||||
ctx.known_reports.insert(report_id);
|
ctx.known_reports.insert(report_id);
|
||||||
break Some(
|
break Some(
|
||||||
just(move |ctx| ctx.controller.say(killer, Content::localized(phrase)))
|
just(move |ctx, _| {
|
||||||
.l(),
|
ctx.controller.say(killer, Content::localized(phrase))
|
||||||
|
})
|
||||||
|
.l(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Some(ReportKind::Death { .. }) => {}, // We don't care about death
|
Some(ReportKind::Death { .. }) => {}, // We don't care about death
|
||||||
@ -1062,7 +1084,7 @@ fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_enemies(ctx: &mut NpcCtx) -> Option<impl Action> {
|
fn check_for_enemies<S: State>(ctx: &mut NpcCtx) -> Option<impl Action<S>> {
|
||||||
// TODO: Instead of checking all nearby actors every tick, it would be more
|
// TODO: Instead of checking all nearby actors every tick, it would be more
|
||||||
// effective to have the actor grid generate a per-tick diff so that we only
|
// effective to have the actor grid generate a per-tick diff so that we only
|
||||||
// need to check new actors in the local area. Be careful though:
|
// need to check new actors in the local area. Be careful though:
|
||||||
@ -1074,17 +1096,17 @@ fn check_for_enemies(ctx: &mut NpcCtx) -> Option<impl Action> {
|
|||||||
.npcs
|
.npcs
|
||||||
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 24.0)
|
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 24.0)
|
||||||
.find(|actor| ctx.sentiments.toward(*actor).is(Sentiment::ENEMY))
|
.find(|actor| ctx.sentiments.toward(*actor).is(Sentiment::ENEMY))
|
||||||
.map(|enemy| just(move |ctx| ctx.controller.attack(enemy)))
|
.map(|enemy| just(move |ctx, _| ctx.controller.attack(enemy)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn react_to_events(ctx: &mut NpcCtx) -> Option<impl Action> {
|
fn react_to_events<S: State>(ctx: &mut NpcCtx, _: &mut S) -> Option<impl Action<S>> {
|
||||||
check_inbox(ctx)
|
check_inbox::<S>(ctx)
|
||||||
.map(|action| action.boxed())
|
.map(|action| action.boxed())
|
||||||
.or_else(|| check_for_enemies(ctx).map(|action| action.boxed()))
|
.or_else(|| check_for_enemies(ctx).map(|action| action.boxed()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn humanoid() -> impl Action {
|
fn humanoid() -> impl Action<DefaultState> {
|
||||||
choose(|ctx| {
|
choose(|ctx, _| {
|
||||||
if let Some(riding) = &ctx.npc.riding {
|
if let Some(riding) = &ctx.npc.riding {
|
||||||
if riding.steering {
|
if riding.steering {
|
||||||
if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) {
|
if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) {
|
||||||
@ -1100,7 +1122,9 @@ fn humanoid() -> impl Action {
|
|||||||
casual(finish())
|
casual(finish())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
important(socialize())
|
important(
|
||||||
|
socialize().map_state(|state: &mut DefaultState| &mut state.socialize_timer),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let action = if matches!(
|
let action = if matches!(
|
||||||
@ -1119,8 +1143,8 @@ fn humanoid() -> impl Action {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bird_large() -> impl Action {
|
fn bird_large() -> impl Action<DefaultState> {
|
||||||
choose(|ctx| {
|
choose(|ctx, _| {
|
||||||
let data = ctx.state.data();
|
let data = ctx.state.data();
|
||||||
if let Some(home) = ctx.npc.home {
|
if let Some(home) = ctx.npc.home {
|
||||||
let is_home = ctx.npc.current_site.map_or(false, |site| home == site);
|
let is_home = ctx.npc.current_site.map_or(false, |site| home == site);
|
||||||
@ -1161,26 +1185,58 @@ fn bird_large() -> impl Action {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn monster() -> impl Action {
|
fn monster() -> impl Action<DefaultState> {
|
||||||
let mut bearing = Vec2::zero();
|
now(|ctx, bearing: &mut Vec2<f32>| {
|
||||||
now(move |ctx| {
|
*bearing = bearing
|
||||||
bearing = bearing
|
|
||||||
.map(|e| e + ctx.rng.gen_range(-0.1..0.1))
|
.map(|e| e + ctx.rng.gen_range(-0.1..0.1))
|
||||||
.try_normalized()
|
.try_normalized()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
goto_2d(ctx.npc.wpos.xy() + bearing * 24.0, 0.7, 8.0)
|
let bearing_dist = 24.0;
|
||||||
.debug(move || format!("Moving with a bearing of {:?}", bearing))
|
let mut pos = ctx.npc.wpos.xy() + *bearing * bearing_dist;
|
||||||
|
let is_deep_water = ctx
|
||||||
|
.world
|
||||||
|
.sim()
|
||||||
|
.get_interpolated(pos.as_(), |c| c.alt - c.water_alt)
|
||||||
|
.unwrap_or(f32::NEG_INFINITY)
|
||||||
|
< -10.0
|
||||||
|
&& ctx
|
||||||
|
.world
|
||||||
|
.sim()
|
||||||
|
.get(pos.as_().wpos_to_cpos())
|
||||||
|
.map_or(false, |c| c.river.is_ocean() || c.river.is_lake());
|
||||||
|
if !is_deep_water {
|
||||||
|
goto_2d(pos, 0.7, 8.0)
|
||||||
|
} else {
|
||||||
|
*bearing *= -1.0;
|
||||||
|
|
||||||
|
pos = ctx.npc.wpos.xy() + *bearing * 24.0;
|
||||||
|
|
||||||
|
goto_2d(pos, 0.7, 8.0)
|
||||||
|
}
|
||||||
|
// If we are too far away from our goal position we can stop since we aren't going to a specific place.
|
||||||
|
.stop_if(move |ctx: &mut NpcCtx| {
|
||||||
|
ctx.npc.wpos.xy().distance_squared(pos) > (bearing_dist + 5.0).powi(2)
|
||||||
|
})
|
||||||
|
.debug({
|
||||||
|
let bearing = *bearing;
|
||||||
|
move || format!("Moving with a bearing of {:?}", bearing)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.map(|_| ())
|
.with_state(Vec2::<f32>::zero())
|
||||||
|
.map(|_, _| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn think() -> impl Action {
|
fn think() -> impl Action<DefaultState> {
|
||||||
now(|ctx| match ctx.npc.body {
|
now(|ctx, _| match ctx.npc.body {
|
||||||
common::comp::Body::Humanoid(_) => humanoid().l().l().l(),
|
common::comp::Body::Humanoid(_) => humanoid().l().l().l(),
|
||||||
common::comp::Body::BirdLarge(_) => bird_large().r().l().l(),
|
common::comp::Body::BirdLarge(_) => bird_large().r().l().l(),
|
||||||
_ => match &ctx.npc.role {
|
_ => match &ctx.npc.role {
|
||||||
Role::Civilised(_) => socialize().l().r().l(),
|
Role::Civilised(_) => socialize()
|
||||||
|
.map_state(|state: &mut DefaultState| &mut state.socialize_timer)
|
||||||
|
.l()
|
||||||
|
.r()
|
||||||
|
.l(),
|
||||||
Role::Monster => monster().r().r().l(),
|
Role::Monster => monster().r().r().l(),
|
||||||
Role::Wild => idle().r(),
|
Role::Wild => idle().r(),
|
||||||
},
|
},
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
data::{npc::SimulationMode, Npc},
|
data::{npc::SimulationMode, Npc},
|
||||||
event::{EventCtx, OnDeath, OnSetup, OnTick},
|
event::{EventCtx, OnDeath, OnMountVolume, OnSetup, OnTick},
|
||||||
RtState, Rule, RuleError,
|
RtState, Rule, RuleError,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
|
mounting::{Volume, VolumePos},
|
||||||
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
||||||
terrain::CoordinateConversions,
|
terrain::{CoordinateConversions, TerrainChunkSize},
|
||||||
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
use vek::Vec2;
|
use vek::{Clamp, Vec2};
|
||||||
use world::site::SiteKind;
|
use world::site::SiteKind;
|
||||||
|
|
||||||
pub struct SimulateNpcs;
|
pub struct SimulateNpcs;
|
||||||
|
|
||||||
impl Rule for SimulateNpcs {
|
impl Rule for SimulateNpcs {
|
||||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||||
rtstate.bind::<Self, OnSetup>(on_setup);
|
rtstate.bind(on_setup);
|
||||||
rtstate.bind::<Self, OnDeath>(on_death);
|
rtstate.bind(on_death);
|
||||||
rtstate.bind::<Self, OnTick>(on_tick);
|
rtstate.bind(on_tick);
|
||||||
|
rtstate.bind(on_mount_volume);
|
||||||
|
|
||||||
Ok(Self)
|
Ok(Self)
|
||||||
}
|
}
|
||||||
@ -43,6 +46,17 @@ fn on_setup(ctx: EventCtx<SimulateNpcs, OnSetup>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_mount_volume(ctx: EventCtx<SimulateNpcs, OnMountVolume>) {
|
||||||
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
|
if let VolumePos { kind: Volume::Entity(vehicle), .. } = ctx.event.pos
|
||||||
|
&& let Some(vehicle) = data.npcs.vehicles.get(vehicle)
|
||||||
|
&& let Some(Actor::Npc(driver)) = vehicle.driver
|
||||||
|
&& let Some(driver) = data.npcs.get_mut(driver) {
|
||||||
|
driver.controller.actions.push(NpcAction::Say(Some(ctx.event.actor), comp::Content::localized("npc-speech-welcome-aboard")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
@ -238,6 +252,20 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
},
|
},
|
||||||
None => {},
|
None => {},
|
||||||
}
|
}
|
||||||
|
// Make sure NPCs remain on the surface and within the map
|
||||||
|
npc.wpos = npc
|
||||||
|
.wpos
|
||||||
|
.xy()
|
||||||
|
.clamped(
|
||||||
|
Vec2::zero(),
|
||||||
|
(ctx.world.sim().get_size() * TerrainChunkSize::RECT_SIZE).as_(),
|
||||||
|
)
|
||||||
|
.with_z(
|
||||||
|
ctx.world
|
||||||
|
.sim()
|
||||||
|
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||||
|
+ npc.body.flying_height(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consume NPC actions
|
// Consume NPC actions
|
||||||
@ -247,13 +275,6 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure NPCs remain on the surface
|
|
||||||
npc.wpos.z = ctx
|
|
||||||
.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
+ npc.body.flying_height();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move home if required
|
// Move home if required
|
||||||
@ -271,4 +292,24 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
npc.home = Some(new_home);
|
npc.home = Some(new_home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (_, vehicle) in data.npcs.vehicles.iter_mut() {
|
||||||
|
// Try to keep ships above ground and within the map
|
||||||
|
if matches!(vehicle.mode, SimulationMode::Simulated) {
|
||||||
|
vehicle.wpos = vehicle
|
||||||
|
.wpos
|
||||||
|
.xy()
|
||||||
|
.clamped(
|
||||||
|
Vec2::zero(),
|
||||||
|
(ctx.world.sim().get_size() * TerrainChunkSize::RECT_SIZE).as_(),
|
||||||
|
)
|
||||||
|
.with_z(
|
||||||
|
vehicle.wpos.z.max(
|
||||||
|
ctx.world
|
||||||
|
.sim()
|
||||||
|
.get_surface_alt_approx(vehicle.wpos.xy().as_()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,7 @@ impl Client {
|
|||||||
| ServerGeneral::ChatMsg(_)
|
| ServerGeneral::ChatMsg(_)
|
||||||
| ServerGeneral::ChatMode(_)
|
| ServerGeneral::ChatMode(_)
|
||||||
| ServerGeneral::SetPlayerEntity(_)
|
| ServerGeneral::SetPlayerEntity(_)
|
||||||
| ServerGeneral::TimeOfDay(_, _, _)
|
| ServerGeneral::TimeOfDay(_, _, _, _)
|
||||||
| ServerGeneral::EntitySync(_)
|
| ServerGeneral::EntitySync(_)
|
||||||
| ServerGeneral::CompSync(_, _)
|
| ServerGeneral::CompSync(_, _)
|
||||||
| ServerGeneral::CreateEntity(_)
|
| ServerGeneral::CreateEntity(_)
|
||||||
|
@ -44,7 +44,7 @@ use common::{
|
|||||||
npc::{self, get_npc_name},
|
npc::{self, get_npc_name},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
parse_cmd_args,
|
parse_cmd_args,
|
||||||
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay},
|
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
||||||
rtsim::{Actor, Role},
|
rtsim::{Actor, Role},
|
||||||
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
@ -186,6 +186,7 @@ fn do_command(
|
|||||||
ServerChatCommand::Sudo => handle_sudo,
|
ServerChatCommand::Sudo => handle_sudo,
|
||||||
ServerChatCommand::Tell => handle_tell,
|
ServerChatCommand::Tell => handle_tell,
|
||||||
ServerChatCommand::Time => handle_time,
|
ServerChatCommand::Time => handle_time,
|
||||||
|
ServerChatCommand::TimeScale => handle_time_scale,
|
||||||
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,
|
||||||
@ -1118,12 +1119,14 @@ fn handle_time(
|
|||||||
let mut tod_lazymsg = None;
|
let mut tod_lazymsg = None;
|
||||||
let clients = server.state.ecs().read_storage::<Client>();
|
let clients = server.state.ecs().read_storage::<Client>();
|
||||||
let calendar = server.state.ecs().read_resource::<Calendar>();
|
let calendar = server.state.ecs().read_resource::<Calendar>();
|
||||||
|
let time_scale = server.state.ecs().read_resource::<TimeScale>();
|
||||||
for client in (&clients).join() {
|
for client in (&clients).join() {
|
||||||
let msg = tod_lazymsg.unwrap_or_else(|| {
|
let msg = tod_lazymsg.unwrap_or_else(|| {
|
||||||
client.prepare(ServerGeneral::TimeOfDay(
|
client.prepare(ServerGeneral::TimeOfDay(
|
||||||
TimeOfDay(new_time),
|
TimeOfDay(new_time),
|
||||||
(*calendar).clone(),
|
(*calendar).clone(),
|
||||||
*time,
|
*time,
|
||||||
|
*time_scale,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
let _ = client.send_prepared(&msg);
|
let _ = client.send_prepared(&msg);
|
||||||
@ -1144,6 +1147,64 @@ fn handle_time(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_time_scale(
|
||||||
|
server: &mut Server,
|
||||||
|
client: EcsEntity,
|
||||||
|
_target: EcsEntity,
|
||||||
|
args: Vec<String>,
|
||||||
|
_action: &ServerChatCommand,
|
||||||
|
) -> CmdResult<()> {
|
||||||
|
let time_scale = server
|
||||||
|
.state
|
||||||
|
.ecs_mut()
|
||||||
|
.get_mut::<TimeScale>()
|
||||||
|
.expect("Expected time scale to be added.");
|
||||||
|
if args.is_empty() {
|
||||||
|
let time_scale = time_scale.0;
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ServerGeneral::server_msg(
|
||||||
|
ChatType::CommandInfo,
|
||||||
|
format!("The current time scale is {time_scale}."),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if let Some(scale) = parse_cmd_args!(args, f64) {
|
||||||
|
time_scale.0 = scale.clamp(0.0001, 1000.0);
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ServerGeneral::server_msg(ChatType::CommandInfo, format!("Set time scale to {scale}.")),
|
||||||
|
);
|
||||||
|
// Update all clients with the new TimeOfDay (without this they would have to
|
||||||
|
// wait for the next 100th tick to receive the update).
|
||||||
|
let mut tod_lazymsg = None;
|
||||||
|
let clients = server.state.ecs().read_storage::<Client>();
|
||||||
|
let time = server.state.ecs().read_resource::<Time>();
|
||||||
|
let time_of_day = server.state.ecs().read_resource::<TimeOfDay>();
|
||||||
|
let calendar = server.state.ecs().read_resource::<Calendar>();
|
||||||
|
for client in (&clients).join() {
|
||||||
|
let msg = tod_lazymsg.unwrap_or_else(|| {
|
||||||
|
client.prepare(ServerGeneral::TimeOfDay(
|
||||||
|
*time_of_day,
|
||||||
|
(*calendar).clone(),
|
||||||
|
*time,
|
||||||
|
TimeScale(scale),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
let _ = client.send_prepared(&msg);
|
||||||
|
tod_lazymsg = Some(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ServerGeneral::server_msg(
|
||||||
|
ChatType::CommandError,
|
||||||
|
"Wrong parameter, expected f32.".to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_health(
|
fn handle_health(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
_client: EcsEntity,
|
_client: EcsEntity,
|
||||||
@ -1302,6 +1363,7 @@ fn handle_rtsim_info(
|
|||||||
|
|
||||||
let _ = writeln!(&mut info, "-- General Information --");
|
let _ = writeln!(&mut info, "-- General Information --");
|
||||||
let _ = writeln!(&mut info, "Seed: {}", npc.seed);
|
let _ = writeln!(&mut info, "Seed: {}", npc.seed);
|
||||||
|
let _ = writeln!(&mut info, "Pos: {:?}", npc.wpos);
|
||||||
let _ = writeln!(&mut info, "Role: {:?}", npc.role);
|
let _ = writeln!(&mut info, "Role: {:?}", npc.role);
|
||||||
let _ = writeln!(&mut info, "Home: {:?}", npc.home);
|
let _ = writeln!(&mut info, "Home: {:?}", npc.home);
|
||||||
let _ = writeln!(&mut info, "Faction: {:?}", npc.faction);
|
let _ = writeln!(&mut info, "Faction: {:?}", npc.faction);
|
||||||
@ -1309,6 +1371,11 @@ fn handle_rtsim_info(
|
|||||||
let _ = writeln!(&mut info, "-- Status --");
|
let _ = writeln!(&mut info, "-- Status --");
|
||||||
let _ = writeln!(&mut info, "Current site: {:?}", npc.current_site);
|
let _ = writeln!(&mut info, "Current site: {:?}", npc.current_site);
|
||||||
let _ = writeln!(&mut info, "Current mode: {:?}", npc.mode);
|
let _ = writeln!(&mut info, "Current mode: {:?}", npc.mode);
|
||||||
|
let _ = writeln!(
|
||||||
|
&mut info,
|
||||||
|
"Riding: {:?}",
|
||||||
|
npc.riding.as_ref().map(|riding| riding.vehicle)
|
||||||
|
);
|
||||||
let _ = writeln!(&mut info, "-- Action State --");
|
let _ = writeln!(&mut info, "-- Action State --");
|
||||||
if let Some(brain) = &npc.brain {
|
if let Some(brain) = &npc.brain {
|
||||||
let mut bt = Vec::new();
|
let mut bt = Vec::new();
|
||||||
@ -1345,7 +1412,7 @@ fn handle_rtsim_npc(
|
|||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map(|s| s.trim().to_lowercase())
|
.map(|s| s.trim().to_lowercase())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let npc_names = &*common::npc::NPC_NAMES.read();
|
||||||
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
||||||
let data = rtsim.state().data();
|
let data = rtsim.state().data();
|
||||||
let mut npcs = data
|
let mut npcs = data
|
||||||
@ -1353,7 +1420,7 @@ fn handle_rtsim_npc(
|
|||||||
.values()
|
.values()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(idx, npc)| {
|
.filter(|(idx, npc)| {
|
||||||
let tags = [
|
let mut tags = vec![
|
||||||
npc.profession()
|
npc.profession()
|
||||||
.map(|p| format!("{:?}", p))
|
.map(|p| format!("{:?}", p))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@ -1364,7 +1431,11 @@ fn handle_rtsim_npc(
|
|||||||
},
|
},
|
||||||
format!("{:?}", npc.mode),
|
format!("{:?}", npc.mode),
|
||||||
format!("{}", idx),
|
format!("{}", idx),
|
||||||
|
npc_names[&npc.body].keyword.clone(),
|
||||||
];
|
];
|
||||||
|
if let Some(species_meta) = npc_names.get_species_meta(&npc.body) {
|
||||||
|
tags.push(species_meta.keyword.clone());
|
||||||
|
}
|
||||||
terms
|
terms
|
||||||
.iter()
|
.iter()
|
||||||
.all(|term| tags.iter().any(|tag| term.eq_ignore_ascii_case(tag.trim())))
|
.all(|term| tags.iter().any(|tag| term.eq_ignore_ascii_case(tag.trim())))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use specs::{world::WorldExt, Entity as EcsEntity, Join};
|
use specs::{saveload::MarkerAllocator, world::WorldExt, Entity as EcsEntity, Join};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
@ -19,19 +19,20 @@ use common::{
|
|||||||
link::Is,
|
link::Is,
|
||||||
mounting::{Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
|
mounting::{Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
|
rtsim::RtSimVehicle,
|
||||||
terrain::{Block, SpriteKind},
|
terrain::{Block, SpriteKind},
|
||||||
uid::Uid,
|
uid::{Uid, UidAllocator},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_net::sync::WorldSyncExt;
|
use common_net::sync::WorldSyncExt;
|
||||||
|
|
||||||
use crate::{state_ext::StateExt, Server, Time};
|
use crate::{rtsim::RtSim, state_ext::StateExt, Server, Time};
|
||||||
|
|
||||||
use crate::pet::tame_pet;
|
use crate::pet::tame_pet;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::iter::FromIterator;
|
use std::{iter::FromIterator, sync::Arc};
|
||||||
|
|
||||||
pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
|
pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
|
||||||
let ecs = server.state_mut().ecs();
|
let ecs = server.state_mut().ecs();
|
||||||
@ -169,11 +170,28 @@ pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: Vo
|
|||||||
let maybe_uid = state.ecs().read_storage::<Uid>().get(rider).copied();
|
let maybe_uid = state.ecs().read_storage::<Uid>().get(rider).copied();
|
||||||
|
|
||||||
if let Some(rider) = maybe_uid && within_range {
|
if let Some(rider) = maybe_uid && within_range {
|
||||||
let _ = state.link(VolumeMounting {
|
let _link_successful = state.link(VolumeMounting {
|
||||||
pos: volume_pos,
|
pos: volume_pos,
|
||||||
block,
|
block,
|
||||||
rider,
|
rider,
|
||||||
});
|
}).is_ok();
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
|
if _link_successful {
|
||||||
|
let uid_allocator = state.ecs().read_resource::<UidAllocator>();
|
||||||
|
if let Some(rider_entity) = uid_allocator.retrieve_entity_internal(rider.0)
|
||||||
|
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
|
||||||
|
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
|
||||||
|
let entity = uid_allocator.retrieve_entity_internal(uid.0)?;
|
||||||
|
state.read_storage::<RtSimVehicle>().get(entity).map(|v| v.0)
|
||||||
|
}) {
|
||||||
|
state.ecs().write_resource::<RtSim>().hook_character_mount_volume(
|
||||||
|
&state.ecs().read_resource::<Arc<world::World>>(),
|
||||||
|
state.ecs().read_resource::<world::IndexOwned>().as_index_ref(),
|
||||||
|
volume_pos,
|
||||||
|
rider_actor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ pub mod tick;
|
|||||||
use atomicwrites::{AtomicFile, OverwriteBehavior};
|
use atomicwrites::{AtomicFile, OverwriteBehavior};
|
||||||
use common::{
|
use common::{
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
|
mounting::VolumePos,
|
||||||
|
rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, VehicleId, WorldSettings},
|
||||||
};
|
};
|
||||||
use common_ecs::dispatch;
|
use common_ecs::dispatch;
|
||||||
use common_state::BlockDiff;
|
use common_state::BlockDiff;
|
||||||
@ -13,7 +14,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
|
|||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use rtsim::{
|
use rtsim::{
|
||||||
data::{npc::SimulationMode, Data, ReadError},
|
data::{npc::SimulationMode, Data, ReadError},
|
||||||
event::{OnDeath, OnSetup},
|
event::{OnDeath, OnMountVolume, OnSetup},
|
||||||
RtState,
|
RtState,
|
||||||
};
|
};
|
||||||
use specs::DispatcherBuilder;
|
use specs::DispatcherBuilder;
|
||||||
@ -147,6 +148,16 @@ impl RtSim {
|
|||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hook_character_mount_volume(
|
||||||
|
&mut self,
|
||||||
|
world: &World,
|
||||||
|
index: IndexRef,
|
||||||
|
pos: VolumePos<VehicleId>,
|
||||||
|
actor: Actor,
|
||||||
|
) {
|
||||||
|
self.state.emit(OnMountVolume { actor, pos }, world, index)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
|
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
|
||||||
if let Some(chunk_state) = self.state.get_resource_mut::<ChunkStates>().0.get_mut(key) {
|
if let Some(chunk_state) = self.state.get_resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||||
*chunk_state = Some(LoadedChunkState { max_res });
|
*chunk_state = Some(LoadedChunkState { max_res });
|
||||||
@ -167,14 +178,26 @@ impl RtSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
||||||
if let Some(npc) = self.state.get_data_mut().npcs.get_mut(entity.0) {
|
if let Some(npc) = self
|
||||||
|
.state
|
||||||
|
.get_data_mut()
|
||||||
|
.npcs
|
||||||
|
.get_mut(entity.0)
|
||||||
|
.filter(|npc| npc.riding.is_none())
|
||||||
|
{
|
||||||
npc.mode = SimulationMode::Simulated;
|
npc.mode = SimulationMode::Simulated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
||||||
if let Some(vehicle) = self.state.get_data_mut().npcs.vehicles.get_mut(entity.0) {
|
let data = self.state.get_data_mut();
|
||||||
|
if let Some(vehicle) = data.npcs.vehicles.get_mut(entity.0) {
|
||||||
vehicle.mode = SimulationMode::Simulated;
|
vehicle.mode = SimulationMode::Simulated;
|
||||||
|
if let Some(Actor::Npc(npc)) = vehicle.driver {
|
||||||
|
if let Some(npc) = data.npcs.get_mut(npc) {
|
||||||
|
npc.mode = SimulationMode::Simulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use common::{
|
|||||||
event::EventBus,
|
event::EventBus,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
region::{Event as RegionEvent, RegionMap},
|
region::{Event as RegionEvent, RegionMap},
|
||||||
resources::{PlayerPhysicsSettings, Time, TimeOfDay},
|
resources::{PlayerPhysicsSettings, Time, TimeOfDay, TimeScale},
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
@ -31,6 +31,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadExpect<'a, TimeOfDay>,
|
ReadExpect<'a, TimeOfDay>,
|
||||||
ReadExpect<'a, Time>,
|
ReadExpect<'a, Time>,
|
||||||
ReadExpect<'a, Calendar>,
|
ReadExpect<'a, Calendar>,
|
||||||
|
ReadExpect<'a, TimeScale>,
|
||||||
ReadExpect<'a, RegionMap>,
|
ReadExpect<'a, RegionMap>,
|
||||||
ReadExpect<'a, UpdateTrackers>,
|
ReadExpect<'a, UpdateTrackers>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
@ -63,6 +64,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
time_of_day,
|
time_of_day,
|
||||||
time,
|
time,
|
||||||
calendar,
|
calendar,
|
||||||
|
time_scale,
|
||||||
region_map,
|
region_map,
|
||||||
trackers,
|
trackers,
|
||||||
positions,
|
positions,
|
||||||
@ -418,6 +420,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
*time_of_day,
|
*time_of_day,
|
||||||
(*calendar).clone(),
|
(*calendar).clone(),
|
||||||
*time,
|
*time,
|
||||||
|
*time_scale,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
// We don't care much about stream errors here since they could just represent
|
// We don't care much about stream errors here since they could just represent
|
||||||
|
@ -1931,10 +1931,11 @@ impl WorldSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the altitude of the surface, could be water or ground.
|
/// Get the altitude of the surface, could be water or ground.
|
||||||
pub fn get_surface_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
|
pub fn get_surface_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
|
||||||
self.get_interpolated(wpos, |chunk| chunk.alt)
|
self.get_interpolated(wpos, |chunk| chunk.alt)
|
||||||
.zip(self.get_interpolated(wpos, |chunk| chunk.water_alt))
|
.zip(self.get_interpolated(wpos, |chunk| chunk.water_alt))
|
||||||
.map(|(alt, water_alt)| alt.max(water_alt))
|
.map(|(alt, water_alt)| alt.max(water_alt))
|
||||||
|
.unwrap_or(CONFIG.sea_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
|
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> Option<f32> {
|
||||||
|
@ -258,7 +258,7 @@ fn render_flat(bridge: &Bridge, painter: &Painter) {
|
|||||||
|
|
||||||
let len = bridge.dir.select(aabr.size());
|
let len = bridge.dir.select(aabr.size());
|
||||||
let true_offset = vault_width + vault_offset;
|
let true_offset = vault_width + vault_offset;
|
||||||
let n = len / true_offset;
|
let n = (len / true_offset).max(1);
|
||||||
let p = len / n;
|
let p = len / n;
|
||||||
|
|
||||||
let holes = painter
|
let holes = painter
|
||||||
|
Loading…
Reference in New Issue
Block a user