From 94ed03643164fe2881ad206e66d990d2b921cf59 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 11 May 2025 10:12:02 -0400 Subject: [PATCH] Add script for the Teleport action This makes the Teleport action functional now, although it looks pretty rough as it warps you instantly instead of waiting for the action to actually finish. --- Cargo.lock | 8 +++--- Cargo.toml | 2 +- resources/scripts/Global.lua | 1 + resources/scripts/actions/Teleport.lua | 11 ++++++++ src/bin/kawari-world.rs | 12 ++++++-- src/common/gamedata.rs | 12 ++++++++ src/ipc/zone/actor_control.rs | 6 ++++ src/ipc/zone/client_trigger.rs | 6 ++++ src/world/common.rs | 8 ++++-- src/world/connection.rs | 39 ++++++++++++++++++++++++++ src/world/lua.rs | 21 +++++++++++++- src/world/server.rs | 23 ++++++++++++--- 12 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 resources/scripts/actions/Teleport.lua diff --git a/Cargo.lock b/Cargo.lock index 1fbe3cf..0902815 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ dependencies = [ [[package]] name = "icarus" version = "0.0.0" -source = "git+https://github.com/redstrate/Icarus?branch=ver%2F2025.04.16.0000.0000#5d5f1dded6949b790307546c9ddc0604af636451" +source = "git+https://github.com/redstrate/Icarus?branch=ver%2F2025.04.16.0000.0000#76c7c6c18bee63927fc723d4a99e2e1fba96fcb9" dependencies = [ "physis", ] @@ -1025,7 +1025,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "physis" version = "0.4.0" -source = "git+https://github.com/redstrate/physis#309726163a0d76f26831236f43740a2c8bd1b01d" +source = "git+https://github.com/redstrate/physis#76120b2dd0843c16af648f7b18ba0f92c5d56460" dependencies = [ "binrw", "bitflags", @@ -1472,9 +1472,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" dependencies = [ "bitflags", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 5b6bee2..bf968df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,4 +104,4 @@ rkon = { version = "0.1" } tower-http = { version = "0.6", features = ["fs"] } # excel sheet data -icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.04.16.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race"], default-features = false } +icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.04.16.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte"], default-features = false } diff --git a/resources/scripts/Global.lua b/resources/scripts/Global.lua index b42bf70..f41a897 100644 --- a/resources/scripts/Global.lua +++ b/resources/scripts/Global.lua @@ -20,6 +20,7 @@ end -- Actions registerAction(3, "actions/Sprint.lua") +registerAction(5, "actions/Teleport.lua") registerAction(9, "actions/FastBlade.lua") -- Items diff --git a/resources/scripts/actions/Teleport.lua b/resources/scripts/actions/Teleport.lua new file mode 100644 index 0000000..d849fae --- /dev/null +++ b/resources/scripts/actions/Teleport.lua @@ -0,0 +1,11 @@ +function doAction(player) + effects = EffectsBuilder() + + -- get the aetheryte they requested + local id = player.teleport_query.aetheryte_id + + -- warp there + player:warp_aetheryte(id) + + return effects +end diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index 24f15b5..4d77e6a 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -9,9 +9,9 @@ use kawari::config::get_config; use kawari::inventory::Item; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; use kawari::ipc::zone::{ - ActionEffect, ActionResult, ClientZoneIpcData, CommonSpawn, EffectKind, EventStart, - GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, - SocialListRequestType, + ActionEffect, ActionResult, ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EffectKind, + EventStart, GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData, + ServerZoneIpcSegment, SocialListRequestType, }; use kawari::ipc::zone::{ ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList, @@ -371,6 +371,11 @@ async fn client_loop( connection.exit_rotation = None; } ClientZoneIpcData::ClientTrigger(trigger) => { + // store the query for scripts + if let ClientTriggerCommand::TeleportQuery { aetheryte_id } = trigger.trigger { + connection.player_data.teleport_query.aetheryte_id = aetheryte_id as u16; + } + // inform the server of our trigger, it will handle sending it to other clients connection.handle.send(ToServer::ClientTrigger(connection.id, connection.player_data.actor_id, trigger.clone())).await; } @@ -851,6 +856,7 @@ async fn client_loop( FromServer::ActorControl(actor_id, actor_control) => connection.actor_control(actor_id, actor_control).await, FromServer::ActorControlTarget(actor_id, actor_control) => connection.actor_control_target(actor_id, actor_control).await, FromServer::SpawnNPC(npc) => connection.send_npc(npc).await, + FromServer::ActorControlSelf(actor_control) => connection.actor_control_self(actor_control).await, }, None => break, } diff --git a/src/common/gamedata.rs b/src/common/gamedata.rs index d58abd1..5d1e936 100644 --- a/src/common/gamedata.rs +++ b/src/common/gamedata.rs @@ -1,3 +1,4 @@ +use icarus::Aetheryte::AetheryteSheet; use icarus::ClassJob::ClassJobSheet; use icarus::World::WorldSheet; use icarus::{Tribe::TribeSheet, Warp::WarpSheet}; @@ -106,4 +107,15 @@ impl GameData { Some((*pop_range_id, *zone_id)) } + + pub fn get_aetheryte(&mut self, aetheryte_id: u32) -> Option<(u32, u16)> { + let sheet = AetheryteSheet::read_from(&mut self.game_data, Language::English)?; + let row = sheet.get_row(aetheryte_id)?; + + // TODO: just look in the level sheet? + let pop_range_id = row.Level()[0].into_u32()?; + let zone_id = row.Territory().into_u16()?; + + Some((*pop_range_id, *zone_id)) + } } diff --git a/src/ipc/zone/actor_control.rs b/src/ipc/zone/actor_control.rs index 408f8e0..6ba078d 100644 --- a/src/ipc/zone/actor_control.rs +++ b/src/ipc/zone/actor_control.rs @@ -63,6 +63,12 @@ pub enum ActorControlCategory { #[bw(map = write_bool_as::)] unlocked: bool, }, + #[brw(magic = 0xCBu16)] + TeleportStart { + #[brw(pad_before = 2)] //padding + insufficient_gil: u32, + aetheryte_id: u32, + }, } #[binrw] diff --git a/src/ipc/zone/client_trigger.rs b/src/ipc/zone/client_trigger.rs index b3a3b42..3fba1a0 100644 --- a/src/ipc/zone/client_trigger.rs +++ b/src/ipc/zone/client_trigger.rs @@ -24,6 +24,12 @@ pub enum ClientTriggerCommand { unk1: u32, pose: u32, }, + #[brw(magic = 0xCAu16)] + TeleportQuery { + #[brw(pad_before = 2)] + aetheryte_id: u32, + // TODO: fill out the rest + }, } #[binrw] diff --git a/src/world/common.rs b/src/world/common.rs index d84f881..cdc80b2 100644 --- a/src/world/common.rs +++ b/src/world/common.rs @@ -10,7 +10,9 @@ use tokio::sync::mpsc::Sender; use crate::{ common::Position, - ipc::zone::{ActorControl, ActorControlTarget, ClientTrigger, CommonSpawn, NpcSpawn}, + ipc::zone::{ + ActorControl, ActorControlSelf, ActorControlTarget, ClientTrigger, CommonSpawn, NpcSpawn, + }, }; use super::Actor; @@ -29,8 +31,10 @@ pub enum FromServer { ActorDespawn(u32), /// We need to update an actor ActorControl(u32, ActorControl), - /// We need to update an actor's target' + /// We need to update an actor's target ActorControlTarget(u32, ActorControlTarget), + /// We need to update the player actor + ActorControlSelf(ActorControlSelf), /// Spawn an NPC SpawnNPC(NpcSpawn), } diff --git a/src/world/connection.rs b/src/world/connection.rs index 4e16633..9db73ca 100644 --- a/src/world/connection.rs +++ b/src/world/connection.rs @@ -32,6 +32,11 @@ use super::{ lua::Task, }; +#[derive(Debug, Default, Clone)] +pub struct TeleportQuery { + pub aetheryte_id: u16, +} + #[derive(Debug, Default, Clone)] pub struct PlayerData { // Static data @@ -52,6 +57,8 @@ pub struct PlayerData { pub rotation: f32, pub zone_id: u16, pub inventory: Inventory, + + pub teleport_query: TeleportQuery, } /// Represents a single connection between an instance of the client and the world server @@ -363,6 +370,35 @@ impl ZoneConnection { self.change_zone(territory_type as u16).await; } + pub async fn warp_aetheryte(&mut self, aetheryte_id: u32) { + tracing::info!("Warping to aetheryte {}", aetheryte_id); + + let territory_type; + // find the pop range on the other side + { + let mut game_data = self.gamedata.lock().unwrap(); + let (pop_range_id, zone_id) = game_data + .get_aetheryte(aetheryte_id) + .expect("Failed to find the aetheryte!"); + + let new_zone = Zone::load(&mut game_data.game_data, zone_id); + + // find it on the other side + let (object, _) = new_zone.find_pop_range(pop_range_id).unwrap(); + + // set the exit position + self.exit_position = Some(Position { + x: object.transform.translation[0], + y: object.transform.translation[1], + z: object.transform.translation[2], + }); + + territory_type = zone_id; + } + + self.change_zone(territory_type as u16).await; + } + pub async fn change_weather(&mut self, new_weather_id: u16) { let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::WeatherId, @@ -519,6 +555,9 @@ impl ZoneConnection { self.player_data.classjob_id = *classjob_id; self.update_class_info().await; } + Task::WarpAetheryte { aetheryte_id } => { + self.warp_aetheryte(*aetheryte_id).await; + } } } player.queued_tasks.clear(); diff --git a/src/world/lua.rs b/src/world/lua.rs index b750225..48052ea 100644 --- a/src/world/lua.rs +++ b/src/world/lua.rs @@ -10,7 +10,7 @@ use crate::{ packet::{PacketSegment, SegmentData, SegmentType}, }; -use super::{PlayerData, StatusEffects, Zone}; +use super::{PlayerData, StatusEffects, Zone, connection::TeleportQuery}; pub enum Task { ChangeTerritory { zone_id: u16 }, @@ -19,6 +19,7 @@ pub enum Task { BeginLogOut, FinishEvent { handler_id: u32 }, SetClassJob { classjob_id: u8 }, + WarpAetheryte { aetheryte_id: u32 }, } #[derive(Default)] @@ -129,6 +130,10 @@ impl LuaPlayer { fn set_classjob(&mut self, classjob_id: u8) { self.queued_tasks.push(Task::SetClassJob { classjob_id }); } + + fn warp_aetheryte(&mut self, aetheryte_id: u32) { + self.queued_tasks.push(Task::WarpAetheryte { aetheryte_id }); + } } impl UserData for LuaPlayer { @@ -181,6 +186,10 @@ impl UserData for LuaPlayer { this.set_classjob(classjob_id); Ok(()) }); + methods.add_method_mut("warp_aetheryte", |_, this, aetheryte_id: u32| { + this.warp_aetheryte(aetheryte_id); + Ok(()) + }); } fn add_fields>(fields: &mut F) { @@ -190,6 +199,16 @@ impl UserData for LuaPlayer { object_type: 0, }) }); + + fields.add_field_method_get("teleport_query", |_, this| { + Ok(this.player_data.teleport_query.clone()) + }); + } +} + +impl UserData for TeleportQuery { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("aetheryte_id", |_, this| Ok(this.aetheryte_id)); } } diff --git a/src/world/server.rs b/src/world/server.rs index d7d8a04..9912899 100644 --- a/src/world/server.rs +++ b/src/world/server.rs @@ -4,7 +4,7 @@ use tokio::sync::mpsc::Receiver; use crate::{ common::{ObjectId, Position}, ipc::zone::{ - ActorControl, ActorControlCategory, ActorControlTarget, BattleNpcSubKind, + ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget, BattleNpcSubKind, ClientTriggerCommand, CommonSpawn, NpcSpawn, ObjectKind, }, }; @@ -208,13 +208,28 @@ pub async fn server_main_loop(mut recv: Receiver) -> Result<(), std::i for (id, (handle, _)) in &mut data.clients { let id = *id; - // there's no reason to tell the actor what it just did + tracing::info!("{:#?}", trigger); + + // handle player-to-server actions if id == from_id { + match &trigger.trigger { + ClientTriggerCommand::TeleportQuery { aetheryte_id } => { + let msg = FromServer::ActorControlSelf(ActorControlSelf { + category: ActorControlCategory::TeleportStart { + insufficient_gil: 0, + aetheryte_id: *aetheryte_id, + }, + }); + + if handle.send(msg).is_err() { + to_remove.push(id); + } + } + _ => {} + } continue; } - tracing::info!("{:#?}", trigger); - match &trigger.trigger { ClientTriggerCommand::SetTarget { actor_id } => { let msg = FromServer::ActorControlTarget(