Allowed rtsim NPCs to greet nearby actors

This commit is contained in:
Joshua Barretto 2023-04-02 18:37:38 +01:00
parent 8d91ebb23e
commit 364255c7fe
38 changed files with 453 additions and 564 deletions

1
Cargo.lock generated
View File

@ -7032,6 +7032,7 @@ dependencies = [
"veloren-common-base", "veloren-common-base",
"veloren-common-dynlib", "veloren-common-dynlib",
"veloren-common-ecs", "veloren-common-ecs",
"veloren-common-net",
"veloren-rtsim", "veloren-rtsim",
] ]

View File

@ -30,7 +30,7 @@ use common::{
slot::{EquipSlot, InvSlotId, Slot}, slot::{EquipSlot, InvSlotId, Slot},
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent, GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
MapMarkerChange, UtteranceKind, MapMarkerChange, PresenceKind, UtteranceKind,
}, },
event::{EventBus, LocalEvent, UpdateCharacterMetadata}, event::{EventBus, LocalEvent, UpdateCharacterMetadata},
grid::Grid, grid::Grid,
@ -59,8 +59,8 @@ use common_net::{
self, self,
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo}, world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason, ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, PresenceKind, InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer, ServerGeneral, ServerInit, ServerRegisterAnswer,
}, },
sync::WorldSyncExt, sync::WorldSyncExt,
}; };

View File

