1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-07-09 23:47:46 +00:00

Extend the Lua API:

-Implement GM command collect (subtracts gil from player), but the ingame command's params are unknown, Lua only
-Implement GM command inspect, but the IPC opcode for it is different than GMCommand and is thus far unimplemented, Lua only
-The Lua API can now access all of the player's inventory (read-only)
This commit is contained in:
The Dax 2025-07-01 07:52:50 -04:00 committed by Joshua Goins
parent 877ec335a9
commit 3b6fcb7ba1
6 changed files with 193 additions and 3 deletions

View file

@ -368,9 +368,9 @@
"size": 16
},
{
"name": "GilShopTransaction",
"opcode": 108,
"size": 24
"name": "GilShopTransaction",
"opcode": 108,
"size": 24
}
],
"ServerLobbyIpcType": [

View file

@ -12,6 +12,8 @@ GM_EXP = 104
GM_ORCHESTRION = 116
GM_GIVE_ITEM = 200
GM_GIL = 201
GM_COLLECT = 202
GM_INSPECT = 422
GM_WIREFRAME = 550
GM_TERRITORY = 600
GM_TERRITORY_INFO = 605
@ -20,11 +22,15 @@ registerGMCommand(GM_SET_LEVEL, GM_DIR.."SetLevel.lua")
registerGMCommand(GM_CHANGE_WEATHER, GM_DIR.."ChangeWeather.lua")
registerGMCommand(GM_SPEED, GM_DIR.."SetSpeed.lua")
registerGMCommand(GM_INVISIBILITY, GM_DIR.."ToggleInvisibility.lua")
-- TODO: Implement the GMInspect IPC opcode, it's completely different than the normal GMCommand opcode
--registerGMCommand(GM_INSPECT, GM_DIR.."InspectPlayer.lua")
registerGMCommand(GM_AETHERYTE, GM_DIR.."UnlockAetheryte.lua")
registerGMCommand(GM_EXP, GM_DIR.."Exp.lua")
registerGMCommand(GM_ORCHESTRION, GM_DIR.."Orchestrion.lua")
registerGMCommand(GM_GIVE_ITEM, GM_DIR.."GiveItem.lua")
registerGMCommand(GM_GIL, GM_DIR.."Gil.lua")
-- TODO: Figure out how the //gm collect command works ingame, its parameters are not the same as //gm gil
--registerGMCommand(GM_COLLECT, GM_DIR.."Collect.lua")
registerGMCommand(GM_WIREFRAME, GM_DIR.."ToggleWireframe.lua")
registerGMCommand(GM_TERRITORY, GM_DIR.."ChangeTerritory.lua")
registerGMCommand(GM_TERRITORY_INFO, GM_DIR.."TerritoryInfo.lua")
@ -33,7 +39,9 @@ registerGMCommand(GM_TERRITORY_INFO, GM_DIR.."TerritoryInfo.lua")
-- Please keep these in alphabetical order!
registerCommand("classjob", DBG_DIR.."ClassJob.lua")
registerCommand("collect", GM_DIR.."Collect.lua") -- TODO: remove this once we figure out the //gm collect command's parameters (see comments above)
registerCommand("festival", DBG_DIR.."Festival.lua")
registerCommand("inspect", GM_DIR.."InspectPlayer.lua") -- TODO: remove this once we figure out the GMInspect IPC opcode
registerCommand("nudge", DBG_DIR.."Nudge.lua")
registerCommand("ost", DBG_DIR.."OnScreenTest.lua")
registerCommand("permtest", DBG_DIR.."PermissionTest.lua")

View file

@ -0,0 +1,12 @@
required_rank = GM_RANK_DEBUG
command_sender = "[collect] "
function onCommand(args, player)
local amount = tonumber(args[1])
if player.gil >= amount then
player:remove_gil(amount)
printf(player, "Collected %s gil.", amount)
else
printf(player, "Player does not have that much gil to take! They only possess %s.", player.gil)
end
end

View file

@ -0,0 +1,79 @@
required_rank = GM_RANK_DEBUG
command_sender = "[inspect] "
function getItemCondition(condition)
return (condition / 30000) * 100
end
function onCommand(args, player)
info = "\z
--- Info for player ---\n\z
Current region: %s\n\z
Current zone: %s (%s, %s)\n\z
Position: %.3f %.3f %.3f\n\z
Rotation: %.3f\n\z
--- Currency ---\n\z
Gil: %s\n\z
--- Equipped items ---\n\z
Main hand: (id: %s, condition: %s%%)\n\z
Off hand: (id: %s, condition: %s%%)\n\z
Head: (id: %s, condition: %s%%)\n\z
Body: (id: %s, condition: %s%%)\n\z
Hands: (id: %s, condition: %s%%)\n\z
Legs: (id: %s, condition: %s%%)\n\z
Feet: (id: %s, condition: %s%%)\n\z
Ears: (id: %s, condition: %s%%)\n\z
Neck: (id: %s, condition: %s%%)\n\z
Wrists: (id: %s, condition: %s%%)\n\z
Right Ring: (id: %s, condition: %s%%)\n\z
Left Ring: (id: %s, condition: %s%%)\n\z
Soul Crystal: (id: %s, condition: %s%%)\z
"
-- Skipping belts because they don't exist anymore.
main_hand = player.inventory.equipped.main_hand
off_hand = player.inventory.equipped.off_hand
head = player.inventory.equipped.head
body = player.inventory.equipped.body
hands = player.inventory.equipped.hands
legs = player.inventory.equipped.legs
feet = player.inventory.equipped.feet
ears = player.inventory.equipped.ears
neck = player.inventory.equipped.neck
wrists = player.inventory.equipped.wrists
rring = player.inventory.equipped.right_ring
lring = player.inventory.equipped.left_ring
scrystal = player.inventory.equipped.soul_crystal
printf(player, info,
player.zone.region_name, player.zone.place_name, player.zone.internal_name, player.zone.id,
player.position.x, player.position.y, player.position.z,
player.rotation, player.gil,
main_hand.id, getItemCondition(main_hand.condition),
off_hand.id, getItemCondition(off_hand.condition),
head.id, getItemCondition(head.condition),
body.id, getItemCondition(body.condition),
hands.id, getItemCondition(hands.condition),
legs.id, getItemCondition(legs.condition),
feet.id, getItemCondition(feet.condition),
ears.id, getItemCondition(ears.condition),
neck.id, getItemCondition(neck.condition),
wrists.id, getItemCondition(wrists.condition),
lring.id, getItemCondition(lring.condition),
rring.id, getItemCondition(rring.condition),
scrystal.id, getItemCondition(scrystal.condition)
)
local NO_ITEM <const> = 0
command_sender = "" -- hush further sender printfs, it looks ugly here
printf(player, "--- Player's inventory ---")
for page_num, page in pairs(player.inventory.pages) do
printf(player, "--- Page %s ---", page_num)
for slot_num, slot in pairs(page.slots) do
if slot.id ~= NO_ITEM then
printf(player, "slot %s: (id: %s, condition: %s%%)", slot_num, slot.id, getItemCondition(slot.condition))
end
end
end
end

View file

@ -745,6 +745,10 @@ impl ZoneConnection {
self.player_data.inventory.currency.get_slot_mut(0).quantity += *amount;
self.send_inventory(false).await;
}
Task::RemoveGil { amount } => {
self.player_data.inventory.currency.get_slot_mut(0).quantity -= *amount;
self.send_inventory(false).await;
}
Task::UnlockOrchestrion { id, on } => {
// id == 0 means "all"
if *id == 0 {

View file

@ -6,6 +6,7 @@ use crate::{
write_quantized_rotation,
},
config::get_config,
inventory::{CurrencyStorage, EquippedStorage, GenericStorage, Inventory, Item},
ipc::zone::{
ActionEffect, ActorControlCategory, ActorControlSelf, DamageElement, DamageKind,
DamageType, EffectKind, EventScene, ServerZoneIpcData, ServerZoneIpcSegment, Warp,
@ -32,6 +33,7 @@ pub enum Task {
SetLevel { level: i32 },
ChangeWeather { id: u16 },
AddGil { amount: u32 },
RemoveGil { amount: u32 },
UnlockOrchestrion { id: u16, on: bool },
AddItem { id: u32 },
}
@ -353,6 +355,10 @@ impl LuaPlayer {
self.queued_tasks.push(Task::AddGil { amount });
}
fn remove_gil(&mut self, amount: u32) {
self.queued_tasks.push(Task::RemoveGil { amount });
}
fn unlock_orchestrion(&mut self, unlocked: u32, id: u16) {
self.queued_tasks.push(Task::UnlockOrchestrion {
id,
@ -478,6 +484,10 @@ impl UserData for LuaPlayer {
this.add_gil(amount);
Ok(())
});
methods.add_method_mut("remove_gil", |_, this, amount: u32| {
this.remove_gil(amount);
Ok(())
});
methods.add_method_mut("unlock_orchestrion", |_, this, (unlock, id): (u32, u16)| {
this.unlock_orchestrion(unlock, id);
Ok(())
@ -501,7 +511,14 @@ impl UserData for LuaPlayer {
});
fields.add_field_method_get("rotation", |_, this| Ok(this.player_data.rotation));
fields.add_field_method_get("position", |_, this| Ok(this.player_data.position));
fields.add_field_method_get("inventory", |_, this| {
Ok(this.player_data.inventory.clone())
});
fields.add_field_method_get("zone", |_, this| Ok(this.zone_data.clone()));
// Helper method to reduce the amount of typing for gil
fields.add_field_method_get("gil", |_, this| {
Ok(this.player_data.inventory.currency.gil.quantity)
});
}
}
@ -519,6 +536,76 @@ impl UserData for Position {
}
}
impl UserData for Inventory {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("equipped", |_, this| Ok(this.equipped));
fields.add_field_method_get("pages", |_, this| Ok(this.pages.clone()));
fields.add_field_method_get("armoury_main_hand", |_, this| {
Ok(this.armoury_main_hand.clone())
});
fields.add_field_method_get("armoury_head", |_, this| Ok(this.armoury_head.clone()));
fields.add_field_method_get("armoury_body", |_, this| Ok(this.armoury_body.clone()));
fields.add_field_method_get("armoury_hands", |_, this| Ok(this.armoury_hands.clone()));
fields.add_field_method_get("armoury_legs", |_, this| Ok(this.armoury_legs.clone()));
fields.add_field_method_get("armoury_feet", |_, this| Ok(this.armoury_feet.clone()));
fields.add_field_method_get("armoury_off_hand", |_, this| {
Ok(this.armoury_off_hand.clone())
});
fields.add_field_method_get("armoury_earring", |_, this| {
Ok(this.armoury_earring.clone())
});
fields.add_field_method_get("armoury_necklace", |_, this| {
Ok(this.armoury_necklace.clone())
});
fields.add_field_method_get("armoury_bracelet", |_, this| Ok(this.armoury_body.clone()));
fields.add_field_method_get("armoury_rings", |_, this| Ok(this.armoury_rings.clone()));
fields.add_field_method_get("armoury_soul_crystal", |_, this| {
Ok(this.armoury_soul_crystal.clone())
});
fields.add_field_method_get("currency", |_, this| Ok(this.currency));
}
}
impl UserData for Item {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("quantity", |_, this| Ok(this.quantity));
fields.add_field_method_get("id", |_, this| Ok(this.id));
fields.add_field_method_get("condition", |_, this| Ok(this.condition));
fields.add_field_method_get("glamour_catalog_id", |_, this| Ok(this.condition));
}
}
impl UserData for CurrencyStorage {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("gil", |_, this| Ok(this.gil));
}
}
impl<const N: usize> UserData for GenericStorage<N> {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("slots", |_, this| Ok(this.slots.clone()));
}
}
impl UserData for EquippedStorage {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("main_hand", |_, this| Ok(this.main_hand));
fields.add_field_method_get("off_hand", |_, this| Ok(this.off_hand));
fields.add_field_method_get("head", |_, this| Ok(this.head));
fields.add_field_method_get("body", |_, this| Ok(this.body));
fields.add_field_method_get("hands", |_, this| Ok(this.hands));
fields.add_field_method_get("belt", |_, this| Ok(this.belt));
fields.add_field_method_get("legs", |_, this| Ok(this.legs));
fields.add_field_method_get("feet", |_, this| Ok(this.feet));
fields.add_field_method_get("ears", |_, this| Ok(this.ears));
fields.add_field_method_get("neck", |_, this| Ok(this.neck));
fields.add_field_method_get("wrists", |_, this| Ok(this.wrists));
fields.add_field_method_get("right_ring", |_, this| Ok(this.right_ring));
fields.add_field_method_get("left_ring", |_, this| Ok(this.left_ring));
fields.add_field_method_get("soul_crystal", |_, this| Ok(this.soul_crystal));
}
}
impl UserData for ObjectTypeId {}
impl FromLua for ObjectTypeId {