1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-23 23:57:46 +00:00

ActorControl and ActorMove packet fixes, add support for //gm invis

I don't think this is the right kind of invisibility, but it's more of a test of
ActorControlSelf really. I have also been trying (and failing) to make the
client show another player.
This commit is contained in:
Joshua Goins 2025-03-23 15:28:53 -04:00
parent d47779c8d6
commit 90a78fcaa8
6 changed files with 133 additions and 79 deletions

View file

@ -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 { .. } => {

View file

@ -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

View file

@ -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 },
}
}
}

View file

@ -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,
}

View file

@ -93,6 +93,7 @@ pub enum OnlineStatus {
GameMaster = 2,
GameMasterBlue = 3,
EventParticipant = 4,
NewAdventurer = 32, // TODO: This is actually a flag!
#[default]
Online = 47,
}

View file

@ -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 {