@ -101,7 +101,7 @@ impl ClientMsg {
&self, &self,
c_type: ClientType, c_type: ClientType,
registered: bool, registered: bool,
presence: Option<super::PresenceKind>, presence: Option<comp::PresenceKind>,
) -> bool { ) -> bool {
match self { match self {
ClientMsg::Type(t) => c_type == *t, ClientMsg::Type(t) => c_type == *t,

View File

@ -19,23 +19,8 @@ pub use self::{
}, },
world_msg::WorldMapMsg, world_msg::WorldMapMsg,
}; };
use common::character::CharacterId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PresenceKind {
Spectator,
Character(CharacterId),
Possessor,
}
impl PresenceKind {
/// Check if the presence represents a control of a character, and thus
/// certain in-game messages from the client such as control inputs
/// should be handled.
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PingMsg { pub enum PingMsg {
Ping, Ping,

View File

@ -296,7 +296,7 @@ impl ServerMsg {
&self, &self,
c_type: ClientType, c_type: ClientType,
registered: bool, registered: bool,
presence: Option<super::PresenceKind>, presence: Option<comp::PresenceKind>,
) -> bool { ) -> bool {
match self { match self {
ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => { ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {

View File

@ -4,7 +4,7 @@ use crate::{
quadruped_small, ship, Body, UtteranceKind, quadruped_small, ship, Body, UtteranceKind,
}, },
path::Chaser, path::Chaser,
rtsim::{Memory, MemoryItem, RtSimController, RtSimEvent}, rtsim::RtSimController,
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
uid::Uid, uid::Uid,
}; };
@ -713,23 +713,6 @@ impl Agent {
} }
pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) } pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
pub fn forget_enemy(&mut self, target_name: &str) {
self.rtsim_controller
.events
.push(RtSimEvent::ForgetEnemy(target_name.to_owned()));
}
pub fn add_fight_to_memory(&mut self, target_name: &str, time: f64) {
self.rtsim_controller
.events
.push(RtSimEvent::AddMemory(Memory {
item: MemoryItem::CharacterFight {
name: target_name.to_owned(),
},
time_to_forget: time + 300.0,
}));
}
} }
impl Component for Agent { impl Component for Agent {

View File

@ -38,6 +38,8 @@ pub mod loot_owner;
#[cfg(not(target_arch = "wasm32"))] mod player; #[cfg(not(target_arch = "wasm32"))] mod player;
#[cfg(not(target_arch = "wasm32"))] pub mod poise; #[cfg(not(target_arch = "wasm32"))] pub mod poise;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod presence;
#[cfg(not(target_arch = "wasm32"))]
pub mod projectile; pub mod projectile;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod shockwave; pub mod shockwave;
@ -107,6 +109,7 @@ pub use self::{
player::DisconnectReason, player::DisconnectReason,
player::{AliasError, Player, MAX_ALIAS_LEN}, player::{AliasError, Player, MAX_ALIAS_LEN},
poise::{Poise, PoiseChange, PoiseState}, poise::{Poise, PoiseChange, PoiseState},
presence::{Presence, PresenceKind},
projectile::{Projectile, ProjectileConstructor}, projectile::{Projectile, ProjectileConstructor},
shockwave::{Shockwave, ShockwaveHitEntities}, shockwave::{Shockwave, ShockwaveHitEntities},
skillset::{ skillset::{

128
common/src/comp/presence.rs Normal file
View File

@ -0,0 +1,128 @@
use crate::{character::CharacterId, ViewDistances};
use serde::{Deserialize, Serialize};
use specs::Component;
use std::time::{Duration, Instant};
use vek::*;
#[derive(Debug)]
pub struct Presence {
pub terrain_view_distance: ViewDistance,
pub entity_view_distance: ViewDistance,
pub kind: PresenceKind,
pub lossy_terrain_compression: bool,
}
impl Presence {
pub fn new(view_distances: ViewDistances, kind: PresenceKind) -> Self {
let now = Instant::now();
Self {
terrain_view_distance: ViewDistance::new(view_distances.terrain, now),
entity_view_distance: ViewDistance::new(view_distances.entity, now),
kind,
lossy_terrain_compression: false,
}
}
}
impl Component for Presence {
type Storage = specs::DenseVecStorage<Self>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PresenceKind {
Spectator,
Character(CharacterId),
Possessor,
}
impl PresenceKind {
/// Check if the presence represents a control of a character, and thus
/// certain in-game messages from the client such as control inputs
/// should be handled.
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
}
#[derive(PartialEq, Debug, Clone, Copy)]
enum Direction {
Up,
Down,
}
/// Distance from the [Presence] from which the world is loaded and information
/// is synced to clients.
///
/// We limit the frequency that changes in the view distance change direction
/// (e.g. shifting from increasing the value to decreasing it). This is useful
/// since we want to avoid rapid cycles of shrinking and expanding of the view
/// distance.
#[derive(Debug)]
pub struct ViewDistance {
direction: Direction,
last_direction_change_time: Instant,
target: Option<u32>,
current: u32,
}
impl ViewDistance {
/// Minimum time allowed between changes in direction of value adjustments.
const TIME_PER_DIR_CHANGE: Duration = Duration::from_millis(300);
pub fn new(start_value: u32, now: Instant) -> Self {
Self {
direction: Direction::Up,
last_direction_change_time: now.checked_sub(Self::TIME_PER_DIR_CHANGE).unwrap_or(now),
target: None,
current: start_value,
}
}
/// Returns the current value.
pub fn current(&self) -> u32 { self.current }
/// Applies deferred change based on the whether the time to apply it has
/// been reached.
pub fn update(&mut self, now: Instant) {
if let Some(target_val) = self.target {
if now.saturating_duration_since(self.last_direction_change_time)
> Self::TIME_PER_DIR_CHANGE
{
self.last_direction_change_time = now;
self.current = target_val;
self.target = None;
}
}
}
/// Sets the target value.
///
/// If this hasn't been changed recently or it is in the same direction as
/// the previous change it will be applied immediately. Otherwise, it
/// will be deferred to a later time (limiting the frequency of changes
/// in the change direction).
pub fn set_target(&mut self, new_target: u32, now: Instant) {
use core::cmp::Ordering;
let new_direction = match new_target.cmp(&self.current) {
Ordering::Equal => return, // No change needed.
Ordering::Less => Direction::Down,
Ordering::Greater => Direction::Up,
};
// Change is in the same direction as before so we can just apply it.
if new_direction == self.direction {
self.current = new_target;
self.target = None;
// If it has already been a while since the last direction change we can
// directly apply the request and switch the direction.
} else if now.saturating_duration_since(self.last_direction_change_time)
> Self::TIME_PER_DIR_CHANGE
{
self.direction = new_direction;
self.last_direction_change_time = now;
self.current = new_target;
self.target = None;
// Otherwise, we need to defer the request.
} else {
self.target = Some(new_target);
}
}
}

View File

@ -3,14 +3,14 @@
// `Agent`). When possible, this should be moved to the `rtsim` // `Agent`). When possible, this should be moved to the `rtsim`
// module in `server`. // module in `server`.
use crate::character::CharacterId;
use rand::{seq::IteratorRandom, Rng}; use rand::{seq::IteratorRandom, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::Component; use specs::Component;
use std::collections::VecDeque;
use strum::{EnumIter, IntoEnumIterator}; use strum::{EnumIter, IntoEnumIterator};
use vek::*; use vek::*;
use crate::comp::dialogue::MoodState;
slotmap::new_key_type! { pub struct NpcId; } slotmap::new_key_type! { pub struct NpcId; }
slotmap::new_key_type! { pub struct VehicleId; } slotmap::new_key_type! { pub struct VehicleId; }
@ -26,6 +26,21 @@ impl Component for RtSimEntity {
type Storage = specs::VecStorage<Self>; type Storage = specs::VecStorage<Self>;
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum Actor {
Npc(NpcId),
Character(CharacterId),
}
impl Actor {
pub fn npc(&self) -> Option<NpcId> {
match self {
Actor::Npc(id) => Some(*id),
Actor::Character(_) => None,
}
}
}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct RtSimVehicle(pub VehicleId); pub struct RtSimVehicle(pub VehicleId);
@ -33,29 +48,6 @@ impl Component for RtSimVehicle {
type Storage = specs::VecStorage<Self>; type Storage = specs::VecStorage<Self>;
} }
#[derive(Clone, Debug)]
pub enum RtSimEvent {
AddMemory(Memory),
SetMood(Memory),
ForgetEnemy(String),
PrintMemories,
}
#[derive(Clone, Debug)]
pub struct Memory {
pub item: MemoryItem,
pub time_to_forget: f64,
}
#[derive(Clone, Debug)]
pub enum MemoryItem {
// These are structs to allow more data beyond name to be stored
// such as clothing worn, weapon used, etc.
CharacterInteraction { name: String },
CharacterFight { name: String },
Mood { state: MoodState },
}
#[derive(EnumIter, Clone, Copy)] #[derive(EnumIter, Clone, Copy)]
pub enum PersonalityTrait { pub enum PersonalityTrait {
Open, Open,
@ -210,8 +202,7 @@ pub struct RtSimController {
pub heading_to: Option<String>, pub heading_to: Option<String>,
/// Proportion of full speed to move /// Proportion of full speed to move
pub speed_factor: f32, pub speed_factor: f32,
/// Events pub actions: VecDeque<NpcAction>,
pub events: Vec<RtSimEvent>,
} }
impl Default for RtSimController { impl Default for RtSimController {
@ -221,7 +212,7 @@ impl Default for RtSimController {
personality: Personality::default(), personality: Personality::default(),
heading_to: None, heading_to: None,
speed_factor: 1.0, speed_factor: 1.0,
events: Vec::new(), actions: VecDeque::new(),
} }
} }
} }
@ -233,11 +224,16 @@ impl RtSimController {
personality: Personality::default(), personality: Personality::default(),
heading_to: None, heading_to: None,
speed_factor: 0.5, speed_factor: 0.5,
events: Vec::new(), actions: VecDeque::new(),
} }
} }
} }
#[derive(Clone, Copy, Debug)]
pub enum NpcAction {
Greet(Actor),
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, enum_map::Enum)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, enum_map::Enum)]
pub enum ChunkResource { pub enum ChunkResource {
#[serde(rename = "0")] #[serde(rename = "0")]

View File

@ -1,5 +1,4 @@
use super::Actor; pub use common::rtsim::{Actor, FactionId};
pub use common::rtsim::FactionId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap; use slotmap::HopSlotMap;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};

View File

@ -20,21 +20,6 @@ use std::{
marker::PhantomData, marker::PhantomData,
}; };
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Actor {
Npc(NpcId),
Character(common::character::CharacterId),
}
impl Actor {
pub fn npc(&self) -> Option<NpcId> {
match self {
Actor::Npc(id) => Some(*id),
Actor::Character(_) => None,
}
}
}
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Data { pub struct Data {
pub nature: Nature, pub nature: Nature,

View File

@ -3,7 +3,7 @@ pub use common::rtsim::{NpcId, Profession};
use common::{ use common::{
comp, comp,
grid::Grid, grid::Grid,
rtsim::{FactionId, Personality, SiteId, VehicleId}, rtsim::{Actor, FactionId, NpcAction, Personality, SiteId, VehicleId},
store::Id, store::Id,
vol::RectVolSize, vol::RectVolSize,
}; };
@ -21,8 +21,6 @@ use world::{
util::{RandomPerm, LOCALITY}, util::{RandomPerm, LOCALITY},
}; };
use super::Actor;
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]
pub enum SimulationMode { pub enum SimulationMode {
/// The NPC is unloaded and is being simulated via rtsim. /// The NPC is unloaded and is being simulated via rtsim.
@ -45,24 +43,21 @@ pub struct PathingMemory {
pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>, pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
} }
#[derive(Clone, Copy)] #[derive(Default)]
pub enum NpcAction {
/// (wpos, speed_factor)
Goto(Vec3<f32>, f32),
}
pub struct Controller { pub struct Controller {
pub action: Option<NpcAction>, pub actions: Vec<NpcAction>,
/// (wpos, speed_factor)
pub goto: Option<(Vec3<f32>, f32)>,
} }
impl Controller { impl Controller {
pub fn idle() -> Self { Self { action: None } } pub fn do_idle(&mut self) { self.goto = None; }
pub fn goto(wpos: Vec3<f32>, speed_factor: f32) -> Self { pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
Self { self.goto = Some((wpos, speed_factor));
action: Some(NpcAction::Goto(wpos, speed_factor)),
}
} }
pub fn do_greet(&mut self, actor: Actor) { self.actions.push(NpcAction::Greet(actor)); }
} }
pub struct Brain { pub struct Brain {
@ -91,7 +86,7 @@ pub struct Npc {
pub current_site: Option<SiteId>, pub current_site: Option<SiteId>,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub action: Option<NpcAction>, pub controller: Controller,
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the /// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
/// server, loaded corresponds to being within a loaded chunk). When in /// server, loaded corresponds to being within a loaded chunk). When in
@ -118,7 +113,7 @@ impl Clone for Npc {
// Not persisted // Not persisted
chunk_pos: None, chunk_pos: None,
current_site: Default::default(), current_site: Default::default(),
action: Default::default(), controller: Default::default(),
mode: Default::default(), mode: Default::default(),
brain: Default::default(), brain: Default::default(),
} }
@ -138,7 +133,7 @@ impl Npc {
riding: None, riding: None,
chunk_pos: None, chunk_pos: None,
current_site: None, current_site: None,
action: None, controller: Controller::default(),
mode: SimulationMode::Simulated, mode: SimulationMode::Simulated,
brain: None, brain: None,
} }
@ -248,6 +243,7 @@ impl Vehicle {
#[derive(Default, Clone, Serialize, Deserialize)] #[derive(Default, Clone, Serialize, Deserialize)]
pub struct GridCell { pub struct GridCell {
pub npcs: Vec<NpcId>, pub npcs: Vec<NpcId>,
pub characters: Vec<common::character::CharacterId>,
pub vehicles: Vec<VehicleId>, pub vehicles: Vec<VehicleId>,
} }
@ -269,23 +265,33 @@ impl Npcs {
} }
/// Queries nearby npcs, not garantueed to work if radius > 32.0 /// Queries nearby npcs, not garantueed to work if radius > 32.0
pub fn nearby(&self, wpos: Vec2<f32>, radius: f32) -> impl Iterator<Item = NpcId> + '_ { pub fn nearby(&self, wpos: Vec2<f32>, radius: f32) -> impl Iterator<Item = Actor> + '_ {
let chunk_pos = let chunk_pos =
wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>(); wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>();
let r_sqr = radius * radius; let r_sqr = radius * radius;
LOCALITY LOCALITY
.into_iter() .into_iter()
.filter_map(move |neighbor| { .flat_map(move |neighbor| {
self.npc_grid.get(chunk_pos + neighbor).map(|cell| { self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
cell.npcs cell.npcs
.iter() .iter()
.copied() .copied()
.filter(|npc| { .filter(move |npc| {
self.npcs self.npcs
.get(*npc) .get(*npc)
.map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr) .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
}) })
.collect::<Vec<_>>() .map(Actor::Npc)
.chain(cell.characters
.iter()
.copied()
// TODO: Filter characters by distance too
// .filter(move |npc| {
// self.npcs
// .get(*npc)
// .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
// })
.map(Actor::Character))
}) })
}) })
.flatten() .flatten()

