mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-24 16:17:44 +00:00
Add support for spawning NPCs, and a debug command to do it
Since the structs are so similar, I created a CommonSpawn struct to hold most of the interesting fields.
This commit is contained in:
parent
039f4d7f95
commit
bd67eb0127
8 changed files with 325 additions and 159 deletions
BIN
resources/tests/npc_spawn.bin
Normal file
BIN
resources/tests/npc_spawn.bin
Normal file
Binary file not shown.
|
@ -1,8 +1,8 @@
|
||||||
use kawari::oodle::OodleNetwork;
|
use kawari::oodle::OodleNetwork;
|
||||||
use kawari::packet::{ConnectionType, PacketSegment, PacketState, SegmentType, send_keep_alive};
|
use kawari::packet::{ConnectionType, PacketSegment, PacketState, SegmentType, send_keep_alive};
|
||||||
use kawari::world::ipc::{
|
use kawari::world::ipc::{
|
||||||
ClientZoneIpcData, GameMasterCommandType, ServerZoneIpcData, ServerZoneIpcSegment,
|
ClientZoneIpcData, CommonSpawn, GameMasterCommandType, ObjectKind, ServerZoneIpcData,
|
||||||
ServerZoneIpcType, SocialListRequestType,
|
ServerZoneIpcSegment, ServerZoneIpcType, SocialListRequestType,
|
||||||
};
|
};
|
||||||
use kawari::world::{
|
use kawari::world::{
|
||||||
ChatHandler, Zone, ZoneConnection,
|
ChatHandler, Zone, ZoneConnection,
|
||||||
|
@ -282,34 +282,37 @@ async fn main() {
|
||||||
timestamp: timestamp_secs(),
|
timestamp: timestamp_secs(),
|
||||||
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
||||||
content_id: CONTENT_ID,
|
content_id: CONTENT_ID,
|
||||||
current_world_id: WORLD_ID,
|
common: CommonSpawn {
|
||||||
home_world_id: WORLD_ID,
|
current_world_id: WORLD_ID,
|
||||||
title: 1,
|
home_world_id: WORLD_ID,
|
||||||
class_job: 35,
|
title: 1,
|
||||||
name: CHAR_NAME.to_string(),
|
class_job: 35,
|
||||||
hp_curr: 100,
|
name: CHAR_NAME.to_string(),
|
||||||
hp_max: 100,
|
hp_curr: 100,
|
||||||
mp_curr: 100,
|
hp_max: 100,
|
||||||
mp_max: 100,
|
mp_curr: 100,
|
||||||
model_type: 1,
|
mp_max: 100,
|
||||||
gm_rank: 3,
|
object_kind: ObjectKind::Player,
|
||||||
look: CUSTOMIZE_DATA,
|
gm_rank: 3,
|
||||||
fc_tag: "LOCAL".to_string(),
|
look: CUSTOMIZE_DATA,
|
||||||
subtype: 4,
|
fc_tag: "LOCAL".to_string(),
|
||||||
models: [
|
subtype: 4,
|
||||||
0, // head
|
models: [
|
||||||
89, // body
|
0, // head
|
||||||
89, // hands
|
89, // body
|
||||||
89, // legs
|
89, // hands
|
||||||
89, // feet
|
89, // legs
|
||||||
0, // ears
|
89, // feet
|
||||||
0, // neck
|
0, // ears
|
||||||
0, // wrists
|
0, // neck
|
||||||
0, // left finger
|
0, // wrists
|
||||||
0, // right finger
|
0, // left finger
|
||||||
],
|
0, // right finger
|
||||||
pos: exit_position
|
],
|
||||||
.unwrap_or(Position::default()),
|
pos: exit_position
|
||||||
|
.unwrap_or(Position::default()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub const WORLD_NAME: &str = "KAWARI";
|
||||||
pub const ZONE_ID: u16 = 132;
|
pub const ZONE_ID: u16 = 132;
|
||||||
|
|
||||||
pub const CONTENT_ID: u64 = 11111111111111111;
|
pub const CONTENT_ID: u64 = 11111111111111111;
|
||||||
|
pub const INVALID_OBJECT_ID: u32 = 0xE0000000;
|
||||||
|
|
||||||
pub const CUSTOMIZE_DATA: CustomizeData = CustomizeData {
|
pub const CUSTOMIZE_DATA: CustomizeData = CustomizeData {
|
||||||
race: 4,
|
race: 4,
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
CHAR_NAME, CUSTOMIZE_DATA, WORLD_ID,
|
CHAR_NAME, CUSTOMIZE_DATA, INVALID_OBJECT_ID, WORLD_ID,
|
||||||
common::timestamp_secs,
|
common::timestamp_secs,
|
||||||
packet::{PacketSegment, SegmentType},
|
packet::{PacketSegment, SegmentType},
|
||||||
world::ipc::{PlayerSpawn, ServerZoneIpcData, ServerZoneIpcSegment, ServerZoneIpcType},
|
world::ipc::{
|
||||||
|
CommonSpawn, NpcSpawn, ObjectKind, PlayerSpawn, ServerZoneIpcData, ServerZoneIpcSegment,
|
||||||
|
ServerZoneIpcType,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -45,33 +48,87 @@ impl ChatHandler {
|
||||||
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
||||||
some_unique_id: 1,
|
some_unique_id: 1,
|
||||||
content_id: 1,
|
content_id: 1,
|
||||||
current_world_id: WORLD_ID,
|
common: CommonSpawn {
|
||||||
home_world_id: WORLD_ID,
|
current_world_id: WORLD_ID,
|
||||||
title: 1,
|
home_world_id: WORLD_ID,
|
||||||
class_job: 35,
|
title: 1,
|
||||||
name: CHAR_NAME.to_string(),
|
class_job: 35,
|
||||||
hp_curr: 100,
|
name: CHAR_NAME.to_string(),
|
||||||
hp_max: 100,
|
hp_curr: 100,
|
||||||
mp_curr: 100,
|
hp_max: 100,
|
||||||
mp_max: 100,
|
mp_curr: 100,
|
||||||
model_type: 1,
|
mp_max: 100,
|
||||||
gm_rank: 3,
|
object_kind: ObjectKind::Player,
|
||||||
spawn_index: connection.get_free_spawn_index(),
|
gm_rank: 3,
|
||||||
look: CUSTOMIZE_DATA,
|
spawn_index: connection.get_free_spawn_index(),
|
||||||
fc_tag: "LOCAL".to_string(),
|
look: CUSTOMIZE_DATA,
|
||||||
models: [
|
fc_tag: "LOCAL".to_string(),
|
||||||
0, // head
|
models: [
|
||||||
89, // body
|
0, // head
|
||||||
89, // hands
|
89, // body
|
||||||
89, // legs
|
89, // hands
|
||||||
89, // feet
|
89, // legs
|
||||||
0, // ears
|
89, // feet
|
||||||
0, // neck
|
0, // ears
|
||||||
0, // wrists
|
0, // neck
|
||||||
0, // left finger
|
0, // wrists
|
||||||
0, // right finger
|
0, // left finger
|
||||||
],
|
0, // right finger
|
||||||
pos: Position::default(),
|
],
|
||||||
|
pos: Position::default(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
connection
|
||||||
|
.send_segment(PacketSegment {
|
||||||
|
source_actor: 0x106ad804,
|
||||||
|
target_actor: connection.player_id,
|
||||||
|
segment_type: SegmentType::Ipc { data: ipc },
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"!spawnnpc" => {
|
||||||
|
// spawn another one of us
|
||||||
|
{
|
||||||
|
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: 100,
|
||||||
|
hp_max: 100,
|
||||||
|
mp_curr: 100,
|
||||||
|
mp_max: 100,
|
||||||
|
look: CUSTOMIZE_DATA,
|
||||||
|
spawn_index: connection.get_free_spawn_index(),
|
||||||
|
bnpc_base: 13498,
|
||||||
|
bnpc_name: 10261,
|
||||||
|
spawner_id: connection.player_id,
|
||||||
|
parent_actor_id: INVALID_OBJECT_ID, // TODO: make default?
|
||||||
|
object_kind: ObjectKind::BattleNpc,
|
||||||
|
level: 1,
|
||||||
|
models: [
|
||||||
|
0, // head
|
||||||
|
89, // body
|
||||||
|
89, // hands
|
||||||
|
89, // legs
|
||||||
|
89, // feet
|
||||||
|
0, // ears
|
||||||
|
0, // neck
|
||||||
|
0, // wrists
|
||||||
|
0, // left finger
|
||||||
|
0, // right finger
|
||||||
|
],
|
||||||
|
pos: Position::default(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
93
src/world/ipc/common_spawn.rs
Normal file
93
src/world/ipc/common_spawn.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
use crate::CHAR_NAME_MAX_LENGTH;
|
||||||
|
use crate::common::{CustomizeData, read_string, write_string};
|
||||||
|
|
||||||
|
use super::position::Position;
|
||||||
|
use super::{CharacterMode, ObjectKind, StatusEffect};
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct CommonSpawn {
|
||||||
|
pub title: u16,
|
||||||
|
pub u1b: u16,
|
||||||
|
pub current_world_id: u16,
|
||||||
|
pub home_world_id: u16,
|
||||||
|
|
||||||
|
pub gm_rank: u8,
|
||||||
|
pub u3c: u8,
|
||||||
|
pub u4: u8,
|
||||||
|
pub online_status: u8,
|
||||||
|
|
||||||
|
pub pose: u8,
|
||||||
|
pub u5a: u8,
|
||||||
|
pub u5b: u8,
|
||||||
|
pub u5c: u8,
|
||||||
|
|
||||||
|
pub target_id: u64,
|
||||||
|
pub u6: u32,
|
||||||
|
pub u7: u32,
|
||||||
|
pub main_weapon_model: u64,
|
||||||
|
pub sec_weapon_model: u64,
|
||||||
|
pub craft_tool_model: u64,
|
||||||
|
|
||||||
|
pub u14: u32,
|
||||||
|
pub u15: u32,
|
||||||
|
pub bnpc_base: u32, // See BNpcBase Excel sheet
|
||||||
|
pub bnpc_name: u32, // See BNpcName Excel sheet
|
||||||
|
pub unk3: [u8; 8],
|
||||||
|
pub director_id: u32, // FIXME: i think the next three are in the wrong order
|
||||||
|
pub spawner_id: u32,
|
||||||
|
pub parent_actor_id: u32,
|
||||||
|
pub hp_max: u32,
|
||||||
|
pub hp_curr: u32,
|
||||||
|
pub display_flags: u32, // assumed
|
||||||
|
pub fate_id: u16, // assumed
|
||||||
|
pub mp_curr: u16,
|
||||||
|
pub mp_max: u16,
|
||||||
|
pub unk: u16,
|
||||||
|
pub model_chara: u16, // See ModelChara Excel sheet
|
||||||
|
pub rotation: u16, // assumed
|
||||||
|
pub current_mount: u16, // assumed
|
||||||
|
pub active_minion: u16, // assumed
|
||||||
|
pub u23: u8, // assumed
|
||||||
|
pub u24: u8, // assumed
|
||||||
|
pub u25: u8, // assumed
|
||||||
|
pub u26: u8, // assumed
|
||||||
|
pub spawn_index: u8,
|
||||||
|
pub mode: CharacterMode,
|
||||||
|
pub persistent_emote: u8,
|
||||||
|
pub object_kind: ObjectKind,
|
||||||
|
pub subtype: u8,
|
||||||
|
pub voice: u8,
|
||||||
|
pub enemy_type: u8,
|
||||||
|
pub unk27: u8,
|
||||||
|
pub level: u8,
|
||||||
|
pub class_job: u8,
|
||||||
|
pub unk28: u8,
|
||||||
|
pub unk29: u8,
|
||||||
|
pub mount_head: u8,
|
||||||
|
pub mount_body: u8,
|
||||||
|
pub mount_feet: u8,
|
||||||
|
pub mount_color: u8,
|
||||||
|
pub scale: u8,
|
||||||
|
pub element_data: [u8; 6],
|
||||||
|
pub padding2: [u8; 1],
|
||||||
|
pub effect: [StatusEffect; 30],
|
||||||
|
pub pos: Position,
|
||||||
|
pub models: [u32; 10],
|
||||||
|
pub unknown6_58: [u8; 10],
|
||||||
|
pub padding3: [u8; 4],
|
||||||
|
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
||||||
|
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub name: String,
|
||||||
|
pub look: CustomizeData,
|
||||||
|
#[br(count = 6)]
|
||||||
|
#[bw(pad_size_to = 6)]
|
||||||
|
#[br(map = read_string)]
|
||||||
|
#[bw(map = write_string)]
|
||||||
|
pub fc_tag: String,
|
||||||
|
}
|
|
@ -34,6 +34,12 @@ pub use actor_control_self::ActorControlType;
|
||||||
mod init_zone;
|
mod init_zone;
|
||||||
pub use init_zone::InitZone;
|
pub use init_zone::InitZone;
|
||||||
|
|
||||||
|
mod npc_spawn;
|
||||||
|
pub use npc_spawn::NpcSpawn;
|
||||||
|
|
||||||
|
mod common_spawn;
|
||||||
|
pub use common_spawn::CommonSpawn;
|
||||||
|
|
||||||
use crate::common::read_string;
|
use crate::common::read_string;
|
||||||
use crate::common::write_string;
|
use crate::common::write_string;
|
||||||
use crate::packet::IpcSegment;
|
use crate::packet::IpcSegment;
|
||||||
|
@ -86,6 +92,7 @@ impl ReadWriteIpcSegment for ServerZoneIpcSegment {
|
||||||
ServerZoneIpcType::Unk17 => 104,
|
ServerZoneIpcType::Unk17 => 104,
|
||||||
ServerZoneIpcType::SocialList => 1136,
|
ServerZoneIpcType::SocialList => 1136,
|
||||||
ServerZoneIpcType::PrepareZoning => 16,
|
ServerZoneIpcType::PrepareZoning => 16,
|
||||||
|
ServerZoneIpcType::NpcSpawn => 648,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,6 +128,30 @@ pub enum GameMasterCommandType {
|
||||||
ChangeTerritory = 0x58,
|
ChangeTerritory = 0x58,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(repr = u8)]
|
||||||
|
#[derive(Clone, PartialEq, Debug, Default)]
|
||||||
|
pub enum ObjectKind {
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(repr = u16)]
|
#[brw(repr = u16)]
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
@ -172,6 +203,8 @@ pub enum ServerZoneIpcType {
|
||||||
Unk17 = 0x2A1,
|
Unk17 = 0x2A1,
|
||||||
// Sent by the server in response to SocialListRequest
|
// Sent by the server in response to SocialListRequest
|
||||||
SocialList = 0x36C,
|
SocialList = 0x36C,
|
||||||
|
// Sent by the server to spawn an NPC
|
||||||
|
NpcSpawn = 0x100,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -287,6 +320,7 @@ pub enum ServerZoneIpcData {
|
||||||
unk: [u8; 104],
|
unk: [u8; 104],
|
||||||
},
|
},
|
||||||
SocialList(SocialList),
|
SocialList(SocialList),
|
||||||
|
NpcSpawn(NpcSpawn),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
|
@ -429,6 +463,10 @@ mod tests {
|
||||||
ServerZoneIpcType::ActorSetPos,
|
ServerZoneIpcType::ActorSetPos,
|
||||||
ServerZoneIpcData::ActorSetPos(ActorSetPos::default()),
|
ServerZoneIpcData::ActorSetPos(ActorSetPos::default()),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
ServerZoneIpcType::NpcSpawn,
|
||||||
|
ServerZoneIpcData::NpcSpawn(NpcSpawn::default()),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (opcode, data) in &ipc_types {
|
for (opcode, data) in &ipc_types {
|
||||||
|
|
50
src/world/ipc/npc_spawn.rs
Normal file
50
src/world/ipc/npc_spawn.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use binrw::binrw;
|
||||||
|
|
||||||
|
use crate::CHAR_NAME_MAX_LENGTH;
|
||||||
|
use crate::common::{CustomizeData, read_string, write_string};
|
||||||
|
|
||||||
|
use super::position::Position;
|
||||||
|
use super::{CharacterMode, CommonSpawn, ObjectKind, StatusEffect};
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct NpcSpawn {
|
||||||
|
pub common: CommonSpawn,
|
||||||
|
pub padding: [u8; 10],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{fs::read, io::Cursor, path::PathBuf};
|
||||||
|
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_npcspawn() {
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests/npc_spawn.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, 1393);
|
||||||
|
assert_eq!(npc_spawn.common.hp_curr, 1393);
|
||||||
|
assert_eq!(npc_spawn.common.mp_curr, 10000);
|
||||||
|
assert_eq!(npc_spawn.common.mp_max, 10000);
|
||||||
|
assert_eq!(npc_spawn.common.display_flags, 0);
|
||||||
|
assert_eq!(npc_spawn.common.pos.x, -64.17707);
|
||||||
|
assert_eq!(npc_spawn.common.pos.y, -2.0206506);
|
||||||
|
assert_eq!(npc_spawn.common.pos.z, 15.913875);
|
||||||
|
assert_eq!(npc_spawn.common.model_chara, 411);
|
||||||
|
assert_eq!(npc_spawn.common.bnpc_base, 13498);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ use crate::common::{CustomizeData, read_string, write_string};
|
||||||
|
|
||||||
use super::position::Position;
|
use super::position::Position;
|
||||||
use super::status_effect::StatusEffect;
|
use super::status_effect::StatusEffect;
|
||||||
|
use super::{CommonSpawn, ObjectKind};
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
|
@ -27,87 +28,8 @@ pub struct PlayerSpawn {
|
||||||
#[brw(pad_before = 4)] // always empty?
|
#[brw(pad_before = 4)] // always empty?
|
||||||
pub content_id: u64,
|
pub content_id: u64,
|
||||||
|
|
||||||
pub title: u16,
|
pub common: CommonSpawn,
|
||||||
pub u1b: u16,
|
|
||||||
pub current_world_id: u16,
|
|
||||||
pub home_world_id: u16,
|
|
||||||
|
|
||||||
pub gm_rank: u8,
|
|
||||||
pub u3c: u8,
|
|
||||||
pub u4: u8,
|
|
||||||
pub online_status: u8,
|
|
||||||
|
|
||||||
pub pose: u8,
|
|
||||||
pub u5a: u8,
|
|
||||||
pub u5b: u8,
|
|
||||||
pub u5c: u8,
|
|
||||||
|
|
||||||
pub target_id: u64,
|
|
||||||
pub u6: u32,
|
|
||||||
pub u7: u32,
|
|
||||||
pub main_weapon_model: u64,
|
|
||||||
pub sec_weapon_model: u64,
|
|
||||||
pub craft_tool_model: u64,
|
|
||||||
|
|
||||||
pub u14: u32,
|
|
||||||
pub u15: u32,
|
|
||||||
pub b_npc_base: u32,
|
|
||||||
pub b_npc_name: u32,
|
|
||||||
pub u18: u32,
|
|
||||||
pub u19: u32,
|
|
||||||
pub director_id: u32,
|
|
||||||
pub owner_id: u32,
|
|
||||||
pub u22: u32,
|
|
||||||
pub hp_max: u32,
|
|
||||||
pub hp_curr: u32,
|
|
||||||
pub display_flags: u32,
|
|
||||||
pub fate_id: u16,
|
|
||||||
pub mp_curr: u16,
|
|
||||||
pub mp_max: u16,
|
|
||||||
pub unk: u16,
|
|
||||||
pub model_chara: u16,
|
|
||||||
pub rotation: u16,
|
|
||||||
pub current_mount: u16,
|
|
||||||
pub active_minion: u16,
|
|
||||||
pub u23: u8,
|
|
||||||
pub u24: u8,
|
|
||||||
pub u25: u8,
|
|
||||||
pub u26: u8,
|
|
||||||
pub spawn_index: u8,
|
|
||||||
pub mode: CharacterMode,
|
|
||||||
pub persistent_emote: u8,
|
|
||||||
pub model_type: u8,
|
|
||||||
pub subtype: u8,
|
|
||||||
pub voice: u8,
|
|
||||||
pub enemy_type: u8,
|
|
||||||
pub unk27: u8,
|
|
||||||
pub level: u8,
|
|
||||||
pub class_job: u8,
|
|
||||||
pub unk28: u8,
|
|
||||||
pub unk29: u8,
|
|
||||||
pub mount_head: u8,
|
|
||||||
pub mount_body: u8,
|
|
||||||
pub mount_feet: u8,
|
|
||||||
pub mount_color: u8,
|
|
||||||
pub scale: u8,
|
|
||||||
pub element_data: [u8; 6],
|
|
||||||
pub padding2: [u8; 1],
|
|
||||||
pub effect: [StatusEffect; 30],
|
|
||||||
pub pos: Position,
|
|
||||||
pub models: [u32; 10],
|
|
||||||
pub unknown6_58: [u8; 10],
|
|
||||||
pub padding3: [u8; 4],
|
|
||||||
#[br(count = CHAR_NAME_MAX_LENGTH)]
|
|
||||||
#[bw(pad_size_to = CHAR_NAME_MAX_LENGTH)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub name: String,
|
|
||||||
pub look: CustomizeData,
|
|
||||||
#[br(count = 6)]
|
|
||||||
#[bw(pad_size_to = 6)]
|
|
||||||
#[br(map = read_string)]
|
|
||||||
#[bw(map = write_string)]
|
|
||||||
pub fc_tag: String,
|
|
||||||
pub padding: [u8; 2],
|
pub padding: [u8; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,25 +50,27 @@ 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.current_world_id, 0x4F);
|
assert_eq!(player_spawn.common.current_world_id, 0x4F);
|
||||||
assert_eq!(player_spawn.home_world_id, 0x4F);
|
assert_eq!(player_spawn.common.home_world_id, 0x4F);
|
||||||
assert_eq!(player_spawn.hp_curr, 159);
|
assert_eq!(player_spawn.common.hp_curr, 159);
|
||||||
assert_eq!(player_spawn.hp_max, 159);
|
assert_eq!(player_spawn.common.hp_max, 159);
|
||||||
assert_eq!(player_spawn.mp_curr, 10000);
|
assert_eq!(player_spawn.common.mp_curr, 10000);
|
||||||
assert_eq!(player_spawn.mp_max, 10000);
|
assert_eq!(player_spawn.common.mp_max, 10000);
|
||||||
assert_eq!(player_spawn.mode, CharacterMode::Normal);
|
assert_eq!(player_spawn.common.mode, CharacterMode::Normal);
|
||||||
assert_eq!(player_spawn.spawn_index, 0);
|
assert_eq!(player_spawn.common.spawn_index, 0);
|
||||||
assert_eq!(player_spawn.level, 1);
|
assert_eq!(player_spawn.common.level, 1);
|
||||||
assert_eq!(player_spawn.class_job, 1); // adventurer
|
assert_eq!(player_spawn.common.class_job, 1); // adventurer
|
||||||
assert_eq!(player_spawn.scale, 36);
|
assert_eq!(player_spawn.common.scale, 36);
|
||||||
assert_eq!(player_spawn.pos.x, 40.519722);
|
assert_eq!(player_spawn.common.pos.x, 40.519722);
|
||||||
assert_eq!(player_spawn.pos.y, 4.0);
|
assert_eq!(player_spawn.common.pos.y, 4.0);
|
||||||
assert_eq!(player_spawn.pos.z, -150.33124);
|
assert_eq!(player_spawn.common.pos.z, -150.33124);
|
||||||
assert_eq!(player_spawn.name, "Lavenaa Warren");
|
assert_eq!(player_spawn.common.name, "Lavenaa Warren");
|
||||||
assert_eq!(player_spawn.look.race, 1);
|
assert_eq!(player_spawn.common.look.race, 1);
|
||||||
assert_eq!(player_spawn.look.gender, 1);
|
assert_eq!(player_spawn.common.look.gender, 1);
|
||||||
assert_eq!(player_spawn.look.bust, 100);
|
assert_eq!(player_spawn.common.look.bust, 100);
|
||||||
assert_eq!(player_spawn.fc_tag, "");
|
assert_eq!(player_spawn.common.fc_tag, "");
|
||||||
assert_eq!(player_spawn.subtype, 4);
|
assert_eq!(player_spawn.common.subtype, 4);
|
||||||
|
assert_eq!(player_spawn.common.model_chara, 0);
|
||||||
|
assert_eq!(player_spawn.common.object_kind, ObjectKind::Player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue