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-base",
|
||||||
"veloren-common-dynlib",
|
"veloren-common-dynlib",
|
||||||
"veloren-common-ecs",
|
"veloren-common-ecs",
|
||||||
|
"veloren-common-net",
|
||||||
"veloren-rtsim",
|
"veloren-rtsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ use common::{
|
|||||||
slot::{EquipSlot, InvSlotId, Slot},
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
||||||
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
||||||
MapMarkerChange, UtteranceKind,
|
MapMarkerChange, PresenceKind, UtteranceKind,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
|
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
@ -59,8 +59,8 @@ use common_net::{
|
|||||||
self,
|
self,
|
||||||
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
|
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
|
||||||
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
|
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
|
||||||
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, PresenceKind,
|
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
|
||||||
RegisterError, ServerGeneral, ServerInit, ServerRegisterAnswer,
|
ServerGeneral, ServerInit, ServerRegisterAnswer,
|
||||||
},
|
},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
|
@ -101,7 +101,7 @@ impl ClientMsg {
|
|||||||
&self,
|
&self,
|
||||||
c_type: ClientType,
|
c_type: ClientType,
|
||||||
registered: bool,
|
registered: bool,
|
||||||
presence: Option<super::PresenceKind>,
|
presence: Option<comp::PresenceKind>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ClientMsg::Type(t) => c_type == *t,
|
ClientMsg::Type(t) => c_type == *t,
|
||||||
|
@ -19,23 +19,8 @@ pub use self::{
|
|||||||
},
|
},
|
||||||
world_msg::WorldMapMsg,
|
world_msg::WorldMapMsg,
|
||||||
};
|
};
|
||||||
use common::character::CharacterId;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum PresenceKind {
|
|
||||||
Spectator,
|
|
||||||
Character(CharacterId),
|
|
||||||
Possessor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PresenceKind {
|
|
||||||
/// Check if the presence represents a control of a character, and thus
|
|
||||||
/// certain in-game messages from the client such as control inputs
|
|
||||||
/// should be handled.
|
|
||||||
pub fn controlling_char(&self) -> bool { matches!(self, Self::Character(_) | Self::Possessor) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PingMsg {
|
pub enum PingMsg {
|
||||||
Ping,
|
Ping,
|
||||||
|
@ -296,7 +296,7 @@ impl ServerMsg {
|
|||||||
&self,
|
&self,
|
||||||
c_type: ClientType,
|
c_type: ClientType,
|
||||||
registered: bool,
|
registered: bool,
|
||||||
presence: Option<super::PresenceKind>,
|
presence: Option<comp::PresenceKind>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
|
ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
quadruped_small, ship, Body, UtteranceKind,
|
quadruped_small, ship, Body, UtteranceKind,
|
||||||
},
|
},
|
||||||
path::Chaser,
|
path::Chaser,
|
||||||
rtsim::{Memory, MemoryItem, RtSimController, RtSimEvent},
|
rtsim::RtSimController,
|
||||||
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
@ -713,23 +713,6 @@ impl Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
|
pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
|
||||||
|
|
||||||
pub fn forget_enemy(&mut self, target_name: &str) {
|
|
||||||
self.rtsim_controller
|
|
||||||
.events
|
|
||||||
.push(RtSimEvent::ForgetEnemy(target_name.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_fight_to_memory(&mut self, target_name: &str, time: f64) {
|
|
||||||
self.rtsim_controller
|
|
||||||
.events
|
|
||||||
.push(RtSimEvent::AddMemory(Memory {
|
|
||||||
item: MemoryItem::CharacterFight {
|
|
||||||
name: target_name.to_owned(),
|
|
||||||
},
|
|
||||||
time_to_forget: time + 300.0,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Agent {
|
impl Component for Agent {
|
||||||
|
@ -38,6 +38,8 @@ pub mod loot_owner;
|
|||||||
#[cfg(not(target_arch = "wasm32"))] mod player;
|
#[cfg(not(target_arch = "wasm32"))] mod player;
|
||||||
#[cfg(not(target_arch = "wasm32"))] pub mod poise;
|
#[cfg(not(target_arch = "wasm32"))] pub mod poise;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub mod presence;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod projectile;
|
pub mod projectile;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod shockwave;
|
pub mod shockwave;
|
||||||
@ -107,6 +109,7 @@ pub use self::{
|
|||||||
player::DisconnectReason,
|
player::DisconnectReason,
|
||||||
player::{AliasError, Player, MAX_ALIAS_LEN},
|
player::{AliasError, Player, MAX_ALIAS_LEN},
|
||||||
poise::{Poise, PoiseChange, PoiseState},
|
poise::{Poise, PoiseChange, PoiseState},
|
||||||
|
presence::{Presence, PresenceKind},
|
||||||
projectile::{Projectile, ProjectileConstructor},
|
projectile::{Projectile, ProjectileConstructor},
|
||||||
shockwave::{Shockwave, ShockwaveHitEntities},
|
shockwave::{Shockwave, ShockwaveHitEntities},
|
||||||
skillset::{
|
skillset::{
|
||||||
|
128
common/src/comp/presence.rs
Normal file
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`
|
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||||
// module in `server`.
|
// module in `server`.
|
||||||
|
|
||||||
|
use crate::character::CharacterId;
|
||||||
use rand::{seq::IteratorRandom, Rng};
|
use rand::{seq::IteratorRandom, Rng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::Component;
|
use specs::Component;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use strum::{EnumIter, IntoEnumIterator};
|
use strum::{EnumIter, IntoEnumIterator};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
use crate::comp::dialogue::MoodState;
|
|
||||||
|
|
||||||
slotmap::new_key_type! { pub struct NpcId; }
|
slotmap::new_key_type! { pub struct NpcId; }
|
||||||
|
|
||||||
slotmap::new_key_type! { pub struct VehicleId; }
|
slotmap::new_key_type! { pub struct VehicleId; }
|
||||||
@ -26,6 +26,21 @@ impl Component for RtSimEntity {
|
|||||||
type Storage = specs::VecStorage<Self>;
|
type Storage = specs::VecStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum Actor {
|
||||||
|
Npc(NpcId),
|
||||||
|
Character(CharacterId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor {
|
||||||
|
pub fn npc(&self) -> Option<NpcId> {
|
||||||
|
match self {
|
||||||
|
Actor::Npc(id) => Some(*id),
|
||||||
|
Actor::Character(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct RtSimVehicle(pub VehicleId);
|
pub struct RtSimVehicle(pub VehicleId);
|
||||||
|
|
||||||
@ -33,29 +48,6 @@ impl Component for RtSimVehicle {
|
|||||||
type Storage = specs::VecStorage<Self>;
|
type Storage = specs::VecStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum RtSimEvent {
|
|
||||||
AddMemory(Memory),
|
|
||||||
SetMood(Memory),
|
|
||||||
ForgetEnemy(String),
|
|
||||||
PrintMemories,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Memory {
|
|
||||||
pub item: MemoryItem,
|
|
||||||
pub time_to_forget: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum MemoryItem {
|
|
||||||
// These are structs to allow more data beyond name to be stored
|
|
||||||
// such as clothing worn, weapon used, etc.
|
|
||||||
CharacterInteraction { name: String },
|
|
||||||
CharacterFight { name: String },
|
|
||||||
Mood { state: MoodState },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(EnumIter, Clone, Copy)]
|
#[derive(EnumIter, Clone, Copy)]
|
||||||
pub enum PersonalityTrait {
|
pub enum PersonalityTrait {
|
||||||
Open,
|
Open,
|
||||||
@ -210,8 +202,7 @@ pub struct RtSimController {
|
|||||||
pub heading_to: Option<String>,
|
pub heading_to: Option<String>,
|
||||||
/// Proportion of full speed to move
|
/// Proportion of full speed to move
|
||||||
pub speed_factor: f32,
|
pub speed_factor: f32,
|
||||||
/// Events
|
pub actions: VecDeque<NpcAction>,
|
||||||
pub events: Vec<RtSimEvent>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RtSimController {
|
impl Default for RtSimController {
|
||||||
@ -221,7 +212,7 @@ impl Default for RtSimController {
|
|||||||
personality: Personality::default(),
|
personality: Personality::default(),
|
||||||
heading_to: None,
|
heading_to: None,
|
||||||
speed_factor: 1.0,
|
speed_factor: 1.0,
|
||||||
events: Vec::new(),
|
actions: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,11 +224,16 @@ impl RtSimController {
|
|||||||
personality: Personality::default(),
|
personality: Personality::default(),
|
||||||
heading_to: None,
|
heading_to: None,
|
||||||
speed_factor: 0.5,
|
speed_factor: 0.5,
|
||||||
events: Vec::new(),
|
actions: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum NpcAction {
|
||||||
|
Greet(Actor),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, enum_map::Enum)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, enum_map::Enum)]
|
||||||
pub enum ChunkResource {
|
pub enum ChunkResource {
|
||||||
#[serde(rename = "0")]
|
#[serde(rename = "0")]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use super::Actor;
|
pub use common::rtsim::{Actor, FactionId};
|
||||||
pub use common::rtsim::FactionId;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use slotmap::HopSlotMap;
|
use slotmap::HopSlotMap;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
@ -20,21 +20,6 @@ use std::{
|
|||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub enum Actor {
|
|
||||||
Npc(NpcId),
|
|
||||||
Character(common::character::CharacterId),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor {
|
|
||||||
pub fn npc(&self) -> Option<NpcId> {
|
|
||||||
match self {
|
|
||||||
Actor::Npc(id) => Some(*id),
|
|
||||||
Actor::Character(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
pub nature: Nature,
|
pub nature: Nature,
|
||||||
|
@ -3,7 +3,7 @@ pub use common::rtsim::{NpcId, Profession};
|
|||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{FactionId, Personality, SiteId, VehicleId},
|
rtsim::{Actor, FactionId, NpcAction, Personality, SiteId, VehicleId},
|
||||||
store::Id,
|
store::Id,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
@ -21,8 +21,6 @@ use world::{
|
|||||||
util::{RandomPerm, LOCALITY},
|
util::{RandomPerm, LOCALITY},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Actor;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
pub enum SimulationMode {
|
pub enum SimulationMode {
|
||||||
/// The NPC is unloaded and is being simulated via rtsim.
|
/// The NPC is unloaded and is being simulated via rtsim.
|
||||||
@ -45,24 +43,21 @@ pub struct PathingMemory {
|
|||||||
pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
|
pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Default)]
|
||||||
pub enum NpcAction {
|
|
||||||
/// (wpos, speed_factor)
|
|
||||||
Goto(Vec3<f32>, f32),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
pub action: Option<NpcAction>,
|
pub actions: Vec<NpcAction>,
|
||||||
|
/// (wpos, speed_factor)
|
||||||
|
pub goto: Option<(Vec3<f32>, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub fn idle() -> Self { Self { action: None } }
|
pub fn do_idle(&mut self) { self.goto = None; }
|
||||||
|
|
||||||
pub fn goto(wpos: Vec3<f32>, speed_factor: f32) -> Self {
|
pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
|
||||||
Self {
|
self.goto = Some((wpos, speed_factor));
|
||||||
action: Some(NpcAction::Goto(wpos, speed_factor)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn do_greet(&mut self, actor: Actor) { self.actions.push(NpcAction::Greet(actor)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Brain {
|
pub struct Brain {
|
||||||
@ -91,7 +86,7 @@ pub struct Npc {
|
|||||||
pub current_site: Option<SiteId>,
|
pub current_site: Option<SiteId>,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub action: Option<NpcAction>,
|
pub controller: Controller,
|
||||||
|
|
||||||
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
|
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
|
||||||
/// server, loaded corresponds to being within a loaded chunk). When in
|
/// server, loaded corresponds to being within a loaded chunk). When in
|
||||||
@ -118,7 +113,7 @@ impl Clone for Npc {
|
|||||||
// Not persisted
|
// Not persisted
|
||||||
chunk_pos: None,
|
chunk_pos: None,
|
||||||
current_site: Default::default(),
|
current_site: Default::default(),
|
||||||
action: Default::default(),
|
controller: Default::default(),
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
brain: Default::default(),
|
brain: Default::default(),
|
||||||
}
|
}
|
||||||
@ -138,7 +133,7 @@ impl Npc {
|
|||||||
riding: None,
|
riding: None,
|
||||||
chunk_pos: None,
|
chunk_pos: None,
|
||||||
current_site: None,
|
current_site: None,
|
||||||
action: None,
|
controller: Controller::default(),
|
||||||
mode: SimulationMode::Simulated,
|
mode: SimulationMode::Simulated,
|
||||||
brain: None,
|
brain: None,
|
||||||
}
|
}
|
||||||
@ -248,6 +243,7 @@ impl Vehicle {
|
|||||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||||
pub struct GridCell {
|
pub struct GridCell {
|
||||||
pub npcs: Vec<NpcId>,
|
pub npcs: Vec<NpcId>,
|
||||||
|
pub characters: Vec<common::character::CharacterId>,
|
||||||
pub vehicles: Vec<VehicleId>,
|
pub vehicles: Vec<VehicleId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,23 +265,33 @@ impl Npcs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Queries nearby npcs, not garantueed to work if radius > 32.0
|
/// Queries nearby npcs, not garantueed to work if radius > 32.0
|
||||||
pub fn nearby(&self, wpos: Vec2<f32>, radius: f32) -> impl Iterator<Item = NpcId> + '_ {
|
pub fn nearby(&self, wpos: Vec2<f32>, radius: f32) -> impl Iterator<Item = Actor> + '_ {
|
||||||
let chunk_pos =
|
let chunk_pos =
|
||||||
wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||||
let r_sqr = radius * radius;
|
let r_sqr = radius * radius;
|
||||||
LOCALITY
|
LOCALITY
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(move |neighbor| {
|
.flat_map(move |neighbor| {
|
||||||
self.npc_grid.get(chunk_pos + neighbor).map(|cell| {
|
self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
|
||||||
cell.npcs
|
cell.npcs
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|npc| {
|
.filter(move |npc| {
|
||||||
self.npcs
|
self.npcs
|
||||||
.get(*npc)
|
.get(*npc)
|
||||||
.map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
|
.map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.map(Actor::Npc)
|
||||||
|
.chain(cell.characters
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
// TODO: Filter characters by distance too
|
||||||
|
// .filter(move |npc| {
|
||||||
|
// self.npcs
|
||||||
|
// .get(*npc)
|
||||||
|
// .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
|
||||||
|
// })
|
||||||
|
.map(Actor::Character))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
|
@ -3,7 +3,7 @@ use std::hash::BuildHasherDefault;
|
|||||||
use crate::{
|
use crate::{
|
||||||
ai::{casual, choose, finish, important, just, now, seq, until, urgent, Action, NpcCtx},
|
ai::{casual, choose, finish, important, just, now, seq, until, urgent, Action, NpcCtx},
|
||||||
data::{
|
data::{
|
||||||
npc::{Brain, Controller, PathData},
|
npc::{Brain, PathData},
|
||||||
Sites,
|
Sites,
|
||||||
},
|
},
|
||||||
event::OnTick,
|
event::OnTick,
|
||||||
@ -217,7 +217,7 @@ impl Rule for NpcAi {
|
|||||||
data.npcs
|
data.npcs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|(npc_id, npc)| {
|
.map(|(npc_id, npc)| {
|
||||||
let controller = Controller { action: npc.action };
|
let controller = std::mem::take(&mut npc.controller);
|
||||||
let brain = npc.brain.take().unwrap_or_else(|| Brain {
|
let brain = npc.brain.take().unwrap_or_else(|| Brain {
|
||||||
action: Box::new(think().repeat()),
|
action: Box::new(think().repeat()),
|
||||||
});
|
});
|
||||||
@ -251,7 +251,7 @@ impl Rule for NpcAi {
|
|||||||
// Reinsert NPC brains
|
// Reinsert NPC brains
|
||||||
let mut data = ctx.state.data_mut();
|
let mut data = ctx.state.data_mut();
|
||||||
for (npc_id, controller, brain) in npc_data {
|
for (npc_id, controller, brain) in npc_data {
|
||||||
data.npcs[npc_id].action = controller.action;
|
data.npcs[npc_id].controller = controller;
|
||||||
data.npcs[npc_id].brain = Some(brain);
|
data.npcs[npc_id].brain = Some(brain);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -260,7 +260,7 @@ impl Rule for NpcAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn idle() -> impl Action { just(|ctx| *ctx.controller = Controller::idle()).debug(|| "idle") }
|
fn idle() -> impl Action { just(|ctx| ctx.controller.do_idle()).debug(|| "idle") }
|
||||||
|
|
||||||
/// Try to walk toward a 3D position without caring for obstacles.
|
/// Try to walk toward a 3D position without caring for obstacles.
|
||||||
fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
||||||
@ -292,7 +292,7 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
*ctx.controller = Controller::goto(*waypoint, speed_factor);
|
ctx.controller.do_goto(*waypoint, speed_factor);
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2))
|
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2))
|
||||||
@ -452,6 +452,24 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
|||||||
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
|
move |ctx| ctx.time.0 > *timeout.get_or_insert(ctx.time.0 + time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn socialize() -> impl Action {
|
||||||
|
just(|ctx| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
// TODO: Bit odd, should wait for a while after greeting
|
||||||
|
if thread_rng().gen_bool(0.0002) {
|
||||||
|
if let Some(other) = ctx
|
||||||
|
.state
|
||||||
|
.data()
|
||||||
|
.npcs
|
||||||
|
.nearby(ctx.npc.wpos.xy(), 8.0)
|
||||||
|
.choose(&mut rng)
|
||||||
|
{
|
||||||
|
ctx.controller.do_greet(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn adventure() -> impl Action {
|
fn adventure() -> impl Action {
|
||||||
choose(|ctx| {
|
choose(|ctx| {
|
||||||
// Choose a random site that's fairly close by
|
// Choose a random site that's fairly close by
|
||||||
@ -540,7 +558,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
{
|
{
|
||||||
travel_to_point(house_wpos)
|
travel_to_point(house_wpos)
|
||||||
.debug(|| "walk to house")
|
.debug(|| "walk to house")
|
||||||
.then(idle().repeat().debug(|| "wait in house"))
|
.then(socialize().repeat().debug(|| "wait in house"))
|
||||||
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
.stop_if(|ctx| DayPeriod::from(ctx.time_of_day.0).is_light())
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.boxed()
|
.boxed()
|
||||||
@ -570,7 +588,7 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
// ...then wait for some time before moving on
|
// ...then wait for some time before moving on
|
||||||
.then({
|
.then({
|
||||||
let wait_time = thread_rng().gen_range(10.0..30.0);
|
let wait_time = thread_rng().gen_range(10.0..30.0);
|
||||||
idle().repeat().stop_if(timeout(wait_time))
|
socialize().repeat().stop_if(timeout(wait_time))
|
||||||
.debug(|| "wait at plaza")
|
.debug(|| "wait at plaza")
|
||||||
})
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
@ -735,7 +753,7 @@ fn humanoid() -> impl Action {
|
|||||||
casual(finish())
|
casual(finish())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
important(idle())
|
important(socialize())
|
||||||
}
|
}
|
||||||
} else if matches!(
|
} else if matches!(
|
||||||
ctx.npc.profession,
|
ctx.npc.profession,
|
||||||
@ -806,6 +824,6 @@ fn think() -> impl Action {
|
|||||||
choose(|ctx| match ctx.npc.body {
|
choose(|ctx| match ctx.npc.body {
|
||||||
common::comp::Body::Humanoid(_) => casual(humanoid()),
|
common::comp::Body::Humanoid(_) => casual(humanoid()),
|
||||||
common::comp::Body::BirdLarge(_) => casual(bird_large()),
|
common::comp::Body::BirdLarge(_) => casual(bird_large()),
|
||||||
_ => casual(idle()),
|
_ => casual(socialize()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::Personality,
|
rtsim::{Actor, Personality},
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
@ -25,7 +25,7 @@ impl Rule for SimulateNpcs {
|
|||||||
for (npc_id, npc) in data.npcs.npcs.iter() {
|
for (npc_id, npc) in data.npcs.npcs.iter() {
|
||||||
if let Some(ride) = &npc.riding {
|
if let Some(ride) = &npc.riding {
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) {
|
if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) {
|
||||||
let actor = crate::data::Actor::Npc(npc_id);
|
let actor = Actor::Npc(npc_id);
|
||||||
vehicle.riders.push(actor);
|
vehicle.riders.push(actor);
|
||||||
if ride.steering && vehicle.driver.replace(actor).is_some() {
|
if ride.steering && vehicle.driver.replace(actor).is_some() {
|
||||||
panic!("Replaced driver");
|
panic!("Replaced driver");
|
||||||
@ -153,9 +153,12 @@ impl Rule for SimulateNpcs {
|
|||||||
.world
|
.world
|
||||||
.sim()
|
.sim()
|
||||||
.get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
|
.get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
|
||||||
.and_then(|chunk| chunk.sites
|
.and_then(|chunk| {
|
||||||
.iter()
|
chunk
|
||||||
.find_map(|site| data.sites.world_site_map.get(site).copied()));
|
.sites
|
||||||
|
.iter()
|
||||||
|
.find_map(|site| data.sites.world_site_map.get(site).copied())
|
||||||
|
});
|
||||||
|
|
||||||
let chunk_pos =
|
let chunk_pos =
|
||||||
npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||||
@ -176,83 +179,98 @@ impl Rule for SimulateNpcs {
|
|||||||
|
|
||||||
// Simulate the NPC's movement and interactions
|
// Simulate the NPC's movement and interactions
|
||||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
if matches!(npc.mode, SimulationMode::Simulated) {
|
||||||
if let Some(riding) = &npc.riding {
|
// Move NPCs if they have a target destination
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
if let Some((target, speed_factor)) = npc.controller.goto {
|
||||||
if let Some(action) = npc.action && riding.steering {
|
// Simulate NPC movement when riding
|
||||||
match action {
|
if let Some(riding) = &npc.riding {
|
||||||
crate::data::npc::NpcAction::Goto(target, speed_factor) => {
|
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
||||||
let diff = target.xy() - vehicle.wpos.xy();
|
// If steering, the NPC controls the vehicle's motion
|
||||||
let dist2 = diff.magnitude_squared();
|
if riding.steering {
|
||||||
|
let diff = target.xy() - vehicle.wpos.xy();
|
||||||
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
if dist2 > 0.5f32.powi(2) {
|
||||||
let mut wpos = vehicle.wpos + (diff
|
let mut wpos = vehicle.wpos
|
||||||
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
+ (diff
|
||||||
|
* (vehicle.get_speed()
|
||||||
|
* speed_factor
|
||||||
|
* ctx.event.dt
|
||||||
/ dist2.sqrt())
|
/ dist2.sqrt())
|
||||||
.min(1.0))
|
.min(1.0))
|
||||||
.with_z(0.0);
|
.with_z(0.0);
|
||||||
|
|
||||||
let is_valid = match vehicle.body {
|
let is_valid = match vehicle.body {
|
||||||
common::comp::ship::Body::DefaultAirship | common::comp::ship::Body::AirBalloon => true,
|
common::comp::ship::Body::DefaultAirship
|
||||||
common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => {
|
| common::comp::ship::Body::AirBalloon => true,
|
||||||
let chunk_pos = wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
common::comp::ship::Body::SailBoat
|
||||||
ctx.world.sim().get(chunk_pos).map_or(true, |f| f.river.river_kind.is_some())
|
| common::comp::ship::Body::Galleon => {
|
||||||
},
|
let chunk_pos = wpos.xy().as_::<i32>()
|
||||||
_ => false,
|
/ TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||||
};
|
ctx.world
|
||||||
|
.sim()
|
||||||
|
.get(chunk_pos)
|
||||||
|
.map_or(true, |f| f.river.river_kind.is_some())
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
if is_valid {
|
if is_valid {
|
||||||
match vehicle.body {
|
match vehicle.body {
|
||||||
common::comp::ship::Body::DefaultAirship | common::comp::ship::Body::AirBalloon => {
|
common::comp::ship::Body::DefaultAirship
|
||||||
if let Some(alt) = ctx.world.sim().get_alt_approx(wpos.xy().as_()).filter(|alt| wpos.z < *alt) {
|
| common::comp::ship::Body::AirBalloon => {
|
||||||
wpos.z = alt;
|
if let Some(alt) = ctx
|
||||||
}
|
.world
|
||||||
},
|
.sim()
|
||||||
common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => {
|
.get_alt_approx(wpos.xy().as_())
|
||||||
wpos.z = ctx
|
.filter(|alt| wpos.z < *alt)
|
||||||
.world
|
{
|
||||||
.sim()
|
wpos.z = alt;
|
||||||
.get_interpolated(wpos.xy().map(|e| e as i32), |chunk| chunk.water_alt)
|
}
|
||||||
.unwrap_or(0.0);
|
},
|
||||||
},
|
common::comp::ship::Body::SailBoat
|
||||||
_ => {},
|
| common::comp::ship::Body::Galleon => {
|
||||||
}
|
wpos.z = ctx
|
||||||
vehicle.wpos = wpos;
|
.world
|
||||||
|
.sim()
|
||||||
|
.get_interpolated(
|
||||||
|
wpos.xy().map(|e| e as i32),
|
||||||
|
|chunk| chunk.water_alt,
|
||||||
|
)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
}
|
}
|
||||||
|
vehicle.wpos = wpos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
npc.wpos = vehicle.wpos;
|
||||||
|
} else {
|
||||||
|
// Vehicle doens't exist anymore
|
||||||
|
npc.riding = None;
|
||||||
}
|
}
|
||||||
npc.wpos = vehicle.wpos;
|
// If not riding, we assume they're just walking
|
||||||
} else {
|
} else {
|
||||||
// Vehicle doens't exist anymore
|
let diff = target.xy() - npc.wpos.xy();
|
||||||
npc.riding = None;
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
|
if dist2 > 0.5f32.powi(2) {
|
||||||
|
npc.wpos += (diff
|
||||||
|
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
||||||
|
/ dist2.sqrt())
|
||||||
|
.min(1.0))
|
||||||
|
.with_z(0.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Move NPCs if they have a target destination
|
|
||||||
else if let Some(action) = npc.action {
|
|
||||||
match action {
|
|
||||||
crate::data::npc::NpcAction::Goto(target, speed_factor) => {
|
|
||||||
let diff = target.xy() - npc.wpos.xy();
|
|
||||||
let dist2 = diff.magnitude_squared();
|
|
||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
|
||||||
npc.wpos += (diff
|
|
||||||
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
|
||||||
/ dist2.sqrt())
|
|
||||||
.min(1.0))
|
|
||||||
.with_z(0.0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure NPCs remain on the surface
|
|
||||||
npc.wpos.z = ctx
|
|
||||||
.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
|
||||||
.unwrap_or(0.0) + npc.body.flying_height();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Make sure NPCs remain on the surface
|
||||||
|
npc.wpos.z = ctx
|
||||||
|
.world
|
||||||
|
.sim()
|
||||||
|
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
||||||
|
.unwrap_or(0.0)
|
||||||
|
+ npc.body.flying_height();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,7 @@ be-dyn-lib = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
common = { package = "veloren-common", path = "../../common"}
|
common = { package = "veloren-common", path = "../../common"}
|
||||||
common-base = { package = "veloren-common-base", path = "../../common/base" }
|
common-base = { package = "veloren-common-base", path = "../../common/base" }
|
||||||
|
common-net = { package = "veloren-common-net", path = "../../common/net" }
|
||||||
common-ecs = { package = "veloren-common-ecs", path = "../../common/ecs" }
|
common-ecs = { package = "veloren-common-ecs", path = "../../common/ecs" }
|
||||||
common-dynlib = { package = "veloren-common-dynlib", path = "../../common/dynlib", optional = true}
|
common-dynlib = { package = "veloren-common-dynlib", path = "../../common/dynlib", optional = true}
|
||||||
rtsim = { package = "veloren-rtsim", path = "../../rtsim" }
|
rtsim = { package = "veloren-rtsim", path = "../../rtsim" }
|
||||||
|
@ -229,10 +229,11 @@ impl<'a> AgentData<'a> {
|
|||||||
controller.push_cancel_input(InputKind::Fly)
|
controller.push_cancel_input(InputKind::Fly)
|
||||||
}
|
}
|
||||||
|
|
||||||
let chase_tgt = *travel_to/*read_data.terrain
|
let chase_tgt = read_data
|
||||||
|
.terrain
|
||||||
.try_find_space(travel_to.as_())
|
.try_find_space(travel_to.as_())
|
||||||
.map(|pos| pos.as_())
|
.map(|pos| pos.as_())
|
||||||
.unwrap_or(*travel_to)*/;
|
.unwrap_or(*travel_to);
|
||||||
|
|
||||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||||
&*read_data.terrain,
|
&*read_data.terrain,
|
||||||
@ -1460,11 +1461,12 @@ impl<'a> AgentData<'a> {
|
|||||||
self.idle(agent, controller, read_data, rng);
|
self.idle(agent, controller, read_data, rng);
|
||||||
} else {
|
} else {
|
||||||
let target_data = TargetData::new(tgt_pos, target, read_data);
|
let target_data = TargetData::new(tgt_pos, target, read_data);
|
||||||
if let Some(tgt_name) =
|
// TODO: Reimplement this in rtsim
|
||||||
read_data.stats.get(target).map(|stats| stats.name.clone())
|
// if let Some(tgt_name) =
|
||||||
{
|
// read_data.stats.get(target).map(|stats| stats.name.clone())
|
||||||
agent.add_fight_to_memory(&tgt_name, read_data.time.0)
|
// {
|
||||||
}
|
// agent.add_fight_to_memory(&tgt_name, read_data.time.0)
|
||||||
|
// }
|
||||||
self.attack(agent, controller, &target_data, read_data, rng);
|
self.attack(agent, controller, &target_data, read_data, rng);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,21 @@ use common::{
|
|||||||
group,
|
group,
|
||||||
item::MaterialStatManifest,
|
item::MaterialStatManifest,
|
||||||
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
|
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
|
||||||
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, Stats,
|
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, PresenceKind, Scale,
|
||||||
Vel,
|
SkillSet, Stance, Stats, Vel,
|
||||||
},
|
},
|
||||||
link::Is,
|
link::Is,
|
||||||
mounting::{Mount, Rider},
|
mounting::{Mount, Rider},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
resources::{DeltaTime, Time, TimeOfDay},
|
resources::{DeltaTime, Time, TimeOfDay},
|
||||||
rtsim::RtSimEntity,
|
rtsim::{Actor, RtSimEntity},
|
||||||
states::utils::{ForcedMovement, StageSection},
|
states::utils::{ForcedMovement, StageSection},
|
||||||
terrain::TerrainGrid,
|
terrain::TerrainGrid,
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
};
|
};
|
||||||
use specs::{
|
use specs::{
|
||||||
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
|
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
|
||||||
World,
|
SystemData, World,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
|
// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
|
||||||
@ -246,6 +246,25 @@ pub struct ReadData<'a> {
|
|||||||
pub msm: ReadExpect<'a, MaterialStatManifest>,
|
pub msm: ReadExpect<'a, MaterialStatManifest>,
|
||||||
pub poises: ReadStorage<'a, Poise>,
|
pub poises: ReadStorage<'a, Poise>,
|
||||||
pub stances: ReadStorage<'a, Stance>,
|
pub stances: ReadStorage<'a, Stance>,
|
||||||
|
pub presences: ReadStorage<'a, Presence>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ReadData<'a> {
|
||||||
|
pub fn lookup_actor(&self, actor: Actor) -> Option<EcsEntity> {
|
||||||
|
// TODO: We really shouldn't be doing a linear search here. The only saving
|
||||||
|
// grace is that the set of entities that fit each case should be
|
||||||
|
// *relatively* small.
|
||||||
|
match actor {
|
||||||
|
Actor::Character(character_id) => (&self.entities, &self.presences)
|
||||||
|
.join()
|
||||||
|
.find(|(_, p)| p.kind == PresenceKind::Character(character_id))
|
||||||
|
.map(|(entity, _)| entity),
|
||||||
|
Actor::Npc(npc_id) => (&self.entities, &self.rtsim_entities)
|
||||||
|
.join()
|
||||||
|
.find(|(_, e)| e.0 == npc_id)
|
||||||
|
.map(|(entity, _)| entity),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Path {
|
pub enum Path {
|
||||||
|
@ -5,7 +5,6 @@ use crate::{
|
|||||||
client::Client,
|
client::Client,
|
||||||
location::Locations,
|
location::Locations,
|
||||||
login_provider::LoginProvider,
|
login_provider::LoginProvider,
|
||||||
presence::Presence,
|
|
||||||
settings::{
|
settings::{
|
||||||
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
||||||
},
|
},
|
||||||
@ -31,7 +30,7 @@ use common::{
|
|||||||
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
|
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
|
||||||
inventory::item::{tool::AbilityMap, MaterialStatManifest, Quality},
|
inventory::item::{tool::AbilityMap, MaterialStatManifest, Quality},
|
||||||
invite::InviteKind,
|
invite::InviteKind,
|
||||||
AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea,
|
AdminRole, ChatType, Inventory, Item, LightEmitter, Presence, PresenceKind, WaypointArea,
|
||||||
},
|
},
|
||||||
depot,
|
depot,
|
||||||
effect::Effect,
|
effect::Effect,
|
||||||
@ -49,7 +48,7 @@ use common::{
|
|||||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||||
};
|
};
|
||||||
use common_net::{
|
use common_net::{
|
||||||
msg::{DisconnectReason, Notification, PlayerListUpdate, PresenceKind, ServerGeneral},
|
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use common_state::{BuildAreaError, BuildAreas};
|
use common_state::{BuildAreaError, BuildAreas};
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use super::Event;
|
use super::Event;
|
||||||
use crate::{
|
use crate::{
|
||||||
client::Client, metrics::PlayerMetrics, persistence::character_updater::CharacterUpdater,
|
client::Client, metrics::PlayerMetrics, persistence::character_updater::CharacterUpdater,
|
||||||
presence::Presence, state_ext::StateExt, BattleModeBuffer, Server,
|
state_ext::StateExt, BattleModeBuffer, Server,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
comp,
|
comp,
|
||||||
comp::{group, pet::is_tameable},
|
comp::{group, pet::is_tameable, Presence, PresenceKind},
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
};
|
};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
use common_net::msg::{PlayerListUpdate, PresenceKind, ServerGeneral};
|
use common_net::msg::{PlayerListUpdate, ServerGeneral};
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
|
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
|
||||||
use tracing::{debug, error, trace, warn, Instrument};
|
use tracing::{debug, error, trace, warn, Instrument};
|
||||||
|
@ -62,7 +62,7 @@ use crate::{
|
|||||||
location::Locations,
|
location::Locations,
|
||||||
login_provider::LoginProvider,
|
login_provider::LoginProvider,
|
||||||
persistence::PersistedComponents,
|
persistence::PersistedComponents,
|
||||||
presence::{Presence, RegionSubscription, RepositionOnChunkLoad},
|
presence::{RegionSubscription, RepositionOnChunkLoad},
|
||||||
state_ext::StateExt,
|
state_ext::StateExt,
|
||||||
sys::sentinel::DeletedEntities,
|
sys::sentinel::DeletedEntities,
|
||||||
};
|
};
|
||||||
@ -376,7 +376,7 @@ impl Server {
|
|||||||
// Server-only components
|
// Server-only components
|
||||||
state.ecs_mut().register::<RegionSubscription>();
|
state.ecs_mut().register::<RegionSubscription>();
|
||||||
state.ecs_mut().register::<Client>();
|
state.ecs_mut().register::<Client>();
|
||||||
state.ecs_mut().register::<Presence>();
|
state.ecs_mut().register::<comp::Presence>();
|
||||||
state.ecs_mut().register::<wiring::WiringElement>();
|
state.ecs_mut().register::<wiring::WiringElement>();
|
||||||
state.ecs_mut().register::<wiring::Circuit>();
|
state.ecs_mut().register::<wiring::Circuit>();
|
||||||
state.ecs_mut().register::<Anchor>();
|
state.ecs_mut().register::<Anchor>();
|
||||||
@ -833,7 +833,7 @@ impl Server {
|
|||||||
(
|
(
|
||||||
&self.state.ecs().entities(),
|
&self.state.ecs().entities(),
|
||||||
&self.state.ecs().read_storage::<comp::Pos>(),
|
&self.state.ecs().read_storage::<comp::Pos>(),
|
||||||
!&self.state.ecs().read_storage::<Presence>(),
|
!&self.state.ecs().read_storage::<comp::Presence>(),
|
||||||
self.state.ecs().read_storage::<Anchor>().maybe(),
|
self.state.ecs().read_storage::<Anchor>().maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
|
@ -1,34 +1,8 @@
|
|||||||
use common_net::msg::PresenceKind;
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::{Component, NullStorage};
|
use specs::{Component, NullStorage};
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Presence {
|
|
||||||
pub terrain_view_distance: ViewDistance,
|
|
||||||
pub entity_view_distance: ViewDistance,
|
|
||||||
pub kind: PresenceKind,
|
|
||||||
pub lossy_terrain_compression: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Presence {
|
|
||||||
pub fn new(view_distances: common::ViewDistances, kind: PresenceKind) -> Self {
|
|
||||||
let now = Instant::now();
|
|
||||||
Self {
|
|
||||||
terrain_view_distance: ViewDistance::new(view_distances.terrain, now),
|
|
||||||
entity_view_distance: ViewDistance::new(view_distances.entity, now),
|
|
||||||
kind,
|
|
||||||
lossy_terrain_compression: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Presence {
|
|
||||||
type Storage = specs::DenseVecStorage<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distance from fuzzy_chunk before snapping to current chunk
|
// Distance from fuzzy_chunk before snapping to current chunk
|
||||||
pub const CHUNK_FUZZ: u32 = 2;
|
pub const CHUNK_FUZZ: u32 = 2;
|
||||||
// Distance out of the range of a region before removing it from subscriptions
|
// Distance out of the range of a region before removing it from subscriptions
|
||||||
@ -51,88 +25,3 @@ pub struct RepositionOnChunkLoad;
|
|||||||
impl Component for RepositionOnChunkLoad {
|
impl Component for RepositionOnChunkLoad {
|
||||||
type Storage = NullStorage<Self>;
|
type Storage = NullStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
|
||||||
enum Direction {
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Distance from the [Presence] from which the world is loaded and information
|
|
||||||
/// is synced to clients.
|
|
||||||
///
|
|
||||||
/// We limit the frequency that changes in the view distance change direction
|
|
||||||
/// (e.g. shifting from increasing the value to decreasing it). This is useful
|
|
||||||
/// since we want to avoid rapid cycles of shrinking and expanding of the view
|
|
||||||
/// distance.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ViewDistance {
|
|
||||||
direction: Direction,
|
|
||||||
last_direction_change_time: Instant,
|
|
||||||
target: Option<u32>,
|
|
||||||
current: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewDistance {
|
|
||||||
/// Minimum time allowed between changes in direction of value adjustments.
|
|
||||||
const TIME_PER_DIR_CHANGE: Duration = Duration::from_millis(300);
|
|
||||||
|
|
||||||
pub fn new(start_value: u32, now: Instant) -> Self {
|
|
||||||
Self {
|
|
||||||
direction: Direction::Up,
|
|
||||||
last_direction_change_time: now.checked_sub(Self::TIME_PER_DIR_CHANGE).unwrap_or(now),
|
|
||||||
target: None,
|
|
||||||
current: start_value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current value.
|
|
||||||
pub fn current(&self) -> u32 { self.current }
|
|
||||||
|
|
||||||
/// Applies deferred change based on the whether the time to apply it has
|
|
||||||
/// been reached.
|
|
||||||
pub fn update(&mut self, now: Instant) {
|
|
||||||
if let Some(target_val) = self.target {
|
|
||||||
if now.saturating_duration_since(self.last_direction_change_time)
|
|
||||||
> Self::TIME_PER_DIR_CHANGE
|
|
||||||
{
|
|
||||||
self.last_direction_change_time = now;
|
|
||||||
self.current = target_val;
|
|
||||||
self.target = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the target value.
|
|
||||||
///
|
|
||||||
/// If this hasn't been changed recently or it is in the same direction as
|
|
||||||
/// the previous change it will be applied immediately. Otherwise, it
|
|
||||||
/// will be deferred to a later time (limiting the frequency of changes
|
|
||||||
/// in the change direction).
|
|
||||||
pub fn set_target(&mut self, new_target: u32, now: Instant) {
|
|
||||||
use core::cmp::Ordering;
|
|
||||||
let new_direction = match new_target.cmp(&self.current) {
|
|
||||||
Ordering::Equal => return, // No change needed.
|
|
||||||
Ordering::Less => Direction::Down,
|
|
||||||
Ordering::Greater => Direction::Up,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Change is in the same direction as before so we can just apply it.
|
|
||||||
if new_direction == self.direction {
|
|
||||||
self.current = new_target;
|
|
||||||
self.target = None;
|
|
||||||
// If it has already been a while since the last direction change we can
|
|
||||||
// directly apply the request and switch the direction.
|
|
||||||
} else if now.saturating_duration_since(self.last_direction_change_time)
|
|
||||||
> Self::TIME_PER_DIR_CHANGE
|
|
||||||
{
|
|
||||||
self.direction = new_direction;
|
|
||||||
self.last_direction_change_time = now;
|
|
||||||
self.current = new_target;
|
|
||||||
self.target = None;
|
|
||||||
// Otherwise, we need to defer the request.
|
|
||||||
} else {
|
|
||||||
self.target = Some(new_target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,7 @@ use common::{
|
|||||||
event::{EventBus, NpcBuilder, ServerEvent},
|
event::{EventBus, NpcBuilder, ServerEvent},
|
||||||
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
||||||
resources::{DeltaTime, Time, TimeOfDay},
|
resources::{DeltaTime, Time, TimeOfDay},
|
||||||
rtsim::{RtSimEntity, RtSimVehicle},
|
rtsim::{Actor, RtSimEntity, RtSimVehicle},
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::CoordinateConversions,
|
terrain::CoordinateConversions,
|
||||||
trade::{Good, SiteInformation},
|
trade::{Good, SiteInformation},
|
||||||
@ -16,7 +16,7 @@ use common::{
|
|||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use rtsim::data::{
|
use rtsim::data::{
|
||||||
npc::{Profession, SimulationMode},
|
npc::{Profession, SimulationMode},
|
||||||
Actor, Npc, Sites,
|
Npc, Sites,
|
||||||
};
|
};
|
||||||
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
@ -366,17 +366,17 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Update entity state
|
// Update entity state
|
||||||
if let Some(agent) = agent {
|
if let Some(agent) = agent {
|
||||||
agent.rtsim_controller.personality = npc.personality;
|
agent.rtsim_controller.personality = npc.personality;
|
||||||
if let Some(action) = npc.action {
|
if let Some((wpos, speed_factor)) = npc.controller.goto {
|
||||||
match action {
|
agent.rtsim_controller.travel_to = Some(wpos);
|
||||||
rtsim::data::npc::NpcAction::Goto(wpos, sf) => {
|
agent.rtsim_controller.speed_factor = speed_factor;
|
||||||
agent.rtsim_controller.travel_to = Some(wpos);
|
|
||||||
agent.rtsim_controller.speed_factor = sf;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
agent.rtsim_controller.travel_to = None;
|
agent.rtsim_controller.travel_to = None;
|
||||||
agent.rtsim_controller.speed_factor = 1.0;
|
agent.rtsim_controller.speed_factor = 1.0;
|
||||||
}
|
}
|
||||||
|
agent
|
||||||
|
.rtsim_controller
|
||||||
|
.actions
|
||||||
|
.extend(std::mem::take(&mut npc.controller.actions));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
events::{self, update_map_markers},
|
events::{self, update_map_markers},
|
||||||
persistence::PersistedComponents,
|
persistence::PersistedComponents,
|
||||||
pet::restore_pet,
|
pet::restore_pet,
|
||||||
presence::{Presence, RepositionOnChunkLoad},
|
presence::RepositionOnChunkLoad,
|
||||||
rtsim::RtSim,
|
rtsim::RtSim,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
sys::sentinel::DeletedEntities,
|
sys::sentinel::DeletedEntities,
|
||||||
@ -19,7 +19,7 @@ use common::{
|
|||||||
self,
|
self,
|
||||||
item::{ItemKind, MaterialStatManifest},
|
item::{ItemKind, MaterialStatManifest},
|
||||||
skills::{GeneralSkill, Skill},
|
skills::{GeneralSkill, Skill},
|
||||||
ChatType, Group, Inventory, Item, Player, Poise,
|
ChatType, Group, Inventory, Item, Player, Poise, Presence, PresenceKind,
|
||||||
},
|
},
|
||||||
effect::Effect,
|
effect::Effect,
|
||||||
link::{Link, LinkHandle},
|
link::{Link, LinkHandle},
|
||||||
@ -30,7 +30,7 @@ use common::{
|
|||||||
LoadoutBuilder, ViewDistances,
|
LoadoutBuilder, ViewDistances,
|
||||||
};
|
};
|
||||||
use common_net::{
|
use common_net::{
|
||||||
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
|
msg::{CharacterInfo, PlayerListUpdate, ServerGeneral},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
|
@ -9,7 +9,7 @@ use common::{
|
|||||||
},
|
},
|
||||||
event::{Emitter, ServerEvent},
|
event::{Emitter, ServerEvent},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
rtsim::RtSimEntity,
|
rtsim::{NpcAction, RtSimEntity},
|
||||||
};
|
};
|
||||||
use rand::{prelude::ThreadRng, thread_rng, Rng};
|
use rand::{prelude::ThreadRng, thread_rng, Rng};
|
||||||
use specs::{
|
use specs::{
|
||||||
@ -160,7 +160,11 @@ impl BehaviorTree {
|
|||||||
/// Idle BehaviorTree
|
/// Idle BehaviorTree
|
||||||
pub fn idle() -> Self {
|
pub fn idle() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tree: vec![set_owner_if_no_target, handle_timed_events],
|
tree: vec![
|
||||||
|
set_owner_if_no_target,
|
||||||
|
handle_rtsim_actions,
|
||||||
|
handle_timed_events,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,6 +468,42 @@ fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle action requests from rtsim, such as talking to NPCs or attacking
|
||||||
|
fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
|
||||||
|
if let Some(action) = bdata.agent.rtsim_controller.actions.pop_front() {
|
||||||
|
match action {
|
||||||
|
NpcAction::Greet(actor) => {
|
||||||
|
if bdata.agent.allowed_to_speak() {
|
||||||
|
if let Some(target) = bdata.read_data.lookup_actor(actor) {
|
||||||
|
let target_pos = bdata.read_data.positions.get(target).map(|pos| pos.0);
|
||||||
|
|
||||||
|
bdata.agent.target = Some(Target::new(
|
||||||
|
target,
|
||||||
|
false,
|
||||||
|
bdata.read_data.time.0,
|
||||||
|
false,
|
||||||
|
target_pos,
|
||||||
|
));
|
||||||
|
|
||||||
|
if bdata.agent_data.look_toward(
|
||||||
|
&mut bdata.controller,
|
||||||
|
&bdata.read_data,
|
||||||
|
target,
|
||||||
|
) {
|
||||||
|
bdata.controller.push_utterance(UtteranceKind::Greeting);
|
||||||
|
bdata.controller.push_action(ControlAction::Talk);
|
||||||
|
bdata
|
||||||
|
.agent_data
|
||||||
|
.chat_npc("npc-speech-villager", &mut bdata.event_emitter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle timed events, like looking at the player we are talking to
|
/// Handle timed events, like looking at the player we are talking to
|
||||||
fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
|
fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
|
||||||
let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) {
|
let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) {
|
||||||
@ -746,9 +786,11 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
|||||||
|
|
||||||
if aggro_on {
|
if aggro_on {
|
||||||
let target_data = TargetData::new(tgt_pos, target, read_data);
|
let target_data = TargetData::new(tgt_pos, target, read_data);
|
||||||
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
|
// let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
|
||||||
|
|
||||||
tgt_name.map(|tgt_name| agent.add_fight_to_memory(&tgt_name, read_data.time.0));
|
// TODO: Reimplement in rtsim2
|
||||||
|
// tgt_name.map(|tgt_name| agent.add_fight_to_memory(&tgt_name,
|
||||||
|
// read_data.time.0));
|
||||||
agent_data.attack(agent, controller, &target_data, read_data, rng);
|
agent_data.attack(agent, controller, &target_data, read_data, rng);
|
||||||
} else {
|
} else {
|
||||||
agent_data.menacing(
|
agent_data.menacing(
|
||||||
@ -760,7 +802,9 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
|||||||
rng,
|
rng,
|
||||||
remembers_fight_with(agent_data.rtsim_entity, read_data, target),
|
remembers_fight_with(agent_data.rtsim_entity, read_data, target),
|
||||||
);
|
);
|
||||||
remember_fight(agent_data.rtsim_entity, read_data, agent, target);
|
// TODO: Reimplement in rtsim2
|
||||||
|
// remember_fight(agent_data.rtsim_entity, read_data, agent,
|
||||||
|
// target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -784,17 +828,17 @@ fn remembers_fight_with(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remember target.
|
// /// Remember target.
|
||||||
fn remember_fight(
|
// fn remember_fight(
|
||||||
rtsim_entity: Option<&RtSimEntity>,
|
// rtsim_entity: Option<&RtSimEntity>,
|
||||||
read_data: &ReadData,
|
// read_data: &ReadData,
|
||||||
agent: &mut Agent,
|
// agent: &mut Agent,
|
||||||
target: EcsEntity,
|
// target: EcsEntity,
|
||||||
) {
|
// ) {
|
||||||
rtsim_entity.is_some().then(|| {
|
// rtsim_entity.is_some().then(|| {
|
||||||
read_data
|
// read_data
|
||||||
.stats
|
// .stats
|
||||||
.get(target)
|
// .get(target)
|
||||||
.map(|stats| agent.add_fight_to_memory(&stats.name, read_data.time.0))
|
// .map(|stats| agent.add_fight_to_memory(&stats.name,
|
||||||
});
|
// read_data.time.0)) });
|
||||||
}
|
// }
|
||||||
|
@ -9,7 +9,7 @@ use common::{
|
|||||||
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
|
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
|
||||||
},
|
},
|
||||||
event::ServerEvent,
|
event::ServerEvent,
|
||||||
rtsim::{Memory, MemoryItem, PersonalityTrait, RtSimEvent},
|
rtsim::PersonalityTrait,
|
||||||
trade::{TradeAction, TradePhase, TradeResult},
|
trade::{TradeAction, TradePhase, TradeResult},
|
||||||
};
|
};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
@ -106,15 +106,6 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
match subject {
|
match subject {
|
||||||
Subject::Regular => {
|
Subject::Regular => {
|
||||||
if let Some(tgt_stats) = read_data.stats.get(target) {
|
if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||||
agent
|
|
||||||
.rtsim_controller
|
|
||||||
.events
|
|
||||||
.push(RtSimEvent::AddMemory(Memory {
|
|
||||||
item: MemoryItem::CharacterInteraction {
|
|
||||||
name: tgt_stats.name.clone(),
|
|
||||||
},
|
|
||||||
time_to_forget: read_data.time.0 + 600.0,
|
|
||||||
}));
|
|
||||||
if let Some(destination_name) = &agent.rtsim_controller.heading_to {
|
if let Some(destination_name) = &agent.rtsim_controller.heading_to {
|
||||||
let personality = &agent.rtsim_controller.personality;
|
let personality = &agent.rtsim_controller.personality;
|
||||||
let standard_response_msg = || -> String {
|
let standard_response_msg = || -> String {
|
||||||
@ -252,6 +243,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Subject::Mood => {
|
Subject::Mood => {
|
||||||
|
// TODO: Reimplement in rtsim2
|
||||||
/*
|
/*
|
||||||
if let Some(rtsim_entity) = &bdata.rtsim_entity {
|
if let Some(rtsim_entity) = &bdata.rtsim_entity {
|
||||||
if !rtsim_entity.brain.remembers_mood() {
|
if !rtsim_entity.brain.remembers_mood() {
|
||||||
|
@ -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},
|
chunk_serialize::{ChunkSendEntry, SerializedChunk},
|
||||||
client::Client,
|
client::Client,
|
||||||
metrics::NetworkRequestMetrics,
|
metrics::NetworkRequestMetrics,
|
||||||
presence::Presence,
|
|
||||||
Tick,
|
Tick,
|
||||||
};
|
};
|
||||||
use common::{event::EventBus, slowjob::SlowJobPool, terrain::TerrainGrid};
|
use common::{comp::Presence, event::EventBus, slowjob::SlowJobPool, terrain::TerrainGrid};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
|
use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
|
||||||
use hashbrown::{hash_map::Entry, HashMap};
|
use hashbrown::{hash_map::Entry, HashMap};
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
use super::sentinel::{DeletedEntities, TrackedStorages, UpdateTrackers};
|
use super::sentinel::{DeletedEntities, TrackedStorages, UpdateTrackers};
|
||||||
use crate::{
|
use crate::{client::Client, presence::RegionSubscription, Tick};
|
||||||
client::Client,
|
|
||||||
presence::{Presence, RegionSubscription},
|
|
||||||
Tick,
|
|
||||||
};
|
|
||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Player, Pos, Vel},
|
comp::{Collider, ForceUpdate, InventoryUpdate, Last, Ori, Player, Pos, Presence, Vel},
|
||||||
event::EventBus,
|
event::EventBus,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
region::{Event as RegionEvent, RegionMap},
|
region::{Event as RegionEvent, RegionMap},
|
||||||
|
@ -8,11 +8,10 @@ use crate::{
|
|||||||
character_creator,
|
character_creator,
|
||||||
client::Client,
|
client::Client,
|
||||||
persistence::{character_loader::CharacterLoader, character_updater::CharacterUpdater},
|
persistence::{character_loader::CharacterLoader, character_updater::CharacterUpdater},
|
||||||
presence::Presence,
|
|
||||||
EditableSettings,
|
EditableSettings,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg, Waypoint},
|
comp::{Admin, AdminRole, ChatType, Player, Presence, UnresolvedChatMsg, Waypoint},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
resources::Time,
|
resources::Time,
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
#[cfg(feature = "persistent_world")]
|
#[cfg(feature = "persistent_world")]
|
||||||
use crate::TerrainPersistence;
|
use crate::TerrainPersistence;
|
||||||
use crate::{client::Client, presence::Presence, Settings};
|
use crate::{client::Client, Settings};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
|
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
|
||||||
Pos, SkillSet, Vel,
|
Pos, Presence, PresenceKind, SkillSet, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
link::Is,
|
link::Is,
|
||||||
@ -15,7 +15,7 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral};
|
use common_net::msg::{ClientGeneral, ServerGeneral};
|
||||||
use common_state::{BlockChange, BuildAreas};
|
use common_state::{BlockChange, BuildAreas};
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
chunk_serialize::ChunkSendEntry, client::Client, lod::Lod, metrics::NetworkRequestMetrics,
|
chunk_serialize::ChunkSendEntry, client::Client, lod::Lod, metrics::NetworkRequestMetrics,
|
||||||
presence::Presence, ChunkRequest,
|
ChunkRequest,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::Pos,
|
comp::{Pos, Presence},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
spiral::Spiral2d,
|
spiral::Spiral2d,
|
||||||
terrain::{CoordinateConversions, TerrainChunkSize, TerrainGrid},
|
terrain::{CoordinateConversions, TerrainChunkSize, TerrainGrid},
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use crate::{persistence::character_updater, presence::Presence, sys::SysScheduler};
|
use crate::{persistence::character_updater, sys::SysScheduler};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
pet::{is_tameable, Pet},
|
pet::{is_tameable, Pet},
|
||||||
ActiveAbilities, Alignment, Body, Inventory, MapMarker, SkillSet, Stats, Waypoint,
|
ActiveAbilities, Alignment, Body, Inventory, MapMarker, Presence, PresenceKind, SkillSet,
|
||||||
|
Stats, Waypoint,
|
||||||
},
|
},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::PresenceKind;
|
|
||||||
use specs::{Join, ReadStorage, Write, WriteExpect};
|
use specs::{Join, ReadStorage, Write, WriteExpect};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use super::sentinel::{DeletedEntities, TrackedStorages};
|
use super::sentinel::{DeletedEntities, TrackedStorages};
|
||||||
use crate::{
|
use crate::{
|
||||||
client::Client,
|
client::Client,
|
||||||
presence::{self, Presence, RegionSubscription},
|
presence::{self, RegionSubscription},
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Ori, Pos, Vel},
|
comp::{Ori, Pos, Presence, Vel},
|
||||||
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
|
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
|
||||||
terrain::{CoordinateConversions, TerrainChunkSize},
|
terrain::{CoordinateConversions, TerrainChunkSize},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
|
@ -6,18 +6,14 @@ use crate::TerrainPersistence;
|
|||||||
use world::{IndexOwned, World};
|
use world::{IndexOwned, World};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk_generator::ChunkGenerator,
|
chunk_generator::ChunkGenerator, chunk_serialize::ChunkSendEntry, client::Client,
|
||||||
chunk_serialize::ChunkSendEntry,
|
presence::RepositionOnChunkLoad, rtsim, settings::Settings, ChunkRequest, Tick,
|
||||||
client::Client,
|
|
||||||
presence::{Presence, RepositionOnChunkLoad},
|
|
||||||
rtsim,
|
|
||||||
settings::Settings,
|
|
||||||
ChunkRequest, Tick,
|
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
comp::{
|
comp::{
|
||||||
self, agent, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate, Pos, Waypoint,
|
self, agent, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate, Pos, Presence,
|
||||||
|
Waypoint,
|
||||||
},
|
},
|
||||||
event::{EventBus, NpcBuilder, ServerEvent},
|
event::{EventBus, NpcBuilder, ServerEvent},
|
||||||
generation::EntityInfo,
|
generation::EntityInfo,
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use crate::{chunk_serialize::ChunkSendEntry, client::Client, presence::Presence, Settings};
|
use crate::{chunk_serialize::ChunkSendEntry, client::Client, Settings};
|
||||||
use common::{comp::Pos, event::EventBus};
|
use common::{
|
||||||
|
comp::{Pos, Presence},
|
||||||
|
event::EventBus,
|
||||||
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{CompressedData, ServerGeneral};
|
use common_net::msg::{CompressedData, ServerGeneral};
|
||||||
use common_state::TerrainChanges;
|
use common_state::TerrainChanges;
|
||||||
|
@ -99,7 +99,7 @@ use common::{
|
|||||||
loot_owner::LootOwnerKind,
|
loot_owner::LootOwnerKind,
|
||||||
pet::is_mountable,
|
pet::is_mountable,
|
||||||
skillset::{skills::Skill, SkillGroupKind, SkillsPersistenceError},
|
skillset::{skills::Skill, SkillGroupKind, SkillsPersistenceError},
|
||||||
BuffData, BuffKind, Health, Item, MapMarkerChange,
|
BuffData, BuffKind, Health, Item, MapMarkerChange, PresenceKind,
|
||||||
},
|
},
|
||||||
consts::MAX_PICKUP_RANGE,
|
consts::MAX_PICKUP_RANGE,
|
||||||
link::Is,
|
link::Is,
|
||||||
@ -115,7 +115,7 @@ use common::{
|
|||||||
};
|
};
|
||||||
use common_base::{prof_span, span};
|
use common_base::{prof_span, span};
|
||||||
use common_net::{
|
use common_net::{
|
||||||
msg::{world_msg::SiteId, Notification, PresenceKind},
|
msg::{world_msg::SiteId, Notification},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
|
@ -38,7 +38,6 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_base::{prof_span, span};
|
use common_base::{prof_span, span};
|
||||||
use common_net::msg::PresenceKind;
|
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
use comp::item::Reagent;
|
use comp::item::Reagent;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@ -311,7 +310,7 @@ impl Scene {
|
|||||||
let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
|
let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
|
||||||
|
|
||||||
let camera_mode = match client.presence() {
|
let camera_mode = match client.presence() {
|
||||||
Some(PresenceKind::Spectator) => CameraMode::Freefly,
|
Some(comp::PresenceKind::Spectator) => CameraMode::Freefly,
|
||||||
_ => CameraMode::ThirdPerson,
|
_ => CameraMode::ThirdPerson,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ use common::{
|
|||||||
inventory::slot::{EquipSlot, Slot},
|
inventory::slot::{EquipSlot, Slot},
|
||||||
invite::InviteKind,
|
invite::InviteKind,
|
||||||
item::{tool::ToolKind, ItemDesc},
|
item::{tool::ToolKind, ItemDesc},
|
||||||
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Stats, UtteranceKind, Vel,
|
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, PresenceKind, Stats,
|
||||||
|
UtteranceKind, Vel,
|
||||||
},
|
},
|
||||||
consts::MAX_MOUNT_RANGE,
|
consts::MAX_MOUNT_RANGE,
|
||||||
event::UpdateCharacterMetadata,
|
event::UpdateCharacterMetadata,
|
||||||
@ -32,10 +33,7 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_base::{prof_span, span};
|
use common_base::{prof_span, span};
|
||||||
use common_net::{
|
use common_net::{msg::server::InviteAnswer, sync::WorldSyncExt};
|
||||||
msg::{server::InviteAnswer, PresenceKind},
|
|
||||||
sync::WorldSyncExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::sfx::SfxEvent,
|
audio::sfx::SfxEvent,
|
||||||
|
Loading…
Reference in New Issue
Block a user