View File

@ -3,7 +3,7 @@ use std::hash::BuildHasherDefault;
use crate::{ use crate::{
ai::{casual, choose, finish, important, just, now, seq, until, urgent, Action, NpcCtx}, ai::{casual, choose, finish, important, just, now, seq, until, urgent, Action, NpcCtx},
data::{ data::{
npc::{Brain, Controller, PathData}, npc::{Brain, PathData},
Sites, Sites,
}, },
event::OnTick, event::OnTick,
@ -217,7 +217,7 @@ impl Rule for NpcAi {
data.npcs data.npcs
.iter_mut() .iter_mut()
.map(|(npc_id, npc)| { .map(|(npc_id, npc)| {
let controller = Controller { action: npc.action }; let controller = std::mem::take(&mut npc.controller);
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()),
}); });
@ -251,7 +251,7 @@ impl Rule for NpcAi {
// Reinsert NPC brains // Reinsert NPC brains
let mut data = ctx.state.data_mut(); let mut data = ctx.state.data_mut();
for (npc_id, controller, brain) in npc_data { for (npc_id, controller, brain) in npc_data {
data.npcs[npc_id].action = controller.action; data.npcs[npc_id].controller = controller;
data.npcs[npc_id].brain = Some(brain); data.npcs[npc_id].brain = Some(brain);
} }
}); });
@ -260,7 +260,7 @@ impl Rule for NpcAi {
} }
} }
fn idle() -> impl Action { just(|ctx| *ctx.controller = Controller::idle()).debug(|| "idle") } fn idle() -> impl Action { 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(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
@ -292,7 +292,7 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
) )
}); });
*ctx.controller = Controller::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| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2))
@ -452,6 +452,24 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time) move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
} }
fn socialize() -> impl Action {
just(|ctx| {
let mut rng = thread_rng();
// TODO: Bit odd, should wait for a while after greeting
if thread_rng().gen_bool(0.0002) {
if let Some(other) = ctx
.state
.data()
.npcs
.nearby(ctx.npc.wpos.xy(), 8.0)
.choose(&mut rng)
{
ctx.controller.do_greet(other);
}
}
})
}
fn adventure() -> impl Action { fn adventure() -> impl Action {
choose(|ctx| { choose(|ctx| {
// Choose a random site that's fairly close by // Choose a random site that's fairly close by
@ -540,7 +558,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
{ {
travel_to_point(house_wpos) travel_to_point(house_wpos)
.debug(|| "walk to house") .debug(|| "walk to house")
.then(idle().repeat().debug(|| "wait in house")) .then(socialize().repeat().debug(|| "wait in house"))
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light()) .stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
.map(|_| ()) .map(|_| ())
.boxed() .boxed()
@ -570,7 +588,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
// ...then wait for some time before moving on // ...then wait for some time before moving on
.then({ .then({
let wait_time = thread_rng().gen_range(10.0..30.0); let wait_time = thread_rng().gen_range(10.0..30.0);
idle().repeat().stop_if(timeout(wait_time)) socialize().repeat().stop_if(timeout(wait_time))
.debug(|| "wait at plaza") .debug(|| "wait at plaza")
}) })
.map(|_| ()) .map(|_| ())
@ -735,7 +753,7 @@ fn humanoid() -> impl Action {
casual(finish()) casual(finish())
} }
} else { } else {
important(idle()) important(socialize())
} }
} else if matches!( } else if matches!(
ctx.npc.profession, ctx.npc.profession,
@ -806,6 +824,6 @@ fn think() -> impl Action {
choose(|ctx| match ctx.npc.body { choose(|ctx| match ctx.npc.body {
common::comp::Body::Humanoid(_) => casual(humanoid()), common::comp::Body::Humanoid(_) => casual(humanoid()),
common::comp::Body::BirdLarge(_) => casual(bird_large()), common::comp::Body::BirdLarge(_) => casual(bird_large()),
_ => casual(idle()), _ => casual(socialize()),
}) })
} }

View File

@ -6,7 +6,7 @@ use crate::{
use common::{ use common::{
comp::{self, Body}, comp::{self, Body},
grid::Grid, grid::Grid,
rtsim::Personality, rtsim::{Actor, Personality},
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
vol::RectVolSize, vol::RectVolSize,
}; };
@ -25,7 +25,7 @@ impl Rule for SimulateNpcs {
for (npc_id, npc) in data.npcs.npcs.iter() { for (npc_id, npc) in data.npcs.npcs.iter() {
if let Some(ride) = &npc.riding { if let Some(ride) = &npc.riding {
if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) { if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) {
let actor = crate::data::Actor::Npc(npc_id); let actor = Actor::Npc(npc_id);
vehicle.riders.push(actor); vehicle.riders.push(actor);
if ride.steering && vehicle.driver.replace(actor).is_some() { if ride.steering && vehicle.driver.replace(actor).is_some() {
panic!("Replaced driver"); panic!("Replaced driver");
@ -153,9 +153,12 @@ impl Rule for SimulateNpcs {
.world .world
.sim() .sim()
.get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_()) .get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
.and_then(|chunk| chunk.sites .and_then(|chunk| {
.iter() chunk
.find_map(|site| data.sites.world_site_map.get(site).copied())); .sites
.iter()
.find_map(|site| data.sites.world_site_map.get(site).copied())
});
let chunk_pos = let chunk_pos =
npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>(); npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
@ -176,83 +179,98 @@ impl Rule for SimulateNpcs {
// Simulate the NPC's movement and interactions // Simulate the NPC's movement and interactions
if matches!(npc.mode, SimulationMode::Simulated) { if matches!(npc.mode, SimulationMode::Simulated) {
if let Some(riding) = &npc.riding { // Move NPCs if they have a target destination
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) { if let Some((target, speed_factor)) = npc.controller.goto {
if let Some(action) = npc.action && riding.steering { // Simulate NPC movement when riding
match action { if let Some(riding) = &npc.riding {
crate::data::npc::NpcAction::Goto(target, speed_factor) => { if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
let diff = target.xy() - vehicle.wpos.xy(); // If steering, the NPC controls the vehicle's motion
let dist2 = diff.magnitude_squared(); if riding.steering {
let diff = target.xy() - vehicle.wpos.xy();
let dist2 = diff.magnitude_squared();
if dist2 > 0.5f32.powi(2) { if dist2 > 0.5f32.powi(2) {
let mut wpos = vehicle.wpos + (diff let mut wpos = vehicle.wpos
* (vehicle.get_speed() * speed_factor * ctx.event.dt + (diff
* (vehicle.get_speed()
* speed_factor
* ctx.event.dt
/ dist2.sqrt()) / dist2.sqrt())
.min(1.0)) .min(1.0))
.with_z(0.0); .with_z(0.0);
let is_valid = match vehicle.body { let is_valid = match vehicle.body {
common::comp::ship::Body::DefaultAirship | common::comp::ship::Body::AirBalloon => true, common::comp::ship::Body::DefaultAirship
common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => { | common::comp::ship::Body::AirBalloon => true,
let chunk_pos = wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>(); common::comp::ship::Body::SailBoat
ctx.world.sim().get(chunk_pos).map_or(true, |f| f.river.river_kind.is_some()) | common::comp::ship::Body::Galleon => {
}, let chunk_pos = wpos.xy().as_::<i32>()
_ => false, / TerrainChunkSize::RECT_SIZE.as_::<i32>();
}; ctx.world
.sim()
.get(chunk_pos)
.map_or(true, |f| f.river.river_kind.is_some())
},
_ => false,
};
if is_valid { if is_valid {
match vehicle.body { match vehicle.body {
common::comp::ship::Body::DefaultAirship | common::comp::ship::Body::AirBalloon => { common::comp::ship::Body::DefaultAirship
if let Some(alt) = ctx.world.sim().get_alt_approx(wpos.xy().as_()).filter(|alt| wpos.z < *alt) { | common::comp::ship::Body::AirBalloon => {
wpos.z = alt; if let Some(alt) = ctx
} .world
}, .sim()
common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => { .get_alt_approx(wpos.xy().as_())
wpos.z = ctx .filter(|alt| wpos.z < *alt)
.world {
.sim() wpos.z = alt;
.get_interpolated(wpos.xy().map(|e| e as i32), |chunk| chunk.water_alt) }
.unwrap_or(0.0); },
}, common::comp::ship::Body::SailBoat
_ => {}, | common::comp::ship::Body::Galleon => {
} wpos.z = ctx
vehicle.wpos = wpos; .world
.sim()
.get_interpolated(
wpos.xy().map(|e| e as i32),
|chunk| chunk.water_alt,
)
.unwrap_or(0.0);
},
_ => {},
} }
vehicle.wpos = wpos;
} }
} }
} }
npc.wpos = vehicle.wpos;
} else {
// Vehicle doens't exist anymore
npc.riding = None;
} }
npc.wpos = vehicle.wpos; // If not riding, we assume they're just walking
} else { } else {
// Vehicle doens't exist anymore let diff = target.xy() - npc.wpos.xy();
npc.riding = None; let dist2 = diff.magnitude_squared();
if dist2 > 0.5f32.powi(2) {
npc.wpos += (diff
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
/ dist2.sqrt())
.min(1.0))
.with_z(0.0);
}
} }
} }
// Move NPCs if they have a target destination
else if let Some(action) = npc.action {
match action {
crate::data::npc::NpcAction::Goto(target, speed_factor) => {
let diff = target.xy() - npc.wpos.xy();
let dist2 = diff.magnitude_squared();
if dist2 > 0.5f32.powi(2) {
npc.wpos += (diff
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
/ dist2.sqrt())
.min(1.0))
.with_z(0.0);
}
},
}
// 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();
}
// 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();
} }
} }
}); });

View File

@ -11,6 +11,7 @@ be-dyn-lib = []
[dependencies] [dependencies]
common = { package = "veloren-common", path = "../../common"} common = { package = "veloren-common", path = "../../common"}
common-base = { package = "veloren-common-base", path = "../../common/base" } common-base = { package = "veloren-common-base", path = "../../common/base" }
common-net = { package = "veloren-common-net", path = "../../common/net" }
common-ecs = { package = "veloren-common-ecs", path = "../../common/ecs" } common-ecs = { package = "veloren-common-ecs", path = "../../common/ecs" }
common-dynlib = { package = "veloren-common-dynlib", path = "../../common/dynlib", optional = true} common-dynlib = { package = "veloren-common-dynlib", path = "../../common/dynlib", optional = true}
rtsim = { package = "veloren-rtsim", path = "../../rtsim" } rtsim = { package = "veloren-rtsim", path = "../../rtsim" }

View File

@ -229,10 +229,11 @@ impl<'a> AgentData<'a> {
controller.push_cancel_input(InputKind::Fly) controller.push_cancel_input(InputKind::Fly)
} }
let chase_tgt = *travel_to/*read_data.terrain let chase_tgt = read_data
.terrain
.try_find_space(travel_to.as_()) .try_find_space(travel_to.as_())
.map(|pos| pos.as_()) .map(|pos| pos.as_())
.unwrap_or(*travel_to)*/; .unwrap_or(*travel_to);
if let Some((bearing, speed)) = agent.chaser.chase( if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain, &*read_data.terrain,
@ -1460,11 +1461,12 @@ impl<'a> AgentData<'a> {
self.idle(agent, controller, read_data, rng); self.idle(agent, controller, read_data, rng);
} else { } else {
let target_data = TargetData::new(tgt_pos, target, read_data); let target_data = TargetData::new(tgt_pos, target, read_data);
if let Some(tgt_name) = // TODO: Reimplement this in rtsim
read_data.stats.get(target).map(|stats| stats.name.clone()) // if let Some(tgt_name) =
{ // read_data.stats.get(target).map(|stats| stats.name.clone())
agent.add_fight_to_memory(&tgt_name, read_data.time.0) // {
} // agent.add_fight_to_memory(&tgt_name, read_data.time.0)
// }
self.attack(agent, controller, &target_data, read_data, rng); self.attack(agent, controller, &target_data, read_data, rng);
} }
} }

