Skip to content

Commit

Permalink
bank command uses object interaction, support for fake widgets, suppo…
Browse files Browse the repository at this point in the history
…rt for item-on-player and basic potato
  • Loading branch information
Promises committed Apr 4, 2021
1 parent 6673f45 commit fc2aa8b
Show file tree
Hide file tree
Showing 19 changed files with 489 additions and 12 deletions.
5 changes: 5 additions & 0 deletions data/config/items/other.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
"rs:dwarf_remains": {
"game_id": 0,
"tradable": false
},
"rs:rotten_potato": {
"game_id": 5733,
"tradable": false,
"examine": "Yuk!"
}
}
3 changes: 2 additions & 1 deletion data/config/widgets.json5
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
bank: {
depositBoxWidget: {
widgetId: 11,
containerId: 61
containerId: 61,
titleText: 60
},
pinSettingsWidget: {
widgetId: 14
Expand Down
2 changes: 1 addition & 1 deletion src/game-engine/game-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Player } from '@engine/world/actor/player/player';
import { Subject, timer } from 'rxjs';
import { Position } from '@engine/world/position';
import { ActionHook, sortActionHooks } from '@engine/world/action/hooks';
import { ActionPipeline, ActionType } from '@engine/world/action';
import { ActionType } from '@engine/world/action';


/**
Expand Down
14 changes: 10 additions & 4 deletions src/game-engine/net/inbound-packets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { gameEngineDist } from '@engine/util/directories';
interface InboundPacket {
opcode: number;
size: number;
handler: (player: Player, packet: { packetId: number, packetSize: number, buffer: ByteBuffer }) => void;
handler: (player: Player, packet: PacketData) => void;
}

export interface PacketData {
packetId: number,
packetSize: number,
buffer: ByteBuffer
}

export const incomingPackets = new Map<number, InboundPacket>();
Expand All @@ -16,10 +22,10 @@ export const PACKET_DIRECTORY = `${gameEngineDist}/net/inbound-packets`;
export async function loadPackets(): Promise<Map<number, InboundPacket>> {
incomingPackets.clear();

for await(const path of getFiles(PACKET_DIRECTORY, [ '.js' ], true)) {
for await(const path of getFiles(PACKET_DIRECTORY, ['.js'], true)) {
const location = './inbound-packets' + path.substring(PACKET_DIRECTORY.length).replace('.js', '');
const packet = require(location).default;
if(Array.isArray(packet)) {
if (Array.isArray(packet)) {
packet.forEach(p => incomingPackets.set(p.opcode, p));
} else {
incomingPackets.set(packet.opcode, packet);
Expand All @@ -32,7 +38,7 @@ export async function loadPackets(): Promise<Map<number, InboundPacket>> {
export function handlePacket(player: Player, packetId: number, packetSize: number, buffer: ByteBuffer): void {
const incomingPacket = incomingPackets.get(packetId);

if(!incomingPacket) {
if (!incomingPacket) {
logger.info(`Unknown packet ${packetId} with size ${packetSize} received.`);
return;
}
Expand Down
64 changes: 64 additions & 0 deletions src/game-engine/net/inbound-packets/item-on-player-packet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { logger } from '@runejs/core';
import { world } from '../../game-server';
import { World } from '../../world';
import { widgets } from '../../config';
import { Player } from "@engine/world/actor/player/player";
import { PacketData } from "@engine/net/inbound-packets";

const itemOnPlayerPacket = (player: Player, packet: PacketData) => {
const { buffer } = packet;
const playerIndex = buffer.get('SHORT', 'UNSIGNED', 'LITTLE_ENDIAN') - 1;
const itemWidgetId = buffer.get('SHORT', 'SIGNED', 'LITTLE_ENDIAN');
const itemContainerId = buffer.get('SHORT');
const itemId = buffer.get('SHORT', 'UNSIGNED', 'BIG_ENDIAN');
const itemSlot = buffer.get('SHORT', 'UNSIGNED', 'BIG_ENDIAN');


let usedItem;
if(itemWidgetId === widgets.inventory.widgetId && itemContainerId === widgets.inventory.containerId) {
if(itemSlot < 0 || itemSlot > 27) {
return;
}

usedItem = player.inventory.items[itemSlot];
if(!usedItem) {
return;
}

if(usedItem.itemId !== itemId) {
return;
}
} else {
logger.warn(`Unhandled item on object case using widget ${ itemWidgetId }:${ itemContainerId }`);
}


if(playerIndex < 0 || playerIndex > World.MAX_PLAYERS - 1) {
return;
}

const otherPlayer = world.playerList[playerIndex];
if(!otherPlayer) {
return;
}


const position = otherPlayer.position;
const distance = Math.floor(position.distanceBetween(player.position));



// Too far away
if(distance > 16) {
return;
}


player.actionPipeline.call('item_on_player', player, otherPlayer, position, usedItem, itemWidgetId, itemContainerId)
};

export default {
opcode: 110,
size: 10,
handler: itemOnPlayerPacket
};
1 change: 1 addition & 0 deletions src/game-engine/world/action/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type ActionType =
| 'item_interaction'
| 'item_on_object'
| 'item_on_npc'
| 'item_on_player'
| 'item_on_item'
| 'item_swap'
| 'move_item'
Expand Down
5 changes: 5 additions & 0 deletions src/game-engine/world/action/item-interaction.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ const itemInteractionActionPipe = (player: Player, itemId: number, slot: number,
}

let cancelActions = false;
const playerWidget = Object.values(player.interfaceState.widgetSlots).find((widget) => widget && widget.widgetId === widgetId);

if(playerWidget && playerWidget.fakeWidget != undefined) {
widgetId = playerWidget.fakeWidget;
}

// Find all object action plugins that reference this location object
let interactionActions = getActionHooks<ItemInteractionActionHook>('item_interaction', plugin => {
Expand Down
5 changes: 3 additions & 2 deletions src/game-engine/world/action/item-on-item.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ActionPipe } from '@engine/world/action/index';
*/
export interface ItemOnItemActionHook extends ActionHook<itemOnItemActionHandler> {
// The item pairs being used. Each item can be used on the other, so item order does not matter.
items: { item1: number, item2: number }[];
items: { item1: number, item2?: number }[];
}


Expand Down Expand Up @@ -61,7 +61,8 @@ const itemOnItemActionPipe = (player: Player, usedItem: Item, usedSlot: number,
let interactionActions = getActionHooks<ItemOnItemActionHook>('item_on_item').filter(plugin =>
questHookFilter(player, plugin) &&
(plugin.items.findIndex(i => i.item1 === usedItem.itemId && i.item2 === usedWithItem.itemId) !== -1 ||
plugin.items.findIndex(i => i.item2 === usedItem.itemId && i.item1 === usedWithItem.itemId) !== -1));
plugin.items.findIndex(i => i.item2 === usedItem.itemId && i.item1 === usedWithItem.itemId) !== -1 ||
plugin.items.findIndex(i => i.item1 === usedItem.itemId && !i.item2 || i.item1 === usedWithItem.itemId && !i.item2 ) !== -1));

