Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions src/world/items/world-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Position } from '@server/world/position';
import { Player } from '@server/world/mob/player/player';

export interface WorldItem {
itemId: number;
amount: number;
position: Position;
initiallyVisibleTo?: Player;
expires?: number;
}
62 changes: 62 additions & 0 deletions src/world/map/chunk-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { Position } from '../position';
import { gameCache } from '../../game-server';
import { logger } from '@runejs/logger';
import { LandscapeObject } from '@runejs/cache-parser';
import { Item } from '@server/world/items/item';
import { Player } from '@server/world/mob/player/player';
import { WorldItem } from '@server/world/items/world-item';
import { World } from '@server/world/world';

/**
* Controls all of the game world's map chunks.
Expand All @@ -15,6 +19,64 @@ export class ChunkManager {
this.chunkMap = new Map<string, Chunk>();
}

public spawnWorldItem(item: Item, position: Position, initiallyVisibleTo?: Player, expires?: number): void {
const chunk = this.getChunkForWorldPosition(position);
const worldItem: WorldItem = {
itemId: item.itemId,
amount: item.amount,
position,
initiallyVisibleTo,
expires
};

chunk.addWorldItem(worldItem);

if(initiallyVisibleTo) {
initiallyVisibleTo.packetSender.setWorldItem(worldItem, worldItem.position);
setTimeout(() => {
this.spawnWorldItemForPlayers(worldItem, chunk, initiallyVisibleTo);
worldItem.initiallyVisibleTo = undefined;
}, 100 * World.TICK_LENGTH);
} else {
this.spawnWorldItemForPlayers(worldItem, chunk);
}

if(expires) {
setTimeout(() => {
chunk.removeWorldItem(worldItem);
this.deleteWorldItemForPlayers(worldItem, chunk);
}, expires * World.TICK_LENGTH);
}
}

private spawnWorldItemForPlayers(worldItem: WorldItem, chunk: Chunk, excludePlayer?: Player): Promise<void> {
return new Promise(resolve => {
const nearbyPlayers = this.getSurroundingChunks(chunk).map(chunk => chunk.players).flat();

nearbyPlayers.forEach(player => {
if(excludePlayer && excludePlayer.equals(player)) {
return;
}

player.packetSender.setWorldItem(worldItem, worldItem.position);
});

resolve();
});
}

private deleteWorldItemForPlayers(worldItem: WorldItem, chunk: Chunk): Promise<void> {
return new Promise(resolve => {
const nearbyPlayers = this.getSurroundingChunks(chunk).map(chunk => chunk.players).flat();

nearbyPlayers.forEach(player => {
player.packetSender.removeWorldItem(worldItem, worldItem.position);
});

resolve();
});
}

public toggleObjects(newObject: LandscapeObject, oldObject: LandscapeObject, newPosition: Position, oldPosition: Position,
newChunk: Chunk, oldChunk: Chunk, newObjectInCache: boolean): void {
if(newObjectInCache) {
Expand Down
32 changes: 31 additions & 1 deletion src/world/map/chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { CollisionMap } from './collision-map';
import { gameCache } from '../../game-server';
import { LandscapeObject, LandscapeObjectDefinition, MapRegionTile } from '@runejs/cache-parser';
import { Npc } from '../mob/npc/npc';
import { WorldItem } from '@server/world/items/world-item';

export interface ChunkUpdateItem {
object: LandscapeObject,
object?: LandscapeObject,
worldItem?: WorldItem,
type: 'ADD' | 'REMOVE'
}

Expand All @@ -23,6 +25,7 @@ export class Chunk {
private readonly _cacheLandscapeObjects: Map<string, LandscapeObject>;
private readonly _addedLandscapeObjects: Map<string, LandscapeObject>;
private readonly _removedLandscapeObjects: Map<string, LandscapeObject>;
private readonly _worldItems: Map<string, WorldItem[]>;

public constructor(position: Position) {
this._position = position;
Expand All @@ -33,6 +36,29 @@ export class Chunk {
this._cacheLandscapeObjects = new Map<string, LandscapeObject>();
this._addedLandscapeObjects = new Map<string, LandscapeObject>();
this._removedLandscapeObjects = new Map<string, LandscapeObject>();
this._worldItems = new Map<string, WorldItem[]>();
}

public addWorldItem(worldItem: WorldItem): void {
const key = worldItem.position.key;

if(this._worldItems.has(key)) {
const list = this._worldItems.get(key);
list.push(worldItem);
this._worldItems.set(key, list);
} else {
this._worldItems.set(worldItem.position.key, [worldItem]);
}
}

public removeWorldItem(worldItem: WorldItem): void {
const key = worldItem.position.key;

if(this._worldItems.has(key)) {
let list = this._worldItems.get(key);
list = list.splice(list.indexOf(worldItem), 1);
this._worldItems.set(key, list);
}
}

public setCacheLandscapeObject(landscapeObject: LandscapeObject, objectPosition: Position): void {
Expand Down Expand Up @@ -182,4 +208,8 @@ export class Chunk {
public get removedLandscapeObjects(): Map<string, LandscapeObject> {
return this._removedLandscapeObjects;
}

public get worldItems(): Map<string, WorldItem[]> {
return this._worldItems;
}
}
12 changes: 12 additions & 0 deletions src/world/mob/player/action/drop-item-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Player } from '../player';
import { world } from '@server/game-server';
import { Item } from '@server/world/items/item';
import { interfaceIds } from '@server/world/mob/player/game-interface';

export const dropItemAction = (player: Player, item: Item, inventorySlot: number) => {
player.inventory.remove(inventorySlot);
// @TODO change packets to only update modified container slots
player.packetSender.sendUpdateAllInterfaceItems(interfaceIds.inventory, player.inventory);
player.packetSender.playSound(376, 7);
world.chunkManager.spawnWorldItem(item, player.position, player, 300);
};
1 change: 0 additions & 1 deletion src/world/mob/player/action/equip-item-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const equipItemAction = (player: Player, itemId: number, inventorySlot: n
}

// @TODO change packets to only update modified container slots

player.packetSender.sendUpdateAllInterfaceItems(interfaceIds.inventory, inventory);
player.packetSender.sendUpdateAllInterfaceItems(interfaceIds.equipment, equipment);
player.updateBonuses();
Expand Down
36 changes: 36 additions & 0 deletions src/world/mob/player/packet/impl/drop-item-packet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { incomingPacket } from '../incoming-packet';
import { Player } from '../../player';
import { RsBuffer } from '@server/net/rs-buffer';
import { logger } from '@runejs/logger/dist/logger';
import { interfaceIds } from '../../game-interface';
import { dropItemAction } from '@server/world/mob/player/action/drop-item-action';

export const dropItemPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: RsBuffer): void => {
const slot = packet.readShortLE();
const itemId = packet.readNegativeOffsetShortLE();
const interfaceId = packet.readNegativeOffsetShortLE();

if(interfaceId !== interfaceIds.inventory) {
logger.warn(`${player.username} attempted to drop item from incorrect interface id ${interfaceId}.`);
return;
}

if(slot < 0 || slot > 27) {
logger.warn(`${player.username} attempted to drop item ${itemId} in invalid slot ${slot}.`);
return;
}

const itemInSlot = player.inventory.items[slot];

if(!itemInSlot) {
logger.warn(`${player.username} attempted to drop item ${itemId} in slot ${slot}, but they do not have that item.`);
return;
}

if(itemInSlot.itemId !== itemId) {
logger.warn(`${player.username} attempted to drop item ${itemId} in slot ${slot}, but ${itemInSlot.itemId} was found there instead.`);
return;
}

dropItemAction(player, itemInSlot, slot);
};
2 changes: 2 additions & 0 deletions src/world/mob/player/packet/incoming-packet-directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { itemSwapPacket } from './impl/item-swap-packet';
import { dialogueInteractionPacket } from '@server/world/mob/player/packet/impl/dialogue-interaction-packet';
import { npcInteractionPacket } from '@server/world/mob/player/packet/impl/npc-interaction-packet';
import { objectInteractionPacket } from '@server/world/mob/player/packet/impl/object-interaction-packet';
import { dropItemPacket } from '@server/world/mob/player/packet/impl/drop-item-packet';

const packets: { [key: number]: incomingPacket } = {
19: interfaceClickPacket,
Expand All @@ -35,6 +36,7 @@ const packets: { [key: number]: incomingPacket } = {
24: itemEquipPacket,
3: itemOption1Packet,
123: itemSwapPacket,
4: dropItemPacket,

56: commandPacket
};
Expand Down
42 changes: 36 additions & 6 deletions src/world/mob/player/packet/packet-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Item } from '@server/world/items/item';
import { Position } from '@server/world/position';
import { LandscapeObject } from '@runejs/cache-parser';
import { Chunk, ChunkUpdateItem } from '@server/world/map/chunk';
import { WorldItem } from '@server/world/items/world-item';

/**
* 6 = set chatbox input type to 2
Expand Down Expand Up @@ -128,14 +129,22 @@ export class PacketSender {
packet.writeOffsetByte(offsetY);

chunkUpdates.forEach(update => {
const offset = this.getChunkPositionOffset(update.object.x, update.object.y, chunk);

if(update.type === 'ADD') {
packet.writeUnsignedByte(152);
packet.writeByteInverted((update.object.type << 2) + (update.object.rotation & 3));
packet.writeOffsetShortLE(update.object.objectId);
packet.writeOffsetByte(offset);
if(update.object) {
const offset = this.getChunkPositionOffset(update.object.x, update.object.y, chunk);
packet.writeUnsignedByte(152);
packet.writeByteInverted((update.object.type << 2) + (update.object.rotation & 3));
packet.writeOffsetShortLE(update.object.objectId);
packet.writeOffsetByte(offset);
} else if(update.worldItem) {
const offset = this.getChunkPositionOffset(update.worldItem.position.x, update.worldItem.position.y, chunk);
packet.writeUnsignedByte(107);
packet.writeShortBE(update.worldItem.itemId);
packet.writeByteInverted(offset);
packet.writeNegativeOffsetShortBE(update.worldItem.amount);
}
} else if(update.type === 'REMOVE') {
const offset = this.getChunkPositionOffset(update.object.x, update.object.y, chunk);
packet.writeUnsignedByte(88);
packet.writeNegativeOffsetByte(offset);
packet.writeNegativeOffsetByte((update.object.type << 2) + (update.object.rotation & 3));
Expand All @@ -155,6 +164,27 @@ export class PacketSender {
this.send(packet);
}

public setWorldItem(worldItem: WorldItem, position: Position, offset: number = 0): void {
this.updateReferencePosition(position);

const packet = new Packet(107);
packet.writeShortBE(worldItem.itemId);
packet.writeByteInverted(offset);
packet.writeNegativeOffsetShortBE(worldItem.amount);

this.send(packet);
}

public removeWorldItem(worldItem: WorldItem, position: Position, offset: number = 0): void {
this.updateReferencePosition(position);

const packet = new Packet(208);
packet.writeNegativeOffsetShortBE(worldItem.itemId);
packet.writeOffsetByte(offset);

this.send(packet);
}

public setLandscapeObject(landscapeObject: LandscapeObject, position: Position, offset: number = 0): void {
this.updateReferencePosition(position);

Expand Down
13 changes: 12 additions & 1 deletion src/world/mob/player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ export class Player extends Mob {

/**
* Sends chunk updates to notify the client of added & removed landscape objects
* @TODO ground items
* @param chunks The chunks to update.
*/
private sendChunkUpdates(chunks: Chunk[]): void {
Expand All @@ -199,6 +198,18 @@ export class Player extends Mob {
chunk.removedLandscapeObjects.forEach(object => chunkUpdateItems.push({ object, type: 'REMOVE' }));
}

if(chunk.worldItems.size !== 0) {
chunk.worldItems.forEach(worldItemList => {
if(worldItemList && worldItemList.length !== 0) {
worldItemList.forEach(worldItem => {
if(!worldItem.initiallyVisibleTo || worldItem.initiallyVisibleTo.equals(this)) {
chunkUpdateItems.push({worldItem, type: 'ADD'});
}
});
}
});
}

if(chunkUpdateItems.length !== 0) {
this.packetSender.updateChunk(chunk, chunkUpdateItems);
}
Expand Down