1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-20 14:47:45 +00:00

Replicate actor spawning and movement to other clients

This now works and Kawari has achieved basic multiplayer! This is of course with
a hundred caveats:
* Previously spawned players are not backfilled
* There is no range or zone detection
* They are carbuncle clones of you, not the other character's data

But it actually WORKS!
This commit is contained in:
Joshua Goins 2025-03-30 17:49:45 -04:00
parent 9cb7cd9abd
commit ae1ca2f92d
8 changed files with 80 additions and 47 deletions

View file

@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType};
use kawari::common::{GameData, timestamp_secs};
use kawari::common::{GameData, ObjectId, timestamp_secs};
use kawari::common::{Position, determine_initial_starting_zone};
use kawari::config::get_config;
use kawari::lobby::CharaMake;
@ -488,6 +488,9 @@ async fn client_loop(
// wipe any exit position so it isn't accidentally reused
exit_position = None;
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 })).await;
}
ClientZoneIpcData::Unk1 {
category, param1, ..
@ -608,6 +611,8 @@ async fn client_loop(
connection.player_data.rotation = *rotation;
connection.player_data.position = *position;
connection.handle.send(ToServer::ActorMoved(connection.id, connection.player_data.actor_id, *position)).await;
}
ClientZoneIpcData::LogOut { .. } => {
tracing::info!("Recieved log out from client!");
@ -768,6 +773,7 @@ async fn client_loop(
EffectKind::Damage => {
actor.hp -= effect.value as u32;
}
_ => todo!()
}
}

View file

@ -77,6 +77,26 @@ pub(crate) fn write_quantized_rotation(quantized: &f32) -> u16 {
((quantized + pi / (2.0 * pi)) * max) as u16
}
pub(crate) fn read_packed_float(packed: u16) -> f32 {
todo!()
}
pub(crate) fn write_packed_float(float: f32) -> u16 {
(((float + 1000.0) * 100.0) * 0.32767501) as u16
}
pub(crate) fn read_packed_position(packed: [u16; 3]) -> Position {
todo!()
}
pub(crate) fn write_packed_position(pos: &Position) -> [u16; 3] {
[
write_packed_float(pos.x),
write_packed_float(pos.y),
write_packed_float(pos.z),
]
}
/// Get the number of seconds since UNIX epoch.
pub fn timestamp_secs() -> u32 {
SystemTime::now()

View file

@ -154,32 +154,6 @@ impl ChatHandler {
})
.await;
}
// move
{
let ipc = ServerZoneIpcSegment {
unk1: 20,
unk2: 0,
op_code: ServerZoneIpcType::ActorMove,
server_id: 0,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ActorMove {
pos: Position {
x: 1.0,
y: 0.0,
z: 1.0,
},
},
};
connection
.send_segment(PacketSegment {
source_actor: 0x106ad804,
target_actor: connection.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
}
"!spawnnpc" => {
// spawn another one of us

View file

@ -21,9 +21,10 @@ use super::{
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
chat_handler::CUSTOMIZE_DATA,
ipc::{
ActorControlSelf, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment, CommonSpawn,
ContainerInfo, ContainerType, InitZone, ItemInfo, NpcSpawn, ObjectKind, ServerZoneIpcData,
ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, WeatherChange,
ActorControlSelf, ActorMove, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment,
CommonSpawn, ContainerInfo, ContainerType, InitZone, ItemInfo, NpcSpawn, ObjectKind,
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo,
WeatherChange,
},
};
@ -258,7 +259,11 @@ impl ZoneConnection {
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ActorMove,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ActorMove { pos: position },
data: ServerZoneIpcData::ActorMove(ActorMove {
speed: 24,
position,
..Default::default()
}),
..Default::default()
};

View file

@ -7,7 +7,9 @@ use crate::common::{ObjectTypeId, read_quantized_rotation, write_quantized_rotat
#[brw(repr = u8)]
pub enum EffectKind {
#[default]
Miss = 0, // FIXME: is this correct?
Damage = 3,
BeginCombo = 27,
}
#[binrw]
@ -80,7 +82,7 @@ mod tests {
assert_eq!(action_result.effect_count, 1);
// effect 0: attack
assert_eq!(action_result.effects[0].action_type, 3);
assert_eq!(action_result.effects[0].kind, EffectKind::Damage);
assert_eq!(action_result.effects[0].param0, 0);
assert_eq!(action_result.effects[0].param1, 113);
assert_eq!(action_result.effects[0].param2, 0);
@ -89,6 +91,6 @@ mod tests {
assert_eq!(action_result.effects[0].value, 22);
// effect 1: start action combo
assert_eq!(action_result.effects[1].action_type, 27);
assert_eq!(action_result.effects[1].kind, EffectKind::BeginCombo);
}
}

View file

@ -0,0 +1,17 @@
use binrw::binrw;
use crate::common::{Position, read_packed_position, write_packed_position};
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct ActorMove {
pub dir: u8,
pub dir_before_slip: u8,
pub flag1: u8,
pub flat2: u8,
pub speed: u8,
#[brw(pad_before = 1, pad_after = 4)] // empty
#[br(map = read_packed_position)]
#[bw(map = write_packed_position)]
pub position: Position,
}

View file

@ -0,0 +1,12 @@
use binrw::binrw;
use crate::common::Position;
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct ActorSetPos {
pub unk: u32,
pub layer_id: u32,
pub position: Position,
pub unk3: u32,
}

View file

@ -62,6 +62,12 @@ pub use event_start::EventStart;
mod action_result;
pub use action_result::{ActionEffect, ActionResult, EffectKind};
mod actor_move;
pub use actor_move::ActorMove;
mod actor_set_pos;
pub use actor_set_pos::ActorSetPos;
use crate::common::Position;
use crate::common::read_string;
use crate::common::write_string;
@ -116,16 +122,6 @@ impl Default for ServerZoneIpcSegment {
}
}
// TODO: move to their own files
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct ActorSetPos {
pub unk: u32,
pub layer_id: u32,
pub position: Position,
pub unk3: u32,
}
#[binrw]
#[brw(repr = u8)]
#[derive(Clone, PartialEq, Debug)]
@ -197,10 +193,7 @@ pub enum ServerZoneIpcData {
/// Sent by the server
ActorControl(ActorControl),
/// Sent by the server
ActorMove {
#[brw(pad_after = 4)] // empty
pos: Position,
},
ActorMove(ActorMove),
/// Sent by the server
Unk17 { unk: [u8; 104] },
/// Sent by the server in response to SocialListRequest
@ -450,6 +443,10 @@ mod tests {
ServerZoneIpcType::ActionResult,
ServerZoneIpcData::ActionResult(ActionResult::default()),
),
(
ServerZoneIpcType::ActorMove,
ServerZoneIpcData::ActorMove(ActorMove::default()),
),
];
for (opcode, data) in &ipc_types {