const questActions = interactionActions.filter(plugin => plugin.questRequirement !== undefined);

Expand Down
120 changes: 120 additions & 0 deletions src/game-engine/world/action/item-on-player.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Player } from '@engine/world/actor/player/player';
import { Position } from '@engine/world/position';
import { ActionHook, getActionHooks } from '@engine/world/action/hooks';
import { logger } from '@runejs/core';
import { Item } from '@engine/world/items/item';
import { Npc } from '@engine/world/actor/npc/npc';
import { playerWalkTo } from '@engine/game-server';
import { advancedNumberHookFilter, questHookFilter, stringHookFilter } from '@engine/world/action/hooks/hook-filters';
import { ActionPipe } from '@engine/world/action/index';


/**
* Defines an item-on-player action hook.
*/
export interface ItemOnPlayerActionHook extends ActionHook<itemOnPlayerActionHandler> {
// A single game item ID or a list of item IDs that this action applies to.
itemIds: number | number[];
// Whether or not the player needs to walk to this Player before performing the action.
walkTo: boolean;
}


/**
* The item-on-player action hook handler function to be called when the hook's conditions are met.
*/
export type itemOnPlayerActionHandler = (itemOnPlayerAction: ItemOnPlayerAction) => void;


/**
* Details about an item-on-player action being performed.
*/
export interface ItemOnPlayerAction {
// The player performing the action.
player: Player;
// The player the action is being performed on.
otherPlayer: Player;
// The position that the Player was at when the action was initiated.
position: Position;
// The item being used.
item: Item;
// The ID of the UI widget that the item being used is in.
itemWidgetId: number;
// The ID of the UI container that the item being used is in.
itemContainerId: number;
}


/**
* The pipe that the game engine hands item-on-player actions off to.
* @param player
* @param otherPlayer
* @param position
* @param item
* @param itemWidgetId
* @param itemContainerId
*/
const itemOnPlayerActionPipe = (player: Player, otherPlayer: Player, position: Position, item: Item,
itemWidgetId: number, itemContainerId: number): void => {
if(player.busy) {
return;
}

// Find all item on player action plugins that reference this item
let interactionActions = getActionHooks<ItemOnPlayerActionHook>('item_on_player').filter(plugin =>
questHookFilter(player, plugin) && advancedNumberHookFilter(plugin.itemIds, item.itemId));
const questActions = interactionActions.filter(plugin => plugin.questRequirement !== undefined);

if(questActions.length !== 0) {
interactionActions = questActions;
}

if(interactionActions.length === 0) {
player.outgoingPackets.chatboxMessage(`Unhandled item on player interaction: ${ item.itemId } ` +
`@ ${ position.x },${ position.y },${ position.level }`);
return;
}

player.actionsCancelled.next(null);

// Separate out walk-to actions from immediate actions
const walkToPlugins = interactionActions.filter(plugin => plugin.walkTo);
const immediateHooks = interactionActions.filter(plugin => !plugin.walkTo);

// Make sure we walk to the player before running any of the walk-to plugins
if(walkToPlugins.length !== 0) {
playerWalkTo(player, position)
.then(() => {
player.face(position);

walkToPlugins.forEach(plugin =>
plugin.handler({
player,
otherPlayer,
position,
item,
itemWidgetId,
itemContainerId
}));
})
.catch(() => logger.warn(`Unable to complete walk-to action.`));
}

// Immediately run any non-walk-to plugins
for(const actionHook of immediateHooks) {
actionHook.handler({
player,
otherPlayer,
position,
item,
itemWidgetId,
itemContainerId
});
}
};


