1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-05-13 07:07:45 +00:00

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.
This commit is contained in:
Joshua Goins 2025-05-09 19:27:18 -04:00
parent 9787126a1b
commit cbeaa83307
3 changed files with 178 additions and 54 deletions

View file

@ -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

View file

@ -79,6 +79,7 @@ pub struct ClientHandle {
pub id: ClientId,
pub ip: SocketAddr,
pub channel: Sender<FromServer>,
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));

View file

@ -10,13 +10,62 @@ use crate::{
use super::{Actor, ClientHandle, ClientId, FromServer, ToServer};
#[derive(Default, Debug)]
struct WorldServer {
clients: HashMap<ClientId, ClientHandle>,
#[derive(Default, Debug, Clone)]
struct Instance {
zone_id: u16,
// structure temporary, of course
actors: HashMap<ObjectId, CommonSpawn>,
}
#[derive(Default, Debug, Clone)]
struct ClientState {
zone_id: u16,
}
#[derive(Default, Debug)]
struct WorldServer {
clients: HashMap<ClientId, (ClientHandle, ClientState)>,
/// Indexed by zone id
instances: HashMap<u16, Instance>,
}
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<ToServer>) -> 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<ToServer>) -> 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<ToServer>) -> 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<ToServer>) -> 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<ToServer>) -> 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