Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extremely simple implementation of shops. #67

Merged
merged 4 commits into from
Feb 20, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions data/config/npc-spawns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@
x: 3222
y: 3220
radius: 4
- npcId: 520
x: 3211
y: 3247
radius: 1
- npcId: 519
x: 3230
y: 3203
radius: 1
70 changes: 70 additions & 0 deletions data/config/shops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
- identification: LUMBRIDGE_GENERAL_STORE
name: Lumbridge General Store
items:
- id: 1931
text: Pot
price: 1
amountInStock: 5
- id: 1935
text: Jug
price: 1
amountInStock: 2
- id: 5603
text: Shears
price: 1
amountInStock: 2
- id: 1925
text: Bucket
price: 2
amountInStock: 3
- id: 1887
text: Cake tin
price: 13
amountInStock: 2
- id: 590
text: Tinderbox
price: 1
amountInStock: 2
- id: 1755
text: Chisel
price: 1
amountInStock: 2
- id: 952
text: Spade
price: 3
amountInStock: 5
- id: 2347
text: Hammer
price: 1
amountInStock: 5
- identification: BOBS_AXES
name: Bob's Brilliant Axes
items:
- id: 1265
text: Bronze pickaxe
price: 1
amountInStock: 5
- id: 1351
text: Bronze axe
price: 16
amountInStock: 10
- id: 1349
text: Iron axe
price: 56
amountInStock: 5
- id: 1353
text: Steel axe
price: 200
amountInStock: 3
- id: 1363
text: Iron battleaxe
price: 182
amountInStock: 5
- id: 1365
text: Steel battleaxe
price: 650
amountInStock: 2
- id: 1369
text: Mithril battleaxe
price: 1690
amountInStock: 1
4 changes: 4 additions & 0 deletions src/net/rs-buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ export class RsBuffer {
return value;
}

public writeUnsignedByteInverted(value: number): void {
this.writeUnsignedByte(~value & 0xff);
}

public readSmart(): number {
const peek = this.buffer.readUInt8(this.readerIndex);
if(peek < 128) {
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/npc/lumbridge/bobs-axes/bob-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { npcAction, NpcActionPlugin } from '@server/world/mob/player/action/npc-action';
import { openShop } from '@server/world/config/shops';

const action: npcAction = (details) => {
const { player, npc } = details;
openShop(details.player, 'BOBS_AXES');
};

export default { npcIds: 519, options: 'trade', walkTo: true, action } as NpcActionPlugin;
9 changes: 9 additions & 0 deletions src/plugins/npc/lumbridge/general-store/shopkeeper-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { npcAction, NpcActionPlugin } from '@server/world/mob/player/action/npc-action';
import { openShop } from '@server/world/config/shops';

const action: npcAction = (details) => {
const { player, npc } = details;
openShop(details.player, 'LUMBRIDGE_GENERAL_STORE');
};

export default { npcIds: 520, options: 'trade', walkTo: true, action } as NpcActionPlugin;
80 changes: 80 additions & 0 deletions src/world/config/shops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { logger } from '@runejs/logger/dist/logger';
import { JSON_SCHEMA, safeLoad } from 'js-yaml';
import { readFileSync } from 'fs';
import { world } from '@server/game-server';
import { Player } from '@server/world/mob/player/player';

export enum ShopName {

}
export interface Shop {
identification: string;
name: string;
interfaceId: number;
items: ShopItems[];
}

interface ShopItems {
id: number;
name: string;
amountInStock: number;
price: number;
}

export function parseShops(): Shop[] {
try {
logger.info('Parsing shops...');

const shops = safeLoad(readFileSync('data/config/shops.yaml', 'utf8'), { schema: JSON_SCHEMA }) as Shop[];

if(!shops || shops.length === 0) {
throw 'Unable to read shops.';
}

logger.info(`${shops.length} shops found.`);

return shops;
} catch(error) {
logger.error('Error parsing shops: ' + error);
return null;
}
}

function findShop(identification: string): Shop {
for(let i = 0; i <= world.shops.length; i++) {
if(world.shops[i].identification === identification) return world.shops[i];
}
return undefined;
}

export function openShop(player: Player, identification: string, closeOnWalk: boolean = true): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer this to be it's own action as opposed to having it inside the shop config file

try {
const openedShop = findShop(identification);
if(openedShop === undefined) {
throw `Unable to find the shop with identification of: ${identification}`;
}
player.packetSender.updateWidgetString(3901, openedShop.name);
for(let i = 0; i < 30; i++) {
if(openedShop.items.length <= i) {
player.packetSender.sendUpdateSingleWidgetItem(3900, i, null);
} else {
player.packetSender.sendUpdateSingleWidgetItem(3900, i, {
itemId: openedShop.items[i].id, amount: openedShop.items[i].amountInStock
});
}
}
for(let i = 0; i < openedShop.items.length; i++) {
player.packetSender.sendUpdateSingleWidgetItem(3900, i, {
itemId: openedShop.items[i].id, amount: openedShop.items[i].amountInStock
});
}
player.activeWidget = {
widgetId: 3824,
type: 'SCREEN',
closeOnWalk: closeOnWalk
};
} catch (error) {
logger.error(`Error opening shop ${identification}: ` + error);
}

}
6 changes: 5 additions & 1 deletion src/world/items/item-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class ItemContainer {
}
}