/**
* Item-on-player action pipe definition.
*/
export default [ 'item_on_player', itemOnPlayerActionPipe ] as ActionPipe;
6 changes: 6 additions & 0 deletions src/game-engine/world/action/widget-interaction.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export interface WidgetInteractionAction {
* @param optionId The widget context option chosen by the player.
*/
const widgetActionPipe = (player: Player, widgetId: number, childId: number, optionId: number): void => {
const playerWidget = Object.values(player.interfaceState.widgetSlots).find((widget) => widget && widget.widgetId === widgetId);

if(playerWidget && playerWidget.fakeWidget != undefined) {
widgetId = playerWidget.fakeWidget;
}

// Find all item on item action plugins that match this action
let interactionActions = getActionHooks<WidgetInteractionActionHook>('widget_interaction').filter(plugin => {
if(!questHookFilter(player, plugin)) {
Expand Down
11 changes: 9 additions & 2 deletions src/game-engine/world/actor/player/interface-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface WidgetOptions {
queued?: boolean;
containerId?: number;
container?: ItemContainer;
fakeWidget?: number;
metadata?: { [key: string]: any };
}


Expand All @@ -44,16 +46,20 @@ export class Widget {
public queued: boolean = false;
public containerId: number;
public container: ItemContainer = null;
public fakeWidget?: number;
public metadata: { [key: string]: any };

public constructor(interfaceId: number, options: WidgetOptions) {
const { slot, multi, queued, containerId, container } = options;
const { slot, multi, queued, containerId, container, fakeWidget, metadata } = options;

this.widgetId = interfaceId;
this.fakeWidget = fakeWidget;
this.slot = slot;
this.multi = multi || false;
this.queued = queued || false;
this.containerId = containerId || -1;
this.container = container || null;
this.metadata = { ...metadata }
}

}
Expand Down Expand Up @@ -143,7 +149,7 @@ export class InterfaceState {
this.widgetSlots[widget.slot] = null;
}

public openWidget(widgetId: number, options: WidgetOptions): void {
public openWidget(widgetId: number, options: WidgetOptions): Widget {
// if(this.widgetOpen(options.slot, widgetId)) {
// return;
// }
Expand All @@ -160,6 +166,7 @@ export class InterfaceState {

this.widgetSlots[widget.slot] = widget;
this.showWidget(widget);
return widget;
}

public setTab(type: TabType, widget: Widget | number | null): void {
Expand Down
2 changes: 1 addition & 1 deletion src/game-engine/world/actor/player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ export class Player extends Actor {
this.outgoingPackets.sendUpdateSingleWidgetItem(widgets.inventory, slot, null);
}

public giveItem(item: number | Item): boolean {
public giveItem(item: number | Item | string): boolean {
const addedItem = this.inventory.add(item);
if(addedItem === null) {
return false;
Expand Down
23 changes: 22 additions & 1 deletion src/plugins/commands/bank-command.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import { commandActionHandler } from '@engine/world/action/player-command.action';
import { openBankInterface } from '@plugins/objects/bank/bank.plugin';
import { getActionHooks } from "@engine/world/action/hooks";
import { ObjectInteractionActionHook } from "@engine/world/action/object-interaction.action";
import { advancedNumberHookFilter } from "@engine/world/action/hooks/hook-filters";
import { objectIds } from "@engine/world/config/object-ids";

const action: commandActionHandler = (details) => {
openBankInterface(details as any);
let interactionActions = getActionHooks<ObjectInteractionActionHook>('object_interaction')
.filter(plugin => advancedNumberHookFilter(plugin.objectIds, objectIds.bankBooth, plugin.options, "use-quickly"));
interactionActions.forEach(plugin =>
plugin.handler({
player: details.player,
object: {
objectId: objectIds.bankBooth,
level: details.player.position.level,
x: details.player.position.x,
y: details.player.position.y,
orientation: 0,
type: 0
},
objectDefinition: undefined,
option: "use-quickly",
position: details.player.position,
cacheOriginal: undefined
}));
};

export default {
Expand Down
Loading

0 comments on commit fc2aa8b

Please sign in to comment.