diff --git a/Cargo.lock b/Cargo.lock index fa46bcc324..17a61eee1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7032,6 +7032,7 @@ dependencies = [ "veloren-common-base", "veloren-common-dynlib", "veloren-common-ecs", + "veloren-common-net", "veloren-rtsim", ] diff --git a/client/src/lib.rs b/client/src/lib.rs index 9c5ee7d6a5..665f806f36 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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, }; diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index a599b9117a..f752db223d 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -101,7 +101,7 @@ impl ClientMsg { &self, c_type: ClientType, registered: bool, - presence: Option, + presence: Option, ) -> bool { match self { ClientMsg::Type(t) => c_type == *t, diff --git a/common/net/src/msg/mod.rs b/common/net/src/msg/mod.rs index 429ef7f0d1..316f7429ba 100644 --- a/common/net/src/msg/mod.rs +++ b/common/net/src/msg/mod.rs @@ -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, diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 4d158d5345..65d5d6d856 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -296,7 +296,7 @@ impl ServerMsg { &self, c_type: ClientType, registered: bool, - presence: Option, + presence: Option, ) -> bool { match self { ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => { diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 5af82f0c82..71d4113585 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -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 { diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 82a7b1e314..a66a22f130 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -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::{ diff --git a/common/src/comp/presence.rs b/common/src/comp/presence.rs new file mode 100644 index 0000000000..fcb7588993 --- /dev/null +++ b/common/src/comp/presence.rs @@ -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; +} + +#[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, + 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); + } + } +} diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 9fef179a51..6b04ff8e14 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -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; } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum Actor { + Npc(NpcId), + Character(CharacterId), +} + +impl Actor { + pub fn npc(&self) -> Option { + 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; } -#[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, /// Proportion of full speed to move pub speed_factor: f32, - /// Events - pub events: Vec, + pub actions: VecDeque, } 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")] diff --git a/rtsim/src/data/faction.rs b/rtsim/src/data/faction.rs index 65322e5097..4a4504825f 100644 --- a/rtsim/src/data/faction.rs +++ b/rtsim/src/data/faction.rs @@ -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}; diff --git a/rtsim/src/data/mod.rs b/rtsim/src/data/mod.rs index 5b537c1ac6..45a212cd26 100644 --- a/rtsim/src/data/mod.rs +++ b/rtsim/src/data/mod.rs @@ -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 { - match self { - Actor::Npc(id) => Some(*id), - Actor::Character(_) => None, - } - } -} - #[derive(Clone, Serialize, Deserialize)] pub struct Data { pub nature: Nature, diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index 6c0c26e6f1..99f4c5de96 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -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, bool), SiteId>, usize)>, } -#[derive(Clone, Copy)] -pub enum NpcAction { - /// (wpos, speed_factor) - Goto(Vec3, f32), -} - +#[derive(Default)] pub struct Controller { - pub action: Option, + pub actions: Vec, + /// (wpos, speed_factor) + pub goto: Option<(Vec3, f32)>, } impl Controller { - pub fn idle() -> Self { Self { action: None } } + pub fn do_idle(&mut self) { self.goto = None; } - pub fn goto(wpos: Vec3, speed_factor: f32) -> Self { - Self { - action: Some(NpcAction::Goto(wpos, speed_factor)), - } + pub fn do_goto(&mut self, wpos: Vec3, 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, #[serde(skip_serializing, skip_deserializing)] - pub action: Option, + 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, + pub characters: Vec, pub vehicles: Vec, } @@ -269,23 +265,33 @@ impl Npcs { } /// Queries nearby npcs, not garantueed to work if radius > 32.0 - pub fn nearby(&self, wpos: Vec2, radius: f32) -> impl Iterator + '_ { + pub fn nearby(&self, wpos: Vec2, radius: f32) -> impl Iterator + '_ { let chunk_pos = wpos.as_::() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::(); 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::>() + .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() diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index 428e193071..718c04535a 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -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, speed_factor: f32, goal_dist: f32) -> impl Action { @@ -292,7 +292,7 @@ fn goto(wpos: Vec3, 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()), }) } diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index 1498a7a3cf..8a514857e2 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -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_::() / 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_::() / TerrainChunkSize::RECT_SIZE.as_::(); @@ -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_::() / TerrainChunkSize::RECT_SIZE.as_::(); - 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_::() + / TerrainChunkSize::RECT_SIZE.as_::(); + 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(); } } }); diff --git a/server/agent/Cargo.toml b/server/agent/Cargo.toml index 9009caae63..1c617fc1e6 100644 --- a/server/agent/Cargo.toml +++ b/server/agent/Cargo.toml @@ -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" } diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 115cc5fb11..d1ab7689ed 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -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); } } diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index 10f3739f33..6ca3b9c9ce 100644 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -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 { + // 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 { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index acc753ce1a..4459993b97 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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}; diff --git a/server/src/events/player.rs b/server/src/events/player.rs index a634686cb3..3a0cdce141 100644 --- a/server/src/events/player.rs +++ b/server/src/events/player.rs @@ -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}; diff --git a/server/src/lib.rs b/server/src/lib.rs index 3ae719da85..c79365524d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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::(); state.ecs_mut().register::(); - state.ecs_mut().register::(); + state.ecs_mut().register::(); state.ecs_mut().register::(); state.ecs_mut().register::(); state.ecs_mut().register::(); @@ -833,7 +833,7 @@ impl Server { ( &self.state.ecs().entities(), &self.state.ecs().read_storage::(), - !&self.state.ecs().read_storage::(), + !&self.state.ecs().read_storage::(), self.state.ecs().read_storage::().maybe(), ) .join() diff --git a/server/src/presence.rs b/server/src/presence.rs index 99c7c10c4c..732225a205 100644 --- a/server/src/presence.rs +++ b/server/src/presence.rs @@ -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; -} - // 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; } - -#[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, - 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); - } - } -} diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 443957a097..ca0544da03 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -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)); } }); } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index a939bd113a..4cc1f23cc4 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -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; diff --git a/server/src/sys/agent/behavior_tree.rs b/server/src/sys/agent/behavior_tree.rs index ce2fd6891a..6e40207ca8 100644 --- a/server/src/sys/agent/behavior_tree.rs +++ b/server/src/sys/agent/behavior_tree.rs @@ -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)) }); +// } diff --git a/server/src/sys/agent/behavior_tree/interaction.rs b/server/src/sys/agent/behavior_tree/interaction.rs index 45b05a843f..567758d4f1 100644 --- a/server/src/sys/agent/behavior_tree/interaction.rs +++ b/server/src/sys/agent/behavior_tree/interaction.rs @@ -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() { diff --git a/server/src/sys/agent/data.rs b/server/src/sys/agent/data.rs deleted file mode 100644 index 0b13b2524d..0000000000 --- a/server/src/sys/agent/data.rs +++ /dev/null @@ -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>, - pub time_of_day: Read<'a, TimeOfDay>, - pub light_emitter: ReadStorage<'a, LightEmitter>, - #[cfg(feature = "worldgen")] - pub world: ReadExpect<'a, Arc>, - 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, -} diff --git a/server/src/sys/chunk_serialize.rs b/server/src/sys/chunk_serialize.rs index c12006007a..0810aa9951 100644 --- a/server/src/sys/chunk_serialize.rs +++ b/server/src/sys/chunk_serialize.rs @@ -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}; diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index c2c246e61c..c6ac05e2d9 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -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}, diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index 661aaabc9f..1aea142188 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -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, diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 6c50879800..4e216483d8 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -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::*; diff --git a/server/src/sys/msg/terrain.rs b/server/src/sys/msg/terrain.rs index 6eb01033a3..6c050d6a83 100644 --- a/server/src/sys/msg/terrain.rs +++ b/server/src/sys/msg/terrain.rs @@ -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}, diff --git a/server/src/sys/persistence.rs b/server/src/sys/persistence.rs index de4eb10885..3011664ebe 100644 --- a/server/src/sys/persistence.rs +++ b/server/src/sys/persistence.rs @@ -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)] diff --git a/server/src/sys/subscription.rs b/server/src/sys/subscription.rs index 1be6e66d28..3b20bde524 100644 --- a/server/src/sys/subscription.rs +++ b/server/src/sys/subscription.rs @@ -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, diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index d401221aae..c4deb96978 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -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, diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index 78accf71f3..cfc32fe04e 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -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; diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 342d470dc9..808772a2f2 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -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::{ diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 7a6ff8bbdc..c23375c1d7 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -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, }; diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 9939c8500b..849dc0c313 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -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,