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

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.
This commit is contained in:
Joshua Goins 2025-03-23 12:16:15 -04:00
parent 45f92e9e54
commit 37dbef99db
6 changed files with 134 additions and 69 deletions

View file

@ -12,7 +12,7 @@ use kawari::packet::{
}; };
use kawari::world::ipc::{ use kawari::world::ipc::{
ClientZoneIpcData, CommonSpawn, GameMasterCommandType, GameMasterRank, ObjectKind, ClientZoneIpcData, CommonSpawn, GameMasterCommandType, GameMasterRank, ObjectKind,
OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, OnlineStatus, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType,
SocialListRequestType, StatusEffect, SocialListRequestType, StatusEffect,
}; };
use kawari::world::{ use kawari::world::{
@ -326,22 +326,24 @@ async fn main() {
op_code: ServerZoneIpcType::PlayerSpawn, op_code: ServerZoneIpcType::PlayerSpawn,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn { data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
account_id: connection.player_data.account_id,
content_id: connection.player_data.content_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 { common: CommonSpawn {
current_world_id: config.world.world_id,
home_world_id: config.world.world_id,
class_job: 35, class_job: 35,
name: chara_details.name, name: chara_details.name,
hp_curr: 100, hp_curr: 100,
hp_max: 100, hp_max: 100,
mp_curr: 100, mp_curr: 100,
mp_max: 100, mp_max: 100,
object_kind: ObjectKind::Player, object_kind: ObjectKind::Player(
gm_rank: GameMasterRank::Debug, PlayerSubKind::Player,
online_status: OnlineStatus::GameMasterBlue, ),
look: chara_details.chara_make.customize, look: chara_details.chara_make.customize,
fc_tag: "LOCAL".to_string(), fc_tag: "LOCAL".to_string(),
subtype: 4,
models: [ models: [
0, // head 0, // head
89, // body 89, // body

View file

@ -3,8 +3,9 @@ use crate::{
config::get_config, config::get_config,
packet::{PacketSegment, SegmentType}, packet::{PacketSegment, SegmentType},
world::ipc::{ world::ipc::{
ActorControl, ActorControlCategory, CommonSpawn, NpcSpawn, ObjectKind, PlayerSpawn, ActorControl, ActorControlCategory, BattleNpcSubKind, CommonSpawn, NpcSpawn, ObjectKind,
ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType, StatusEffectList, PlayerSpawn, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType,
StatusEffectList,
}, },
}; };
@ -103,22 +104,21 @@ impl ChatHandler {
server_id: 0, server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn { data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
some_unique_id: 1, account_id: 1,
content_id: 1, content_id: 1,
current_world_id: config.world.world_id,
home_world_id: config.world.world_id,
common: CommonSpawn { common: CommonSpawn {
current_world_id: config.world.world_id,
home_world_id: config.world.world_id,
class_job: 35, class_job: 35,
name: "Test Actor".to_string(), name: "Test Actor".to_string(),
hp_curr: 100, hp_curr: 100,
hp_max: 100, hp_max: 100,
mp_curr: 100, mp_curr: 100,
mp_max: 100, mp_max: 100,
object_kind: ObjectKind::Player, object_kind: ObjectKind::Player(PlayerSubKind::Player),
spawn_index: connection.get_free_spawn_index(), spawn_index: connection.get_free_spawn_index(),
look: CUSTOMIZE_DATA, look: CUSTOMIZE_DATA,
fc_tag: "LOCAL".to_string(), fc_tag: "LOCAL".to_string(),
subtype: 4,
models: [ models: [
0, // head 0, // head
89, // body 89, // body
@ -189,7 +189,7 @@ impl ChatHandler {
spawn_index: connection.get_free_spawn_index(), spawn_index: connection.get_free_spawn_index(),
bnpc_base: 13498, bnpc_base: 13498,
bnpc_name: 10261, bnpc_name: 10261,
object_kind: ObjectKind::BattleNpc, object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
target_id: ObjectTypeId { target_id: ObjectTypeId {
object_id: ObjectId(connection.player_data.actor_id), object_id: ObjectId(connection.player_data.actor_id),
object_type: 0, object_type: 0,
@ -233,6 +233,7 @@ impl ChatHandler {
server_id: 0, server_id: 0,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: ServerZoneIpcData::NpcSpawn(NpcSpawn { data: ServerZoneIpcData::NpcSpawn(NpcSpawn {
aggression_mode: 1,
common: CommonSpawn { common: CommonSpawn {
hp_curr: 91, hp_curr: 91,
hp_max: 91, hp_max: 91,
@ -241,7 +242,7 @@ impl ChatHandler {
spawn_index: connection.get_free_spawn_index(), spawn_index: connection.get_free_spawn_index(),
bnpc_base: 13498, // TODO: changing this prevents it from spawning... bnpc_base: 13498, // TODO: changing this prevents it from spawning...
bnpc_name: 405, bnpc_name: 405,
object_kind: ObjectKind::BattleNpc, object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
level: 1, level: 1,
battalion: 4, battalion: 4,
model_chara: 297, model_chara: 297,

View file

@ -10,25 +10,63 @@ use super::StatusEffect;
#[binrw] #[binrw]
#[brw(repr = u8)] #[brw(repr = u8)]
#[derive(Clone, PartialEq, Debug, Default)] #[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] #[default]
None = 0, None = 0,
Player = 1, Part = 1,
BattleNpc = 2, Pet = 2,
EventNpc = 3, Chocobo = 3,
Treasure = 4, Enemy = 5,
Aetheryte = 5, NpcPartyMember = 9,
GatheringPoint = 6, }
EventObj = 7,
Mount = 8, #[binrw]
Companion = 9, #[derive(Clone, PartialEq, Debug, Default)]
Retainer = 10, pub enum ObjectKind {
AreaObject = 11, #[default]
HousingEventObject = 12, #[brw(magic = 0u8)]
Cutscene = 13, None,
MjiObject = 14, #[brw(magic = 1u8)]
Ornament = 15, Player(PlayerSubKind),
CardStand = 16, #[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] #[binrw]
@ -52,7 +90,7 @@ pub enum OnlineStatus {
GameQA = 1, GameQA = 1,
GameMaster = 2, GameMaster = 2,
GameMasterBlue = 3, GameMasterBlue = 3,
EventParticipant = 4, // used by NPCs? EventParticipant = 4,
#[default] #[default]
Online = 47, Online = 47,
} }
@ -78,22 +116,6 @@ pub enum GameMasterRank {
#[brw(little)] #[brw(little)]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct CommonSpawn { 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 target_id: ObjectTypeId,
pub u6: u32, pub u6: u32,
pub u7: u32, pub u7: u32,
@ -133,8 +155,8 @@ pub struct CommonSpawn {
/// Argument used in CharacterMode. /// Argument used in CharacterMode.
// TODO: move to enum // TODO: move to enum
pub persistent_emote: u8, pub persistent_emote: u8,
#[brw(pad_size_to = 2)] // for kinds that don't have a param
pub object_kind: ObjectKind, pub object_kind: ObjectKind,
pub subtype: u8,
pub voice: u8, pub voice: u8,
pub unk27: u8, pub unk27: u8,
/// See Battalion Excel sheet. Used for determing whether it's friendy or an enemy. /// See Battalion Excel sheet. Used for determing whether it's friendy or an enemy.

View file

@ -36,7 +36,10 @@ mod npc_spawn;
pub use npc_spawn::NpcSpawn; pub use npc_spawn::NpcSpawn;
mod common_spawn; 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; mod status_effect_list;
pub use status_effect_list::StatusEffectList; pub use status_effect_list::StatusEffectList;

View file

@ -1,11 +1,24 @@
use binrw::binrw; use binrw::binrw;
use super::CommonSpawn; use super::{CommonSpawn, GameMasterRank, OnlineStatus};
#[binrw] #[binrw]
#[brw(little)] #[brw(little)]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct NpcSpawn { 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 common: CommonSpawn,
pub padding: [u8; 10], pub padding: [u8; 10],
} }
@ -18,7 +31,7 @@ mod tests {
use crate::{ use crate::{
common::INVALID_OBJECT_ID, common::INVALID_OBJECT_ID,
world::ipc::{CharacterMode, ObjectKind, OnlineStatus}, world::ipc::{BattleNpcSubKind, CharacterMode, ObjectKind, OnlineStatus},
}; };
use super::*; use super::*;
@ -45,10 +58,13 @@ mod tests {
assert_eq!(npc_spawn.common.bnpc_name, 10261); assert_eq!(npc_spawn.common.bnpc_name, 10261);
assert_eq!(npc_spawn.common.spawn_index, 56); assert_eq!(npc_spawn.common.spawn_index, 56);
assert_eq!(npc_spawn.common.mode, CharacterMode::Normal); assert_eq!(npc_spawn.common.mode, CharacterMode::Normal);
assert_eq!(npc_spawn.common.object_kind, ObjectKind::BattleNpc); assert_eq!(
assert_eq!(npc_spawn.common.subtype, 2); npc_spawn.common.object_kind,
ObjectKind::BattleNpc(BattleNpcSubKind::Pet)
);
assert_eq!(npc_spawn.common.battalion, 0); 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] #[test]
@ -73,11 +89,14 @@ mod tests {
assert_eq!(npc_spawn.common.bnpc_name, 405); assert_eq!(npc_spawn.common.bnpc_name, 405);
assert_eq!(npc_spawn.common.spawn_index, 14); assert_eq!(npc_spawn.common.spawn_index, 14);
assert_eq!(npc_spawn.common.mode, CharacterMode::Normal); assert_eq!(npc_spawn.common.mode, CharacterMode::Normal);
assert_eq!(npc_spawn.common.object_kind, ObjectKind::BattleNpc); assert_eq!(
assert_eq!(npc_spawn.common.subtype, 5); npc_spawn.common.object_kind,
ObjectKind::BattleNpc(BattleNpcSubKind::Enemy)
);
assert_eq!(npc_spawn.common.battalion, 4); assert_eq!(npc_spawn.common.battalion, 4);
assert_eq!(npc_spawn.common.parent_actor_id, INVALID_OBJECT_ID); 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.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);
} }
} }

View file

@ -1,17 +1,33 @@
use binrw::binrw; use binrw::binrw;
use super::CommonSpawn; use super::{CommonSpawn, GameMasterRank, OnlineStatus};
#[binrw] #[binrw]
#[brw(little)] #[brw(little)]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct PlayerSpawn { pub struct PlayerSpawn {
// also shows up in the friends list. // yes, really.
pub some_unique_id: u32, pub account_id: u32,
#[brw(pad_before = 4)] // always empty? #[brw(pad_before = 4)] // always empty?
pub content_id: u64, 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 common: CommonSpawn,
pub padding: [u8; 2], pub padding: [u8; 2],
@ -23,7 +39,7 @@ mod tests {
use binrw::BinRead; use binrw::BinRead;
use crate::world::ipc::{CharacterMode, ObjectKind, OnlineStatus}; use crate::world::ipc::{CharacterMode, ObjectKind, OnlineStatus, PlayerSubKind};
use super::*; use super::*;
@ -36,8 +52,8 @@ mod tests {
let mut buffer = Cursor::new(&buffer); let mut buffer = Cursor::new(&buffer);
let player_spawn = PlayerSpawn::read_le(&mut buffer).unwrap(); let player_spawn = PlayerSpawn::read_le(&mut buffer).unwrap();
assert_eq!(player_spawn.common.current_world_id, 0x4F); assert_eq!(player_spawn.current_world_id, 0x4F);
assert_eq!(player_spawn.common.home_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_curr, 159);
assert_eq!(player_spawn.common.hp_max, 159); assert_eq!(player_spawn.common.hp_max, 159);
assert_eq!(player_spawn.common.mp_curr, 10000); 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.gender, 1);
assert_eq!(player_spawn.common.look.bust, 100); assert_eq!(player_spawn.common.look.bust, 100);
assert_eq!(player_spawn.common.fc_tag, ""); 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.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.display_flags, 262144);
assert_eq!(player_spawn.common.online_status, OnlineStatus::Offline); assert_eq!(player_spawn.online_status, OnlineStatus::Offline);
} }
} }