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

Add debug command to spawn a monster (Tiny Mandragora), various fixes

This fixes various problems in CommonSpawn, adds a test for an enemy NPC spawn,
and an unkwon packet I get when entering some zones.
This commit is contained in:
Joshua Goins 2025-03-23 10:33:49 -04:00
parent af6f95fa61
commit 9111ef6a82
7 changed files with 111 additions and 12 deletions

View file

@ -80,7 +80,8 @@ These special debug commands start with `!` and are custom to Kawari.
* `!setpos <x> <y> <z>`: Teleport to the specified location
* `!spawnactor`: Spawn another actor for debugging
* `!spawnnpc`: Spawn an NPC for debugging
* `!spawnnpc`: Spawn a NPC for debugging
* `!spawnmonster`: Spawn a monster for debugging
### GM commands

View file

@ -60,6 +60,7 @@ async fn main() {
player_data: PlayerData::default(),
spawn_index: 0,
zone: None,
position: Position::default(),
};
tokio::spawn(async move {
@ -516,8 +517,12 @@ async fn main() {
.await;
}
}
ClientZoneIpcData::UpdatePositionHandler { .. } => {
tracing::info!("Recieved UpdatePositionHandler!");
ClientZoneIpcData::UpdatePositionHandler {
position, ..
} => {
tracing::info!("Character moved to {position:#?}");
connection.position = *position;
}
ClientZoneIpcData::LogOut { .. } => {
tracing::info!("Recieved log out from client!");
@ -707,6 +712,9 @@ async fn main() {
.await;
}
}
ClientZoneIpcData::Unk15 { .. } => {
tracing::info!("Recieved Unk15!");
}
}
}
SegmentType::KeepAlive { id, timestamp } => {

View file

@ -133,7 +133,7 @@ impl ChatHandler {
0, // left finger
0, // right finger
],
pos: Position::default(),
pos: connection.position,
..Default::default()
},
..Default::default()
@ -207,7 +207,48 @@ impl ChatHandler {
0, // left finger
0, // right finger
],
pos: Position::default(),
pos: connection.position,
..Default::default()
},
..Default::default()
}),
};
connection
.send_segment(PacketSegment {
source_actor: 0x106ad804,
target_actor: connection.player_data.actor_id,
segment_type: SegmentType::Ipc { data: ipc },
})
.await;
}
}
"!spawnmonster" => {
// spawn a tiny mandragora
{
let ipc = ServerZoneIpcSegment {
unk1: 20,
unk2: 0,
op_code: ServerZoneIpcType::NpcSpawn,
server_id: 0,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::NpcSpawn(NpcSpawn {
common: CommonSpawn {
hp_curr: 91,
hp_max: 91,
mp_curr: 100,
mp_max: 100,
spawn_index: connection.get_free_spawn_index(),
bnpc_base: 13498, // TODO: changing this prevents it from spawning...
bnpc_name: 405,
spawner_id: connection.player_data.actor_id,
parent_actor_id: INVALID_OBJECT_ID, // TODO: make default?
object_kind: ObjectKind::BattleNpc,
target_id: INVALID_OBJECT_ID as u64,
level: 1,
battalion: 4,
model_chara: 297,
pos: connection.position,
..Default::default()
},
..Default::default()

View file

@ -32,6 +32,8 @@ pub struct ZoneConnection {
pub zone: Option<Zone>,
pub spawn_index: u8,
pub position: Position,
}
impl ZoneConnection {

View file

@ -67,8 +67,10 @@ pub struct CommonSpawn {
pub u14: u32,
pub u15: u32,
pub bnpc_base: u32, // See BNpcBase Excel sheet
pub bnpc_name: u32, // See BNpcName Excel sheet
/// See BNpcBase Excel sheet
pub bnpc_base: u32,
/// See BNpcName Excel sheet
pub bnpc_name: u32,
pub unk3: [u8; 8],
pub director_id: u32, // FIXME: i think the next three are in the wrong order
pub spawner_id: u32,
@ -80,7 +82,8 @@ pub struct CommonSpawn {
pub mp_curr: u16,
pub mp_max: u16,
pub unk: u16,
pub model_chara: u16, // See ModelChara Excel sheet
/// See ModelChara Excel sheet
pub model_chara: u16,
pub rotation: u16, // assumed
pub current_mount: u16, // assumed
pub active_minion: u16, // assumed
@ -88,15 +91,20 @@ pub struct CommonSpawn {
pub u24: u8, // assumed
pub u25: u8, // assumed
pub u26: u8, // assumed
/// Must be unique for each actor.
pub spawn_index: u8,
pub mode: CharacterMode,
/// Argument used in CharacterMode.
// TODO: move to enum
pub persistent_emote: u8,
pub object_kind: ObjectKind,
pub subtype: u8,
pub voice: u8,
pub enemy_type: u8,
pub unk27: u8,
/// See Battalion Excel sheet. Used for determing whether it's friendy or an enemy.
pub battalion: u8,
pub level: u8,
/// See ClassJob Excel sheet.
pub class_job: u8,
pub unk28: u8,
pub unk29: u8,

View file

@ -237,6 +237,8 @@ pub enum ClientZoneIpcType {
Unk14 = 0x87,
// Sent by the client when a character performs an action
ActionRequest = 0x213,
/// Sent by the client for unknown reasons, it's a bunch of numbers?
Unk15 = 0x10B,
}
#[binrw]
@ -365,7 +367,9 @@ pub enum ClientZoneIpcData {
#[br(pre_assert(*magic == ClientZoneIpcType::UpdatePositionHandler))]
UpdatePositionHandler {
// TODO: full of possibly interesting information
unk: [u8; 24],
unk: [u8; 8], // not empty
#[brw(pad_after = 4)] // empty
position: Position,
},
#[br(pre_assert(*magic == ClientZoneIpcType::LogOut))]
LogOut {
@ -408,6 +412,8 @@ pub enum ClientZoneIpcData {
},
#[br(pre_assert(*magic == ClientZoneIpcType::ActionRequest))]
ActionRequest(ActionRequest),
#[br(pre_assert(*magic == ClientZoneIpcType::Unk15))]
Unk15 { unk: [u8; 632] },
}
#[cfg(test)]

View file

@ -16,12 +16,15 @@ mod tests {
use binrw::BinRead;
use crate::world::ipc::{CharacterMode, ObjectKind};
use crate::{
common::INVALID_OBJECT_ID,
world::ipc::{CharacterMode, ObjectKind},
};
use super::*;
#[test]
fn read_npcspawn() {
fn read_carbuncle() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests/npc_spawn.bin");
@ -44,5 +47,35 @@ mod tests {
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.battalion, 0);
}
#[test]
fn read_tiny_mandragora() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests/tiny_mandragora.bin");
let buffer = read(d).unwrap();
let mut buffer = Cursor::new(&buffer);
let npc_spawn = NpcSpawn::read_le(&mut buffer).unwrap();
assert_eq!(npc_spawn.common.hp_max, 91);
assert_eq!(npc_spawn.common.hp_curr, 91);
assert_eq!(npc_spawn.common.mp_curr, 0);
assert_eq!(npc_spawn.common.mp_max, 0);
assert_eq!(npc_spawn.common.display_flags, 0);
assert_eq!(npc_spawn.common.pos.x, 116.99154);
assert_eq!(npc_spawn.common.pos.y, 76.64936);
assert_eq!(npc_spawn.common.pos.z, -187.02414);
assert_eq!(npc_spawn.common.model_chara, 297);
assert_eq!(npc_spawn.common.bnpc_base, 118);
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.battalion, 4);
assert_eq!(npc_spawn.common.parent_actor_id, INVALID_OBJECT_ID);
assert_eq!(npc_spawn.common.spawner_id, INVALID_OBJECT_ID);
}
}