diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 746a21e..6aa2b8c 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -11,13 +11,15 @@ use kawari::packet::{ send_packet, }; use kawari::world::ipc::{ - ClientZoneIpcData, CommonSpawn, DisplayFlag, GameMasterCommandType, GameMasterRank, ObjectKind, OnlineStatus, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, SocialListRequestType, StatusEffect + ClientZoneIpcData, CommonSpawn, DisplayFlag, GameMasterCommandType, GameMasterRank, ObjectKind, + OnlineStatus, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, + SocialListRequestType, StatusEffect, }; use kawari::world::{ ChatHandler, Zone, ZoneConnection, ipc::{ - ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSetup, PlayerSpawn, PlayerStats, - SocialList, + ActorControl, ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSetup, + PlayerSpawn, PlayerStats, SocialList, }, }; use kawari::world::{PlayerData, WorldDatabase}; @@ -194,13 +196,10 @@ async fn main() { data: ServerZoneIpcData::ActorControlSelf( ActorControlSelf { category: - ActorControlCategory::SetCharaGearParamUI, - param1: 1, - param2: 1, - param3: 0, - param4: 0, - param5: 0, - param6: 0, + ActorControlCategory::SetCharaGearParamUI { + unk1: 1, + unk2: 1, + } }, ), ..Default::default() @@ -571,6 +570,38 @@ async fn main() { GameMasterCommandType::ChangeTerritory => { connection.change_zone(*arg as u16).await } + GameMasterCommandType::ToggleInvisibility => { + // Control Data + { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::ActorControlSelf, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::ActorControlSelf( + ActorControlSelf { + category: + ActorControlCategory::ToggleInvisibility { + invisible: 1 + }, + }, + ), + ..Default::default() + }; + + connection + .send_segment(PacketSegment { + source_actor: connection + .player_data + .actor_id, + target_actor: connection + .player_data + .actor_id, + segment_type: SegmentType::Ipc { + data: ipc, + }, + }) + .await; + } + } } } ClientZoneIpcData::Unk12 { .. } => { diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index b2637cf..c611cb6 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -3,9 +3,9 @@ use crate::{ config::get_config, packet::{PacketSegment, SegmentType}, world::ipc::{ - ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, NpcSpawn, ObjectKind, - PlayerSpawn, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, - StatusEffectList, + ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, DisplayFlag, NpcSpawn, + ObjectKind, PlayerSpawn, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, + ServerZoneIpcType, StatusEffectList, }, }; @@ -64,35 +64,6 @@ 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_data.actor_id, - segment_type: SegmentType::Ipc { data: ipc }, - }) - .await; - } - let config = get_config(); // send player spawn @@ -104,8 +75,8 @@ impl ChatHandler { server_id: 0, timestamp: timestamp_secs(), data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn { - account_id: 1, - content_id: 1, + account_id: 1000000, + content_id: 1000000, current_world_id: config.world.world_id, home_world_id: config.world.world_id, common: CommonSpawn { @@ -120,6 +91,9 @@ impl ChatHandler { spawn_index: connection.get_free_spawn_index(), look: CUSTOMIZE_DATA, fc_tag: "LOCAL".to_string(), + display_flags: DisplayFlag::INVISIBLE + | DisplayFlag::HIDE_HEAD + | DisplayFlag::UNK, models: [ 0, // head 89, // body @@ -157,7 +131,10 @@ impl ChatHandler { server_id: 0, timestamp: timestamp_secs(), data: ServerZoneIpcData::ActorControl(ActorControl { - category: ActorControlCategory::ZoneIn, + category: ActorControlCategory::ZoneIn { + warp_finish_anim: 0x0, + raise_anim: 0x0, + }, ..Default::default() }), }; @@ -170,6 +147,32 @@ 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 diff --git a/src/world/ipc/actor_control.rs b/src/world/ipc/actor_control.rs index d234aaa..20b4d2c 100644 --- a/src/world/ipc/actor_control.rs +++ b/src/world/ipc/actor_control.rs @@ -2,22 +2,56 @@ 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)] +#[derive(Debug, Eq, PartialEq, Clone)] pub enum ActorControlCategory { - #[default] - ZoneIn = 0xC8, - SetCharaGearParamUI = 0x260, + #[brw(magic = 0x26u16)] + ToggleInvisibility { + #[brw(pad_before = 2)] + invisible: u32, // FIXME: change to bool + }, + #[brw(magic = 0xC8u16)] + ZoneIn { + #[brw(pad_before = 2)] + warp_finish_anim: u32, + raise_anim: u32, + }, + #[brw(magic = 0x260u16)] + SetCharaGearParamUI { + #[brw(pad_before = 2)] + unk1: u32, + unk2: u32, + }, } #[binrw] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ActorControl { - #[brw(pad_after = 2)] + #[brw(pad_after = 4)] + #[brw(pad_size_to = 20)] // take into account categories without params pub category: ActorControlCategory, - pub param1: u32, - pub param2: u32, - pub param3: u32, - #[brw(pad_after = 4)] // maybe not always empty? - pub param4: u32, +} + +impl Default for ActorControl { + fn default() -> Self { + Self { + category: ActorControlCategory::ToggleInvisibility { invisible: 1 }, + } + } +} + +// Has more padding than ActorControl? +#[binrw] +#[derive(Debug, Clone)] +pub struct ActorControlSelf { + #[brw(pad_after = 12)] + #[brw(pad_size_to = 20)] // take into account categories without params + pub category: ActorControlCategory, +} + +impl Default for ActorControlSelf { + fn default() -> Self { + Self { + category: ActorControlCategory::ToggleInvisibility { invisible: 1 }, + } + } } diff --git a/src/world/ipc/actor_control_self.rs b/src/world/ipc/actor_control_self.rs deleted file mode 100644 index 62a73ce..0000000 --- a/src/world/ipc/actor_control_self.rs +++ /dev/null @@ -1,17 +0,0 @@ -use binrw::binrw; - -use super::ActorControlCategory; - -#[binrw] -#[derive(Debug, Clone, Default)] -pub struct ActorControlSelf { - #[brw(pad_after = 2)] - pub category: ActorControlCategory, - pub param1: u32, - pub param2: u32, - pub param3: u32, - pub param4: u32, - pub param5: u32, - #[brw(pad_after = 4)] - pub param6: u32, -} diff --git a/src/world/ipc/common_spawn.rs b/src/world/ipc/common_spawn.rs index 5abddb4..79e5601 100644 --- a/src/world/ipc/common_spawn.rs +++ b/src/world/ipc/common_spawn.rs @@ -93,6 +93,7 @@ pub enum OnlineStatus { GameMaster = 2, GameMasterBlue = 3, EventParticipant = 4, + NewAdventurer = 32, // TODO: This is actually a flag! #[default] Online = 47, } diff --git a/src/world/ipc/mod.rs b/src/world/ipc/mod.rs index b5e2ebc..7be01af 100644 --- a/src/world/ipc/mod.rs +++ b/src/world/ipc/mod.rs @@ -23,11 +23,8 @@ pub use player_setup::PlayerSetup; mod player_stats; pub use player_stats::PlayerStats; -mod actor_control_self; -pub use actor_control_self::ActorControlSelf; - mod actor_control; -pub use actor_control::{ActorControl, ActorControlCategory}; +pub use actor_control::{ActorControl, ActorControlCategory, ActorControlSelf}; mod init_zone; pub use init_zone::InitZone; @@ -138,6 +135,7 @@ pub struct ActorSetPos { #[derive(Clone, PartialEq, Debug)] pub enum GameMasterCommandType { ChangeWeather = 0x6, + ToggleInvisibility = 0xD, ChangeTerritory = 0x58, } @@ -184,7 +182,7 @@ pub enum ServerZoneIpcType { // Sent by the server ActorControl = 0x38E, // Sent by the server - ActorMove = 0x3D8, + ActorMove = 0x31C, // Sent by the server Unk17 = 0x2A1, // Sent by the server in response to SocialListRequest @@ -475,6 +473,10 @@ mod tests { ServerZoneIpcType::WeatherChange, ServerZoneIpcData::WeatherChange(WeatherChange::default()), ), + ( + ServerZoneIpcType::ActorControl, + ServerZoneIpcData::ActorControl(ActorControl::default()), + ), ]; for (opcode, data) in &ipc_types {