mirror of
https://github.com/redstrate/Kawari.git
synced 2025-07-23 13:07:45 +00:00
Implement walk-in trigger events in S9 (#133)
Implement walk-in trigger events, and more specifically, the teleporter pads in Solution Nine. Include a buildfix in kawari-world.rs as well.
This commit is contained in:
parent
2acbfe6df8
commit
468ca97257
14 changed files with 334 additions and 45 deletions
|
@ -334,6 +334,11 @@
|
|||
"name": "SetSearchComment",
|
||||
"opcode": 512,
|
||||
"size": 216
|
||||
},
|
||||
{
|
||||
"name": "WalkInEvent",
|
||||
"opcode": 316,
|
||||
"size": 24
|
||||
}
|
||||
],
|
||||
"ClientZoneIpcType": [
|
||||
|
@ -496,6 +501,11 @@
|
|||
"name": "EquipGearset",
|
||||
"opcode": 101,
|
||||
"size": 72
|
||||
},
|
||||
{
|
||||
"name": "StartWalkInEvent",
|
||||
"opcode": 607,
|
||||
"size": 24
|
||||
}
|
||||
],
|
||||
"ServerLobbyIpcType": [
|
||||
|
|
|
@ -375,6 +375,15 @@ generic_currency_exchange = {
|
|||
-- 3539075, -- Dibourdier <Mahjong Vendor> doesn't respond when interacted with right now, probably needs special handling
|
||||
}
|
||||
|
||||
solution_nine_teleporters = {
|
||||
4194305, -- Teleporter from eastern Aetheryte Plaza to Recreation Zone
|
||||
4194306, -- Teleporter from Recreation Zone to eastern Aetheryte Plaza
|
||||
4194307, -- Teleporter from northern Aetheryte Plaza to Government Sector
|
||||
4194308, -- Teleporter from Government Sector to northern Aetheryte Plaza
|
||||
4194309, -- Teleporter from Nexus Arcade ground floor to upper balcony
|
||||
4194310, -- Teleporter from upper balcony to Nexus Arcade ground floor
|
||||
}
|
||||
|
||||
-- Not custom in the sense of non-SQEX content, just going based off the directory name
|
||||
custom0_events = {
|
||||
[720916] = "cmndefinnbed_00020.lua",
|
||||
|
@ -397,6 +406,7 @@ TOSORT_DIR = "events/tosort/"
|
|||
OPENING_DIR = "events/quest/opening/"
|
||||
CUSTOM0_DIR = "events/custom/000/"
|
||||
CUSTOM1_DIR = "events/custom/001/"
|
||||
TRIGGER_DIR = "events/walkin_trigger/"
|
||||
|
||||
for _, event_id in pairs(generic_warps) do
|
||||
registerEvent(event_id, "events/common/GenericWarp.lua")
|
||||
|
@ -441,3 +451,7 @@ end
|
|||
for event_id, script_file in pairs(quests) do
|
||||
registerEvent(event_id, OPENING_DIR..script_file)
|
||||
end
|
||||
|
||||
for _, event_id in pairs(solution_nine_teleporters) do
|
||||
registerEvent(event_id, TRIGGER_DIR.."SolutionNineTeleporter.lua")
|
||||
end
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
-- TODO: Do any of the sheets contain any of this info so we don't have to hardcode it?
|
||||
-- Yes, this table is ugly. Currently these values are complete unknowns.
|
||||
TELEPORTER_INFO = {
|
||||
[4194305] = { 10114730, 1965, 17743871, 2619867137, 2158200772 }, -- Teleporter from eastern Aetheryte Plaza to Recreation Zone
|
||||
[4194306] = { 10114817, 1966, 17711103, 2462253057, 2136702993 }, -- Teleporter from Recreation Zone to eastern Aetheryte Plaza
|
||||
[4194307] = { 10114878, 1967, 17694720, 2158166017, 1782416562 }, -- Teleporter from northern Aetheryte Plaza to Government Sector
|
||||
[4194308] = { 10114891, 1968, 17727487, 2136670209, 1896185871 }, -- Teleporter from Government Sector to northern Aetheryte Plaza
|
||||
[4194309] = { 10114905, 1969, 11804671, 1665204225, 1989510302 }, -- Teleporter from Nexus Arcade ground floor to upper balcony
|
||||
[4194310] = { 10114944, 1970, 11837439, 1670053889, 2009628707 }, -- Teleporter from upper balcony to Nexus Arcade ground floor
|
||||
}
|
||||
|
||||
EVENT_ARG = {
|
||||
[4194305] = 10611851,
|
||||
[4194306] = 10611861,
|
||||
[4194307] = 10611862,
|
||||
[4194308] = 10611864,
|
||||
[4194309] = 10611868,
|
||||
[4194310] = 10611881,
|
||||
}
|
||||
|
||||
function onEnterTrigger(player)
|
||||
player:do_solnine_teleporter(EVENT_ID, table.unpack(TELEPORTER_INFO[EVENT_ID]))
|
||||
-- TODO: We should probably take the event arg in Event::new on the rust side, but this works for now.
|
||||
player:finish_event(EVENT_ID, EVENT_ARG[EVENT_ID], 1)
|
||||
end
|
|
@ -3,15 +3,17 @@ use std::sync::{Arc, Mutex};
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use kawari::common::Position;
|
||||
use kawari::common::{GameData, INVALID_OBJECT_ID, ItemInfoQuery, timestamp_secs};
|
||||
use kawari::common::{
|
||||
GameData, INVALID_OBJECT_ID, ItemInfoQuery, ObjectId, ObjectTypeId, timestamp_secs,
|
||||
};
|
||||
use kawari::config::get_config;
|
||||
use kawari::inventory::{
|
||||
BuyBackItem, ContainerType, CurrencyKind, Item, ItemOperationKind, get_container_type,
|
||||
};
|
||||
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
|
||||
use kawari::ipc::zone::{
|
||||
ActorControlCategory, ActorControlSelf, ItemOperation, PlayerEntry, PlayerSpawn, PlayerStatus,
|
||||
SocialList,
|
||||
ActorControl, ActorControlCategory, ActorControlSelf, ItemOperation, PlayerEntry, PlayerSpawn,
|
||||
PlayerStatus, SocialList,
|
||||
};
|
||||
|
||||
use kawari::ipc::zone::{
|
||||
|
@ -27,8 +29,8 @@ use kawari::world::{
|
|||
ChatHandler, ExtraLuaState, LuaZone, ObsfucationData, Zone, ZoneConnection, load_init_script,
|
||||
};
|
||||
use kawari::world::{
|
||||
ClientHandle, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects, ToServer,
|
||||
WorldDatabase, handle_custom_ipc, server_main_loop,
|
||||
ClientHandle, EventFinishType, FromServer, LuaPlayer, PlayerData, ServerHandle, StatusEffects,
|
||||
ToServer, WorldDatabase, handle_custom_ipc, server_main_loop,
|
||||
};
|
||||
use kawari::{
|
||||
ERR_INVENTORY_ADD_FAILED, LogMessageType, RECEIVE_BUFFER_SIZE, TITLE_UNLOCK_BITMASK_SIZE,
|
||||
|
@ -463,9 +465,6 @@ async fn client_loop(
|
|||
}
|
||||
}
|
||||
}
|
||||
ClientZoneIpcData::Unk2 { .. } => {
|
||||
// no-op
|
||||
}
|
||||
ClientZoneIpcData::Unk3 { .. } => {
|
||||
// no-op
|
||||
}
|
||||
|
@ -916,15 +915,15 @@ async fn client_loop(
|
|||
} else {
|
||||
tracing::error!(ERR_INVENTORY_ADD_FAILED);
|
||||
connection.send_message(ERR_INVENTORY_ADD_FAILED).await;
|
||||
connection.event_finish(*event_id, 0).await;
|
||||
connection.event_finish(*event_id, 0, EventFinishType::Normal).await;
|
||||
}
|
||||
} else {
|
||||
connection.send_message("Insufficient gil to buy item. Nice try bypassing the client-side check!").await;
|
||||
connection.event_finish(*event_id, 0).await;
|
||||
connection.event_finish(*event_id, 0, EventFinishType::Normal).await;
|
||||
}
|
||||
} else {
|
||||
connection.send_message("Unable to find shop item, this is a bug in Kawari!").await;
|
||||
connection.event_finish(*event_id, 0).await;
|
||||
connection.event_finish(*event_id, 0, EventFinishType::Normal).await;
|
||||
}
|
||||
} else if *buy_sell_mode == SELL {
|
||||
let storage = get_container_type(*item_index).unwrap();
|
||||
|
@ -1033,11 +1032,11 @@ async fn client_loop(
|
|||
connection.event_scene(&target_id, *event_id, 10, 8193, params).await;
|
||||
} else {
|
||||
connection.send_message("Unable to find shop item, this is a bug in Kawari!").await;
|
||||
connection.event_finish(*event_id, 0).await;
|
||||
connection.event_finish(*event_id, 0, EventFinishType::Normal).await;
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Received unknown transaction mode {buy_sell_mode}!");
|
||||
connection.event_finish(*event_id, 0).await;
|
||||
connection.event_finish(*event_id, 0, EventFinishType::Normal).await;
|
||||
}
|
||||
}
|
||||
ClientZoneIpcData::StartTalkEvent { actor_id, event_id } => {
|
||||
|
@ -1319,6 +1318,22 @@ async fn client_loop(
|
|||
tracing::info!("Client tried to equip a gearset!");
|
||||
connection.send_message("Gearsets are not yet implemented.").await;
|
||||
}
|
||||
ClientZoneIpcData::StartWalkInEvent { event_arg, event_id, .. } => {
|
||||
// Yes, an ActorControl is sent here, not an ActorControlSelf!
|
||||
connection.actor_control(connection.player_data.actor_id, ActorControl {
|
||||
category: ActorControlCategory::ToggleWeapon {
|
||||
shown: false,
|
||||
}
|
||||
}).await;
|
||||
connection.send_unk18([64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).await;
|
||||
let actor_id = ObjectTypeId { object_id: ObjectId(connection.player_data.actor_id), object_type: 0 };
|
||||
connection.start_event(actor_id, *event_id, 10, *event_arg).await;
|
||||
|
||||
// begin walk-in trigger function if it exists
|
||||
if let Some(event) = connection.event.as_mut() {
|
||||
event.enter_trigger(&mut lua_player);
|
||||
}
|
||||
}
|
||||
ClientZoneIpcData::Unknown { .. } => {
|
||||
tracing::warn!("Unknown packet {:?} recieved, this should be handled!", data.op_code);
|
||||
}
|
||||
|
@ -1391,6 +1406,7 @@ async fn client_loop(
|
|||
FromServer::ActorEquip(actor_id, main_weapon_id, model_ids) => connection.update_equip(actor_id, main_weapon_id, model_ids).await,
|
||||
FromServer::ReplayPacket(segment) => connection.send_segment(segment).await,
|
||||
FromServer::LoseEffect(effect_id, effect_param, effect_source_actor_id) => connection.lose_effect(effect_id, effect_param, effect_source_actor_id, &mut lua_player).await,
|
||||
FromServer::Unk18(unk) => connection.send_unk18(unk).await,
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
|
|
|
@ -72,6 +72,11 @@ pub enum ActorControlCategory {
|
|||
#[bw(map = write_bool_as::<u32>)]
|
||||
unlocked: bool,
|
||||
},
|
||||
#[brw(magic = 0x8Au16)]
|
||||
EventRelatedUnk3 {
|
||||
#[brw(pad_before = 2)] //padding
|
||||
event_id: u32,
|
||||
},
|
||||
#[brw(magic = 0xCBu16)]
|
||||
TeleportStart {
|
||||
#[brw(pad_before = 2)] //padding
|
||||
|
@ -83,6 +88,11 @@ pub enum ActorControlCategory {
|
|||
#[brw(pad_before = 2)] // padding
|
||||
speed: u16,
|
||||
},
|
||||
#[brw(magic = 0xECu16)]
|
||||
WalkInTriggerRelatedUnk3 {
|
||||
#[brw(pad_before = 2)] // padding
|
||||
unk1: u32,
|
||||
},
|
||||
#[brw(magic = 0x386u16)]
|
||||
SetFestival {
|
||||
#[brw(pad_before = 2)] // padding
|
||||
|
@ -177,6 +187,20 @@ pub enum ActorControlCategory {
|
|||
unk2: u32,
|
||||
unk3: u32,
|
||||
},
|
||||
#[brw(magic = 0x104u16)]
|
||||
WalkInTriggerRelatedUnk2 {
|
||||
#[brw(pad_before = 2)] // padding
|
||||
unk1: u32,
|
||||
unk2: u32,
|
||||
unk3: u32,
|
||||
/// Usually 7?
|
||||
unk4: u32,
|
||||
},
|
||||
#[brw(magic = 0x107u16)]
|
||||
WalkInTriggerRelatedUnk1 {
|
||||
#[brw(pad_before = 2)] // padding
|
||||
unk1: u32,
|
||||
},
|
||||
Unknown {
|
||||
category: u16,
|
||||
#[brw(pad_before = 2)] // padding
|
||||
|
|
|
@ -50,6 +50,11 @@ pub enum ClientTriggerCommand {
|
|||
aetheryte_id: u32,
|
||||
// TODO: fill out the rest
|
||||
},
|
||||
#[brw(magic = 0x25eu16)]
|
||||
WalkInTriggerFinished {
|
||||
#[brw(pad_before = 2)]
|
||||
unk1: u32,
|
||||
},
|
||||
#[brw(magic = 0x033Eu16)]
|
||||
EventRelatedUnk {
|
||||
// seen in haircut event
|
||||
|
|
|
@ -586,6 +586,18 @@ pub enum ServerZoneIpcData {
|
|||
// TODO: fill this out
|
||||
unk1: [u8; 968],
|
||||
},
|
||||
/// Sent by the server when walking over a trigger (e.g. the teleport pads in Solution Nine).
|
||||
/// All of these fields are currently unknown in meaning.
|
||||
#[br(pre_assert(*magic == ServerZoneIpcType::WalkInEvent))]
|
||||
WalkInEvent {
|
||||
unk1: u32,
|
||||
unk2: u16,
|
||||
#[brw(pad_before = 2)]
|
||||
unk3: u32,
|
||||
unk4: u32,
|
||||
#[brw(pad_after = 4)]
|
||||
unk5: u32,
|
||||
},
|
||||
Unknown {
|
||||
#[br(count = size - 32)]
|
||||
unk: Vec<u8>,
|
||||
|
@ -791,6 +803,13 @@ pub enum ClientZoneIpcData {
|
|||
#[brw(pad_after = 2)]
|
||||
unk2: u16,
|
||||
},
|
||||
#[br(pre_assert(*magic == ClientZoneIpcType::StartWalkInEvent))]
|
||||
StartWalkInEvent {
|
||||
event_arg: u32,
|
||||
event_id: u32,
|
||||
#[brw(pad_after = 4)]
|
||||
pos: Position,
|
||||
},
|
||||
#[br(pre_assert(*magic == ClientZoneIpcType::ContentFinderAction))]
|
||||
ContentFinderAction { unk1: [u8; 8] },
|
||||
Unknown {
|
||||
|
@ -1161,6 +1180,16 @@ mod tests {
|
|||
ServerZoneIpcType::Unk17,
|
||||
ServerZoneIpcData::Unk17 { unk1: [0; 968] },
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::WalkInEvent,
|
||||
ServerZoneIpcData::WalkInEvent {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
unk3: 0,
|
||||
unk4: 0,
|
||||
unk5: 0,
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
for (opcode, data) in &ipc_types {
|
||||
|
@ -1339,6 +1368,18 @@ mod tests {
|
|||
ClientZoneIpcType::UnkCall2,
|
||||
ClientZoneIpcData::UnkCall2 { unk1: [0; 8] },
|
||||
),
|
||||
(
|
||||
ClientZoneIpcType::StartWalkInEvent,
|
||||
ClientZoneIpcData::StartWalkInEvent {
|
||||
event_arg: 0,
|
||||
event_id: 0,
|
||||
pos: Position {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
},
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
for (opcode, data) in &ipc_types {
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
common::ItemInfoQuery,
|
||||
inventory::{Item, Storage},
|
||||
ipc::zone::{ChatMessage, GameMasterRank},
|
||||
world::ToServer,
|
||||
world::{EventFinishType, ToServer},
|
||||
};
|
||||
|
||||
use super::ZoneConnection;
|
||||
|
@ -110,7 +110,9 @@ impl ChatHandler {
|
|||
}
|
||||
"!finishevent" => {
|
||||
if let Some(event) = &connection.event {
|
||||
connection.event_finish(event.id, 0).await;
|
||||
connection
|
||||
.event_finish(event.id, 0, EventFinishType::Normal)
|
||||
.await;
|
||||
connection
|
||||
.send_message("Current event forcefully finished.")
|
||||
.await;
|
||||
|
|
|
@ -50,6 +50,7 @@ pub enum FromServer {
|
|||
ReplayPacket(PacketSegment<ServerZoneIpcSegment>),
|
||||
/// The player should lose this effect.
|
||||
LoseEffect(u16, u16, ObjectId),
|
||||
Unk18([u8; 16]),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -39,8 +39,8 @@ use crate::{
|
|||
};
|
||||
|
||||
use super::{
|
||||
Actor, CharacterData, EffectsBuilder, Event, LuaPlayer, StatusEffects, ToServer, WorldDatabase,
|
||||
Zone,
|
||||
Actor, CharacterData, EffectsBuilder, Event, EventFinishType, LuaPlayer, StatusEffects,
|
||||
ToServer, WorldDatabase, Zone,
|
||||
common::{ClientId, ServerHandle},
|
||||
load_init_script,
|
||||
lua::Task,
|
||||
|
@ -860,7 +860,11 @@ impl ZoneConnection {
|
|||
self.warp(*warp_id).await;
|
||||
}
|
||||
Task::BeginLogOut => self.begin_log_out().await,
|
||||
Task::FinishEvent { handler_id, arg } => self.event_finish(*handler_id, *arg).await,
|
||||
Task::FinishEvent {
|
||||
handler_id,
|
||||
arg,
|
||||
finish_type,
|
||||
} => self.event_finish(*handler_id, *arg, *finish_type).await,
|
||||
Task::SetClassJob { classjob_id } => {
|
||||
self.player_data.classjob_id = *classjob_id;
|
||||
self.update_class_info().await;
|
||||
|
@ -1081,11 +1085,12 @@ impl ZoneConnection {
|
|||
"Unable to play event {event_id}, scene {:?}, scene_flags {scene_flags}!",
|
||||
scene
|
||||
);
|
||||
self.event_finish(event_id, 0).await;
|
||||
self.event_finish(event_id, 0, EventFinishType::Normal)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn event_finish(&mut self, handler_id: u32, arg: u32) {
|
||||
pub async fn event_finish(&mut self, handler_id: u32, arg: u32, finish_type: EventFinishType) {
|
||||
self.player_data.target_actorid = ObjectTypeId::default();
|
||||
// sent event finish
|
||||
{
|
||||
|
@ -1111,22 +1116,29 @@ impl ZoneConnection {
|
|||
}
|
||||
|
||||
// give back control to the player
|
||||
{
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::Unk18,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::Unk18 { unk: [0; 16] },
|
||||
..Default::default()
|
||||
};
|
||||
let unk = match finish_type {
|
||||
EventFinishType::Normal => [0; 16],
|
||||
EventFinishType::Jumping => [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: self.player_data.actor_id,
|
||||
target_actor: self.player_data.actor_id,
|
||||
segment_type: SegmentType::Ipc,
|
||||
data: SegmentData::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
self.send_unk18(unk).await;
|
||||
}
|
||||
|
||||
pub async fn send_unk18(&mut self, unk: [u8; 16]) {
|
||||
let ipc = ServerZoneIpcSegment {
|
||||
op_code: ServerZoneIpcType::Unk18,
|
||||
timestamp: timestamp_secs(),
|
||||
data: ServerZoneIpcData::Unk18 { unk },
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.send_segment(PacketSegment {
|
||||
source_actor: self.player_data.actor_id,
|
||||
target_actor: self.player_data.actor_id,
|
||||
segment_type: SegmentType::Ipc,
|
||||
data: SegmentData::Ipc { data: ipc },
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn send_inventory_ack(&mut self, sequence: u32, action_type: u16) {
|
||||
|
@ -1843,7 +1855,8 @@ impl ZoneConnection {
|
|||
|
||||
if should_cancel {
|
||||
// give control back to the player so they aren't stuck
|
||||
self.event_finish(event_id, 0).await;
|
||||
self.event_finish(event_id, 0, EventFinishType::Normal)
|
||||
.await;
|
||||
self.send_message(&format!(
|
||||
"Event {event_id} tried to start, but it doesn't have a script associated with it!"
|
||||
))
|
||||
|
|
|
@ -10,6 +10,12 @@ pub struct Event {
|
|||
pub id: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum EventFinishType {
|
||||
Normal,
|
||||
Jumping,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn new(id: u32, path: &str) -> Self {
|
||||
let lua = Lua::new();
|
||||
|
@ -52,6 +58,24 @@ impl Event {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn enter_trigger(&mut self, player: &mut LuaPlayer) {
|
||||
let mut run_script = || {
|
||||
self.lua.scope(|scope| {
|
||||
let player = scope.create_userdata_ref_mut(player)?;
|
||||
|
||||
let func: Function = self.lua.globals().get("onEnterTrigger")?;
|
||||
|
||||
func.call::<()>(player)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
|
||||
if let Err(err) = run_script() {
|
||||
tracing::warn!("Syntax error in {}: {:?}", self.file_name, err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scene_finished(&mut self, player: &mut LuaPlayer, scene: u16) {
|
||||
let mut run_script = || {
|
||||
self.lua.scope(|scope| {
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
},
|
||||
opcodes::ServerZoneIpcType,
|
||||
packet::{PacketSegment, SegmentData, SegmentType},
|
||||
world::ExtraLuaState,
|
||||
world::{EventFinishType, ExtraLuaState},
|
||||
};
|
||||
use mlua::{FromLua, Lua, LuaSerdeExt, UserData, UserDataFields, UserDataMethods, Value};
|
||||
|
||||
|
@ -35,6 +35,7 @@ pub enum Task {
|
|||
FinishEvent {
|
||||
handler_id: u32,
|
||||
arg: u32,
|
||||
finish_type: EventFinishType,
|
||||
},
|
||||
SetClassJob {
|
||||
classjob_id: u8,
|
||||
|
@ -210,7 +211,7 @@ impl LuaPlayer {
|
|||
let error_message = "Unsupported amount of parameters in play_scene! This is likely a bug in your script! Cancelling event...".to_string();
|
||||
tracing::warn!(error_message);
|
||||
self.send_message(&error_message, 0);
|
||||
self.finish_event(event_id, 0);
|
||||
self.finish_event(event_id, 0, EventFinishType::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,9 +285,12 @@ impl LuaPlayer {
|
|||
self.queued_tasks.push(Task::BeginLogOut);
|
||||
}
|
||||
|
||||
fn finish_event(&mut self, handler_id: u32, arg: u32) {
|
||||
self.queued_tasks
|
||||
.push(Task::FinishEvent { handler_id, arg });
|
||||
fn finish_event(&mut self, handler_id: u32, arg: u32, finish_type: EventFinishType) {
|
||||
self.queued_tasks.push(Task::FinishEvent {
|
||||
handler_id,
|
||||
arg,
|
||||
finish_type,
|
||||
});
|
||||
}
|
||||
|
||||
fn set_classjob(&mut self, classjob_id: u8) {
|
||||
|
@ -455,6 +459,53 @@ impl LuaPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
fn do_solnine_teleporter(
|
||||
&mut self,
|
||||
event_id: u32,
|
||||
unk1: u32,
|
||||
unk2: u16,
|
||||
unk3: u32,
|
||||
unk4: u32,
|
||||
unk5: u32,
|
||||
) {
|
||||
let packets_to_send = [
|
||||
(
|
||||
ServerZoneIpcType::ActorControlSelf,
|
||||
ServerZoneIpcData::ActorControlSelf(ActorControlSelf {
|
||||
category: ActorControlCategory::EventRelatedUnk3 { event_id },
|
||||
}),
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::WalkInEvent,
|
||||
ServerZoneIpcData::WalkInEvent {
|
||||
unk1,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
unk5,
|
||||
},
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::ActorControlSelf,
|
||||
ServerZoneIpcData::ActorControlSelf(ActorControlSelf {
|
||||
category: ActorControlCategory::WalkInTriggerRelatedUnk3 {
|
||||
unk1: 1, // Sometimes the server sends 2 for this, but it's still completely unknown what it means.
|
||||
},
|
||||
}),
|
||||
),
|
||||
(
|
||||
ServerZoneIpcType::ActorControlSelf,
|
||||
ServerZoneIpcData::ActorControlSelf(ActorControlSelf {
|
||||
category: ActorControlCategory::WalkInTriggerRelatedUnk1 { unk1: 1 },
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
for (op_code, data) in packets_to_send {
|
||||
self.create_segment_self(op_code, data);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_exp(&mut self, amount: u32) {
|
||||
self.queued_tasks.push(Task::AddExp { amount });
|
||||
}
|
||||
|
@ -564,10 +615,20 @@ impl UserData for LuaPlayer {
|
|||
this.begin_log_out();
|
||||
Ok(())
|
||||
});
|
||||
methods.add_method_mut("finish_event", |_, this, (handler_id, arg): (u32, u32)| {
|
||||
this.finish_event(handler_id, arg);
|
||||
Ok(())
|
||||
});
|
||||
methods.add_method_mut(
|
||||
"finish_event",
|
||||
|lua, this, (handler_id, arg, finish_type): (u32, u32, Value)| {
|
||||
// It's desirable for finish_type to be optional since we do normal finishes 99% of the time.
|
||||
let finish_type: u32 = lua.from_value(finish_type).unwrap_or(0);
|
||||
let finish_type = match finish_type {
|
||||
0 => EventFinishType::Normal,
|
||||
1 => EventFinishType::Jumping,
|
||||
_ => EventFinishType::Normal,
|
||||
};
|
||||
this.finish_event(handler_id, arg, finish_type);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
methods.add_method_mut("set_classjob", |_, this, classjob_id: u8| {
|
||||
this.set_classjob(classjob_id);
|
||||
Ok(())
|
||||
|
@ -642,6 +703,13 @@ impl UserData for LuaPlayer {
|
|||
this.set_inn_wakeup(watched);
|
||||
Ok(())
|
||||
});
|
||||
methods.add_method_mut(
|
||||
"do_solnine_teleporter",
|
||||
|_, this, (event_id, unk1, unk2, unk3, unk4, unk5): (u32, u32, u16, u32, u32, u32)| {
|
||||
this.do_solnine_teleporter(event_id, unk1, unk2, unk3, unk4, unk5);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
|
|
|
@ -15,6 +15,7 @@ pub use lua::{EffectsBuilder, LuaPlayer, LuaZone, load_init_script};
|
|||
|
||||
mod event;
|
||||
pub use event::Event;
|
||||
pub use event::EventFinishType;
|
||||
|
||||
mod actor;
|
||||
pub use actor::Actor;
|
||||
|
|
|
@ -568,6 +568,51 @@ pub async fn server_main_loop(mut recv: Receiver<ToServer>) -> Result<(), std::i
|
|||
to_remove.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
if let ClientTriggerCommand::WalkInTriggerFinished { .. } = &trigger.trigger
|
||||
{
|
||||
// This is where we finally release the client after the walk-in trigger.
|
||||
let msg = FromServer::Unk18([0; 16]);
|
||||
|
||||
if handle.send(msg).is_err() {
|
||||
to_remove.push(id);
|
||||
}
|
||||
|
||||
let msg = FromServer::ActorControlSelf(ActorControlSelf {
|
||||
category: ActorControlCategory::WalkInTriggerRelatedUnk1 {
|
||||
unk1: 0,
|
||||
},
|
||||
});
|
||||
|
||||
if handle.send(msg).is_err() {
|
||||
to_remove.push(id);
|
||||
}
|
||||
|
||||
// Yes, this is actually sent every time the trigger event finishes...
|
||||
let msg = FromServer::ActorControlSelf(ActorControlSelf {
|
||||
category: ActorControlCategory::CompanionUnlock {
|
||||
unk1: 0,
|
||||
unk2: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if handle.send(msg).is_err() {
|
||||
to_remove.push(id);
|
||||
}
|
||||
|
||||
let msg = FromServer::ActorControlSelf(ActorControlSelf {
|
||||
category: ActorControlCategory::WalkInTriggerRelatedUnk2 {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
unk3: 0,
|
||||
unk4: 7,
|
||||
},
|
||||
});
|
||||
|
||||
if handle.send(msg).is_err() {
|
||||
to_remove.push(id);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue