1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-19 22:36:49 +00:00

Add opcode for despawning actors, send that when changing zones

This is to workaround a bigger bug where I don't properly enclose actors
in their zone, so you can hit an assert while traveling between zones.
But this is something that has been needed anyway, and also fixes that.
This commit is contained in:
Joshua Goins 2025-04-14 16:03:57 -04:00
parent 55340f4e8c
commit 10c1369119
6 changed files with 86 additions and 20 deletions

View file

@ -154,6 +154,11 @@
"name": "Equip",
"opcode": 468,
"size": 72
},
{
"name": "ActorFreeSpawn",
"opcode": 144,
"size": 8
}
],
"ClientZoneIpcType": [

View file

@ -65,8 +65,14 @@ async fn main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::io::Error> {
if id == from_id {
// send existing player data
for (id, common) in &data.actors {
let msg =
FromServer::ActorSpawn(Actor { id: *id, hp: 100 }, common.clone());
let msg = FromServer::ActorSpawn(
Actor {
id: *id,
hp: 100,
spawn_index: 0,
},
common.clone(),
);
handle.send(msg).unwrap();
}
@ -107,6 +113,23 @@ async fn main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::io::Error> {
}
}
}
ToServer::ActorDespawned(from_id, actor_id) => {
data.actors.remove(&ObjectId(actor_id));
for (id, handle) in &mut data.clients {
let id = *id;
if id == from_id {
continue;
}
let msg = FromServer::ActorDespawn(actor_id);
if handle.send(msg).is_err() {
to_remove.push(id);
}
}
}
ToServer::ActorMoved(from_id, actor_id, position, rotation) => {
for (id, handle) in &mut data.clients {
let id = *id;
@ -453,7 +476,7 @@ async fn client_loop(
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 }, connection.get_player_common_spawn(None, None))).await;
connection.handle.send(ToServer::ActorSpawned(connection.id, Actor { id: ObjectId(connection.player_data.actor_id), hp: 100, spawn_index: 0 }, connection.get_player_common_spawn(None, None))).await;
}
ClientZoneIpcData::Unk1 {
category, param1, ..
@ -713,7 +736,7 @@ async fn client_loop(
.copy_from_slice(&effects_builder.effects);
if let Some(actor) =
connection.get_actor(request.target.object_id)
connection.get_actor_mut(request.target.object_id)
{
for effect in &effects_builder.effects {
match effect.kind {
@ -1014,10 +1037,9 @@ async fn client_loop(
msg = internal_recv.recv() => match msg {
Some(msg) => match msg {
FromServer::Message(msg)=> connection.send_message(&msg).await,
FromServer::ActorSpawn(actor, common) => {
connection.spawn_actor(actor, common).await
},
FromServer::ActorSpawn(actor, common) => connection.spawn_actor(actor, common).await,
FromServer::ActorMove(actor_id, position, rotation) => connection.set_actor_position(actor_id, position, rotation).await,
FromServer::ActorDespawn(actor_id) => connection.remove_actor(actor_id).await
},
None => break,
}

View file

@ -4,4 +4,5 @@ use crate::common::ObjectId;
pub struct Actor {
pub id: ObjectId,
pub hp: u32,
pub spawn_index: u32, // TODO: local to each connection, terrible place to put this
}

View file

@ -203,13 +203,6 @@ impl ChatHandler {
.await;
}
"!spawnmonster" => {
let actor = Actor {
id: ObjectId(0x106ad804),
hp: 100,
};
connection.add_actor(actor);
// spawn a tiny mandragora
{
let ipc = ServerZoneIpcSegment {

View file

@ -60,6 +60,8 @@ pub enum FromServer {
ActorSpawn(Actor, CommonSpawn),
/// An actor moved to a new position.
ActorMove(u32, Position, f32),
// An actor has despawned.
ActorDespawn(u32),
}
#[derive(Debug, Clone)]
@ -98,6 +100,7 @@ pub enum ToServer {
Message(ClientId, String),
ActorSpawned(ClientId, Actor, CommonSpawn),
ActorMoved(ClientId, u32, Position, f32),
ActorDespawned(ClientId, u32),
ZoneLoaded(ClientId),
Disconnected(ClientId),
FatalError(std::io::Error),
@ -310,11 +313,12 @@ impl ZoneConnection {
.await;
}
pub async fn spawn_actor(&mut self, actor: Actor, mut common: CommonSpawn) {
pub async fn spawn_actor(&mut self, mut actor: Actor, mut common: CommonSpawn) {
// There is no reason for us to spawn our own player again. It's probably a bug!'
assert!(actor.id.0 != self.player_data.actor_id);
common.spawn_index = self.get_free_spawn_index();
actor.spawn_index = self.get_free_spawn_index() as u32;
common.spawn_index = actor.spawn_index as u8;
let ipc = ServerZoneIpcSegment {
unk1: 20,
@ -334,6 +338,38 @@ impl ZoneConnection {
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
self.actors.push(actor);
}
pub async fn remove_actor(&mut self, actor_id: u32) {
if let Some(actor) = self.get_actor(ObjectId(actor_id)).cloned() {
let ipc = ServerZoneIpcSegment {
unk1: 20,
unk2: 0,
op_code: ServerZoneIpcType::ActorFreeSpawn,
server_id: 0,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ActorFreeSpawn {
spawn_index: actor.spawn_index,
actor_id,
},
};
self.send_segment(PacketSegment {
source_actor: actor.id.0,
target_actor: self.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
self.actors.remove(
self.actors
.iter()
.position(|actor| actor.id == ObjectId(actor_id))
.unwrap(),
);
}
}
pub async fn update_class_info(&mut self) {
@ -359,6 +395,13 @@ 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;
{
let mut game_data = self.gamedata.lock().unwrap();
self.zone = Some(Zone::load(&mut game_data.game_data, new_zone_id));
@ -708,12 +751,12 @@ impl ZoneConnection {
.await;
}
pub fn add_actor(&mut self, actor: Actor) {
self.actors.push(actor);
pub fn get_actor_mut(&mut self, id: ObjectId) -> Option<&mut Actor> {
self.actors.iter_mut().find(|actor| actor.id == id)
}
pub fn get_actor(&mut self, id: ObjectId) -> Option<&mut Actor> {
self.actors.iter_mut().find(|actor| actor.id == id)
pub fn get_actor(&mut self, id: ObjectId) -> Option<&Actor> {
self.actors.iter().find(|actor| actor.id == id)
}
pub async fn actor_control_self(&mut self, actor_control: ActorControlSelf) {

View file

@ -214,6 +214,8 @@ pub enum ServerZoneIpcData {
ActionResult(ActionResult),
/// Sent to to the client to update their appearance
Equip(Equip),
/// Sent to the client to free up a spawn index
ActorFreeSpawn { spawn_index: u32, actor_id: u32 },
}
#[binrw]