diff --git a/crates/messages/src/players/entity.rs b/crates/messages/src/players/entity.rs index 91f61563..c2abb0e3 100644 --- a/crates/messages/src/players/entity.rs +++ b/crates/messages/src/players/entity.rs @@ -1,14 +1,21 @@ -#[cfg(feature = "bevy")] -use bevy::ecs::entity::Entity; use bincode::{Decode, Encode}; +use de_types::player::Player; /// Bevy ECS Entity derived identification of an entity. #[derive(Clone, Copy, Debug, Encode, Decode, Hash, PartialEq, Eq)] -pub struct EntityNet(u32); +pub struct EntityNet { + player: Player, + index: u32, +} -#[cfg(feature = "bevy")] -impl From for EntityNet { - fn from(entity: Entity) -> Self { - Self(entity.index()) +impl EntityNet { + /// # Arguments + /// + /// * `player` - the human player executing the entity simulating game + /// instance. + /// + /// * `index` - locally unique index of the entity. + pub fn new(player: Player, index: u32) -> Self { + Self { player, index } } } diff --git a/crates/multiplayer/src/lib.rs b/crates/multiplayer/src/lib.rs index 741cc1a9..9602732c 100644 --- a/crates/multiplayer/src/lib.rs +++ b/crates/multiplayer/src/lib.rs @@ -23,7 +23,7 @@ pub use crate::{ lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent}, messages::{MessagesSet, ToPlayersEvent}, netstate::NetState, - playermsg::{GameNetSet, NetRecvDespawnActiveEvent, NetRecvSpawnActiveEvent}, + playermsg::{GameNetSet, NetEntities, NetRecvDespawnActiveEvent, NetRecvSpawnActiveEvent}, }; use crate::{netstate::NetStatePlugin, network::NetworkPlugin}; diff --git a/crates/multiplayer/src/playermsg.rs b/crates/multiplayer/src/playermsg.rs index 63c4f000..fcf16203 100644 --- a/crates/multiplayer/src/playermsg.rs +++ b/crates/multiplayer/src/playermsg.rs @@ -1,6 +1,6 @@ use ahash::AHashMap; -use bevy::prelude::*; -use de_core::{schedule::PreMovement, state::AppState}; +use bevy::{ecs::system::SystemParam, prelude::*}; +use de_core::{gconfig::GameConfig, schedule::PreMovement, state::AppState}; use de_messages::{EntityNet, ToPlayers}; use de_types::{objects::ActiveObjectType, player::Player}; @@ -96,20 +96,47 @@ impl NetRecvDespawnActiveEvent { } } -/// Mapping between remote and local entity IDs. +#[derive(SystemParam)] +pub struct NetEntities<'w> { + config: Res<'w, GameConfig>, + map: Res<'w, EntityIdMapRes>, +} + +impl<'w> NetEntities<'w> { + /// Translates a local entity ID to a remote entity ID. This works for both + /// locally simulated and non-local entities. + /// + /// It is assumed that the entity exists. + pub fn net_id(&self, entity: Entity) -> EntityNet { + match self.map.translate_local(entity) { + Some(id) => id, + None => self.local_net_id(entity), + } + } + + /// Translates a local entity ID to a remote entity ID. This works only for + /// locally simulated entities. + /// + /// It is assumed that the entity exists. + pub fn local_net_id(&self, entity: Entity) -> EntityNet { + let player = self.config.locals().playable(); + EntityNet::new(player, entity.index()) + } +} + +/// Mapping between remote and local entity IDs for non-locally simulated +/// entities. #[derive(Resource)] struct EntityIdMapRes { - /// Associated player is not the player owning the entity but the only - /// human player co-located with the player of the entity. Thus the player - /// is either the same (if it is a human) or a different player (if the - /// owning player is AI). - remote_to_local: AHashMap<(Player, EntityNet), Entity>, + remote_to_local: AHashMap, + local_to_remote: AHashMap, } impl EntityIdMapRes { fn new() -> Self { Self { remote_to_local: AHashMap::new(), + local_to_remote: AHashMap::new(), } } @@ -117,9 +144,6 @@ impl EntityIdMapRes { /// /// # Arguments /// - /// * `source` - human player executing the remote side (not necessarily - /// the player of the registered entity which may be AI simulated). - /// /// * `remote` - remote entity identification. /// /// * `local` - local entity (present in the local ECS). @@ -127,8 +151,10 @@ impl EntityIdMapRes { /// # Panics /// /// Panics if the remote entity is already registered. - fn register(&mut self, source: Player, remote: EntityNet, local: Entity) { - let result = self.remote_to_local.insert((source, remote), local); + fn register(&mut self, remote: EntityNet, local: Entity) { + let result = self.remote_to_local.insert(remote, local); + assert!(result.is_none()); + let result = self.local_to_remote.insert(local, remote); assert!(result.is_none()); } @@ -139,8 +165,16 @@ impl EntityIdMapRes { /// # Panics /// /// Panics if the entity is not registered. - fn deregister(&mut self, source: Player, remote: EntityNet) -> Entity { - self.remote_to_local.remove(&(source, remote)).unwrap() + fn deregister(&mut self, remote: EntityNet) -> Entity { + let local = self.remote_to_local.remove(&remote).unwrap(); + self.local_to_remote.remove(&local).unwrap(); + local + } + + /// Translates local entity ID to a remote entity ID in case the entity is + /// not locally simulated. + fn translate_local(&self, local: Entity) -> Option { + self.local_to_remote.get(&local).copied() } } @@ -168,7 +202,7 @@ fn recv_messages( transform, } => { let local = commands.spawn_empty().id(); - map.register(input.source(), *entity, local); + map.register(*entity, local); spawn_events.send(NetRecvSpawnActiveEvent::new( *player, @@ -178,7 +212,7 @@ fn recv_messages( )); } ToPlayers::Despawn { entity } => { - let local = map.deregister(input.source(), *entity); + let local = map.deregister(*entity); despawn_events.send(NetRecvDespawnActiveEvent::new(local)); } _ => (), diff --git a/crates/spawner/src/despawner.rs b/crates/spawner/src/despawner.rs index 28f84e17..605c1db2 100644 --- a/crates/spawner/src/despawner.rs +++ b/crates/spawner/src/despawner.rs @@ -10,7 +10,7 @@ use de_core::{ state::AppState, }; use de_messages::ToPlayers; -use de_multiplayer::{NetRecvDespawnActiveEvent, ToPlayersEvent}; +use de_multiplayer::{NetEntities, NetRecvDespawnActiveEvent, ToPlayersEvent}; use de_objects::Health; use de_types::objects::{ActiveObjectType, ObjectType}; @@ -77,6 +77,7 @@ fn find_dead( fn despawn_active_local( config: Res, + net_entities: NetEntities, mut event_reader: EventReader, mut event_writer: EventWriter, mut net_events: EventWriter, @@ -86,7 +87,7 @@ fn despawn_active_local( if config.multiplayer() { net_events.send(ToPlayersEvent::new(ToPlayers::Despawn { - entity: event.0.into(), + entity: net_entities.local_net_id(event.0), })); } } diff --git a/crates/spawner/src/spawner.rs b/crates/spawner/src/spawner.rs index 1069ea8b..9a7f83c4 100644 --- a/crates/spawner/src/spawner.rs +++ b/crates/spawner/src/spawner.rs @@ -11,7 +11,7 @@ use de_core::{ }; use de_energy::Battery; use de_messages::ToPlayers; -use de_multiplayer::{NetRecvSpawnActiveEvent, ToPlayersEvent}; +use de_multiplayer::{NetEntities, NetRecvSpawnActiveEvent, ToPlayersEvent}; use de_objects::{AssetCollection, InitialHealths, SceneType, Scenes, SolidObjects}; use de_pathing::{PathTarget, UpdateEntityPathEvent}; use de_terrain::{CircleMarker, MarkerVisibility, RectangleMarker}; @@ -141,6 +141,7 @@ impl SpawnEvent { fn spawn_local_active( mut commands: Commands, config: Res, + net_entities: NetEntities, mut event_reader: EventReader, mut event_writer: EventWriter, mut path_events: EventWriter, @@ -167,7 +168,7 @@ fn spawn_local_active( if config.multiplayer() { net_events.send(ToPlayersEvent::new(ToPlayers::Spawn { - entity: entity.into(), + entity: net_entities.local_net_id(entity), player: event.player, object_type: event.object_type, transform: event.transform.into(),