mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-19 22:36:49 +00:00
Add !spawnclone command, rename !spawnactor
Curiously the spawned clone shows up as a player, despite not using the NPCSpawn packet. This might be a suitable workaround for the PlayerSpawn packet being buggy from our side.
This commit is contained in:
parent
b1653f0808
commit
4f8f0d1fe2
4 changed files with 124 additions and 124 deletions
3
USAGE.md
3
USAGE.md
|
@ -85,13 +85,14 @@ This feature is still a work-in-progress, and not all data is imported yet.
|
|||
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
|
||||
* `!spawnplayer`: Spawn another player for debugging, not known to work at the moment
|
||||
* `!spawnnpc`: Spawn a NPC for debugging
|
||||
* `!spawnmonster`: Spawn a monster for debugging
|
||||
* `!playscene <id>`: Plays an event. Only some events are supported for now:
|
||||
* Territory `181`, Event `1245185` plays the Limsa opening sequence
|
||||
* Territory `182`, Event `1245187` plays the Ul'dah opening sequence
|
||||
* Territory `183`, Event `1245186` plays the Gridania opening sequence
|
||||
* `!spawnclone`: Spawn a clone of yourself
|
||||
|
||||
### GM commands
|
||||
|
||||
|
|
|
@ -14,9 +14,8 @@ use kawari::packet::{
|
|||
send_packet,
|
||||
};
|
||||
use kawari::world::ipc::{
|
||||
ActionEffect, ActionResult, ClientZoneIpcData, CommonSpawn, DisplayFlag, EffectKind,
|
||||
GameMasterCommandType, GameMasterRank, ObjectKind, OnlineStatus, PlayerSubKind,
|
||||
ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
||||
ActionEffect, ActionResult, ClientZoneIpcData, EffectKind, GameMasterCommandType,
|
||||
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
|
||||
};
|
||||
use kawari::world::{
|
||||
Actor, ClientHandle, ClientId, EffectsBuilder, FromServer, Inventory, LuaPlayer, PlayerData,
|
||||
|
@ -375,17 +374,9 @@ async fn client_loop(
|
|||
.unwrap();
|
||||
}
|
||||
ClientZoneIpcData::FinishLoading { .. } => {
|
||||
let chara_details =
|
||||
database.find_chara_make(connection.player_data.content_id);
|
||||
|
||||
// send player spawn
|
||||
{
|
||||
let ipc;
|
||||
{
|
||||
let mut game_data = game_data.lock().unwrap();
|
||||
let equipped = &connection.player_data.inventory.equipped;
|
||||
|
||||
ipc = ServerZoneIpcSegment {
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::PlayerSpawn,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::PlayerSpawn(PlayerSpawn {
|
||||
|
@ -395,64 +386,11 @@ async fn client_loop(
|
|||
home_world_id: config.world.world_id,
|
||||
gm_rank: GameMasterRank::Debug,
|
||||
online_status: OnlineStatus::GameMasterBlue,
|
||||
common: CommonSpawn {
|
||||
class_job: connection.player_data.classjob_id,
|
||||
name: chara_details.name,
|
||||
hp_curr: connection.player_data.curr_hp,
|
||||
hp_max: connection.player_data.max_hp,
|
||||
mp_curr: connection.player_data.curr_mp,
|
||||
mp_max: connection.player_data.max_mp,
|
||||
object_kind: ObjectKind::Player(
|
||||
PlayerSubKind::Player,
|
||||
),
|
||||
look: chara_details.chara_make.customize,
|
||||
fc_tag: "LOCAL".to_string(),
|
||||
display_flags: DisplayFlag::UNK,
|
||||
models: [
|
||||
game_data
|
||||
.get_primary_model_id(equipped.head.id)
|
||||
as u32,
|
||||
game_data
|
||||
.get_primary_model_id(equipped.body.id)
|
||||
as u32,
|
||||
game_data
|
||||
.get_primary_model_id(equipped.hands.id)
|
||||
as u32,
|
||||
game_data
|
||||
.get_primary_model_id(equipped.legs.id)
|
||||
as u32,
|
||||
game_data
|
||||
.get_primary_model_id(equipped.feet.id)
|
||||
as u32,
|
||||
game_data
|
||||
.get_primary_model_id(equipped.ears.id)
|
||||
as u32,
|
||||
game_data
|
||||
.get_primary_model_id(equipped.neck.id)
|
||||
as u32,
|
||||
game_data.get_primary_model_id(
|
||||
equipped.wrists.id,
|
||||
)
|
||||
as u32,
|
||||
game_data.get_primary_model_id(
|
||||
equipped.left_ring.id,
|
||||
)
|
||||
as u32,
|
||||
game_data.get_primary_model_id(
|
||||
equipped.right_ring.id,
|
||||
)
|
||||
as u32,
|
||||
],
|
||||
pos: exit_position
|
||||
.unwrap_or(Position::default()),
|
||||
rotation: exit_rotation.unwrap_or(0.0),
|
||||
..Default::default()
|
||||
},
|
||||
common: connection.get_player_common_spawn(exit_position, exit_rotation),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
|
@ -538,7 +476,6 @@ async fn client_loop(
|
|||
level: 100,
|
||||
one: 1,
|
||||
name: "INVALID".to_string(),
|
||||
fc_tag: "LOCAL".to_string(),
|
||||
..Default::default()
|
||||
}],
|
||||
}),
|
||||
|
|
|
@ -69,9 +69,7 @@ impl ChatHandler {
|
|||
})
|
||||
.await;
|
||||
}
|
||||
"!spawnactor" => {
|
||||
tracing::info!("Spawning actor...");
|
||||
|
||||
"!spawnplayer" => {
|
||||
let config = get_config();
|
||||
|
||||
// send player spawn
|
||||
|
@ -98,7 +96,6 @@ impl ChatHandler {
|
|||
object_kind: ObjectKind::Player(PlayerSubKind::Player),
|
||||
spawn_index: connection.get_free_spawn_index(),
|
||||
look: CUSTOMIZE_DATA,
|
||||
fc_tag: "LOCAL".to_string(),
|
||||
display_flags: DisplayFlag::INVISIBLE
|
||||
| DisplayFlag::HIDE_HEAD
|
||||
| DisplayFlag::UNK,
|
||||
|
@ -156,57 +153,54 @@ impl ChatHandler {
|
|||
}
|
||||
}
|
||||
"!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,
|
||||
object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
|
||||
target_id: ObjectTypeId {
|
||||
object_id: ObjectId(connection.player_data.actor_id),
|
||||
object_type: 0,
|
||||
}, // target the player
|
||||
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: connection.player_data.position,
|
||||
..Default::default()
|
||||
},
|
||||
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,
|
||||
object_kind: ObjectKind::BattleNpc(BattleNpcSubKind::Enemy),
|
||||
target_id: ObjectTypeId {
|
||||
object_id: ObjectId(connection.player_data.actor_id),
|
||||
object_type: 0,
|
||||
}, // target the player
|
||||
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: connection.player_data.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;
|
||||
}
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0x106ad804,
|
||||
target_actor: connection.player_data.actor_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
"!spawnmonster" => {
|
||||
let actor = Actor {
|
||||
|
@ -325,6 +319,35 @@ impl ChatHandler {
|
|||
.unwrap()
|
||||
.enter_territory(lua_player, connection.zone.as_ref().unwrap());
|
||||
}
|
||||
"!spawnclone" => {
|
||||
// spawn another one of us
|
||||
|
||||
let player = connection.player_data;
|
||||
|
||||
let mut common = connection
|
||||
.get_player_common_spawn(Some(player.position), Some(player.rotation));
|
||||
common.spawn_index = connection.get_free_spawn_index();
|
||||
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
unk1: 20,
|
||||
unk2: 0,
|
||||
op_code: ServerZoneIpcType::NpcSpawn,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::NpcSpawn(NpcSpawn {
|
||||
common,
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
||||
connection
|
||||
.send_segment(PacketSegment {
|
||||
source_actor: 0x106ad804,
|
||||
target_actor: connection.player_data.actor_id,
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => tracing::info!("Unrecognized debug command!"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ use super::{
|
|||
chat_handler::CUSTOMIZE_DATA,
|
||||
ipc::{
|
||||
ActorControlSelf, ActorMove, ActorSetPos, BattleNpcSubKind, ClientZoneIpcSegment,
|
||||
CommonSpawn, ContainerInfo, ContainerType, Equip, InitZone, ItemInfo, NpcSpawn, ObjectKind,
|
||||
ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect, StatusEffectList, UpdateClassInfo,
|
||||
WeatherChange,
|
||||
CommonSpawn, ContainerInfo, ContainerType, DisplayFlag, Equip, InitZone, ItemInfo,
|
||||
NpcSpawn, ObjectKind, PlayerSubKind, ServerZoneIpcData, ServerZoneIpcSegment, StatusEffect,
|
||||
StatusEffectList, UpdateClassInfo, WeatherChange,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -714,4 +714,43 @@ impl ZoneConnection {
|
|||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn get_player_common_spawn(
|
||||
&mut self,
|
||||
exit_position: Option<Position>,
|
||||
exit_rotation: Option<f32>,
|
||||
) -> CommonSpawn {
|
||||
let mut game_data = self.gamedata.lock().unwrap();
|
||||
|
||||
let chara_details = self.database.find_chara_make(self.player_data.content_id);
|
||||
|
||||
let equipped = &self.player_data.inventory.equipped;
|
||||
CommonSpawn {
|
||||
class_job: self.player_data.classjob_id,
|
||||
name: chara_details.name,
|
||||
hp_curr: self.player_data.curr_hp,
|
||||
hp_max: self.player_data.max_hp,
|
||||
mp_curr: self.player_data.curr_mp,
|
||||
mp_max: self.player_data.max_mp,
|
||||
level: self.player_data.level,
|
||||
object_kind: ObjectKind::Player(PlayerSubKind::Player),
|
||||
look: chara_details.chara_make.customize,
|
||||
display_flags: DisplayFlag::UNK,
|
||||
models: [
|
||||
game_data.get_primary_model_id(equipped.head.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.body.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.hands.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.legs.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.feet.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.ears.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.neck.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.wrists.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.left_ring.id) as u32,
|
||||
game_data.get_primary_model_id(equipped.right_ring.id) as u32,
|
||||
],
|
||||
pos: exit_position.unwrap_or(Position::default()),
|
||||
rotation: exit_rotation.unwrap_or(0.0),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue