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:
parent
9cb7cd9abd
commit
ae1ca2f92d
8 changed files with 80 additions and 47 deletions
|
@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use kawari::common::custom_ipc::{CustomIpcData, CustomIpcSegment, CustomIpcType};
|
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::common::{Position, determine_initial_starting_zone};
|
||||||
use kawari::config::get_config;
|
use kawari::config::get_config;
|
||||||
use kawari::lobby::CharaMake;
|
use kawari::lobby::CharaMake;
|
||||||
|
@ -488,6 +488,9 @@ async fn client_loop(
|
||||||
// wipe any exit position so it isn't accidentally reused
|
// wipe any exit position so it isn't accidentally reused
|
||||||
exit_position = None;
|
exit_position = None;
|
||||||
exit_rotation = 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 {
|
ClientZoneIpcData::Unk1 {
|
||||||
category, param1, ..
|
category, param1, ..
|
||||||
|
@ -608,6 +611,8 @@ async fn client_loop(
|
||||||
|
|
||||||
connection.player_data.rotation = *rotation;
|
connection.player_data.rotation = *rotation;
|
||||||
connection.player_data.position = *position;
|
connection.player_data.position = *position;
|
||||||
|
|
||||||
|
connection.handle.send(ToServer::ActorMoved(connection.id, connection.player_data.actor_id, *position)).await;
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::LogOut { .. } => {
|
ClientZoneIpcData::LogOut { .. } => {
|
||||||
tracing::info!("Recieved log out from client!");
|
tracing::info!("Recieved log out from client!");
|
||||||
|
@ -768,6 +773,7 @@ async fn client_loop(
|
||||||
EffectKind::Damage => {
|
EffectKind::Damage => {
|
||||||
actor.hp -= effect.value as u32;
|
actor.hp -= effect.value as u32;
|
||||||
}
|
}
|
||||||
|
_ => todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,26 @@ pub(crate) fn write_quantized_rotation(quantized: &f32) -> u16 {
|
||||||
((quantized + pi / (2.0 * pi)) * max) as 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.
|
/// Get the number of seconds since UNIX epoch.
|
||||||
pub fn timestamp_secs() -> u32 {
|
pub fn timestamp_secs() -> u32 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
|
|
|
@ -154,32 +154,6 @@ impl ChatHandler {
|
||||||
})
|
})
|
||||||
.await;
|
.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" => {
|
"!spawnnpc" => {
|
||||||
// spawn another one of us
|
// spawn another one of us
|
||||||
|
|
|
@ -21,9 +21,10 @@ use super::{
|
||||||
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
|
Actor, Event, Inventory, Item, LuaPlayer, StatusEffects, WorldDatabase, Zone,
|
||||||
chat_handler::CUSTOMIZE_DATA,
|
chat_handler::CUSTOMIZE_DATA,
|
||||||
ipc::{
|
ipc::{
|
||||||
ActorControlSelf, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment, CommonSpawn,
|
ActorControlSelf, ActorMove, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment,
|
||||||
ContainerInfo, ContainerType, InitZone, ItemInfo, NpcSpawn, ObjectKind, ServerZoneIpcData,
|
CommonSpawn, ContainerInfo, ContainerType, InitZone, ItemInfo, NpcSpawn, ObjectKind,
|
||||||
ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo, WeatherChange,
|
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo,
|
||||||
|
WeatherChange,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -258,7 +259,11 @@ impl ZoneConnection {
|
||||||
let ipc = ServerZoneIpcSegment {
|
let ipc = ServerZoneIpcSegment {
|
||||||
op_code: ServerZoneIpcType::ActorMove,
|
op_code: ServerZoneIpcType::ActorMove,
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: ServerZoneIpcData::ActorMove { pos: position },
|
data: ServerZoneIpcData::ActorMove(ActorMove {
|
||||||
|
speed: 24,
|
||||||
|
position,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ use crate::common::{ObjectTypeId, read_quantized_rotation, write_quantized_rotat
|
||||||
#[brw(repr = u8)]
|
#[brw(repr = u8)]
|
||||||
pub enum EffectKind {
|
pub enum EffectKind {
|
||||||
#[default]
|
#[default]
|
||||||
|
Miss = 0, // FIXME: is this correct?
|
||||||
Damage = 3,
|
Damage = 3,
|
||||||
|
BeginCombo = 27,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -80,7 +82,7 @@ mod tests {
|
||||||
assert_eq!(action_result.effect_count, 1);
|
assert_eq!(action_result.effect_count, 1);
|
||||||
|
|
||||||
// effect 0: attack
|
// 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].param0, 0);
|
||||||
assert_eq!(action_result.effects[0].param1, 113);
|
assert_eq!(action_result.effects[0].param1, 113);
|
||||||
assert_eq!(action_result.effects[0].param2, 0);
|
assert_eq!(action_result.effects[0].param2, 0);
|
||||||
|
@ -89,6 +91,6 @@ mod tests {
|
||||||
assert_eq!(action_result.effects[0].value, 22);
|
assert_eq!(action_result.effects[0].value, 22);
|
||||||
|
|
||||||
// effect 1: start action combo
|
// effect 1: start action combo
|
||||||
assert_eq!(action_result.effects[1].action_type, 27);
|
assert_eq!(action_result.effects[1].kind, EffectKind::BeginCombo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
src/world/ipc/actor_move.rs
Normal file
17
src/world/ipc/actor_move.rs
Normal 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,
|
||||||
|
}
|
12
src/world/ipc/actor_set_pos.rs
Normal file
12
src/world/ipc/actor_set_pos.rs
Normal 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,
|
||||||
|
}
|
|
@ -62,6 +62,12 @@ pub use event_start::EventStart;
|
||||||
mod action_result;
|
mod action_result;
|
||||||
pub use action_result::{ActionEffect, ActionResult, EffectKind};
|
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::Position;
|
||||||
use crate::common::read_string;
|
use crate::common::read_string;
|
||||||
use crate::common::write_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]
|
#[binrw]
|
||||||
#[brw(repr = u8)]
|
#[brw(repr = u8)]
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
@ -197,10 +193,7 @@ pub enum ServerZoneIpcData {
|
||||||
/// Sent by the server
|
/// Sent by the server
|
||||||
ActorControl(ActorControl),
|
ActorControl(ActorControl),
|
||||||
/// Sent by the server
|
/// Sent by the server
|
||||||
ActorMove {
|
ActorMove(ActorMove),
|
||||||
#[brw(pad_after = 4)] // empty
|
|
||||||
pos: Position,
|
|
||||||
},
|
|
||||||
/// Sent by the server
|
/// Sent by the server
|
||||||
Unk17 { unk: [u8; 104] },
|
Unk17 { unk: [u8; 104] },
|
||||||
/// Sent by the server in response to SocialListRequest
|
/// Sent by the server in response to SocialListRequest
|
||||||
|
@ -450,6 +443,10 @@ mod tests {
|
||||||
ServerZoneIpcType::ActionResult,
|
ServerZoneIpcType::ActionResult,
|
||||||
ServerZoneIpcData::ActionResult(ActionResult::default()),
|
ServerZoneIpcData::ActionResult(ActionResult::default()),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::ActorMove,
|
||||||
|
ServerZoneIpcData::ActorMove(ActorMove::default()),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (opcode, data) in &ipc_types {
|
for (opcode, data) in &ipc_types {
|
||||||
|
|
Loading…
Add table
Reference in a new issue