View File

@ -6,21 +6,21 @@ use common::{
group, group,
item::MaterialStatManifest, item::MaterialStatManifest,
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory, ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, Stats, LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, PresenceKind, Scale,
Vel, SkillSet, Stance, Stats, Vel,
}, },
link::Is, link::Is,
mounting::{Mount, Rider}, mounting::{Mount, Rider},
path::TraversalConfig, path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay}, resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity, rtsim::{Actor, RtSimEntity},
states::utils::{ForcedMovement, StageSection}, states::utils::{ForcedMovement, StageSection},
terrain::TerrainGrid, terrain::TerrainGrid,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
}; };
use specs::{ use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
World, SystemData, World,
}; };
// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate // TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
@ -246,6 +246,25 @@ pub struct ReadData<'a> {
pub msm: ReadExpect<'a, MaterialStatManifest>, pub msm: ReadExpect<'a, MaterialStatManifest>,
pub poises: ReadStorage<'a, Poise>, pub poises: ReadStorage<'a, Poise>,
pub stances: ReadStorage<'a, Stance>, pub stances: ReadStorage<'a, Stance>,
pub presences: ReadStorage<'a, Presence>,
}
impl<'a> ReadData<'a> {
pub fn lookup_actor(&self, actor: Actor) -> Option<EcsEntity> {
// TODO: We really shouldn't be doing a linear search here. The only saving
// grace is that the set of entities that fit each case should be
// *relatively* small.
match actor {
Actor::Character(character_id) => (&self.entities, &self.presences)
.join()
.find(|(_, p)| p.kind == PresenceKind::Character(character_id))
.map(|(entity, _)| entity),
Actor::Npc(npc_id) => (&self.entities, &self.rtsim_entities)
.join()
.find(|(_, e)| e.0 == npc_id)
.map(|(entity, _)| entity),
}
}
} }
pub enum Path { pub enum Path {

View File

@ -5,7 +5,6 @@ use crate::{
client::Client, client::Client,
location::Locations, location::Locations,
login_provider::LoginProvider, login_provider::LoginProvider,
presence::Presence,
settings::{ settings::{
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord, Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
}, },
@ -31,7 +30,7 @@ use common::{
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource}, buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
inventory::item::{tool::AbilityMap, MaterialStatManifest, Quality}, inventory::item::{tool::AbilityMap, MaterialStatManifest, Quality},
invite::InviteKind, invite::InviteKind,
AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea, AdminRole, ChatType, Inventory, Item, LightEmitter, Presence, PresenceKind, WaypointArea,
}, },
depot, depot,
effect::Effect, effect::Effect,
@ -49,7 +48,7 @@ use common::{
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect, weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
}; };
use common_net::{ use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, PresenceKind, ServerGeneral}, msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
sync::WorldSyncExt, sync::WorldSyncExt,
}; };
use common_state::{BuildAreaError, BuildAreas}; use common_state::{BuildAreaError, BuildAreas};

