From 37dbef99db89091a47b8875757732086ad19c641 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 23 Mar 2025 12:16:15 -0400 Subject: [PATCH] More CommonSpawn, PlayerSpawn and NPCSpawn struct improvements This is just a barrage of random stuff I needed to fix, along with finally figuring out what subkinds are. --- src/bin/kawari-world.rs | 16 +++--- src/world/chat_handler.rs | 19 ++++---- src/world/ipc/common_spawn.rs | 92 ++++++++++++++++++++++------------- src/world/ipc/mod.rs | 5 +- src/world/ipc/npc_spawn.rs | 35 ++++++++++--- src/world/ipc/player_spawn.rs | 36 ++++++++++---- 6 files changed, 134 insertions(+), 69 deletions(-) diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index ca630cc..814be40 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -12,7 +12,7 @@ use kawari::packet::{ }; use kawari::world::ipc::{ ClientZoneIpcData, CommonSpawn, GameMasterCommandType, GameMasterRank, ObjectKind, - OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, + OnlineStatus, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, SocialListRequestType, StatusEffect, }; use kawari::world::{ @@ -326,22 +326,24 @@ async fn main() { op_code: ServerZoneIpcType::PlayerSpawn, timestamp: timestamp_secs(), data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn { + account_id: connection.player_data.account_id, content_id: connection.player_data.content_id, + current_world_id: config.world.world_id, + home_world_id: config.world.world_id, + gm_rank: GameMasterRank::Debug, + online_status: OnlineStatus::GameMasterBlue, common: CommonSpawn { - current_world_id: config.world.world_id, - home_world_id: config.world.world_id, class_job: 35, name: chara_details.name, hp_curr: 100, hp_max: 100, mp_curr: 100, mp_max: 100, - object_kind: ObjectKind::Player, - gm_rank: GameMasterRank::Debug, - online_status: OnlineStatus::GameMasterBlue, + object_kind: ObjectKind::Player( + PlayerSubKind::Player, + ), look: chara_details.chara_make.customize, fc_tag: "LOCAL".to_string(), - subtype: 4, models: [ 0, // head 89, // body diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index 373f849..7ae833e 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -3,8 +3,9 @@ use crate::{ config::get_config, packet::{PacketSegment, SegmentType}, world::ipc::{ - ActorControl, ActorControlCategory, CommonSpawn, NpcSpawn, ObjectKind, PlayerSpawn, - ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, StatusEffectList, + ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, NpcSpawn, ObjectKind, + PlayerSpawn, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, + StatusEffectList, }, }; @@ -103,22 +104,21 @@ impl ChatHandler { server_id: 0, timestamp: timestamp_secs(), data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn { - some_unique_id: 1, + account_id: 1, content_id: 1, + current_world_id: config.world.world_id, + home_world_id: config.world.world_id, common: CommonSpawn { - current_world_id: config.world.world_id, - home_world_id: config.world.world_id, class_job: 35, name: "Test Actor".to_string(), hp_curr: 100, hp_max: 100, mp_curr: 100, mp_max: 100, - object_kind: ObjectKind::Player, + object_kind: ObjectKind::Player(PlayerSubKind::Player), spawn_index: connection.get_free_spawn_index(), look: CUSTOMIZE_DATA, fc_tag: "LOCAL".to_string(), - subtype: 4, models: [ 0, // head 89, // body @@ -189,7 +189,7 @@ impl ChatHandler { spawn_index: connection.get_free_spawn_index(), bnpc_base: 13498, bnpc_name: 10261, - object_kind: ObjectKind::BattleNpc, + object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy), target_id: ObjectTypeId { object_id: ObjectId(connection.player_data.actor_id), object_type: 0, @@ -233,6 +233,7 @@ impl ChatHandler { server_id: 0, timestamp: timestamp_secs(), data: ServerZoneIpcData::NpcSpawn(NpcSpawn { + aggression_mode: 1, common: CommonSpawn { hp_curr: 91, hp_max: 91, @@ -241,7 +242,7 @@ impl ChatHandler { spawn_index: connection.get_free_spawn_index(), bnpc_base: 13498, // TODO: changing this prevents it from spawning... bnpc_name: 405, - object_kind: ObjectKind::BattleNpc, + object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy), level: 1, battalion: 4, model_chara: 297, diff --git a/src/world/ipc/common_spawn.rs b/src/world/ipc/common_spawn.rs index 1e58ecd..5764c20 100644 --- a/src/world/ipc/common_spawn.rs +++ b/src/world/ipc/common_spawn.rs @@ -10,25 +10,63 @@ use super::StatusEffect; #[binrw] #[brw(repr = u8)] #[derive(Clone, PartialEq, Debug, Default)] -pub enum ObjectKind { +pub enum PlayerSubKind { + #[default] + Player = 4, +} + +// See https://github.com/Caraxi/Dalamud/blob/e6017f96c09b8cde20e02371914ec25cfa989ef7/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs#L6 +#[binrw] +#[brw(repr = u8)] +#[derive(Clone, PartialEq, Debug, Default)] +pub enum BattleNpcSubKind { #[default] None = 0, - Player = 1, - BattleNpc = 2, - EventNpc = 3, - Treasure = 4, - Aetheryte = 5, - GatheringPoint = 6, - EventObj = 7, - Mount = 8, - Companion = 9, - Retainer = 10, - AreaObject = 11, - HousingEventObject = 12, - Cutscene = 13, - MjiObject = 14, - Ornament = 15, - CardStand = 16, + Part = 1, + Pet = 2, + Chocobo = 3, + Enemy = 5, + NpcPartyMember = 9, +} + +#[binrw] +#[derive(Clone, PartialEq, Debug, Default)] +pub enum ObjectKind { + #[default] + #[brw(magic = 0u8)] + None, + #[brw(magic = 1u8)] + Player(PlayerSubKind), + #[brw(magic = 2u8)] + BattleNpc(BattleNpcSubKind), + #[brw(magic = 3u8)] + EventNpc, + #[brw(magic = 4u8)] + Treasure, + #[brw(magic = 5u8)] + Aetheryte, + #[brw(magic = 6u8)] + GatheringPoint, + #[brw(magic = 7u8)] + EventObj, + #[brw(magic = 8u8)] + Mount, + #[brw(magic = 9u8)] + Companion, + #[brw(magic = 10u8)] + Retainer, + #[brw(magic = 11u8)] + AreaObject, + #[brw(magic = 12u8)] + HousingEventObject, + #[brw(magic = 13u8)] + Cutscene, + #[brw(magic = 14u8)] + MjiObject, + #[brw(magic = 15u8)] + Ornament, + #[brw(magic = 16u8)] + CardStand, } #[binrw] @@ -52,7 +90,7 @@ pub enum OnlineStatus { GameQA = 1, GameMaster = 2, GameMasterBlue = 3, - EventParticipant = 4, // used by NPCs? + EventParticipant = 4, #[default] Online = 47, } @@ -78,22 +116,6 @@ pub enum GameMasterRank { #[brw(little)] #[derive(Debug, Clone, Default)] pub struct CommonSpawn { - /// See Title Excel sheet - pub title_id: u16, - pub u1b: u16, - pub current_world_id: u16, - pub home_world_id: u16, - - pub gm_rank: GameMasterRank, - pub u3c: u8, - pub u4: u8, - pub online_status: OnlineStatus, - - pub pose: u8, - pub u5a: u8, - pub u5b: u8, - pub u5c: u8, - pub target_id: ObjectTypeId, pub u6: u32, pub u7: u32, @@ -133,8 +155,8 @@ pub struct CommonSpawn { /// Argument used in CharacterMode. // TODO: move to enum pub persistent_emote: u8, + #[brw(pad_size_to = 2)] // for kinds that don't have a param pub object_kind: ObjectKind, - pub subtype: u8, pub voice: u8, pub unk27: u8, /// See Battalion Excel sheet. Used for determing whether it's friendy or an enemy. diff --git a/src/world/ipc/mod.rs b/src/world/ipc/mod.rs index 0d73e1f..1d6b681 100644 --- a/src/world/ipc/mod.rs +++ b/src/world/ipc/mod.rs @@ -36,7 +36,10 @@ mod npc_spawn; pub use npc_spawn::NpcSpawn; mod common_spawn; -pub use common_spawn::{CharacterMode, CommonSpawn, GameMasterRank, ObjectKind, OnlineStatus}; +pub use common_spawn::{ + BattleNpcSubKind, CharacterMode, CommonSpawn, GameMasterRank, ObjectKind, OnlineStatus, + PlayerSubKind, +}; mod status_effect_list; pub use status_effect_list::StatusEffectList; diff --git a/src/world/ipc/npc_spawn.rs b/src/world/ipc/npc_spawn.rs index 3c8994d..ec72a32 100644 --- a/src/world/ipc/npc_spawn.rs +++ b/src/world/ipc/npc_spawn.rs @@ -1,11 +1,24 @@ use binrw::binrw; -use super::CommonSpawn; +use super::{CommonSpawn, GameMasterRank, OnlineStatus}; #[binrw] #[brw(little)] #[derive(Debug, Clone, Default)] pub struct NpcSpawn { + pub gimmick_id: u32, + pub u1b: u8, + pub u2b: u8, + pub gm_rank: GameMasterRank, // lol really? what does an NPC need GM rank privileges for? + pub u3b: u8, + + pub aggression_mode: u8, + pub online_status: OnlineStatus, + pub u5a: u8, + pub pose: u8, + + pub u5b: u32, + pub common: CommonSpawn, pub padding: [u8; 10], } @@ -18,7 +31,7 @@ mod tests { use crate::{ common::INVALID_OBJECT_ID, - world::ipc::{CharacterMode, ObjectKind, OnlineStatus}, + world::ipc::{BattleNpcSubKind, CharacterMode, ObjectKind, OnlineStatus}, }; use super::*; @@ -45,10 +58,13 @@ mod tests { assert_eq!(npc_spawn.common.bnpc_name, 10261); assert_eq!(npc_spawn.common.spawn_index, 56); assert_eq!(npc_spawn.common.mode, CharacterMode::Normal); - assert_eq!(npc_spawn.common.object_kind, ObjectKind::BattleNpc); - assert_eq!(npc_spawn.common.subtype, 2); + assert_eq!( + npc_spawn.common.object_kind, + ObjectKind::BattleNpc(BattleNpcSubKind::Pet) + ); assert_eq!(npc_spawn.common.battalion, 0); - assert_eq!(npc_spawn.common.online_status, OnlineStatus::Offline); // TODO: why is this guy offline? + assert_eq!(npc_spawn.aggression_mode, 1); // passive + assert_eq!(npc_spawn.online_status, OnlineStatus::Offline); } #[test] @@ -73,11 +89,14 @@ mod tests { assert_eq!(npc_spawn.common.bnpc_name, 405); assert_eq!(npc_spawn.common.spawn_index, 14); assert_eq!(npc_spawn.common.mode, CharacterMode::Normal); - assert_eq!(npc_spawn.common.object_kind, ObjectKind::BattleNpc); - assert_eq!(npc_spawn.common.subtype, 5); + assert_eq!( + npc_spawn.common.object_kind, + ObjectKind::BattleNpc(BattleNpcSubKind::Enemy) + ); assert_eq!(npc_spawn.common.battalion, 4); assert_eq!(npc_spawn.common.parent_actor_id, INVALID_OBJECT_ID); assert_eq!(npc_spawn.common.spawner_id, INVALID_OBJECT_ID); - assert_eq!(npc_spawn.common.online_status, OnlineStatus::EventParticipant); + assert_eq!(npc_spawn.aggression_mode, 1); // passive + assert_eq!(npc_spawn.online_status, OnlineStatus::Offline); } } diff --git a/src/world/ipc/player_spawn.rs b/src/world/ipc/player_spawn.rs index 146c77d..2489f24 100644 --- a/src/world/ipc/player_spawn.rs +++ b/src/world/ipc/player_spawn.rs @@ -1,17 +1,33 @@ use binrw::binrw; -use super::CommonSpawn; +use super::{CommonSpawn, GameMasterRank, OnlineStatus}; #[binrw] #[brw(little)] #[derive(Debug, Clone, Default)] pub struct PlayerSpawn { - // also shows up in the friends list. - pub some_unique_id: u32, + // yes, really. + pub account_id: u32, #[brw(pad_before = 4)] // always empty? pub content_id: u64, + /// See Title Excel sheet + pub title_id: u16, + pub u1b: u16, + pub current_world_id: u16, + pub home_world_id: u16, + + pub gm_rank: GameMasterRank, + pub u3c: u8, + pub u4: u8, + pub online_status: OnlineStatus, + + pub pose: u8, + pub u5a: u8, + pub u5b: u8, + pub u5c: u8, + pub common: CommonSpawn, pub padding: [u8; 2], @@ -23,7 +39,7 @@ mod tests { use binrw::BinRead; - use crate::world::ipc::{CharacterMode, ObjectKind, OnlineStatus}; + use crate::world::ipc::{CharacterMode, ObjectKind, OnlineStatus, PlayerSubKind}; use super::*; @@ -36,8 +52,8 @@ mod tests { let mut buffer = Cursor::new(&buffer); let player_spawn = PlayerSpawn::read_le(&mut buffer).unwrap(); - assert_eq!(player_spawn.common.current_world_id, 0x4F); - assert_eq!(player_spawn.common.home_world_id, 0x4F); + assert_eq!(player_spawn.current_world_id, 0x4F); + assert_eq!(player_spawn.home_world_id, 0x4F); assert_eq!(player_spawn.common.hp_curr, 159); assert_eq!(player_spawn.common.hp_max, 159); assert_eq!(player_spawn.common.mp_curr, 10000); @@ -55,10 +71,12 @@ mod tests { assert_eq!(player_spawn.common.look.gender, 1); assert_eq!(player_spawn.common.look.bust, 100); assert_eq!(player_spawn.common.fc_tag, ""); - 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.object_kind, + ObjectKind::Player(PlayerSubKind::Player) + ); assert_eq!(player_spawn.common.display_flags, 262144); - assert_eq!(player_spawn.common.online_status, OnlineStatus::Offline); + assert_eq!(player_spawn.online_status, OnlineStatus::Offline); } }