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
4 changes: 4 additions & 0 deletions src/net/rs-buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ export class RsBuffer {
this.writeByte(-value);
}

public writeOffsetByte(value: number): void {
this.writeUnsignedByte(value + 128);
}

public writeNegativeOffsetByte(value: number): void {
this.writeUnsignedByte(128 - value);
}
Expand Down
2 changes: 1 addition & 1 deletion src/world/config/npc-spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { JSON_SCHEMA, safeLoad } from 'js-yaml';
import { readFileSync } from 'fs';
import { join } from 'path';
import { serverDir } from '@server/game-server';
import { Direction } from '@server/world/world';
import { Direction } from '@server/world/direction';

export interface NpcSpawn {
npcId: number;
Expand Down
62 changes: 62 additions & 0 deletions src/world/direction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
interface DirectionData {
index: number;
deltaX: number;
deltaY: number;
rotation: number;
}

/**
* A direction within the world.
*/
export type Direction = 'NORTH' | 'SOUTH' | 'EAST' | 'WEST' | 'NORTHEAST' | 'NORTHWEST' | 'SOUTHEAST' | 'SOUTHWEST';
export const directionData: { [key: string]: DirectionData } = {
'NORTH': {
index: 1,
deltaX: 0,
deltaY: 1,
rotation: 1
},
'SOUTH': {
index: 6,
deltaX: 0,
deltaY: -1,
rotation: 3
},
'EAST': {
index: 4,
deltaX: 1,
deltaY: 0,
rotation: 2
},
'WEST': {
index: 3,
deltaX: -1,
deltaY: 0,
rotation: 0
},
'NORTHEAST': {
index: 2,
deltaX: 1,
deltaY: 1,
rotation: 1
},
'NORTHWEST': {
index: 0,
deltaX: -1,
deltaY: 1,
rotation: 0
},
'SOUTHEAST': {
index: 7,
deltaX: 1,
deltaY: -1,
rotation: 2
},
'SOUTHWEST': {
index: 5,
deltaX: -1,
deltaY: -1,
rotation: 3
}
};
export const WNES: Direction[] = ['WEST', 'NORTH', 'EAST', 'SOUTH'];
52 changes: 51 additions & 1 deletion src/world/map/chunk-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Chunk } from './chunk';
import { Position } from '../position';
import { gameCache } from '../../game-server';
import { logger } from '@runejs/logger';
import { LandscapeObject } from '@runejs/cache-parser';

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

public toggleObjects(newObject: LandscapeObject, oldObject: LandscapeObject, newPosition: Position, oldPosition: Position,
newChunk: Chunk, oldChunk: Chunk, newObjectInCache: boolean): void {
if(newObjectInCache) {
this.deleteRemovedObjectMarker(newObject, newPosition, newChunk);
this.deleteAddedObjectMarker(oldObject, oldPosition, oldChunk);
}

this.addLandscapeObject(newObject, newPosition);
this.removeLandscapeObject(oldObject, oldPosition);
}

public deleteAddedObjectMarker(object: LandscapeObject, position: Position, chunk: Chunk): void {
chunk.addedLandscapeObjects.delete(`${position.x},${position.y},${object.objectId}`);
}

public deleteRemovedObjectMarker(object: LandscapeObject, position: Position, chunk: Chunk): void {
chunk.removedLandscapeObjects.delete(`${position.x},${position.y},${object.objectId}`);
}

public removeLandscapeObject(object: LandscapeObject, position: Position): Promise<void> {
const chunk = this.getChunkForWorldPosition(position);
chunk.removeObject(object, position);

return new Promise(resolve => {
const nearbyPlayers = this.getSurroundingChunks(chunk).map(chunk => chunk.players).flat();

nearbyPlayers.forEach(player => {
player.packetSender.removeLandscapeObject(object, position);
});

resolve();
});
}

public addLandscapeObject(object: LandscapeObject, position: Position): Promise<void> {
const chunk = this.getChunkForWorldPosition(position);
chunk.addObject(object, position);

return new Promise(resolve => {
const nearbyPlayers = this.getSurroundingChunks(chunk).map(chunk => chunk.players).flat();

nearbyPlayers.forEach(player => {
player.packetSender.setLandscapeObject(object, position);
});

resolve();
});
}

