diff --git a/USAGE.md b/USAGE.md index afdc4ad..ad180d4 100644 --- a/USAGE.md +++ b/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 `: 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 `: 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 diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 4da3875..6dad038 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -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() }], }), diff --git a/src/world/chat_handler.rs b/src/world/chat_handler.rs index 1adcb54..fc22a50 100644 --- a/src/world/chat_handler.rs +++ b/src/world/chat_handler.rs @@ -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!"), } } diff --git a/src/world/connection.rs b/src/world/connection.rs index befad2e..62dc2f9 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -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, + exit_rotation: Option, + ) -> 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() + } + } }