return -1;
return undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change this to undefined? That may mess with other things across the server

}

public add(item: number | Item, fireEvent: boolean = true): { item: Item, slot: number} {
Expand Down Expand Up @@ -102,6 +102,10 @@ export class ItemContainer {
}
}

public amountInStack(index: number): number {
return this._items[index].amount;
}

public removeFirst(item: number | Item, fireEvent: boolean = true): number {
const slot = this.findIndex(item);
if(slot === -1) {
Expand Down
30 changes: 30 additions & 0 deletions src/world/mob/player/action/buy-item-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Player } from '@server/world/mob/player/player';
import { gameCache } from '@server/game-server';
import { widgetIds } from '@server/world/mob/player/widget';

export const buyItemAction = (player: Player, itemId: number, amount: number, slot: number, interfaceId: number) => {

const purchasedItem = gameCache.itemDefinitions.get(itemId);
const coinsInInventoryIndex = player.inventory.findIndex(995);
const amountInStack = player.inventory.amountInStack(coinsInInventoryIndex);
const amountLeftAfterPurchase = amountInStack - (purchasedItem.value * amount);

// Take the money.
player.inventory.set(player.inventory.findIndex(itemId), { itemId, amount: amount});
player.inventory.set(coinsInInventoryIndex, {itemId: 995, amount: amountLeftAfterPurchase});

// Add the purchased item(s) to the inventory.
if (amount > 1) {
for (let i = 0; i < amount; i++) {
player.inventory.add(itemId);
}
}

if(amount === 1) {
player.inventory.add(itemId);
}

// Update the inventory items.
player.packetSender.sendUpdateAllWidgetItems(widgetIds.inventory, player.inventory);

};
49 changes: 49 additions & 0 deletions src/world/mob/player/packet/impl/buy-item-packet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { incomingPacket } from '@server/world/mob/player/packet/incoming-packet';
import { Player } from '@server/world/mob/player/player';
import { RsBuffer } from '@server/net/rs-buffer';
import { gameCache } from '@server/game-server';
import { buyItemAction } from '@server/world/mob/player/action/buy-item-action';

export const buyItemPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: RsBuffer): void => {

if(packetId === 177) {
const slot = packet.readNegativeOffsetShortBE();
const itemId = packet.readShortLE();
const interfaceId = packet.readShortLE();

if(player.inventory.findItemIndex({itemId: 995, amount: gameCache.itemDefinitions.get(itemId).value}) === undefined) {
player.packetSender.chatboxMessage(`You don't have enough coins.`);
return;
}

buyItemAction(player, itemId, 1, slot, interfaceId);
}

if(packetId === 91) {
const itemId = packet.readShortLE();
const slot = packet.readNegativeOffsetShortLE();
const interfaceId = packet.readShortBE();

if(player.inventory.findItemIndex({itemId: 995, amount: gameCache.itemDefinitions.get(itemId).value * 5}) === undefined) {
player.packetSender.chatboxMessage(`You don't have enough coins.`);
return;
}

buyItemAction(player, itemId, 5, slot, interfaceId);
}

if(packetId === 231) {
const interfaceId = packet.readNegativeOffsetShortLE();
const slot = packet.readShortLE();
const itemId = packet.readShortBE();

if(player.inventory.findItemIndex({itemId: 995, amount: gameCache.itemDefinitions.get(itemId).value * 10}) === undefined) {
player.packetSender.chatboxMessage(`You don't have enough coins.`);
return;
}

buyItemAction(player, itemId, 10, slot, interfaceId);
}

return;
};
7 changes: 6 additions & 1 deletion src/world/mob/player/packet/incoming-packet-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { objectInteractionPacket } from '@server/world/mob/player/packet/impl/ob
import { chatPacket } from '@server/world/mob/player/packet/impl/chat-packet';
import { dropItemPacket } from '@server/world/mob/player/packet/impl/drop-item-packet';
import { itemOnItemPacket } from '@server/world/mob/player/packet/impl/item-on-item-packet';
import { buyItemPacket } from '@server/world/mob/player/packet/impl/buy-item-packet';

