diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 476117b..507fbf6 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -7,7 +7,7 @@ use kawari::world::ipc::{ use kawari::world::{ ChatHandler, Zone, ZoneConnection, ipc::{ - ActorControlSelf, ActorControlType, PlayerEntry, PlayerSetup, PlayerSpawn, PlayerStats, + ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSetup, PlayerSpawn, PlayerStats, Position, SocialList, }, }; @@ -171,7 +171,7 @@ async fn main() { data: ServerZoneIpcData::ActorControlSelf( ActorControlSelf { category: - ActorControlType::SetCharaGearParamUI, + ActorControlCategory::SetCharaGearParamUI, param1: 1, param2: 1, param3: 0, diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index f64560a..408d78e 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -1,10 +1,11 @@ + use crate::{ CHAR_NAME, CUSTOMIZE_DATA, INVALID_OBJECT_ID, WORLD_ID, common::timestamp_secs, packet::{PacketSegment, SegmentType}, world::ipc::{ - CommonSpawn, NpcSpawn, ObjectKind, PlayerSpawn, ServerZoneIpcData, ServerZoneIpcSegment, - ServerZoneIpcType, + ActorControl, ActorControlCategory, CommonSpawn, NpcSpawn, ObjectKind, PlayerSpawn, + ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, StatusEffectList, }, }; @@ -37,6 +38,35 @@ impl ChatHandler { "!spawnactor" => { tracing::info!("Spawning actor..."); + // status effect + { + let ipc = ServerZoneIpcSegment { + unk1: 20, + unk2: 0, + op_code: ServerZoneIpcType::StatusEffectList, + server_id: 0, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::StatusEffectList(StatusEffectList { + classjob_id: 3, + level: 10, + unk1: 10, + curr_hp: 241, + max_hp: 241, + curr_mp: 10000, + max_mp: 10000, + ..Default::default() + }), + }; + + connection + .send_segment(PacketSegment { + source_actor: 0x106ad804, + target_actor: connection.player_id, + segment_type: SegmentType::Ipc { data: ipc }, + }) + .await; + } + // send player spawn { let ipc = ServerZoneIpcSegment { @@ -63,6 +93,7 @@ impl ChatHandler { spawn_index: connection.get_free_spawn_index(), look: CUSTOMIZE_DATA, fc_tag: "LOCAL".to_string(), + subtype: 4, models: [ 0, // head 89, // body @@ -90,6 +121,29 @@ impl ChatHandler { }) .await; } + + // zone in + { + let ipc = ServerZoneIpcSegment { + unk1: 20, + unk2: 0, + op_code: ServerZoneIpcType::ActorControl, + server_id: 0, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ActorControl(ActorControl { + category: ActorControlCategory::ZoneIn, + ..Default::default() + }), + }; + + connection + .send_segment(PacketSegment { + source_actor: 0x106ad804, + target_actor: connection.player_id, + segment_type: SegmentType::Ipc { data: ipc }, + }) + .await; + } } "!spawnnpc" => { // spawn another one of us diff --git a/src/world/ipc/actor_control.rs b/src/world/ipc/actor_control.rs new file mode 100644 index 0000000..d234aaa --- /dev/null +++ b/src/world/ipc/actor_control.rs @@ -0,0 +1,23 @@ +use binrw::binrw; + +// See https://github.com/awgil/ffxiv_reverse/blob/f35b6226c1478234ca2b7149f82d251cffca2f56/vnetlog/vnetlog/ServerIPC.cs#L266 for a REALLY useful list of known values +#[binrw] +#[derive(Debug, Eq, PartialEq, Clone, Default)] +#[brw(repr = u16)] +pub enum ActorControlCategory { + #[default] + ZoneIn = 0xC8, + SetCharaGearParamUI = 0x260, +} + +#[binrw] +#[derive(Debug, Clone, Default)] +pub struct ActorControl { + #[brw(pad_after = 2)] + pub category: ActorControlCategory, + pub param1: u32, + pub param2: u32, + pub param3: u32, + #[brw(pad_after = 4)] // maybe not always empty? + pub param4: u32, +} diff --git a/src/world/ipc/actor_control_self.rs b/src/world/ipc/actor_control_self.rs index 5ecc8d7..62a73ce 100644 --- a/src/world/ipc/actor_control_self.rs +++ b/src/world/ipc/actor_control_self.rs @@ -1,18 +1,12 @@ use binrw::binrw; -#[binrw] -#[derive(Debug, Eq, PartialEq, Clone, Default)] -#[brw(repr = u16)] -pub enum ActorControlType { - #[default] - SetCharaGearParamUI = 0x260, -} +use super::ActorControlCategory; #[binrw] #[derive(Debug, Clone, Default)] pub struct ActorControlSelf { #[brw(pad_after = 2)] - pub category: ActorControlType, + pub category: ActorControlCategory, pub param1: u32, pub param2: u32, pub param3: u32, diff --git a/src/world/ipc/mod.rs b/src/world/ipc/mod.rs index bf13d7d..13d9ece 100644 --- a/src/world/ipc/mod.rs +++ b/src/world/ipc/mod.rs @@ -28,7 +28,9 @@ pub use player_stats::PlayerStats; mod actor_control_self; pub use actor_control_self::ActorControlSelf; -pub use actor_control_self::ActorControlType; + +mod actor_control; +pub use actor_control::{ActorControl, ActorControlCategory}; mod init_zone; pub use init_zone::InitZone; @@ -39,6 +41,9 @@ pub use npc_spawn::NpcSpawn; mod common_spawn; pub use common_spawn::{CharacterMode, CommonSpawn, ObjectKind}; +mod status_effect_list; +pub use status_effect_list::StatusEffectList; + use crate::common::read_string; use crate::common::write_string; use crate::packet::IpcSegment; @@ -92,6 +97,7 @@ impl ReadWriteIpcSegment for ServerZoneIpcSegment { ServerZoneIpcType::SocialList => 1136, ServerZoneIpcType::PrepareZoning => 16, ServerZoneIpcType::NpcSpawn => 648, + ServerZoneIpcType::StatusEffectList => 384, } } } @@ -171,7 +177,7 @@ pub enum ServerZoneIpcType { // Sent by the server before init zone??? Unk16 = 0x3AB, // Sent by the server - ActorControl = 0x1B9, + ActorControl = 0x38E, // Sent by the server ActorMove = 0x3D8, // Sent by the server @@ -180,6 +186,8 @@ pub enum ServerZoneIpcType { SocialList = 0x36C, // Sent by the server to spawn an NPC NpcSpawn = 0x100, + // Sent by the server to update an actor's status effect list + StatusEffectList = 0xBB, } #[binrw] @@ -283,10 +291,7 @@ pub enum ServerZoneIpcData { Unk16 { unk: [u8; 136], }, - ActorControl { - #[brw(pad_after = 20)] // empty - unk: u32, - }, + ActorControl(ActorControl), ActorMove { #[brw(pad_after = 4)] // empty pos: Position, @@ -296,6 +301,7 @@ pub enum ServerZoneIpcData { }, SocialList(SocialList), NpcSpawn(NpcSpawn), + StatusEffectList(StatusEffectList), } #[binrw] @@ -442,6 +448,14 @@ mod tests { ServerZoneIpcType::NpcSpawn, ServerZoneIpcData::NpcSpawn(NpcSpawn::default()), ), + ( + ServerZoneIpcType::ActorControl, + ServerZoneIpcData::ActorControl(ActorControl::default()), + ), + ( + ServerZoneIpcType::StatusEffectList, + ServerZoneIpcData::StatusEffectList(StatusEffectList::default()), + ), ]; for (opcode, data) in &ipc_types { diff --git a/src/world/ipc/npc_spawn.rs b/src/world/ipc/npc_spawn.rs index 6afb498..f6a2fc5 100644 --- a/src/world/ipc/npc_spawn.rs +++ b/src/world/ipc/npc_spawn.rs @@ -1,10 +1,7 @@ use binrw::binrw; -use crate::CHAR_NAME_MAX_LENGTH; -use crate::common::{CustomizeData, read_string, write_string}; -use super::position::Position; -use super::{CharacterMode, CommonSpawn, ObjectKind, StatusEffect}; +use super::CommonSpawn; #[binrw] #[brw(little)] diff --git a/src/world/ipc/player_spawn.rs b/src/world/ipc/player_spawn.rs index f149f80..94199cb 100644 --- a/src/world/ipc/player_spawn.rs +++ b/src/world/ipc/player_spawn.rs @@ -1,11 +1,7 @@ use binrw::binrw; -use crate::CHAR_NAME_MAX_LENGTH; -use crate::common::{CustomizeData, read_string, write_string}; -use super::position::Position; -use super::status_effect::StatusEffect; -use super::{CommonSpawn, ObjectKind}; +use super::CommonSpawn; #[binrw] #[brw(little)] @@ -63,5 +59,6 @@ mod tests { assert_eq!(player_spawn.common.subtype, 4); assert_eq!(player_spawn.common.model_chara, 0); assert_eq!(player_spawn.common.object_kind, ObjectKind::Player); + assert_eq!(player_spawn.common.display_flags, 262144); } } diff --git a/src/world/ipc/status_effect_list.rs b/src/world/ipc/status_effect_list.rs new file mode 100644 index 0000000..4c6a132 --- /dev/null +++ b/src/world/ipc/status_effect_list.rs @@ -0,0 +1,20 @@ +use binrw::binrw; + +use super::StatusEffect; + +#[binrw] +#[derive(Debug, Clone, Copy, Default)] +pub struct StatusEffectList { + pub classjob_id: u8, + pub level: u8, + pub unk1: u8, + pub unk2: u8, + pub curr_hp: u32, + pub max_hp: u32, + pub curr_mp: u16, + pub max_mp: u16, + pub shield: u16, + pub unk3: u16, + pub statues: [StatusEffect; 30], + pub unk4: u32, +}