From 9a3652fb421a016f542850e6880875be16d3144a Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 28 Jun 2025 11:11:42 -0400 Subject: [PATCH] When equipping items, network that to other players See #50 --- src/bin/kawari-world.rs | 11 ++++----- src/world/common.rs | 7 ++++-- src/world/connection.rs | 53 +++++++++++++++++++++++++---------------- src/world/server.rs | 43 ++++++++++++++++++++++++++++++--- 4 files changed, 83 insertions(+), 31 deletions(-) diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 357d12b..280ec4d 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -11,7 +11,7 @@ use kawari::ipc::zone::{ ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList, }; use kawari::ipc::zone::{ - ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EventStart, GameMasterRank, OnlineStatus, + ClientTriggerCommand, ClientZoneIpcData, EventStart, GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, }; use kawari::opcodes::{ServerChatIpcType, ServerZoneIpcType}; @@ -79,7 +79,6 @@ pub fn spawn_client(connection: ZoneConnection) { ip: *ip, channel: send, actor_id: 0, - common: CommonSpawn::default(), }; let _ = my_send.send(handle); } @@ -170,7 +169,6 @@ async fn client_loop( let mut client_handle = client_handle.clone(); client_handle.actor_id = actor_id; - client_handle.common = connection.get_player_common_spawn(connection.exit_position, connection.exit_rotation); // tell the server we exist, now that we confirmed we are a legitimate connection connection.handle.send(ToServer::NewClient(client_handle)).await; @@ -326,11 +324,11 @@ async fn client_loop( .unwrap(); } ClientZoneIpcData::FinishLoading { .. } => { - // tell the server we loaded into the zone, so it can start sending us acors - 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); + // tell the server we loaded into the zone, so it can start sending us acors + connection.handle.send(ToServer::ZoneLoaded(connection.id, connection.zone.as_ref().unwrap().id, common.clone())).await; + let chara_details = database.find_chara_make(connection.player_data.content_id); connection.send_inventory(false).await; @@ -941,6 +939,7 @@ async fn client_loop( FromServer::ActionComplete(request) => connection.execute_action(request, &mut lua_player).await, FromServer::ActionCancelled() => connection.cancel_action().await, FromServer::UpdateConfig(actor_id, config) => connection.update_config(actor_id, config).await, + FromServer::ActorEquip(actor_id, main_weapon_id, model_ids) => connection.update_equip(actor_id, main_weapon_id, model_ids).await, }, None => break, } diff --git a/src/world/common.rs b/src/world/common.rs index 9d1a128..741159f 100644 --- a/src/world/common.rs +++ b/src/world/common.rs @@ -42,6 +42,8 @@ pub enum FromServer { ActionCancelled(), /// Update an actor's equip display flags. UpdateConfig(u32, Config), + /// Update an actor's model IDs. + ActorEquip(u32, u64, [u32; 10]), } #[derive(Debug, Clone)] @@ -50,7 +52,6 @@ pub struct ClientHandle { pub ip: SocketAddr, pub channel: Sender, pub actor_id: u32, - pub common: CommonSpawn, } impl ClientHandle { @@ -86,7 +87,7 @@ pub enum ToServer { ClientTrigger(ClientId, u32, ClientTrigger), /// The connection loaded into a zone. // 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), + ZoneLoaded(ClientId, u16, CommonSpawn), /// The connection left a zone. LeftZone(ClientId, u32, u16), /// The connection disconnected. @@ -103,6 +104,8 @@ pub enum ToServer { ActionRequest(ClientId, u32, ActionRequest), /// We want to update our own equip display flags. Config(ClientId, u32, Config), + /// Tell the server what models IDs we have equipped. + Equip(ClientId, u32, u64, [u32; 10]), } #[derive(Clone, Debug)] diff --git a/src/world/connection.rs b/src/world/connection.rs index 26dd0c4..beac01c 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -585,35 +585,48 @@ impl ZoneConnection { // send them an appearance update if send_appearance_update { - let ipc; + let main_weapon_id; + let model_ids; { let mut game_data = self.gamedata.lock().unwrap(); let inventory = &self.player_data.inventory; - ipc = ServerZoneIpcSegment { - op_code: ServerZoneIpcType::Equip, - timestamp: timestamp_secs(), - data: ServerZoneIpcData::Equip(Equip { - main_weapon_id: inventory.get_main_weapon_id(&mut game_data), - sub_weapon_id: 0, - crest_enable: 0, - pattern_invalid: 0, - model_ids: inventory.get_model_ids(&mut game_data), - }), - ..Default::default() - }; + main_weapon_id = inventory.get_main_weapon_id(&mut game_data); + model_ids = inventory.get_model_ids(&mut game_data); } - self.send_segment(PacketSegment { - source_actor: self.player_data.actor_id, - target_actor: self.player_data.actor_id, - segment_type: SegmentType::Ipc, - data: SegmentData::Ipc { data: ipc }, - }) - .await; + self.handle + .send(ToServer::Equip( + self.id, + self.player_data.actor_id, + main_weapon_id, + model_ids, + )) + .await; } } + pub async fn update_equip(&mut self, actor_id: u32, main_weapon_id: u64, model_ids: [u32; 10]) { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::Equip, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::Equip(Equip { + main_weapon_id, + model_ids, + ..Default::default() + }), + ..Default::default() + }; + + self.send_segment(PacketSegment { + source_actor: actor_id, + target_actor: self.player_data.actor_id, + segment_type: SegmentType::Ipc, + data: SegmentData::Ipc { data: ipc }, + }) + .await; + } + pub async fn send_message(&mut self, message: &str) { let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::ServerChatMessage, diff --git a/src/world/server.rs b/src/world/server.rs index 994356a..a6c7833 100644 --- a/src/world/server.rs +++ b/src/world/server.rs @@ -62,6 +62,10 @@ impl Instance { self.actors.get(&id) } + fn find_actor_mut(&mut self, id: ObjectId) -> Option<&mut NetworkedActor> { + self.actors.get_mut(&id) + } + fn insert_npc(&mut self, id: ObjectId, spawn: NpcSpawn) { self.actors.insert(id, NetworkedActor::Npc(spawn)); } @@ -132,7 +136,7 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i data.clients .insert(handle.id, (handle, ClientState::default())); } - ToServer::ZoneLoaded(from_id, zone_id) => { + ToServer::ZoneLoaded(from_id, zone_id, common_spawn) => { let mut data = data.lock().unwrap(); // create a new instance if necessary @@ -181,7 +185,7 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i instance.actors.insert( ObjectId(client.actor_id), NetworkedActor::Player(NpcSpawn { - common: client.common.clone(), + common: common_spawn.clone(), ..Default::default() }), ); @@ -208,7 +212,7 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i spawn_index: 0, }, NpcSpawn { - common: client.common.clone(), + common: common_spawn.clone(), ..Default::default() }, ); @@ -601,6 +605,39 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i } } } + ToServer::Equip(_from_id, from_actor_id, main_weapon_id, model_ids) => { + // update their stored state so it's correctly sent on new spawns + { + let mut data = data.lock().unwrap(); + + let Some(instance) = data.find_actor_instance_mut(from_actor_id) else { + break; + }; + + let Some(actor) = instance.find_actor_mut(ObjectId(from_actor_id)) else { + break; + }; + + let NetworkedActor::Player(player) = actor else { + break; + }; + + player.common.main_weapon_model = main_weapon_id; + player.common.models = model_ids.clone(); + } + + // Inform all clients about their new equipped model ids + let mut data = data.lock().unwrap(); + for (id, (handle, _)) in &mut data.clients { + let id = *id; + + let msg = FromServer::ActorEquip(from_actor_id, main_weapon_id, model_ids); + + if handle.send(msg).is_err() { + to_remove.push(id); + } + } + } ToServer::Disconnected(from_id) => { let mut data = data.lock().unwrap();