mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/id-maps' into 'master'
IdMaps: Uid refactor and adding mappings from rtsim entities and character IDs to ecs entity IDs. See merge request veloren/veloren!3971
This commit is contained in:
commit
f8ae6cdbbe
@ -89,6 +89,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Multiple model support for dropped items (orichalcum armor)
|
||||
- Made rtsim monsters not go into too deep water, and certainly not outside the map.
|
||||
- Fixed bug where npcs would be dismounted from vehicles if loaded/unloaded in a certain order.
|
||||
- Fixed a slow leak on the server where Uid -> Entity mappings weren't cleaned up.
|
||||
- Clients going back into the character screen now properly have their old entity cleaned up on
|
||||
other clients.
|
||||
|
||||
## [0.14.0] - 2023-01-07
|
||||
|
||||
|
@ -10,9 +10,7 @@ pub use crate::error::Error;
|
||||
pub use authc::AuthClientError;
|
||||
pub use common_net::msg::ServerInfo;
|
||||
pub use specs::{
|
||||
join::Join,
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
|
||||
join::Join, Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
|
||||
};
|
||||
|
||||
use crate::addr::ConnectionArgs;
|
||||
@ -49,7 +47,7 @@ use common::{
|
||||
TerrainGrid,
|
||||
},
|
||||
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
vol::RectVolSize,
|
||||
weather::{Weather, WeatherGrid},
|
||||
};
|
||||
@ -2203,7 +2201,7 @@ impl Client {
|
||||
self.chat_mode = m;
|
||||
},
|
||||
ServerGeneral::SetPlayerEntity(uid) => {
|
||||
if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) {
|
||||
if let Some(entity) = self.state.ecs().entity_from_uid(uid) {
|
||||
let old_player_entity = mem::replace(
|
||||
&mut *self.state.ecs_mut().write_resource(),
|
||||
PlayerEntity(Some(entity)),
|
||||
@ -2223,6 +2221,7 @@ impl Client {
|
||||
if let Some(presence) = self.presence {
|
||||
self.presence = Some(match presence {
|
||||
PresenceKind::Spectator => PresenceKind::Spectator,
|
||||
PresenceKind::LoadingCharacter(_) => PresenceKind::Possessor,
|
||||
PresenceKind::Character(_) => PresenceKind::Possessor,
|
||||
PresenceKind::Possessor => PresenceKind::Possessor,
|
||||
});
|
||||
@ -2252,9 +2251,10 @@ impl Client {
|
||||
self.dt_adjustment = dt_adjustment * time_scale.0;
|
||||
},
|
||||
ServerGeneral::EntitySync(entity_sync_package) => {
|
||||
let uid = self.uid();
|
||||
self.state
|
||||
.ecs_mut()
|
||||
.apply_entity_sync_package(entity_sync_package);
|
||||
.apply_entity_sync_package(entity_sync_package, uid);
|
||||
},
|
||||
ServerGeneral::CompSync(comp_sync_package, force_counter) => {
|
||||
self.force_update_counter = force_counter;
|
||||
@ -2265,11 +2265,11 @@ impl Client {
|
||||
ServerGeneral::CreateEntity(entity_package) => {
|
||||
self.state.ecs_mut().apply_entity_package(entity_package);
|
||||
},
|
||||
ServerGeneral::DeleteEntity(entity) => {
|
||||
if self.uid() != Some(entity) {
|
||||
ServerGeneral::DeleteEntity(entity_uid) => {
|
||||
if self.uid() != Some(entity_uid) {
|
||||
self.state
|
||||
.ecs_mut()
|
||||
.delete_entity_and_clear_from_uid_allocator(entity.0);
|
||||
.delete_entity_and_clear_uid_mapping(entity_uid);
|
||||
}
|
||||
},
|
||||
ServerGeneral::Notification(n) => {
|
||||
@ -2738,24 +2738,21 @@ impl Client {
|
||||
// Clear pending trade
|
||||
self.pending_trade = None;
|
||||
|
||||
let client_uid = self
|
||||
.uid()
|
||||
.map(|u| u.into())
|
||||
.expect("Client doesn't have a Uid!!!");
|
||||
let client_uid = self.uid().expect("Client doesn't have a Uid!!!");
|
||||
|
||||
// Clear ecs of all entities
|
||||
self.state.ecs_mut().delete_all();
|
||||
self.state.ecs_mut().maintain();
|
||||
self.state.ecs_mut().insert(UidAllocator::default());
|
||||
self.state.ecs_mut().insert(IdMaps::default());
|
||||
|
||||
// Recreate client entity with Uid
|
||||
let entity_builder = self.state.ecs_mut().create_entity();
|
||||
let uid = entity_builder
|
||||
entity_builder
|
||||
.world
|
||||
.write_resource::<UidAllocator>()
|
||||
.allocate(entity_builder.entity, Some(client_uid));
|
||||
.write_resource::<IdMaps>()
|
||||
.add_entity(client_uid, entity_builder.entity);
|
||||
|
||||
let entity = entity_builder.with(uid).build();
|
||||
let entity = entity_builder.with(client_uid).build();
|
||||
self.state.ecs().write_resource::<PlayerEntity>().0 = Some(entity);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ mod sync_ext;
|
||||
mod track;
|
||||
|
||||
// Reexports
|
||||
pub use common::uid::{Uid, UidAllocator};
|
||||
pub use common::uid::{IdMaps, Uid};
|
||||
pub use net_sync::{NetSync, SyncFrom};
|
||||
pub use packet::{
|
||||
handle_insert, handle_interp_insert, handle_interp_modify, handle_interp_remove, handle_modify,
|
||||
|
@ -101,26 +101,26 @@ pub enum CompUpdateKind<P: CompPacket> {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EntityPackage<P: CompPacket> {
|
||||
pub uid: u64,
|
||||
pub uid: Uid,
|
||||
pub comps: Vec<P>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EntitySyncPackage {
|
||||
pub created_entities: Vec<u64>,
|
||||
pub deleted_entities: Vec<u64>,
|
||||
pub created_entities: Vec<Uid>,
|
||||
pub deleted_entities: Vec<Uid>,
|
||||
}
|
||||
impl EntitySyncPackage {
|
||||
pub fn new(
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
uid_tracker: &UpdateTracker<Uid>,
|
||||
filter: impl Join + Copy,
|
||||
deleted_entities: Vec<u64>,
|
||||
deleted_entities: Vec<Uid>,
|
||||
) -> Self {
|
||||
// Add created and deleted entities
|
||||
let created_entities = (uids, filter, uid_tracker.inserted())
|
||||
.join()
|
||||
.map(|(uid, _, _)| (*uid).into())
|
||||
.map(|(uid, _, _)| *uid)
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
|
@ -4,13 +4,9 @@ use super::{
|
||||
};
|
||||
use common::{
|
||||
resources::PlayerEntity,
|
||||
uid::{Uid, UidAllocator},
|
||||
};
|
||||
use specs::{
|
||||
saveload::{MarkedBuilder, MarkerAllocator},
|
||||
world::Builder,
|
||||
WorldExt,
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use specs::{world::Builder, WorldExt};
|
||||
use tracing::error;
|
||||
|
||||
pub trait WorldSyncExt {
|
||||
@ -22,21 +18,21 @@ pub trait WorldSyncExt {
|
||||
where
|
||||
C::Storage: Default + specs::storage::Tracked;
|
||||
fn create_entity_synced(&mut self) -> specs::EntityBuilder;
|
||||
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64);
|
||||
fn delete_entity_and_clear_uid_mapping(&mut self, uid: Uid);
|
||||
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>;
|
||||
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity>;
|
||||
fn entity_from_uid(&self, uid: Uid) -> Option<specs::Entity>;
|
||||
fn apply_entity_package<P: CompPacket>(
|
||||
&mut self,
|
||||
entity_package: EntityPackage<P>,
|
||||
) -> specs::Entity;
|
||||
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage);
|
||||
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage, client_uid: Option<Uid>);
|
||||
fn apply_comp_sync_package<P: CompPacket>(&mut self, package: CompSyncPackage<P>);
|
||||
}
|
||||
|
||||
impl WorldSyncExt for specs::World {
|
||||
fn register_sync_marker(&mut self) {
|
||||
self.register_synced::<Uid>();
|
||||
self.insert(UidAllocator::new());
|
||||
self.insert(IdMaps::new());
|
||||
}
|
||||
|
||||
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
|
||||
@ -56,12 +52,29 @@ impl WorldSyncExt for specs::World {
|
||||
}
|
||||
|
||||
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
|
||||
self.create_entity().marked::<Uid>()
|
||||
// TODO: Add metric for number of new entities created in a tick? Most
|
||||
// convenient would be to store counter in `IdMaps` so that we don't
|
||||
// have to fetch another resource nor require an additional parameter here
|
||||
// nor use globals.
|
||||
let builder = self.create_entity();
|
||||
let uid = builder
|
||||
.world
|
||||
.write_resource::<IdMaps>()
|
||||
.allocate(builder.entity);
|
||||
builder.with(uid)
|
||||
}
|
||||
|
||||
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64) {
|
||||
/// This method should be used from the client-side when processing network
|
||||
/// messages that delete entities.
|
||||
///
|
||||
/// Only used on the client.
|
||||
fn delete_entity_and_clear_uid_mapping(&mut self, uid: Uid) {
|
||||
// Clear from uid allocator
|
||||
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity(uid);
|
||||
let maybe_entity = self.write_resource::<IdMaps>()
|
||||
// Note, rtsim entity and character id mappings don't exist on the client but it is
|
||||
// easier to reuse the same structure here since there is shared code that needs to
|
||||
// lookup the entity from a uid.
|
||||
.remove_entity(None, Some(uid), None, None);
|
||||
if let Some(entity) = maybe_entity {
|
||||
if let Err(e) = self.delete_entity(entity) {
|
||||
error!(?e, "Failed to delete entity");
|
||||
@ -75,9 +88,8 @@ impl WorldSyncExt for specs::World {
|
||||
}
|
||||
|
||||
/// Get an entity from a UID
|
||||
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity> {
|
||||
self.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(uid)
|
||||
fn entity_from_uid(&self, uid: Uid) -> Option<specs::Entity> {
|
||||
self.read_resource::<IdMaps>().uid_entity(uid)
|
||||
}
|
||||
|
||||
fn apply_entity_package<P: CompPacket>(
|
||||
@ -94,7 +106,7 @@ impl WorldSyncExt for specs::World {
|
||||
entity
|
||||
}
|
||||
|
||||
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage) {
|
||||
fn apply_entity_sync_package(&mut self, package: EntitySyncPackage, client_uid: Option<Uid>) {
|
||||
// Take ownership of the fields
|
||||
let EntitySyncPackage {
|
||||
created_entities,
|
||||
@ -108,7 +120,13 @@ impl WorldSyncExt for specs::World {
|
||||
|
||||
// Attempt to delete entities that were marked for deletion
|
||||
deleted_entities.into_iter().for_each(|uid| {
|
||||
self.delete_entity_and_clear_from_uid_allocator(uid);
|
||||
// Skip deleting the client's own entity. It will appear here when exiting
|
||||
// "in-game" and the client already handles cleaning things up
|
||||
// there. Although the client might not get this anyway since it
|
||||
// should no longer be subscribed to any regions.
|
||||
if client_uid != Some(uid) {
|
||||
self.delete_entity_and_clear_uid_mapping(uid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -116,10 +134,7 @@ impl WorldSyncExt for specs::World {
|
||||
// Update components
|
||||
let player_entity = self.read_resource::<PlayerEntity>().0;
|
||||
package.comp_updates.into_iter().for_each(|(uid, update)| {
|
||||
if let Some(entity) = self
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(uid)
|
||||
{
|
||||
if let Some(entity) = self.read_resource::<IdMaps>().uid_entity(uid.into()) {
|
||||
let force_update = player_entity == Some(entity);
|
||||
match update {
|
||||
CompUpdateKind::Inserted(packet) => {
|
||||
@ -138,20 +153,23 @@ impl WorldSyncExt for specs::World {
|
||||
}
|
||||
|
||||
// Private utilities
|
||||
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> specs::Entity {
|
||||
let existing_entity = specs_world
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(entity_uid);
|
||||
//
|
||||
// Only used on the client.
|
||||
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: Uid) -> specs::Entity {
|
||||
let existing_entity = specs_world.read_resource::<IdMaps>().uid_entity(entity_uid);
|
||||
|
||||
// TODO: Are there any expected cases where there is an existing entity with
|
||||
// this UID? If not, we may want to log an error. Otherwise, it may be useful to
|
||||
// document these cases.
|
||||
match existing_entity {
|
||||
Some(entity) => entity,
|
||||
None => {
|
||||
let entity_builder = specs_world.create_entity();
|
||||
let uid = entity_builder
|
||||
entity_builder
|
||||
.world
|
||||
.write_resource::<UidAllocator>()
|
||||
.allocate(entity_builder.entity, Some(entity_uid));
|
||||
entity_builder.with(uid).build()
|
||||
.write_resource::<IdMaps>()
|
||||
.add_entity(entity_uid, entity_builder.entity);
|
||||
entity_builder.with(entity_uid).build()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ use crate::{
|
||||
outcome::Outcome,
|
||||
resources::Secs,
|
||||
states::utils::StageSection,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
util::Dir,
|
||||
};
|
||||
|
||||
@ -29,7 +29,7 @@ use crate::{comp::Group, resources::Time};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use {
|
||||
rand::Rng,
|
||||
specs::{saveload::MarkerAllocator, Entity as EcsEntity, ReadStorage},
|
||||
specs::{Entity as EcsEntity, ReadStorage},
|
||||
std::ops::{Mul, MulAssign},
|
||||
vek::*,
|
||||
};
|
||||
@ -714,7 +714,7 @@ impl Attack {
|
||||
pub fn may_harm(
|
||||
alignments: &ReadStorage<Alignment>,
|
||||
players: &ReadStorage<Player>,
|
||||
uid_allocator: &UidAllocator,
|
||||
id_maps: &IdMaps,
|
||||
attacker: Option<EcsEntity>,
|
||||
target: EcsEntity,
|
||||
) -> bool {
|
||||
@ -725,9 +725,7 @@ pub fn may_harm(
|
||||
if let Some(Alignment::Owned(uid)) = alignment {
|
||||
// return original entity
|
||||
// if can't get owner
|
||||
uid_allocator
|
||||
.retrieve_entity_internal(uid.into())
|
||||
.unwrap_or(entity)
|
||||
id_maps.uid_entity(uid).unwrap_or(entity)
|
||||
} else {
|
||||
entity
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ use vek::*;
|
||||
pub struct Presence {
|
||||
pub terrain_view_distance: ViewDistance,
|
||||
pub entity_view_distance: ViewDistance,
|
||||
/// If mutating this (or the adding/replacing the Presence component as a
|
||||
/// whole), make sure the mapping of `CharacterId` in `IdMaps` is
|
||||
/// updated!
|
||||
pub kind: PresenceKind,
|
||||
pub lossy_terrain_compression: bool,
|
||||
}
|
||||
@ -31,6 +34,10 @@ impl Component for Presence {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PresenceKind {
|
||||
Spectator,
|
||||
// Note: we don't know if this character ID is valid and associated with the player until the
|
||||
// character has loaded successfully. The ID should only be trusted and included in the
|
||||
// mapping when the variant is changed to `Character`.
|
||||
LoadingCharacter(CharacterId),
|
||||
Character(CharacterId),
|
||||
Possessor,
|
||||
}
|
||||
@ -63,7 +70,7 @@ enum 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)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ViewDistance {
|
||||
direction: Direction,
|
||||
last_direction_change_time: Instant,
|
||||
|
@ -2,14 +2,14 @@ use crate::{
|
||||
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||
link::{Is, Link, LinkHandle, Role},
|
||||
terrain::{Block, TerrainGrid},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, storage::GenericWriteStorage, Component, DenseVecStorage, Entities,
|
||||
Entity, Read, ReadExpect, ReadStorage, Write, WriteStorage,
|
||||
storage::GenericWriteStorage, Component, DenseVecStorage, Entities, Entity, Read, ReadExpect,
|
||||
ReadStorage, Write, WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
@ -41,13 +41,13 @@ pub enum MountingError {
|
||||
|
||||
impl Link for Mounting {
|
||||
type CreateData<'a> = (
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
WriteStorage<'a, Is<Mount>>,
|
||||
WriteStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
);
|
||||
type DeleteData<'a> = (
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
WriteStorage<'a, Is<Mount>>,
|
||||
WriteStorage<'a, Is<Rider>>,
|
||||
WriteStorage<'a, comp::Pos>,
|
||||
@ -56,7 +56,7 @@ impl Link for Mounting {
|
||||
);
|
||||
type Error = MountingError;
|
||||
type PersistData<'a> = (
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
Entities<'a>,
|
||||
ReadStorage<'a, comp::Health>,
|
||||
ReadStorage<'a, comp::Body>,
|
||||
@ -67,9 +67,9 @@ impl Link for Mounting {
|
||||
|
||||
fn create(
|
||||
this: &LinkHandle<Self>,
|
||||
(uid_allocator, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
|
||||
(id_maps, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
if this.mount == this.rider {
|
||||
// Forbid self-mounting
|
||||
@ -97,9 +97,9 @@ impl Link for Mounting {
|
||||
|
||||
fn persist(
|
||||
this: &LinkHandle<Self>,
|
||||
(uid_allocator, entities, healths, bodies, is_mounts, is_riders, character_states): &mut Self::PersistData<'_>,
|
||||
(id_maps, entities, healths, bodies, is_mounts, is_riders, character_states): &mut Self::PersistData<'_>,
|
||||
) -> bool {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
|
||||
let is_alive = |entity| {
|
||||
@ -126,9 +126,11 @@ impl Link for Mounting {
|
||||
|
||||
fn delete(
|
||||
this: &LinkHandle<Self>,
|
||||
(uid_allocator, is_mounts, is_riders, positions, force_update, terrain): &mut Self::DeleteData<'_>,
|
||||
(id_maps, is_mounts, is_riders, positions, force_update, terrain): &mut Self::DeleteData<
|
||||
'_,
|
||||
>,
|
||||
) {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
let mount = entity(this.mount);
|
||||
let rider = entity(this.rider);
|
||||
@ -218,7 +220,7 @@ impl VolumePos {
|
||||
pub fn get_block_and_transform(
|
||||
&self,
|
||||
terrain: &TerrainGrid,
|
||||
uid_allocator: &UidAllocator,
|
||||
id_maps: &IdMaps,
|
||||
mut read_pos_and_ori: impl FnMut(Entity) -> Option<(comp::Pos, comp::Ori)>,
|
||||
colliders: &ReadStorage<comp::Collider>,
|
||||
) -> Option<(Mat4<f32>, comp::Ori, Block)> {
|
||||
@ -228,26 +230,22 @@ impl VolumePos {
|
||||
comp::Ori::default(),
|
||||
*terrain.get(self.pos).ok()?,
|
||||
)),
|
||||
Volume::Entity(uid) => {
|
||||
uid_allocator
|
||||
.retrieve_entity_internal(uid.0)
|
||||
.and_then(|entity| {
|
||||
let collider = colliders.get(entity)?;
|
||||
let (pos, ori) = read_pos_and_ori(entity)?;
|
||||
Volume::Entity(uid) => id_maps.uid_entity(uid).and_then(|entity| {
|
||||
let collider = colliders.get(entity)?;
|
||||
let (pos, ori) = read_pos_and_ori(entity)?;
|
||||
|
||||
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
||||
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
|
||||
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
||||
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
|
||||
|
||||
let block = *voxel_collider.volume().get(self.pos).ok()?;
|
||||
let block = *voxel_collider.volume().get(self.pos).ok()?;
|
||||
|
||||
let local_translation = voxel_collider.translation + self.pos.as_();
|
||||
let local_translation = voxel_collider.translation + self.pos.as_();
|
||||
|
||||
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
|
||||
* Mat4::<f32>::translation_3d(local_translation);
|
||||
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
|
||||
* Mat4::<f32>::translation_3d(local_translation);
|
||||
|
||||
Some((trans, ori, block))
|
||||
})
|
||||
},
|
||||
Some((trans, ori, block))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,25 +253,21 @@ impl VolumePos {
|
||||
pub fn get_block(
|
||||
&self,
|
||||
terrain: &TerrainGrid,
|
||||
uid_allocator: &UidAllocator,
|
||||
id_maps: &IdMaps,
|
||||
colliders: &ReadStorage<comp::Collider>,
|
||||
) -> Option<Block> {
|
||||
match self.kind {
|
||||
Volume::Terrain => Some(*terrain.get(self.pos).ok()?),
|
||||
Volume::Entity(uid) => {
|
||||
uid_allocator
|
||||
.retrieve_entity_internal(uid.0)
|
||||
.and_then(|entity| {
|
||||
let collider = colliders.get(entity)?;
|
||||
Volume::Entity(uid) => id_maps.uid_entity(uid).and_then(|entity| {
|
||||
let collider = colliders.get(entity)?;
|
||||
|
||||
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
||||
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
|
||||
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
||||
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
|
||||
|
||||
let block = *voxel_collider.volume().get(self.pos).ok()?;
|
||||
let block = *voxel_collider.volume().get(self.pos).ok()?;
|
||||
|
||||
Some(block)
|
||||
})
|
||||
},
|
||||
Some(block)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -301,14 +295,14 @@ impl Link for VolumeMounting {
|
||||
WriteStorage<'a, Is<VolumeRider>>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
ReadStorage<'a, comp::Collider>,
|
||||
);
|
||||
type DeleteData<'a> = (
|
||||
Write<'a, VolumeRiders>,
|
||||
WriteStorage<'a, VolumeRiders>,
|
||||
WriteStorage<'a, Is<VolumeRider>>,
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
);
|
||||
type Error = MountingError;
|
||||
type PersistData<'a> = (
|
||||
@ -318,7 +312,7 @@ impl Link for VolumeMounting {
|
||||
ReadStorage<'a, VolumeRiders>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
ReadStorage<'a, comp::Collider>,
|
||||
);
|
||||
|
||||
@ -330,11 +324,11 @@ impl Link for VolumeMounting {
|
||||
is_volume_riders,
|
||||
is_riders,
|
||||
terrain_grid,
|
||||
uid_allocator,
|
||||
id_maps,
|
||||
colliders,
|
||||
): &mut Self::CreateData<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
let riders = match this.pos.kind {
|
||||
Volume::Terrain => &mut *terrain_riders,
|
||||
@ -351,7 +345,7 @@ impl Link for VolumeMounting {
|
||||
{
|
||||
let block = this
|
||||
.pos
|
||||
.get_block(terrain_grid, uid_allocator, colliders)
|
||||
.get_block(terrain_grid, id_maps, colliders)
|
||||
.ok_or(MountingError::NoSuchEntity)?;
|
||||
|
||||
if block == this.block {
|
||||
@ -375,11 +369,11 @@ impl Link for VolumeMounting {
|
||||
volume_riders,
|
||||
is_volume_riders,
|
||||
terrain_grid,
|
||||
uid_allocator,
|
||||
id_maps,
|
||||
colliders,
|
||||
): &mut Self::PersistData<'_>,
|
||||
) -> bool {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
let is_alive =
|
||||
|entity| entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead);
|
||||
let riders = match this.pos.kind {
|
||||
@ -401,7 +395,7 @@ impl Link for VolumeMounting {
|
||||
|
||||
let block_exists = this
|
||||
.pos
|
||||
.get_block(terrain_grid, uid_allocator, colliders)
|
||||
.get_block(terrain_grid, id_maps, colliders)
|
||||
.map_or(false, |block| block == this.block);
|
||||
|
||||
rider_exists && mount_spot_exists && block_exists
|
||||
@ -409,9 +403,9 @@ impl Link for VolumeMounting {
|
||||
|
||||
fn delete(
|
||||
this: &LinkHandle<Self>,
|
||||
(terrain_riders, volume_riders, is_rider, uid_allocator): &mut Self::DeleteData<'_>,
|
||||
(terrain_riders, volume_riders, is_rider, id_maps): &mut Self::DeleteData<'_>,
|
||||
) {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||
|
||||
let riders = match this.pos.kind {
|
||||
Volume::Terrain => Some(&mut **terrain_riders),
|
||||
|
@ -7,6 +7,9 @@ use vek::*;
|
||||
|
||||
pub enum Event {
|
||||
// Contains the key of the region the entity moved to
|
||||
// TODO: We only actually use the info from this when the entity moves to another region and
|
||||
// isn't deleted, but we still generate and process these events in the case where the entity
|
||||
// was deleted.
|
||||
Left(u32, Option<Vec2<i32>>),
|
||||
// Contains the key of the region the entity came from
|
||||
Entered(u32, Option<Vec2<i32>>),
|
||||
@ -147,7 +150,8 @@ impl RegionMap {
|
||||
// component) TODO: distribute this between ticks
|
||||
None => {
|
||||
// TODO: shouldn't there be a way to extract the bitset of entities with
|
||||
// positions directly from specs?
|
||||
// positions directly from specs? Yes, with `.mask()` on the component
|
||||
// storage.
|
||||
entities_to_remove.push((i, id));
|
||||
},
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ slotmap::new_key_type! { pub struct FactionId; }
|
||||
|
||||
slotmap::new_key_type! { pub struct ReportId; }
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct RtSimEntity(pub NpcId);
|
||||
|
||||
impl Component for RtSimEntity {
|
||||
|
@ -1,14 +1,17 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
world::EntitiesRes,
|
||||
Component, Entity, FlaggedStorage, Join, ReadStorage, VecStorage,
|
||||
};
|
||||
use std::{fmt, u64};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use {
|
||||
crate::character::CharacterId,
|
||||
crate::rtsim::RtSimEntity,
|
||||
core::hash::Hash,
|
||||
hashbrown::HashMap,
|
||||
specs::{Component, Entity, FlaggedStorage, VecStorage},
|
||||
tracing::error,
|
||||
};
|
||||
|
||||
// TODO: could we switch this to `NonZeroU64`?
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Uid(pub u64);
|
||||
|
||||
@ -24,67 +27,171 @@ impl fmt::Display for Uid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) }
|
||||
}
|
||||
|
||||
pub use not_wasm::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Component for Uid {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
mod not_wasm {
|
||||
use super::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Marker for Uid {
|
||||
type Allocator = UidAllocator;
|
||||
type Identifier = u64;
|
||||
|
||||
fn id(&self) -> u64 { self.0 }
|
||||
|
||||
fn update(&mut self, update: Self) {
|
||||
assert_eq!(self.0, update.0);
|
||||
impl Component for Uid {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[derive(Debug)]
|
||||
pub struct UidAllocator {
|
||||
index: u64,
|
||||
mapping: HashMap<u64, Entity>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct UidAllocator {
|
||||
/// Next Uid.
|
||||
next_uid: u64,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl UidAllocator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
mapping: HashMap::new(),
|
||||
impl UidAllocator {
|
||||
fn new() -> Self { Self { next_uid: 0 } }
|
||||
|
||||
fn allocate(&mut self) -> Uid {
|
||||
let id = self.next_uid;
|
||||
self.next_uid += 1;
|
||||
Uid(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Useful for when a single entity is deleted because it doesn't reconstruct the
|
||||
// entire hashmap
|
||||
pub fn remove_entity(&mut self, id: u64) -> Option<Entity> { self.mapping.remove(&id) }
|
||||
}
|
||||
/// Mappings from various Id types to `Entity`s.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct IdMaps {
|
||||
/// "Universal" IDs (used to communicate entity identity over the
|
||||
/// network).
|
||||
uid_mapping: HashMap<Uid, Entity>,
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl Default for UidAllocator {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
// -- Fields below are only used on the server --
|
||||
uid_allocator: UidAllocator,
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl MarkerAllocator<Uid> for UidAllocator {
|
||||
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
|
||||
let id = id.unwrap_or_else(|| {
|
||||
let id = self.index;
|
||||
self.index += 1;
|
||||
id
|
||||
});
|
||||
self.mapping.insert(id, entity);
|
||||
Uid(id)
|
||||
/// Character IDs.
|
||||
character_to_ecs: HashMap<CharacterId, Entity>,
|
||||
/// Rtsim Entities.
|
||||
rtsim_to_ecs: HashMap<RtSimEntity, Entity>,
|
||||
}
|
||||
|
||||
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> { self.mapping.get(&id).copied() }
|
||||
impl IdMaps {
|
||||
pub fn new() -> Self { Default::default() }
|
||||
|
||||
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
|
||||
self.mapping = (entities, storage)
|
||||
.join()
|
||||
.map(|(e, m)| (m.id(), e))
|
||||
.collect();
|
||||
/// Given a `Uid` retrieve the corresponding `Entity`.
|
||||
pub fn uid_entity(&self, id: Uid) -> Option<Entity> { self.uid_mapping.get(&id).copied() }
|
||||
|
||||
/// Given a `CharacterId` retrieve the corresponding `Entity`.
|
||||
pub fn character_entity(&self, id: CharacterId) -> Option<Entity> {
|
||||
self.character_to_ecs.get(&id).copied()
|
||||
}
|
||||
|
||||
/// Given a `RtSimEntity` retrieve the corresponding `Entity`.
|
||||
pub fn rtsim_entity(&self, id: RtSimEntity) -> Option<Entity> {
|
||||
self.rtsim_to_ecs.get(&id).copied()
|
||||
}
|
||||
|
||||
/// Removes mappings for the provided Id(s).
|
||||
///
|
||||
/// Returns the `Entity` that the provided `Uid` was mapped to.
|
||||
///
|
||||
/// Used on both the client and the server when deleting entities,
|
||||
/// although the client only ever provides a Some value for the
|
||||
/// `Uid` parameter since the other mappings are not used on the
|
||||
/// client.
|
||||
pub fn remove_entity(
|
||||
&mut self,
|
||||
expected_entity: Option<Entity>,
|
||||
uid: Option<Uid>,
|
||||
cid: Option<CharacterId>,
|
||||
rid: Option<RtSimEntity>,
|
||||
) -> Option<Entity> {
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn unexpected_entity<ID>() {
|
||||
let kind = core::any::type_name::<ID>();
|
||||
error!("Provided {kind} was mapped to an unexpected entity!");
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn not_present<ID>() {
|
||||
let kind = core::any::type_name::<ID>();
|
||||
error!("Provided {kind} was not mapped to any entity!");
|
||||
}
|
||||
|
||||
fn remove<ID: Hash + Eq>(
|
||||
mapping: &mut HashMap<ID, Entity>,
|
||||
id: Option<ID>,
|
||||
expected: Option<Entity>,
|
||||
) -> Option<Entity> {
|
||||
if let Some(id) = id {
|
||||
if let Some(e) = mapping.remove(&id) {
|
||||
if expected.map_or(false, |expected| e != expected) {
|
||||
unexpected_entity::<ID>();
|
||||
}
|
||||
Some(e)
|
||||
} else {
|
||||
not_present::<ID>();
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let maybe_entity = remove(&mut self.uid_mapping, uid, expected_entity);
|
||||
let expected_entity = expected_entity.or(maybe_entity);
|
||||
remove(&mut self.character_to_ecs, cid, expected_entity);
|
||||
remove(&mut self.rtsim_to_ecs, rid, expected_entity);
|
||||
maybe_entity
|
||||
}
|
||||
|
||||
/// Only used on the client (server solely uses `Self::allocate` to
|
||||
/// allocate and add Uid mappings and `Self::remap` to move the `Uid` to
|
||||
/// a different entity).
|
||||
pub fn add_entity(&mut self, uid: Uid, entity: Entity) {
|
||||
Self::insert(&mut self.uid_mapping, uid, entity);
|
||||
}
|
||||
|
||||
/// Only used on the server.
|
||||
pub fn add_character(&mut self, cid: CharacterId, entity: Entity) {
|
||||
Self::insert(&mut self.character_to_ecs, cid, entity);
|
||||
}
|
||||
|
||||
/// Only used on the server.
|
||||
pub fn add_rtsim(&mut self, rid: RtSimEntity, entity: Entity) {
|
||||
Self::insert(&mut self.rtsim_to_ecs, rid, entity);
|
||||
}
|
||||
|
||||
/// Allocates a new `Uid` and links it to the provided entity.
|
||||
///
|
||||
/// Only used on the server.
|
||||
pub fn allocate(&mut self, entity: Entity) -> Uid {
|
||||
let uid = self.uid_allocator.allocate();
|
||||
self.uid_mapping.insert(uid, entity);
|
||||
uid
|
||||
}
|
||||
|
||||
/// Links an existing `Uid` to a new entity.
|
||||
///
|
||||
/// Only used on the server.
|
||||
///
|
||||
/// Used for `handle_exit_ingame` which moves the same `Uid` to a new
|
||||
/// entity.
|
||||
pub fn remap_entity(&mut self, uid: Uid, new_entity: Entity) {
|
||||
if self.uid_mapping.insert(uid, new_entity).is_none() {
|
||||
error!("Uid {uid:?} remaped but there was no existing entry for it!");
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn already_present<ID>() {
|
||||
let kind = core::any::type_name::<ID>();
|
||||
error!("Provided {kind} was already mapped to an entity!!!");
|
||||
}
|
||||
|
||||
fn insert<ID: Hash + Eq>(mapping: &mut HashMap<ID, Entity>, new_id: ID, entity: Entity) {
|
||||
if let Some(_previous_entity) = mapping.insert(new_id, entity) {
|
||||
Self::already_present::<ID>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UidAllocator {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use wasmer::{Function, Memory, Value};
|
||||
|
||||
use common::{
|
||||
comp::{Health, Player},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
|
||||
use super::errors::{MemoryAllocationError, PluginModuleError};
|
||||
@ -18,7 +18,7 @@ pub struct EcsWorld<'a, 'b> {
|
||||
pub health: EcsComponentAccess<'a, 'b, Health>,
|
||||
pub uid: EcsComponentAccess<'a, 'b, Uid>,
|
||||
pub player: EcsComponentAccess<'a, 'b, Player>,
|
||||
pub uid_allocator: &'b Read<'a, UidAllocator>,
|
||||
pub id_maps: &'b Read<'a, IdMaps>,
|
||||
}
|
||||
|
||||
pub enum EcsComponentAccess<'a, 'b, T: Component> {
|
||||
|
@ -5,7 +5,6 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use specs::saveload::MarkerAllocator;
|
||||
use wasmer::{imports, Cranelift, Function, Instance, Memory, Module, Store, Universal, Value};
|
||||
|
||||
use super::{
|
||||
@ -239,9 +238,12 @@ fn retrieve_action(
|
||||
EcsAccessError::EcsPointerNotAvailable,
|
||||
))?
|
||||
};
|
||||
let player = world.uid_allocator.retrieve_entity_internal(e.0).ok_or(
|
||||
RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)),
|
||||
)?;
|
||||
let player = world
|
||||
.id_maps
|
||||
.uid_entity(e)
|
||||
.ok_or(RetrieveError::EcsAccessError(
|
||||
EcsAccessError::EcsEntityNotFound(e),
|
||||
))?;
|
||||
|
||||
Ok(RetrieveResult::GetPlayerName(
|
||||
world
|
||||
@ -264,9 +266,12 @@ fn retrieve_action(
|
||||
EcsAccessError::EcsPointerNotAvailable,
|
||||
))?
|
||||
};
|
||||
let player = world.uid_allocator.retrieve_entity_internal(e.0).ok_or(
|
||||
RetrieveError::EcsAccessError(EcsAccessError::EcsEntityNotFound(e)),
|
||||
)?;
|
||||
let player = world
|
||||
.id_maps
|
||||
.uid_entity(e)
|
||||
.ok_or(RetrieveError::EcsAccessError(
|
||||
EcsAccessError::EcsEntityNotFound(e),
|
||||
))?;
|
||||
Ok(RetrieveResult::GetEntityHealth(
|
||||
world
|
||||
.health
|
||||
|
@ -4,7 +4,7 @@ use crate::plugin::memory_manager::EcsWorld;
|
||||
use crate::plugin::PluginMgr;
|
||||
use crate::{BuildArea, NoDurabilityArea};
|
||||
#[cfg(feature = "plugins")]
|
||||
use common::uid::UidAllocator;
|
||||
use common::uid::IdMaps;
|
||||
use common::{
|
||||
calendar::Calendar,
|
||||
comp,
|
||||
@ -309,7 +309,7 @@ impl State {
|
||||
entities: &ecs.entities(),
|
||||
health: ecs.read_component().into(),
|
||||
uid: ecs.read_component().into(),
|
||||
uid_allocator: &ecs.read_resource::<UidAllocator>().into(),
|
||||
id_maps: &ecs.read_resource::<IdMaps>().into(),
|
||||
player: ecs.read_component().into(),
|
||||
};
|
||||
if let Err(e) = plugin_mgr
|
||||
|
@ -8,12 +8,11 @@ use common::{
|
||||
},
|
||||
event::{Emitter, EventBus, ServerEvent},
|
||||
resources::Time,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
||||
ReadStorage, SystemData, World,
|
||||
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadStorage, SystemData, World,
|
||||
};
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -22,7 +21,7 @@ pub struct ReadData<'a> {
|
||||
players: ReadStorage<'a, Player>,
|
||||
time: Read<'a, Time>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
char_states: ReadStorage<'a, CharacterState>,
|
||||
@ -96,8 +95,8 @@ impl<'a> System<'a> for Sys {
|
||||
// Ensure the entity is in the group we want to target
|
||||
let same_group = |uid: Uid| {
|
||||
read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(uid.into())
|
||||
.id_maps
|
||||
.uid_entity(uid)
|
||||
.and_then(|e| read_data.groups.get(e))
|
||||
.map_or(false, |owner_group| {
|
||||
Some(owner_group) == read_data.groups.get(target)
|
||||
@ -167,11 +166,7 @@ fn activate_aura(
|
||||
Alignment::Owned(uid) => Some(uid),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(|uid| {
|
||||
read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal((*uid).into())
|
||||
})
|
||||
.and_then(|uid| read_data.id_maps.uid_entity(*uid))
|
||||
.and_then(|owner| read_data.char_states.get(owner))
|
||||
.map_or(false, CharacterState::is_sitting))
|
||||
},
|
||||
@ -188,15 +183,13 @@ fn activate_aura(
|
||||
// when we will add this.
|
||||
let may_harm = || {
|
||||
let owner = match source {
|
||||
BuffSource::Character { by } => {
|
||||
read_data.uid_allocator.retrieve_entity_internal(by.into())
|
||||
},
|
||||
BuffSource::Character { by } => read_data.id_maps.uid_entity(by),
|
||||
_ => None,
|
||||
};
|
||||
combat::may_harm(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
&read_data.id_maps,
|
||||
owner,
|
||||
target,
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
outcome::Outcome,
|
||||
resources::{DeltaTime, Time},
|
||||
terrain::TerrainGrid,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
vol::ReadVol,
|
||||
GroupTarget,
|
||||
};
|
||||
@ -17,8 +17,8 @@ use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||
use rand::Rng;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
|
||||
ReadStorage, SystemData, World, WriteStorage,
|
||||
shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World,
|
||||
WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
@ -31,7 +31,7 @@ pub struct ReadData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
terrain: ReadExpect<'a, TerrainGrid>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
@ -95,9 +95,9 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
||||
|
||||
let beam_owner = beam_segment.owner.and_then(|uid| {
|
||||
read_data.uid_allocator.retrieve_entity_internal(uid.into())
|
||||
});
|
||||
let beam_owner = beam_segment
|
||||
.owner
|
||||
.and_then(|uid| read_data.id_maps.uid_entity(uid));
|
||||
|
||||
// Note: rayon makes it difficult to hold onto a thread-local RNG, if grabbing
|
||||
// this becomes a bottleneck we can look into alternatives.
|
||||
@ -243,7 +243,7 @@ impl<'a> System<'a> for Sys {
|
||||
let may_harm = combat::may_harm(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
&read_data.id_maps,
|
||||
beam_owner,
|
||||
target,
|
||||
);
|
||||
|
@ -15,15 +15,15 @@ use common::{
|
||||
event::{Emitter, EventBus, ServerEvent},
|
||||
resources::{DeltaTime, Secs, Time},
|
||||
terrain::SpriteKind,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
Damage, DamageSource,
|
||||
};
|
||||
use common_base::prof_span;
|
||||
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity, Join, ParJoin, Read,
|
||||
ReadExpect, ReadStorage, SystemData, World, WriteStorage,
|
||||
shred::ResourceId, Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
};
|
||||
|
||||
#[derive(SystemData)]
|
||||
@ -36,7 +36,7 @@ pub struct ReadData<'a> {
|
||||
energies: ReadStorage<'a, Energy>,
|
||||
physics_states: ReadStorage<'a, PhysicsState>,
|
||||
groups: ReadStorage<'a, Group>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
time: Read<'a, Time>,
|
||||
msm: ReadExpect<'a, MaterialStatManifest>,
|
||||
buffs: ReadStorage<'a, Buffs>,
|
||||
@ -275,10 +275,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
})
|
||||
.for_each(|(buff_id, buff, uid, aura_key)| {
|
||||
let replace = if let Some(aura_entity) = read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal((*uid).into())
|
||||
{
|
||||
let replace = if let Some(aura_entity) = read_data.id_maps.uid_entity(*uid) {
|
||||
if let Some(aura) = read_data
|
||||
.auras
|
||||
.get(aura_entity)
|
||||
@ -504,12 +501,9 @@ fn execute_effect(
|
||||
ModifierKind::Fractional => health.maximum() * amount,
|
||||
};
|
||||
let damage_contributor = by.and_then(|uid| {
|
||||
read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(uid.0)
|
||||
.map(|entity| {
|
||||
DamageContributor::new(uid, read_data.groups.get(entity).cloned())
|
||||
})
|
||||
read_data.id_maps.uid_entity(uid).map(|entity| {
|
||||
DamageContributor::new(uid, read_data.groups.get(entity).cloned())
|
||||
})
|
||||
});
|
||||
server_emitter.emit(ServerEvent::HealthChange {
|
||||
entity,
|
||||
|
@ -6,20 +6,19 @@ use common::{
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
terrain::TerrainGrid,
|
||||
uid::UidAllocator,
|
||||
uid::IdMaps,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
shred::ResourceId,
|
||||
Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World, WriteStorage,
|
||||
shred::ResourceId, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World,
|
||||
WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadData<'a> {
|
||||
entities: Entities<'a>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
terrain_grid: ReadExpect<'a, TerrainGrid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
@ -49,17 +48,14 @@ impl<'a> System<'a> for Sys {
|
||||
for event in controller.events.drain(..) {
|
||||
match event {
|
||||
ControlEvent::Mount(mountee_uid) => {
|
||||
if let Some(mountee_entity) = read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(mountee_uid.id())
|
||||
{
|
||||
if let Some(mountee_entity) = read_data.id_maps.uid_entity(mountee_uid) {
|
||||
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
|
||||
}
|
||||
},
|
||||
ControlEvent::MountVolume(volume) => {
|
||||
if let Some(block) = volume.get_block(
|
||||
&read_data.terrain_grid,
|
||||
&read_data.uid_allocator,
|
||||
&read_data.id_maps,
|
||||
&read_data.colliders,
|
||||
) {
|
||||
if block.is_mountable() {
|
||||
@ -81,10 +77,7 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::DisableLantern(entity))
|
||||
},
|
||||
ControlEvent::Interact(npc_uid, subject) => {
|
||||
if let Some(npc_entity) = read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(npc_uid.id())
|
||||
{
|
||||
if let Some(npc_entity) = read_data.id_maps.uid_entity(npc_uid) {
|
||||
server_emitter
|
||||
.emit(ServerEvent::NpcInteract(entity, npc_entity, subject));
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use common::{
|
||||
outcome::Outcome,
|
||||
resources::Time,
|
||||
terrain::TerrainGrid,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
util::{find_dist::Cylinder, Dir},
|
||||
vol::ReadVol,
|
||||
GroupTarget,
|
||||
@ -27,7 +27,7 @@ use vek::*;
|
||||
pub struct ReadData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
terrain: ReadExpect<'a, TerrainGrid>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
entities: Entities<'a>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
@ -213,7 +213,7 @@ impl<'a> System<'a> for Sys {
|
||||
let may_harm = combat::may_harm(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
&read_data.id_maps,
|
||||
Some(attacker),
|
||||
target,
|
||||
);
|
||||
|
@ -3,13 +3,10 @@ use common::{
|
||||
link::Is,
|
||||
mounting::{Mount, VolumeRider},
|
||||
terrain::TerrainGrid,
|
||||
uid::UidAllocator,
|
||||
uid::IdMaps,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage,
|
||||
};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage};
|
||||
use tracing::error;
|
||||
use vek::*;
|
||||
|
||||
@ -18,7 +15,7 @@ use vek::*;
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
Entities<'a>,
|
||||
WriteStorage<'a, Controller>,
|
||||
@ -39,7 +36,7 @@ impl<'a> System<'a> for Sys {
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(
|
||||
uid_allocator,
|
||||
id_maps,
|
||||
terrain,
|
||||
entities,
|
||||
mut controllers,
|
||||
@ -56,8 +53,8 @@ impl<'a> System<'a> for Sys {
|
||||
// For each mount...
|
||||
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
|
||||
// ...find the rider...
|
||||
let Some((inputs_and_actions, rider)) = uid_allocator
|
||||
.retrieve_entity_internal(is_mount.rider.id())
|
||||
let Some((inputs_and_actions, rider)) = id_maps
|
||||
.uid_entity(is_mount.rider)
|
||||
.and_then(|rider| {
|
||||
controllers
|
||||
.get_mut(rider)
|
||||
@ -105,7 +102,7 @@ impl<'a> System<'a> for Sys {
|
||||
for (entity, is_volume_rider) in (&entities, &is_volume_riders).join() {
|
||||
if let Some((mut mat, volume_ori, _)) = is_volume_rider.pos.get_block_and_transform(
|
||||
&terrain,
|
||||
&uid_allocator,
|
||||
&id_maps,
|
||||
|e| positions.get(e).copied().zip(orientations.get(e).copied()),
|
||||
&colliders,
|
||||
) {
|
||||
@ -140,10 +137,7 @@ impl<'a> System<'a> for Sys {
|
||||
let v = match is_volume_rider.pos.kind {
|
||||
common::mounting::Volume::Terrain => Vec3::zero(),
|
||||
common::mounting::Volume::Entity(uid) => {
|
||||
if let Some(v) = uid_allocator
|
||||
.retrieve_entity_internal(uid.into())
|
||||
.and_then(|e| velocities.get(e))
|
||||
{
|
||||
if let Some(v) = id_maps.uid_entity(uid).and_then(|e| velocities.get(e)) {
|
||||
v.0
|
||||
} else {
|
||||
Vec3::zero()
|
||||
@ -174,9 +168,8 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some((actions, inputs)) = inputs {
|
||||
match is_volume_rider.pos.kind {
|
||||
common::mounting::Volume::Entity(uid) => {
|
||||
if let Some(controller) = uid_allocator
|
||||
.retrieve_entity_internal(uid.into())
|
||||
.and_then(|e| controllers.get_mut(e))
|
||||
if let Some(controller) =
|
||||
id_maps.uid_entity(uid).and_then(|e| controllers.get_mut(e))
|
||||
{
|
||||
controller.inputs = inputs;
|
||||
controller.actions = actions;
|
||||
|
@ -8,7 +8,7 @@ use common::{
|
||||
event::{Emitter, EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
resources::{DeltaTime, Time},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
util::Dir,
|
||||
GroupTarget,
|
||||
};
|
||||
@ -17,8 +17,8 @@ use common::vol::ReadVol;
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use rand::Rng;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Entity as EcsEntity, Join, Read,
|
||||
ReadExpect, ReadStorage, SystemData, World, WriteStorage,
|
||||
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
|
||||
SystemData, World, WriteStorage,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
@ -31,7 +31,7 @@ pub struct ReadData<'a> {
|
||||
entities: Entities<'a>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
@ -85,7 +85,7 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
let projectile_owner = projectile
|
||||
.owner
|
||||
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
|
||||
.and_then(|uid| read_data.id_maps.uid_entity(uid));
|
||||
|
||||
if physics.on_surface().is_none() && rng.gen_bool(0.05) {
|
||||
server_emitter.emit(ServerEvent::Sound {
|
||||
@ -103,8 +103,8 @@ impl<'a> System<'a> for Sys {
|
||||
// if there is at least one touching entity
|
||||
.and_then(|e| read_data.groups.get(e))
|
||||
.map_or(false, |owner_group|
|
||||
Some(owner_group) == read_data.uid_allocator
|
||||
.retrieve_entity_internal(other.into())
|
||||
Some(owner_group) == read_data.id_maps
|
||||
.uid_entity(other)
|
||||
.and_then(|e| read_data.groups.get(e))
|
||||
);
|
||||
|
||||
@ -125,8 +125,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let projectile = &mut *projectile;
|
||||
|
||||
let entity_of =
|
||||
|uid: Uid| read_data.uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity_of = |uid: Uid| read_data.id_maps.uid_entity(uid);
|
||||
|
||||
// Don't hit if there is terrain between the projectile and where the entity was
|
||||
// supposed to be hit by it.
|
||||
@ -334,7 +333,7 @@ fn dispatch_hit(
|
||||
let may_harm = combat::may_harm(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
&read_data.id_maps,
|
||||
owner,
|
||||
target,
|
||||
);
|
||||
|
@ -8,15 +8,14 @@ use common::{
|
||||
event::{EventBus, ServerEvent},
|
||||
outcome::Outcome,
|
||||
resources::{DeltaTime, Time},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
util::Dir,
|
||||
GroupTarget,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use rand::Rng;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
@ -27,7 +26,7 @@ pub struct ReadData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
players: ReadStorage<'a, Player>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
id_maps: Read<'a, IdMaps>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
orientations: ReadStorage<'a, Ori>,
|
||||
@ -92,7 +91,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let shockwave_owner = shockwave
|
||||
.owner
|
||||
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
|
||||
.and_then(|uid| read_data.id_maps.uid_entity(uid));
|
||||
|
||||
if rng.gen_bool(0.05) {
|
||||
server_emitter.emit(ServerEvent::Sound {
|
||||
@ -229,7 +228,7 @@ impl<'a> System<'a> for Sys {
|
||||
let may_harm = combat::may_harm(
|
||||
&read_data.alignments,
|
||||
&read_data.players,
|
||||
&read_data.uid_allocator,
|
||||
&read_data.id_maps,
|
||||
shockwave_owner,
|
||||
target,
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ use common::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{saveload::Marker, Entity as EcsEntity};
|
||||
use specs::Entity as EcsEntity;
|
||||
use vek::*;
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
@ -397,7 +397,7 @@ impl<'a> AgentData<'a> {
|
||||
break 'activity;
|
||||
}
|
||||
} else if let Some(Alignment::Owned(owner_uid)) = self.alignment
|
||||
&& let Some(owner) = get_entity_by_id(owner_uid.id(), read_data)
|
||||
&& let Some(owner) = get_entity_by_id(*owner_uid, read_data)
|
||||
&& let Some(pos) = read_data.positions.get(owner)
|
||||
&& pos.0.distance_squared(self.pos.0) < MAX_MOUNT_RANGE.powi(2)
|
||||
&& rng.gen_bool(0.01)
|
||||
@ -455,7 +455,7 @@ impl<'a> AgentData<'a> {
|
||||
if let Some(patrol_origin) = agent.patrol_origin
|
||||
// Use owner as patrol origin otherwise
|
||||
.or_else(|| if let Some(Alignment::Owned(owner_uid)) = self.alignment
|
||||
&& let Some(owner) = get_entity_by_id(owner_uid.id(), read_data)
|
||||
&& let Some(owner) = get_entity_by_id(*owner_uid, read_data)
|
||||
&& let Some(pos) = read_data.positions.get(owner)
|
||||
{
|
||||
Some(pos.0)
|
||||
@ -1613,7 +1613,7 @@ impl<'a> AgentData<'a> {
|
||||
if let Some(Target { target, .. }) = agent.target {
|
||||
if let Some(tgt_health) = read_data.healths.get(target) {
|
||||
if let Some(by) = tgt_health.last_change.damage_by() {
|
||||
if let Some(attacker) = get_entity_by_id(by.uid().0, read_data) {
|
||||
if let Some(attacker) = get_entity_by_id(by.uid(), read_data) {
|
||||
if agent.target.is_none() {
|
||||
controller.push_utterance(UtteranceKind::Angry);
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ use common::{
|
||||
slot::EquipSlot,
|
||||
},
|
||||
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
|
||||
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, PresenceKind, Scale,
|
||||
SkillSet, Stance, Stats, Vel,
|
||||
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, Scale, SkillSet, Stance,
|
||||
Stats, Vel,
|
||||
},
|
||||
consts::GRAVITY,
|
||||
link::Is,
|
||||
@ -21,11 +21,11 @@ use common::{
|
||||
rtsim::{Actor, RtSimEntity},
|
||||
states::utils::{ForcedMovement, StageSection},
|
||||
terrain::TerrainGrid,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use specs::{
|
||||
shred::ResourceId, Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage,
|
||||
SystemData, World,
|
||||
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
|
||||
World,
|
||||
};
|
||||
|
||||
// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
|
||||
@ -284,7 +284,7 @@ impl SwordTactics {
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadData<'a> {
|
||||
pub entities: Entities<'a>,
|
||||
pub uid_allocator: Read<'a, UidAllocator>,
|
||||
pub id_maps: Read<'a, IdMaps>,
|
||||
pub dt: Read<'a, DeltaTime>,
|
||||
pub time: Read<'a, Time>,
|
||||
pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
|
||||
@ -325,18 +325,9 @@ pub struct ReadData<'a> {
|
||||
|
||||
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),
|
||||
Actor::Character(character_id) => self.id_maps.character_entity(character_id),
|
||||
Actor::Npc(npc_id) => self.id_maps.rtsim_entity(RtSimEntity(npc_id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,13 @@ use common::{
|
||||
},
|
||||
consts::GRAVITY,
|
||||
terrain::Block,
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use core::f32::consts::PI;
|
||||
use rand::Rng;
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entity as EcsEntity,
|
||||
};
|
||||
use specs::Entity as EcsEntity;
|
||||
use vek::*;
|
||||
|
||||
pub fn is_dead_or_invulnerable(entity: EcsEntity, read_data: &ReadData) -> bool {
|
||||
@ -43,8 +41,8 @@ pub fn try_owner_alignment<'a>(
|
||||
alignment: Option<&'a Alignment>,
|
||||
read_data: &'a ReadData,
|
||||
) -> Option<&'a Alignment> {
|
||||
if let Some(Alignment::Owned(owner_uid)) = alignment {
|
||||
if let Some(owner) = get_entity_by_id(owner_uid.id(), read_data) {
|
||||
if let Some(&Alignment::Owned(owner_uid)) = alignment {
|
||||
if let Some(owner) = get_entity_by_id(owner_uid, read_data) {
|
||||
return read_data.alignments.get(owner);
|
||||
}
|
||||
}
|
||||
@ -66,8 +64,8 @@ pub fn aim_projectile(speed: f32, pos: Vec3<f32>, tgt: Vec3<f32>) -> Option<Dir>
|
||||
Dir::from_unnormalized(to_tgt)
|
||||
}
|
||||
|
||||
pub fn get_entity_by_id(id: u64, read_data: &ReadData) -> Option<EcsEntity> {
|
||||
read_data.uid_allocator.retrieve_entity_internal(id)
|
||||
pub fn get_entity_by_id(uid: Uid, read_data: &ReadData) -> Option<EcsEntity> {
|
||||
read_data.id_maps.uid_entity(uid)
|
||||
}
|
||||
|
||||
/// Calculates whether the agent should continue chase or let the target escape.
|
||||
@ -198,7 +196,7 @@ pub fn get_attacker(entity: EcsEntity, read_data: &ReadData) -> Option<EcsEntity
|
||||
.get(entity)
|
||||
.filter(|health| health.last_change.amount < 0.0)
|
||||
.and_then(|health| health.last_change.damage_by())
|
||||
.and_then(|damage_contributor| get_entity_by_id(damage_contributor.uid().0, read_data))
|
||||
.and_then(|damage_contributor| get_entity_by_id(damage_contributor.uid(), read_data))
|
||||
}
|
||||
|
||||
impl<'a> AgentData<'a> {
|
||||
|
@ -47,7 +47,7 @@ use common::{
|
||||
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
||||
rtsim::{Actor, Role},
|
||||
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
vol::ReadVol,
|
||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||
};
|
||||
@ -60,9 +60,7 @@ use core::{cmp::Ordering, convert::TryFrom};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use humantime::Duration as HumanDuration;
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
|
||||
};
|
||||
use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
use std::{fmt::Write, ops::DerefMut, str::FromStr, sync::Arc};
|
||||
use vek::*;
|
||||
use wiring::{Circuit, Wire, WireNode, WiringAction, WiringActionEffect, WiringElement};
|
||||
@ -247,8 +245,8 @@ fn position_mut<T>(
|
||||
server
|
||||
.state
|
||||
.ecs()
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(is_rider.mount.into())
|
||||
.read_resource::<IdMaps>()
|
||||
.uid_entity(is_rider.mount)
|
||||
})
|
||||
.map(Ok)
|
||||
.or_else(|| {
|
||||
@ -264,8 +262,8 @@ fn position_mut<T>(
|
||||
common::mounting::Volume::Entity(uid) => Ok(server
|
||||
.state
|
||||
.ecs()
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(uid.into())?),
|
||||
.read_resource::<IdMaps>()
|
||||
.uid_entity(uid)?),
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2279,7 +2277,7 @@ fn handle_kill_npcs(
|
||||
.filter_map(|(entity, _health, (), alignment, pos)| {
|
||||
let should_kill = kill_pets
|
||||
|| if let Some(Alignment::Owned(owned)) = alignment {
|
||||
ecs.entity_from_uid(owned.0)
|
||||
ecs.entity_from_uid(*owned)
|
||||
.map_or(true, |owner| !players.contains(owner))
|
||||
} else {
|
||||
true
|
||||
@ -2562,6 +2560,7 @@ fn handle_light(
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
// TODO: I don't think we intend to add this component to non-client entities?
|
||||
.with(comp::ForceUpdate::forced())
|
||||
.with(light_emitter);
|
||||
if let Some(light_offset) = light_offset_opt {
|
||||
|
@ -18,7 +18,7 @@ use common::{
|
||||
outcome::Outcome,
|
||||
resources::{Secs, Time},
|
||||
rtsim::RtSimVehicle,
|
||||
uid::Uid,
|
||||
uid::{IdMaps, Uid},
|
||||
util::Dir,
|
||||
vol::IntoFullVolIterator,
|
||||
ViewDistances,
|
||||
@ -50,7 +50,8 @@ pub fn handle_initialize_character(
|
||||
}
|
||||
} else {
|
||||
// A character delete or update was somehow initiated after the login commenced,
|
||||
// so disconnect the client without saving any data and abort the login process.
|
||||
// so kick the client out of "ingame" without saving any data and abort
|
||||
// the character loading process.
|
||||
handle_exit_ingame(server, entity, true);
|
||||
}
|
||||
}
|
||||
@ -83,12 +84,19 @@ pub fn handle_loaded_character_data(
|
||||
))),
|
||||
);
|
||||
}
|
||||
server
|
||||
|
||||
let result_msg = if let Err(err) = server
|
||||
.state
|
||||
.update_character_data(entity, loaded_components);
|
||||
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
|
||||
// We notify the client with the metadata result from the operation.
|
||||
server.notify_client(entity, ServerGeneral::CharacterDataLoadResult(Ok(metadata)));
|
||||
.update_character_data(entity, loaded_components)
|
||||
{
|
||||
handle_exit_ingame(server, entity, false); // remove client from in-game state
|
||||
ServerGeneral::CharacterDataLoadResult(Err(err))
|
||||
} else {
|
||||
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
|
||||
// We notify the client with the metadata result from the operation.
|
||||
ServerGeneral::CharacterDataLoadResult(Ok(metadata))
|
||||
};
|
||||
server.notify_client(entity, result_msg);
|
||||
}
|
||||
|
||||
pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) -> EcsEntity {
|
||||
@ -132,6 +140,7 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
||||
entity
|
||||
};
|
||||
|
||||
// Rtsim entity added to IdMaps below.
|
||||
let entity = if let Some(rtsim_entity) = npc.rtsim_entity {
|
||||
entity.with(rtsim_entity).with(RepositionOnChunkLoad {
|
||||
needs_ground: false,
|
||||
@ -148,13 +157,21 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
||||
|
||||
let new_entity = entity.build();
|
||||
|
||||
if let Some(rtsim_entity) = npc.rtsim_entity {
|
||||
server
|
||||
.state()
|
||||
.ecs()
|
||||
.write_resource::<IdMaps>()
|
||||
.add_rtsim(rtsim_entity, new_entity);
|
||||
}
|
||||
|
||||
// Add to group system if a pet
|
||||
if let comp::Alignment::Owned(owner_uid) = npc.alignment {
|
||||
let state = server.state();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
|
||||
if let Some(owner) = state.ecs().entity_from_uid(owner_uid.into()) {
|
||||
if let Some(owner) = state.ecs().entity_from_uid(owner_uid) {
|
||||
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
|
||||
group_manager.new_pet(
|
||||
new_entity,
|
||||
|
@ -32,7 +32,7 @@ use common::{
|
||||
states::utils::StageSection,
|
||||
terrain::{Block, BlockKind, TerrainGrid},
|
||||
trade::{TradeResult, Trades},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
Damage, DamageKind, DamageSource, Explosion, GroupTarget, RadiusEffect,
|
||||
@ -41,9 +41,7 @@ use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
||||
use common_state::{AreasContainer, BlockChange, NoDurabilityArea};
|
||||
use hashbrown::HashSet;
|
||||
use rand::Rng;
|
||||
use specs::{
|
||||
join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity, Entity, WorldExt,
|
||||
};
|
||||
use specs::{join::Join, Builder, Entity as EcsEntity, Entity, WorldExt};
|
||||
use std::{collections::HashMap, iter, sync::Arc, time::Duration};
|
||||
use tracing::{debug, error};
|
||||
use vek::{Vec2, Vec3};
|
||||
@ -143,7 +141,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
||||
|
||||
let get_attacker_name = |cause_of_death: KillType, by: Uid| -> KillSource {
|
||||
// Get attacker entity
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) {
|
||||
if let Some(char_entity) = state.ecs().entity_from_uid(by) {
|
||||
// Check if attacker is another player or entity with stats (npc)
|
||||
if state
|
||||
.ecs()
|
||||
@ -259,7 +257,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
||||
for (damage_contributor, damage) in entity_health.damage_contributions() {
|
||||
match damage_contributor {
|
||||
DamageContributor::Solo(uid) => {
|
||||
if let Some(attacker) = state.ecs().entity_from_uid(uid.0) {
|
||||
if let Some(attacker) = state.ecs().entity_from_uid(*uid) {
|
||||
damage_contributors.insert(DamageContrib::Solo(attacker), (*damage, 0.0));
|
||||
} else {
|
||||
// An entity who was not in a group contributed damage but is now either
|
||||
@ -570,8 +568,8 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
||||
| DamageContributor::Group { entity_uid, .. })| {
|
||||
state
|
||||
.ecs()
|
||||
.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal((*entity_uid).into())
|
||||
.read_resource::<IdMaps>()
|
||||
.uid_entity(*entity_uid)
|
||||
},
|
||||
)
|
||||
.and_then(|killer| state.entity_as_actor(killer)),
|
||||
@ -744,10 +742,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
let settings = server.settings();
|
||||
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
|
||||
let time = ecs.read_resource::<Time>();
|
||||
let owner_entity = owner.and_then(|uid| {
|
||||
ecs.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(uid.into())
|
||||
});
|
||||
let owner_entity = owner.and_then(|uid| ecs.read_resource::<IdMaps>().uid_entity(uid));
|
||||
|
||||
let explosion_volume = 6.25 * explosion.radius;
|
||||
let mut emitter = server_eventbus.emitter();
|
||||
@ -952,7 +947,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
let combos = &ecs.read_storage::<comp::Combo>();
|
||||
let inventories = &ecs.read_storage::<Inventory>();
|
||||
let alignments = &ecs.read_storage::<Alignment>();
|
||||
let uid_allocator = &ecs.read_resource::<UidAllocator>();
|
||||
let id_maps = &ecs.read_resource::<IdMaps>();
|
||||
let players = &ecs.read_storage::<Player>();
|
||||
let buffs = &ecs.read_storage::<comp::Buffs>();
|
||||
let stats = &ecs.read_storage::<comp::Stats>();
|
||||
@ -1045,13 +1040,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
.and_then(|cs| cs.attack_immunities())
|
||||
.map_or(false, |i| i.explosions);
|
||||
// PvP check
|
||||
let may_harm = combat::may_harm(
|
||||
alignments,
|
||||
players,
|
||||
uid_allocator,
|
||||
owner_entity,
|
||||
entity_b,
|
||||
);
|
||||
let may_harm =
|
||||
combat::may_harm(alignments, players, id_maps, owner_entity, entity_b);
|
||||
let attack_options = combat::AttackOptions {
|
||||
target_dodging,
|
||||
may_harm,
|
||||
@ -1077,7 +1067,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
},
|
||||
RadiusEffect::Entity(mut effect) => {
|
||||
let alignments = &ecs.read_storage::<Alignment>();
|
||||
let uid_allocator = &ecs.read_resource::<UidAllocator>();
|
||||
let id_maps = &ecs.read_resource::<IdMaps>();
|
||||
let players = &ecs.read_storage::<Player>();
|
||||
for (entity_b, pos_b, body_b_maybe) in (
|
||||
&ecs.entities(),
|
||||
@ -1109,7 +1099,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
//
|
||||
// This can be changed later.
|
||||
let may_harm = || {
|
||||
combat::may_harm(alignments, players, uid_allocator, owner_entity, entity_b)
|
||||
combat::may_harm(alignments, players, id_maps, owner_entity, entity_b)
|
||||
|| owner_entity.map_or(true, |entity_a| entity_a == entity_b)
|
||||
};
|
||||
if strength > 0.0 {
|
||||
@ -1421,7 +1411,7 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r
|
||||
let mut positions = ecs.write_storage::<Pos>();
|
||||
|
||||
let target_pos = ecs
|
||||
.entity_from_uid(target.into())
|
||||
.entity_from_uid(target)
|
||||
.and_then(|e| positions.get(e))
|
||||
.copied();
|
||||
|
||||
@ -1493,7 +1483,7 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
|
||||
if let Some(trade) = trades.entity_trades.get(uid).copied() {
|
||||
trades
|
||||
.decline_trade(trade, *uid)
|
||||
.and_then(|uid| ecs.entity_from_uid(uid.0))
|
||||
.and_then(|uid| ecs.entity_from_uid(uid))
|
||||
.map(|entity_b| {
|
||||
// Notify both parties that the trade ended
|
||||
let clients = ecs.read_storage::<Client>();
|
||||
|
@ -148,7 +148,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let alignments = state.ecs().read_storage::<comp::Alignment>();
|
||||
|
||||
let target = match state.ecs().entity_from_uid(uid.into()) {
|
||||
let target = match state.ecs().entity_from_uid(uid) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
// Inform of failure
|
||||
@ -254,7 +254,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
|
||||
let state = server.state_mut();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let target = match state.ecs().entity_from_uid(uid.into()) {
|
||||
let target = match state.ecs().entity_from_uid(uid) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
// Inform of failure
|
||||
@ -305,7 +305,7 @@ pub fn handle_group(server: &mut Server, entity: Entity, manip: GroupManip) {
|
||||
));
|
||||
}
|
||||
// Tell the old leader that the transfer was succesful
|
||||
if let Some(client) = clients.get(target) {
|
||||
if let Some(client) = clients.get(entity) {
|
||||
client.send_fallible(ServerGeneral::server_msg(
|
||||
ChatType::Meta,
|
||||
"You are no longer the group leader.",
|
||||
|
@ -1,4 +1,4 @@
|
||||
use specs::{saveload::MarkerAllocator, world::WorldExt, Entity as EcsEntity, Join};
|
||||
use specs::{world::WorldExt, Entity as EcsEntity, Join};
|
||||
use vek::*;
|
||||
|
||||
use common::{
|
||||
@ -21,7 +21,7 @@ use common::{
|
||||
outcome::Outcome,
|
||||
rtsim::RtSimVehicle,
|
||||
terrain::{Block, SpriteKind},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use common_net::sync::WorldSyncExt;
|
||||
@ -177,11 +177,11 @@ pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: Vo
|
||||
}).is_ok();
|
||||
#[cfg(feature = "worldgen")]
|
||||
if _link_successful {
|
||||
let uid_allocator = state.ecs().read_resource::<UidAllocator>();
|
||||
if let Some(rider_entity) = uid_allocator.retrieve_entity_internal(rider.0)
|
||||
let uid_allocator = state.ecs().read_resource::<IdMaps>();
|
||||
if let Some(rider_entity) = uid_allocator.uid_entity(rider)
|
||||
&& let Some(rider_actor) = state.entity_as_actor(rider_entity)
|
||||
&& let Some(volume_pos) = volume_pos.try_map_entity(|uid| {
|
||||
let entity = uid_allocator.retrieve_entity_internal(uid.0)?;
|
||||
let entity = uid_allocator.uid_entity(uid)?;
|
||||
state.read_storage::<RtSimVehicle>().get(entity).map(|v| v.0)
|
||||
}) {
|
||||
state.ecs().write_resource::<RtSim>().hook_character_mount_volume(
|
||||
|
@ -120,16 +120,15 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
|
||||
match manip {
|
||||
comp::InventoryManip::Pickup(pickup_uid) => {
|
||||
let item_entity =
|
||||
if let Some(item_entity) = state.ecs().entity_from_uid(pickup_uid.into()) {
|
||||
item_entity
|
||||
} else {
|
||||
// Item entity could not be found - most likely because the entity
|
||||
// attempted to pick up the same item very quickly before its deletion of the
|
||||
// world from the first pickup attempt was processed.
|
||||
debug!("Failed to get entity for item Uid: {}", pickup_uid);
|
||||
return;
|
||||
};
|
||||
let item_entity = if let Some(item_entity) = state.ecs().entity_from_uid(pickup_uid) {
|
||||
item_entity
|
||||
} else {
|
||||
// Item entity could not be found - most likely because the entity
|
||||
// attempted to pick up the same item very quickly before its deletion of the
|
||||
// world from the first pickup attempt was processed.
|
||||
debug!("Failed to get entity for item Uid: {}", pickup_uid);
|
||||
return;
|
||||
};
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
|
||||
// FIXME: Raycast so we can't pick up items through walls.
|
||||
|
@ -30,7 +30,7 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
|
||||
let max_group_size = server.settings().max_player_group_size;
|
||||
let state = server.state_mut();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let invitee = match state.ecs().entity_from_uid(invitee_uid.into()) {
|
||||
let invitee = match state.ecs().entity_from_uid(invitee_uid) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
// Inform of failure
|
||||
@ -85,7 +85,7 @@ pub fn handle_invite(server: &mut Server, inviter: Entity, invitee_uid: Uid, kin
|
||||
if let Some(active_trade) = trades.entity_trades.get(&inviter_uid).copied() {
|
||||
trades
|
||||
.decline_trade(active_trade, inviter_uid)
|
||||
.and_then(|u| state.ecs().entity_from_uid(u.0))
|
||||
.and_then(|u| state.ecs().entity_from_uid(u))
|
||||
.map(|e| {
|
||||
if let Some(client) = clients.get(e) {
|
||||
client
|
||||
@ -309,7 +309,7 @@ fn handle_invite_answer(
|
||||
if let Some(active_trade) = trades.entity_trades.get(&invitee_uid).copied() {
|
||||
trades
|
||||
.decline_trade(active_trade, invitee_uid)
|
||||
.and_then(|u| state.ecs().entity_from_uid(u.0))
|
||||
.and_then(|u| state.ecs().entity_from_uid(u))
|
||||
.map(|e| {
|
||||
if let Some(client) = clients.get(e) {
|
||||
client
|
||||
|
@ -8,12 +8,12 @@ use common::{
|
||||
comp,
|
||||
comp::{group, pet::is_tameable, Presence, PresenceKind},
|
||||
resources::Time,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use common_base::span;
|
||||
use common_net::msg::{PlayerListUpdate, ServerGeneral};
|
||||
use common_state::State;
|
||||
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
|
||||
use tracing::{debug, error, trace, warn, Instrument};
|
||||
|
||||
pub fn handle_character_delete(
|
||||
@ -56,59 +56,55 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persisten
|
||||
entity
|
||||
};
|
||||
|
||||
// Create new entity with just `Client`, `Uid`, `Player`, and `...Stream`
|
||||
// Create new entity with just `Client`, `Uid`, `Player`, `Admin`, `Group`
|
||||
// components.
|
||||
//
|
||||
// Easier than checking and removing all other known components.
|
||||
//
|
||||
// Also, allows clients to not update their Uid based references to this
|
||||
// client (e.g. for this specific client's knowledge of its own Uid and for
|
||||
// groups since exiting in-game does not affect group membership)
|
||||
//
|
||||
// Note: If other `ServerEvent`s are referring to this entity they will be
|
||||
// disrupted.
|
||||
|
||||
// Since we remove `Uid` below, any trades won't be cancelled by
|
||||
// `delete_entity_recorded`. So we cancel the trade here. (maybe the trade
|
||||
// key could be switched from `Uid` to `Entity`)
|
||||
// Cancel trades here since we don't use `delete_entity_recorded` and we
|
||||
// remove `Uid` below.
|
||||
super::cancel_trades_for(state, entity);
|
||||
|
||||
let maybe_admin = state.ecs().write_storage::<comp::Admin>().remove(entity);
|
||||
let maybe_group = state
|
||||
.ecs()
|
||||
.write_storage::<group::Group>()
|
||||
.get(entity)
|
||||
.cloned();
|
||||
let maybe_group = state.read_component_copied::<group::Group>(entity);
|
||||
let maybe_admin = state.delete_component::<comp::Admin>(entity);
|
||||
// Not sure if we still need to actually remove the Uid or if the group
|
||||
// logic below relies on this...
|
||||
let maybe_uid = state.delete_component::<Uid>(entity);
|
||||
|
||||
if let Some((client, uid, player)) = (|| {
|
||||
let ecs = state.ecs();
|
||||
Some((
|
||||
ecs.write_storage::<Client>().remove(entity)?,
|
||||
ecs.write_storage::<Uid>().remove(entity)?,
|
||||
ecs.write_storage::<comp::Player>().remove(entity)?,
|
||||
))
|
||||
})() {
|
||||
if let Some(client) = state.delete_component::<Client>(entity)
|
||||
&& let Some(uid) = maybe_uid
|
||||
&& let Some(player) = state.delete_component::<comp::Player>(entity)
|
||||
{
|
||||
// Tell client its request was successful
|
||||
client.send_fallible(ServerGeneral::ExitInGameSuccess);
|
||||
|
||||
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
|
||||
let new_entity = state
|
||||
.ecs_mut()
|
||||
.create_entity()
|
||||
.with(client)
|
||||
.with(player)
|
||||
// Preserve group component if present
|
||||
.maybe_with(maybe_group)
|
||||
// Preserve admin component if present
|
||||
.maybe_with(maybe_admin)
|
||||
.with(uid)
|
||||
.build();
|
||||
|
||||
// Preserve group component if present
|
||||
let entity_builder = match maybe_group {
|
||||
Some(group) => entity_builder.with(group),
|
||||
None => entity_builder,
|
||||
};
|
||||
// Ensure IdMaps maps this uid to the new entity.
|
||||
state.mut_resource::<IdMaps>().remap_entity(uid, new_entity);
|
||||
|
||||
// Preserve admin component if present
|
||||
let entity_builder = match maybe_admin {
|
||||
Some(admin) => entity_builder.with(admin),
|
||||
None => entity_builder,
|
||||
};
|
||||
|
||||
// Ensure UidAllocator maps this uid to the new entity
|
||||
let uid = entity_builder
|
||||
.world
|
||||
.write_resource::<UidAllocator>()
|
||||
.allocate(entity_builder.entity, Some(uid.into()));
|
||||
let new_entity = entity_builder.with(uid).build();
|
||||
let ecs = state.ecs();
|
||||
// Note, we use `delete_entity_common` directly to avoid `delete_entity_recorded` from
|
||||
// making any changes to the group.
|
||||
if let Some(group) = maybe_group {
|
||||
let mut group_manager = state.ecs().write_resource::<group::GroupManager>();
|
||||
let mut group_manager = ecs.write_resource::<group::GroupManager>();
|
||||
if group_manager
|
||||
.group_info(group)
|
||||
.map(|info| info.leader == entity)
|
||||
@ -116,21 +112,38 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity, skip_persisten
|
||||
{
|
||||
group_manager.assign_leader(
|
||||
new_entity,
|
||||
&state.ecs().read_storage(),
|
||||
&state.ecs().entities(),
|
||||
&state.ecs().read_storage(),
|
||||
&state.ecs().read_storage(),
|
||||
&ecs.read_storage(),
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage(),
|
||||
&ecs.read_storage(),
|
||||
// Nothing actually changing since Uid is transferred
|
||||
|_, _| {},
|
||||
);
|
||||
}
|
||||
}
|
||||
// delete_entity_recorded` is not used so we don't need to worry aobut
|
||||
// group restructuring when deleting this entity.
|
||||
} else {
|
||||
error!("handle_exit_ingame called with entity that is missing expected components");
|
||||
}
|
||||
// Erase group component to avoid group restructure when deleting the entity
|
||||
state.ecs().write_storage::<group::Group>().remove(entity);
|
||||
|
||||
let maybe_character = state
|
||||
.read_storage::<Presence>()
|
||||
.get(entity)
|
||||
.and_then(|p| p.kind.character_id());
|
||||
let maybe_rtsim = state.read_component_copied::<common::rtsim::RtSimEntity>(entity);
|
||||
state.mut_resource::<IdMaps>().remove_entity(
|
||||
Some(entity),
|
||||
None, // Uid re-mapped, we don't want to remove the mapping
|
||||
maybe_character,
|
||||
maybe_rtsim,
|
||||
);
|
||||
|
||||
// We don't want to use delete_entity_recorded since we are transfering the
|
||||
// Uid to a new entity (and e.g. don't want it to be unmapped).
|
||||
//
|
||||
// Delete old entity
|
||||
if let Err(e) = state.delete_entity_recorded(entity) {
|
||||
if let Err(e) = crate::state_ext::delete_entity_common(state, entity, maybe_uid) {
|
||||
error!(
|
||||
?e,
|
||||
?entity,
|
||||
@ -260,6 +273,12 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
|
||||
state.ecs().fetch_mut::<BattleModeBuffer>(),
|
||||
) {
|
||||
match presence.kind {
|
||||
PresenceKind::LoadingCharacter(_char_id) => {
|
||||
error!(
|
||||
"Unexpected state when persist_entity is called! Some of the components \
|
||||
required above should only be present after a character is loaded!"
|
||||
);
|
||||
},
|
||||
PresenceKind::Character(char_id) => {
|
||||
let waypoint = state
|
||||
.ecs()
|
||||
@ -331,12 +350,12 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
||||
let mut delete_entity = None;
|
||||
|
||||
if let (Some(possessor), Some(possessee)) = (
|
||||
state.ecs().entity_from_uid(possessor_uid.into()),
|
||||
state.ecs().entity_from_uid(possessee_uid.into()),
|
||||
state.ecs().entity_from_uid(possessor_uid),
|
||||
state.ecs().entity_from_uid(possessee_uid),
|
||||
) {
|
||||
// In this section we check various invariants and can return early if any of
|
||||
// them are not met.
|
||||
{
|
||||
let new_presence = {
|
||||
let ecs = state.ecs();
|
||||
// Check that entities still exist
|
||||
if !possessor.gen().is_alive()
|
||||
@ -353,6 +372,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
||||
|
||||
let clients = ecs.read_storage::<Client>();
|
||||
let players = ecs.read_storage::<comp::Player>();
|
||||
let presences = ecs.read_storage::<comp::Presence>();
|
||||
|
||||
if clients.contains(possessee) || players.contains(possessee) {
|
||||
error!("Can't possess other players!");
|
||||
@ -379,8 +399,32 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(presence) = presences.get(possessor) {
|
||||
delete_entity = match presence.kind {
|
||||
k @ (PresenceKind::LoadingCharacter(_) | PresenceKind::Spectator) => {
|
||||
error!(?k, "Unexpected presence kind for a possessor.");
|
||||
return;
|
||||
},
|
||||
PresenceKind::Possessor => None,
|
||||
// Since we call `persist_entity` below we will want to delete the entity (to
|
||||
// avoid item duplication).
|
||||
PresenceKind::Character(_) => Some(possessor),
|
||||
};
|
||||
|
||||
Some(Presence {
|
||||
terrain_view_distance: presence.terrain_view_distance,
|
||||
entity_view_distance: presence.entity_view_distance,
|
||||
// This kind (rather than copying Character presence) prevents persistence
|
||||
// from overwriting original character info with stuff from the new character.
|
||||
kind: PresenceKind::Possessor,
|
||||
lossy_terrain_compression: presence.lossy_terrain_compression,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
// No early returns allowed after this.
|
||||
}
|
||||
};
|
||||
|
||||
// Sync the player's character data to the database. This must be done before
|
||||
// moving any components from the entity.
|
||||
@ -428,31 +472,38 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
||||
}
|
||||
|
||||
let mut players = ecs.write_storage::<comp::Player>();
|
||||
let mut presence = ecs.write_storage::<Presence>();
|
||||
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
|
||||
let mut admins = ecs.write_storage::<comp::Admin>();
|
||||
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
|
||||
let mut force_updates = ecs.write_storage::<comp::ForceUpdate>();
|
||||
|
||||
transfer_component(&mut players, possessor, possessee, |x| x);
|
||||
transfer_component(&mut presence, possessor, possessee, |mut presence| {
|
||||
presence.kind = match presence.kind {
|
||||
PresenceKind::Spectator => PresenceKind::Spectator,
|
||||
// This prevents persistence from overwriting original character info with stuff
|
||||
// from the new character.
|
||||
PresenceKind::Character(_) => {
|
||||
delete_entity = Some(possessor);
|
||||
PresenceKind::Possessor
|
||||
},
|
||||
PresenceKind::Possessor => PresenceKind::Possessor,
|
||||
};
|
||||
|
||||
presence
|
||||
});
|
||||
transfer_component(&mut subscriptions, possessor, possessee, |x| x);
|
||||
transfer_component(&mut admins, possessor, possessee, |x| x);
|
||||
transfer_component(&mut waypoints, possessor, possessee, |x| x);
|
||||
let mut update_counter = 0;
|
||||
transfer_component(&mut force_updates, possessor, possessee, |mut x| {
|
||||
x.update();
|
||||
update_counter = x.counter();
|
||||
x
|
||||
});
|
||||
|
||||
// If a player is posessing, add possessee to playerlist as player and remove
|
||||
let mut presences = ecs.write_storage::<Presence>();
|
||||
// We leave Presence on the old entity for character IDs to be properly removed
|
||||
// from the ID mapping if deleting the previous entity.
|
||||
//
|
||||
// If the entity is not going to be deleted, we remove it so that the entity
|
||||
// doesn't keep an area loaded.
|
||||
if delete_entity.is_none() {
|
||||
presences.remove(possessor);
|
||||
}
|
||||
if let Some(p) = new_presence {
|
||||
presences
|
||||
.insert(possessee, p)
|
||||
.expect("Checked entity was alive!");
|
||||
}
|
||||
|
||||
// If a player is possessing, add possessee to playerlist as player and remove
|
||||
// old player.
|
||||
// Fetches from possessee entity here since we have transferred over the
|
||||
// `Player` component.
|
||||
@ -527,7 +578,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
||||
possessee,
|
||||
);
|
||||
if !comp_sync_package.is_empty() {
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package, 0)); // TODO: Check if this should be zero
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package, update_counter));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ pub(super) fn handle_process_trade_action(
|
||||
if let TradeAction::Decline = action {
|
||||
let to_notify = trades.decline_trade(trade_id, uid);
|
||||
to_notify
|
||||
.and_then(|u| server.state.ecs().entity_from_uid(u.0))
|
||||
.and_then(|u| server.state.ecs().entity_from_uid(u))
|
||||
.map(|e| {
|
||||
server.notify_client(e, ServerGeneral::FinishedTrade(TradeResult::Declined));
|
||||
notify_agent_simple(
|
||||
@ -78,7 +78,7 @@ pub(super) fn handle_process_trade_action(
|
||||
let ecs = server.state.ecs();
|
||||
let inventories = ecs.read_component::<Inventory>();
|
||||
let get_inventory = |uid: Uid| {
|
||||
if let Some(entity) = ecs.entity_from_uid(uid.0) {
|
||||
if let Some(entity) = ecs.entity_from_uid(uid) {
|
||||
inventories.get(entity)
|
||||
} else {
|
||||
None
|
||||
@ -92,7 +92,7 @@ pub(super) fn handle_process_trade_action(
|
||||
let result = commit_trade(server.state.ecs(), entry.get());
|
||||
entry.remove();
|
||||
for party in parties.iter() {
|
||||
if let Some(e) = server.state.ecs().entity_from_uid(party.0) {
|
||||
if let Some(e) = server.state.ecs().entity_from_uid(*party) {
|
||||
server.notify_client(e, ServerGeneral::FinishedTrade(result.clone()));
|
||||
notify_agent_simple(
|
||||
server.state.ecs().write_storage::<Agent>(),
|
||||
@ -110,7 +110,7 @@ pub(super) fn handle_process_trade_action(
|
||||
// sadly there is no map and collect on arrays
|
||||
for i in 0..2 {
|
||||
// parties.len()) {
|
||||
entities[i] = server.state.ecs().entity_from_uid(parties[i].0);
|
||||
entities[i] = server.state.ecs().entity_from_uid(parties[i]);
|
||||
if let Some(e) = entities[i] {
|
||||
inventories[i] = server
|
||||
.state
|
||||
@ -180,7 +180,7 @@ pub(crate) fn cancel_trades_for(state: &mut common_state::State, entity: EcsEnti
|
||||
};
|
||||
|
||||
let to_notify = trades.decline_trade(active_trade, uid);
|
||||
to_notify.and_then(|u| ecs.entity_from_uid(u.0)).map(|e| {
|
||||
to_notify.and_then(|u| ecs.entity_from_uid(u)).map(|e| {
|
||||
if let Some(c) = ecs.read_storage::<crate::Client>().get(e) {
|
||||
c.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
|
||||
}
|
||||
@ -198,7 +198,7 @@ pub(crate) fn cancel_trades_for(state: &mut common_state::State, entity: EcsEnti
|
||||
fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult {
|
||||
let mut entities = Vec::new();
|
||||
for party in trade.parties.iter() {
|
||||
match ecs.entity_from_uid(party.0) {
|
||||
match ecs.entity_from_uid(*party) {
|
||||
Some(entity) => entities.push(entity),
|
||||
None => return TradeResult::Declined,
|
||||
}
|
||||
@ -410,7 +410,7 @@ mod tests {
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::*;
|
||||
use common::{comp::slot::InvSlotId, uid::UidAllocator};
|
||||
use common::{comp::slot::InvSlotId, uid::IdMaps};
|
||||
|
||||
use specs::{Builder, World};
|
||||
|
||||
@ -423,7 +423,7 @@ mod tests {
|
||||
merchant_inv_size: usize,
|
||||
) -> (World, EcsEntity, EcsEntity) {
|
||||
let mut mockworld = World::new();
|
||||
mockworld.insert(UidAllocator::new());
|
||||
mockworld.insert(IdMaps::new());
|
||||
mockworld.insert(MaterialStatManifest::load().cloned());
|
||||
mockworld.insert(AbilityMap::load().cloned());
|
||||
mockworld.register::<Inventory>();
|
||||
@ -440,12 +440,11 @@ mod tests {
|
||||
.build();
|
||||
|
||||
{
|
||||
use specs::saveload::MarkerAllocator;
|
||||
let mut uids = mockworld.write_component::<Uid>();
|
||||
let mut uid_allocator = mockworld.write_resource::<UidAllocator>();
|
||||
uids.insert(player, uid_allocator.allocate(player, None))
|
||||
let mut id_maps = mockworld.write_resource::<IdMaps>();
|
||||
uids.insert(player, id_maps.allocate(player))
|
||||
.expect("inserting player uid failed");
|
||||
uids.insert(merchant, uid_allocator.allocate(merchant, None))
|
||||
uids.insert(merchant, id_maps.allocate(merchant))
|
||||
.expect("inserting merchant uid failed");
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ use crate::settings::Protocol;
|
||||
|
||||
#[cfg(feature = "plugins")]
|
||||
use {
|
||||
common::uid::UidAllocator,
|
||||
common::uid::IdMaps,
|
||||
common_state::plugin::{memory_manager::EcsWorld, PluginMgr},
|
||||
};
|
||||
|
||||
@ -954,6 +954,8 @@ impl Server {
|
||||
active_abilities,
|
||||
map_marker,
|
||||
);
|
||||
// TODO: Does this need to be a server event? E.g. we could
|
||||
// just handle it here.
|
||||
ServerEvent::UpdateCharacterData {
|
||||
entity: response.target_entity,
|
||||
components: character_data,
|
||||
@ -962,9 +964,8 @@ impl Server {
|
||||
},
|
||||
Err(error) => {
|
||||
// We failed to load data for the character from the DB. Notify
|
||||
// the client to push the
|
||||
// state back to character selection, with the error
|
||||
// to display
|
||||
// the client to push the state back to character selection,
|
||||
// with the error to display
|
||||
self.notify_client(
|
||||
response.target_entity,
|
||||
ServerGeneral::CharacterDataLoadResult(Err(
|
||||
@ -1218,7 +1219,7 @@ impl Server {
|
||||
entities: &self.state.ecs().entities(),
|
||||
health: self.state.ecs().read_component().into(),
|
||||
uid: self.state.ecs().read_component().into(),
|
||||
uid_allocator: &self.state.ecs().read_resource::<UidAllocator>().into(),
|
||||
id_maps: &self.state.ecs().read_resource::<IdMaps>().into(),
|
||||
player: self.state.ecs().read_component().into(),
|
||||
};
|
||||
let uid = if let Some(uid) = ecs_world.uid.get(entity).copied() {
|
||||
|
@ -27,7 +27,7 @@ use common::{
|
||||
resources::{Secs, Time, TimeOfDay},
|
||||
rtsim::{Actor, RtSimEntity},
|
||||
slowjob::SlowJobPool,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
LoadoutBuilder, ViewDistances,
|
||||
};
|
||||
use common_net::{
|
||||
@ -36,12 +36,9 @@ use common_net::{
|
||||
};
|
||||
use common_state::State;
|
||||
use rand::prelude::*;
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder,
|
||||
Join, WorldExt,
|
||||
};
|
||||
use specs::{Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt};
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::{trace, warn};
|
||||
use tracing::{error, trace, warn};
|
||||
use vek::*;
|
||||
|
||||
pub trait StateExt {
|
||||
@ -128,7 +125,11 @@ pub trait StateExt {
|
||||
fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances);
|
||||
/// Update the components associated with the entity's current character.
|
||||
/// Performed after loading component data from the database
|
||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
||||
fn update_character_data(
|
||||
&mut self,
|
||||
entity: EcsEntity,
|
||||
components: PersistedComponents,
|
||||
) -> Result<(), String>;
|
||||
/// Iterates over registered clients and send each `ServerMsg`
|
||||
fn validate_chat_msg(
|
||||
&self,
|
||||
@ -169,7 +170,7 @@ impl StateExt for State {
|
||||
let groups = self.ecs().read_storage::<Group>();
|
||||
|
||||
let damage_contributor = source.and_then(|uid| {
|
||||
self.ecs().entity_from_uid(uid.0).map(|attacker_entity| {
|
||||
self.ecs().entity_from_uid(uid).map(|attacker_entity| {
|
||||
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
|
||||
})
|
||||
});
|
||||
@ -214,7 +215,7 @@ impl StateExt for State {
|
||||
if !character_state.is_stunned() {
|
||||
let groups = self.ecs().read_storage::<Group>();
|
||||
let damage_contributor = source.and_then(|uid| {
|
||||
self.ecs().entity_from_uid(uid.0).map(|attacker_entity| {
|
||||
self.ecs().entity_from_uid(uid).map(|attacker_entity| {
|
||||
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
|
||||
})
|
||||
});
|
||||
@ -643,7 +644,7 @@ impl StateExt for State {
|
||||
|
||||
self.write_component_ignore_entity_dead(
|
||||
entity,
|
||||
Presence::new(view_distances, PresenceKind::Character(character_id)),
|
||||
Presence::new(view_distances, PresenceKind::LoadingCharacter(character_id)),
|
||||
);
|
||||
|
||||
// Tell the client its request was successful.
|
||||
@ -678,7 +679,12 @@ impl StateExt for State {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) {
|
||||
/// Returned error intended to be sent to the client.
|
||||
fn update_character_data(
|
||||
&mut self,
|
||||
entity: EcsEntity,
|
||||
components: PersistedComponents,
|
||||
) -> Result<(), String> {
|
||||
let PersistedComponents {
|
||||
body,
|
||||
stats,
|
||||
@ -691,6 +697,27 @@ impl StateExt for State {
|
||||
} = components;
|
||||
|
||||
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
|
||||
let result =
|
||||
if let Some(presence) = self.ecs().write_storage::<Presence>().get_mut(entity) {
|
||||
if let PresenceKind::LoadingCharacter(id) = presence.kind {
|
||||
presence.kind = PresenceKind::Character(id);
|
||||
self.ecs()
|
||||
.write_resource::<IdMaps>()
|
||||
.add_character(id, entity);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("PresenceKind is not LoadingCharacter")
|
||||
}
|
||||
} else {
|
||||
Err("Presence component missing")
|
||||
};
|
||||
if let Err(err) = result {
|
||||
let err = format!("Unexpected state when applying loaded character info: {err}");
|
||||
error!("{err}");
|
||||
// TODO: we could produce a `comp::Content` for this to allow localization.
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Notify clients of a player list update
|
||||
self.notify_players(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
|
||||
@ -733,6 +760,9 @@ impl StateExt for State {
|
||||
self.write_component_ignore_entity_dead(entity, waypoint);
|
||||
self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
|
||||
self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
|
||||
// TODO: We probably want to increment the existing force update counter since
|
||||
// it is added in initialized_character (to be robust we can also insert it if
|
||||
// it doesn't exist)
|
||||
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
|
||||
}
|
||||
|
||||
@ -771,7 +801,7 @@ impl StateExt for State {
|
||||
restore_pet(self.ecs(), pet_entity, entity, pet);
|
||||
}
|
||||
} else {
|
||||
warn!("Player has no pos, cannot load {} pets", pets.len());
|
||||
error!("Player has no pos, cannot load {} pets", pets.len());
|
||||
}
|
||||
|
||||
let presences = self.ecs().read_storage::<Presence>();
|
||||
@ -789,6 +819,9 @@ impl StateExt for State {
|
||||
player_info.last_battlemode_change = Some(*change);
|
||||
}
|
||||
} else {
|
||||
// TODO: this sounds related to handle_exit_ingame? Actually, sounds like
|
||||
// trying to place character specific info on the `Player` component. TODO
|
||||
// document component better.
|
||||
// FIXME:
|
||||
// ???
|
||||
//
|
||||
@ -804,6 +837,8 @@ impl StateExt for State {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_chat_msg(
|
||||
@ -862,16 +897,17 @@ impl StateExt for State {
|
||||
.clone()
|
||||
.map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
|
||||
|
||||
let id_maps = ecs.read_resource::<IdMaps>();
|
||||
let entity_from_uid = |uid| id_maps.uid_entity(uid);
|
||||
|
||||
if msg.chat_type.uid().map_or(true, |sender| {
|
||||
(*ecs.read_resource::<UidAllocator>())
|
||||
.retrieve_entity_internal(sender.0)
|
||||
.map_or(false, |e| {
|
||||
self.validate_chat_msg(
|
||||
e,
|
||||
&msg.chat_type,
|
||||
msg.content().as_plain().unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
entity_from_uid(sender).map_or(false, |e| {
|
||||
self.validate_chat_msg(
|
||||
e,
|
||||
&msg.chat_type,
|
||||
msg.content().as_plain().unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
}) {
|
||||
match &msg.chat_type {
|
||||
comp::ChatType::Offline(_)
|
||||
@ -911,8 +947,7 @@ impl StateExt for State {
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// Send kill message to the dead player's group
|
||||
let killed_entity =
|
||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||
let killed_entity = entity_from_uid(*uid);
|
||||
let groups = ecs.read_storage::<Group>();
|
||||
let killed_group = killed_entity.and_then(|e| groups.get(e));
|
||||
if let Some(g) = &killed_group {
|
||||
@ -947,8 +982,7 @@ impl StateExt for State {
|
||||
}
|
||||
},
|
||||
comp::ChatType::Say(uid) => {
|
||||
let entity_opt =
|
||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||
let entity_opt = entity_from_uid(*uid);
|
||||
|
||||
let positions = ecs.read_storage::<comp::Pos>();
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
@ -960,8 +994,7 @@ impl StateExt for State {
|
||||
}
|
||||
},
|
||||
comp::ChatType::Region(uid) => {
|
||||
let entity_opt =
|
||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||
let entity_opt = entity_from_uid(*uid);
|
||||
|
||||
let positions = ecs.read_storage::<comp::Pos>();
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
@ -973,8 +1006,7 @@ impl StateExt for State {
|
||||
}
|
||||
},
|
||||
comp::ChatType::Npc(uid) => {
|
||||
let entity_opt =
|
||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||
let entity_opt = entity_from_uid(*uid);
|
||||
|
||||
let positions = ecs.read_storage::<comp::Pos>();
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
@ -986,8 +1018,7 @@ impl StateExt for State {
|
||||
}
|
||||
},
|
||||
comp::ChatType::NpcSay(uid) => {
|
||||
let entity_opt =
|
||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||
let entity_opt = entity_from_uid(*uid);
|
||||
|
||||
let positions = ecs.read_storage::<comp::Pos>();
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
@ -1121,7 +1152,11 @@ impl StateExt for State {
|
||||
&mut self,
|
||||
entity: EcsEntity,
|
||||
) -> Result<(), specs::error::WrongGeneration> {
|
||||
// Remove entity from a group if they are in one
|
||||
// NOTE: both this and handle_exit_ingame call delete_entity_common, so cleanup
|
||||
// added here may need to be duplicated in handle_exit_ingame (depending
|
||||
// on its nature).
|
||||
|
||||
// Remove entity from a group if they are in one.
|
||||
{
|
||||
let clients = self.ecs().read_storage::<Client>();
|
||||
let uids = self.ecs().read_storage::<Uid>();
|
||||
@ -1152,37 +1187,23 @@ impl StateExt for State {
|
||||
// Cancel extant trades
|
||||
events::cancel_trades_for(self, entity);
|
||||
|
||||
let (maybe_uid, maybe_pos) = (
|
||||
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
||||
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
||||
// NOTE: We expect that these 3 components are never removed from an entity (nor
|
||||
// mutated) (at least not without updating the relevant mappings)!
|
||||
let maybe_uid = self.read_component_copied::<Uid>(entity);
|
||||
let maybe_character = self
|
||||
.read_storage::<Presence>()
|
||||
.get(entity)
|
||||
.and_then(|p| p.kind.character_id());
|
||||
let maybe_rtsim = self.read_component_copied::<RtSimEntity>(entity);
|
||||
|
||||
self.mut_resource::<IdMaps>().remove_entity(
|
||||
Some(entity),
|
||||
maybe_uid,
|
||||
maybe_character,
|
||||
maybe_rtsim,
|
||||
);
|
||||
|
||||
let res = self.ecs_mut().delete_entity(entity);
|
||||
if res.is_ok() {
|
||||
if let (Some(uid), Some(pos)) = (maybe_uid, maybe_pos) {
|
||||
if let Some(region_key) = self
|
||||
.ecs()
|
||||
.read_resource::<common::region::RegionMap>()
|
||||
.find_region(entity, pos.0)
|
||||
{
|
||||
self.ecs()
|
||||
.write_resource::<DeletedEntities>()
|
||||
.record_deleted_entity(uid, region_key);
|
||||
} else {
|
||||
// Don't panic if the entity wasn't found in a region maybe it was just created
|
||||
// and then deleted before the region manager had a chance to assign it a
|
||||
// region
|
||||
warn!(
|
||||
?uid,
|
||||
?pos,
|
||||
"Failed to find region containing entity during entity deletion, assuming \
|
||||
it wasn't sent to any clients and so deletion doesn't need to be \
|
||||
recorded for sync purposes"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
delete_entity_common(self, entity, maybe_uid)
|
||||
}
|
||||
|
||||
fn entity_as_actor(&self, entity: EcsEntity) -> Option<Actor> {
|
||||
@ -1213,3 +1234,50 @@ fn send_to_group(g: &Group, ecs: &specs::World, msg: &comp::ChatMsg) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This should only be called from `handle_exit_ingame` and
|
||||
/// `delete_entity_recorded`!!!!!!!
|
||||
pub(crate) fn delete_entity_common(
|
||||
state: &mut State,
|
||||
entity: EcsEntity,
|
||||
maybe_uid: Option<Uid>,
|
||||
) -> Result<(), specs::error::WrongGeneration> {
|
||||
if maybe_uid.is_none() {
|
||||
// For now we expect all entities have a Uid component.
|
||||
error!("Deleting entity without Uid component");
|
||||
}
|
||||
let maybe_pos = state.read_component_copied::<comp::Pos>(entity);
|
||||
|
||||
let res = state.ecs_mut().delete_entity(entity);
|
||||
if res.is_ok() {
|
||||
// Note: Adding the `Uid` to the deleted list when exiting "in-game" relies on
|
||||
// the client not being able to immediately re-enter the game in the
|
||||
// same tick (since we could then mix up the ordering of things and
|
||||
// tell other clients to forget the new entity).
|
||||
//
|
||||
// The client will ignore requests to delete its own entity that are triggered
|
||||
// by this.
|
||||
if let Some((uid, pos)) = maybe_uid.zip(maybe_pos) {
|
||||
let region_key = state
|
||||
.ecs()
|
||||
.read_resource::<common::region::RegionMap>()
|
||||
.find_region(entity, pos.0);
|
||||
if let Some(region_key) = region_key {
|
||||
state
|
||||
.mut_resource::<DeletedEntities>()
|
||||
.record_deleted_entity(uid, region_key);
|
||||
} else {
|
||||
// Don't panic if the entity wasn't found in a region, maybe it was just created
|
||||
// and then deleted before the region manager had a chance to assign it a region
|
||||
warn!(
|
||||
?uid,
|
||||
?pos,
|
||||
"Failed to find region containing entity during entity deletion, assuming it \
|
||||
wasn't sent to any clients and so deletion doesn't need to be recorded for \
|
||||
sync purposes"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ use common_base::prof_span;
|
||||
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
||||
use rand::thread_rng;
|
||||
use rayon::iter::ParallelIterator;
|
||||
use specs::{saveload::MarkerAllocator, Join, ParJoin, Read, WriteStorage};
|
||||
use specs::{Join, ParJoin, Read, WriteStorage};
|
||||
|
||||
/// This system will allow NPCs to modify their controller
|
||||
#[derive(Default)]
|
||||
@ -102,18 +102,12 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// The entity that is moving, if riding it's the mount, otherwise it's itself
|
||||
let moving_entity = is_rider
|
||||
.and_then(|is_rider| {
|
||||
read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(is_rider.mount.into())
|
||||
})
|
||||
.and_then(|is_rider| read_data.id_maps.uid_entity(is_rider.mount))
|
||||
.or_else(|| {
|
||||
is_volume_rider.and_then(|is_volume_rider| {
|
||||
match is_volume_rider.pos.kind {
|
||||
Volume::Terrain => None,
|
||||
Volume::Entity(uid) => {
|
||||
read_data.uid_allocator.retrieve_entity_internal(uid.into())
|
||||
},
|
||||
Volume::Entity(uid) => read_data.id_maps.uid_entity(uid),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -13,10 +13,7 @@ use common::{
|
||||
rtsim::{NpcAction, RtSimEntity},
|
||||
};
|
||||
use rand::{prelude::ThreadRng, thread_rng, Rng};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entity as EcsEntity,
|
||||
};
|
||||
use specs::Entity as EcsEntity;
|
||||
use vek::{Vec2, Vec3};
|
||||
|
||||
use self::interaction::{
|
||||
@ -247,11 +244,7 @@ fn target_if_attacked(bdata: &mut BehaviorData) -> bool {
|
||||
&& health.last_change.amount < 0.0 =>
|
||||
{
|
||||
if let Some(by) = health.last_change.damage_by() {
|
||||
if let Some(attacker) = bdata
|
||||
.read_data
|
||||
.uid_allocator
|
||||
.retrieve_entity_internal(by.uid().0)
|
||||
{
|
||||
if let Some(attacker) = bdata.read_data.id_maps.uid_entity(by.uid()) {
|
||||
// If target is dead or invulnerable (for now, this only
|
||||
// means safezone), untarget them and idle.
|
||||
if is_dead_or_invulnerable(attacker, bdata.read_data) {
|
||||
@ -460,7 +453,7 @@ fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
|
||||
|
||||
if bdata.agent.target.is_none() && small_chance {
|
||||
if let Some(Alignment::Owned(owner)) = bdata.agent_data.alignment {
|
||||
if let Some(owner) = get_entity_by_id(owner.id(), bdata.read_data) {
|
||||
if let Some(owner) = get_entity_by_id(*owner, bdata.read_data) {
|
||||
let owner_pos = bdata.read_data.positions.get(owner).map(|pos| pos.0);
|
||||
|
||||
bdata.agent.target = Some(Target::new(
|
||||
|
@ -14,7 +14,6 @@ use common::{
|
||||
trade::{TradeAction, TradePhase, TradeResult},
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::saveload::Marker;
|
||||
|
||||
use crate::sys::agent::util::get_entity_by_id;
|
||||
|
||||
@ -87,7 +86,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
|
||||
if let Some(AgentEvent::Talk(by, subject)) = agent.inbox.pop_front() {
|
||||
let by_entity = get_entity_by_id(by.id(), read_data);
|
||||
let by_entity = get_entity_by_id(by, read_data);
|
||||
|
||||
if let Some(rtsim_outbox) = &mut agent.rtsim_outbox {
|
||||
if let Subject::Regular
|
||||
@ -297,7 +296,7 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
|
||||
// stand still and looking towards the trading player
|
||||
controller.push_action(ControlAction::Stand);
|
||||
controller.push_action(ControlAction::Talk);
|
||||
if let Some(target) = get_entity_by_id(with.id(), read_data) {
|
||||
if let Some(target) = get_entity_by_id(with, read_data) {
|
||||
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
|
||||
|
||||
agent.target = Some(Target::new(
|
||||
@ -344,7 +343,7 @@ pub fn handle_inbox_trade_accepted(bdata: &mut BehaviorData) -> bool {
|
||||
|
||||
if let Some(AgentEvent::TradeAccepted(with)) = agent.inbox.pop_front() {
|
||||
if !agent.behavior.is(BehaviorState::TRADING) {
|
||||
if let Some(target) = get_entity_by_id(with.id(), read_data) {
|
||||
if let Some(target) = get_entity_by_id(with, read_data) {
|
||||
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
|
||||
|
||||
agent.target = Some(Target::new(
|
||||
@ -561,7 +560,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
let used = match msg {
|
||||
AgentEvent::Talk(by, _) | AgentEvent::TradeAccepted(by) => {
|
||||
if let (Some(target), Some(speaker)) =
|
||||
(agent.target, get_entity_by_id(by.id(), bdata.read_data))
|
||||
(agent.target, get_entity_by_id(*by, bdata.read_data))
|
||||
{
|
||||
// in combat, speak to players that aren't the current target
|
||||
if !target.hostile || target.target != speaker {
|
||||
@ -578,7 +577,7 @@ pub fn handle_inbox_cancel_interactions(bdata: &mut BehaviorData) -> bool {
|
||||
AgentEvent::TradeInvite(by) => {
|
||||
controller.push_invite_response(InviteResponse::Decline);
|
||||
if let (Some(target), Some(speaker)) =
|
||||
(agent.target, get_entity_by_id(by.id(), bdata.read_data))
|
||||
(agent.target, get_entity_by_id(*by, bdata.read_data))
|
||||
{
|
||||
// in combat, speak to players that aren't the current target
|
||||
if !target.hostile || target.target != speaker {
|
||||
|
@ -8,7 +8,6 @@ use common::{
|
||||
region::{Event as RegionEvent, RegionMap},
|
||||
resources::{PlayerPhysicsSettings, Time, TimeOfDay, TimeScale},
|
||||
terrain::TerrainChunkSize,
|
||||
uid::Uid,
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
@ -191,6 +190,9 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|key| !regions.contains(key))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
// TODO: I suspect it would be more efficient (in terms of
|
||||
// bandwidth) to batch messages like this (same in
|
||||
// subscription.rs).
|
||||
client.send_fallible(ServerGeneral::DeleteEntity(uid));
|
||||
}
|
||||
}
|
||||
@ -309,6 +311,11 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: force update counter only needs to be sent once per frame (and only if
|
||||
// it changed, although it might not be worth having a separate message for
|
||||
// optionally sending it since individual messages may have a bandwidth
|
||||
// overhead), however, here we send it potentially 2 times per subscribed
|
||||
// region by including it in the `CompSync` message.
|
||||
client.send_fallible(ServerGeneral::CompSync(
|
||||
comp_sync_package,
|
||||
force_updates.get(*client_entity).map_or(0, |f| f.counter()),
|
||||
@ -350,7 +357,7 @@ impl<'a> System<'a> for Sys {
|
||||
})
|
||||
{
|
||||
for uid in &deleted {
|
||||
client.send_fallible(ServerGeneral::DeleteEntity(Uid(*uid)));
|
||||
client.send_fallible(ServerGeneral::DeleteEntity(*uid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use common::{
|
||||
comp::{group::GroupManager, loot_owner::LootOwnerKind, LootOwner},
|
||||
uid::UidAllocator,
|
||||
uid::IdMaps,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Entity, Join, Read, WriteStorage};
|
||||
use specs::{Entities, Entity, Join, Read, WriteStorage};
|
||||
use tracing::debug;
|
||||
|
||||
// This system manages loot that exists in the world
|
||||
@ -13,7 +13,7 @@ impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
WriteStorage<'a, LootOwner>,
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
Read<'a, GroupManager>,
|
||||
);
|
||||
|
||||
@ -23,7 +23,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(entities, mut loot_owners, uid_allocator, group_manager): Self::SystemData,
|
||||
(entities, mut loot_owners, id_maps, group_manager): Self::SystemData,
|
||||
) {
|
||||
// Find and remove expired loot ownership. Loot ownership is expired when either
|
||||
// the expiry time has passed, or the owner no longer exists
|
||||
@ -32,8 +32,8 @@ impl<'a> System<'a> for Sys {
|
||||
.filter(|(_, loot_owner)| {
|
||||
loot_owner.expired()
|
||||
|| match loot_owner.owner() {
|
||||
LootOwnerKind::Player(uid) => uid_allocator
|
||||
.retrieve_entity_internal(uid.into())
|
||||
LootOwnerKind::Player(uid) => id_maps
|
||||
.uid_entity(uid)
|
||||
.map_or(true, |entity| !entities.is_alive(entity)),
|
||||
LootOwnerKind::Group(group) => group_manager.group_info(group).is_none(),
|
||||
}
|
||||
|
@ -85,6 +85,12 @@ impl Sys {
|
||||
},
|
||||
ClientGeneral::Character(character_id, requested_view_distances) => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
// NOTE: Because clients retain their Uid when exiting to the character
|
||||
// selection screen, we rely on this check to prevent them from immediately
|
||||
// re-entering in-game in the same tick so that we can synchronize their
|
||||
// removal without this being mixed up (and e.g. telling other clients to
|
||||
// delete the re-joined version) or requiring more complex handling to avoid
|
||||
// this.
|
||||
if presences.contains(entity) {
|
||||
debug!("player already ingame, aborting");
|
||||
} else if character_updater.has_pending_database_action(character_id)
|
||||
|
@ -102,15 +102,12 @@ impl Sys {
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControlEvent(event) => {
|
||||
if presence.kind.controlling_char() {
|
||||
if presence.kind.controlling_char() && let Some(controller) = controller {
|
||||
// Skip respawn if client entity is alive
|
||||
if let ControlEvent::Respawn = event {
|
||||
if healths.get(entity).map_or(true, |h| !h.is_dead) {
|
||||
//Todo: comment why return!
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(controller) = controller {
|
||||
let skip_respawn = matches!(event, ControlEvent::Respawn)
|
||||
&& healths.get(entity).map_or(true, |h| !h.is_dead);
|
||||
|
||||
if !skip_respawn {
|
||||
controller.push_event(event);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use common::{
|
||||
recipe::{default_component_recipe_book, default_recipe_book, default_repair_recipe_book},
|
||||
resources::TimeOfDay,
|
||||
shared_server_config::ServerConstants,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use common_base::prof_span;
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
@ -54,7 +54,7 @@ pub struct ReadData<'a> {
|
||||
trackers: TrackedStorages<'a>,
|
||||
_healths: ReadStorage<'a, Health>, // used by plugin feature
|
||||
_plugin_mgr: ReadPlugin<'a>, // used by plugin feature
|
||||
_uid_allocator: Read<'a, UidAllocator>, // used by plugin feature
|
||||
_id_maps: Read<'a, IdMaps>, // used by plugin feature
|
||||
}
|
||||
|
||||
/// This system will handle new messages from clients
|
||||
@ -146,7 +146,7 @@ impl<'a> System<'a> for Sys {
|
||||
// NOTE: Only the old player list is provided, to avoid scalability
|
||||
// bottlenecks.
|
||||
player: (&players).into(),
|
||||
uid_allocator: &read_data._uid_allocator,
|
||||
id_maps: &read_data._id_maps,
|
||||
};
|
||||
|
||||
// NOTE: this is just default value.
|
||||
|
@ -9,6 +9,7 @@ use common::{
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{Join, ReadStorage, Write, WriteExpect};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Sys;
|
||||
@ -74,6 +75,14 @@ impl<'a> System<'a> for Sys {
|
||||
active_abilities,
|
||||
map_marker,
|
||||
)| match presence.kind {
|
||||
PresenceKind::LoadingCharacter(_char_id) => {
|
||||
error!(
|
||||
"Unexpected state when persisting characters! Some of the \
|
||||
components required above should only be present after a \
|
||||
character is loaded!"
|
||||
);
|
||||
None
|
||||
},
|
||||
PresenceKind::Character(id) => {
|
||||
let pets = (&alignments, &bodies, &stats, &pets)
|
||||
.join()
|
||||
|
@ -1,12 +1,10 @@
|
||||
use common::{
|
||||
comp::{Alignment, Pet, PhysicsState, Pos},
|
||||
terrain::TerrainGrid,
|
||||
uid::UidAllocator,
|
||||
uid::IdMaps,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, Entities, Entity, Join, Read, ReadExpect, ReadStorage, WriteStorage,
|
||||
};
|
||||
use specs::{Entities, Entity, Join, Read, ReadExpect, ReadStorage, WriteStorage};
|
||||
|
||||
/// This system is responsible for handling pets
|
||||
#[derive(Default)]
|
||||
@ -19,7 +17,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Alignment>,
|
||||
ReadStorage<'a, Pet>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
Read<'a, UidAllocator>,
|
||||
Read<'a, IdMaps>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "pets";
|
||||
@ -28,7 +26,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(entities, terrain, mut positions, alignments, pets, physics, uid_allocator): Self::SystemData,
|
||||
(entities, terrain, mut positions, alignments, pets, physics, id_maps): Self::SystemData,
|
||||
) {
|
||||
const LOST_PET_DISTANCE_THRESHOLD: f32 = 200.0;
|
||||
|
||||
@ -36,20 +34,18 @@ impl<'a> System<'a> for Sys {
|
||||
let lost_pets: Vec<(Entity, Pos)> = (&entities, &positions, &alignments, &pets)
|
||||
.join()
|
||||
.filter_map(|(entity, pos, alignment, _)| match alignment {
|
||||
Alignment::Owned(owner_uid) => Some((entity, pos, owner_uid)),
|
||||
Alignment::Owned(owner_uid) => Some((entity, pos, *owner_uid)),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(|(pet_entity, pet_pos, owner_uid)| {
|
||||
uid_allocator
|
||||
.retrieve_entity_internal(owner_uid.0)
|
||||
.and_then(|owner_entity| {
|
||||
match (positions.get(owner_entity), physics.get(owner_entity)) {
|
||||
(Some(position), Some(physics)) => {
|
||||
Some((pet_entity, position, physics, pet_pos))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
id_maps.uid_entity(owner_uid).and_then(|owner_entity| {
|
||||
match (positions.get(owner_entity), physics.get(owner_entity)) {
|
||||
(Some(position), Some(physics)) => {
|
||||
Some((pet_entity, position, physics, pet_pos))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.filter(|(_, owner_pos, owner_physics, pet_pos)| {
|
||||
// Don't teleport pets to the player if they're in the air, nobody wants
|
||||
|
@ -1,4 +1,3 @@
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
use common::{
|
||||
comp::{
|
||||
item::{tool::AbilityMap, MaterialStatManifest},
|
||||
@ -80,7 +79,6 @@ macro_rules! trackers {
|
||||
vel: Option<Vel>,
|
||||
ori: Option<Ori>,
|
||||
) -> EntityPackage<EcsCompPacket> {
|
||||
let uid = uid.0;
|
||||
let mut comps = Vec::new();
|
||||
// NOTE: we could potentially include a bitmap indicating which components are present instead of tagging
|
||||
// components with the type in order to save bandwidth
|
||||
@ -208,7 +206,7 @@ macro_rules! trackers {
|
||||
&self,
|
||||
comps: &TrackedStorages,
|
||||
filter: impl Join + Copy,
|
||||
deleted_entities: Vec<u64>,
|
||||
deleted_entities: Vec<Uid>,
|
||||
) -> (EntitySyncPackage, CompSyncPackage<EcsCompPacket>) {
|
||||
let entity_sync_package =
|
||||
EntitySyncPackage::new(&comps.uid, &self.uid, filter, deleted_entities);
|
||||
@ -278,9 +276,13 @@ use common_net::synced_components::*;
|
||||
// of components. This will declare the types defined in the macro above.
|
||||
common_net::synced_components!(trackers);
|
||||
|
||||
/// Deleted entities grouped by region
|
||||
/// Deleted entities grouped by region.
|
||||
///
|
||||
/// Note, this is primarily for syncing purposes and can include the Uid of
|
||||
/// clients that have left "in-game" to return to the character screen (even
|
||||
/// though there is still an entity with this Uid!).
|
||||
pub struct DeletedEntities {
|
||||
map: HashMap<Vec2<i32>, Vec<u64>>,
|
||||
map: HashMap<Vec2<i32>, Vec<Uid>>,
|
||||
}
|
||||
|
||||
impl Default for DeletedEntities {
|
||||
@ -293,18 +295,18 @@ impl Default for DeletedEntities {
|
||||
|
||||
impl DeletedEntities {
|
||||
pub fn record_deleted_entity(&mut self, uid: Uid, region_key: Vec2<i32>) {
|
||||
self.map.entry(region_key).or_default().push(uid.into());
|
||||
self.map.entry(region_key).or_default().push(uid);
|
||||
}
|
||||
|
||||
pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Vec<u64> {
|
||||
pub fn take_deleted_in_region(&mut self, key: Vec2<i32>) -> Vec<Uid> {
|
||||
self.map.remove(&key).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_deleted_in_region(&self, key: Vec2<i32>) -> &[u64] {
|
||||
pub fn get_deleted_in_region(&self, key: Vec2<i32>) -> &[Uid] {
|
||||
self.map.get(&key).map_or(&[], |v| v.as_slice())
|
||||
}
|
||||
|
||||
pub fn take_remaining_deleted(&mut self) -> impl Iterator<Item = (Vec2<i32>, Vec<u64>)> + '_ {
|
||||
pub fn take_remaining_deleted(&mut self) -> impl Iterator<Item = (Vec2<i32>, Vec<Uid>)> + '_ {
|
||||
self.map.drain()
|
||||
}
|
||||
}
|
||||
|
@ -135,10 +135,10 @@ impl<'a> System<'a> for Sys {
|
||||
// TODO: consider changing system ordering??
|
||||
for event in region.events() {
|
||||
match event {
|
||||
RegionEvent::Entered(_, _) => {}, /* These don't need to be */
|
||||
// processed because this
|
||||
// region is being thrown out
|
||||
// anyway
|
||||
RegionEvent::Entered(_, _) => {
|
||||
// These don't need to be processed because
|
||||
// this region is being thrown out anyway
|
||||
},
|
||||
RegionEvent::Left(id, maybe_key) => {
|
||||
// Lookup UID for entity
|
||||
// Doesn't overlap with entity deletion in sync packages
|
||||
@ -147,7 +147,9 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some(&uid) = uids.get(entities.entity(*id)) {
|
||||
if !maybe_key
|
||||
.as_ref()
|
||||
// Don't need to check that this isn't also in the regions to remove since the entity will be removed when we get to that one
|
||||
// Don't need to check that this isn't also in the
|
||||
// regions to remove since the entity will be removed
|
||||
// when we get to that one.
|
||||
.map(|key| subscription.regions.contains(key))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -162,10 +164,10 @@ impl<'a> System<'a> for Sys {
|
||||
client.send_fallible(ServerGeneral::DeleteEntity(uid));
|
||||
}
|
||||
}
|
||||
// Send deleted entities since they won't be processed for this client in entity
|
||||
// sync
|
||||
// Send deleted entities since they won't be processed for this client
|
||||
// in entity sync
|
||||
for uid in deleted_entities.get_deleted_in_region(key).iter() {
|
||||
client.send_fallible(ServerGeneral::DeleteEntity(Uid(*uid)));
|
||||
client.send_fallible(ServerGeneral::DeleteEntity(*uid));
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +197,7 @@ impl<'a> System<'a> for Sys {
|
||||
ori.copied(),
|
||||
)
|
||||
})
|
||||
// TODO: batch this into a single message
|
||||
.for_each(|msg| {
|
||||
// Send message to create entity and tracked components and
|
||||
// physics components
|
||||
|
@ -18,7 +18,7 @@ use common::{
|
||||
combat,
|
||||
comp::{group::Role, inventory::item::MaterialStatManifest, invite::InviteKind, Stats},
|
||||
resources::Time,
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use common_net::sync::WorldSyncExt;
|
||||
use conrod_core::{
|
||||
@ -28,7 +28,7 @@ use conrod_core::{
|
||||
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use i18n::Localization;
|
||||
use specs::{saveload::MarkerAllocator, WorldExt};
|
||||
use specs::WorldExt;
|
||||
|
||||
widget_ids! {
|
||||
pub struct Ids {
|
||||
@ -202,7 +202,7 @@ impl<'a> Widget for Group<'a> {
|
||||
None => client
|
||||
.state()
|
||||
.ecs()
|
||||
.entity_from_uid(uid.0)
|
||||
.entity_from_uid(uid)
|
||||
.and_then(|entity| {
|
||||
client
|
||||
.state()
|
||||
@ -373,7 +373,7 @@ impl<'a> Widget for Group<'a> {
|
||||
let energy = client_state.ecs().read_storage::<common::comp::Energy>();
|
||||
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
|
||||
let inventory = client_state.ecs().read_storage::<common::comp::Inventory>();
|
||||
let uid_allocator = client_state.ecs().read_resource::<UidAllocator>();
|
||||
let id_maps = client_state.ecs().read_resource::<IdMaps>();
|
||||
let bodies = client_state.ecs().read_storage::<common::comp::Body>();
|
||||
let poises = client_state.ecs().read_storage::<common::comp::Poise>();
|
||||
let stances = client_state.ecs().read_storage::<common::comp::Stance>();
|
||||
@ -382,7 +382,7 @@ impl<'a> Widget for Group<'a> {
|
||||
let mut total_buff_count = 0;
|
||||
for (i, &uid) in group_members.iter().copied().enumerate() {
|
||||
self.show.group = true;
|
||||
let entity = uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = id_maps.uid_entity(uid);
|
||||
let stats = entity.and_then(|entity| stats.get(entity));
|
||||
let skill_set = entity.and_then(|entity| skill_sets.get(entity));
|
||||
let health = entity.and_then(|entity| healths.get(entity));
|
||||
|
@ -27,7 +27,7 @@ use conrod_core::{
|
||||
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon,
|
||||
};
|
||||
use i18n::Localization;
|
||||
use specs::{saveload::MarkerAllocator, WorldExt};
|
||||
use specs::WorldExt;
|
||||
use std::borrow::Cow;
|
||||
use vek::*;
|
||||
use winit::event::MouseButton;
|
||||
@ -1240,9 +1240,9 @@ impl<'a> Widget for Map<'a> {
|
||||
.collect::<Vec<_>>();
|
||||
let group_size = group_members.len();
|
||||
//let in_group = !group_members.is_empty();
|
||||
let uid_allocator = client_state
|
||||
let id_maps = client_state
|
||||
.ecs()
|
||||
.read_resource::<common_net::sync::UidAllocator>();
|
||||
.read_resource::<common_net::sync::IdMaps>();
|
||||
if state.ids.member_indicators.len() < group_size {
|
||||
state.update(|s| {
|
||||
s.ids
|
||||
@ -1251,7 +1251,7 @@ impl<'a> Widget for Map<'a> {
|
||||
})
|
||||
};
|
||||
for (i, &uid) in group_members.iter().copied().enumerate() {
|
||||
let entity = uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = id_maps.uid_entity(uid);
|
||||
let member_pos = entity.and_then(|entity| member_pos.get(entity));
|
||||
let stats = entity.and_then(|entity| stats.get(entity));
|
||||
let name = if let Some(stats) = stats {
|
||||
@ -1323,8 +1323,8 @@ impl<'a> Widget for Map<'a> {
|
||||
.get(&uid)
|
||||
.map(|info| info.player_alias.as_str())
|
||||
.or_else(|| {
|
||||
uid_allocator
|
||||
.retrieve_entity_internal(uid.into())
|
||||
id_maps
|
||||
.uid_entity(uid)
|
||||
.and_then(|entity| stats.get(entity))
|
||||
.map(|stats| stats.name.as_str())
|
||||
})
|
||||
|
@ -28,7 +28,7 @@ use conrod_core::{
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use image::{DynamicImage, RgbaImage};
|
||||
use specs::{saveload::MarkerAllocator, WorldExt};
|
||||
use specs::WorldExt;
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
@ -762,9 +762,9 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
.collect::<Vec<_>>();
|
||||
let group_size = group_members.len();
|
||||
//let in_group = !group_members.is_empty();
|
||||
let uid_allocator = client_state
|
||||
let id_maps = client_state
|
||||
.ecs()
|
||||
.read_resource::<common_net::sync::UidAllocator>();
|
||||
.read_resource::<common_net::sync::IdMaps>();
|
||||
if state.ids.member_indicators.len() < group_size {
|
||||
state.update(|s| {
|
||||
s.ids
|
||||
@ -773,7 +773,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
})
|
||||
};
|
||||
for (i, &uid) in group_members.iter().copied().enumerate() {
|
||||
let entity = uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let entity = id_maps.uid_entity(uid);
|
||||
let member_pos = entity.and_then(|entity| member_pos.get(entity));
|
||||
|
||||
if let Some(member_pos) = member_pos {
|
||||
|
@ -1247,12 +1247,11 @@ impl HudCollectFailedReason {
|
||||
CollectFailedReason::LootOwned { owner, expiry_secs } => {
|
||||
let owner = match owner {
|
||||
LootOwnerKind::Player(owner_uid) => {
|
||||
let maybe_owner_name =
|
||||
ecs.entity_from_uid((*owner_uid).into()).and_then(|entity| {
|
||||
ecs.read_storage::<comp::Stats>()
|
||||
.get(entity)
|
||||
.map(|stats| stats.name.clone())
|
||||
});
|
||||
let maybe_owner_name = ecs.entity_from_uid(*owner_uid).and_then(|entity| {
|
||||
ecs.read_storage::<comp::Stats>()
|
||||
.get(entity)
|
||||
.map(|stats| stats.name.clone())
|
||||
});
|
||||
|
||||
if let Some(name) = maybe_owner_name {
|
||||
HudLootOwner::Name(name)
|
||||
@ -1351,6 +1350,7 @@ impl Hud {
|
||||
|
||||
let character_id = match client.presence().unwrap() {
|
||||
PresenceKind::Character(id) => Some(id),
|
||||
PresenceKind::LoadingCharacter(id) => Some(id),
|
||||
PresenceKind::Spectator => None,
|
||||
PresenceKind::Possessor => None,
|
||||
};
|
||||
@ -4056,7 +4056,7 @@ impl Hud {
|
||||
let ecs = client.state().ecs();
|
||||
let inventories = ecs.read_component::<comp::Inventory>();
|
||||
let get_inventory = |uid: Uid| {
|
||||
if let Some(entity) = ecs.entity_from_uid(uid.0) {
|
||||
if let Some(entity) = ecs.entity_from_uid(uid) {
|
||||
inventories.get(entity)
|
||||
} else {
|
||||
None
|
||||
@ -4914,7 +4914,7 @@ impl Hud {
|
||||
let me = client.entity();
|
||||
let my_uid = uids.get(me);
|
||||
|
||||
if let Some(entity) = ecs.entity_from_uid(info.target.0) {
|
||||
if let Some(entity) = ecs.entity_from_uid(info.target) {
|
||||
if let Some(floater_list) = hp_floater_lists.get_mut(entity) {
|
||||
let hit_me = my_uid.map_or(false, |&uid| {
|
||||
(info.target == uid) && global_state.settings.interface.sct_inc_dmg
|
||||
|
@ -204,7 +204,7 @@ impl<'a> Trade<'a> {
|
||||
let inventories = self.client.inventories();
|
||||
let check_if_us = |who: usize| -> Option<_> {
|
||||
let uid = trade.parties[who];
|
||||
let entity = self.client.state().ecs().entity_from_uid(uid.0)?;
|
||||
let entity = self.client.state().ecs().entity_from_uid(uid)?;
|
||||
let is_ours = entity == self.client.entity();
|
||||
Some(((who, uid, entity), is_ours))
|
||||
};
|
||||
|
@ -48,7 +48,7 @@ use common::{
|
||||
resources::{DeltaTime, Time},
|
||||
states::{equipping, idle, utils::StageSection, wielding},
|
||||
terrain::{Block, SpriteKind, TerrainChunk, TerrainGrid},
|
||||
uid::UidAllocator,
|
||||
uid::IdMaps,
|
||||
util::Dir,
|
||||
vol::{ReadVol, RectRasterableVol},
|
||||
};
|
||||
@ -62,7 +62,7 @@ use core::{
|
||||
};
|
||||
use guillotiere::AtlasAllocator;
|
||||
use hashbrown::HashMap;
|
||||
use specs::{saveload::MarkerAllocator, Entity as EcsEntity, Join, LazyUpdate, WorldExt};
|
||||
use specs::{Entity as EcsEntity, Join, LazyUpdate, WorldExt};
|
||||
use std::sync::Arc;
|
||||
use treeculler::{BVol, BoundingSphere};
|
||||
use vek::*;
|
||||
@ -829,7 +829,7 @@ impl FigureMgr {
|
||||
|
||||
let mut update_buf = [Default::default(); anim::MAX_BONE_COUNT];
|
||||
|
||||
let uid_allocator = ecs.read_resource::<UidAllocator>();
|
||||
let id_maps = ecs.read_resource::<IdMaps>();
|
||||
|
||||
let bodies = ecs.read_storage::<Body>();
|
||||
|
||||
@ -1055,7 +1055,7 @@ impl FigureMgr {
|
||||
let mount_transform_pos = (|| -> Option<_> {
|
||||
if let Some(is_rider) = is_rider {
|
||||
let mount = is_rider.mount;
|
||||
let mount = uid_allocator.retrieve_entity_internal(mount.into())?;
|
||||
let mount = id_maps.uid_entity(mount)?;
|
||||
let body = *bodies.get(mount)?;
|
||||
let meta = self.states.get_mut(&body, &mount)?;
|
||||
Some((meta.mount_transform, meta.mount_world_pos))
|
||||
|
@ -20,13 +20,13 @@ use common::{
|
||||
spiral::Spiral2d,
|
||||
states::{self, utils::StageSection},
|
||||
terrain::{Block, SpriteKind, TerrainChunk, TerrainGrid},
|
||||
uid::UidAllocator,
|
||||
uid::IdMaps,
|
||||
vol::{ReadVol, RectRasterableVol, SizedVol},
|
||||
};
|
||||
use common_base::span;
|
||||
use hashbrown::HashMap;
|
||||
use rand::prelude::*;
|
||||
use specs::{saveload::MarkerAllocator, Join, WorldExt};
|
||||
use specs::{Join, WorldExt};
|
||||
use std::{
|
||||
f32::consts::{PI, TAU},
|
||||
time::Duration,
|
||||
@ -287,10 +287,7 @@ impl ParticleMgr {
|
||||
if target.is_some() {
|
||||
let ecs = scene_data.state.ecs();
|
||||
if target
|
||||
.and_then(|target| {
|
||||
ecs.read_resource::<UidAllocator>()
|
||||
.retrieve_entity_internal(target.0)
|
||||
})
|
||||
.and_then(|target| ecs.read_resource::<IdMaps>().uid_entity(target))
|
||||
.and_then(|entity| {
|
||||
ecs.read_storage::<Body>()
|
||||
.get(entity)
|
||||
|
@ -14,7 +14,7 @@ use common::{
|
||||
link::Is,
|
||||
mounting::{Mount, Rider, VolumePos, VolumeRider},
|
||||
terrain::{Block, TerrainGrid, UnlockKind},
|
||||
uid::{Uid, UidAllocator},
|
||||
uid::{IdMaps, Uid},
|
||||
util::find_dist::{Cube, Cylinder, FindDist},
|
||||
vol::ReadVol,
|
||||
CachedSpatialGrid,
|
||||
@ -53,12 +53,12 @@ impl Interactable {
|
||||
|
||||
fn from_block_pos(
|
||||
terrain: &TerrainGrid,
|
||||
uid_allocator: &UidAllocator,
|
||||
id_maps: &IdMaps,
|
||||
colliders: &ReadStorage<Collider>,
|
||||
volume_pos: VolumePos,
|
||||
interaction: Interaction,
|
||||
) -> Option<Self> {
|
||||
let Some(block) = volume_pos.get_block(terrain, uid_allocator, colliders) else { return None };
|
||||
let Some(block) = volume_pos.get_block(terrain, id_maps, colliders) else { return None };
|
||||
let block_interaction = match interaction {
|
||||
Interaction::Collect => {
|
||||
// Check if this is an unlockable sprite
|
||||
@ -346,7 +346,7 @@ pub(super) fn select_interactable(
|
||||
.and_then(|(_, block_pos, interaction)| {
|
||||
Interactable::from_block_pos(
|
||||
&terrain,
|
||||
&ecs.read_resource::<UidAllocator>(),
|
||||
&ecs.read_resource::<IdMaps>(),
|
||||
&ecs.read_storage(),
|
||||
block_pos,
|
||||
*interaction,
|
||||
|
@ -294,7 +294,7 @@ impl SessionState {
|
||||
};
|
||||
let target_name = match client.player_list().get(&target) {
|
||||
Some(info) => info.player_alias.clone(),
|
||||
None => match client.state().ecs().entity_from_uid(target.0) {
|
||||
None => match client.state().ecs().entity_from_uid(target) {
|
||||
Some(entity) => {
|
||||
let stats = client.state().read_storage::<Stats>();
|
||||
stats
|
||||
@ -362,9 +362,7 @@ impl SessionState {
|
||||
entity: uid,
|
||||
reason,
|
||||
} => {
|
||||
if let Some(entity) =
|
||||
client.state().ecs().entity_from_uid(uid.into())
|
||||
{
|
||||
if let Some(entity) = client.state().ecs().entity_from_uid(uid) {
|
||||
self.hud.add_failed_entity_pickup(
|
||||
entity,
|
||||
HudCollectFailedReason::from_server_reason(
|
||||
@ -1680,6 +1678,7 @@ impl PlayState for SessionState {
|
||||
// If we are changing the hotbar state this CANNOT be None.
|
||||
let character_id = match client.presence().unwrap() {
|
||||
PresenceKind::Character(id) => Some(id),
|
||||
PresenceKind::LoadingCharacter(id) => Some(id),
|
||||
PresenceKind::Spectator => {
|
||||
unreachable!("HUD adaption in Spectator mode!")
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user