From cbeaa83307430bd0ec0c1b8c7e826dfd8a8b4b7a Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 9 May 2025 19:27:18 -0400 Subject: [PATCH] Implement basic zone isolation for multiplayer Now you don't see every player from every zone, like some kind of madman. The code still sucks, but it works. --- src/bin/kawari-world.rs | 11 ++- src/world/connection.rs | 22 +++-- src/world/server.rs | 199 +++++++++++++++++++++++++++++++--------- 3 files changed, 178 insertions(+), 54 deletions(-) diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index f2e5962..9a926b2 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -76,6 +76,7 @@ pub fn spawn_client(connection: ZoneConnection) { let id = &connection.id.clone(); let ip = &connection.ip.clone(); + let actor_id = connection.player_data.actor_id; let data = ClientData { //id: connection.id, @@ -93,6 +94,7 @@ pub fn spawn_client(connection: ZoneConnection) { id: *id, ip: *ip, channel: send, + actor_id, //kill, }; let _ = my_send.send(handle); @@ -163,8 +165,11 @@ async fn client_loop( connection.exit_position = Some(connection.player_data.position); connection.exit_rotation = Some(connection.player_data.rotation); + let mut client_handle = client_handle.clone(); + client_handle.actor_id = actor_id; + // tell the server we exist, now that we confirmed we are a legitimate connection - connection.handle.send(ToServer::NewClient(client_handle.clone())).await; + connection.handle.send(ToServer::NewClient(client_handle)).await; } else if connection_type == ConnectionType::Chat { // We have send THEM a keep alive connection.send_chat_segment(PacketSegment { @@ -314,7 +319,7 @@ async fn client_loop( } ClientZoneIpcData::FinishLoading { .. } => { // tell the server we loaded into the zone, so it can start sending us acors - connection.handle.send(ToServer::ZoneLoaded(connection.id)).await; + connection.handle.send(ToServer::ZoneLoaded(connection.id, connection.zone.as_ref().unwrap().id)).await; let common = connection.get_player_common_spawn(connection.exit_position, connection.exit_rotation); @@ -372,7 +377,7 @@ async fn client_loop( connection.exit_rotation = None; // tell the other players we're here - connection.handle.send(ToServer::ActorSpawned(connection.id, Actor { id: ObjectId(connection.player_data.actor_id), hp: 100, spawn_index: 0 }, common)).await; + connection.handle.send(ToServer::ActorSpawned(connection.id, connection.zone.as_ref().unwrap().id, Actor { id: ObjectId(connection.player_data.actor_id), hp: 100, spawn_index: 0 }, common)).await; } ClientZoneIpcData::ClientTrigger(trigger) => { // inform the server of our trigger, it will handle sending it to other clients diff --git a/src/world/connection.rs b/src/world/connection.rs index d0c1a9b..eb66e7a 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -79,6 +79,7 @@ pub struct ClientHandle { pub id: ClientId, pub ip: SocketAddr, pub channel: Sender, + pub actor_id: u32, // TODO: restore, i guess //pub kill: JoinHandle<()>, } @@ -108,11 +109,14 @@ impl ClientHandle { pub enum ToServer { NewClient(ClientHandle), Message(ClientId, String), - ActorSpawned(ClientId, Actor, CommonSpawn), + // TODO: ditto, zone id should not be here + ActorSpawned(ClientId, u16, Actor, CommonSpawn), ActorMoved(ClientId, u32, Position, f32), ActorDespawned(ClientId, u32), ClientTrigger(ClientId, u32, ClientTrigger), - ZoneLoaded(ClientId), + // TODO: the connection should not be in charge and telling the global server what zone they just loaded in! but this will work for now + ZoneLoaded(ClientId, u16), + LeftZone(ClientId, u32, u16), Disconnected(ClientId), FatalError(std::io::Error), } @@ -365,12 +369,16 @@ impl ZoneConnection { pub async fn change_zone(&mut self, new_zone_id: u16) { // tell everyone we're gone - // TODO: check if we ever sent an initial ActorSpawn packet first, before sending this. // the connection already checks to see if the actor already exists, so it's seems harmless if we do - self.handle - .send(ToServer::ActorDespawned(self.id, self.player_data.actor_id)) - .await; - + if self.zone.is_some() { + self.handle + .send(ToServer::LeftZone( + self.id, + self.player_data.actor_id, + self.zone.as_ref().unwrap().id, + )) + .await; + } { let mut game_data = self.gamedata.lock().unwrap(); self.zone = Some(Zone::load(&mut game_data.game_data, new_zone_id)); diff --git a/src/world/server.rs b/src/world/server.rs index 2c94cc1..1297bbe 100644 --- a/src/world/server.rs +++ b/src/world/server.rs @@ -10,13 +10,62 @@ use crate::{ use super::{Actor, ClientHandle, ClientId, FromServer, ToServer}; -#[derive(Default, Debug)] -struct WorldServer { - clients: HashMap, +#[derive(Default, Debug, Clone)] +struct Instance { + zone_id: u16, // structure temporary, of course actors: HashMap, } +#[derive(Default, Debug, Clone)] +struct ClientState { + zone_id: u16, +} + +#[derive(Default, Debug)] +struct WorldServer { + clients: HashMap, + /// Indexed by zone id + instances: HashMap, +} + +impl WorldServer { + /// Finds the instance associated with a zone, or None if it doesn't exist yet. + fn find_instance(&self, zone_id: u16) -> Option<&Instance> { + self.instances.get(&zone_id) + } + + /// Finds the instance associated with a zone, or creates it if it doesn't exist yet + fn find_instance_mut(&mut self, zone_id: u16) -> &mut Instance { + if self.instances.contains_key(&zone_id) { + self.instances.get_mut(&zone_id).unwrap() + } else { + self.instances.insert(zone_id, Instance::default()); + self.instances.get_mut(&zone_id).unwrap() + } + } + + /// Finds the instance associated with an actor, or returns None if they are not found. + fn find_actor_instance(&self, actor_id: u32) -> Option<&Instance> { + for (_, instance) in &self.instances { + if instance.actors.contains_key(&ObjectId(actor_id)) { + return Some(instance); + } + } + None + } + + /// Finds the instance associated with an actor, or returns None if they are not found. + fn find_actor_instance_mut(&mut self, actor_id: u32) -> Option<&mut Instance> { + for (_, instance) in &mut self.instances { + if instance.actors.contains_key(&ObjectId(actor_id)) { + return Some(instance); + } + } + None + } +} + pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::io::Error> { let mut data = WorldServer::default(); let mut to_remove = Vec::new(); @@ -24,33 +73,70 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i while let Some(msg) = recv.recv().await { match msg { ToServer::NewClient(handle) => { - data.clients.insert(handle.id, handle); + data.clients + .insert(handle.id, (handle, ClientState::default())); } - ToServer::ZoneLoaded(from_id) => { - for (id, handle) in &mut data.clients { + ToServer::ZoneLoaded(from_id, zone_id) => { + // create a new instance if nessecary + if !data.instances.contains_key(&zone_id) { + data.instances.insert(zone_id, Instance::default()); + } + + // Send existing player data, if any + if let Some(instance) = data.find_instance(zone_id).cloned() { + for (id, (handle, state)) in &mut data.clients { + let id = *id; + + if id == from_id { + state.zone_id = zone_id; + + // send existing player data + for (id, common) in &instance.actors { + let msg = FromServer::ActorSpawn( + Actor { + id: *id, + hp: 100, + spawn_index: 0, + }, + common.clone(), + ); + + handle.send(msg).unwrap(); + } + + break; + } + } + } + } + ToServer::LeftZone(from_id, actor_id, zone_id) => { + // when the actor leaves the zone, remove them from the instance + let current_instance = data.find_actor_instance_mut(actor_id).unwrap(); + current_instance.actors.remove(&ObjectId(actor_id)); + + // Then tell any clients in the zone that we left + for (id, (handle, state)) in &mut data.clients { let id = *id; + // don't bother telling the client who told us if id == from_id { - // send existing player data - for (id, common) in &data.actors { - let msg = FromServer::ActorSpawn( - Actor { - id: *id, - hp: 100, - spawn_index: 0, - }, - common.clone(), - ); + continue; + } - handle.send(msg).unwrap(); - } + // skip any clients not in our zone + if state.zone_id != zone_id { + continue; + } - break; + let msg = FromServer::ActorDespawn(actor_id); + + if handle.send(msg).is_err() { + to_remove.push(id); } } } ToServer::Message(from_id, msg) => { - for (id, handle) in &mut data.clients { + for (id, (handle, _)) in &mut data.clients { let id = *id; if id == from_id { @@ -64,16 +150,24 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i } } } - ToServer::ActorSpawned(from_id, actor, common) => { - data.actors.insert(actor.id, common.clone()); + ToServer::ActorSpawned(from_id, zone_id, actor, common) => { + let instance = data.find_instance_mut(zone_id); + instance.actors.insert(actor.id, common.clone()); - for (id, handle) in &mut data.clients { + // Then tell any clients in the zone that we spawned + for (id, (handle, state)) in &mut data.clients { let id = *id; + // don't bother telling the client who told us if id == from_id { continue; } + // skip any clients not in our zone + if state.zone_id != zone_id { + continue; + } + let msg = FromServer::ActorSpawn(actor, common.clone()); if handle.send(msg).is_err() { @@ -81,12 +175,27 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i } } } - ToServer::ActorDespawned(_from_id, actor_id) => { - data.actors.remove(&ObjectId(actor_id)); + ToServer::ActorDespawned(from_id, actor_id) => { + // NOTE: the order of operations here is very intentional + // the client will send actordespawn before we get a ZoneLoaded from the server + let current_instance = data.find_actor_instance_mut(actor_id).unwrap(); + let instance = current_instance.clone(); + current_instance.actors.remove(&ObjectId(actor_id)); - for (id, handle) in &mut data.clients { + // Then tell any clients in the zone that we left + for (id, (handle, state)) in &mut data.clients { let id = *id; + // don't bother telling the client who told us + if id == from_id { + continue; + } + + // skip any clients not in our zone + if state.zone_id != instance.zone_id { + continue; + } + let msg = FromServer::ActorDespawn(actor_id); if handle.send(msg).is_err() { @@ -95,31 +204,33 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i } } ToServer::ActorMoved(from_id, actor_id, position, rotation) => { - if let Some((_, common)) = data - .actors - .iter_mut() - .find(|actor| *actor.0 == ObjectId(actor_id)) - { - common.pos = position; - common.rotation = rotation; - } - - for (id, handle) in &mut data.clients { - let id = *id; - - if id == from_id { - continue; + if let Some(instance) = data.find_actor_instance_mut(actor_id) { + if let Some((_, common)) = instance + .actors + .iter_mut() + .find(|actor| *actor.0 == ObjectId(actor_id)) + { + common.pos = position; + common.rotation = rotation; } - let msg = FromServer::ActorMove(actor_id, position, rotation); + for (id, (handle, _)) in &mut data.clients { + let id = *id; - if handle.send(msg).is_err() { - to_remove.push(id); + if id == from_id { + continue; + } + + let msg = FromServer::ActorMove(actor_id, position, rotation); + + if handle.send(msg).is_err() { + to_remove.push(id); + } } } } ToServer::ClientTrigger(from_id, from_actor_id, trigger) => { - for (id, handle) in &mut data.clients { + for (id, (handle, _)) in &mut data.clients { let id = *id; // there's no reason to tell the actor what it just did