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

Begin scripting the inn NPCs

This kinda works, you can now talk to them without locking up your
character but you aren't warped yet. I also need to clean up the mess of
the Lua API.
This commit is contained in:
Joshua Goins 2025-05-05 20:51:49 -04:00
parent 85a2abb49e
commit 3139d63b91
7 changed files with 216 additions and 6 deletions

View file

@ -154,6 +154,16 @@
"name": "Delete", "name": "Delete",
"opcode": 149, "opcode": 149,
"size": 8 "size": 8
},
{
"name": "EventFinish",
"opcode": 375,
"size": 16
},
{
"name": "Unk18",
"opcode": 116,
"size": 16
} }
], ],
"ClientZoneIpcType": [ "ClientZoneIpcType": [
@ -266,6 +276,16 @@
"name": "ItemOperation", "name": "ItemOperation",
"opcode": 583, "opcode": 583,
"size": 48 "size": 48
},
{
"name": "StartTalkEvent",
"opcode": 300,
"size": 16
},
{
"name": "EventHandlerReturn",
"opcode": 281,
"size": 16
} }
], ],
"ServerChatIpcType": [ "ServerChatIpcType": [

View file

@ -10,3 +10,9 @@ registerAction(9, "actions/FastBlade.lua")
-- Items -- Items
registerAction(6221, "items/Fantasia.lua") 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")

View file

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

View file

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

View file

@ -11,7 +11,7 @@ use kawari::inventory::{Inventory, Item};
use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment}; use kawari::ipc::chat::{ServerChatIpcData, ServerChatIpcSegment};
use kawari::ipc::kawari::{CustomIpcData, CustomIpcSegment, CustomIpcType}; use kawari::ipc::kawari::{CustomIpcData, CustomIpcSegment, CustomIpcType};
use kawari::ipc::zone::{ use kawari::ipc::zone::{
ActionEffect, ActionResult, ClientZoneIpcData, EffectKind, GameMasterCommandType, ActionEffect, ActionResult, ClientZoneIpcData, EffectKind, EventStart, GameMasterCommandType,
GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType, GameMasterRank, OnlineStatus, ServerZoneIpcData, ServerZoneIpcSegment, SocialListRequestType,
}; };
use kawari::ipc::zone::{ use kawari::ipc::zone::{
@ -40,6 +40,7 @@ use tokio::task::JoinHandle;
#[derive(Default)] #[derive(Default)]
struct ExtraLuaState { struct ExtraLuaState {
action_scripts: HashMap<u32, String>, action_scripts: HashMap<u32, String>,
event_scripts: HashMap<u32, String>,
} }
fn spawn_main_loop() -> (ServerHandle, JoinHandle<()>) { fn spawn_main_loop() -> (ServerHandle, JoinHandle<()>) {
@ -727,6 +728,119 @@ async fn client_loop(
connection.player_data.inventory.process_action(action); connection.player_data.inventory.process_action(action);
connection.send_inventory(true).await; 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::<ExtraLuaState>().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 } => { SegmentData::KeepAliveRequest { id, timestamp } => {
@ -1038,10 +1152,22 @@ async fn main() {
}) })
.unwrap(); .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::<ExtraLuaState>().unwrap();
let _ = state.event_scripts.insert(event_id, event_script);
Ok(())
})
.unwrap();
lua.set_app_data(ExtraLuaState::default()); lua.set_app_data(ExtraLuaState::default());
lua.globals() lua.globals()
.set("registerAction", register_action_func) .set("registerAction", register_action_func)
.unwrap(); .unwrap();
lua.globals()
.set("registerEvent", register_event_func)
.unwrap();
let effectsbuilder_constructor = lua let effectsbuilder_constructor = lua
.create_function(|_, ()| Ok(EffectsBuilder::default())) .create_function(|_, ()| Ok(EffectsBuilder::default()))

View file

@ -76,6 +76,7 @@ pub use item_operation::ItemOperation;
mod equip; mod equip;
pub use equip::Equip; pub use equip::Equip;
use crate::common::ObjectTypeId;
use crate::common::Position; use crate::common::Position;
use crate::common::read_string; use crate::common::read_string;
use crate::common::write_string; use crate::common::write_string;
@ -220,6 +221,19 @@ pub enum ServerZoneIpcData {
#[brw(pad_before = 3)] // padding #[brw(pad_before = 3)] // padding
actor_id: u32, 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] #[binrw]
@ -354,6 +368,21 @@ pub enum ClientZoneIpcData {
}, },
#[br(pre_assert(*magic == ClientZoneIpcType::ItemOperation))] #[br(pre_assert(*magic == ClientZoneIpcType::ItemOperation))]
ItemOperation(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)] #[cfg(test)]

View file

@ -53,14 +53,21 @@ impl LuaPlayer {
self.status_effects.add(effect_id, duration); 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 { let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::EventScene, op_code: ServerZoneIpcType::EventScene,
timestamp: timestamp_secs(), timestamp: timestamp_secs(),
data: ServerZoneIpcData::EventScene(EventScene { data: ServerZoneIpcData::EventScene(EventScene {
actor_id: ObjectTypeId { actor_id: ObjectTypeId {
object_id: ObjectId(self.player_data.actor_id), object_id: ObjectId(actor_id),
object_type: 0, object_type: 1,
}, },
event_id, event_id,
scene, scene,
@ -71,6 +78,8 @@ impl LuaPlayer {
..Default::default() ..Default::default()
}; };
dbg!(&ipc);
self.queue_segment(PacketSegment { self.queue_segment(PacketSegment {
source_actor: self.player_data.actor_id, source_actor: self.player_data.actor_id,
target_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( methods.add_method_mut(
"play_scene", "play_scene",
|_, this, (event_id, scene, scene_flags, param): (u32, u16, u32, u8)| { |_, this, (actor_id, event_id, scene, scene_flags, param): (u32, u32, u16, u32, u8)| {
this.play_scene(event_id, scene, scene_flags, param); this.play_scene(actor_id, event_id, scene, scene_flags, param);
Ok(()) Ok(())
}, },
); );