View File

@ -1,16 +1,16 @@
use super::Event; use super::Event;
use crate::{ use crate::{
client::Client, metrics::PlayerMetrics, persistence::character_updater::CharacterUpdater, client::Client, metrics::PlayerMetrics, persistence::character_updater::CharacterUpdater,
presence::Presence, state_ext::StateExt, BattleModeBuffer, Server, state_ext::StateExt, BattleModeBuffer, Server,
}; };
use common::{ use common::{
character::CharacterId, character::CharacterId,
comp, comp,
comp::{group, pet::is_tameable}, comp::{group, pet::is_tameable, Presence, PresenceKind},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
}; };
use common_base::span; use common_base::span;
use common_net::msg::{PlayerListUpdate, PresenceKind, ServerGeneral}; use common_net::msg::{PlayerListUpdate, ServerGeneral};
use common_state::State; use common_state::State;
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt}; use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
use tracing::{debug, error, trace, warn, Instrument}; use tracing::{debug, error, trace, warn, Instrument};

View File

@ -62,7 +62,7 @@ use crate::{
location::Locations, location::Locations,
login_provider::LoginProvider, login_provider::LoginProvider,
persistence::PersistedComponents, persistence::PersistedComponents,
presence::{Presence, RegionSubscription, RepositionOnChunkLoad}, presence::{RegionSubscription, RepositionOnChunkLoad},
state_ext::StateExt, state_ext::StateExt,
sys::sentinel::DeletedEntities, sys::sentinel::DeletedEntities,
}; };
@ -376,7 +376,7 @@ impl Server {
// Server-only components // Server-only components
state.ecs_mut().register::<RegionSubscription>(); state.ecs_mut().register::<RegionSubscription>();
state.ecs_mut().register::<Client>(); state.ecs_mut().register::<Client>();
state.ecs_mut().register::<Presence>(); state.ecs_mut().register::<comp::Presence>();
state.ecs_mut().register::<wiring::WiringElement>(); state.ecs_mut().register::<wiring::WiringElement>();
state.ecs_mut().register::<wiring::Circuit>(); state.ecs_mut().register::<wiring::Circuit>();
state.ecs_mut().register::<Anchor>(); state.ecs_mut().register::<Anchor>();
@ -833,7 +833,7 @@ impl Server {
( (
&self.state.ecs().entities(), &self.state.ecs().entities(),
&self.state.ecs().read_storage::<comp::Pos>(), &self.state.ecs().read_storage::<comp::Pos>(),
!&self.state.ecs().read_storage::<Presence>(), !&self.state.ecs().read_storage::<comp::Presence>(),
self.state.ecs().read_storage::<Anchor>().maybe(), self.state.ecs().read_storage::<Anchor>().maybe(),
) )
.join() .join()

View File

@ -1,34 +1,8 @@
use common_net::msg::PresenceKind;
use hashbrown::HashSet; use hashbrown::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, NullStorage}; use specs::{Component, NullStorage};
use std::time::{Duration, Instant};
use vek::*; use vek::*;
#[derive(Debug)]
pub struct Presence {
pub terrain_view_distance: ViewDistance,
pub entity_view_distance: ViewDistance,
pub kind: PresenceKind,
pub lossy_terrain_compression: bool,
}
impl Presence {
pub fn new(view_distances: common::ViewDistances, kind: PresenceKind) -> Self {
let now = Instant::now();
Self {
terrain_view_distance: ViewDistance::new(view_distances.terrain, now),
entity_view_distance: ViewDistance::new(view_distances.entity, now),
kind,
lossy_terrain_compression: false,
}
}
}
impl Component for Presence {
type Storage = specs::DenseVecStorage<Self>;
}
// Distance from fuzzy_chunk before snapping to current chunk // Distance from fuzzy_chunk before snapping to current chunk
pub const CHUNK_FUZZ: u32 = 2; pub const CHUNK_FUZZ: u32 = 2;
// Distance out of the range of a region before removing it from subscriptions // Distance out of the range of a region before removing it from subscriptions
@ -51,88 +25,3 @@ pub struct RepositionOnChunkLoad;
impl Component for RepositionOnChunkLoad { impl Component for RepositionOnChunkLoad {
type Storage = NullStorage<Self>; type Storage = NullStorage<Self>;
} }
#[derive(PartialEq, Debug, Clone, Copy)]
enum Direction {
Up,
Down,
}
/// Distance from the [Presence] from which the world is loaded and information
/// is synced to clients.
///
/// We limit the frequency that changes in the view distance change direction
/// (e.g. shifting from increasing the value to decreasing it). This is useful
/// since we want to avoid rapid cycles of shrinking and expanding of the view
/// distance.
#[derive(Debug)]
pub struct ViewDistance {
direction: Direction,
last_direction_change_time: Instant,
target: Option<u32>,
current: u32,
}
impl ViewDistance {
/// Minimum time allowed between changes in direction of value adjustments.
const TIME_PER_DIR_CHANGE: Duration = Duration::from_millis(300);
pub fn new(start_value: u32, now: Instant) -> Self {
Self {
direction: Direction::Up,
last_direction_change_time: now.checked_sub(Self::TIME_PER_DIR_CHANGE).unwrap_or(now),
target: None,
current: start_value,
}
}
/// Returns the current value.
pub fn current(&self) -> u32 { self.current }
/// Applies deferred change based on the whether the time to apply it has
/// been reached.
pub fn update(&mut self, now: Instant) {
if let Some(target_val) = self.target {
if now.saturating_duration_since(self.last_direction_change_time)
> Self::TIME_PER_DIR_CHANGE
{
self.last_direction_change_time = now;
self.current = target_val;
self.target = None;
}
}
}
/// Sets the target value.
///
/// If this hasn't been changed recently or it is in the same direction as
/// the previous change it will be applied immediately. Otherwise, it
/// will be deferred to a later time (limiting the frequency of changes
/// in the change direction).
pub fn set_target(&mut self, new_target: u32, now: Instant) {
use core::cmp::Ordering;
let new_direction = match new_target.cmp(&self.current) {
Ordering::Equal => return, // No change needed.
Ordering::Less => Direction::Down,
Ordering::Greater => Direction::Up,
};
// Change is in the same direction as before so we can just apply it.
if new_direction == self.direction {
self.current = new_target;
self.target = None;
// If it has already been a while since the last direction change we can
// directly apply the request and switch the direction.
} else if now.saturating_duration_since(self.last_direction_change_time)
> Self::TIME_PER_DIR_CHANGE
{
self.direction = new_direction;
self.last_direction_change_time = now;
self.current = new_target;
self.target = None;
// Otherwise, we need to defer the request.
} else {
self.target = Some(new_target);
}
}
}

View File

@ -7,7 +7,7 @@ use common::{
event::{EventBus, NpcBuilder, ServerEvent}, event::{EventBus, NpcBuilder, ServerEvent},
generation::{BodyBuilder, EntityConfig, EntityInfo}, generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time, TimeOfDay}, resources::{DeltaTime, Time, TimeOfDay},
rtsim::{RtSimEntity, RtSimVehicle}, rtsim::{Actor, RtSimEntity, RtSimVehicle},
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::CoordinateConversions, terrain::CoordinateConversions,
trade::{Good, SiteInformation}, trade::{Good, SiteInformation},
@ -16,7 +16,7 @@ use common::{
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use rtsim::data::{ use rtsim::data::{
npc::{Profession, SimulationMode}, npc::{Profession, SimulationMode},
Actor, Npc, Sites, Npc, Sites,
}; };
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
@ -366,17 +366,17 @@ impl<'a> System<'a> for Sys {
// Update entity state // Update entity state
if let Some(agent) = agent { if let Some(agent) = agent {
agent.rtsim_controller.personality = npc.personality; agent.rtsim_controller.personality = npc.personality;
if let Some(action) = npc.action { if let Some((wpos, speed_factor)) = npc.controller.goto {
match action { agent.rtsim_controller.travel_to = Some(wpos);
rtsim::data::npc::NpcAction::Goto(wpos, sf) => { agent.rtsim_controller.speed_factor = speed_factor;
agent.rtsim_controller.travel_to = Some(wpos);
agent.rtsim_controller.speed_factor = sf;
},
}
} else { } else {
agent.rtsim_controller.travel_to = None; agent.rtsim_controller.travel_to = None;
agent.rtsim_controller.speed_factor = 1.0; agent.rtsim_controller.speed_factor = 1.0;
} }
agent
.rtsim_controller
.actions
.extend(std::mem::take(&mut npc.controller.actions));
} }
}); });
} }

