1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-05-12 14:47:46 +00:00

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.
This commit is contained in:
Joshua Goins 2025-05-11 10:12:02 -04:00
parent 1914531d89
commit 94ed036431
12 changed files with 134 additions and 15 deletions

8
Cargo.lock generated
View file

@ -575,7 +575,7 @@ dependencies = [
[[package]] [[package]]
name = "icarus" name = "icarus"
version = "0.0.0" 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 = [ dependencies = [
"physis", "physis",
] ]
@ -1025,7 +1025,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]] [[package]]
name = "physis" name = "physis"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/redstrate/physis#309726163a0d76f26831236f43740a2c8bd1b01d" source = "git+https://github.com/redstrate/physis#76120b2dd0843c16af648f7b18ba0f92c5d56460"
dependencies = [ dependencies = [
"binrw", "binrw",
"bitflags", "bitflags",
@ -1472,9 +1472,9 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.6.2" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes",

View file

@ -104,4 +104,4 @@ rkon = { version = "0.1" }
tower-http = { version = "0.6", features = ["fs"] } tower-http = { version = "0.6", features = ["fs"] }
# excel sheet data # 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 }

View file

@ -20,6 +20,7 @@ end
-- Actions -- Actions
registerAction(3, "actions/Sprint.lua") registerAction(3, "actions/Sprint.lua")
registerAction(5, "actions/Teleport.lua")
registerAction(9, "actions/FastBlade.lua") registerAction(9, "actions/FastBlade.lua")
-- Items -- Items

View file

@ -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

View file

@ -9,9 +9,9 @@ use kawari::config::get_config;
use kawari::inventory::Item; use kawari::inventory::Item;
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
use kawari::ipc::zone::{ use kawari::ipc::zone::{
ActionEffect, ActionResult, ClientZoneIpcData, CommonSpawn, EffectKind, EventStart, ActionEffect, ActionResult, ClientTriggerCommand, ClientZoneIpcData, CommonSpawn, EffectKind,
GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, EventStart, GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData,
SocialListRequestType, ServerZoneIpcSegment, SocialListRequestType,
}; };
use kawari::ipc::zone::{ use kawari::ipc::zone::{
ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList, ActorControlCategory, ActorControlSelf, PlayerEntry, PlayerSpawn, PlayerStatus, SocialList,
@ -371,6 +371,11 @@ async fn client_loop(
connection.exit_rotation = None; connection.exit_rotation = None;
} }
ClientZoneIpcData::ClientTrigger(trigger) => { 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 // 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; 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::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::ActorControlTarget(actor_id, actor_control) => connection.actor_control_target(actor_id, actor_control).await,
FromServer::SpawnNPC(npc) => connection.send_npc(npc).await, FromServer::SpawnNPC(npc) => connection.send_npc(npc).await,
FromServer::ActorControlSelf(actor_control) => connection.actor_control_self(actor_control).await,
}, },
None => break, None => break,
} }

View file

@ -1,3 +1,4 @@
use icarus::Aetheryte::AetheryteSheet;
use icarus::ClassJob::ClassJobSheet; use icarus::ClassJob::ClassJobSheet;
use icarus::World::WorldSheet; use icarus::World::WorldSheet;
use icarus::{Tribe::TribeSheet, Warp::WarpSheet}; use icarus::{Tribe::TribeSheet, Warp::WarpSheet};
@ -106,4 +107,15 @@ impl GameData {
Some((*pop_range_id, *zone_id)) 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))
}
} }

View file

@ -63,6 +63,12 @@ pub enum ActorControlCategory {
#[bw(map = write_bool_as::<u32>)] #[bw(map = write_bool_as::<u32>)]
unlocked: bool, unlocked: bool,
}, },
#[brw(magic = 0xCBu16)]
TeleportStart {
#[brw(pad_before = 2)] //padding
insufficient_gil: u32,
aetheryte_id: u32,
},
} }
#[binrw] #[binrw]

View file

@ -24,6 +24,12 @@ pub enum ClientTriggerCommand {
unk1: u32, unk1: u32,
pose: u32, pose: u32,
}, },
#[brw(magic = 0xCAu16)]
TeleportQuery {
#[brw(pad_before = 2)]
aetheryte_id: u32,
// TODO: fill out the rest
},
} }
#[binrw] #[binrw]

View file

