1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-07-01 12:17:46 +00:00

Document some opcodes related to shops and implement a generic gil shopkeeper script (#85)

Document some opcodes related to shops and implement a generic gil shopkeeper script
* You can now interact with shopkeepers, and if you have enough gil, you can attempt to purchase items
* Upon trying to buy items the event will auto-cancel for now, because we're missing implementations of several opcodes related to inventory management
This commit is contained in:
thedax 2025-06-30 15:21:08 -04:00 committed by GitHub
parent f778f9a571
commit 5a580149b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 82 additions and 0 deletions

View file

@ -224,6 +224,11 @@
"name": "UnkCall",
"opcode": 886,
"size": 32
},
{
"name": "InventoryActionAck",
"opcode": 483,
"size": 16
}
],
"ClientZoneIpcType": [
@ -361,6 +366,11 @@
"name": "EventUnkRequest",
"opcode": 448,
"size": 16
},
{
"name": "GilShopTransaction",
"opcode": 108,
"size": 24
}
],
"ServerLobbyIpcType": [

View file

@ -304,6 +304,11 @@ common_events = {
-- [721620] = "GenericGemstoneTrader.lua", -- Generic Endwalker & Dawntrail in-city gemstone traders, but they do nothing when interacted with right now
}
-- NPC shops that accept gil for purchasing items
generic_gil_shops = {
263220, -- Neon <Air-wheeler dealer>, Solution Nine
}
-- Not all Hunt NPCs are spawning right now, unfortunately.
generic_currency_exchange = {
1769533, -- Gold Saucer Attendant <Prize Claim> (behind counter) -> Prize Exchange (Gear)
@ -377,6 +382,10 @@ for _, event_id in pairs(generic_anetshards) do
registerEvent(event_id, "events/common/GenericAethernetShard.lua")
end
for _, event_id in pairs(generic_gil_shops) do
registerEvent(event_id, "events/common/GenericShopkeeper.lua") --TODO: It might be okay to combine gil shops with battle currency shops, still unclear
end
for _, event_id in pairs(generic_currency_exchange) do
registerEvent(event_id, "events/common/GenericHuntCurrencyExchange.lua") --TODO: Should probably rename this since it now covers other generic currency vendors like Gold Saucer ones
end

View file

@ -0,0 +1,27 @@
-- TODO: actually implement this menu
-- Scene 00000: NPC greeting (usually an animation, sometimes text too?)
-- Scene 00010: Displays shop interface
-- Scene 00255: Unknown, but this was also observed when capturing gil shop transaction packets. When used standalone it softlocks.
function onTalk(target, player)
--[[ Params observed:
Gil shops: [0, 1]
Non- shops: [1, 0]
MGP shops: [1, 100]
It's unclear what these mean since shops seem to open fine without these.
]]
player:play_scene(target, EVENT_ID, 00000, 8192, {0})
end
function onReturn(scene, results, player)
if scene == 0 then
--[[ Retail sends 221 zeroes as u32s as the params to the shop cutscene, but it opens fine with a single zero u32.
Perhaps they are leftovers from earlier expansions? According to Sapphire, the params used to be significantly more complex.
Historically, it also seems cutscene 00040 was used instead of 00010 as it is now.
]]
player:play_scene(player.id, EVENT_ID, 00010, 1 | 0x2000, {0})
elseif scene == 10 then
player:finish_event(EVENT_ID)
end
end

View file

@ -786,6 +786,16 @@ async fn client_loop(
connection.player_data.inventory.process_action(action);
connection.send_inventory(true).await;
}
// TODO: Likely rename this opcode if non-gil shops also use this same opcode
ClientZoneIpcData::GilShopTransaction { event_id, unk1: _, buy_sell_mode, item_index, item_quantity, unk2: _ } => {
tracing::info!("Client is interacting with a shop! {event_id:#?} {buy_sell_mode:#?} {item_quantity:#?} {item_index:#?}");
// TODO: update the client's inventory, adjust their gil, and send the proper response packets!
connection.send_message("Shops are not implemented yet. Cancelling event...").await;
// Cancel the event for now so the client doesn't get stuck
connection.event_finish(*event_id).await;
}
ClientZoneIpcData::StartTalkEvent { actor_id, event_id } => {
// load event
{
@ -811,6 +821,11 @@ async fn client_loop(
.await;
}
/* TODO: ServerZoneIpcType::Unk18 with data [64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
* was observed to always be sent by the server upon interacting with shops. They open and function fine without
* it, but should we send it anyway, for the sake of accuracy? It's also still unclear if this
* happens for -every- NPC/actor. */
let mut should_cancel = false;
{
let lua = lua.lock().unwrap();

View file

@ -315,6 +315,12 @@ pub enum ServerZoneIpcData {
#[brw(pad_after = 8)]
unk3: u8,
},
#[br(pre_assert(*magic == ServerZoneIpcType::InventoryActionAck))]
InventoryActionAck {
sequence: u32,
#[brw(pad_after = 12)]
action_type: u16,
},
#[br(pre_assert(*magic == ServerZoneIpcType::UnkCall))]
UnkCall {
unk1: u32,
@ -455,6 +461,21 @@ pub enum ClientZoneIpcData {
#[brw(pad_after = 4)] // padding
event_id: u32,
},
#[br(pre_assert(*magic == ClientZoneIpcType::GilShopTransaction))]
GilShopTransaction {
event_id: u32,
/// Seems to always be 0x300000a at gil shops
unk1: u32,
/// 1 is buy, 2 is sell
buy_sell_mode: u32,
/// Index into the shopkeeper's or the player's inventory
item_index: u32,
/// Quantity of items being bought or sold
item_quantity: u32,
/// unk 2: Flags? These change quite a bit when dealing with stackable items, but are apparently always 0 when buying non-stackable
/// Observed values so far: 0xDDDDDDDD (when buying 99 of a stackable item), 0xFFFFFFFF, 0xFFE0FFD0, 0xfffefffe, 0x0000FF64
unk2: u32,
},
#[br(pre_assert(*magic == ClientZoneIpcType::EventYieldHandler))]
EventYieldHandler(EventYieldHandler<2>),
#[br(pre_assert(*magic == ClientZoneIpcType::EventYieldHandler8))]