mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Allowed rtsim NPCs to greet nearby actors
This commit is contained in:
parent
8d91ebb23e
commit
364255c7fe
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7032,6 +7032,7 @@ dependencies = [
|
||||
"veloren-common-base",
|
||||
"veloren-common-dynlib",
|
||||
"veloren-common-ecs",
|
||||
"veloren-common-net",
|
||||
"veloren-rtsim",
|
||||
]
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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(_) => {
|
||||
|
@ -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 {
|
||||
|
@ -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
128
common/src/comp/presence.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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")]
|
||||
|
@ -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};
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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()),
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
.and_then(|chunk| {
|
||||
chunk
|
||||
.sites
|
||||
.iter()
|
||||
.find_map(|site| data.sites.world_site_map.get(site).copied()));
|
||||
.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,42 +179,63 @@ impl Rule for SimulateNpcs {
|
||||
|
||||
// Simulate the NPC's movement and interactions
|
||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
||||
// Move NPCs if they have a target destination
|
||||
if let Some((target, speed_factor)) = npc.controller.goto {
|
||||
// Simulate NPC movement when riding
|
||||
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) => {
|
||||
// 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
|
||||
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())
|
||||
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) {
|
||||
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 => {
|
||||
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)
|
||||
.get_interpolated(
|
||||
wpos.xy().map(|e| e as i32),
|
||||
|chunk| chunk.water_alt,
|
||||
)
|
||||
.unwrap_or(0.0);
|
||||
},
|
||||
_ => {},
|
||||
@ -220,18 +244,13 @@ impl Rule for SimulateNpcs {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
npc.wpos = vehicle.wpos;
|
||||
} else {
|
||||
// Vehicle doens't exist anymore
|
||||
npc.riding = None;
|
||||
}
|
||||
}
|
||||
// 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) => {
|
||||
// If not riding, we assume they're just walking
|
||||
} else {
|
||||
let diff = target.xy() - npc.wpos.xy();
|
||||
let dist2 = diff.magnitude_squared();
|
||||
|
||||
@ -242,7 +261,7 @@ impl Rule for SimulateNpcs {
|
||||
.min(1.0))
|
||||
.with_z(0.0);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure NPCs remain on the surface
|
||||
@ -250,9 +269,8 @@ impl Rule for SimulateNpcs {
|
||||
.world
|
||||
.sim()
|
||||
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||
.unwrap_or(0.0) + npc.body.flying_height();
|
||||
}
|
||||
|
||||
.unwrap_or(0.0)
|
||||
+ npc.body.flying_height();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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" }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) => {
|
||||
if let Some((wpos, speed_factor)) = npc.controller.goto {
|
||||
agent.rtsim_controller.travel_to = Some(wpos);
|
||||
agent.rtsim_controller.speed_factor = sf;
|
||||
},
|
||||
}
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)) });
|
||||
// }
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
}
|
@ -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};
|
||||
|
@ -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},
|
||||
|
@ -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,
|
||||
|
@ -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::*;
|
||||
|
@ -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},
|
||||
|
@ -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)]
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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::{
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user