@ -10,7 +10,9 @@ use tokio::sync::mpsc::Sender;
use crate::{ use crate::{
common::Position, common::Position,
ipc::zone::{ActorControl, ActorControlTarget, ClientTrigger, CommonSpawn, NpcSpawn}, ipc::zone::{
ActorControl, ActorControlSelf, ActorControlTarget, ClientTrigger, CommonSpawn, NpcSpawn,
},
}; };
use super::Actor; use super::Actor;
@ -29,8 +31,10 @@ pub enum FromServer {
ActorDespawn(u32), ActorDespawn(u32),
/// We need to update an actor /// We need to update an actor
ActorControl(u32, ActorControl), ActorControl(u32, ActorControl),
/// We need to update an actor's target' /// We need to update an actor's target
ActorControlTarget(u32, ActorControlTarget), ActorControlTarget(u32, ActorControlTarget),
/// We need to update the player actor
ActorControlSelf(ActorControlSelf),
/// Spawn an NPC /// Spawn an NPC
SpawnNPC(NpcSpawn), SpawnNPC(NpcSpawn),
} }

View file

@ -32,6 +32,11 @@ use super::{
lua::Task, lua::Task,
}; };
#[derive(Debug, Default, Clone)]
pub struct TeleportQuery {
pub aetheryte_id: u16,
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct PlayerData { pub struct PlayerData {
// Static data // Static data
@ -52,6 +57,8 @@ pub struct PlayerData {
pub rotation: f32, pub rotation: f32,
pub zone_id: u16, pub zone_id: u16,
pub inventory: Inventory, pub inventory: Inventory,
pub teleport_query: TeleportQuery,
} }
/// Represents a single connection between an instance of the client and the world server /// 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; 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) { pub async fn change_weather(&mut self, new_weather_id: u16) {
let ipc = ServerZoneIpcSegment { let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::WeatherId, op_code: ServerZoneIpcType::WeatherId,
@ -519,6 +555,9 @@ impl ZoneConnection {
self.player_data.classjob_id = *classjob_id; self.player_data.classjob_id = *classjob_id;
self.update_class_info().await; self.update_class_info().await;
} }
Task::WarpAetheryte { aetheryte_id } => {
self.warp_aetheryte(*aetheryte_id).await;
}
} }
} }
player.queued_tasks.clear(); player.queued_tasks.clear();

View file

@ -10,7 +10,7 @@ use crate::{
packet::{PacketSegment, SegmentData, SegmentType}, packet::{PacketSegment, SegmentData, SegmentType},
}; };
use super::{PlayerData, StatusEffects, Zone}; use super::{PlayerData, StatusEffects, Zone, connection::TeleportQuery};
pub enum Task { pub enum Task {
ChangeTerritory { zone_id: u16 }, ChangeTerritory { zone_id: u16 },
@ -19,6 +19,7 @@ pub enum Task {
BeginLogOut, BeginLogOut,
FinishEvent { handler_id: u32 }, FinishEvent { handler_id: u32 },
SetClassJob { classjob_id: u8 }, SetClassJob { classjob_id: u8 },
WarpAetheryte { aetheryte_id: u32 },
} }
#[derive(Default)] #[derive(Default)]
@ -129,6 +130,10 @@ impl LuaPlayer {
fn set_classjob(&mut self, classjob_id: u8) { fn set_classjob(&mut self, classjob_id: u8) {
self.queued_tasks.push(Task::SetClassJob { classjob_id }); 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 { impl UserData for LuaPlayer {
@ -181,6 +186,10 @@ impl UserData for LuaPlayer {
this.set_classjob(classjob_id); this.set_classjob(classjob_id);
Ok(()) Ok(())
}); });
methods.add_method_mut("warp_aetheryte", |_, this, aetheryte_id: u32| {
this.warp_aetheryte(aetheryte_id);
Ok(())
});
} }
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
@ -190,6 +199,16 @@ impl UserData for LuaPlayer {
object_type: 0, 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<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("aetheryte_id", |_, this| Ok(this.aetheryte_id));
} }
} }

View file

@ -4,7 +4,7 @@ use tokio::sync::mpsc::Receiver;
use crate::{ use crate::{
common::{ObjectId, Position}, common::{ObjectId, Position},
ipc::zone::{ ipc::zone::{
ActorControl, ActorControlCategory, ActorControlTarget, BattleNpcSubKind, ActorControl, ActorControlCategory, ActorControlSelf, ActorControlTarget, BattleNpcSubKind,
ClientTriggerCommand, CommonSpawn, NpcSpawn, ObjectKind, ClientTriggerCommand, CommonSpawn, NpcSpawn, ObjectKind,
}, },
}; };
@ -208,13 +208,28 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
for (id, (handle, _)) in &mut data.clients { for (id, (handle, _)) in &mut data.clients {
let id = *id; 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 { 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; continue;
} }
tracing::info!("{:#?}", trigger);
match &trigger.trigger { match &trigger.trigger {
ClientTriggerCommand::SetTarget { actor_id } => { ClientTriggerCommand::SetTarget { actor_id } => {
let msg = FromServer::ActorControlTarget( let msg = FromServer::ActorControlTarget(