const packets: { [key: number]: incomingPacket } = {
19: interfaceClickPacket,
Expand Down Expand Up @@ -51,7 +52,11 @@ const packets: { [key: number]: incomingPacket } = {
1: itemOnItemPacket,

49: chatPacket,
56: commandPacket
56: commandPacket,

177: buyItemPacket,
91: buyItemPacket,
231: buyItemPacket
};

export function handlePacket(player: Player, packetId: number, packetSize: number, buffer: Buffer): void {
Expand Down
20 changes: 10 additions & 10 deletions src/world/mob/player/packet/packet-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,20 +314,20 @@ export class PacketSender {

public sendUpdateSingleWidgetItem(widgetId: number, slot: number, item: Item): void {
const packet = new Packet(134, PacketType.DYNAMIC_LARGE);
packet.writeShortBE(widgetId);
packet.writeUnsignedShortBE(widgetId);
packet.writeSmart(slot);

if(!item) {
packet.writeShortBE(0);
packet.writeByte(0);
packet.writeUnsignedShortBE(0);
packet.writeUnsignedByte(0);
} else {
packet.writeShortBE(item.itemId + 1); // +1 because 0 means an empty slot
packet.writeUnsignedShortBE(item.itemId + 1); // +1 because 0 means an empty slot

if(item.amount >= 255) {
packet.writeByte(255);
packet.writeUnsignedByte(255);
packet.writeIntBE(item.amount);
} else {
packet.writeByte(item.amount);
packet.writeUnsignedByte(item.amount);
}
}

Expand All @@ -344,15 +344,15 @@ export class PacketSender {
if(!item) {
// Empty slot
packet.writeOffsetShortLE(0);
packet.writeByteInverted(0);
packet.writeUnsignedByteInverted(0 - 1);
} else {
packet.writeOffsetShortLE(item.itemId + 1); // +1 because 0 means an empty slot

if(item.amount >= 255) {
packet.writeByteInverted(255);
packet.writeIntBE(item.amount);
packet.writeUnsignedByteInverted(254);
packet.writeIntLE(item.amount);
} else {
packet.writeByteInverted(item.amount);
packet.writeUnsignedByteInverted(item.amount - 1);
}
}
});
Expand Down
3 changes: 3 additions & 0 deletions src/world/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Position } from './position';
import yargs from 'yargs';
import { NpcSpawn, parseNpcSpawns } from './config/npc-spawn';
import { Npc } from './mob/npc/npc';
import { parseShops, Shop } from '@server/world/config/shops';

/**
* Controls the game world and all entities within it.
Expand All @@ -22,10 +23,12 @@ export class World {
public readonly chunkManager: ChunkManager = new ChunkManager();
public readonly itemData: Map<number, ItemDetails>;
public readonly npcSpawns: NpcSpawn[];
public readonly shops: Shop[];

public constructor() {
this.itemData = parseItemData(gameCache.itemDefinitions);
this.npcSpawns = parseNpcSpawns();
this.shops = parseShops();

this.setupWorldTick();
}
Expand Down