public generateCollisionMaps(): void {
logger.info('Generating game world collision maps...');

Expand All @@ -30,7 +80,7 @@ export class ChunkManager {
for(const landscapeObject of objectList) {
const position = new Position(landscapeObject.x, landscapeObject.y, landscapeObject.level);
const chunk = this.getChunkForWorldPosition(position);
chunk.addObjectToCollisionMap(landscapeObject, position);
chunk.setCacheLandscapeObject(landscapeObject, position);
}

logger.info('Game world collision maps generated.', true);
Expand Down
57 changes: 56 additions & 1 deletion src/world/map/chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { gameCache } from '../../game-server';
import { LandscapeObject, LandscapeObjectDefinition, MapRegionTile } from '@runejs/cache-parser';
import { Npc } from '../mob/npc/npc';

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

/**
* A single map chunk within the game world that keeps track of the entities within it.
*/
Expand All @@ -15,16 +20,22 @@ export class Chunk {
private readonly _npcs: Npc[];
private readonly _collisionMap: CollisionMap;
private readonly _tileList: MapRegionTile[];
private readonly _cacheLandscapeObjects: Map<string, LandscapeObject>;
private readonly _addedLandscapeObjects: Map<string, LandscapeObject>;
private readonly _removedLandscapeObjects: Map<string, LandscapeObject>;

public constructor(position: Position) {
this._position = position;
this._players = [];
this._npcs = [];
this._collisionMap = new CollisionMap(8, 8, (position.x + 6) * 8, (position.y + 6) * 8, this);
this._tileList = [];
this._cacheLandscapeObjects = new Map<string, LandscapeObject>();
this._addedLandscapeObjects = new Map<string, LandscapeObject>();
this._removedLandscapeObjects = new Map<string, LandscapeObject>();
}

public addObjectToCollisionMap(landscapeObject: LandscapeObject, objectPosition: Position): void {
public setCacheLandscapeObject(landscapeObject: LandscapeObject, objectPosition: Position): void {
let tile = this.getTile(objectPosition);

if(!tile) {
Expand All @@ -33,6 +44,7 @@ export class Chunk {
}

this.markOnCollisionMap(landscapeObject, objectPosition, true);
this._cacheLandscapeObjects.set(`${objectPosition.x},${objectPosition.y},${landscapeObject.objectId}`, landscapeObject);
}

public addTile(tile: MapRegionTile, tilePosition: Position): void {
Expand Down Expand Up @@ -104,6 +116,37 @@ export class Chunk {
}
}

public removeObject(object: LandscapeObject, position: Position): void {
if(this.getCacheObject(object.objectId, position)) {
// Only add this as an "removed" object if it's from the cache, as that's all we care about
this.removedLandscapeObjects.set(`${position.x},${position.y},${object.objectId}`, object);
}

this.markOnCollisionMap(object, position, false);
}

public addObject(object: LandscapeObject, position: Position): void {
if(!this.getCacheObject(object.objectId, position)) {
// Only add this as an "added" object if there's not a cache object with the same id and position
// This becomes a "custom" added object
this.addedLandscapeObjects.set(`${position.x},${position.y},${object.objectId}`, object);
}

this.markOnCollisionMap(object, position, true);
}

public getCacheObject(objectId: number, position: Position): LandscapeObject {
return this.cacheLandscapeObjects.get(`${position.x},${position.y},${objectId}`);
}

public getAddedObject(objectId: number, position: Position): LandscapeObject {
return this.addedLandscapeObjects.get(`${position.x},${position.y},${objectId}`);
}

public getRemovedObject(objectId: number, position: Position): LandscapeObject {
return this.removedLandscapeObjects.get(`${position.x},${position.y},${objectId}`);
}

public equals(chunk: Chunk): boolean {
return this.position.x === chunk.position.x && this.position.y === chunk.position.y && this.position.level === chunk.position.level;
}
Expand All @@ -127,4 +170,16 @@ export class Chunk {
public get tileList(): MapRegionTile[] {
return this._tileList;
}

public get cacheLandscapeObjects(): Map<string, LandscapeObject> {
return this._cacheLandscapeObjects;
}

public get addedLandscapeObjects(): Map<string, LandscapeObject> {
return this._addedLandscapeObjects;
}

public get removedLandscapeObjects(): Map<string, LandscapeObject> {
return this._removedLandscapeObjects;
}
}
9 changes: 9 additions & 0 deletions src/world/map/landscape-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LandscapeObject } from '@runejs/cache-parser';

export interface ModifiedLandscapeObject extends LandscapeObject {
metadata?: { [key: string]: any };
}

export const objectKey = (object: LandscapeObject, level: boolean = false): string => {
return `${object.x},${object.y}${level ? `,${object.level}` : ''},${object.objectId}`;
};
2 changes: 1 addition & 1 deletion src/world/mob/npc/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { NpcSpawn } from '@server/world/config/npc-spawn';
import { NpcDefinition } from '@runejs/cache-parser';
import uuidv4 from 'uuid/v4';
import { Position } from '@server/world/position';
import { Direction } from '@server/world/world';
import { world } from '@server/game-server';
import { Direction } from '@server/world/direction';

interface NpcAnimations {
walk: number;
Expand Down
27 changes: 27 additions & 0 deletions src/world/mob/player/action/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Player } from '@server/world/mob/player/player';
import { Position } from '@server/world/position';

export const walkToAction = (player: Player, position: Position): Promise<void> => {
return new Promise<void>((resolve, reject) => {
player.walkingTo = position;

const inter = setInterval(() => {
if(!player.walkingTo) {
clearInterval(inter);
reject();
return;
}

if(!player.walkingQueue.moving()) {
if(player.position.distanceBetween(position) > 1) {
reject();
} else {
resolve();
}

clearInterval(inter);
player.walkingTo = null;
}
}, 100);
});
};
68 changes: 68 additions & 0 deletions src/world/mob/player/action/door-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Player } from '@server/world/mob/player/player';
import { LandscapeObject } from '@runejs/cache-parser';
import { Position } from '@server/world/position';
import { Direction, directionData, WNES } from '@server/world/direction';
import { world } from '@server/game-server';
import { ModifiedLandscapeObject } from '@server/world/map/landscape-object';
import { Chunk } from '@server/world/map/chunk';

const leftHinge = [1516, 1536, 1533];
const rightHinge = [1519, 1530, 4465, 4467, 3014, 3017, 3018, 3019, 1531];
const preOpened = [1531, 1534];

export const doorAction = (player: Player, door: LandscapeObject, position: Position, cacheOriginal: boolean): void => {
let newDoor: ModifiedLandscapeObject;
let newPosition: Position;
const originalDoorChunk: Chunk = world.chunkManager.getChunkForWorldPosition(position);

if(cacheOriginal) {
let leftHingeDirections: { [key: string]: string } = {
'NORTH': 'WEST',
'SOUTH': 'EAST',
'WEST': 'SOUTH',
'EAST': 'NORTH'
};
let rightHingeDirections: { [key: string]: string } = {
'NORTH': 'EAST',
'SOUTH': 'WEST',
'WEST': 'NORTH',
'EAST': 'SOUTH'
};
let alreadyOpen = false;

if(preOpened.indexOf(door.objectId) !== -1) {
alreadyOpen = true;
}

let hinge: 'RIGHT' | 'LEFT';
if(leftHinge.indexOf(door.objectId) !== -1) {
hinge = alreadyOpen ? 'RIGHT' : 'LEFT';
} else {
hinge = alreadyOpen ? 'LEFT' : 'RIGHT';
}

const originalDirection = WNES[door.rotation];
const newDirection = hinge === 'LEFT' ? leftHingeDirections[originalDirection] : rightHingeDirections[originalDirection];
newPosition = position.step(alreadyOpen ? -1 : 1, alreadyOpen ? newDirection as Direction : originalDirection);

newDoor = {
objectId: door.objectId,
x: newPosition.x,
y: newPosition.y,
level: position.level,
type: door.type,
rotation: directionData[newDirection].rotation,
metadata: {
'originalPosition': position,
'originalObject': door
}
};
} else {
newPosition = (door as ModifiedLandscapeObject).metadata['originalPosition'];
newDoor = (door as ModifiedLandscapeObject).metadata['originalObject'];
}

const newDoorChunk = world.chunkManager.getChunkForWorldPosition(newPosition);

world.chunkManager.toggleObjects(newDoor, door, newPosition, position, newDoorChunk, originalDoorChunk, !cacheOriginal);
};
1 change: 1 addition & 0 deletions src/world/mob/player/action/input-command-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const commands: { [key: string]: commandHandler } = {
if(!oldChunk.equals(newChunk)) {
oldChunk.removePlayer(player);
newChunk.addPlayer(player);
player.chunkChanged(newChunk);
player.packetSender.updateCurrentMapChunk();
}

Expand Down
Loading