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-dynlib",
"veloren-common-ecs",
"veloren-common-net",
"veloren-rtsim",
]

View File

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

View File

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

View File

@ -19,23 +19,8 @@ pub use self::{
},
world_msg::WorldMapMsg,
};
use common::character::CharacterId;
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)]
pub enum PingMsg {
Ping,

View File

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

View File

@ -4,7 +4,7 @@ use crate::{
quadruped_small, ship, Body, UtteranceKind,
},
path::Chaser,
rtsim::{Memory, MemoryItem, RtSimController, RtSimEvent},
rtsim::RtSimController,
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
uid::Uid,
};
@ -713,23 +713,6 @@ impl Agent {
}
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 {

View File

@ -38,6 +38,8 @@ pub mod loot_owner;
#[cfg(not(target_arch = "wasm32"))] mod player;
#[cfg(not(target_arch = "wasm32"))] pub mod poise;
#[cfg(not(target_arch = "wasm32"))]
pub mod presence;
#[cfg(not(target_arch = "wasm32"))]
pub mod projectile;
#[cfg(not(target_arch = "wasm32"))]
pub mod shockwave;
@ -107,6 +109,7 @@ pub use self::{
player::DisconnectReason,
player::{AliasError, Player, MAX_ALIAS_LEN},
poise::{Poise, PoiseChange, PoiseState},
presence::{Presence, PresenceKind},
projectile::{Projectile, ProjectileConstructor},
shockwave::{Shockwave, ShockwaveHitEntities},
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`
// module in `server`.
use crate::character::CharacterId;
use rand::{seq::IteratorRandom, Rng};
use serde::{Deserialize, Serialize};
use specs::Component;
use std::collections::VecDeque;
use strum::{EnumIter, IntoEnumIterator};
use vek::*;
use crate::comp::dialogue::MoodState;
slotmap::new_key_type! { pub struct NpcId; }
slotmap::new_key_type! { pub struct VehicleId; }
@ -26,6 +26,21 @@ impl Component for RtSimEntity {
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)]
pub struct RtSimVehicle(pub VehicleId);
@ -33,29 +48,6 @@ impl Component for RtSimVehicle {
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)]
pub enum PersonalityTrait {
Open,
@ -210,8 +202,7 @@ pub struct RtSimController {
pub heading_to: Option<String>,
/// Proportion of full speed to move
pub speed_factor: f32,
/// Events
pub events: Vec<RtSimEvent>,
pub actions: VecDeque<NpcAction>,
}
impl Default for RtSimController {
@ -221,7 +212,7 @@ impl Default for RtSimController {
personality: Personality::default(),
heading_to: None,
speed_factor: 1.0,
events: Vec::new(),
actions: VecDeque::new(),
}
}
}
@ -233,11 +224,16 @@ impl RtSimController {
personality: Personality::default(),
heading_to: None,
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)]
pub enum ChunkResource {
#[serde(rename = "0")]

View File

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

View File

@ -20,21 +20,6 @@ use std::{
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)]
pub struct Data {
pub nature: Nature,

View File

@ -3,7 +3,7 @@ pub use common::rtsim::{NpcId, Profession};
use common::{
comp,
grid::Grid,
rtsim::{FactionId, Personality, SiteId, VehicleId},
rtsim::{Actor, FactionId, NpcAction, Personality, SiteId, VehicleId},
store::Id,
vol::RectVolSize,
};
@ -21,8 +21,6 @@ use world::{
util::{RandomPerm, LOCALITY},
};
use super::Actor;
#[derive(Copy, Clone, Debug, Default)]
pub enum SimulationMode {
/// 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)>,
}
#[derive(Clone, Copy)]
pub enum NpcAction {
/// (wpos, speed_factor)
Goto(Vec3<f32>, f32),
}
#[derive(Default)]
pub struct Controller {
pub action: Option<NpcAction>,
pub actions: Vec<NpcAction>,
/// (wpos, speed_factor)
pub goto: Option<(Vec3<f32>, f32)>,
}
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 {
Self {
action: Some(NpcAction::Goto(wpos, speed_factor)),
}
pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
self.goto = Some((wpos, speed_factor));
}
pub fn do_greet(&mut self, actor: Actor) { self.actions.push(NpcAction::Greet(actor)); }
}
pub struct Brain {
@ -91,7 +86,7 @@ pub struct Npc {
pub current_site: Option<SiteId>,
#[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
/// server, loaded corresponds to being within a loaded chunk). When in
@ -118,7 +113,7 @@ impl Clone for Npc {
// Not persisted
chunk_pos: None,
current_site: Default::default(),
action: Default::default(),
controller: Default::default(),
mode: Default::default(),
brain: Default::default(),
}
@ -138,7 +133,7 @@ impl Npc {
riding: None,
chunk_pos: None,
current_site: None,
action: None,
controller: Controller::default(),
mode: SimulationMode::Simulated,
brain: None,
}
@ -248,6 +243,7 @@ impl Vehicle {
#[derive(Default, Clone, Serialize, Deserialize)]
pub struct GridCell {
pub npcs: Vec<NpcId>,
pub characters: Vec<common::character::CharacterId>,
pub vehicles: Vec<VehicleId>,
}
@ -269,23 +265,33 @@ impl Npcs {
}
/// 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 =
wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>();
let r_sqr = radius * radius;
LOCALITY
.into_iter()
.filter_map(move |neighbor| {
self.npc_grid.get(chunk_pos + neighbor).map(|cell| {
.flat_map(move |neighbor| {
self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
cell.npcs
.iter()
.copied()
.filter(|npc| {
.filter(move |npc| {
self.npcs
.get(*npc)
.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()

View File

@ -3,7 +3,7 @@ use std::hash::BuildHasherDefault;
use crate::{
ai::{casual, choose, finish, important, just, now, seq, until, urgent, Action, NpcCtx},
data::{
npc::{Brain, Controller, PathData},
npc::{Brain, PathData},
Sites,
},
event::OnTick,
@ -217,7 +217,7 @@ impl Rule for NpcAi {
data.npcs
.iter_mut()
.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 {
action: Box::new(think().repeat()),
});
@ -251,7 +251,7 @@ impl Rule for NpcAi {
// Reinsert NPC brains
let mut data = ctx.state.data_mut();
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);
}
});
@ -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.
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()
.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)
}
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 {
choose(|ctx| {
// 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)
.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())
.map(|_| ())
.boxed()
@ -570,7 +588,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
// ...then wait for some time before moving on
.then({
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")
})
.map(|_| ())
@ -735,7 +753,7 @@ fn humanoid() -> impl Action {
casual(finish())
}
} else {
important(idle())
important(socialize())
}
} else if matches!(
ctx.npc.profession,
@ -806,6 +824,6 @@ fn think() -> impl Action {
choose(|ctx| match ctx.npc.body {
common::comp::Body::Humanoid(_) => casual(humanoid()),
common::comp::Body::BirdLarge(_) => casual(bird_large()),
_ => casual(idle()),
_ => casual(socialize()),
})
}

View File

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

View File

@ -229,10 +229,11 @@ impl<'a> AgentData<'a> {
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_())
.map(|pos| pos.as_())
.unwrap_or(*travel_to)*/;
.unwrap_or(*travel_to);
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
@ -1460,11 +1461,12 @@ impl<'a> AgentData<'a> {
self.idle(agent, controller, read_data, rng);
} else {
let target_data = TargetData::new(tgt_pos, target, read_data);
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)
}
// TODO: Reimplement this in rtsim
// 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)
// }
self.attack(agent, controller, &target_data, read_data, rng);
}
}

View File

@ -6,21 +6,21 @@ use common::{
group,
item::MaterialStatManifest,
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, Stats,
Vel,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, PresenceKind, Scale,
SkillSet, Stance, Stats, Vel,
},
link::Is,
mounting::{Mount, Rider},
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity,
rtsim::{Actor, RtSimEntity},
states::utils::{ForcedMovement, StageSection},
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
};
use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
World,
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
SystemData, World,
};
// 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 poises: ReadStorage<'a, Poise>,
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 {

View File

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

View File

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

View File

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

View File

@ -1,34 +1,8 @@
use common_net::msg::PresenceKind;
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
use specs::{Component, NullStorage};
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: 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
pub const CHUNK_FUZZ: u32 = 2;
// Distance out of the range of a region before removing it from subscriptions
@ -51,88 +25,3 @@ pub struct RepositionOnChunkLoad;
impl Component for RepositionOnChunkLoad {
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},
generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time, TimeOfDay},
rtsim::{RtSimEntity, RtSimVehicle},
rtsim::{Actor, RtSimEntity, RtSimVehicle},
slowjob::SlowJobPool,
terrain::CoordinateConversions,
trade::{Good, SiteInformation},
@ -16,7 +16,7 @@ use common::{
use common_ecs::{Job, Origin, Phase, System};
use rtsim::data::{
npc::{Profession, SimulationMode},
Actor, Npc, Sites,
Npc, Sites,
};
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::{sync::Arc, time::Duration};
@ -366,17 +366,17 @@ impl<'a> System<'a> for Sys {
// Update entity state
if let Some(agent) = agent {
agent.rtsim_controller.personality = npc.personality;
if let Some(action) = npc.action {
match action {
rtsim::data::npc::NpcAction::Goto(wpos, sf) => {
agent.rtsim_controller.travel_to = Some(wpos);
agent.rtsim_controller.speed_factor = sf;
},
}
if let Some((wpos, speed_factor)) = npc.controller.goto {
agent.rtsim_controller.travel_to = Some(wpos);
agent.rtsim_controller.speed_factor = speed_factor;
} else {
agent.rtsim_controller.travel_to = None;
agent.rtsim_controller.speed_factor = 1.0;
}
agent
.rtsim_controller
.actions
.extend(std::mem::take(&mut npc.controller.actions));
}
});
}

View File

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

View File

@ -9,7 +9,7 @@ use common::{
},
event::{Emitter, ServerEvent},
path::TraversalConfig,
rtsim::RtSimEntity,
rtsim::{NpcAction, RtSimEntity},
};
use rand::{prelude::ThreadRng, thread_rng, Rng};
use specs::{
@ -160,7 +160,11 @@ impl BehaviorTree {
/// Idle BehaviorTree
pub fn idle() -> 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
}
/// 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
fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) {
@ -746,9 +786,11 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
if aggro_on {
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);
} else {
agent_data.menacing(
@ -760,7 +802,9 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
rng,
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
}
/// Remember target.
fn remember_fight(
rtsim_entity: Option<&RtSimEntity>,
read_data: &ReadData,
agent: &mut Agent,
target: EcsEntity,
) {
rtsim_entity.is_some().then(|| {
read_data
.stats
.get(target)
.map(|stats| agent.add_fight_to_memory(&stats.name, read_data.time.0))
});
}
// /// Remember target.
// fn remember_fight(
// rtsim_entity: Option<&RtSimEntity>,
// read_data: &ReadData,
// agent: &mut Agent,
// target: EcsEntity,
// ) {
// rtsim_entity.is_some().then(|| {
// read_data
// .stats
// .get(target)
// .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,
},
event::ServerEvent,
rtsim::{Memory, MemoryItem, PersonalityTrait, RtSimEvent},
rtsim::PersonalityTrait,
trade::{TradeAction, TradePhase, TradeResult},
};
use rand::{thread_rng, Rng};
@ -106,15 +106,6 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
match subject {
Subject::Regular => {
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 {
let personality = &agent.rtsim_controller.personality;
let standard_response_msg = || -> String {
@ -252,6 +243,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
}
},
Subject::Mood => {
// TODO: Reimplement in rtsim2
/*
if let Some(rtsim_entity) = &bdata.rtsim_entity {
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},
client::Client,
metrics::NetworkRequestMetrics,
presence::Presence,
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_net::msg::{SerializedTerrainChunk, ServerGeneral};
use hashbrown::{hash_map::Entry, HashMap};

View File

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

View File

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

View File

@ -1,10 +1,10 @@
#[cfg(feature = "persistent_world")]
use crate::TerrainPersistence;
use crate::{client::Client, presence::Presence, Settings};
use crate::{client::Client, Settings};
use common::{
comp::{
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
Pos, SkillSet, Vel,
Pos, Presence, PresenceKind, SkillSet, Vel,
},
event::{EventBus, ServerEvent},
link::Is,
@ -15,7 +15,7 @@ use common::{
vol::ReadVol,
};
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 core::mem;
use rayon::prelude::*;

View File

@ -1,9 +1,9 @@
use crate::{
chunk_serialize::ChunkSendEntry, client::Client, lod::Lod, metrics::NetworkRequestMetrics,
presence::Presence, ChunkRequest,
ChunkRequest,
};
use common::{
comp::Pos,
comp::{Pos, Presence},
event::{EventBus, ServerEvent},
spiral::Spiral2d,
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::{
comp::{
pet::{is_tameable, Pet},
ActiveAbilities, Alignment, Body, Inventory, MapMarker, SkillSet, Stats, Waypoint,
ActiveAbilities, Alignment, Body, Inventory, MapMarker, Presence, PresenceKind, SkillSet,
Stats, Waypoint,
},
uid::Uid,
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::PresenceKind;
use specs::{Join, ReadStorage, Write, WriteExpect};
#[derive(Default)]

View File

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

View File

@ -6,18 +6,14 @@ use crate::TerrainPersistence;
use world::{IndexOwned, World};
use crate::{
chunk_generator::ChunkGenerator,
chunk_serialize::ChunkSendEntry,
client::Client,
presence::{Presence, RepositionOnChunkLoad},
rtsim,
settings::Settings,
ChunkRequest, Tick,
chunk_generator::ChunkGenerator, chunk_serialize::ChunkSendEntry, client::Client,
presence::RepositionOnChunkLoad, rtsim, settings::Settings, ChunkRequest, Tick,
};
use common::{
calendar::Calendar,
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},
generation::EntityInfo,

View File

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

View File

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

View File

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

View File

@ -18,7 +18,8 @@ use common::{
inventory::slot::{EquipSlot, Slot},
invite::InviteKind,
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,
event::UpdateCharacterMetadata,
@ -32,10 +33,7 @@ use common::{
vol::ReadVol,
};
use common_base::{prof_span, span};
use common_net::{
msg::{server::InviteAnswer, PresenceKind},
sync::WorldSyncExt,
};
use common_net::{msg::server::InviteAnswer, sync::WorldSyncExt};
use crate::{
audio::sfx::SfxEvent,