From 8547d0376c3f9ba5de538a319c564b1610215738 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Fri, 28 Feb 2020 14:29:08 -0600 Subject: [PATCH] Adding support for world item action plugins and a plugin for picking up world items. --- src/game-server.ts | 2 + src/net/incoming-packet-directory.ts | 5 +- .../incoming-packets/pickup-item-packet.ts | 27 ++++++ src/plugins/items/pickup-item-plugin.ts | 54 ++++++++++++ src/plugins/plugin.ts | 8 +- .../actor/player/action/world-item-action.ts | 84 +++++++++++++++++++ src/world/config/sound-ids.ts | 1 + src/world/map/chunk.ts | 17 ++++ 8 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 src/net/incoming-packets/pickup-item-packet.ts create mode 100644 src/plugins/items/pickup-item-plugin.ts create mode 100644 src/world/actor/player/action/world-item-action.ts diff --git a/src/game-server.ts b/src/game-server.ts index 9feab1dc5..0b026fa87 100644 --- a/src/game-server.ts +++ b/src/game-server.ts @@ -18,6 +18,7 @@ import { setButtonPlugins } from '@server/world/actor/player/action/button-actio import { setCommandPlugins } from '@server/world/actor/player/action/input-command-action'; import { setWidgetPlugins } from '@server/world/actor/player/action/widget-action'; import { setItemPlugins } from '@server/world/actor/player/action/item-action'; +import { setWorldItemPlugins } from '@server/world/actor/player/action/world-item-action'; export let serverConfig: ServerConfig; export let gameCache377: EarlyFormatGameCache; @@ -41,6 +42,7 @@ export async function injectPlugins(): Promise { setObjectPlugins(actionTypes[ActionType.OBJECT_ACTION]); setItemOnItemPlugins(actionTypes[ActionType.ITEM_ON_ITEM]); setItemPlugins(actionTypes[ActionType.ITEM_ACTION]); + setWorldItemPlugins(actionTypes[ActionType.WORLD_ITEM_ACTION]); setCommandPlugins(actionTypes[ActionType.COMMAND]); setWidgetPlugins(actionTypes[ActionType.WIDGET_ACTION]); } diff --git a/src/net/incoming-packet-directory.ts b/src/net/incoming-packet-directory.ts index 228c10a3f..7a1bc3063 100644 --- a/src/net/incoming-packet-directory.ts +++ b/src/net/incoming-packet-directory.ts @@ -5,8 +5,6 @@ import { logger } from '@runejs/logger'; import { incomingPacket } from './incoming-packet'; import { characterDesignPacket } from './incoming-packets/character-design-packet'; import { itemEquipPacket } from './incoming-packets/item-equip-packet'; -import { interfaceClickPacket } from './incoming-packets/interface-click-packet'; -import { cameraTurnPacket } from './incoming-packets/camera-turn-packet'; import { buttonClickPacket } from './incoming-packets/button-click-packet'; import { walkPacket } from './incoming-packets/walk-packet'; import { itemOption1Packet } from './incoming-packets/item-option-1-packet'; @@ -18,8 +16,8 @@ import { objectInteractionPacket } from '@server/net/incoming-packets/object-int import { chatPacket } from '@server/net/incoming-packets/chat-packet'; import { dropItemPacket } from '@server/net/incoming-packets/drop-item-packet'; import { itemOnItemPacket } from '@server/net/incoming-packets/item-on-item-packet'; -import { buyItemPacket } from '@server/net/incoming-packets/buy-item-packet'; import { widgetsClosedPacket } from '@server/net/incoming-packets/widgets-closed-packet'; +import { pickupItemPacket } from '@server/net/incoming-packets/pickup-item-packet'; const ignore = [ 234, 160, 58 /* camera move */ ]; @@ -41,6 +39,7 @@ const packets: { [key: number]: incomingPacket } = { 102: itemEquipPacket, 38: itemOption1Packet, 29: dropItemPacket, + 85: pickupItemPacket, 63: npcInteractionPacket, diff --git a/src/net/incoming-packets/pickup-item-packet.ts b/src/net/incoming-packets/pickup-item-packet.ts new file mode 100644 index 000000000..8225edf6a --- /dev/null +++ b/src/net/incoming-packets/pickup-item-packet.ts @@ -0,0 +1,27 @@ +import { incomingPacket } from '../incoming-packet'; +import { Player } from '../../world/actor/player/player'; +import { RsBuffer } from '@server/net/rs-buffer'; +import { world } from '@server/game-server'; +import { Position } from '@server/world/position'; +import { worldItemAction } from '@server/world/actor/player/action/world-item-action'; + +export const pickupItemPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: RsBuffer): void => { + const y = packet.readNegativeOffsetShortBE(); + const itemId = packet.readNegativeOffsetShortBE(); + const x = packet.readUnsignedShortLE(); + + const level = player.position.level; + const worldItemPosition = new Position(x, y, level); + const chunk = world.chunkManager.getChunkForWorldPosition(worldItemPosition); + const worldItem = chunk.getWorldItem(itemId, worldItemPosition); + + if(!worldItem || worldItem.removed) { + return; + } + + if(worldItem.initiallyVisibleTo && !worldItem.initiallyVisibleTo.equals(player)) { + return; + } + + worldItemAction(player, worldItem, 'pick-up'); +}; diff --git a/src/plugins/items/pickup-item-plugin.ts b/src/plugins/items/pickup-item-plugin.ts new file mode 100644 index 000000000..f079674b5 --- /dev/null +++ b/src/plugins/items/pickup-item-plugin.ts @@ -0,0 +1,54 @@ +import { ActionType, RunePlugin } from '@server/plugins/plugin'; +import { worldItemAction } from '@server/world/actor/player/action/world-item-action'; +import { world } from '../../game-server'; +import { Item } from '../../world/items/item'; +import { widgets } from '../../world/config/widget'; +import { soundIds } from '@server/world/config/sound-ids'; + +export const action: worldItemAction = (details) => { + const { player, worldItem } = details; + + const inventory = player.inventory; + let slot = -1; + const itemData = world.itemData.get(worldItem.itemId); + let amount = worldItem.amount; + + if(itemData.stackable) { + const existingItemIndex = inventory.findIndex(worldItem.itemId); + if(existingItemIndex !== -1) { + const existingItem = inventory.items[existingItemIndex]; + if(existingItem.amount + worldItem.amount < 2147483647) { + existingItem.amount += worldItem.amount; + amount += existingItem.amount; + slot = existingItemIndex; + } + } + } + + if(slot === -1) { + slot = inventory.getFirstOpenSlot(); + } + + if(slot === -1) { + player.outgoingPackets.chatboxMessage(`You don't have enough free space to do that.`); + return; + } + + world.chunkManager.removeWorldItem(worldItem); + + const item: Item = { + itemId: worldItem.itemId, + amount + }; + + inventory.add(item); + player.outgoingPackets.sendUpdateSingleWidgetItem(widgets.inventory, slot, item); + player.outgoingPackets.playSound(soundIds.pickupItem, 3); +}; + +export default new RunePlugin({ + type: ActionType.WORLD_ITEM_ACTION, + options: 'pick-up', + action, + walkTo: true +}); diff --git a/src/plugins/plugin.ts b/src/plugins/plugin.ts index 604f04be1..b160dbb41 100644 --- a/src/plugins/plugin.ts +++ b/src/plugins/plugin.ts @@ -5,12 +5,14 @@ import { ItemOnItemActionPlugin } from '@server/world/actor/player/action/item-o import { CommandActionPlugin } from '@server/world/actor/player/action/input-command-action'; import { WidgetActionPlugin } from '@server/world/actor/player/action/widget-action'; import { ItemActionPlugin } from '@server/world/actor/player/action/item-action'; +import { WorldItemActionPlugin } from '@server/world/actor/player/action/world-item-action'; export enum ActionType { BUTTON = 'button', WIDGET_ACTION = 'widget_action', ITEM_ON_ITEM = 'item_on_item', ITEM_ACTION = 'item_action', + WORLD_ITEM_ACTION = 'world_item_action', NPC_ACTION = 'npc_action', OBJECT_ACTION = 'object_action', COMMAND = 'command' @@ -23,12 +25,12 @@ export interface ActionPlugin { export class RunePlugin { public actions: (NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | - CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin)[]; + CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin)[]; public constructor(actions: NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | - CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | + CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | (NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | - CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin)[]) { + CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin)[]) { if(!Array.isArray(actions)) { this.actions = [actions]; } else { diff --git a/src/world/actor/player/action/world-item-action.ts b/src/world/actor/player/action/world-item-action.ts new file mode 100644 index 000000000..a0ad1ae67 --- /dev/null +++ b/src/world/actor/player/action/world-item-action.ts @@ -0,0 +1,84 @@ +import { Player } from '@server/world/actor/player/player'; +import { walkToAction } from '@server/world/actor/player/action/action'; +import { basicNumberFilter, basicStringFilter } from '@server/plugins/plugin-loader'; +import { logger } from '@runejs/logger/dist/logger'; +import { ActionPlugin } from '@server/plugins/plugin'; +import { WorldItem } from '@server/world/items/world-item'; + +/** + * The definition for a world item action function. + */ +export type worldItemAction = (details: WorldItemActionDetails) => void; + +/** + * Details about a world item being interacted with. + */ +export interface WorldItemActionDetails { + player: Player; + worldItem: WorldItem; +} + +/** + * Defines an world item interaction plugin. + */ +export interface WorldItemActionPlugin extends ActionPlugin { + itemIds?: number | number[]; + options: string | string[]; + walkTo: boolean; + action: worldItemAction; +} + +/** + * A directory of all world item interaction plugins. + */ +let worldItemInteractions: WorldItemActionPlugin[] = [ +]; + +/** + * Sets the list of world item interaction plugins. + * @param plugins The plugin list. + */ +export const setWorldItemPlugins = (plugins: ActionPlugin[]): void => { + worldItemInteractions = plugins as WorldItemActionPlugin[]; +}; + +// @TODO priority and cancelling other (lower priority) actions +export const worldItemAction = (player: Player, worldItem: WorldItem, option: string): void => { + // Find all world item action plugins that reference this world item + const interactionPlugins = worldItemInteractions.filter(plugin => { + if(plugin.itemIds !== undefined) { + if(!basicNumberFilter(plugin.itemIds, worldItem.itemId)) { + return false; + } + } + + if(!basicStringFilter(plugin.options, option)) { + return false; + } + + return true; + }); + + if(interactionPlugins.length === 0) { + player.outgoingPackets.chatboxMessage(`Unhandled world item interaction: ${option} ${worldItem.itemId}`); + return; + } + + player.actionsCancelled.next(); + + // Separate out walk-to actions from immediate actions + const walkToPlugins = interactionPlugins.filter(plugin => plugin.walkTo); + const immediatePlugins = interactionPlugins.filter(plugin => !plugin.walkTo); + + // Make sure we walk to the NPC before running any of the walk-to plugins + if(walkToPlugins.length !== 0) { + walkToAction(player, worldItem.position) + .then(() => walkToPlugins.forEach(plugin => plugin.action({ player, worldItem }))) + .catch(() => logger.warn(`Unable to complete walk-to action.`)); + } + + // Immediately run any non-walk-to plugins + if(immediatePlugins.length !== 0) { + immediatePlugins.forEach(plugin => plugin.action({ player, worldItem })); + } +}; diff --git a/src/world/config/sound-ids.ts b/src/world/config/sound-ids.ts index 2d41ed959..1a81b2f67 100644 --- a/src/world/config/sound-ids.ts +++ b/src/world/config/sound-ids.ts @@ -1,5 +1,6 @@ export const soundIds = { dropItem: 2739, + pickupItem: 2582, milkCow: 372, lightingFire: 2599, fireLit: 2594, diff --git a/src/world/map/chunk.ts b/src/world/map/chunk.ts index 0aef913fd..4037fcc74 100644 --- a/src/world/map/chunk.ts +++ b/src/world/map/chunk.ts @@ -39,6 +39,23 @@ export class Chunk { this._worldItems = new Map(); } + public getWorldItem(itemId: number, position: Position): WorldItem { + const key = position.key; + + if(this._worldItems.has(key)) { + const list = this._worldItems.get(key); + const worldItem = list.find(item => item.itemId === itemId); + + if(!worldItem) { + return null; + } + + return worldItem; + } + + return null; + } + public addWorldItem(worldItem: WorldItem): void { const key = worldItem.position.key;