mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-24 08:07:45 +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:
parent
af6f95fa61
commit
9111ef6a82
7 changed files with 111 additions and 12 deletions
3
USAGE.md
3
USAGE.md
|
@ -80,7 +80,8 @@ These special debug commands start with `!` and are custom to Kawari.
|
||||||
|
|
||||||
* `!setpos <x> <y> <z>`: Teleport to the specified location
|
* `!setpos <x> <y> <z>`: Teleport to the specified location
|
||||||
* `!spawnactor`: Spawn another actor for debugging
|
* `!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
|
### GM commands
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ async fn main() {
|
||||||
player_data: PlayerData::default(),
|
player_data: PlayerData::default(),
|
||||||
spawn_index: 0,
|
spawn_index: 0,
|
||||||
zone: None,
|
zone: None,
|
||||||
|
position: Position::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
@ -516,8 +517,12 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::UpdatePositionHandler { .. } => {
|
ClientZoneIpcData::UpdatePositionHandler {
|
||||||
tracing::info!("Recieved UpdatePositionHandler!");
|
position, ..
|
||||||
|
} => {
|
||||||
|
tracing::info!("Character moved to {position:#?}");
|
||||||
|
|
||||||
|
connection.position = *position;
|
||||||
}
|
}
|
||||||
ClientZoneIpcData::LogOut { .. } => {
|
ClientZoneIpcData::LogOut { .. } => {
|
||||||
tracing::info!("Recieved log out from client!");
|
tracing::info!("Recieved log out from client!");
|
||||||
|
@ -707,6 +712,9 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ClientZoneIpcData::Unk15 { .. } => {
|
||||||
|
tracing::info!("Recieved Unk15!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SegmentType::KeepAlive { id, timestamp } => {
|
SegmentType::KeepAlive { id, timestamp } => {
|
||||||
|
|
|
@ -133,7 +133,7 @@ impl ChatHandler {
|
||||||
0, // left finger
|
0, // left finger
|
||||||
0, // right finger
|
0, // right finger
|
||||||
],
|
],
|
||||||
pos: Position::default(),
|
pos: connection.position,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -207,7 +207,48 @@ impl ChatHandler {
|
||||||
0, // left finger
|
0, // left finger
|
||||||
0, // right 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()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -32,6 +32,8 @@ pub struct ZoneConnection {
|
||||||
|
|
||||||
pub zone: Option<Zone>,
|
pub zone: Option<Zone>,
|
||||||
pub spawn_index: u8,
|
pub spawn_index: u8,
|
||||||
|
|
||||||
|
pub position: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZoneConnection {
|
impl ZoneConnection {
|
||||||
|
|
|
@ -67,8 +67,10 @@ pub struct CommonSpawn {
|
||||||
|
|
||||||
pub u14: u32,
|
pub u14: u32,
|
||||||
pub u15: u32,
|
pub u15: u32,
|
||||||
pub bnpc_base: u32, // See BNpcBase Excel sheet
|
/// See BNpcBase Excel sheet
|
||||||
pub bnpc_name: u32, // See BNpcName Excel sheet
|
pub bnpc_base: u32,
|
||||||
|
/// See BNpcName Excel sheet
|
||||||
|
pub bnpc_name: u32,
|
||||||
pub unk3: [u8; 8],
|
pub unk3: [u8; 8],
|
||||||
pub director_id: u32, // FIXME: i think the next three are in the wrong order
|
pub director_id: u32, // FIXME: i think the next three are in the wrong order
|
||||||
pub spawner_id: u32,
|
pub spawner_id: u32,
|
||||||
|
@ -80,7 +82,8 @@ pub struct CommonSpawn {
|
||||||
pub mp_curr: u16,
|
pub mp_curr: u16,
|
||||||
pub mp_max: u16,
|
pub mp_max: u16,
|
||||||
pub unk: 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 rotation: u16, // assumed
|
||||||
pub current_mount: u16, // assumed
|
pub current_mount: u16, // assumed
|
||||||
pub active_minion: u16, // assumed
|
pub active_minion: u16, // assumed
|
||||||
|
@ -88,15 +91,20 @@ pub struct CommonSpawn {
|
||||||
pub u24: u8, // assumed
|
pub u24: u8, // assumed
|
||||||
pub u25: u8, // assumed
|
pub u25: u8, // assumed
|
||||||
pub u26: u8, // assumed
|
pub u26: u8, // assumed
|
||||||
|
/// Must be unique for each actor.
|
||||||
pub spawn_index: u8,
|
pub spawn_index: u8,
|
||||||
pub mode: CharacterMode,
|
pub mode: CharacterMode,
|
||||||
|
/// Argument used in CharacterMode.
|
||||||
|
// TODO: move to enum
|
||||||
pub persistent_emote: u8,
|
pub persistent_emote: u8,
|
||||||
pub object_kind: ObjectKind,
|
pub object_kind: ObjectKind,
|
||||||
pub subtype: u8,
|
pub subtype: u8,
|
||||||
pub voice: u8,
|
pub voice: u8,
|
||||||
pub enemy_type: u8,
|
|
||||||
pub unk27: u8,
|
pub unk27: u8,
|
||||||
|
/// See Battalion Excel sheet. Used for determing whether it's friendy or an enemy.
|
||||||
|
pub battalion: u8,
|
||||||
pub level: u8,
|
pub level: u8,
|
||||||
|
/// See ClassJob Excel sheet.
|
||||||
pub class_job: u8,
|
pub class_job: u8,
|
||||||
pub unk28: u8,
|
pub unk28: u8,
|
||||||
pub unk29: u8,
|
pub unk29: u8,
|
||||||
|
|
|
@ -237,6 +237,8 @@ pub enum ClientZoneIpcType {
|
||||||
Unk14 = 0x87,
|
Unk14 = 0x87,
|
||||||
// Sent by the client when a character performs an action
|
// Sent by the client when a character performs an action
|
||||||
ActionRequest = 0x213,
|
ActionRequest = 0x213,
|
||||||
|
/// Sent by the client for unknown reasons, it's a bunch of numbers?
|
||||||
|
Unk15 = 0x10B,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -365,7 +367,9 @@ pub enum ClientZoneIpcData {
|
||||||
#[br(pre_assert(*magic == ClientZoneIpcType::UpdatePositionHandler))]
|
#[br(pre_assert(*magic == ClientZoneIpcType::UpdatePositionHandler))]
|
||||||
UpdatePositionHandler {
|
UpdatePositionHandler {
|
||||||
// TODO: full of possibly interesting information
|
// 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))]
|
#[br(pre_assert(*magic == ClientZoneIpcType::LogOut))]
|
||||||
LogOut {
|
LogOut {
|
||||||
|
@ -408,6 +412,8 @@ pub enum ClientZoneIpcData {
|
||||||
},
|
},
|
||||||
#[br(pre_assert(*magic == ClientZoneIpcType::ActionRequest))]
|
#[br(pre_assert(*magic == ClientZoneIpcType::ActionRequest))]
|
||||||
ActionRequest(ActionRequest),
|
ActionRequest(ActionRequest),
|
||||||
|
#[br(pre_assert(*magic == ClientZoneIpcType::Unk15))]
|
||||||
|
Unk15 { unk: [u8; 632] },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -16,12 +16,15 @@ mod tests {
|
||||||
|
|
||||||
use binrw::BinRead;
|
use binrw::BinRead;
|
||||||
|
|
||||||
use crate::world::ipc::{CharacterMode, ObjectKind};
|
use crate::{
|
||||||
|
common::INVALID_OBJECT_ID,
|
||||||
|
world::ipc::{CharacterMode, ObjectKind},
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_npcspawn() {
|
fn read_carbuncle() {
|
||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
d.push("resources/tests/npc_spawn.bin");
|
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.mode, CharacterMode::Normal);
|
||||||
assert_eq!(npc_spawn.common.object_kind, ObjectKind::BattleNpc);
|
assert_eq!(npc_spawn.common.object_kind, ObjectKind::BattleNpc);
|
||||||
assert_eq!(npc_spawn.common.subtype, 2);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue