diff --git a/resources/opcodes.json b/resources/opcodes.json index fba615b..a96e31a 100644 --- a/resources/opcodes.json +++ b/resources/opcodes.json @@ -154,6 +154,16 @@ "name": "Delete", "opcode": 149, "size": 8 + }, + { + "name": "EventFinish", + "opcode": 375, + "size": 16 + }, + { + "name": "Unk18", + "opcode": 116, + "size": 16 } ], "ClientZoneIpcType": [ @@ -266,6 +276,16 @@ "name": "ItemOperation", "opcode": 583, "size": 48 + }, + { + "name": "StartTalkEvent", + "opcode": 300, + "size": 16 + }, + { + "name": "EventHandlerReturn", + "opcode": 281, + "size": 16 } ], "ServerChatIpcType": [ diff --git a/resources/scripts/Global.lua b/resources/scripts/Global.lua index 9a919fa..3b7981a 100644 --- a/resources/scripts/Global.lua +++ b/resources/scripts/Global.lua @@ -10,3 +10,9 @@ registerAction(9, "actions/FastBlade.lua") -- Items registerAction(6221, "items/Fantasia.lua") +-- Events +registerEvent(1245185, "opening/OpeningLimsaLominsa.lua") +registerEvent(1245186, "opening/OpeningGridania.lua") +registerEvent(1245187, "opening/OpeningUldah.lua") +registerEvent(131078, "warp/WarpInnGridania.lua") +registerEvent(131079, "warp/WarpInnLimsaLominsa.lua") diff --git a/resources/scripts/warp/WarpInnGridania.lua b/resources/scripts/warp/WarpInnGridania.lua new file mode 100644 index 0000000..d5bd580 --- /dev/null +++ b/resources/scripts/warp/WarpInnGridania.lua @@ -0,0 +1,10 @@ +--- TODO: find a way to hardcode it this way +EVENT_ID = 131079 -- TODO: wrong, i was testing in limsa + +function onTalk(actorId, player) + -- has inn access + -- player:play_scene(131079, 00001, 1, 0) + + -- doesn't have inn access + player:play_scene(actorId, EVENT_ID, 00002, 8192, 0) +end diff --git a/resources/scripts/warp/WarpInnLimsaLominsa.lua b/resources/scripts/warp/WarpInnLimsaLominsa.lua new file mode 100644 index 0000000..051e065 --- /dev/null +++ b/resources/scripts/warp/WarpInnLimsaLominsa.lua @@ -0,0 +1,10 @@ +--- TODO: find a way to hardcode it this way +EVENT_ID = 131079 + +function onTalk(actorId, player) + -- has inn access + player:play_scene(actorId, EVENT_ID, 00001, 8192, 0) + + -- doesn't have inn access + --player:play_scene(actorId, EVENT_ID, 00002, 8192, 0) +end diff --git a/src/bin/kawari-world.rs b/src/bin/kawari-world.rs index a2efc2f..2c5acc2 100644 --- a/src/bin/kawari-world.rs +++ b/src/bin/kawari-world.rs @@ -11,7 +11,7 @@ use kawari::inventory::{Inventory, Item}; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; use kawari::ipc::kawari::{CustomIpcData, CustomIpcSegment, CustomIpcType}; use kawari::ipc::zone::{ - ActionEffect, ActionResult, ClientZoneIpcData, EffectKind, GameMasterCommandType, + ActionEffect, ActionResult, ClientZoneIpcData, EffectKind, EventStart, GameMasterCommandType, GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, }; use kawari::ipc::zone::{ @@ -40,6 +40,7 @@ use tokio::task::JoinHandle; #[derive(Default)] struct ExtraLuaState { action_scripts: HashMap, + event_scripts: HashMap, } fn spawn_main_loop() -> (ServerHandle, JoinHandle<()>) { @@ -727,6 +728,119 @@ async fn client_loop( connection.player_data.inventory.process_action(action); connection.send_inventory(true).await; } + ClientZoneIpcData::StartTalkEvent { actor_id, event_id } => { + // load scene + { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::EventStart, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::EventStart(EventStart { + target_id: *actor_id, + event_id: *event_id, + event_type: 1, // talk? + ..Default::default() + }), + ..Default::default() + }; + + dbg!(&ipc); + + connection + .send_segment(PacketSegment { + source_actor: connection.player_data.actor_id, + target_actor: connection.player_data.actor_id, + segment_type: SegmentType::Ipc, + data: SegmentData::Ipc { data: ipc }, + }) + .await; + } + + let lua = lua.lock().unwrap(); + let state = lua.app_data_ref::().unwrap(); + + if let Some(event_script) = + state.event_scripts.get(&event_id) + { + // run script + lua.scope(|scope| { + let connection_data = scope + .create_userdata_ref_mut(&mut lua_player) + .unwrap(); + + let config = get_config(); + + let file_name = format!( + "{}/{}", + &config.world.scripts_location, event_script + ); + lua.load( + std::fs::read(&file_name) + .expect("Failed to locate scripts directory!"), + ) + .set_name("@".to_string() + &file_name) + .exec() + .unwrap(); + + let func: Function = + lua.globals().get("onTalk").unwrap(); + + func.call::<()>((actor_id.object_id.0, &connection_data)) + .unwrap(); + + Ok(()) + }) + .unwrap(); + } else { + tracing::warn!("Event {event_id} isn't scripted yet! Ignoring..."); + } + } + ClientZoneIpcData::EventHandlerReturn { handler_id, scene, error_code, num_results, results } => { + tracing::info!("Finishing this event... {handler_id} {scene} {error_code} {num_results} {results:#?}"); + + { + // TODO: handle in lua script + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::EventFinish, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::EventFinish { + handler_id: *handler_id, + event: 1, + result: 1, + arg: 0 + }, + ..Default::default() + }; + + connection + .send_segment(PacketSegment { + source_actor: connection.player_data.actor_id, + target_actor: connection.player_data.actor_id, + segment_type: SegmentType::Ipc, + data: SegmentData::Ipc { data: ipc }, + }) + .await; + } + + { + let ipc = ServerZoneIpcSegment { + op_code: ServerZoneIpcType::Unk18, + timestamp: timestamp_secs(), + data: ServerZoneIpcData::Unk18 { + unk: [0; 16] + }, + ..Default::default() + }; + + connection + .send_segment(PacketSegment { + source_actor: connection.player_data.actor_id, + target_actor: connection.player_data.actor_id, + segment_type: SegmentType::Ipc, + data: SegmentData::Ipc { data: ipc }, + }) + .await; + } + } } } SegmentData::KeepAliveRequest { id, timestamp } => { @@ -1038,10 +1152,22 @@ async fn main() { }) .unwrap(); + let register_event_func = lua + .create_function(|lua, (event_id, event_script): (u32, String)| { + tracing::info!("Registering {event_id} with {event_script}!"); + let mut state = lua.app_data_mut::().unwrap(); + let _ = state.event_scripts.insert(event_id, event_script); + Ok(()) + }) + .unwrap(); + lua.set_app_data(ExtraLuaState::default()); lua.globals() .set("registerAction", register_action_func) .unwrap(); + lua.globals() + .set("registerEvent", register_event_func) + .unwrap(); let effectsbuilder_constructor = lua .create_function(|_, ()| Ok(EffectsBuilder::default())) diff --git a/src/ipc/zone/mod.rs b/src/ipc/zone/mod.rs index 8abb7ec..350dcbc 100644 --- a/src/ipc/zone/mod.rs +++ b/src/ipc/zone/mod.rs @@ -76,6 +76,7 @@ pub use item_operation::ItemOperation; mod equip; pub use equip::Equip; +use crate::common::ObjectTypeId; use crate::common::Position; use crate::common::read_string; use crate::common::write_string; @@ -220,6 +221,19 @@ pub enum ServerZoneIpcData { #[brw(pad_before = 3)] // padding actor_id: u32, }, + /// Sent to the client to stop their currently playing event. + EventFinish { + handler_id: u32, + event: u8, + result: u8, + #[brw(pad_before = 2)] // padding + #[brw(pad_after = 4)] // padding + arg: u32, + }, + /// Sent after EventFinish? it un-occupies the character lol + Unk18 { + unk: [u8; 16], // all zero... + }, } #[binrw] @@ -354,6 +368,21 @@ pub enum ClientZoneIpcData { }, #[br(pre_assert(*magic == ClientZoneIpcType::ItemOperation))] ItemOperation(ItemOperation), + #[br(pre_assert(*magic == ClientZoneIpcType::StartTalkEvent))] + StartTalkEvent { + actor_id: ObjectTypeId, + #[brw(pad_after = 4)] // padding + event_id: u32, + }, + #[br(pre_assert(*magic == ClientZoneIpcType::EventHandlerReturn))] + EventHandlerReturn { + handler_id: u32, + scene: u16, + error_code: u8, + num_results: u8, + #[brw(pad_after = 4)] // padding + results: [u32; 1], + }, } #[cfg(test)] diff --git a/src/world/lua.rs b/src/world/lua.rs index d47c171..d6d919f 100644 --- a/src/world/lua.rs +++ b/src/world/lua.rs @@ -53,14 +53,21 @@ impl LuaPlayer { self.status_effects.add(effect_id, duration); } - fn play_scene(&mut self, event_id: u32, scene: u16, scene_flags: u32, param: u8) { + fn play_scene( + &mut self, + actor_id: u32, + event_id: u32, + scene: u16, + scene_flags: u32, + param: u8, + ) { let ipc = ServerZoneIpcSegment { op_code: ServerZoneIpcType::EventScene, timestamp: timestamp_secs(), data: ServerZoneIpcData::EventScene(EventScene { actor_id: ObjectTypeId { - object_id: ObjectId(self.player_data.actor_id), - object_type: 0, + object_id: ObjectId(actor_id), + object_type: 1, }, event_id, scene, @@ -71,6 +78,8 @@ impl LuaPlayer { ..Default::default() }; + dbg!(&ipc); + self.queue_segment(PacketSegment { source_actor: self.player_data.actor_id, target_actor: self.player_data.actor_id, @@ -122,8 +131,8 @@ impl UserData for LuaPlayer { ); methods.add_method_mut( "play_scene", - |_, this, (event_id, scene, scene_flags, param): (u32, u16, u32, u8)| { - this.play_scene(event_id, scene, scene_flags, param); + |_, this, (actor_id, event_id, scene, scene_flags, param): (u32, u32, u16, u32, u8)| { + this.play_scene(actor_id, event_id, scene, scene_flags, param); Ok(()) }, );