mirror of
https://github.com/bdnugget/rs-sdk.git
synced 2026-11-07 09:07:30 +00:00
fix(client): route non-inventory useInventoryItem to INV_BUTTON
Both clients sent OPHELD1-5 no matter which interfaceId was passed, but the engine only wires OPHELD to the main inventory (3214) - item options on any other component (tradeside:inv, bank_side:inv, ...) are inv_button triggers. The packet passed the wire and the handler silently dropped it, which broke trade offers for weeks with zero error surface (bug report msplxq69). Non-3214 components now dispatch INV_BUTTON1-5 via clickInterfaceIop, interfaceId is plumbed through the gateway action and sdk.sendUseItem, and an encoding test pins both packet families. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
697b43933d
commit
47bf4ee5d1
8 files changed
+67
-7
No files matched your search
+1
-1
@@ -236,7 +236,7 @@
|
||||
| `async sendInteractPlayer(playerIndex: number, option: number = 2): Promise<ActionResult>` | Interact with a player by index and option (1-5). Option 2 = Attack (wilderness), 3 = Follow, 4 = Trade. |
|
||||
| `async sendTalkToNpc(npcIndex: number): Promise<ActionResult>` | Talk to an NPC by index. |
|
||||
| `async sendPickup(x: number, z: number, itemId: number): Promise<ActionResult>` | Pick up a ground item. |
|
||||
| `async sendUseItem(slot: number, option: number = 1): Promise<ActionResult>` | Use an inventory item (eat, equip, etc). |
|
||||
| `async sendUseItem(slot: number, option: number = 1, interfaceId?: number): Promise<ActionResult>` | Use an inventory item (eat, equip, etc). `interfaceId` selects which inventory component holds the item. The main inventory (3214, the default) dispatches OPHELD1-5; any other component (trade offer, bank side inventory, ...) dispatches INV_BUTTON1-5, which is the packet family the engine actually handles for interface-defined item options - OPHELD with a foreign component id is silently dropped. |
|
||||
| `async sendUseEquipmentItem(slot: number, option: number = 1): Promise<ActionResult>` | Use an equipped item (remove, operate, etc). |
|
||||
| `async sendDropItem(slot: number): Promise<ActionResult>` | Drop an inventory item. |
|
||||
| `async sendUseItemOnItem(sourceSlot: number, targetSlot: number): Promise<ActionResult>` | Use one inventory item on another. Rejected up front while a shop or bank modal is open: those replace the inventory tab, so the server drops the packet as "component not visible" and sends no message at all. Close the modal first — `bot.closeShop()`, `bot.closeInterface()`, or `sendCloseModal()`. |
|
||||
|
||||
+11
-3
@@ -1081,9 +1081,17 @@ export class BotSDK {
|
||||
return this.sendAction({ type: 'pickupItem', x, z, itemId, reason: 'SDK' });
|
||||
}
|
||||
|
||||
/** Use an inventory item (eat, equip, etc). */
|
||||
async sendUseItem(slot: number, option: number = 1): Promise<ActionResult> {
|
||||
return this.sendAction({ type: 'useInventoryItem', slot, optionIndex: option, reason: 'SDK' });
|
||||
/**
|
||||
* Use an inventory item (eat, equip, etc).
|
||||
*
|
||||
* `interfaceId` selects which inventory component holds the item. The main
|
||||
* inventory (3214, the default) dispatches OPHELD1-5; any other component
|
||||
* (trade offer, bank side inventory, ...) dispatches INV_BUTTON1-5, which
|
||||
* is the packet family the engine actually handles for interface-defined
|
||||
* item options - OPHELD with a foreign component id is silently dropped.
|
||||
*/
|
||||
async sendUseItem(slot: number, option: number = 1, interfaceId?: number): Promise<ActionResult> {
|
||||
return this.sendAction({ type: 'useInventoryItem', slot, optionIndex: option, interfaceId, reason: 'SDK' });
|
||||
}
|
||||
|
||||
/** Use an equipped item (remove, operate, etc). */
|
||||
|
||||
+1
-1
@@ -561,7 +561,7 @@ export type BotAction =
|
||||
// Randomize character appearance (gender, body parts, colors) with valid random values
|
||||
| { type: 'randomizeCharacterDesign'; reason: string }
|
||||
| { type: 'walkTo'; x: number; z: number; running?: boolean; reason: string }
|
||||
| { type: 'useInventoryItem'; slot: number; optionIndex: number; reason: string }
|
||||
| { type: 'useInventoryItem'; slot: number; optionIndex: number; interfaceId?: number; reason: string }
|
||||
| { type: 'useEquipmentItem'; slot: number; optionIndex: number; reason: string }
|
||||
| { type: 'dropItem'; slot: number; reason: string }
|
||||
| { type: 'pickupItem'; x: number; z: number; itemId: number; reason: string }
|
||||
|
||||
@@ -108,7 +108,9 @@ export class ActionExecutor {
|
||||
|
||||
case 'useInventoryItem':
|
||||
return this.wrapBool(
|
||||
this.client.useInventoryItem(action.slot, action.optionIndex),
|
||||
action.interfaceId !== undefined
|
||||
? this.client.useInventoryItem(action.slot, action.optionIndex, action.interfaceId)
|
||||
: this.client.useInventoryItem(action.slot, action.optionIndex),
|
||||
`Using inventory slot ${action.slot}`,
|
||||
'Failed to use inventory item'
|
||||
);
|
||||
|
||||
@@ -467,7 +467,7 @@ export type BotAction =
|
||||
| { type: 'acceptCharacterDesign'; reason: string }
|
||||
| { type: 'randomizeCharacterDesign'; reason: string }
|
||||
| { type: 'walkTo'; x: number; z: number; running?: boolean; reason: string }
|
||||
| { type: 'useInventoryItem'; slot: number; optionIndex: number; reason: string }
|
||||
| { type: 'useInventoryItem'; slot: number; optionIndex: number; interfaceId?: number; reason: string }
|
||||
| { type: 'dropItem'; slot: number; reason: string }
|
||||
| { type: 'pickupItem'; x: number; z: number; itemId: number; reason: string }
|
||||
| { type: 'interactGroundItem'; x: number; z: number; itemId: number; optionIndex: number; reason: string }
|
||||
|
||||
@@ -1442,6 +1442,14 @@ export class Client extends GameShell {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The engine only wires OPHELD1-5 to the main inventory (3214); every
|
||||
// other component's item options are inv_button triggers. OPHELD with
|
||||
// a foreign com id passes the wire and is silently dropped by the
|
||||
// handler, so route those to INV_BUTTON instead.
|
||||
if (interfaceId !== 3214) {
|
||||
return this.clickInterfaceIop(interfaceId, optionIndex, slot);
|
||||
}
|
||||
|
||||
// Get item ID from the inventory slot
|
||||
const component = IfType.list[interfaceId];
|
||||
if (!component || !component.linkObjType) {
|
||||
|
||||
@@ -254,6 +254,39 @@ describe('outgoing action encoding', () => {
|
||||
expect(r.g2()).toBe(INV_COMPONENT);
|
||||
});
|
||||
|
||||
test('useInventoryItem routes non-inventory components to INV_BUTTON (no OPHELD handlers there)', () => {
|
||||
if (!available) {
|
||||
return;
|
||||
}
|
||||
|
||||
const c = makeClient('bota');
|
||||
giveItems(c, INV_COMPONENT, { 2: 385 }); // shark in the main inventory
|
||||
giveItems(c, BANK_SIDE_INV, { 1: 385 }); // shark in the bank side inventory
|
||||
|
||||
// Main inventory (default): OPHELDn, as the engine expects for 3214.
|
||||
let start = c.out.pos;
|
||||
expect(c.useInventoryItem(2, 4)).toBe(true);
|
||||
|
||||
let r = new Capture(c.out, start);
|
||||
expect(r.g1()).toBe(ClientProt.OPHELD4);
|
||||
expect(r.g2()).toBe(385);
|
||||
expect(r.g2()).toBe(2);
|
||||
expect(r.g2()).toBe(INV_COMPONENT);
|
||||
expect(r.at).toBe(c.out.pos);
|
||||
|
||||
// Foreign component: INV_BUTTONn with the same field order - OPHELD
|
||||
// against a non-3214 com id is silently dropped by the engine.
|
||||
start = c.out.pos;
|
||||
expect(c.useInventoryItem(1, 4, BANK_SIDE_INV)).toBe(true);
|
||||
|
||||
r = new Capture(c.out, start);
|
||||
expect(r.g1()).toBe(ClientProt.INV_BUTTON4);
|
||||
expect(r.g2()).toBe(385);
|
||||
expect(r.g2()).toBe(1);
|
||||
expect(r.g2()).toBe(BANK_SIDE_INV);
|
||||
expect(r.at).toBe(c.out.pos);
|
||||
});
|
||||
|
||||
test('long walks clamp to the current scene edge like the browser client', () => {
|
||||
if (!available) {
|
||||
return;
|
||||
|
||||
@@ -204,6 +204,15 @@ export function useInventoryItem(c: LiteClient, slot: number, optionIndex: numbe
|
||||
if (!c.isInGame() || optionIndex < 1 || optionIndex > 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The engine only wires OPHELD1-5 to the main inventory; every other
|
||||
// component's item options are inv_button triggers (e.g. [inv_button4,
|
||||
// tradeside:inv]). OPHELD with a foreign com id passes the wire and is
|
||||
// silently dropped by the handler, so route those to INV_BUTTON instead.
|
||||
if (interfaceId !== INVENTORY_INTERFACE_ID) {
|
||||
return clickInterfaceIop(c, interfaceId, optionIndex, slot);
|
||||
}
|
||||
|
||||
const itemId = slotItemId(interfaceId, slot);
|
||||
if (!itemId) {
|
||||
return false;
|
||||
|
||||
Reference in new issue
Block a user