mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Make vehicles npcs and npc uid used in rtsim commands
This commit is contained in:
parent
b9bfb3f1dd
commit
1f195fd848
@ -12,7 +12,7 @@ use crate::{
|
|||||||
lottery::LootSpec,
|
lottery::LootSpec,
|
||||||
mounting::VolumePos,
|
mounting::VolumePos,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
rtsim::{RtSimEntity, RtSimVehicle},
|
rtsim::RtSimEntity,
|
||||||
terrain::SpriteKind,
|
terrain::SpriteKind,
|
||||||
trade::{TradeAction, TradeId},
|
trade::{TradeAction, TradeId},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
@ -225,13 +225,15 @@ pub enum ServerEvent {
|
|||||||
// TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type?
|
// TODO: to avoid breakage when adding new fields, perhaps have an `NpcBuilder` type?
|
||||||
CreateNpc {
|
CreateNpc {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
|
ori: Ori,
|
||||||
npc: NpcBuilder,
|
npc: NpcBuilder,
|
||||||
|
rider: Option<NpcBuilder>,
|
||||||
},
|
},
|
||||||
CreateShip {
|
CreateShip {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
ori: Ori,
|
ori: Ori,
|
||||||
ship: comp::ship::Body,
|
ship: comp::ship::Body,
|
||||||
rtsim_entity: Option<RtSimVehicle>,
|
rtsim_entity: Option<RtSimEntity>,
|
||||||
driver: Option<NpcBuilder>,
|
driver: Option<NpcBuilder>,
|
||||||
},
|
},
|
||||||
CreateWaypoint(Vec3<f32>),
|
CreateWaypoint(Vec3<f32>),
|
||||||
|
@ -17,8 +17,6 @@ use vek::*;
|
|||||||
|
|
||||||
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 SiteId; }
|
slotmap::new_key_type! { pub struct SiteId; }
|
||||||
|
|
||||||
slotmap::new_key_type! { pub struct FactionId; }
|
slotmap::new_key_type! { pub struct FactionId; }
|
||||||
@ -32,7 +30,7 @@ impl Component for RtSimEntity {
|
|||||||
type Storage = specs::VecStorage<Self>;
|
type Storage = specs::VecStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
pub enum Actor {
|
pub enum Actor {
|
||||||
Npc(NpcId),
|
Npc(NpcId),
|
||||||
Character(CharacterId),
|
Character(CharacterId),
|
||||||
@ -47,11 +45,12 @@ impl Actor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
impl From<NpcId> for Actor {
|
||||||
pub struct RtSimVehicle(pub VehicleId);
|
fn from(value: NpcId) -> Self { Actor::Npc(value) }
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for RtSimVehicle {
|
impl From<CharacterId> for Actor {
|
||||||
type Storage = specs::VecStorage<Self>;
|
fn from(value: CharacterId) -> Self { Actor::Character(value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(EnumIter, Clone, Copy)]
|
#[derive(EnumIter, Clone, Copy)]
|
||||||
@ -313,6 +312,8 @@ pub enum Role {
|
|||||||
Wild,
|
Wild,
|
||||||
#[serde(rename = "2")]
|
#[serde(rename = "2")]
|
||||||
Monster,
|
Monster,
|
||||||
|
#[serde(rename = "2")]
|
||||||
|
Vehicle,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: the `serde(name = "...")` is to minimise the length of field
|
// Note: the `serde(name = "...")` is to minimise the length of field
|
||||||
|
@ -185,9 +185,17 @@ impl CharacterBehavior for Data {
|
|||||||
is_point: false,
|
is_point: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
// Send server event to create npc
|
// Send server event to create npc
|
||||||
output_events.emit_server(ServerEvent::CreateNpc {
|
output_events.emit_server(ServerEvent::CreateNpc {
|
||||||
pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
|
pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
|
||||||
|
ori: crate::util::Dir::from_unnormalized(Vec3::new(
|
||||||
|
rng.gen_range(-1.0..=1.0),
|
||||||
|
rng.gen_range(-1.0..=1.0),
|
||||||
|
0.0,
|
||||||
|
))
|
||||||
|
.map(|dir| comp::Ori::from(dir))
|
||||||
|
.unwrap_or_default(),
|
||||||
npc: NpcBuilder::new(stats, body, comp::Alignment::Owned(*data.uid))
|
npc: NpcBuilder::new(stats, body, comp::Alignment::Owned(*data.uid))
|
||||||
.with_skill_set(skill_set)
|
.with_skill_set(skill_set)
|
||||||
.with_health(health)
|
.with_health(health)
|
||||||
@ -204,6 +212,7 @@ impl CharacterBehavior for Data {
|
|||||||
.unwrap_or(comp::Scale(1.0)),
|
.unwrap_or(comp::Scale(1.0)),
|
||||||
)
|
)
|
||||||
.with_projectile(projectile),
|
.with_projectile(projectile),
|
||||||
|
rider: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send local event used for frontend shenanigans
|
// Send local event used for frontend shenanigans
|
||||||
|
@ -29,7 +29,7 @@ use std::{
|
|||||||
/// Note that this number does *not* need incrementing on every change: most
|
/// Note that this number does *not* need incrementing on every change: most
|
||||||
/// field removals/additions are fine. This number should only be incremented
|
/// field removals/additions are fine. This number should only be incremented
|
||||||
/// when we wish to perform a *hard purge* of rtsim data.
|
/// when we wish to perform a *hard purge* of rtsim data.
|
||||||
pub const CURRENT_VERSION: u32 = 4;
|
pub const CURRENT_VERSION: u32 = 5;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
|
@ -10,7 +10,7 @@ use common::{
|
|||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{
|
rtsim::{
|
||||||
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, NpcInput, Personality, ReportId,
|
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, NpcInput, Personality, ReportId,
|
||||||
Role, SiteId, VehicleId,
|
Role, SiteId,
|
||||||
},
|
},
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::CoordinateConversions,
|
terrain::CoordinateConversions,
|
||||||
@ -24,6 +24,7 @@ use std::{
|
|||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
use tracing::error;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use world::{
|
use world::{
|
||||||
civ::Track,
|
civ::Track,
|
||||||
@ -95,19 +96,26 @@ pub struct Brain {
|
|||||||
pub action: Box<dyn Action<(), !>>,
|
pub action: Box<dyn Action<(), !>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
|
// pub struct Relations {
|
||||||
|
// #[serde(skip)]
|
||||||
|
// pub driver: Option<Actor>,
|
||||||
|
// }
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Npc {
|
pub struct Npc {
|
||||||
|
pub uid: u64,
|
||||||
// Persisted state
|
// Persisted state
|
||||||
pub seed: u32,
|
pub seed: u32,
|
||||||
/// Represents the location of the NPC.
|
/// Represents the location of the NPC.
|
||||||
pub wpos: Vec3<f32>,
|
pub wpos: Vec3<f32>,
|
||||||
|
pub dir: Vec2<f32>,
|
||||||
|
|
||||||
pub body: comp::Body,
|
pub body: comp::Body,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
pub home: Option<SiteId>,
|
pub home: Option<SiteId>,
|
||||||
pub faction: Option<FactionId>,
|
pub faction: Option<FactionId>,
|
||||||
pub riding: Option<Riding>,
|
// pub relations: Relations,
|
||||||
|
|
||||||
pub is_dead: bool,
|
pub is_dead: bool,
|
||||||
|
|
||||||
/// The [`Report`]s that the NPC is aware of.
|
/// The [`Report`]s that the NPC is aware of.
|
||||||
@ -143,12 +151,14 @@ pub struct Npc {
|
|||||||
impl Clone for Npc {
|
impl Clone for Npc {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
uid: self.uid,
|
||||||
seed: self.seed,
|
seed: self.seed,
|
||||||
wpos: self.wpos,
|
wpos: self.wpos,
|
||||||
|
dir: self.dir,
|
||||||
role: self.role.clone(),
|
role: self.role.clone(),
|
||||||
home: self.home,
|
home: self.home,
|
||||||
faction: self.faction,
|
faction: self.faction,
|
||||||
riding: self.riding.clone(),
|
// relations: self.relations.clone(),
|
||||||
is_dead: self.is_dead,
|
is_dead: self.is_dead,
|
||||||
known_reports: self.known_reports.clone(),
|
known_reports: self.known_reports.clone(),
|
||||||
body: self.body,
|
body: self.body,
|
||||||
@ -171,15 +181,18 @@ impl Npc {
|
|||||||
|
|
||||||
pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
|
pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
// To be assigned later
|
||||||
|
uid: 0,
|
||||||
seed,
|
seed,
|
||||||
wpos,
|
wpos,
|
||||||
|
dir: Vec2::unit_x(),
|
||||||
body,
|
body,
|
||||||
personality: Default::default(),
|
personality: Default::default(),
|
||||||
sentiments: Default::default(),
|
sentiments: Default::default(),
|
||||||
role,
|
role,
|
||||||
home: None,
|
home: None,
|
||||||
faction: None,
|
faction: None,
|
||||||
riding: None,
|
// relations: Default::default(),
|
||||||
is_dead: false,
|
is_dead: false,
|
||||||
known_reports: Default::default(),
|
known_reports: Default::default(),
|
||||||
chunk_pos: None,
|
chunk_pos: None,
|
||||||
@ -213,24 +226,6 @@ impl Npc {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: have a dedicated `NpcBuilder` type for this.
|
|
||||||
pub fn steering(mut self, vehicle: impl Into<Option<VehicleId>>) -> Self {
|
|
||||||
self.riding = vehicle.into().map(|vehicle| Riding {
|
|
||||||
vehicle,
|
|
||||||
steering: true,
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: have a dedicated `NpcBuilder` type for this.
|
|
||||||
pub fn riding(mut self, vehicle: impl Into<Option<VehicleId>>) -> Self {
|
|
||||||
self.riding = vehicle.into().map(|vehicle| Riding {
|
|
||||||
vehicle,
|
|
||||||
steering: false,
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: have a dedicated `NpcBuilder` type for this.
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
|
pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
|
||||||
self.faction = faction.into();
|
self.faction = faction.into();
|
||||||
@ -246,7 +241,7 @@ impl Npc {
|
|||||||
pub fn profession(&self) -> Option<Profession> {
|
pub fn profession(&self) -> Option<Profession> {
|
||||||
match &self.role {
|
match &self.role {
|
||||||
Role::Civilised(profession) => profession.clone(),
|
Role::Civilised(profession) => profession.clone(),
|
||||||
Role::Monster | Role::Wild => None,
|
Role::Monster | Role::Wild | Role::Vehicle => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,78 +259,221 @@ impl Npc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Riding {
|
|
||||||
pub vehicle: VehicleId,
|
|
||||||
pub steering: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub enum VehicleKind {
|
|
||||||
Airship,
|
|
||||||
Boat,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Merge into `Npc`?
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Vehicle {
|
|
||||||
pub wpos: Vec3<f32>,
|
|
||||||
pub dir: Vec2<f32>,
|
|
||||||
|
|
||||||
pub body: comp::ship::Body,
|
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
pub chunk_pos: Option<Vec2<i32>>,
|
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
pub driver: Option<Actor>,
|
|
||||||
|
|
||||||
/// Whether the Vehicle is in simulated or loaded mode (when rtsim is run on
|
|
||||||
/// the server, loaded corresponds to being within a loaded chunk). When
|
|
||||||
/// in loaded mode, the interactions of the Vehicle should not be
|
|
||||||
/// simulated but should instead be derived from the game.
|
|
||||||
#[serde(skip)]
|
|
||||||
pub mode: SimulationMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vehicle {
|
|
||||||
pub fn new(wpos: Vec3<f32>, body: comp::ship::Body) -> Self {
|
|
||||||
Self {
|
|
||||||
wpos,
|
|
||||||
dir: Vec2::unit_y(),
|
|
||||||
body,
|
|
||||||
chunk_pos: None,
|
|
||||||
driver: None,
|
|
||||||
mode: SimulationMode::Simulated,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_body(&self) -> comp::Body { comp::Body::Ship(self.body) }
|
|
||||||
|
|
||||||
/// Max speed in block/s
|
|
||||||
pub fn get_speed(&self) -> f32 {
|
|
||||||
match self.body {
|
|
||||||
comp::ship::Body::DefaultAirship => 7.0,
|
|
||||||
comp::ship::Body::AirBalloon => 8.0,
|
|
||||||
comp::ship::Body::SailBoat => 5.0,
|
|
||||||
comp::ship::Body::Galleon => 6.0,
|
|
||||||
comp::ship::Body::Skiff => 6.0,
|
|
||||||
comp::ship::Body::Submarine => 4.0,
|
|
||||||
_ => 10.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 vehicles: Vec<VehicleId>,
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct NpcLink {
|
||||||
|
pub mount: NpcId,
|
||||||
|
pub rider: Actor,
|
||||||
|
pub is_steering: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||||
|
struct Riders {
|
||||||
|
steerer: Option<MountId>,
|
||||||
|
riders: Vec<MountId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(
|
||||||
|
from = "HopSlotMap<MountId, NpcLink>",
|
||||||
|
into = "HopSlotMap<MountId, NpcLink>"
|
||||||
|
)]
|
||||||
|
pub struct NpcLinks {
|
||||||
|
links: HopSlotMap<MountId, NpcLink>,
|
||||||
|
mount_map: slotmap::SecondaryMap<NpcId, Riders>,
|
||||||
|
rider_map: HashMap<Actor, MountId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NpcLinks {
|
||||||
|
pub fn remove_mount(&mut self, mount: NpcId) {
|
||||||
|
if let Some(riders) = self.mount_map.remove(mount) {
|
||||||
|
for link in riders
|
||||||
|
.riders
|
||||||
|
.into_iter()
|
||||||
|
.chain(riders.steerer)
|
||||||
|
.filter_map(|link_id| self.links.get(link_id))
|
||||||
|
{
|
||||||
|
self.rider_map.remove(&link.rider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal function, only removes from `mount_map`.
|
||||||
|
fn remove_rider(&mut self, id: MountId, link: &NpcLink) {
|
||||||
|
if let Some(riders) = self.mount_map.get_mut(link.mount) {
|
||||||
|
if link.is_steering && riders.steerer == Some(id) {
|
||||||
|
riders.steerer = None;
|
||||||
|
} else {
|
||||||
|
if let Some((i, _)) = riders.riders.iter().enumerate().find(|(_, i)| **i == id) {
|
||||||
|
riders.riders.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if riders.steerer.is_none() && riders.riders.is_empty() {
|
||||||
|
self.mount_map.remove(link.mount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_link(&mut self, link_id: MountId) {
|
||||||
|
if let Some(link) = self.links.remove(link_id) {
|
||||||
|
self.rider_map.remove(&link.rider);
|
||||||
|
self.remove_rider(link_id, &link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dismount(&mut self, rider: impl Into<Actor>) {
|
||||||
|
if let Some(id) = self.rider_map.remove(&rider.into()) {
|
||||||
|
if let Some(link) = self.links.remove(id) {
|
||||||
|
self.remove_rider(id, &link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the only function to actually add a mount link.
|
||||||
|
// And it ensures that there isn't link chaining
|
||||||
|
pub fn add_mounting(
|
||||||
|
&mut self,
|
||||||
|
mount: NpcId,
|
||||||
|
rider: impl Into<Actor>,
|
||||||
|
steering: bool,
|
||||||
|
) -> Result<MountId, MountingError> {
|
||||||
|
let rider = rider.into();
|
||||||
|
if Actor::Npc(mount) == rider {
|
||||||
|
return Err(MountingError::MountSelf);
|
||||||
|
}
|
||||||
|
if let Actor::Npc(rider) = rider && self.mount_map.contains_key(rider) {
|
||||||
|
return Err(MountingError::RiderIsMounted);
|
||||||
|
}
|
||||||
|
if self.rider_map.contains_key(&Actor::Npc(mount)) {
|
||||||
|
return Err(MountingError::MountIsRiding);
|
||||||
|
}
|
||||||
|
if let Some(mount_entry) = self.mount_map.entry(mount) {
|
||||||
|
if let hashbrown::hash_map::Entry::Vacant(rider_entry) = self.rider_map.entry(rider) {
|
||||||
|
let riders = mount_entry.or_insert(Riders::default());
|
||||||
|
|
||||||
|
if steering {
|
||||||
|
if riders.steerer.is_none() {
|
||||||
|
let id = self.links.insert(NpcLink {
|
||||||
|
mount,
|
||||||
|
rider,
|
||||||
|
is_steering: true,
|
||||||
|
});
|
||||||
|
riders.steerer = Some(id);
|
||||||
|
rider_entry.insert(id);
|
||||||
|
Ok(id)
|
||||||
|
} else {
|
||||||
|
Err(MountingError::HasSteerer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Maybe have some limit on the number of riders depending on the mount?
|
||||||
|
let id = self.links.insert(NpcLink {
|
||||||
|
mount,
|
||||||
|
rider,
|
||||||
|
is_steering: false,
|
||||||
|
});
|
||||||
|
riders.riders.push(id);
|
||||||
|
rider_entry.insert(id);
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(MountingError::AlreadyRiding)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(MountingError::MountDead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn steer(
|
||||||
|
&mut self,
|
||||||
|
mount: NpcId,
|
||||||
|
rider: impl Into<Actor>,
|
||||||
|
) -> Result<MountId, MountingError> {
|
||||||
|
self.add_mounting(mount, rider, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ride(
|
||||||
|
&mut self,
|
||||||
|
mount: NpcId,
|
||||||
|
rider: impl Into<Actor>,
|
||||||
|
) -> Result<MountId, MountingError> {
|
||||||
|
self.add_mounting(mount, rider, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mount_link(&self, rider: impl Into<Actor>) -> Option<&NpcLink> {
|
||||||
|
self.rider_map
|
||||||
|
.get(&rider.into())
|
||||||
|
.and_then(|link| self.links.get(*link))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_steerer_link(&self, mount: NpcId) -> Option<&NpcLink> {
|
||||||
|
self.mount_map
|
||||||
|
.get(mount)
|
||||||
|
.and_then(|mount| self.links.get(mount.steerer?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: MountId) -> Option<&NpcLink> { self.links.get(id) }
|
||||||
|
|
||||||
|
pub fn ids(&self) -> impl Iterator<Item = MountId> + '_ { self.links.keys() }
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &NpcLink> + '_ { self.links.values() }
|
||||||
|
|
||||||
|
pub fn iter_mounts(&self) -> impl Iterator<Item = NpcId> + '_ {
|
||||||
|
self.mount_map.keys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HopSlotMap<MountId, NpcLink>> for NpcLinks {
|
||||||
|
fn from(mut value: HopSlotMap<MountId, NpcLink>) -> Self {
|
||||||
|
let mut from_map = slotmap::SecondaryMap::new();
|
||||||
|
let mut to_map = HashMap::with_capacity(value.len());
|
||||||
|
let mut delete = Vec::new();
|
||||||
|
for (id, link) in value.iter() {
|
||||||
|
if let Some(entry) = from_map.entry(link.mount) {
|
||||||
|
let riders = entry.or_insert(Riders::default());
|
||||||
|
if link.is_steering {
|
||||||
|
if let Some(old) = riders.steerer.replace(id) {
|
||||||
|
error!("Replaced steerer {old:?} with {id:?}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
riders.riders.push(id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete.push(id);
|
||||||
|
}
|
||||||
|
to_map.insert(link.rider, id);
|
||||||
|
}
|
||||||
|
for id in delete {
|
||||||
|
value.remove(id);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
links: value,
|
||||||
|
mount_map: from_map,
|
||||||
|
rider_map: to_map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<HopSlotMap<MountId, NpcLink>> for NpcLinks {
|
||||||
|
fn into(self) -> HopSlotMap<MountId, NpcLink> { self.links }
|
||||||
|
}
|
||||||
|
slotmap::new_key_type! {
|
||||||
|
pub struct MountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MountData {
|
||||||
|
is_steering: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Npcs {
|
pub struct Npcs {
|
||||||
|
pub uid_counter: u64,
|
||||||
pub npcs: HopSlotMap<NpcId, Npc>,
|
pub npcs: HopSlotMap<NpcId, Npc>,
|
||||||
pub vehicles: HopSlotMap<VehicleId, Vehicle>,
|
pub mounts: NpcLinks,
|
||||||
// TODO: This feels like it should be its own rtsim resource
|
// TODO: This feels like it should be its own rtsim resource
|
||||||
// TODO: Consider switching to `common::util::SpatialGrid` instead
|
// TODO: Consider switching to `common::util::SpatialGrid` instead
|
||||||
#[serde(skip, default = "construct_npc_grid")]
|
#[serde(skip, default = "construct_npc_grid")]
|
||||||
@ -347,8 +485,9 @@ pub struct Npcs {
|
|||||||
impl Default for Npcs {
|
impl Default for Npcs {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
uid_counter: 0,
|
||||||
npcs: Default::default(),
|
npcs: Default::default(),
|
||||||
vehicles: Default::default(),
|
mounts: Default::default(),
|
||||||
npc_grid: construct_npc_grid(),
|
npc_grid: construct_npc_grid(),
|
||||||
character_map: Default::default(),
|
character_map: Default::default(),
|
||||||
}
|
}
|
||||||
@ -357,11 +496,22 @@ impl Default for Npcs {
|
|||||||
|
|
||||||
fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
|
fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
|
||||||
|
|
||||||
impl Npcs {
|
#[derive(Debug)]
|
||||||
pub fn create_npc(&mut self, npc: Npc) -> NpcId { self.npcs.insert(npc) }
|
pub enum MountingError {
|
||||||
|
MountDead,
|
||||||
|
RiderDead,
|
||||||
|
HasSteerer,
|
||||||
|
AlreadyRiding,
|
||||||
|
MountIsRiding,
|
||||||
|
RiderIsMounted,
|
||||||
|
MountSelf,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_vehicle(&mut self, vehicle: Vehicle) -> VehicleId {
|
impl Npcs {
|
||||||
self.vehicles.insert(vehicle)
|
pub fn create_npc(&mut self, mut npc: Npc) -> NpcId {
|
||||||
|
npc.uid = self.uid_counter;
|
||||||
|
self.uid_counter += 1;
|
||||||
|
self.npcs.insert(npc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queries nearby npcs, not garantueed to work if radius > 32.0
|
/// Queries nearby npcs, not garantueed to work if radius > 32.0
|
||||||
|
@ -2,7 +2,7 @@ use crate::{RtState, Rule};
|
|||||||
use common::{
|
use common::{
|
||||||
mounting::VolumePos,
|
mounting::VolumePos,
|
||||||
resources::{Time, TimeOfDay},
|
resources::{Time, TimeOfDay},
|
||||||
rtsim::{Actor, VehicleId},
|
rtsim::{Actor, NpcId},
|
||||||
};
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use world::{IndexRef, World};
|
use world::{IndexRef, World};
|
||||||
@ -41,6 +41,6 @@ impl Event for OnDeath {}
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OnMountVolume {
|
pub struct OnMountVolume {
|
||||||
pub actor: Actor,
|
pub actor: Actor,
|
||||||
pub pos: VolumePos<VehicleId>,
|
pub pos: VolumePos<NpcId>,
|
||||||
}
|
}
|
||||||
impl Event for OnMountVolume {}
|
impl Event for OnMountVolume {}
|
||||||
|
@ -4,13 +4,12 @@ pub mod site;
|
|||||||
|
|
||||||
use crate::data::{
|
use crate::data::{
|
||||||
faction::Faction,
|
faction::Faction,
|
||||||
npc::{Npc, Npcs, Profession, Vehicle},
|
npc::{Npc, Npcs, Profession},
|
||||||
site::Site,
|
site::Site,
|
||||||
Data, Nature, CURRENT_VERSION,
|
Data, Nature, CURRENT_VERSION,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
grid::Grid,
|
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
rtsim::{Personality, Role, WorldSettings},
|
rtsim::{Personality, Role, WorldSettings},
|
||||||
terrain::{BiomeKind, CoordinateConversions, TerrainChunkSize},
|
terrain::{BiomeKind, CoordinateConversions, TerrainChunkSize},
|
||||||
@ -32,12 +31,7 @@ impl Data {
|
|||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
version: CURRENT_VERSION,
|
version: CURRENT_VERSION,
|
||||||
nature: Nature::generate(world),
|
nature: Nature::generate(world),
|
||||||
npcs: Npcs {
|
npcs: Npcs::default(),
|
||||||
npcs: Default::default(),
|
|
||||||
vehicles: Default::default(),
|
|
||||||
npc_grid: Grid::new(Vec2::zero(), Default::default()),
|
|
||||||
character_map: Default::default(),
|
|
||||||
},
|
|
||||||
sites: Default::default(),
|
sites: Default::default(),
|
||||||
factions: Default::default(),
|
factions: Default::default(),
|
||||||
reports: Default::default(),
|
reports: Default::default(),
|
||||||
@ -187,11 +181,14 @@ impl Data {
|
|||||||
|
|
||||||
if rng.gen_bool(0.4) {
|
if rng.gen_bool(0.4) {
|
||||||
let wpos = rand_wpos(&mut rng, matches_plazas) + Vec3::unit_z() * 50.0;
|
let wpos = rand_wpos(&mut rng, matches_plazas) + Vec3::unit_z() * 50.0;
|
||||||
let vehicle_id = this
|
let vehicle_id = this.npcs.create_npc(Npc::new(
|
||||||
.npcs
|
rng.gen(),
|
||||||
.create_vehicle(Vehicle::new(wpos, comp::body::ship::Body::DefaultAirship));
|
wpos,
|
||||||
|
Body::Ship(comp::body::ship::Body::DefaultAirship),
|
||||||
|
Role::Vehicle,
|
||||||
|
));
|
||||||
|
|
||||||
this.npcs.create_npc(
|
let npc_id = this.npcs.create_npc(
|
||||||
Npc::new(
|
Npc::new(
|
||||||
rng.gen(),
|
rng.gen(),
|
||||||
wpos,
|
wpos,
|
||||||
@ -199,9 +196,12 @@ impl Data {
|
|||||||
Role::Civilised(Some(Profession::Captain)),
|
Role::Civilised(Some(Profession::Captain)),
|
||||||
)
|
)
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_personality(Personality::random_good(&mut rng))
|
.with_personality(Personality::random_good(&mut rng)),
|
||||||
.steering(vehicle_id),
|
|
||||||
);
|
);
|
||||||
|
this.npcs
|
||||||
|
.mounts
|
||||||
|
.steer(vehicle_id, npc_id)
|
||||||
|
.expect("We just created these npcs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ use crate::{
|
|||||||
use common::{
|
use common::{
|
||||||
astar::{Astar, PathResult},
|
astar::{Astar, PathResult},
|
||||||
comp::{
|
comp::{
|
||||||
|
self,
|
||||||
compass::{Direction, Distance},
|
compass::{Direction, Distance},
|
||||||
dialogue::Subject,
|
dialogue::Subject,
|
||||||
Content,
|
Content,
|
||||||
@ -252,7 +253,7 @@ impl Rule for NpcAi {
|
|||||||
data.npcs
|
data.npcs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
// Don't run AI for dead NPCs
|
// Don't run AI for dead NPCs
|
||||||
.filter(|(_, npc)| !npc.is_dead)
|
.filter(|(_, npc)| !npc.is_dead && !matches!(npc.role, Role::Vehicle))
|
||||||
// Don't run AI for simulated NPCs every tick
|
// Don't run AI for simulated NPCs every tick
|
||||||
.filter(|(_, npc)| matches!(npc.mode, SimulationMode::Loaded) || (npc.seed as u64 + ctx.event.tick) % SIMULATED_TICK_SKIP == 0)
|
.filter(|(_, npc)| matches!(npc.mode, SimulationMode::Loaded) || (npc.seed as u64 + ctx.event.tick) % SIMULATED_TICK_SKIP == 0)
|
||||||
.map(|(npc_id, npc)| {
|
.map(|(npc_id, npc)| {
|
||||||
@ -1157,15 +1158,17 @@ fn react_to_events<S: State>(ctx: &mut NpcCtx, _: &mut S) -> Option<impl Action<
|
|||||||
|
|
||||||
fn humanoid() -> impl Action<DefaultState> {
|
fn humanoid() -> impl Action<DefaultState> {
|
||||||
choose(|ctx, _| {
|
choose(|ctx, _| {
|
||||||
if let Some(riding) = &ctx.npc.riding {
|
if let Some(riding) = &ctx.state.data().npcs.mounts.get_mount_link(ctx.npc_id) {
|
||||||
if riding.steering {
|
if riding.is_steering {
|
||||||
if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) {
|
if let Some(vehicle) = ctx.state.data().npcs.get(riding.mount) {
|
||||||
match vehicle.body {
|
match vehicle.body {
|
||||||
common::comp::ship::Body::DefaultAirship
|
comp::Body::Ship(
|
||||||
| common::comp::ship::Body::AirBalloon => important(pilot(vehicle.body)),
|
body @ comp::ship::Body::DefaultAirship
|
||||||
common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => {
|
| body @ comp::ship::Body::AirBalloon,
|
||||||
important(captain())
|
) => important(pilot(body)),
|
||||||
},
|
comp::Body::Ship(
|
||||||
|
comp::ship::Body::SailBoat | comp::ship::Body::Galleon,
|
||||||
|
) => important(captain()),
|
||||||
_ => casual(idle()),
|
_ => casual(idle()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1310,6 +1313,7 @@ fn think() -> impl Action<DefaultState> {
|
|||||||
.l(),
|
.l(),
|
||||||
Role::Monster => monster().r().r().l(),
|
Role::Monster => monster().r().r().l(),
|
||||||
Role::Wild => idle().r(),
|
Role::Wild => idle().r(),
|
||||||
|
Role::Vehicle => idle().r(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ use common::{
|
|||||||
};
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
|
use slotmap::SecondaryMap;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
use vek::{Clamp, Vec2};
|
use vek::{Clamp, Vec2};
|
||||||
use world::{site::SiteKind, CONFIG};
|
use world::{site::SiteKind, CONFIG};
|
||||||
@ -29,30 +30,39 @@ impl Rule for SimulateNpcs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_setup(ctx: EventCtx<SimulateNpcs, OnSetup>) {
|
fn on_setup(_ctx: EventCtx<SimulateNpcs, OnSetup>) {
|
||||||
|
/*
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
// Add riders to vehicles
|
// Add riders to vehicles
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
let riders = data
|
||||||
if let Some(ride) = &npc.riding {
|
.npcs
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) {
|
.iter()
|
||||||
let actor = Actor::Npc(npc_id);
|
.filter_map(|(id, npc)| Some((id, npc.relations.riding?)))
|
||||||
if ride.steering && vehicle.driver.replace(actor).is_some() {
|
.collect::<Vec<_>>();
|
||||||
error!("Replaced driver");
|
|
||||||
npc.riding = None;
|
for (npc_id, ride) in riders {
|
||||||
|
if let Some(mount) = data.npcs.get_mut(ride.npc) {
|
||||||
|
if ride.steering && let Some(actor) = mount.relations.driver.replace(Actor::Npc(npc_id)) {
|
||||||
|
error!("Replaced driver");
|
||||||
|
if let Actor::Npc(npc) = actor && let Some(npc) = data.npcs.get_mut(npc) {
|
||||||
|
npc.relations.riding = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
data.npcs[npc_id].relations.riding = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mount_volume(ctx: EventCtx<SimulateNpcs, OnMountVolume>) {
|
fn on_mount_volume(ctx: EventCtx<SimulateNpcs, OnMountVolume>) {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
|
// TODO: Add actor to riders.
|
||||||
if let VolumePos { kind: Volume::Entity(vehicle), .. } = ctx.event.pos
|
if let VolumePos { kind: Volume::Entity(vehicle), .. } = ctx.event.pos
|
||||||
&& let Some(vehicle) = data.npcs.vehicles.get(vehicle)
|
&& let Some(link) = data.npcs.mounts.get_steerer_link(vehicle)
|
||||||
&& let Some(Actor::Npc(driver)) = vehicle.driver
|
&& let Actor::Npc(driver) = link.rider
|
||||||
&& let Some(driver) = data.npcs.get_mut(driver) {
|
&& let Some(driver) = data.npcs.get_mut(driver) {
|
||||||
driver.controller.actions.push(NpcAction::Say(Some(ctx.event.actor), comp::Content::localized("npc-speech-welcome-aboard")))
|
driver.controller.actions.push(NpcAction::Say(Some(ctx.event.actor), comp::Content::localized("npc-speech-welcome-aboard")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,8 +81,8 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
.sites
|
.sites
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(id, site)| {
|
.filter(|(id, site)| {
|
||||||
|
// Don't respawn in the same town
|
||||||
Some(*id) != npc.home
|
Some(*id) != npc.home
|
||||||
&& (npc.faction.is_none() || site.faction == npc.faction)
|
|
||||||
&& site.world_site.map_or(false, |s| {
|
&& site.world_site.map_or(false, |s| {
|
||||||
matches!(
|
matches!(
|
||||||
ctx.index.sites.get(s).kind,
|
ctx.index.sites.get(s).kind,
|
||||||
@ -181,106 +191,37 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
|
|
||||||
fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
|
// Maintain links
|
||||||
|
let ids = data.npcs.mounts.ids().collect::<Vec<_>>();
|
||||||
|
let mut mount_activity = SecondaryMap::new();
|
||||||
|
for link_id in ids {
|
||||||
|
if let Some(link) = data.npcs.mounts.get(link_id) {
|
||||||
|
if let Some(mount) = data
|
||||||
|
.npcs
|
||||||
|
.npcs
|
||||||
|
.get(link.mount)
|
||||||
|
.filter(|mount| !mount.is_dead)
|
||||||
|
{
|
||||||
|
let wpos = mount.wpos;
|
||||||
|
if let Actor::Npc(rider) = link.rider {
|
||||||
|
if let Some(rider) =
|
||||||
|
data.npcs.npcs.get_mut(rider).filter(|rider| !rider.is_dead)
|
||||||
|
{
|
||||||
|
rider.wpos = wpos;
|
||||||
|
mount_activity.insert(link.mount, rider.controller.activity);
|
||||||
|
} else {
|
||||||
|
data.npcs.mounts.dismount(link.rider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.npcs.mounts.remove_mount(link.mount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter_mut().filter(|(_, npc)| !npc.is_dead) {
|
for (npc_id, npc) in data.npcs.npcs.iter_mut().filter(|(_, npc)| !npc.is_dead) {
|
||||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
if matches!(npc.mode, SimulationMode::Simulated) {
|
||||||
// Simulate NPC movement when riding
|
|
||||||
if let Some(riding) = &npc.riding {
|
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
|
||||||
match npc.controller.activity {
|
|
||||||
// If steering, the NPC controls the vehicle's motion
|
|
||||||
Some(NpcActivity::Goto(target, speed_factor)) if riding.steering => {
|
|
||||||
let diff = target - vehicle.wpos;
|
|
||||||
let dist2 = diff.magnitude_squared();
|
|
||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
|
||||||
let wpos = vehicle.wpos
|
|
||||||
+ (diff
|
|
||||||
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
|
||||||
/ dist2.sqrt())
|
|
||||||
.min(1.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_().wpos_to_cpos();
|
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get(chunk_pos)
|
|
||||||
.map_or(true, |f| f.river.river_kind.is_some())
|
|
||||||
},
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_valid {
|
|
||||||
vehicle.wpos = wpos;
|
|
||||||
}
|
|
||||||
vehicle.dir = (target.xy() - vehicle.wpos.xy())
|
|
||||||
.try_normalized()
|
|
||||||
.unwrap_or(vehicle.dir);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// When riding, other actions are disabled
|
|
||||||
Some(
|
|
||||||
NpcActivity::Goto(_, _)
|
|
||||||
| NpcActivity::Gather(_)
|
|
||||||
| NpcActivity::HuntAnimals
|
|
||||||
| NpcActivity::Dance(_)
|
|
||||||
| NpcActivity::Cheer(_)
|
|
||||||
| NpcActivity::Sit(_),
|
|
||||||
) => {},
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
npc.wpos = vehicle.wpos;
|
|
||||||
} else {
|
|
||||||
// Vehicle doens't exist anymore
|
|
||||||
npc.riding = None;
|
|
||||||
}
|
|
||||||
// If not riding, we assume they're just walking
|
|
||||||
} else {
|
|
||||||
match npc.controller.activity {
|
|
||||||
// Move NPCs if they have a target destination
|
|
||||||
Some(NpcActivity::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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(
|
|
||||||
NpcActivity::Gather(_)
|
|
||||||
| NpcActivity::HuntAnimals
|
|
||||||
| NpcActivity::Dance(_)
|
|
||||||
| NpcActivity::Cheer(_)
|
|
||||||
| NpcActivity::Sit(_),
|
|
||||||
) => {
|
|
||||||
// TODO: Maybe they should walk around randomly
|
|
||||||
// when gathering resources?
|
|
||||||
},
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
// Make sure NPCs remain on the surface and within the map
|
|
||||||
npc.wpos = npc
|
|
||||||
.wpos
|
|
||||||
.xy()
|
|
||||||
.clamped(
|
|
||||||
Vec2::zero(),
|
|
||||||
(ctx.world.sim().get_size() * TerrainChunkSize::RECT_SIZE).as_(),
|
|
||||||
)
|
|
||||||
.with_z(
|
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32))
|
|
||||||
+ npc.body.flying_height(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume NPC actions
|
// Consume NPC actions
|
||||||
for action in std::mem::take(&mut npc.controller.actions) {
|
for action in std::mem::take(&mut npc.controller.actions) {
|
||||||
match action {
|
match action {
|
||||||
@ -288,6 +229,87 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let activity = if data.npcs.mounts.get_mount_link(npc_id).is_some() {
|
||||||
|
// We are riding, nothing to do.
|
||||||
|
continue;
|
||||||
|
} else if let Some(activity) = mount_activity.get(npc_id) {
|
||||||
|
*activity
|
||||||
|
} else {
|
||||||
|
npc.controller.activity
|
||||||
|
};
|
||||||
|
|
||||||
|
match activity {
|
||||||
|
// Move NPCs if they have a target destination
|
||||||
|
Some(NpcActivity::Goto(target, speed_factor)) => {
|
||||||
|
let diff = target.xy() - npc.wpos.xy();
|
||||||
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
|
if dist2 > 0.5f32.powi(2) {
|
||||||
|
let new_wpos = npc.wpos
|
||||||
|
+ (diff
|
||||||
|
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
||||||
|
/ dist2.sqrt())
|
||||||
|
.min(1.0))
|
||||||
|
.with_z(0.0);
|
||||||
|
|
||||||
|
let is_valid = match npc.body {
|
||||||
|
// Don't move water bound bodies outside of water.
|
||||||
|
Body::Ship(comp::ship::Body::SailBoat | comp::ship::Body::Galleon)
|
||||||
|
| Body::FishMedium(_)
|
||||||
|
| Body::FishSmall(_) => {
|
||||||
|
let chunk_pos = new_wpos.xy().as_().wpos_to_cpos();
|
||||||
|
ctx.world
|
||||||
|
.sim()
|
||||||
|
.get(chunk_pos)
|
||||||
|
.map_or(true, |f| f.river.river_kind.is_some())
|
||||||
|
},
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
npc.wpos = new_wpos;
|
||||||
|
}
|
||||||
|
|
||||||
|
npc.dir = (target.xy() - npc.wpos.xy())
|
||||||
|
.try_normalized()
|
||||||
|
.unwrap_or(npc.dir);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(
|
||||||
|
NpcActivity::Gather(_)
|
||||||
|
| NpcActivity::HuntAnimals
|
||||||
|
| NpcActivity::Dance(_)
|
||||||
|
| NpcActivity::Cheer(_)
|
||||||
|
| NpcActivity::Sit(_),
|
||||||
|
) => {
|
||||||
|
// TODO: Maybe they should walk around randomly
|
||||||
|
// when gathering resources?
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure NPCs remain in a valid location
|
||||||
|
let clamped_wpos = npc.wpos.xy().clamped(
|
||||||
|
Vec2::zero(),
|
||||||
|
(ctx.world.sim().get_size() * TerrainChunkSize::RECT_SIZE).as_(),
|
||||||
|
);
|
||||||
|
match npc.body {
|
||||||
|
Body::Ship(comp::ship::Body::DefaultAirship | comp::ship::Body::AirBalloon) => {
|
||||||
|
npc.wpos = clamped_wpos.with_z(
|
||||||
|
ctx.world
|
||||||
|
.sim()
|
||||||
|
.get_surface_alt_approx(clamped_wpos.as_())
|
||||||
|
.max(npc.wpos.z),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
npc.wpos = clamped_wpos.with_z(
|
||||||
|
ctx.world.sim().get_surface_alt_approx(clamped_wpos.as_())
|
||||||
|
+ npc.body.flying_height(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move home if required
|
// Move home if required
|
||||||
@ -305,31 +327,4 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
npc.home = Some(new_home);
|
npc.home = Some(new_home);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, vehicle) in data.npcs.vehicles.iter_mut() {
|
|
||||||
// Try to keep ships above ground and within the map
|
|
||||||
if matches!(vehicle.mode, SimulationMode::Simulated) {
|
|
||||||
vehicle.wpos = vehicle
|
|
||||||
.wpos
|
|
||||||
.xy()
|
|
||||||
.clamped(
|
|
||||||
Vec2::zero(),
|
|
||||||
(ctx.world.sim().get_size() * TerrainChunkSize::RECT_SIZE).as_(),
|
|
||||||
)
|
|
||||||
.with_z(
|
|
||||||
vehicle.wpos.z.max(
|
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(vehicle.wpos.xy().as_()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(Actor::Npc(driver)) = vehicle.driver
|
|
||||||
&& data.npcs.npcs.get(driver).and_then(|driver| {
|
|
||||||
Some(driver.riding.as_ref()?.vehicle != id)
|
|
||||||
}).unwrap_or(true)
|
|
||||||
{
|
|
||||||
vehicle.driver = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -84,24 +84,6 @@ fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) {
|
|||||||
|
|
||||||
fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
// Update vehicle grid cells
|
|
||||||
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
|
||||||
let chunk_pos = vehicle.wpos.xy().as_().wpos_to_cpos();
|
|
||||||
if vehicle.chunk_pos != Some(chunk_pos) {
|
|
||||||
if let Some(cell) = vehicle
|
|
||||||
.chunk_pos
|
|
||||||
.and_then(|chunk_pos| data.npcs.npc_grid.get_mut(chunk_pos))
|
|
||||||
{
|
|
||||||
if let Some(index) = cell.vehicles.iter().position(|id| *id == vehicle_id) {
|
|
||||||
cell.vehicles.swap_remove(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vehicle.chunk_pos = Some(chunk_pos);
|
|
||||||
if let Some(cell) = data.npcs.npc_grid.get_mut(chunk_pos) {
|
|
||||||
cell.vehicles.push(vehicle_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
||||||
// Update the NPC's current site, if any
|
// Update the NPC's current site, if any
|
||||||
npc.current_site = ctx
|
npc.current_site = ctx
|
||||||
|
@ -674,7 +674,16 @@ fn handle_make_npc(
|
|||||||
} => {
|
} => {
|
||||||
let mut entity_builder = server
|
let mut entity_builder = server
|
||||||
.state
|
.state
|
||||||
.create_npc(pos, stats, skill_set, health, poise, inventory, body)
|
.create_npc(
|
||||||
|
pos,
|
||||||
|
comp::Ori::default(),
|
||||||
|
stats,
|
||||||
|
skill_set,
|
||||||
|
health,
|
||||||
|
poise,
|
||||||
|
inventory,
|
||||||
|
body,
|
||||||
|
)
|
||||||
.with(alignment)
|
.with(alignment)
|
||||||
.with(scale)
|
.with(scale)
|
||||||
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)));
|
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)));
|
||||||
@ -1303,9 +1312,8 @@ fn handle_rtsim_tp(
|
|||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim::RtSim;
|
use crate::rtsim::RtSim;
|
||||||
let (npc_index, dismount_volume) = parse_cmd_args!(args, u32, bool);
|
let (npc_id, dismount_volume) = parse_cmd_args!(args, u64, bool);
|
||||||
let pos = if let Some(id) = npc_index {
|
let pos = if let Some(id) = npc_id {
|
||||||
// TODO: Take some other identifier than an integer to this command.
|
|
||||||
server
|
server
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
@ -1314,8 +1322,8 @@ fn handle_rtsim_tp(
|
|||||||
.data()
|
.data()
|
||||||
.npcs
|
.npcs
|
||||||
.values()
|
.values()
|
||||||
.nth(id as usize)
|
.find(|npc| npc.uid == id)
|
||||||
.ok_or(action.help_string())?
|
.ok_or_else(|| format!("No NPC has the id {id}"))?
|
||||||
.wpos
|
.wpos
|
||||||
} else {
|
} else {
|
||||||
return Err(Content::Plain(action.help_string()));
|
return Err(Content::Plain(action.help_string()));
|
||||||
@ -1335,15 +1343,14 @@ fn handle_rtsim_info(
|
|||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim::RtSim;
|
use crate::rtsim::RtSim;
|
||||||
if let Some(id) = parse_cmd_args!(args, u32) {
|
if let Some(id) = parse_cmd_args!(args, u64) {
|
||||||
// TODO: Take some other identifier than an integer to this command.
|
|
||||||
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
||||||
let data = rtsim.state().data();
|
let data = rtsim.state().data();
|
||||||
let npc = data
|
let (id, npc) = data
|
||||||
.npcs
|
.npcs
|
||||||
.values()
|
.iter()
|
||||||
.nth(id as usize)
|
.find(|(_, npc)| npc.uid == id)
|
||||||
.ok_or_else(|| format!("No NPC has index {}", id))?;
|
.ok_or_else(|| format!("No NPC has the id {id}"))?;
|
||||||
|
|
||||||
let mut info = String::new();
|
let mut info = String::new();
|
||||||
|
|
||||||
@ -1360,7 +1367,10 @@ fn handle_rtsim_info(
|
|||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
&mut info,
|
&mut info,
|
||||||
"Riding: {:?}",
|
"Riding: {:?}",
|
||||||
npc.riding.as_ref().map(|riding| riding.vehicle)
|
data.npcs
|
||||||
|
.mounts
|
||||||
|
.get_mount_link(id)
|
||||||
|
.map(|link| data.npcs.get(link.mount).map_or(0, |mount| mount.uid))
|
||||||
);
|
);
|
||||||
let _ = writeln!(&mut info, "-- Action State --");
|
let _ = writeln!(&mut info, "-- Action State --");
|
||||||
if let Some(brain) = &npc.brain {
|
if let Some(brain) = &npc.brain {
|
||||||
@ -1404,8 +1414,7 @@ fn handle_rtsim_npc(
|
|||||||
let mut npcs = data
|
let mut npcs = data
|
||||||
.npcs
|
.npcs
|
||||||
.values()
|
.values()
|
||||||
.enumerate()
|
.filter(|npc| {
|
||||||
.filter(|(idx, npc)| {
|
|
||||||
let mut tags = vec![
|
let mut tags = vec![
|
||||||
npc.profession()
|
npc.profession()
|
||||||
.map(|p| format!("{:?}", p))
|
.map(|p| format!("{:?}", p))
|
||||||
@ -1414,9 +1423,10 @@ fn handle_rtsim_npc(
|
|||||||
Role::Civilised(_) => "civilised".to_string(),
|
Role::Civilised(_) => "civilised".to_string(),
|
||||||
Role::Wild => "wild".to_string(),
|
Role::Wild => "wild".to_string(),
|
||||||
Role::Monster => "monster".to_string(),
|
Role::Monster => "monster".to_string(),
|
||||||
|
Role::Vehicle => "vehicle".to_string(),
|
||||||
},
|
},
|
||||||
format!("{:?}", npc.mode),
|
format!("{:?}", npc.mode),
|
||||||
format!("{}", idx),
|
format!("{}", npc.uid),
|
||||||
npc_names[&npc.body].keyword.clone(),
|
npc_names[&npc.body].keyword.clone(),
|
||||||
];
|
];
|
||||||
if let Some(species_meta) = npc_names.get_species_meta(&npc.body) {
|
if let Some(species_meta) = npc_names.get_species_meta(&npc.body) {
|
||||||
@ -1428,14 +1438,14 @@ fn handle_rtsim_npc(
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if let Ok(pos) = position(server, target, "target") {
|
if let Ok(pos) = position(server, target, "target") {
|
||||||
npcs.sort_by_key(|(_, npc)| (npc.wpos.distance_squared(pos.0) * 10.0) as u64);
|
npcs.sort_by_key(|npc| (npc.wpos.distance_squared(pos.0) * 10.0) as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut info = String::new();
|
let mut info = String::new();
|
||||||
|
|
||||||
let _ = writeln!(&mut info, "-- NPCs matching [{}] --", terms.join(", "));
|
let _ = writeln!(&mut info, "-- NPCs matching [{}] --", terms.join(", "));
|
||||||
for (idx, npc) in npcs.iter().take(count.unwrap_or(!0) as usize) {
|
for npc in npcs.iter().take(count.unwrap_or(!0) as usize) {
|
||||||
let _ = write!(&mut info, "{} ({}), ", npc.get_name(), idx);
|
let _ = write!(&mut info, "{} ({}), ", npc.get_name(), npc.uid);
|
||||||
}
|
}
|
||||||
let _ = writeln!(&mut info);
|
let _ = writeln!(&mut info);
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
@ -1600,6 +1610,7 @@ fn handle_spawn(
|
|||||||
.state
|
.state
|
||||||
.create_npc(
|
.create_npc(
|
||||||
pos,
|
pos,
|
||||||
|
comp::Ori::default(),
|
||||||
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body)), body),
|
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body)), body),
|
||||||
comp::SkillSet::default(),
|
comp::SkillSet::default(),
|
||||||
Some(comp::Health::new(body, 0)),
|
Some(comp::Health::new(body, 0)),
|
||||||
@ -1706,6 +1717,7 @@ fn handle_spawn_training_dummy(
|
|||||||
.state
|
.state
|
||||||
.create_npc(
|
.create_npc(
|
||||||
pos,
|
pos,
|
||||||
|
comp::Ori::default(),
|
||||||
stats,
|
stats,
|
||||||
skill_set,
|
skill_set,
|
||||||
Some(health),
|
Some(health),
|
||||||
|
@ -17,7 +17,7 @@ use common::{
|
|||||||
mounting::{Mounting, Volume, VolumeMounting, VolumePos},
|
mounting::{Mounting, Volume, VolumeMounting, VolumePos},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
resources::{Secs, Time},
|
resources::{Secs, Time},
|
||||||
rtsim::RtSimVehicle,
|
rtsim::RtSimEntity,
|
||||||
uid::{IdMaps, Uid},
|
uid::{IdMaps, Uid},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
vol::IntoFullVolIterator,
|
vol::IntoFullVolIterator,
|
||||||
@ -99,11 +99,18 @@ pub fn handle_loaded_character_data(
|
|||||||
server.notify_client(entity, result_msg);
|
server.notify_client(entity, result_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) -> EcsEntity {
|
pub fn handle_create_npc(
|
||||||
|
server: &mut Server,
|
||||||
|
pos: Pos,
|
||||||
|
ori: Ori,
|
||||||
|
mut npc: NpcBuilder,
|
||||||
|
rider: Option<NpcBuilder>,
|
||||||
|
) -> EcsEntity {
|
||||||
let entity = server
|
let entity = server
|
||||||
.state
|
.state
|
||||||
.create_npc(
|
.create_npc(
|
||||||
pos,
|
pos,
|
||||||
|
ori,
|
||||||
npc.stats,
|
npc.stats,
|
||||||
npc.skill_set,
|
npc.skill_set,
|
||||||
npc.health,
|
npc.health,
|
||||||
@ -168,8 +175,8 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
|||||||
// Add to group system if a pet
|
// Add to group system if a pet
|
||||||
if let comp::Alignment::Owned(owner_uid) = npc.alignment {
|
if let comp::Alignment::Owned(owner_uid) = npc.alignment {
|
||||||
let state = server.state();
|
let state = server.state();
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
|
||||||
let uids = state.ecs().read_storage::<Uid>();
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
|
let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
|
||||||
if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
|
if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
|
||||||
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
|
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
|
||||||
@ -207,6 +214,20 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
|||||||
let _ = server.state.ecs().write_storage().insert(new_entity, group);
|
let _ = server.state.ecs().write_storage().insert(new_entity, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(rider) = rider {
|
||||||
|
let rider_entity = handle_create_npc(server, pos, Ori::default(), rider, None);
|
||||||
|
let uids = server.state().ecs().read_storage::<Uid>();
|
||||||
|
let link = Mounting {
|
||||||
|
mount: *uids.get(new_entity).expect("We just created this entity"),
|
||||||
|
rider: *uids.get(rider_entity).expect("We just created this entity"),
|
||||||
|
};
|
||||||
|
drop(uids);
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.link(link)
|
||||||
|
.expect("We just created these entities");
|
||||||
|
}
|
||||||
|
|
||||||
new_entity
|
new_entity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +236,7 @@ pub fn handle_create_ship(
|
|||||||
pos: Pos,
|
pos: Pos,
|
||||||
ori: Ori,
|
ori: Ori,
|
||||||
ship: comp::ship::Body,
|
ship: comp::ship::Body,
|
||||||
rtsim_vehicle: Option<RtSimVehicle>,
|
rtsim_entity: Option<RtSimEntity>,
|
||||||
driver: Option<NpcBuilder>,
|
driver: Option<NpcBuilder>,
|
||||||
passengers: Vec<NpcBuilder>,
|
passengers: Vec<NpcBuilder>,
|
||||||
) {
|
) {
|
||||||
@ -252,13 +273,13 @@ pub fn handle_create_ship(
|
|||||||
entity = entity.with(agent);
|
entity = entity.with(agent);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
if let Some(rtsim_vehicle) = rtsim_vehicle {
|
if let Some(rtsim_vehicle) = rtsim_entity {
|
||||||
entity = entity.with(rtsim_vehicle);
|
entity = entity.with(rtsim_vehicle);
|
||||||
}
|
}
|
||||||
let entity = entity.build();
|
let entity = entity.build();
|
||||||
|
|
||||||
if let Some(driver) = driver {
|
if let Some(driver) = driver {
|
||||||
let npc_entity = handle_create_npc(server, pos, driver);
|
let npc_entity = handle_create_npc(server, pos, ori, driver, None);
|
||||||
|
|
||||||
let uids = server.state.ecs().read_storage::<Uid>();
|
let uids = server.state.ecs().read_storage::<Uid>();
|
||||||
let (rider_uid, mount_uid) = uids
|
let (rider_uid, mount_uid) = uids
|
||||||
@ -292,7 +313,13 @@ pub fn handle_create_ship(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for passenger in passengers {
|
for passenger in passengers {
|
||||||
let npc_entity = handle_create_npc(server, Pos(pos.0 + Vec3::unit_z() * 5.0), passenger);
|
let npc_entity = handle_create_npc(
|
||||||
|
server,
|
||||||
|
Pos(pos.0 + Vec3::unit_z() * 5.0),
|
||||||
|
ori,
|
||||||
|
passenger,
|
||||||
|
None,
|
||||||
|
);
|
||||||
if let Some((rider_pos, rider_block)) = seats.next() {
|
if let Some((rider_pos, rider_block)) = seats.next() {
|
||||||
let uids = server.state.ecs().read_storage::<Uid>();
|
let uids = server.state.ecs().read_storage::<Uid>();
|
||||||
let (rider_uid, mount_uid) = uids
|
let (rider_uid, mount_uid) = uids
|
||||||
|
@ -22,7 +22,7 @@ use common::{
|
|||||||
link::Is,
|
link::Is,
|
||||||
mounting::{Mount, Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
|
mounting::{Mount, Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
rtsim::RtSimVehicle,
|
rtsim::RtSimEntity,
|
||||||
terrain::{Block, SpriteKind},
|
terrain::{Block, SpriteKind},
|
||||||
uid::{IdMaps, Uid},
|
uid::{IdMaps, Uid},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
@ -201,7 +201,7 @@ pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: Vo
|
|||||||
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
|
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
|
||||||
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
|
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
|
||||||
let entity = uid_allocator.uid_entity(uid)?;
|
let entity = uid_allocator.uid_entity(uid)?;
|
||||||
state.read_storage::<RtSimVehicle>().get(entity).map(|v| v.0)
|
state.read_storage::<RtSimEntity>().get(entity).map(|v| v.0)
|
||||||
}) {
|
}) {
|
||||||
state.ecs().write_resource::<RtSim>().hook_character_mount_volume(
|
state.ecs().write_resource::<RtSim>().hook_character_mount_volume(
|
||||||
&state.ecs().read_resource::<Arc<world::World>>(),
|
&state.ecs().read_resource::<Arc<world::World>>(),
|
||||||
|
@ -196,8 +196,13 @@ impl Server {
|
|||||||
ServerEvent::ExitIngame { entity } => {
|
ServerEvent::ExitIngame { entity } => {
|
||||||
handle_exit_ingame(self, entity, false);
|
handle_exit_ingame(self, entity, false);
|
||||||
},
|
},
|
||||||
ServerEvent::CreateNpc { pos, npc } => {
|
ServerEvent::CreateNpc {
|
||||||
handle_create_npc(self, pos, npc);
|
pos,
|
||||||
|
ori,
|
||||||
|
npc,
|
||||||
|
rider,
|
||||||
|
} => {
|
||||||
|
handle_create_npc(self, pos, ori, npc, rider);
|
||||||
},
|
},
|
||||||
ServerEvent::CreateShip {
|
ServerEvent::CreateShip {
|
||||||
pos,
|
pos,
|
||||||
|
@ -74,7 +74,7 @@ use common::{
|
|||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
region::RegionMap,
|
region::RegionMap,
|
||||||
resources::{BattleMode, GameMode, Time, TimeOfDay},
|
resources::{BattleMode, GameMode, Time, TimeOfDay},
|
||||||
rtsim::{RtSimEntity, RtSimVehicle},
|
rtsim::RtSimEntity,
|
||||||
shared_server_config::ServerConstants,
|
shared_server_config::ServerConstants,
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::{TerrainChunk, TerrainChunkSize},
|
terrain::{TerrainChunk, TerrainChunkSize},
|
||||||
@ -405,7 +405,6 @@ impl Server {
|
|||||||
state.ecs_mut().register::<login_provider::PendingLogin>();
|
state.ecs_mut().register::<login_provider::PendingLogin>();
|
||||||
state.ecs_mut().register::<RepositionOnChunkLoad>();
|
state.ecs_mut().register::<RepositionOnChunkLoad>();
|
||||||
state.ecs_mut().register::<RtSimEntity>();
|
state.ecs_mut().register::<RtSimEntity>();
|
||||||
state.ecs_mut().register::<RtSimVehicle>();
|
|
||||||
|
|
||||||
// Load banned words list
|
// Load banned words list
|
||||||
let banned_words = settings.moderation.load_banned_words(data_dir);
|
let banned_words = settings.moderation.load_banned_words(data_dir);
|
||||||
@ -846,7 +845,6 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut rtsim = self.state.ecs().write_resource::<rtsim::RtSim>();
|
let mut rtsim = self.state.ecs().write_resource::<rtsim::RtSim>();
|
||||||
let rtsim_entities = self.state.ecs().read_storage::<RtSimEntity>();
|
|
||||||
// Remove NPCs that are outside the view distances of all players
|
// Remove NPCs that are outside the view distances of all players
|
||||||
// This is done by removing NPCs in unloaded chunks
|
// This is done by removing NPCs in unloaded chunks
|
||||||
let to_delete = {
|
let to_delete = {
|
||||||
@ -856,15 +854,9 @@ impl Server {
|
|||||||
&self.state.ecs().read_storage::<comp::Pos>(),
|
&self.state.ecs().read_storage::<comp::Pos>(),
|
||||||
!&self.state.ecs().read_storage::<comp::Presence>(),
|
!&self.state.ecs().read_storage::<comp::Presence>(),
|
||||||
self.state.ecs().read_storage::<Anchor>().maybe(),
|
self.state.ecs().read_storage::<Anchor>().maybe(),
|
||||||
rtsim_entities.maybe(),
|
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(_, pos, _, anchor, rtsim_entity)| {
|
.filter(|(_, pos, _, anchor)| {
|
||||||
if rtsim_entity.map_or(false, |rtsim_entity| {
|
|
||||||
!rtsim.can_unload_entity(*rtsim_entity)
|
|
||||||
}) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let chunk_key = terrain.pos_key(pos.0.map(|e| e.floor() as i32));
|
let chunk_key = terrain.pos_key(pos.0.map(|e| e.floor() as i32));
|
||||||
match anchor {
|
match anchor {
|
||||||
Some(Anchor::Chunk(hc)) => {
|
Some(Anchor::Chunk(hc)) => {
|
||||||
@ -879,26 +871,20 @@ impl Server {
|
|||||||
None => terrain.get_key_real(chunk_key).is_none(),
|
None => terrain.get_key_real(chunk_key).is_none(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|(entity, _, _, _, _)| entity)
|
.map(|(entity, _, _, _)| entity)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
{
|
{
|
||||||
let rtsim_vehicles = self.state.ecs().read_storage::<RtSimVehicle>();
|
let rtsim_entities = self.state.ecs().read_storage::<RtSimEntity>();
|
||||||
|
|
||||||
// Assimilate entities that are part of the real-time world simulation
|
// Assimilate entities that are part of the real-time world simulation
|
||||||
for entity in &to_delete {
|
for entity in &to_delete {
|
||||||
#[cfg(feature = "worldgen")]
|
|
||||||
if let Some(rtsim_entity) = rtsim_entities.get(*entity) {
|
if let Some(rtsim_entity) = rtsim_entities.get(*entity) {
|
||||||
rtsim.hook_rtsim_entity_unload(*rtsim_entity);
|
rtsim.hook_rtsim_entity_unload(*rtsim_entity);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "worldgen")]
|
|
||||||
if let Some(rtsim_vehicle) = rtsim_vehicles.get(*entity) {
|
|
||||||
rtsim.hook_rtsim_vehicle_unload(*rtsim_vehicle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(rtsim_entities);
|
|
||||||
drop(rtsim);
|
drop(rtsim);
|
||||||
|
|
||||||
// Actually perform entity deletion
|
// Actually perform entity deletion
|
||||||
|
@ -6,7 +6,7 @@ use atomicwrites::{AtomicFile, OverwriteBehavior};
|
|||||||
use common::{
|
use common::{
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
mounting::VolumePos,
|
mounting::VolumePos,
|
||||||
rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, VehicleId, WorldSettings},
|
rtsim::{Actor, ChunkResource, NpcId, RtSimEntity, WorldSettings},
|
||||||
};
|
};
|
||||||
use common_ecs::{dispatch, System};
|
use common_ecs::{dispatch, System};
|
||||||
use common_state::BlockDiff;
|
use common_state::BlockDiff;
|
||||||
@ -151,7 +151,7 @@ impl RtSim {
|
|||||||
&mut self,
|
&mut self,
|
||||||
world: &World,
|
world: &World,
|
||||||
index: IndexRef,
|
index: IndexRef,
|
||||||
pos: VolumePos<VehicleId>,
|
pos: VolumePos<NpcId>,
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
) {
|
) {
|
||||||
self.state.emit(OnMountVolume { actor, pos }, world, index)
|
self.state.emit(OnMountVolume { actor, pos }, world, index)
|
||||||
@ -177,31 +177,13 @@ impl RtSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
||||||
if let Some(npc) = self.state.get_data_mut().npcs.get_mut(entity.0) {
|
|
||||||
npc.mode = SimulationMode::Simulated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_unload_entity(&self, entity: RtSimEntity) -> bool {
|
|
||||||
let data = self.state.data();
|
|
||||||
data.npcs
|
|
||||||
.get(entity.0)
|
|
||||||
.and_then(|npc| {
|
|
||||||
let riding = npc.riding.as_ref()?;
|
|
||||||
let vehicle = data.npcs.vehicles.get(riding.vehicle)?;
|
|
||||||
Some(matches!(vehicle.mode, SimulationMode::Simulated))
|
|
||||||
})
|
|
||||||
.unwrap_or(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
|
||||||
let data = self.state.get_data_mut();
|
let data = self.state.get_data_mut();
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(entity.0) {
|
if data.npcs.mounts.get_mount_link(entity.0).is_none() {
|
||||||
vehicle.mode = SimulationMode::Simulated;
|
if let Some(npc) = data.npcs.get_mut(entity.0) {
|
||||||
if let Some(Actor::Npc(npc)) = vehicle.driver {
|
npc.mode = SimulationMode::Simulated;
|
||||||
if let Some(npc) = data.npcs.get_mut(npc) {
|
}
|
||||||
npc.mode = SimulationMode::Simulated;
|
if let Some(steerer) = data.npcs.mounts.get_steerer_link(entity.0) && let Actor::Npc(npc) = steerer.rider && let Some(npc) = data.npcs.get_mut(npc) {
|
||||||
}
|
npc.mode = SimulationMode::Simulated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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::{Actor, RtSimEntity, RtSimVehicle},
|
rtsim::{Actor, NpcId, RtSimEntity},
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::CoordinateConversions,
|
terrain::CoordinateConversions,
|
||||||
trade::{Good, SiteInformation},
|
trade::{Good, SiteInformation},
|
||||||
@ -223,7 +223,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadExpect<'a, SlowJobPool>,
|
ReadExpect<'a, SlowJobPool>,
|
||||||
ReadStorage<'a, comp::Pos>,
|
ReadStorage<'a, comp::Pos>,
|
||||||
ReadStorage<'a, RtSimEntity>,
|
ReadStorage<'a, RtSimEntity>,
|
||||||
ReadStorage<'a, RtSimVehicle>,
|
|
||||||
WriteStorage<'a, comp::Agent>,
|
WriteStorage<'a, comp::Agent>,
|
||||||
ReadStorage<'a, Presence>,
|
ReadStorage<'a, Presence>,
|
||||||
);
|
);
|
||||||
@ -246,7 +245,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
slow_jobs,
|
slow_jobs,
|
||||||
positions,
|
positions,
|
||||||
rtsim_entities,
|
rtsim_entities,
|
||||||
rtsim_vehicles,
|
|
||||||
mut agents,
|
mut agents,
|
||||||
presences,
|
presences,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
@ -294,14 +292,67 @@ impl<'a> System<'a> for Sys {
|
|||||||
let chunk_states = rtsim.state.resource::<ChunkStates>();
|
let chunk_states = rtsim.state.resource::<ChunkStates>();
|
||||||
let data = &mut *rtsim.state.data_mut();
|
let data = &mut *rtsim.state.data_mut();
|
||||||
|
|
||||||
// Load in vehicles
|
let mut create_event = |id: NpcId, npc: &Npc, steering: Option<NpcBuilder>| match npc.body {
|
||||||
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
Body::Ship(body) => {
|
||||||
let chunk = vehicle.wpos.xy().as_::<i32>().wpos_to_cpos();
|
emitter.emit(ServerEvent::CreateShip {
|
||||||
|
pos: comp::Pos(npc.wpos),
|
||||||
|
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
|
||||||
|
ship: body,
|
||||||
|
rtsim_entity: Some(RtSimEntity(id)),
|
||||||
|
driver: steering,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref());
|
||||||
|
|
||||||
if matches!(vehicle.mode, SimulationMode::Simulated)
|
emitter.emit(match NpcData::from_entity_info(entity_info) {
|
||||||
|
NpcData::Data {
|
||||||
|
pos,
|
||||||
|
stats,
|
||||||
|
skill_set,
|
||||||
|
health,
|
||||||
|
poise,
|
||||||
|
inventory,
|
||||||
|
agent,
|
||||||
|
body,
|
||||||
|
alignment,
|
||||||
|
scale,
|
||||||
|
loot,
|
||||||
|
} => ServerEvent::CreateNpc {
|
||||||
|
pos,
|
||||||
|
ori: comp::Ori::from(Dir::new(npc.dir.with_z(0.0))),
|
||||||
|
npc: NpcBuilder::new(stats, body, alignment)
|
||||||
|
.with_skill_set(skill_set)
|
||||||
|
.with_health(health)
|
||||||
|
.with_poise(poise)
|
||||||
|
.with_inventory(inventory)
|
||||||
|
.with_agent(agent.map(|agent| Agent {
|
||||||
|
rtsim_outbox: Some(Default::default()),
|
||||||
|
..agent
|
||||||
|
}))
|
||||||
|
.with_scale(scale)
|
||||||
|
.with_loot(loot)
|
||||||
|
.with_rtsim(RtSimEntity(id)),
|
||||||
|
rider: steering,
|
||||||
|
},
|
||||||
|
// EntityConfig can't represent Waypoints at all
|
||||||
|
// as of now, and if someone will try to spawn
|
||||||
|
// rtsim waypoint it is definitely error.
|
||||||
|
NpcData::Waypoint(_) => unimplemented!(),
|
||||||
|
NpcData::Teleporter(_, _) => unimplemented!(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load in mounted npcs and their riders
|
||||||
|
for mount in data.npcs.mounts.iter_mounts() {
|
||||||
|
let mount_npc = data.npcs.npcs.get_mut(mount).expect("This should exist");
|
||||||
|
let chunk = mount_npc.wpos.xy().as_::<i32>().wpos_to_cpos();
|
||||||
|
|
||||||
|
if matches!(mount_npc.mode, SimulationMode::Simulated)
|
||||||
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
||||||
{
|
{
|
||||||
vehicle.mode = SimulationMode::Loaded;
|
mount_npc.mode = SimulationMode::Loaded;
|
||||||
|
|
||||||
let mut actor_info = |actor: Actor| {
|
let mut actor_info = |actor: Actor| {
|
||||||
let npc_id = actor.npc()?;
|
let npc_id = actor.npc()?;
|
||||||
@ -348,13 +399,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
emitter.emit(ServerEvent::CreateShip {
|
let steerer = data
|
||||||
pos: comp::Pos(vehicle.wpos),
|
.npcs
|
||||||
ori: comp::Ori::from(Dir::new(vehicle.dir.with_z(0.0))),
|
.mounts
|
||||||
ship: vehicle.body,
|
.get_steerer_link(mount)
|
||||||
rtsim_entity: Some(RtSimVehicle(vehicle_id)),
|
.and_then(|link| actor_info(link.rider));
|
||||||
driver: vehicle.driver.and_then(&mut actor_info),
|
|
||||||
});
|
let mount_npc = data.npcs.npcs.get(mount).expect("This should exist");
|
||||||
|
create_event(mount, mount_npc, steerer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,60 +419,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
if matches!(npc.mode, SimulationMode::Simulated)
|
if matches!(npc.mode, SimulationMode::Simulated)
|
||||||
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
||||||
// Riding npcs will be spawned by the vehicle.
|
// Riding npcs will be spawned by the vehicle.
|
||||||
&& npc.riding.is_none()
|
&& data.npcs.mounts.get_mount_link(npc_id).is_none()
|
||||||
{
|
{
|
||||||
npc.mode = SimulationMode::Loaded;
|
npc.mode = SimulationMode::Loaded;
|
||||||
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref());
|
create_event(npc_id, npc, None);
|
||||||
|
|
||||||
emitter.emit(match NpcData::from_entity_info(entity_info) {
|
|
||||||
NpcData::Data {
|
|
||||||
pos,
|
|
||||||
stats,
|
|
||||||
skill_set,
|
|
||||||
health,
|
|
||||||
poise,
|
|
||||||
inventory,
|
|
||||||
agent,
|
|
||||||
body,
|
|
||||||
alignment,
|
|
||||||
scale,
|
|
||||||
loot,
|
|
||||||
} => ServerEvent::CreateNpc {
|
|
||||||
pos,
|
|
||||||
npc: NpcBuilder::new(stats, body, alignment)
|
|
||||||
.with_skill_set(skill_set)
|
|
||||||
.with_health(health)
|
|
||||||
.with_poise(poise)
|
|
||||||
.with_inventory(inventory)
|
|
||||||
.with_agent(agent.map(|agent| Agent {
|
|
||||||
rtsim_outbox: Some(Default::default()),
|
|
||||||
..agent
|
|
||||||
}))
|
|
||||||
.with_scale(scale)
|
|
||||||
.with_loot(loot)
|
|
||||||
.with_rtsim(RtSimEntity(npc_id)),
|
|
||||||
},
|
|
||||||
// EntityConfig can't represent Waypoints at all
|
|
||||||
// as of now, and if someone will try to spawn
|
|
||||||
// rtsim waypoint it is definitely error.
|
|
||||||
NpcData::Waypoint(_) => unimplemented!(),
|
|
||||||
NpcData::Teleporter(_, _) => unimplemented!(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronise rtsim NPC with entity data
|
|
||||||
for (pos, rtsim_vehicle) in (&positions, &rtsim_vehicles).join() {
|
|
||||||
data.npcs
|
|
||||||
.vehicles
|
|
||||||
.get_mut(rtsim_vehicle.0)
|
|
||||||
.filter(|npc| matches!(npc.mode, SimulationMode::Loaded))
|
|
||||||
.map(|vehicle| {
|
|
||||||
// Update rtsim NPC state
|
|
||||||
vehicle.wpos = pos.0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut emitter = server_event_bus.emitter();
|
let mut emitter = server_event_bus.emitter();
|
||||||
// Synchronise rtsim NPC with entity data
|
// Synchronise rtsim NPC with entity data
|
||||||
for (entity, pos, rtsim_entity, agent) in (
|
for (entity, pos, rtsim_entity, agent) in (
|
||||||
|
@ -52,6 +52,7 @@ pub trait StateExt {
|
|||||||
fn create_npc(
|
fn create_npc(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: comp::Pos,
|
pos: comp::Pos,
|
||||||
|
ori: comp::Ori,
|
||||||
stats: comp::Stats,
|
stats: comp::Stats,
|
||||||
skill_set: comp::SkillSet,
|
skill_set: comp::SkillSet,
|
||||||
health: Option<comp::Health>,
|
health: Option<comp::Health>,
|
||||||
@ -275,6 +276,7 @@ impl StateExt for State {
|
|||||||
fn create_npc(
|
fn create_npc(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: comp::Pos,
|
pos: comp::Pos,
|
||||||
|
ori: comp::Ori,
|
||||||
stats: comp::Stats,
|
stats: comp::Stats,
|
||||||
skill_set: comp::SkillSet,
|
skill_set: comp::SkillSet,
|
||||||
health: Option<comp::Health>,
|
health: Option<comp::Health>,
|
||||||
@ -286,14 +288,7 @@ impl StateExt for State {
|
|||||||
.create_entity_synced()
|
.create_entity_synced()
|
||||||
.with(pos)
|
.with(pos)
|
||||||
.with(comp::Vel(Vec3::zero()))
|
.with(comp::Vel(Vec3::zero()))
|
||||||
.with(
|
.with(ori)
|
||||||
comp::Ori::from_unnormalized_vec(Vec3::new(
|
|
||||||
thread_rng().gen_range(-1.0..1.0),
|
|
||||||
thread_rng().gen_range(-1.0..1.0),
|
|
||||||
0.0,
|
|
||||||
))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
.with(body.mass())
|
.with(body.mass())
|
||||||
.with(body.density())
|
.with(body.density())
|
||||||
.with(body.collider())
|
.with(body.collider())
|
||||||
@ -787,10 +782,20 @@ impl StateExt for State {
|
|||||||
// This is the same as wild creatures naturally spawned in the world
|
// This is the same as wild creatures naturally spawned in the world
|
||||||
const DEFAULT_PET_HEALTH_LEVEL: u16 = 0;
|
const DEFAULT_PET_HEALTH_LEVEL: u16 = 0;
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
for (pet, body, stats) in pets {
|
for (pet, body, stats) in pets {
|
||||||
|
let ori = common::util::Dir::from_unnormalized(Vec3::new(
|
||||||
|
rng.gen_range(-1.0..=1.0),
|
||||||
|
rng.gen_range(-1.0..=1.0),
|
||||||
|
0.0,
|
||||||
|
))
|
||||||
|
.map(|dir| comp::Ori::from(dir))
|
||||||
|
.unwrap_or_default();
|
||||||
let pet_entity = self
|
let pet_entity = self
|
||||||
.create_npc(
|
.create_npc(
|
||||||
player_pos,
|
player_pos,
|
||||||
|
ori,
|
||||||
stats,
|
stats,
|
||||||
comp::SkillSet::default(),
|
comp::SkillSet::default(),
|
||||||
Some(comp::Health::new(body, DEFAULT_PET_HEALTH_LEVEL)),
|
Some(comp::Health::new(body, DEFAULT_PET_HEALTH_LEVEL)),
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
use crate::test_world::{IndexOwned, World};
|
use crate::test_world::{IndexOwned, World};
|
||||||
#[cfg(feature = "persistent_world")]
|
#[cfg(feature = "persistent_world")]
|
||||||
use crate::TerrainPersistence;
|
use crate::TerrainPersistence;
|
||||||
|
use rand::Rng;
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
use world::{IndexOwned, World};
|
use world::{IndexOwned, World};
|
||||||
|
|
||||||
@ -137,6 +138,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
// Fetch any generated `TerrainChunk`s and insert them into the terrain.
|
// Fetch any generated `TerrainChunk`s and insert them into the terrain.
|
||||||
// Also, send the chunk data to anybody that is close by.
|
// Also, send the chunk data to anybody that is close by.
|
||||||
let mut new_chunks = Vec::new();
|
let mut new_chunks = Vec::new();
|
||||||
@ -211,6 +213,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
} => {
|
} => {
|
||||||
server_emitter.emit(ServerEvent::CreateNpc {
|
server_emitter.emit(ServerEvent::CreateNpc {
|
||||||
pos,
|
pos,
|
||||||
|
ori: common::util::Dir::from_unnormalized(Vec3::new(
|
||||||
|
rng.gen_range(-1.0..=1.0),
|
||||||
|
rng.gen_range(-1.0..=1.0),
|
||||||
|
0.0,
|
||||||
|
))
|
||||||
|
.map(|dir| comp::Ori::from(dir))
|
||||||
|
.unwrap_or_default(),
|
||||||
npc: NpcBuilder::new(stats, body, alignment)
|
npc: NpcBuilder::new(stats, body, alignment)
|
||||||
.with_skill_set(skill_set)
|
.with_skill_set(skill_set)
|
||||||
.with_health(health)
|
.with_health(health)
|
||||||
@ -220,6 +229,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
.with_scale(scale)
|
.with_scale(scale)
|
||||||
.with_anchor(comp::Anchor::Chunk(key))
|
.with_anchor(comp::Anchor::Chunk(key))
|
||||||
.with_loot(loot),
|
.with_loot(loot),
|
||||||
|
rider: None,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
NpcData::Teleporter(pos, teleporter) => {
|
NpcData::Teleporter(pos, teleporter) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user