View File

@ -4,7 +4,7 @@ use crate::{
events::{self, update_map_markers}, events::{self, update_map_markers},
persistence::PersistedComponents, persistence::PersistedComponents,
pet::restore_pet, pet::restore_pet,
presence::{Presence, RepositionOnChunkLoad}, presence::RepositionOnChunkLoad,
rtsim::RtSim, rtsim::RtSim,
settings::Settings, settings::Settings,
sys::sentinel::DeletedEntities, sys::sentinel::DeletedEntities,
@ -19,7 +19,7 @@ use common::{
self, self,
item::{ItemKind, MaterialStatManifest}, item::{ItemKind, MaterialStatManifest},
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
ChatType, Group, Inventory, Item, Player, Poise, ChatType, Group, Inventory, Item, Player, Poise, Presence, PresenceKind,
}, },
effect::Effect, effect::Effect,
link::{Link, LinkHandle}, link::{Link, LinkHandle},
@ -30,7 +30,7 @@ use common::{
LoadoutBuilder, ViewDistances, LoadoutBuilder, ViewDistances,
}; };
use common_net::{ use common_net::{
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral}, msg::{CharacterInfo, PlayerListUpdate, ServerGeneral},
sync::WorldSyncExt, sync::WorldSyncExt,
}; };
use common_state::State; use common_state::State;

View File

@ -9,7 +9,7 @@ use common::{
}, },
event::{Emitter, ServerEvent}, event::{Emitter, ServerEvent},
path::TraversalConfig, path::TraversalConfig,
rtsim::RtSimEntity, rtsim::{NpcAction, RtSimEntity},
}; };
use rand::{prelude::ThreadRng, thread_rng, Rng}; use rand::{prelude::ThreadRng, thread_rng, Rng};
use specs::{ use specs::{
@ -160,7 +160,11 @@ impl BehaviorTree {
/// Idle BehaviorTree /// Idle BehaviorTree
pub fn idle() -> Self { pub fn idle() -> Self {
Self { Self {
tree: vec![set_owner_if_no_target, handle_timed_events], tree: vec![
set_owner_if_no_target,
handle_rtsim_actions,
handle_timed_events,
],
} }
} }
@ -464,6 +468,42 @@ fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
false false
} }
/// Handle action requests from rtsim, such as talking to NPCs or attacking
fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
if let Some(action) = bdata.agent.rtsim_controller.actions.pop_front() {
match action {
NpcAction::Greet(actor) => {
if bdata.agent.allowed_to_speak() {
if let Some(target) = bdata.read_data.lookup_actor(actor) {
let target_pos = bdata.read_data.positions.get(target).map(|pos| pos.0);
bdata.agent.target = Some(Target::new(
target,
false,
bdata.read_data.time.0,
false,
target_pos,
));
if bdata.agent_data.look_toward(
&mut bdata.controller,
&bdata.read_data,
target,
) {
bdata.controller.push_utterance(UtteranceKind::Greeting);
bdata.controller.push_action(ControlAction::Talk);
bdata
.agent_data
.chat_npc("npc-speech-villager", &mut bdata.event_emitter);
}
}
}
},
}
}
false
}
/// Handle timed events, like looking at the player we are talking to /// Handle timed events, like looking at the player we are talking to
fn handle_timed_events(bdata: &mut BehaviorData) -> bool { fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) { let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) {
@ -746,9 +786,11 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
if aggro_on { if aggro_on {
let target_data = TargetData::new(tgt_pos, target, read_data); let target_data = TargetData::new(tgt_pos, target, read_data);
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone()); // let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
tgt_name.map(|tgt_name| agent.add_fight_to_memory(&tgt_name, read_data.time.0)); // TODO: Reimplement in rtsim2
// tgt_name.map(|tgt_name| agent.add_fight_to_memory(&tgt_name,
// read_data.time.0));
agent_data.attack(agent, controller, &target_data, read_data, rng); agent_data.attack(agent, controller, &target_data, read_data, rng);
} else { } else {
agent_data.menacing( agent_data.menacing(
@ -760,7 +802,9 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
rng, rng,
remembers_fight_with(agent_data.rtsim_entity, read_data, target), remembers_fight_with(agent_data.rtsim_entity, read_data, target),
); );
remember_fight(agent_data.rtsim_entity, read_data, agent, target); // TODO: Reimplement in rtsim2
// remember_fight(agent_data.rtsim_entity, read_data, agent,
// target);
} }
} }
} }
@ -784,17 +828,17 @@ fn remembers_fight_with(
false false
} }
/// Remember target. // /// Remember target.
fn remember_fight( // fn remember_fight(
rtsim_entity: Option<&RtSimEntity>, // rtsim_entity: Option<&RtSimEntity>,
read_data: &ReadData, // read_data: &ReadData,
agent: &mut Agent, // agent: &mut Agent,
target: EcsEntity, // target: EcsEntity,
) { // ) {
rtsim_entity.is_some().then(|| { // rtsim_entity.is_some().then(|| {
read_data // read_data
.stats // .stats
.get(target) // .get(target)
.map(|stats| agent.add_fight_to_memory(&stats.name, read_data.time.0)) // .map(|stats| agent.add_fight_to_memory(&stats.name,
}); // read_data.time.0)) });
} // }

View File

@ -9,7 +9,7 @@ use common::{
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind, BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
}, },
event::ServerEvent, event::ServerEvent,
rtsim::{Memory, MemoryItem, PersonalityTrait, RtSimEvent}, rtsim::PersonalityTrait,
trade::{TradeAction, TradePhase, TradeResult}, trade::{TradeAction, TradePhase, TradeResult},
}; };
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -106,15 +106,6 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
match subject { match subject {
Subject::Regular => { Subject::Regular => {
if let Some(tgt_stats) = read_data.stats.get(target) { if let Some(tgt_stats) = read_data.stats.get(target) {
agent
.rtsim_controller
.events
.push(RtSimEvent::AddMemory(Memory {
item: MemoryItem::CharacterInteraction {
name: tgt_stats.name.clone(),
},
time_to_forget: read_data.time.0 + 600.0,
}));
if let Some(destination_name) = &agent.rtsim_controller.heading_to { if let Some(destination_name) = &agent.rtsim_controller.heading_to {
let personality = &agent.rtsim_controller.personality; let personality = &agent.rtsim_controller.personality;
let standard_response_msg = || -> String { let standard_response_msg = || -> String {
@ -252,6 +243,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
} }
}, },
Subject::Mood => { Subject::Mood => {
// TODO: Reimplement in rtsim2
/* /*
if let Some(rtsim_entity) = &bdata.rtsim_entity { if let Some(rtsim_entity) = &bdata.rtsim_entity {
if !rtsim_entity.brain.remembers_mood() { if !rtsim_entity.brain.remembers_mood() {

View File

@ -1,169 +0,0 @@
// use crate::rtsim::Entity as RtSimData;
use common::{
comp::{
buff::Buffs, group, item::MaterialStatManifest, ActiveAbilities, Alignment, Body,
CharacterState, Combo, Energy, Health, Inventory, LightEmitter, LootOwner, Ori,
PhysicsState, Pos, Scale, SkillSet, Stats, Vel,
},
link::Is,
mounting::Mount,
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity,
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
};
use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
World,
};
use std::sync::Arc;
pub struct AgentData<'a> {
pub entity: &'a EcsEntity,
pub rtsim_entity: Option<&'a RtSimEntity>,
//pub rtsim_entity: Option<&'a RtSimData>,
pub uid: &'a Uid,
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub energy: &'a Energy,
pub body: Option<&'a Body>,
pub inventory: &'a Inventory,
pub skill_set: &'a SkillSet,
#[allow(dead_code)] // may be useful for pathing
pub physics_state: &'a PhysicsState,
pub alignment: Option<&'a Alignment>,
pub traversal_config: TraversalConfig,
pub scale: f32,
pub damage: f32,
pub light_emitter: Option<&'a LightEmitter>,
pub glider_equipped: bool,
pub is_gliding: bool,
pub health: Option<&'a Health>,
pub char_state: &'a CharacterState,
pub active_abilities: &'a ActiveAbilities,
pub cached_spatial_grid: &'a common::CachedSpatialGrid,
pub msm: &'a MaterialStatManifest,
}
pub struct TargetData<'a> {
pub pos: &'a Pos,
pub body: Option<&'a Body>,
pub scale: Option<&'a Scale>,
}
impl<'a> TargetData<'a> {
pub fn new(pos: &'a Pos, body: Option<&'a Body>, scale: Option<&'a Scale>) -> Self {
Self { pos, body, scale }
}
}
pub struct AttackData {
pub min_attack_dist: f32,
pub dist_sqrd: f32,
pub angle: f32,
pub angle_xy: f32,
}
impl AttackData {
pub fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
}
#[derive(Eq, PartialEq)]
// When adding a new variant, first decide if it should instead fall under one
// of the pre-existing tactics
pub enum Tactic {
// General tactics
SimpleMelee,
SimpleBackstab,
ElevatedRanged,
Turret,
FixedTurret,
RotatingTurret,
RadialTurret,
// Tool specific tactics
Axe,
Hammer,
Sword,
Bow,
Staff,
Sceptre,
// Broad creature tactics
CircleCharge { radius: u32, circle_time: u32 },
QuadLowRanged,
TailSlap,
QuadLowQuick,
QuadLowBasic,
QuadLowBeam,
QuadMedJump,
QuadMedBasic,
Theropod,
BirdLargeBreathe,
BirdLargeFire,
BirdLargeBasic,
ArthropodMelee,
ArthropodRanged,
ArthropodAmbush,
// Specific species tactics
Mindflayer,
Minotaur,
ClayGolem,
TidalWarrior,
Yeti,
Harvester,
StoneGolem,
Deadwood,
Mandragora,
WoodGolem,
GnarlingChieftain,
OrganAura,
Dagon,
Cardinal,
}
#[derive(SystemData)]
pub struct ReadData<'a> {
pub entities: Entities<'a>,
pub uid_allocator: Read<'a, UidAllocator>,
pub dt: Read<'a, DeltaTime>,
pub time: Read<'a, Time>,
pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
pub group_manager: Read<'a, group::GroupManager>,
pub energies: ReadStorage<'a, Energy>,
pub positions: ReadStorage<'a, Pos>,
pub velocities: ReadStorage<'a, Vel>,
pub orientations: ReadStorage<'a, Ori>,
pub scales: ReadStorage<'a, Scale>,
pub healths: ReadStorage<'a, Health>,
pub inventories: ReadStorage<'a, Inventory>,
pub stats: ReadStorage<'a, Stats>,
pub skill_set: ReadStorage<'a, SkillSet>,
pub physics_states: ReadStorage<'a, PhysicsState>,
pub char_states: ReadStorage<'a, CharacterState>,
pub uids: ReadStorage<'a, Uid>,
pub groups: ReadStorage<'a, group::Group>,
pub terrain: ReadExpect<'a, TerrainGrid>,
pub alignments: ReadStorage<'a, Alignment>,
pub bodies: ReadStorage<'a, Body>,
pub is_mounts: ReadStorage<'a, Is<Mount>>,
pub time_of_day: Read<'a, TimeOfDay>,
pub light_emitter: ReadStorage<'a, LightEmitter>,
#[cfg(feature = "worldgen")]
pub world: ReadExpect<'a, Arc<world::World>>,
pub rtsim_entity: ReadStorage<'a, RtSimEntity>,
pub buffs: ReadStorage<'a, Buffs>,
pub combos: ReadStorage<'a, Combo>,
pub active_abilities: ReadStorage<'a, ActiveAbilities>,
pub loot_owners: ReadStorage<'a, LootOwner>,
pub msm: ReadExpect<'a, MaterialStatManifest>,
}
pub enum Path {
Full,
Separate,
Partial,
}

View File

@ -2,10 +2,9 @@ use crate::{
chunk_serialize::{ChunkSendEntry, SerializedChunk}, chunk_serialize::{ChunkSendEntry, SerializedChunk},
client::Client, client::Client,
metrics::NetworkRequestMetrics, metrics::NetworkRequestMetrics,
presence::Presence,
Tick, Tick,
}; };
use common::{event::EventBus, slowjob::SlowJobPool, terrain::TerrainGrid}; use common::{comp::Presence, event::EventBus, slowjob::SlowJobPool, terrain::TerrainGrid};
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{SerializedTerrainChunk, ServerGeneral}; use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
use hashbrown::{hash_map::Entry, HashMap}; use hashbrown::{hash_map::Entry, HashMap};

View File

@ -1,12 +1,8 @@
use super::sentinel::{DeletedEntities, TrackedStorages, UpdateTrackers}; use super::sentinel::{DeletedEntities, TrackedStorages, UpdateTrackers};
use crate::{ use crate::{client::Client, presence::RegionSubscription, Tick};
client::Client,
presence::{Presence, RegionSubscription},
Tick,
};
use common::{ use common::{
calendar::Calendar, calendar::Calendar,
comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Player, Pos, Vel}, comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Player, Pos, Presence, Vel},
event::EventBus, event::EventBus,
outcome::Outcome, outcome::Outcome,
region::{Event as RegionEvent, RegionMap}, region::{Event as RegionEvent, RegionMap},

View File

@ -8,11 +8,10 @@ use crate::{
character_creator, character_creator,
client::Client, client::Client,
persistence::{character_loader::CharacterLoader, character_updater::CharacterUpdater}, persistence::{character_loader::CharacterLoader, character_updater::CharacterUpdater},
presence::Presence,
EditableSettings, EditableSettings,
}; };
use common::{ use common::{
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg, Waypoint}, comp::{Admin, AdminRole, ChatType, Player, Presence, UnresolvedChatMsg, Waypoint},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::Time, resources::Time,
terrain::TerrainChunkSize, terrain::TerrainChunkSize,

View File

@ -1,10 +1,10 @@
#[cfg(feature = "persistent_world")] #[cfg(feature = "persistent_world")]
use crate::TerrainPersistence; use crate::TerrainPersistence;
use crate::{client::Client, presence::Presence, Settings}; use crate::{client::Client, Settings};
use common::{ use common::{
comp::{ comp::{
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
Pos, SkillSet, Vel, Pos, Presence, PresenceKind, SkillSet, Vel,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
link::Is, link::Is,
@ -15,7 +15,7 @@ use common::{
vol::ReadVol, vol::ReadVol,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral}; use common_net::msg::{ClientGeneral, ServerGeneral};
use common_state::{BlockChange, BuildAreas}; use common_state::{BlockChange, BuildAreas};
use core::mem; use core::mem;
use rayon::prelude::*; use rayon::prelude::*;

View File

@ -1,9 +1,9 @@
use crate::{ use crate::{
chunk_serialize::ChunkSendEntry, client::Client, lod::Lod, metrics::NetworkRequestMetrics, chunk_serialize::ChunkSendEntry, client::Client, lod::Lod, metrics::NetworkRequestMetrics,
presence::Presence, ChunkRequest, ChunkRequest,
}; };
use common::{ use common::{
comp::Pos, comp::{Pos, Presence},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
spiral::Spiral2d, spiral::Spiral2d,
terrain::{CoordinateConversions, TerrainChunkSize, TerrainGrid}, terrain::{CoordinateConversions, TerrainChunkSize, TerrainGrid},

View File

@ -1,13 +1,13 @@
use crate::{persistence::character_updater, presence::Presence, sys::SysScheduler}; use crate::{persistence::character_updater, sys::SysScheduler};
use common::{ use common::{
comp::{ comp::{
pet::{is_tameable, Pet}, pet::{is_tameable, Pet},
ActiveAbilities, Alignment, Body, Inventory, MapMarker, SkillSet, Stats, Waypoint, ActiveAbilities, Alignment, Body, Inventory, MapMarker, Presence, PresenceKind, SkillSet,
Stats, Waypoint,
}, },
uid::Uid, uid::Uid,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::PresenceKind;
use specs::{Join, ReadStorage, Write, WriteExpect}; use specs::{Join, ReadStorage, Write, WriteExpect};
#[derive(Default)] #[derive(Default)]

View File

@ -1,10 +1,10 @@
use super::sentinel::{DeletedEntities, TrackedStorages}; use super::sentinel::{DeletedEntities, TrackedStorages};
use crate::{ use crate::{
client::Client, client::Client,
presence::{self, Presence, RegionSubscription}, presence::{self, RegionSubscription},
}; };
use common::{ use common::{
comp::{Ori, Pos, Vel}, comp::{Ori, Pos, Presence, Vel},
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap}, region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
terrain::{CoordinateConversions, TerrainChunkSize}, terrain::{CoordinateConversions, TerrainChunkSize},
uid::Uid, uid::Uid,

View File

@ -6,18 +6,14 @@ use crate::TerrainPersistence;
use world::{IndexOwned, World}; use world::{IndexOwned, World};
use crate::{ use crate::{
chunk_generator::ChunkGenerator, chunk_generator::ChunkGenerator, chunk_serialize::ChunkSendEntry, client::Client,
chunk_serialize::ChunkSendEntry, presence::RepositionOnChunkLoad, rtsim, settings::Settings, ChunkRequest, Tick,
client::Client,
presence::{Presence, RepositionOnChunkLoad},
rtsim,
settings::Settings,
ChunkRequest, Tick,
}; };
use common::{ use common::{
calendar::Calendar, calendar::Calendar,
comp::{ comp::{
self, agent, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate, Pos, Waypoint, self, agent, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate, Pos, Presence,
Waypoint,
}, },
event::{EventBus, NpcBuilder, ServerEvent}, event::{EventBus, NpcBuilder, ServerEvent},
generation::EntityInfo, generation::EntityInfo,

View File

@ -1,5 +1,8 @@
use crate::{chunk_serialize::ChunkSendEntry, client::Client, presence::Presence, Settings}; use crate::{chunk_serialize::ChunkSendEntry, client::Client, Settings};
use common::{comp::Pos, event::EventBus}; use common::{
comp::{Pos, Presence},
event::EventBus,
};
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{CompressedData, ServerGeneral}; use common_net::msg::{CompressedData, ServerGeneral};
use common_state::TerrainChanges; use common_state::TerrainChanges;

View File

@ -99,7 +99,7 @@ use common::{
loot_owner::LootOwnerKind, loot_owner::LootOwnerKind,
pet::is_mountable, pet::is_mountable,
skillset::{skills::Skill, SkillGroupKind, SkillsPersistenceError}, skillset::{skills::Skill, SkillGroupKind, SkillsPersistenceError},
BuffData, BuffKind, Health, Item, MapMarkerChange, BuffData, BuffKind, Health, Item, MapMarkerChange, PresenceKind,
}, },
consts::MAX_PICKUP_RANGE, consts::MAX_PICKUP_RANGE,
link::Is, link::Is,
@ -115,7 +115,7 @@ use common::{
}; };
use common_base::{prof_span, span}; use common_base::{prof_span, span};
use common_net::{ use common_net::{
msg::{world_msg::SiteId, Notification, PresenceKind}, msg::{world_msg::SiteId, Notification},
sync::WorldSyncExt, sync::WorldSyncExt,
}; };
use conrod_core::{ use conrod_core::{

View File

@ -38,7 +38,6 @@ use common::{
vol::ReadVol, vol::ReadVol,
}; };
use common_base::{prof_span, span}; use common_base::{prof_span, span};
use common_net::msg::PresenceKind;
use common_state::State; use common_state::State;
use comp::item::Reagent; use comp::item::Reagent;
use hashbrown::HashMap; use hashbrown::HashMap;
@ -311,7 +310,7 @@ impl Scene {
let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context); let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
let camera_mode = match client.presence() { let camera_mode = match client.presence() {
Some(PresenceKind::Spectator) => CameraMode::Freefly, Some(comp::PresenceKind::Spectator) => CameraMode::Freefly,
_ => CameraMode::ThirdPerson, _ => CameraMode::ThirdPerson,
}; };

View File

@ -18,7 +18,8 @@ use common::{
inventory::slot::{EquipSlot, Slot}, inventory::slot::{EquipSlot, Slot},
invite::InviteKind, invite::InviteKind,
item::{tool::ToolKind, ItemDesc}, item::{tool::ToolKind, ItemDesc},
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Stats, UtteranceKind, Vel, ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, PresenceKind, Stats,
UtteranceKind, Vel,
}, },
consts::MAX_MOUNT_RANGE, consts::MAX_MOUNT_RANGE,
event::UpdateCharacterMetadata, event::UpdateCharacterMetadata,
@ -32,10 +33,7 @@ use common::{
vol::ReadVol, vol::ReadVol,
}; };
use common_base::{prof_span, span}; use common_base::{prof_span, span};
use common_net::{ use common_net::{msg::server::InviteAnswer, sync::WorldSyncExt};
msg::{server::InviteAnswer, PresenceKind},
sync::WorldSyncExt,
};
use crate::{ use crate::{
audio::sfx::SfxEvent, audio::sfx::SfxEvent,