From bf0593db35af873a5a6e93e005b2ab6e607b2709 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Tue, 24 Mar 2020 18:31:03 -0500 Subject: [PATCH 1/8] Working on pathfinding stuff. --- src/world/actor/actor.ts | 3 + src/world/actor/pathfinding.ts | 136 ++++++++++++++++++++++++++++++ src/world/actor/walking-queue.ts | 140 ++----------------------------- 3 files changed, 144 insertions(+), 135 deletions(-) create mode 100644 src/world/actor/pathfinding.ts diff --git a/src/world/actor/actor.ts b/src/world/actor/actor.ts index 0842c5bad..e0f16fc84 100644 --- a/src/world/actor/actor.ts +++ b/src/world/actor/actor.ts @@ -8,6 +8,7 @@ import { Item } from '@server/world/items/item'; import { Position } from '@server/world/position'; import { DirectionData, directionFromIndex } from '@server/world/direction'; import { CombatAction } from '@server/world/actor/player/action/combat-action'; +import { Pathfinding } from '@server/world/actor/pathfinding'; /** * Handles an actor within the game world. @@ -25,6 +26,7 @@ export abstract class Actor extends Entity { private _busy: boolean; public readonly metadata: { [key: string]: any } = {}; private _combatActions: CombatAction[]; + public readonly pathfinding: Pathfinding; protected constructor() { super(); @@ -37,6 +39,7 @@ export abstract class Actor extends Entity { this.skills = new Skills(this); this._busy = false; this._combatActions = []; + this.pathfinding = new Pathfinding(this); } public face(face: Position | Actor, clearWalkingQueue: boolean = true, autoClear: boolean = true): void { diff --git a/src/world/actor/pathfinding.ts b/src/world/actor/pathfinding.ts new file mode 100644 index 000000000..0593e1e01 --- /dev/null +++ b/src/world/actor/pathfinding.ts @@ -0,0 +1,136 @@ +import { world } from '@server/game-server'; +import { Actor } from '@server/world/actor/actor'; +import { Position } from '../position'; +import { Chunk } from '@server/world/map/chunk'; +import { Tile } from '@runejs/cache-parser'; + +export class Pathfinding { + + public constructor(private actor: Actor) { + } + + public pathTo(destinationX: number, destinationY: number): void { + const path: Position[][] = new Array(16).fill(new Array(16)); + } + + public canMoveTo(origin: Position, destination: Position): boolean { + let destinationChunk: Chunk = world.chunkManager.getChunkForWorldPosition(destination); + const positionAbove: Position = new Position(destination.x, destination.y, destination.level + 1); + const chunkAbove: Chunk = world.chunkManager.getChunkForWorldPosition(positionAbove); + let tile: Tile = chunkAbove.getTile(positionAbove); + + if(!tile || !tile.bridge) { + tile = destinationChunk.getTile(destination); + } else { + // Destination is a bridge, so we need to check the chunk above to get the bridge tiles instead of the level we're currently on + destinationChunk = chunkAbove; + } + + if(tile) { + if(tile.nonWalkable) { + return false; + } + } + + const initialX: number = origin.x; + const initialY: number = origin.y; + const destinationAdjacency: number[][] = destinationChunk.collisionMap.adjacency; + const destinationLocalX: number = destination.x - destinationChunk.collisionMap.insetX; + const destinationLocalY: number = destination.y - destinationChunk.collisionMap.insetY; + + // @TODO check objects moving from bridge tile to non bridge tile + // ^ currently possible to clip through some bridge walls thanks to this issue + // not the most important thing since you still can't walk on water or anything + + // West + if(destination.x < initialX && destination.y == initialY) { + if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280108) != 0) { + return false; + } + } + + // East + if(destination.x > initialX && destination.y == initialY) { + if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280180) != 0) { + return false; + } + } + + // South + if(destination.y < initialY && destination.x == initialX) { + if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280102) != 0) { + return false; + } + } + + // North + if(destination.y > initialY && destination.x == initialX) { + if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280120) != 0) { + return false; + } + } + + // South-West + if(destination.x < initialX && destination.y < initialY) { + if(!Pathfinding.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, -1, -1, + 0x128010e, 0x1280108, 0x1280102)) { + return false; + } + } + + // South-East + if(destination.x > initialX && destination.y < initialY) { + if(!Pathfinding.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, 1, -1, + 0x1280183, 0x1280180, 0x1280102)) { + return false; + } + } + + // North-West + if(destination.x < initialX && destination.y > initialY) { + if(!Pathfinding.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, -1, 1, + 0x1280138, 0x1280108, 0x1280120)) { + return false; + } + } + + // North-East + if(destination.x > initialX && destination.y > initialY) { + if(!Pathfinding.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, 1, 1, + 0x12801e0, 0x1280180, 0x1280120)) { + return false; + } + } + + return true; + } + + public static canMoveDiagonally(origin: Position, destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, + initialX: number, initialY: number, offsetX: number, offsetY: number, destMask: number, cornerMask1: number, cornerMask2: number): boolean { + const cornerX1: number = initialX + offsetX; + const cornerY1: number = initialY; + const cornerX2: number = initialX; + const cornerY2: number = initialY + offsetY; + const corner1 = Pathfinding.calculateLocalCornerPosition(cornerX1, cornerY1, origin); + const corner2 = Pathfinding.calculateLocalCornerPosition(cornerX2, cornerY2, origin); + + return ((destinationAdjacency[destinationLocalX][destinationLocalY] & destMask) == 0 && + (corner1.chunk.collisionMap.adjacency[corner1.localX][corner1.localY] & cornerMask1) == 0 && + (corner2.chunk.collisionMap.adjacency[corner2.localX][corner2.localY] & cornerMask2) == 0); + } + + private static calculateLocalCornerPosition(cornerX: number, cornerY: number, origin: Position): { localX: number, localY: number, chunk: Chunk } { + const cornerPosition: Position = new Position(cornerX, cornerY, origin.level + 1); + let cornerChunk: Chunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); + const tileAbove: Tile = cornerChunk.getTile(cornerPosition); + if(!tileAbove || !tileAbove.bridge) { + cornerPosition.level = cornerPosition.level - 1; + cornerChunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); + } + const localX: number = cornerX - cornerChunk.collisionMap.insetX; + const localY: number = cornerY - cornerChunk.collisionMap.insetY; + + return { localX, localY, chunk: cornerChunk }; + } + +} diff --git a/src/world/actor/walking-queue.ts b/src/world/actor/walking-queue.ts index 0a84eee08..fd6e92d1a 100644 --- a/src/world/actor/walking-queue.ts +++ b/src/world/actor/walking-queue.ts @@ -2,8 +2,6 @@ import { Actor } from './actor'; import { Position } from '../position'; import { Player } from './player/player'; import { world } from '@server/game-server'; -import { Chunk } from '../map/chunk'; -import { Tile } from '@runejs/cache-parser'; import { Npc } from './npc/npc'; /** @@ -59,7 +57,7 @@ export class WalkingQueue { const newPosition = new Position(lastX, lastY, this.actor.position.level); - if(this.canMoveTo(lastPosition, newPosition)) { + if(this.actor.pathfinding.canMoveTo(lastPosition, newPosition)) { lastPosition = newPosition; newPosition.metadata = positionMetadata; this.queue.push(newPosition); @@ -72,7 +70,7 @@ export class WalkingQueue { if(lastX !== x || lastY !== y && this.valid) { const newPosition = new Position(x, y, this.actor.position.level); - if(this.canMoveTo(lastPosition, newPosition)) { + if(this.actor.pathfinding.canMoveTo(lastPosition, newPosition)) { newPosition.metadata = positionMetadata; this.queue.push(newPosition); } else { @@ -85,7 +83,7 @@ export class WalkingQueue { const position = this.actor.position; const newPosition = new Position(position.x + xDiff, position.y + yDiff, position.level); - if(this.canMoveTo(position, newPosition)) { + if(this.actor.pathfinding.canMoveTo(position, newPosition)) { this.clear(); this.valid = true; this.add(newPosition.x, newPosition.y, { ignoreWidgets: true }); @@ -95,134 +93,6 @@ export class WalkingQueue { return false; } - public canMoveTo(origin: Position, destination: Position): boolean { - let destinationChunk: Chunk = world.chunkManager.getChunkForWorldPosition(destination); - const positionAbove: Position = new Position(destination.x, destination.y, destination.level + 1); - const chunkAbove: Chunk = world.chunkManager.getChunkForWorldPosition(positionAbove); - let tile: Tile = chunkAbove.getTile(positionAbove); - - if(!tile || !tile.bridge) { - tile = destinationChunk.getTile(destination); - } else { - // Destination is a bridge, so we need to check the chunk above to get the bridge tiles instead of the level we're currently on - destinationChunk = chunkAbove; - } - - if(tile) { - if(tile.nonWalkable) { - return false; - } - } - - const initialX: number = origin.x; - const initialY: number = origin.y; - const destinationAdjacency: number[][] = destinationChunk.collisionMap.adjacency; - const destinationLocalX: number = destination.x - destinationChunk.collisionMap.insetX; - const destinationLocalY: number = destination.y - destinationChunk.collisionMap.insetY; - - // @TODO check objects moving from bridge tile to non bridge tile - // ^ currently possible to clip through some bridge walls thanks to this issue - // not the most important thing since you still can't walk on water or anything - - // West - if(destination.x < initialX && destination.y == initialY) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280108) != 0) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move west.`); - return false; - } - } - - // East - if(destination.x > initialX && destination.y == initialY) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280180) != 0) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move east.`); - return false; - } - } - - // South - if(destination.y < initialY && destination.x == initialX) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280102) != 0) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move south.`); - return false; - } - } - - // North - if(destination.y > initialY && destination.x == initialX) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280120) != 0) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move north.`); - return false; - } - } - - // South-West - if(destination.x < initialX && destination.y < initialY) { - if(!this.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, -1, -1, - 0x128010e, 0x1280108, 0x1280102)) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move south-west.`); - return false; - } - } - - // South-East - if(destination.x > initialX && destination.y < initialY) { - if(!this.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, 1, -1, - 0x1280183, 0x1280180, 0x1280102)) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move south-east.`); - return false; - } - } - - // North-West - if(destination.x < initialX && destination.y > initialY) { - if(!this.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, -1, 1, - 0x1280138, 0x1280108, 0x1280120)) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move north-west.`); - return false; - } - } - - // North-East - if(destination.x > initialX && destination.y > initialY) { - if(!this.canMoveDiagonally(origin, destinationAdjacency, destinationLocalX, destinationLocalY, initialX, initialY, 1, 1, - 0x12801e0, 0x1280180, 0x1280120)) { - // logger.warn(`${this.actor instanceof Player ? this.actor.username + ' c' : 'C'}an not move north-east.`); - return false; - } - } - - return true; - } - - private canMoveDiagonally(origin: Position, destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, - initialX: number, initialY: number, offsetX: number, offsetY: number, destMask: number, cornerMask1: number, cornerMask2: number): boolean { - const cornerX1: number = initialX + offsetX; - const cornerY1: number = initialY; - const cornerX2: number = initialX; - const cornerY2: number = initialY + offsetY; - const corner1 = this.calculateLocalCornerPosition(cornerX1, cornerY1, origin); - const corner2 = this.calculateLocalCornerPosition(cornerX2, cornerY2, origin); - - return ((destinationAdjacency[destinationLocalX][destinationLocalY] & destMask) == 0 && - (corner1.chunk.collisionMap.adjacency[corner1.localX][corner1.localY] & cornerMask1) == 0 && - (corner2.chunk.collisionMap.adjacency[corner2.localX][corner2.localY] & cornerMask2) == 0); - } - - private calculateLocalCornerPosition(cornerX: number, cornerY: number, origin: Position): { localX: number, localY: number, chunk: Chunk } { - const cornerPosition: Position = new Position(cornerX, cornerY, origin.level + 1); - let cornerChunk: Chunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); - const tileAbove: Tile = cornerChunk.getTile(cornerPosition); - if(!tileAbove || !tileAbove.bridge) { - cornerPosition.level = cornerPosition.level - 1; - cornerChunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); - } - const localX: number = cornerX - cornerChunk.collisionMap.insetX; - const localY: number = cornerY - cornerChunk.collisionMap.insetY; - - return { localX, localY, chunk: cornerChunk }; - } - public resetDirections(): void { this.actor.walkDirection = -1; this.actor.runDirection = -1; @@ -286,7 +156,7 @@ export class WalkingQueue { const currentPosition = this.actor.position; - if(this.canMoveTo(currentPosition, walkPosition)) { + if(this.actor.pathfinding.canMoveTo(currentPosition, walkPosition)) { const oldChunk = world.chunkManager.getChunkForWorldPosition(currentPosition); const lastMapRegionUpdatePosition = this.actor.lastMapRegionUpdatePosition; @@ -308,7 +178,7 @@ export class WalkingQueue { if(this.actor.settings.runEnabled && this.queue.length !== 0) { const runPosition = this.queue.shift(); - if(this.canMoveTo(walkPosition, runPosition)) { + if(this.actor.pathfinding.canMoveTo(walkPosition, runPosition)) { const runDiffX = runPosition.x - walkPosition.x; const runDiffY = runPosition.y - walkPosition.y; runDir = this.calculateDirection(runDiffX, runDiffY); From cfacc35652922e7d08f3618cb440e592bd873ed6 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Wed, 25 Mar 2020 23:33:41 -0500 Subject: [PATCH 2/8] Fixing bridge tile path validation completely. --- src/world/actor/pathfinding.ts | 158 ++++++++++++++++++++++++++++----- src/world/map/chunk.ts | 48 +++++++++- 2 files changed, 181 insertions(+), 25 deletions(-) diff --git a/src/world/actor/pathfinding.ts b/src/world/actor/pathfinding.ts index 0593e1e01..d3c11982e 100644 --- a/src/world/actor/pathfinding.ts +++ b/src/world/actor/pathfinding.ts @@ -4,32 +4,135 @@ import { Position } from '../position'; import { Chunk } from '@server/world/map/chunk'; import { Tile } from '@runejs/cache-parser'; +class Point { + + private _parent: Point = null; + private _cost: number; + private _heuristic: number; + private _depth: number; + + public constructor(private readonly _x: number, private readonly _y: number) { + } + + public compare(point: Point): number { + return this._cost - point._cost; + } + + public equals(point: Point): boolean { + if(this._cost === point._cost && this._heuristic === point._heuristic && this._depth === point._depth) { + if(this._parent === null && point._parent !== null) { + return false; + } else if(this._parent !== null && !this._parent.equals(point._parent)) { + return false; + } + + return this._x === point._x && this._y === point._y; + } + + return false; + } + + public get x(): number { + return this._x; + } + + public get y(): number { + return this._y; + } + + public get parent(): Point { + return this._parent; + } + + public set parent(value: Point) { + this._parent = value; + } + + public get cost(): number { + return this._cost; + } + + public set cost(value: number) { + this._cost = value; + } + + public get heuristic(): number { + return this._heuristic; + } + + public set heuristic(value: number) { + this._heuristic = value; + } + + public get depth(): number { + return this._depth; + } + + public set depth(value: number) { + this._depth = value; + } + +} + export class Pathfinding { + private currentPoint: Point; + private points: Point[][]; + private closedPoints: Point[] = []; + private openPoints: Point[] = []; + public constructor(private actor: Actor) { } - public pathTo(destinationX: number, destinationY: number): void { - const path: Position[][] = new Array(16).fill(new Array(16)); - } + public pathTo(destinationX: number, destinationY: number, diameter: number = 32): void { + // @TODO check if destination is too far away - public canMoveTo(origin: Position, destination: Position): boolean { - let destinationChunk: Chunk = world.chunkManager.getChunkForWorldPosition(destination); - const positionAbove: Position = new Position(destination.x, destination.y, destination.level + 1); - const chunkAbove: Chunk = world.chunkManager.getChunkForWorldPosition(positionAbove); - let tile: Tile = chunkAbove.getTile(positionAbove); - - if(!tile || !tile.bridge) { - tile = destinationChunk.getTile(destination); - } else { - // Destination is a bridge, so we need to check the chunk above to get the bridge tiles instead of the level we're currently on - destinationChunk = chunkAbove; + const currentPos = this.actor.position; + const radius = Math.floor(diameter / 2); + const startX = currentPos.x; + const startY = currentPos.y; + const pathingStartX = startX - radius; + const pathingStartY = startY - radius; + + this.points = new Array(diameter).fill(new Array(diameter)); + + for(let x = 0; x < diameter; x++) { + for(let y = 0; y < diameter; y++) { + this.points[x][y] = new Point(x + startX, y + startY); + } } - if(tile) { - if(tile.nonWalkable) { - return false; + // Center point + this.openPoints.push(this.points[radius + 1][radius + 1]); + + while(this.openPoints.length > 0) { + this.currentPoint = this.calculateBestPoint(); + + if(this.currentPoint === this.points[destinationX - pathingStartX][destinationY - pathingStartY]) { + break; } + + this.openPoints.splice(this.openPoints.indexOf(this.currentPoint), 1); + this.closedPoints.push(this.currentPoint); + + let x = this.currentPoint.x; + let y = this.currentPoint.y; + let level = this.actor.position.level; + let currentPosition = new Position(x, y, level); + + let testPosition = new Position(x - 1, y, level); + if(this.canMoveTo(currentPosition, testPosition)) { + const point = this.points[x - 1][y]; + } + } + } + + public canMoveTo(origin: Position, destination: Position): boolean { + const destinationChunk: Chunk = world.chunkManager.getChunkForWorldPosition(destination); + const tile: Tile = destinationChunk.getTile(destination); + + if(tile && tile.nonWalkable) { + return false; } const initialX: number = origin.x; @@ -38,10 +141,6 @@ export class Pathfinding { const destinationLocalX: number = destination.x - destinationChunk.collisionMap.insetX; const destinationLocalY: number = destination.y - destinationChunk.collisionMap.insetY; - // @TODO check objects moving from bridge tile to non bridge tile - // ^ currently possible to clip through some bridge walls thanks to this issue - // not the most important thing since you still can't walk on water or anything - // West if(destination.x < initialX && destination.y == initialY) { if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280108) != 0) { @@ -133,4 +232,21 @@ export class Pathfinding { return { localX, localY, chunk: cornerChunk }; } + private calculateBestPoint(): Point { + let bestPoint: Point = null; + + for(const point of this.openPoints) { + if(bestPoint === null) { + bestPoint = point; + continue; + } + + if(point.cost < bestPoint.cost) { + bestPoint = point; + } + } + + return bestPoint; + } + } diff --git a/src/world/map/chunk.ts b/src/world/map/chunk.ts index 27a2f529b..267f37488 100644 --- a/src/world/map/chunk.ts +++ b/src/world/map/chunk.ts @@ -1,7 +1,7 @@ import { Position } from '../position'; import { Player } from '../actor/player/player'; import { CollisionMap } from './collision-map'; -import { cache } from '../../game-server'; +import { cache, world } from '../../game-server'; import { LocationObject, LocationObjectDefinition, Tile } from '@runejs/cache-parser'; import { Npc } from '../actor/npc/npc'; import { WorldItem } from '@server/world/items/world-item'; @@ -83,12 +83,22 @@ export class Chunk { if(!tile) { tile = new Tile(objectPosition.x, objectPosition.y, objectPosition.level); - tile.flags = 0; + tile.bridge = false; + tile.nonWalkable = false; this.addTile(tile, objectPosition); } - this.markOnCollisionMap(locationObject, objectPosition, true); - this._cacheLocationObjects.set(`${objectPosition.x},${objectPosition.y},${locationObject.objectId}`, locationObject); + if(tile.bridge) { + // Move this marker down one level if it's on a bridge tile + objectPosition.level = objectPosition.level - 1; + const lowerChunk = world.chunkManager.getChunkForWorldPosition(objectPosition); + locationObject.level -= 1; + lowerChunk.markOnCollisionMap(locationObject, objectPosition, true); + lowerChunk.cacheLocationObjects.set(`${ objectPosition.x },${ objectPosition.y },${ locationObject.objectId }`, locationObject); + } else if(tile.bridge !== null) { + this.markOnCollisionMap(locationObject, objectPosition, true); + this._cacheLocationObjects.set(`${ objectPosition.x },${ objectPosition.y },${ locationObject.objectId }`, locationObject); + } } public addTile(tile: Tile, tilePosition: Position): void { @@ -97,6 +107,16 @@ export class Chunk { return; } + if(tile.bridge) { + // Move this tile down one level if it's a bridge tile + const newTilePosition = new Position(tilePosition.x, tilePosition.y, tilePosition.level - 1); + const lowerChunk = world.chunkManager.getChunkForWorldPosition(newTilePosition); + const newTile = new Tile(tilePosition.x, tilePosition.y, tilePosition.level - 1); + newTile.nonWalkable = tile.nonWalkable; + newTile.bridge = null; + lowerChunk.setTile(newTile, newTilePosition); + } + this._tileList.push(tile); } @@ -110,6 +130,26 @@ export class Chunk { return null; } + public findTile(position: Position): number { + for(let i = 0; i < this._tileList.length; i++) { + if(position.equalsIgnoreLevel({ x: this._tileList[i].x, y: this._tileList[i].y })) { + return i; + } + } + + return -1; + } + + public setTile(tile: Tile, tilePosition: Position): void { + const existingTileIndex = this.findTile(tilePosition); + + if(existingTileIndex !== -1) { + this._tileList.splice(existingTileIndex, 1); + } + + this._tileList.push(tile); + } + public addPlayer(player: Player): void { if(this._players.findIndex(p => p.equals(player)) === -1) { this._players.push(player); From ef7fcd835d73bba0c990e5b48334ad483d205402 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Sat, 28 Mar 2020 21:57:32 -0500 Subject: [PATCH 3/8] Adding an implementation of A Star pathfinding. --- src/plugins/commands/path-command.ts | 34 +++ src/world/actor/pathfinding.ts | 238 +++++++++++++----- .../player/action/input-command-action.ts | 2 + 3 files changed, 210 insertions(+), 64 deletions(-) create mode 100644 src/plugins/commands/path-command.ts diff --git a/src/plugins/commands/path-command.ts b/src/plugins/commands/path-command.ts new file mode 100644 index 000000000..8d3630444 --- /dev/null +++ b/src/plugins/commands/path-command.ts @@ -0,0 +1,34 @@ +import { ActionType, RunePlugin } from '@server/plugins/plugin'; +import { commandAction } from '@server/world/actor/player/action/input-command-action'; +import { Position } from '@server/world/position'; + +const action: commandAction = (details) => { + const { player, args } = details; + + const x: number = args.x as number; + const y: number = args.y as number; + const diameter: number = args.diameter as number; + + player.pathfinding.walkTo(new Position(x, y, player.position.level), diameter); +}; + +export default new RunePlugin({ + type: ActionType.COMMAND, + commands: [ 'path' ], + args: [ + { + name: 'x', + type: 'number' + }, + { + name: 'y', + type: 'number' + }, + { + name: 'diameter', + type: 'number', + defaultValue: 64 + } + ], + action +}); diff --git a/src/world/actor/pathfinding.ts b/src/world/actor/pathfinding.ts index d3c11982e..7d45c6a6a 100644 --- a/src/world/actor/pathfinding.ts +++ b/src/world/actor/pathfinding.ts @@ -3,23 +3,19 @@ import { Actor } from '@server/world/actor/actor'; import { Position } from '../position'; import { Chunk } from '@server/world/map/chunk'; import { Tile } from '@runejs/cache-parser'; +import { Player } from '@server/world/actor/player/player'; class Point { private _parent: Point = null; - private _cost: number; - private _heuristic: number; - private _depth: number; + private _cost: number = 0; - public constructor(private readonly _x: number, private readonly _y: number) { - } - - public compare(point: Point): number { - return this._cost - point._cost; + public constructor(private readonly _x: number, private readonly _y: number, + public readonly indexX: number, public readonly indexY: number) { } public equals(point: Point): boolean { - if(this._cost === point._cost && this._heuristic === point._heuristic && this._depth === point._depth) { + if(this._cost === point._cost) { if(this._parent === null && point._parent !== null) { return false; } else if(this._parent !== null && !this._parent.equals(point._parent)) { @@ -55,23 +51,6 @@ class Point { public set cost(value: number) { this._cost = value; } - - public get heuristic(): number { - return this._heuristic; - } - - public set heuristic(value: number) { - this._heuristic = value; - } - - public get depth(): number { - return this._depth; - } - - public set depth(value: number) { - this._depth = value; - } - } export class Pathfinding { @@ -84,26 +63,51 @@ export class Pathfinding { public constructor(private actor: Actor) { } - public pathTo(destinationX: number, destinationY: number, diameter: number = 32): void { + public walkTo(position: Position, pathingDiameter: number = 16): void { + const path = this.pathTo(position.x, position.y, pathingDiameter); + + if(!path) { + throw new Error(`Unable to find path.`); + } + + const walkingQueue = this.actor.walkingQueue; + + if(this.actor instanceof Player) { + this.actor.walkingTo = null; + } + + walkingQueue.clear(); + walkingQueue.valid = true; + + for(const point of path) { + walkingQueue.add(point.x, point.y); + } + } + + public pathTo(destinationX: number, destinationY: number, diameter: number = 16): Point[] { // @TODO check if destination is too far away - const currentPos = this.actor.position; const radius = Math.floor(diameter / 2); - const startX = currentPos.x; - const startY = currentPos.y; - const pathingStartX = startX - radius; - const pathingStartY = startY - radius; + const pathingStartX = this.actor.position.x - radius; + const pathingStartY = this.actor.position.y - radius; + + if(destinationX < pathingStartX || destinationY < pathingStartY) { + throw new Error(`Pathing diameter too small!`); + } + + const pointLen = diameter + 1; // + 1 for the center row & column + this.points = []; - this.points = new Array(diameter).fill(new Array(diameter)); + for(let x = 0; x < pointLen; x++) { + this.points.push([]); - for(let x = 0; x < diameter; x++) { - for(let y = 0; y < diameter; y++) { - this.points[x][y] = new Point(x + startX, y + startY); + for(let y = 0; y < pointLen; y++) { + this.points[x].push(new Point(pathingStartX + x, pathingStartY + y, x, y)); } } // Center point - this.openPoints.push(this.points[radius + 1][radius + 1]); + this.openPoints.push(this.points[radius][radius]); while(this.openPoints.length > 0) { this.currentPoint = this.calculateBestPoint(); @@ -115,18 +119,137 @@ export class Pathfinding { this.openPoints.splice(this.openPoints.indexOf(this.currentPoint), 1); this.closedPoints.push(this.currentPoint); - let x = this.currentPoint.x; - let y = this.currentPoint.y; let level = this.actor.position.level; - let currentPosition = new Position(x, y, level); + let { x, y, indexX, indexY } = this.currentPoint; + + // West + if(indexX > 0 && this.canPathNSEW(new Position(x - 1, y, level), 0x1280108)) { + this.calculateCost(this.points[indexX - 1][indexY]); + } + + // East + if(indexX < pointLen - 1 && this.canPathNSEW(new Position(x + 1, y, level), 0x1280180)) { + this.calculateCost(this.points[indexX + 1][indexY]); + } + + // South + if(indexY > 0 && this.canPathNSEW(new Position(x, y - 1, level), 0x1280102)) { + this.calculateCost(this.points[indexX][indexY - 1]); + } + + // North + if(indexY < pointLen - 1 && this.canPathNSEW(new Position(x, y + 1, level), 0x1280120)) { + this.calculateCost(this.points[indexX][indexY + 1]); + } + + // South-West + if(indexX > 0 && indexY > 0) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y - 1, level), -1, -1, + 0x128010e, 0x1280108, 0x1280102)) { + this.calculateCost(this.points[indexX - 1][indexY - 1]); + } + } + + // South-East + if(indexX < pointLen - 1 && indexY > 0) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y - 1, level), 1, -1, + 0x1280183, 0x1280180, 0x1280102)) { + this.calculateCost(this.points[indexX + 1][indexY - 1]); + } + } + + // North-West + if(indexX > 0 && indexY < pointLen - 1) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y + 1, level), -1, 1, + 0x1280138, 0x1280108, 0x1280120)) { + this.calculateCost(this.points[indexX - 1][indexY + 1]); + } + } + + // North-East + if(indexX < pointLen - 1 && indexY < pointLen - 1) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y + 1, level), 1, 1, + 0x12801e0, 0x1280180, 0x1280120)) { + this.calculateCost(this.points[indexX + 1][indexY + 1]); + } + } + } + + const destinationPoint = this.points[destinationX - pathingStartX][destinationY - pathingStartY]; + + if(destinationPoint === null || destinationPoint.parent === null) { + return null; + } + + // build path + const path: Point[] = []; + let point = destinationPoint; + + while(!point.equals(this.points[radius][radius])) { + path.push(point); + point = point.parent; + + if(point === null) { + return null; + } + } + + return path.reverse(); + } + + private calculateCost(point: Point): void { + const differenceX = this.currentPoint.x - point.x; + const differenceY = this.currentPoint.y - point.y; + const nextStepCost = this.currentPoint.cost + ((Math.abs(differenceX) + Math.abs(differenceY)) * 10); + + if(nextStepCost < point.cost) { + this.openPoints.splice(this.openPoints.indexOf(point)); + this.closedPoints.splice(this.closedPoints.indexOf(point)); + } + + if(this.openPoints.indexOf(point) === -1 && this.closedPoints.indexOf(point) === -1) { + point.parent = this.currentPoint; + point.cost = nextStepCost; + this.openPoints.push(point); + } + } + + private calculateBestPoint(): Point { + let bestPoint: Point = null; + + for(const point of this.openPoints) { + if(bestPoint === null) { + bestPoint = point; + continue; + } - let testPosition = new Position(x - 1, y, level); - if(this.canMoveTo(currentPosition, testPosition)) { - const point = this.points[x - 1][y]; + if(point.cost < bestPoint.cost) { + bestPoint = point; } } + + return bestPoint; } + private canPathNSEW(position: Position, i: number): boolean { + const chunk = world.chunkManager.getChunkForWorldPosition(position); + const destinationAdjacency: number[][] = chunk.collisionMap.adjacency; + const destinationLocalX: number = position.x - chunk.collisionMap.insetX; + const destinationLocalY: number = position.y - chunk.collisionMap.insetY; + return Pathfinding.canMoveNSEW(destinationAdjacency, destinationLocalX, destinationLocalY, i); + } + + private canPathDiagonally(originX: number, originY: number, position: Position, offsetX: number, offsetY: number, + destMask: number, cornerMask1: number, cornerMask2: number): boolean { + const chunk = world.chunkManager.getChunkForWorldPosition(position); + const destinationAdjacency: number[][] = chunk.collisionMap.adjacency; + const destinationLocalX: number = position.x - chunk.collisionMap.insetX; + const destinationLocalY: number = position.y - chunk.collisionMap.insetY; + return Pathfinding.canMoveDiagonally(position, destinationAdjacency, destinationLocalX, destinationLocalY, + originX, originY, offsetX, offsetY, destMask, cornerMask1, cornerMask2); + } + + public canMoveTo(origin: Position, destination: Position): boolean { const destinationChunk: Chunk = world.chunkManager.getChunkForWorldPosition(destination); const tile: Tile = destinationChunk.getTile(destination); @@ -143,28 +266,28 @@ export class Pathfinding { // West if(destination.x < initialX && destination.y == initialY) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280108) != 0) { + if(!Pathfinding.canMoveNSEW(destinationAdjacency, destinationLocalX, destinationLocalY, 0x1280108)) { return false; } } // East if(destination.x > initialX && destination.y == initialY) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280180) != 0) { + if(!Pathfinding.canMoveNSEW(destinationAdjacency, destinationLocalX, destinationLocalY, 0x1280180)) { return false; } } // South if(destination.y < initialY && destination.x == initialX) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280102) != 0) { + if(!Pathfinding.canMoveNSEW(destinationAdjacency, destinationLocalX, destinationLocalY, 0x1280102)) { return false; } } // North if(destination.y > initialY && destination.x == initialX) { - if((destinationAdjacency[destinationLocalX][destinationLocalY] & 0x1280120) != 0) { + if(!Pathfinding.canMoveNSEW(destinationAdjacency, destinationLocalX, destinationLocalY, 0x1280120)) { return false; } } @@ -204,6 +327,10 @@ export class Pathfinding { return true; } + public static canMoveNSEW(destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, i: number): boolean { + return (destinationAdjacency[destinationLocalX][destinationLocalY] & i) === 0; + } + public static canMoveDiagonally(origin: Position, destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, initialX: number, initialY: number, offsetX: number, offsetY: number, destMask: number, cornerMask1: number, cornerMask2: number): boolean { const cornerX1: number = initialX + offsetX; @@ -232,21 +359,4 @@ export class Pathfinding { return { localX, localY, chunk: cornerChunk }; } - private calculateBestPoint(): Point { - let bestPoint: Point = null; - - for(const point of this.openPoints) { - if(bestPoint === null) { - bestPoint = point; - continue; - } - - if(point.cost < bestPoint.cost) { - bestPoint = point; - } - } - - return bestPoint; - } - } diff --git a/src/world/actor/player/action/input-command-action.ts b/src/world/actor/player/action/input-command-action.ts index 3901cc1df..f456e2e8f 100644 --- a/src/world/actor/player/action/input-command-action.ts +++ b/src/world/actor/player/action/input-command-action.ts @@ -1,5 +1,6 @@ import { Player } from '../player'; import { ActionPlugin } from '@server/plugins/plugin'; +import { logger } from '@runejs/logger/dist/logger'; /** * The definition for a command action function. @@ -115,6 +116,7 @@ export const inputCommandAction = (player: Player, command: string, inputArgs: s } } catch(commandError) { player.outgoingPackets.chatboxMessage(`Command error: ${commandError}`); + logger.error(commandError); } }); }; From 5155b7b1a9ffbe20e4ba69e469e2dbb92afab6ca Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Mon, 30 Mar 2020 18:17:20 -0500 Subject: [PATCH 4/8] Starting on a basic implementation of player following. --- src/game-server.ts | 2 + src/net/incoming-packet-directory.ts | 4 + src/net/incoming-packet-sizes.ts | 4 +- .../player-interaction-packet.ts | 46 ++++++++++ src/net/outgoing-packets.ts | 9 ++ src/plugins/commands/path-command.ts | 4 +- src/plugins/player/follow-player-plugin.ts | 47 ++++++++++ src/plugins/plugin.ts | 21 +++-- src/world/actor/actor.ts | 3 +- src/world/actor/pathfinding.ts | 38 +++++++- src/world/actor/player/action/action.ts | 6 +- .../actor/player/action/player-action.ts | 91 +++++++++++++++++++ src/world/actor/player/player.ts | 16 ++++ src/world/actor/walking-queue.ts | 4 +- 14 files changed, 276 insertions(+), 19 deletions(-) create mode 100644 src/net/incoming-packets/player-interaction-packet.ts create mode 100644 src/plugins/player/follow-player-plugin.ts create mode 100644 src/world/actor/player/action/player-action.ts diff --git a/src/game-server.ts b/src/game-server.ts index ae88638d1..f850d16b9 100644 --- a/src/game-server.ts +++ b/src/game-server.ts @@ -25,6 +25,7 @@ import { setItemOnNpcPlugins } from '@server/world/actor/player/action/item-on-n import { setPlayerInitPlugins } from '@server/world/actor/player/player'; import { setNpcInitPlugins } from '@server/world/actor/npc/npc'; import { setQuestPlugins } from '@server/world/config/quests'; +import { setPlayerPlugins } from '@server/world/actor/player/action/player-action'; export let serverConfig: ServerConfig; @@ -59,6 +60,7 @@ export async function injectPlugins(): Promise { setWidgetPlugins(actionTypes[ActionType.WIDGET_ACTION]); setPlayerInitPlugins(actionTypes[ActionType.PLAYER_INIT]); setNpcInitPlugins(actionTypes[ActionType.NPC_INIT]); + setPlayerPlugins(actionTypes[ActionType.PLAYER_ACTION]); } function generateCrcTable(): void { diff --git a/src/net/incoming-packet-directory.ts b/src/net/incoming-packet-directory.ts index e45261004..4a69bfd8e 100644 --- a/src/net/incoming-packet-directory.ts +++ b/src/net/incoming-packet-directory.ts @@ -21,6 +21,7 @@ import { itemInteractionPacket } from '@server/net/incoming-packets/item-interac import { itemOnObjectPacket } from '@server/net/incoming-packets/item-on-object-packet'; import { numberInputPacket } from '@server/net/incoming-packets/number-input-packet'; import { itemOnNpcPacket } from '@server/net/incoming-packets/item-on-npc-packet'; +import { playerInteractionPacket } from '@server/net/incoming-packets/player-interaction-packet'; const ignore = [ 234, 160, 216, 13, 58 /* camera move */ ]; @@ -58,6 +59,9 @@ const packets: { [key: number]: incomingPacket } = { 30: objectInteractionPacket, 164: objectInteractionPacket, 183: objectInteractionPacket, + + 68: playerInteractionPacket, + 211: playerInteractionPacket, }; export function handlePacket(player: Player, packetId: number, packetSize: number, buffer: Buffer): void { diff --git a/src/net/incoming-packet-sizes.ts b/src/net/incoming-packet-sizes.ts index af137ee8d..2fed321e7 100644 --- a/src/net/incoming-packet-sizes.ts +++ b/src/net/incoming-packet-sizes.ts @@ -5,7 +5,7 @@ export const incomingPacketSizes: number[] = [ 6, -3, -3, -3, -3, -3, -3, -3, 8, -3, //30 16, -3, -3, -3, -3, -3, -3, -3, -3, -3, //40 -3, -3, -3, -3, -3, -3, -3, -3, 4, -3, //50 - -3, -3, -3, 2, 4, 6, -3, -3, -3, -3, //60 + -3, -3, -3, 2, 4, 6, -3, -3, 2, -3, //60 -3, -3, -3, -1, -3, -3, -3, -3, -3, -3, //70 -3, -3, -3, 9, -3, 6, 8, -3, -3, -1, //80 -3, -3, -3, -3, -3, -3, -3, -3, 8, -3, //90 @@ -20,7 +20,7 @@ export const incomingPacketSizes: number[] = [ -3, -3, -3, 6, -3, -3, -3, -3, -3, -3, //180 -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, //190 -3, -3, -3, -3, -3, -3, -3, -3, 10, -3, //200 - -3, -3, -3, -3, -3, -3, 0, -3, -3, -3, //210 + -3, 2, -3, -3, -3, -3, 0, -3, -3, -3, //210 -3, -3, -3, -3, -3, -3, -3, -3, 8, -3, //220 -3, 13, -3, -3, 4, -3, -1, -3, 4, -3, //230 -3, -3, -3, -3, -3, -3, -3, -3, -1, -3, //240 diff --git a/src/net/incoming-packets/player-interaction-packet.ts b/src/net/incoming-packets/player-interaction-packet.ts new file mode 100644 index 000000000..9e8ec9c39 --- /dev/null +++ b/src/net/incoming-packets/player-interaction-packet.ts @@ -0,0 +1,46 @@ +import { incomingPacket } from '../incoming-packet'; +import { Player, playerOptions } from '../../world/actor/player/player'; +import { world } from '@server/game-server'; +import { World } from '@server/world/world'; +import { ByteBuffer } from '@runejs/byte-buffer'; +import { playerAction } from '@server/world/actor/player/action/player-action'; +import { logger } from '@runejs/logger/dist/logger'; + +export const playerInteractionPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: ByteBuffer): void => { + const args = { + 68: [ 'SHORT', 'UNSIGNED', 'LITTLE_ENDIAN' ], + 211: [ 'SHORT', 'UNSIGNED', 'LITTLE_ENDIAN' ] + }; + const playerIndex = packet.get(...args[packetId]) - 1; + + 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; + } + + const actions = { + 68: 0, + 211: 1 + }; + + const playerOption = playerOptions.find(playerOption => playerOption.index === actions[packetId]); + + if(!playerOption) { + logger.error(`Invalid player option ${actions[packetId]}!`); + return; + } + + playerAction(player, otherPlayer, position, playerOption.option.toLowerCase()); +}; diff --git a/src/net/outgoing-packets.ts b/src/net/outgoing-packets.ts index 851cdd48e..75dd88981 100644 --- a/src/net/outgoing-packets.ts +++ b/src/net/outgoing-packets.ts @@ -472,6 +472,15 @@ export class OutgoingPackets { this.queue(packet); } + public updatePlayerOption(option: string, index: number = 0, placement: 'TOP' | 'BOTTOM' = 'BOTTOM'): void { + const packet = new Packet(223, PacketType.DYNAMIC_SMALL); + packet.putString(!option ? 'hidden' : option); + packet.put(placement === 'TOP' ? 1 : 0); + packet.put(index + 1); + + this.queue(packet); + } + public flushQueue(): void { if(!this.socket || this.socket.destroyed) { return; diff --git a/src/plugins/commands/path-command.ts b/src/plugins/commands/path-command.ts index 8d3630444..c641e283c 100644 --- a/src/plugins/commands/path-command.ts +++ b/src/plugins/commands/path-command.ts @@ -7,9 +7,9 @@ const action: commandAction = (details) => { const x: number = args.x as number; const y: number = args.y as number; - const diameter: number = args.diameter as number; + const pathingDiameter: number = args.diameter as number; - player.pathfinding.walkTo(new Position(x, y, player.position.level), diameter); + player.pathfinding.walkTo(new Position(x, y, player.position.level), { pathingDiameter }); }; export default new RunePlugin({ diff --git a/src/plugins/player/follow-player-plugin.ts b/src/plugins/player/follow-player-plugin.ts new file mode 100644 index 000000000..3b225b653 --- /dev/null +++ b/src/plugins/player/follow-player-plugin.ts @@ -0,0 +1,47 @@ +import { ActionType, RunePlugin } from '@server/plugins/plugin'; +import { playerAction } from '@server/world/actor/player/action/player-action'; +import { loopingAction } from '@server/world/actor/player/action/action'; +import { Position } from '@server/world/position'; + +export const action: playerAction = (details) => { + const { player, otherPlayer } = details; + + player.face(otherPlayer, false, false, false); + + let followingPosition: Position = new Position(otherPlayer.position.x, otherPlayer.position.y, otherPlayer.position.level); + + const loop = loopingAction(); + + loop.event.subscribe(async () => { + if(player.metadata.pathfinding) { + return; + } + + const distance = Math.floor(otherPlayer.position.distanceBetween(player.position)); + if(distance > 16) { + loop.cancel(); + player.clearFaceActor(); + player.metadata.faceActorClearedByWalking = true; + return; + } + + console.log(distance); + + if(distance > 1 && !followingPosition.equals(otherPlayer.position)) { + followingPosition = new Position(otherPlayer.position.x, otherPlayer.position.y, otherPlayer.position.level); + + try { + player.metadata.pathfinding = true; + await player.pathfinding.walkTo(otherPlayer.position, { pathingDiameter: distance + 5, ignoreDestination: true }); + player.metadata.pathfinding = false; + } catch(error) { + } + } + }); +}; + +export default new RunePlugin({ + type: ActionType.PLAYER_ACTION, + options: 'follow', + action +}); diff --git a/src/plugins/plugin.ts b/src/plugins/plugin.ts index b3f681d26..c312fc9b7 100644 --- a/src/plugins/plugin.ts +++ b/src/plugins/plugin.ts @@ -11,6 +11,7 @@ import { ButtonActionPlugin } from '@server/world/actor/player/action/button-act import { WorldItemActionPlugin } from '@server/world/actor/player/action/world-item-action'; import { ItemActionPlugin } from '@server/world/actor/player/action/item-action'; import { QuestPlugin } from '@server/world/config/quests'; +import { PlayerActionPlugin } from '@server/world/actor/player/action/player-action'; export enum ActionType { BUTTON = 'button', @@ -25,7 +26,8 @@ export enum ActionType { COMMAND = 'command', PLAYER_INIT = 'player_init', NPC_INIT = 'npc_init', - QUEST = 'quest' + QUEST = 'quest', + PLAYER_ACTION = 'player_action', } export interface QuestAction { @@ -61,13 +63,18 @@ export function questFilter(player: Player, plugin: ActionPlugin): boolean { export class RunePlugin { - public actions: (NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | ItemOnObjectActionPlugin | ItemOnNpcActionPlugin | - CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | PlayerInitPlugin | NpcInitPlugin | QuestPlugin)[]; + public actions: (NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | + ItemOnObjectActionPlugin | CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | + PlayerInitPlugin | NpcInitPlugin | QuestPlugin | PlayerActionPlugin | ItemOnNpcActionPlugin)[]; + + public constructor(actions: NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | + ItemOnObjectActionPlugin | CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | + PlayerInitPlugin | NpcInitPlugin | QuestPlugin | PlayerActionPlugin | ItemOnNpcActionPlugin | + + (NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | ItemOnObjectActionPlugin | + CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | PlayerInitPlugin | + NpcInitPlugin | QuestPlugin | PlayerActionPlugin | ItemOnNpcActionPlugin)[], quest?: QuestAction) { - public constructor(actions: NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | ItemOnObjectActionPlugin | - CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | PlayerInitPlugin | NpcInitPlugin | QuestPlugin | ItemOnNpcActionPlugin | - (NpcActionPlugin | ObjectActionPlugin | ButtonActionPlugin | ItemOnItemActionPlugin | ItemOnObjectActionPlugin | ItemOnNpcActionPlugin | - CommandActionPlugin | WidgetActionPlugin | ItemActionPlugin | WorldItemActionPlugin | PlayerInitPlugin | NpcInitPlugin | QuestPlugin)[], quest?: QuestAction) { if(!Array.isArray(actions)) { if(quest !== undefined && !actions.questAction) { actions.questAction = quest; diff --git a/src/world/actor/actor.ts b/src/world/actor/actor.ts index e0f16fc84..364959ac2 100644 --- a/src/world/actor/actor.ts +++ b/src/world/actor/actor.ts @@ -42,12 +42,13 @@ export abstract class Actor extends Entity { this.pathfinding = new Pathfinding(this); } - public face(face: Position | Actor, clearWalkingQueue: boolean = true, autoClear: boolean = true): void { + public face(face: Position | Actor, clearWalkingQueue: boolean = true, autoClear: boolean = true, clearedByWalking: boolean = true): void { if(face instanceof Position) { this.updateFlags.facePosition = face; } else if(face instanceof Actor) { this.updateFlags.faceActor = face; this.metadata['faceActor'] = face; + this.metadata['faceActorClearedByWalking'] = clearedByWalking; if(autoClear) { setTimeout(() => { diff --git a/src/world/actor/pathfinding.ts b/src/world/actor/pathfinding.ts index 7d45c6a6a..ea8f8652a 100644 --- a/src/world/actor/pathfinding.ts +++ b/src/world/actor/pathfinding.ts @@ -53,6 +53,11 @@ class Point { } } +export interface PathingOptions { + pathingDiameter?: number; + ignoreDestination?: boolean; +} + export class Pathfinding { private currentPoint: Point; @@ -63,8 +68,12 @@ export class Pathfinding { public constructor(private actor: Actor) { } - public walkTo(position: Position, pathingDiameter: number = 16): void { - const path = this.pathTo(position.x, position.y, pathingDiameter); + public async walkTo(position: Position, options: PathingOptions): Promise { + if(!options.pathingDiameter) { + options.pathingDiameter = 16; + } + + const path = await this.pathTo(position.x, position.y, options.pathingDiameter); if(!path) { throw new Error(`Unable to find path.`); @@ -79,17 +88,23 @@ export class Pathfinding { walkingQueue.clear(); walkingQueue.valid = true; + if(options.ignoreDestination) { + path.splice(path.length - 1, 1); + } + for(const point of path) { walkingQueue.add(point.x, point.y); } } - public pathTo(destinationX: number, destinationY: number, diameter: number = 16): Point[] { + public async pathTo(destinationX: number, destinationY: number, diameter: number = 16): Promise { // @TODO check if destination is too far away const radius = Math.floor(diameter / 2); const pathingStartX = this.actor.position.x - radius; const pathingStartY = this.actor.position.y - radius; + const destinationIndexX = destinationX - pathingStartX; + const destinationIndexY = destinationY - pathingStartY; if(destinationX < pathingStartX || destinationY < pathingStartY) { throw new Error(`Pathing diameter too small!`); @@ -112,7 +127,7 @@ export class Pathfinding { while(this.openPoints.length > 0) { this.currentPoint = this.calculateBestPoint(); - if(this.currentPoint === this.points[destinationX - pathingStartX][destinationY - pathingStartY]) { + if(this.currentPoint === this.points[destinationIndexX][destinationIndexY]) { break; } @@ -121,25 +136,30 @@ export class Pathfinding { let level = this.actor.position.level; let { x, y, indexX, indexY } = this.currentPoint; + let canPath = false; // West if(indexX > 0 && this.canPathNSEW(new Position(x - 1, y, level), 0x1280108)) { this.calculateCost(this.points[indexX - 1][indexY]); + canPath = true; } // East if(indexX < pointLen - 1 && this.canPathNSEW(new Position(x + 1, y, level), 0x1280180)) { this.calculateCost(this.points[indexX + 1][indexY]); + canPath = true; } // South if(indexY > 0 && this.canPathNSEW(new Position(x, y - 1, level), 0x1280102)) { this.calculateCost(this.points[indexX][indexY - 1]); + canPath = true; } // North if(indexY < pointLen - 1 && this.canPathNSEW(new Position(x, y + 1, level), 0x1280120)) { this.calculateCost(this.points[indexX][indexY + 1]); + canPath = true; } // South-West @@ -147,6 +167,7 @@ export class Pathfinding { if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y - 1, level), -1, -1, 0x128010e, 0x1280108, 0x1280102)) { this.calculateCost(this.points[indexX - 1][indexY - 1]); + canPath = true; } } @@ -155,6 +176,7 @@ export class Pathfinding { if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y - 1, level), 1, -1, 0x1280183, 0x1280180, 0x1280102)) { this.calculateCost(this.points[indexX + 1][indexY - 1]); + canPath = true; } } @@ -163,6 +185,7 @@ export class Pathfinding { if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y + 1, level), -1, 1, 0x1280138, 0x1280108, 0x1280120)) { this.calculateCost(this.points[indexX - 1][indexY + 1]); + canPath = true; } } @@ -171,11 +194,16 @@ export class Pathfinding { if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y + 1, level), 1, 1, 0x12801e0, 0x1280180, 0x1280120)) { this.calculateCost(this.points[indexX + 1][indexY + 1]); + canPath = true; } } + + if(!canPath) { + break; + } } - const destinationPoint = this.points[destinationX - pathingStartX][destinationY - pathingStartY]; + const destinationPoint = this.points[destinationIndexX][destinationIndexY]; if(destinationPoint === null || destinationPoint.parent === null) { return null; diff --git a/src/world/actor/player/action/action.ts b/src/world/actor/player/action/action.ts index 6e0ffdf20..9e887f28d 100644 --- a/src/world/actor/player/action/action.ts +++ b/src/world/actor/player/action/action.ts @@ -25,7 +25,11 @@ export interface InteractingAction { * `npc` the npc that the loop belongs to. This will Providing this field will cause the loop to cancel if * this npc is flagged to no longer exist during the loop. */ -export const loopingAction = (options: { ticks?: number, delayTicks?: number, npc?: Npc, player?: Player }) => { +export const loopingAction = (options?: { ticks?: number, delayTicks?: number, npc?: Npc, player?: Player }) => { + if(!options) { + options = {}; + } + const { ticks, delayTicks, npc, player } = options; const event: Subject = new Subject(); diff --git a/src/world/actor/player/action/player-action.ts b/src/world/actor/player/action/player-action.ts new file mode 100644 index 000000000..3731ab375 --- /dev/null +++ b/src/world/actor/player/action/player-action.ts @@ -0,0 +1,91 @@ +import { Player } from '@server/world/actor/player/player'; +import { Position } from '@server/world/position'; +import { walkToAction } from '@server/world/actor/player/action/action'; +import { basicStringFilter } from '@server/plugins/plugin-loader'; +import { logger } from '@runejs/logger/dist/logger'; +import { ActionPlugin, questFilter } from '@server/plugins/plugin'; + +/** + * The definition for a player action function. + */ +export type playerAction = (details: PlayerActionDetails) => void; + +/** + * Details about a player being interacted with. + */ +export interface PlayerActionDetails { + // The player performing the action. + player: Player; + // The player that the action is being performed on. + otherPlayer: Player; + // The position that the other player was at when the action was initiated. + position: Position; +} + +/** + * Defines a player interaction plugin. + * The option selected, the action to be performed, and whether or not the player must first walk to the other player. + */ +export interface PlayerActionPlugin extends ActionPlugin { + // A single option name or a list of option names that this action applies to. + options: string | string[]; + // Whether or not the player needs to walk to the other player before performing the action. + walkTo: boolean; + // The action function to be performed. + action: playerAction; +} + +/** + * A directory of all player interaction plugins. + */ +let playerInteractions: PlayerActionPlugin[] = [ +]; + +/** + * Sets the list of player interaction plugins. + * @param plugins The plugin list. + */ +export const setPlayerPlugins = (plugins: ActionPlugin[]): void => { + playerInteractions = plugins as PlayerActionPlugin[]; +}; + +// @TODO priority and cancelling other (lower priority) actions +export const playerAction = (player: Player, otherPlayer: Player, position: Position, option: string): void => { + if(player.busy) { + return; + } + + // Find all player action plugins that reference this option + let interactionActions = playerInteractions.filter(plugin => questFilter(player, plugin) && basicStringFilter(plugin.options, option)); + const questActions = interactionActions.filter(plugin => plugin.questAction !== undefined); + + if(questActions.length !== 0) { + interactionActions = questActions; + } + + if(interactionActions.length === 0) { + player.sendMessage(`Unhandled Player interaction: ${option} @ ${position.x},${position.y},${position.level}`); + return; + } + + player.actionsCancelled.next(); + + // Separate out walk-to actions from immediate actions + const walkToPlugins = interactionActions.filter(plugin => plugin.walkTo); + const immediatePlugins = interactionActions.filter(plugin => !plugin.walkTo); + + // Make sure we walk to the other player before running any of the walk-to plugins + if(walkToPlugins.length !== 0) { + walkToAction(player, position) + .then(() => { + player.face(otherPlayer); + walkToPlugins.forEach(plugin => plugin.action({ player, otherPlayer, position })); + }) + .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, otherPlayer, position })); + } +}; diff --git a/src/world/actor/player/player.ts b/src/world/actor/player/player.ts index ae1f03c7c..57f5d56e4 100644 --- a/src/world/actor/player/player.ts +++ b/src/world/actor/player/player.ts @@ -31,6 +31,19 @@ import { colors, hexToRgb, rgbTo16Bit } from '@server/util/colors'; import { quests } from '@server/world/config/quests'; import { ItemDefinition } from '@runejs/cache-parser'; +export const playerOptions: { option: string, index: number, placement: 'TOP' | 'BOTTOM' }[] = [ + { + option: 'Yeet', + index: 1, + placement: 'TOP' + }, + { + option: 'Follow', + index: 0, + placement: 'BOTTOM' + } +]; + const DEFAULT_TAB_WIDGET_IDS = [ 92, widgets.skillsTab, 274, widgets.inventory.widgetId, widgets.equipment.widgetId, 271, 192, -1, 131, 148, widgets.logoutTab, widgets.settingsTab, widgets.emotesTab, 239 @@ -234,6 +247,9 @@ export class Player extends Actor { }; } + for(const playerOption of playerOptions) { + this.outgoingPackets.updatePlayerOption(playerOption.option, playerOption.index, playerOption.placement); + } this.updateBonuses(); this.updateCarryWeight(true); this.modifyWidget(widgets.musicPlayerTab, { childId: 82, textColor: colors.green }); // Set "Harmony" to green/unlocked on the music tab diff --git a/src/world/actor/walking-queue.ts b/src/world/actor/walking-queue.ts index fd6e92d1a..7e11e26e5 100644 --- a/src/world/actor/walking-queue.ts +++ b/src/world/actor/walking-queue.ts @@ -152,7 +152,9 @@ export class WalkingQueue { } } - this.actor.clearFaceActor(); + if(this.actor.metadata['faceActorClearedByWalking'] === undefined || this.actor.metadata['faceActorClearedByWalking']) { + this.actor.clearFaceActor(); + } const currentPosition = this.actor.position; From ff6835df1fcfb24336685e16eb1ebe0f8b60cc0e Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Sat, 6 Jun 2020 20:41:39 -0500 Subject: [PATCH 5/8] Adding player action to rune.js --- plugins/rune.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/rune.js b/plugins/rune.js index a62e84733..05feaae6a 100644 --- a/plugins/rune.js +++ b/plugins/rune.js @@ -26,6 +26,7 @@ module.exports = { WORLD_ITEM_ACTION: ActionType.WORLD_ITEM_ACTION, OBJECT_ACTION: ActionType.OBJECT_ACTION, ITEM_ON_OBJECT_ACTION: ActionType.ITEM_ON_OBJECT_ACTION, ITEM_ON_NPC_ACTION: ActionType.ITEM_ON_NPC_ACTION, COMMAND_ACTION: ActionType.COMMAND, PLAYER_INIT: ActionType.PLAYER_INIT, NPC_INIT: ActionType.NPC_INIT, + PLAYER_ACTION: ActionType.PLAYER_ACTION, QUEST: ActionType.QUEST, RunePlugin, world, InteractingAction, loopingAction, walkToAction, From 25d9f385cb16d1ecebf8b2c1f63db64c2462d000 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Sat, 6 Jun 2020 21:28:14 -0500 Subject: [PATCH 6/8] Making pathingfinding and player following a lot more stable --- src/data-dump.ts | 2 +- src/error-handling.ts | 2 +- src/net/data-parser/client-login-parser.ts | 2 +- .../incoming-packets/item-on-item-packet.ts | 2 +- .../incoming-packets/item-on-npc-packet.ts | 2 +- .../incoming-packets/item-on-object-packet.ts | 2 +- .../npc-interaction-packet.ts | 2 +- .../object-interaction-packet.ts | 2 +- .../player-interaction-packet.ts | 2 +- .../objects/doors/double-door-plugin.ts | 2 +- src/plugins/objects/doors/gate-plugin.ts | 2 +- src/plugins/player/follow-player-plugin.ts | 51 +++----- src/plugins/skills/skill-guide-plugin.ts | 2 +- src/world/actor/dialogue.ts | 2 +- src/world/actor/pathfinding.ts | 118 +++++++++++------- .../player/action/input-command-action.ts | 2 +- .../actor/player/action/item-on-npc-action.ts | 2 +- .../player/action/item-on-object-action.ts | 2 +- src/world/actor/player/action/npc-action.ts | 2 +- .../actor/player/action/object-action.ts | 2 +- .../actor/player/action/player-action.ts | 2 +- src/world/actor/player/action/shop-action.ts | 2 +- .../actor/player/action/world-item-action.ts | 2 +- src/world/config/examine-data.ts | 2 +- src/world/config/item-data.ts | 2 +- src/world/config/npc-spawn.ts | 2 +- src/world/config/scenery-spawns.ts | 2 +- src/world/config/server-config.ts | 2 +- src/world/config/shops.ts | 2 +- 29 files changed, 116 insertions(+), 107 deletions(-) diff --git a/src/data-dump.ts b/src/data-dump.ts index 6ac2e258a..e2f60a027 100644 --- a/src/data-dump.ts +++ b/src/data-dump.ts @@ -1,7 +1,7 @@ import { join } from 'path'; import { writeFileSync } from 'fs'; import { cache } from '@server/game-server'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ItemDefinition, NpcDefinition, Widget } from '@runejs/cache-parser'; function dump(fileName: string, definitions: Map): boolean { diff --git a/src/error-handling.ts b/src/error-handling.ts index 7a7ccd045..badde9191 100644 --- a/src/error-handling.ts +++ b/src/error-handling.ts @@ -1,4 +1,4 @@ -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; /* * Error handling! Feel free to add other types of errors or warnings here. :) diff --git a/src/net/data-parser/client-login-parser.ts b/src/net/data-parser/client-login-parser.ts index 667d7527f..318b5787a 100644 --- a/src/net/data-parser/client-login-parser.ts +++ b/src/net/data-parser/client-login-parser.ts @@ -3,7 +3,7 @@ import { Player } from '@server/world/actor/player/player'; import { Isaac } from '@server/net/isaac'; import { serverConfig, world } from '@server/game-server'; import { DataParser } from './data-parser'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ByteBuffer } from '@runejs/byte-buffer'; import * as bcrypt from 'bcrypt'; import { loadPlayerSave } from '@server/world/actor/player/player-data'; diff --git a/src/net/incoming-packets/item-on-item-packet.ts b/src/net/incoming-packets/item-on-item-packet.ts index 7784b4307..4cdbf9e9b 100644 --- a/src/net/incoming-packets/item-on-item-packet.ts +++ b/src/net/incoming-packets/item-on-item-packet.ts @@ -1,7 +1,7 @@ import { incomingPacket } from '../incoming-packet'; import { Player } from '../../world/actor/player/player'; import { widgets } from '@server/world/config/widget'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { itemOnItemAction } from '@server/world/actor/player/action/item-on-item-action'; import { ByteBuffer } from '@runejs/byte-buffer'; diff --git a/src/net/incoming-packets/item-on-npc-packet.ts b/src/net/incoming-packets/item-on-npc-packet.ts index 9b4954f1d..26c596927 100644 --- a/src/net/incoming-packets/item-on-npc-packet.ts +++ b/src/net/incoming-packets/item-on-npc-packet.ts @@ -1,7 +1,7 @@ import { incomingPacket } from '../incoming-packet'; import { Player } from '../../world/actor/player/player'; import { widgets } from '@server/world/config/widget'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { world } from '@server/game-server'; import { World } from '@server/world/world'; import { itemOnNpcAction } from '@server/world/actor/player/action/item-on-npc-action'; diff --git a/src/net/incoming-packets/item-on-object-packet.ts b/src/net/incoming-packets/item-on-object-packet.ts index 605bacd7a..946dacaf3 100644 --- a/src/net/incoming-packets/item-on-object-packet.ts +++ b/src/net/incoming-packets/item-on-object-packet.ts @@ -1,7 +1,7 @@ import { incomingPacket } from '../incoming-packet'; import { Player } from '../../world/actor/player/player'; import { widgets } from '@server/world/config/widget'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { Position } from '@server/world/position'; import { cache, world } from '@server/game-server'; import { itemOnObjectAction } from '@server/world/actor/player/action/item-on-object-action'; diff --git a/src/net/incoming-packets/npc-interaction-packet.ts b/src/net/incoming-packets/npc-interaction-packet.ts index 8101ad535..dd983805b 100644 --- a/src/net/incoming-packets/npc-interaction-packet.ts +++ b/src/net/incoming-packets/npc-interaction-packet.ts @@ -3,7 +3,7 @@ import { Player } from '../../world/actor/player/player'; import { world } from '@server/game-server'; import { World } from '@server/world/world'; import { npcAction } from '@server/world/actor/player/action/npc-action'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ByteBuffer } from '@runejs/byte-buffer'; export const npcInteractionPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: ByteBuffer): void => { diff --git a/src/net/incoming-packets/object-interaction-packet.ts b/src/net/incoming-packets/object-interaction-packet.ts index a1fa0e2fe..f633b2400 100644 --- a/src/net/incoming-packets/object-interaction-packet.ts +++ b/src/net/incoming-packets/object-interaction-packet.ts @@ -3,7 +3,7 @@ import { Player } from '../../world/actor/player/player'; import { Position } from '@server/world/position'; import { cache, world } from '@server/game-server'; import { objectAction } from '@server/world/actor/player/action/object-action'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ByteBuffer } from '@runejs/byte-buffer'; interface ObjectInteraction { diff --git a/src/net/incoming-packets/player-interaction-packet.ts b/src/net/incoming-packets/player-interaction-packet.ts index 9e8ec9c39..c679af601 100644 --- a/src/net/incoming-packets/player-interaction-packet.ts +++ b/src/net/incoming-packets/player-interaction-packet.ts @@ -4,7 +4,7 @@ import { world } from '@server/game-server'; import { World } from '@server/world/world'; import { ByteBuffer } from '@runejs/byte-buffer'; import { playerAction } from '@server/world/actor/player/action/player-action'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; export const playerInteractionPacket: incomingPacket = (player: Player, packetId: number, packetSize: number, packet: ByteBuffer): void => { const args = { diff --git a/src/plugins/objects/doors/double-door-plugin.ts b/src/plugins/objects/doors/double-door-plugin.ts index 39b42c10b..60bd7661d 100644 --- a/src/plugins/objects/doors/double-door-plugin.ts +++ b/src/plugins/objects/doors/double-door-plugin.ts @@ -1,6 +1,6 @@ import { Position } from '@server/world/position'; import { WNES } from '@server/world/direction'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { world } from '@server/game-server'; import { action as doorAction } from '@server/plugins/objects/doors/door-plugin'; import { objectAction } from '@server/world/actor/player/action/object-action'; diff --git a/src/plugins/objects/doors/gate-plugin.ts b/src/plugins/objects/doors/gate-plugin.ts index 8a308a726..e3ee744d7 100644 --- a/src/plugins/objects/doors/gate-plugin.ts +++ b/src/plugins/objects/doors/gate-plugin.ts @@ -1,6 +1,6 @@ import { Position } from '@server/world/position'; import { directionData, WNES } from '@server/world/direction'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { world } from '@server/game-server'; import { ModifiedLocationObject } from '@server/world/map/location-object'; import { objectAction } from '@server/world/actor/player/action/object-action'; diff --git a/src/plugins/player/follow-player-plugin.ts b/src/plugins/player/follow-player-plugin.ts index 5376566c9..0872e6a87 100644 --- a/src/plugins/player/follow-player-plugin.ts +++ b/src/plugins/player/follow-player-plugin.ts @@ -1,32 +1,41 @@ import { ActionType, RunePlugin } from '@server/plugins/plugin'; import { playerAction } from '@server/world/actor/player/action/player-action'; import { Player } from '@server/world/actor/player/player'; +import { logger } from '@runejs/logger'; async function pathTo(player: Player, otherPlayer: Player): Promise { const distance = Math.floor(otherPlayer.position.distanceBetween(player.position)); if(distance > 16) { - console.log('too big ', distance); player.clearFaceActor(); player.metadata.faceActorClearedByWalking = true; - throw `Distance too great!`; + throw new Error(`Distance too great!`); } if(distance <= 1) { return Promise.resolve(false); } - await player.pathfinding.walkTo(otherPlayer.position, { pathingDiameter: distance + 6, ignoreDestination: true })/*.catch(() => {})*/; - return Promise.resolve(true); + try { + await player.pathfinding.walkTo(otherPlayer.position, { + pathingDiameter: distance + 6, + ignoreDestination: true + }); + + return Promise.resolve(true); + } catch(error) { + player.clearFaceActor(); + logger.warn(error.message); + } } export const action: playerAction = (details) => { const { player, otherPlayer } = details; player.face(otherPlayer, false, false, false); - pathTo(player, otherPlayer); const subscription = otherPlayer.movementEvent.subscribe(() => { + player.face(otherPlayer, false, false, false); pathTo(player, otherPlayer); }); const actionCancelled = player.actionsCancelled.subscribe(type => { @@ -36,38 +45,6 @@ export const action: playerAction = (details) => { player.face(null); } }); - - /*const loop = loopingAction({ ticks: 2 }); - - loop.event.subscribe(async () => { - const distance = Math.floor(otherPlayer.position.distanceBetween(player.position)); - if(distance > 16) { - loop.cancel(); - player.clearFaceActor(); - player.metadata.faceActorClearedByWalking = true; - return; - } - - if(player.metadata.pathfindingPosition) { - if(otherPlayer.position.equals(player.metadata.pathfindingPosition)) { - return; - } else if(distance === 1) { - player.metadata.pathfindingPosition = null; - } - } - - console.log(distance); - - if(distance > 1 && !followingPosition.equals(otherPlayer.position)) { - followingPosition = new Position(otherPlayer.position.x, otherPlayer.position.y, otherPlayer.position.level); - - try { - player.metadata.pathfindingPosition = otherPlayer.position; - await player.pathfinding.walkTo(otherPlayer.position, { pathingDiameter: distance + 5, ignoreDestination: true }); - } catch(error) { - } - } - });*/ }; export default new RunePlugin({ diff --git a/src/plugins/skills/skill-guide-plugin.ts b/src/plugins/skills/skill-guide-plugin.ts index 5fec59cd6..18cc4d621 100644 --- a/src/plugins/skills/skill-guide-plugin.ts +++ b/src/plugins/skills/skill-guide-plugin.ts @@ -1,5 +1,5 @@ import { buttonAction } from '@server/world/actor/player/action/button-action'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; import { ActionType, RunePlugin } from '@server/plugins/plugin'; diff --git a/src/world/actor/dialogue.ts b/src/world/actor/dialogue.ts index 74a1b103d..7f6545e8f 100644 --- a/src/world/actor/dialogue.ts +++ b/src/world/actor/dialogue.ts @@ -2,7 +2,7 @@ import { Npc } from '@server/world/actor/npc/npc'; import { Player } from '@server/world/actor/player/player'; import { Subscription } from 'rxjs'; import { cache } from '@server/game-server'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import _ from 'lodash'; import { wrapText } from '@server/util/strings'; import { ActionsCancelledWarning, WidgetsClosedWarning } from '@server/error-handling'; diff --git a/src/world/actor/pathfinding.ts b/src/world/actor/pathfinding.ts index 94cf932b2..ab1e8144a 100644 --- a/src/world/actor/pathfinding.ts +++ b/src/world/actor/pathfinding.ts @@ -77,8 +77,7 @@ export class Pathfinding { const path = await this.pathTo(position.x, position.y, options.pathingDiameter); if(!path) { - logger.warn(`Unable to find path.`); - return; + throw new Error(`Unable to find path.`); } const walkingQueue = this.actor.walkingQueue; @@ -94,6 +93,7 @@ export class Pathfinding { path.splice(path.length - 1, 1); } + path.shift(); for(const point of path) { walkingQueue.add(point.x, point.y); } @@ -145,64 +145,96 @@ export class Pathfinding { let { x, y, indexX, indexY } = this.currentPoint; let canPath = false; - // West - if(indexX > 0 && this.canPathNSEW(new Position(x - 1, y, level), 0x1280108)) { - this.calculateCost(this.points[indexX - 1][indexY]); - canPath = true; + try { + // West + if(indexX > 0 && this.canPathNSEW(new Position(x - 1, y, level), 0x1280108)) { + this.calculateCost(this.points[indexX - 1][indexY]); + canPath = true; + } + } catch(e) { + logger.warn(`Error calculating path.`); } - // East - if(indexX < pointLen - 1 && this.canPathNSEW(new Position(x + 1, y, level), 0x1280180)) { - this.calculateCost(this.points[indexX + 1][indexY]); - canPath = true; + try { + // East + if(indexX < pointLen - 1 && this.canPathNSEW(new Position(x + 1, y, level), 0x1280180)) { + this.calculateCost(this.points[indexX + 1][indexY]); + canPath = true; + } + } catch(e) { + logger.warn(`Error calculating path.`); } - // South - if(indexY > 0 && this.canPathNSEW(new Position(x, y - 1, level), 0x1280102)) { - this.calculateCost(this.points[indexX][indexY - 1]); - canPath = true; + try { + // South + if(indexY > 0 && this.canPathNSEW(new Position(x, y - 1, level), 0x1280102)) { + this.calculateCost(this.points[indexX][indexY - 1]); + canPath = true; + } + } catch(e) { + logger.warn(`Error calculating path.`); } - // North - if(indexY < pointLen - 1 && this.canPathNSEW(new Position(x, y + 1, level), 0x1280120)) { - this.calculateCost(this.points[indexX][indexY + 1]); - canPath = true; + try { + // North + if(indexY < pointLen - 1 && this.canPathNSEW(new Position(x, y + 1, level), 0x1280120)) { + this.calculateCost(this.points[indexX][indexY + 1]); + canPath = true; + } + } catch(e) { + logger.warn(`Error calculating path.`); } - // South-West - if(indexX > 0 && indexY > 0) { - if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y - 1, level), -1, -1, - 0x128010e, 0x1280108, 0x1280102)) { - this.calculateCost(this.points[indexX - 1][indexY - 1]); - canPath = true; + try { + // South-West + if(indexX > 0 && indexY > 0) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y - 1, level), -1, -1, + 0x128010e, 0x1280108, 0x1280102)) { + this.calculateCost(this.points[indexX - 1][indexY - 1]); + canPath = true; + } } + } catch(e) { + logger.warn(`Error calculating path.`); } - // South-East - if(indexX < pointLen - 1 && indexY > 0) { - if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y - 1, level), 1, -1, - 0x1280183, 0x1280180, 0x1280102)) { - this.calculateCost(this.points[indexX + 1][indexY - 1]); - canPath = true; + try { + // South-East + if(indexX < pointLen - 1 && indexY > 0) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y - 1, level), 1, -1, + 0x1280183, 0x1280180, 0x1280102)) { + this.calculateCost(this.points[indexX + 1][indexY - 1]); + canPath = true; + } } + } catch(e) { + logger.warn(`Error calculating path.`); } - // North-West - if(indexX > 0 && indexY < pointLen - 1) { - if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y + 1, level), -1, 1, - 0x1280138, 0x1280108, 0x1280120)) { - this.calculateCost(this.points[indexX - 1][indexY + 1]); - canPath = true; + try { + // North-West + if(indexX > 0 && indexY < pointLen - 1) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x - 1, y + 1, level), -1, 1, + 0x1280138, 0x1280108, 0x1280120)) { + this.calculateCost(this.points[indexX - 1][indexY + 1]); + canPath = true; + } } + } catch(e) { + logger.warn(`Error calculating path.`); } - // North-East - if(indexX < pointLen - 1 && indexY < pointLen - 1) { - if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y + 1, level), 1, 1, - 0x12801e0, 0x1280180, 0x1280120)) { - this.calculateCost(this.points[indexX + 1][indexY + 1]); - canPath = true; + try { + // North-East + if(indexX < pointLen - 1 && indexY < pointLen - 1) { + if(this.canPathDiagonally(this.currentPoint.x, this.currentPoint.y, new Position(x + 1, y + 1, level), 1, 1, + 0x12801e0, 0x1280180, 0x1280120)) { + this.calculateCost(this.points[indexX + 1][indexY + 1]); + canPath = true; + } } + } catch(e) { + logger.warn(`Error calculating path.`); } if(!canPath) { @@ -227,7 +259,7 @@ export class Pathfinding { iterations++; if(iterations > 10000) { - throw `AAAAHHHHHH!`; + throw new Error(`Path iteration overflow, path will not be used.`); } if(point === null) { diff --git a/src/world/actor/player/action/input-command-action.ts b/src/world/actor/player/action/input-command-action.ts index 4a634fb7c..3232ced39 100644 --- a/src/world/actor/player/action/input-command-action.ts +++ b/src/world/actor/player/action/input-command-action.ts @@ -1,6 +1,6 @@ import { Player } from '../player'; import { ActionPlugin } from '@server/plugins/plugin'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; /** * The definition for a command action function. diff --git a/src/world/actor/player/action/item-on-npc-action.ts b/src/world/actor/player/action/item-on-npc-action.ts index b470ca5a1..09d01ba2e 100644 --- a/src/world/actor/player/action/item-on-npc-action.ts +++ b/src/world/actor/player/action/item-on-npc-action.ts @@ -2,7 +2,7 @@ import { Player } from '@server/world/actor/player/player'; import { Position } from '@server/world/position'; import { walkToAction } from '@server/world/actor/player/action/action'; import { pluginFilter } from '@server/plugins/plugin-loader'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ActionPlugin, questFilter } from '@server/plugins/plugin'; import { Item } from '@server/world/items/item'; import { Npc } from '@server/world/actor/npc/npc'; diff --git a/src/world/actor/player/action/item-on-object-action.ts b/src/world/actor/player/action/item-on-object-action.ts index d5d05f24a..3ee367917 100644 --- a/src/world/actor/player/action/item-on-object-action.ts +++ b/src/world/actor/player/action/item-on-object-action.ts @@ -3,7 +3,7 @@ import { LocationObject, LocationObjectDefinition } from '@runejs/cache-parser'; import { Position } from '@server/world/position'; import { walkToAction } from '@server/world/actor/player/action/action'; import { pluginFilter } from '@server/plugins/plugin-loader'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ActionPlugin, questFilter } from '@server/plugins/plugin'; import { Item } from '@server/world/items/item'; diff --git a/src/world/actor/player/action/npc-action.ts b/src/world/actor/player/action/npc-action.ts index 90aa624ed..154ed1291 100644 --- a/src/world/actor/player/action/npc-action.ts +++ b/src/world/actor/player/action/npc-action.ts @@ -3,7 +3,7 @@ import { Npc } from '@server/world/actor/npc/npc'; import { Position } from '@server/world/position'; import { walkToAction } from '@server/world/actor/player/action/action'; import { pluginFilter } from '@server/plugins/plugin-loader'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ActionPlugin, questFilter } from '@server/plugins/plugin'; /** diff --git a/src/world/actor/player/action/object-action.ts b/src/world/actor/player/action/object-action.ts index f30ddba67..b18a8f63a 100644 --- a/src/world/actor/player/action/object-action.ts +++ b/src/world/actor/player/action/object-action.ts @@ -3,7 +3,7 @@ import { LocationObject, LocationObjectDefinition } from '@runejs/cache-parser'; import { Position } from '@server/world/position'; import { walkToAction } from '@server/world/actor/player/action/action'; import { pluginFilter } from '@server/plugins/plugin-loader'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ActionPlugin, questFilter } from '@server/plugins/plugin'; /** diff --git a/src/world/actor/player/action/player-action.ts b/src/world/actor/player/action/player-action.ts index 3731ab375..0388bed7e 100644 --- a/src/world/actor/player/action/player-action.ts +++ b/src/world/actor/player/action/player-action.ts @@ -2,7 +2,7 @@ import { Player } from '@server/world/actor/player/player'; import { Position } from '@server/world/position'; import { walkToAction } from '@server/world/actor/player/action/action'; import { basicStringFilter } from '@server/plugins/plugin-loader'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { ActionPlugin, questFilter } from '@server/plugins/plugin'; /** diff --git a/src/world/actor/player/action/shop-action.ts b/src/world/actor/player/action/shop-action.ts index 492e56197..a0eaae820 100644 --- a/src/world/actor/player/action/shop-action.ts +++ b/src/world/actor/player/action/shop-action.ts @@ -1,6 +1,6 @@ import { world } from '@server/game-server'; import { Player } from '@server/world/actor/player/player'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { Shop, shopItemContainer } from '@server/world/config/shops'; import { widgets } from '@server/world/config/widget'; diff --git a/src/world/actor/player/action/world-item-action.ts b/src/world/actor/player/action/world-item-action.ts index d028de16a..38572b8b0 100644 --- a/src/world/actor/player/action/world-item-action.ts +++ b/src/world/actor/player/action/world-item-action.ts @@ -1,7 +1,7 @@ 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 { logger } from '@runejs/logger'; import { ActionPlugin, questFilter } from '@server/plugins/plugin'; import { WorldItem } from '@server/world/items/world-item'; diff --git a/src/world/config/examine-data.ts b/src/world/config/examine-data.ts index 7d833960d..809f4dff0 100644 --- a/src/world/config/examine-data.ts +++ b/src/world/config/examine-data.ts @@ -1,5 +1,5 @@ import { readFileSync } from 'fs'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; interface Examine { diff --git a/src/world/config/item-data.ts b/src/world/config/item-data.ts index 259e965ee..97c7c1b65 100644 --- a/src/world/config/item-data.ts +++ b/src/world/config/item-data.ts @@ -1,6 +1,6 @@ import { readFileSync, writeFileSync } from 'fs'; import { ItemDefinition } from '@runejs/cache-parser'; -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad, safeDump } from 'js-yaml'; export enum EquipmentSlot { diff --git a/src/world/config/npc-spawn.ts b/src/world/config/npc-spawn.ts index ba07dda36..bbc6952d5 100644 --- a/src/world/config/npc-spawn.ts +++ b/src/world/config/npc-spawn.ts @@ -1,4 +1,4 @@ -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; import { Direction } from '@server/world/direction'; diff --git a/src/world/config/scenery-spawns.ts b/src/world/config/scenery-spawns.ts index 3b40a3534..3dc799d16 100644 --- a/src/world/config/scenery-spawns.ts +++ b/src/world/config/scenery-spawns.ts @@ -1,4 +1,4 @@ -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; import { LocationObject } from '@runejs/cache-parser'; diff --git a/src/world/config/server-config.ts b/src/world/config/server-config.ts index a159af346..dcc5d2356 100644 --- a/src/world/config/server-config.ts +++ b/src/world/config/server-config.ts @@ -1,4 +1,4 @@ -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; diff --git a/src/world/config/shops.ts b/src/world/config/shops.ts index d6509e179..22327cac4 100644 --- a/src/world/config/shops.ts +++ b/src/world/config/shops.ts @@ -1,4 +1,4 @@ -import { logger } from '@runejs/logger/dist/logger'; +import { logger } from '@runejs/logger'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; import { ItemContainer } from '@server/world/items/item-container'; From e61c731756236aec57b7684af72a0aef25016303 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Sat, 6 Jun 2020 21:32:53 -0500 Subject: [PATCH 7/8] Fixing lint errors --- src/world/actor/pathfinding.ts | 68 +++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/world/actor/pathfinding.ts b/src/world/actor/pathfinding.ts index ab1e8144a..e799d341b 100644 --- a/src/world/actor/pathfinding.ts +++ b/src/world/actor/pathfinding.ts @@ -69,6 +69,38 @@ export class Pathfinding { public constructor(private actor: Actor) { } + public static canMoveNSEW(destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, i: number): boolean { + return (destinationAdjacency[destinationLocalX][destinationLocalY] & i) === 0; + } + + public static canMoveDiagonally(origin: Position, destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, + initialX: number, initialY: number, offsetX: number, offsetY: number, destMask: number, cornerMask1: number, cornerMask2: number): boolean { + const cornerX1: number = initialX + offsetX; + const cornerY1: number = initialY; + const cornerX2: number = initialX; + const cornerY2: number = initialY + offsetY; + const corner1 = Pathfinding.calculateLocalCornerPosition(cornerX1, cornerY1, origin); + const corner2 = Pathfinding.calculateLocalCornerPosition(cornerX2, cornerY2, origin); + + return ((destinationAdjacency[destinationLocalX][destinationLocalY] & destMask) == 0 && + (corner1.chunk.collisionMap.adjacency[corner1.localX][corner1.localY] & cornerMask1) == 0 && + (corner2.chunk.collisionMap.adjacency[corner2.localX][corner2.localY] & cornerMask2) == 0); + } + + private static calculateLocalCornerPosition(cornerX: number, cornerY: number, origin: Position): { localX: number, localY: number, chunk: Chunk } { + const cornerPosition: Position = new Position(cornerX, cornerY, origin.level + 1); + let cornerChunk: Chunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); + const tileAbove: Tile = cornerChunk.getTile(cornerPosition); + if(!tileAbove || !tileAbove.bridge) { + cornerPosition.level = cornerPosition.level - 1; + cornerChunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); + } + const localX: number = cornerX - cornerChunk.collisionMap.insetX; + const localY: number = cornerY - cornerChunk.collisionMap.insetY; + + return { localX, localY, chunk: cornerChunk }; + } + public async walkTo(position: Position, options: PathingOptions): Promise { if(!options.pathingDiameter) { options.pathingDiameter = 16; @@ -141,8 +173,8 @@ export class Pathfinding { this.openPoints.splice(this.openPoints.indexOf(this.currentPoint), 1); this.closedPoints.push(this.currentPoint); - let level = this.actor.position.level; - let { x, y, indexX, indexY } = this.currentPoint; + const level = this.actor.position.level; + const { x, y, indexX, indexY } = this.currentPoint; let canPath = false; try { @@ -404,36 +436,4 @@ export class Pathfinding { return true; } - public static canMoveNSEW(destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, i: number): boolean { - return (destinationAdjacency[destinationLocalX][destinationLocalY] & i) === 0; - } - - public static canMoveDiagonally(origin: Position, destinationAdjacency: number[][], destinationLocalX: number, destinationLocalY: number, - initialX: number, initialY: number, offsetX: number, offsetY: number, destMask: number, cornerMask1: number, cornerMask2: number): boolean { - const cornerX1: number = initialX + offsetX; - const cornerY1: number = initialY; - const cornerX2: number = initialX; - const cornerY2: number = initialY + offsetY; - const corner1 = Pathfinding.calculateLocalCornerPosition(cornerX1, cornerY1, origin); - const corner2 = Pathfinding.calculateLocalCornerPosition(cornerX2, cornerY2, origin); - - return ((destinationAdjacency[destinationLocalX][destinationLocalY] & destMask) == 0 && - (corner1.chunk.collisionMap.adjacency[corner1.localX][corner1.localY] & cornerMask1) == 0 && - (corner2.chunk.collisionMap.adjacency[corner2.localX][corner2.localY] & cornerMask2) == 0); - } - - private static calculateLocalCornerPosition(cornerX: number, cornerY: number, origin: Position): { localX: number, localY: number, chunk: Chunk } { - const cornerPosition: Position = new Position(cornerX, cornerY, origin.level + 1); - let cornerChunk: Chunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); - const tileAbove: Tile = cornerChunk.getTile(cornerPosition); - if(!tileAbove || !tileAbove.bridge) { - cornerPosition.level = cornerPosition.level - 1; - cornerChunk = world.chunkManager.getChunkForWorldPosition(cornerPosition); - } - const localX: number = cornerX - cornerChunk.collisionMap.insetX; - const localY: number = cornerY - cornerChunk.collisionMap.insetY; - - return { localX, localY, chunk: cornerChunk }; - } - } From 16db8fe7913187ceea8b8edd088c8011ddd1c233 Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Mon, 8 Jun 2020 21:07:41 -0500 Subject: [PATCH 8/8] Adding some more little skill accessor shortcuts for easier fetching I like helper functions :) --- .../skills/crafting/spinning-wheel-plugin.ts | 2 +- src/plugins/skills/firemaking-plugin.ts | 6 +- .../skills/smithing/smelting-plugin.ts | 6 +- src/world/actor/actor.ts | 6 +- src/world/actor/skills.ts | 112 +++++++++++++----- src/world/config/harvest-tool.ts | 4 +- src/world/skill-util/harvest-skill.ts | 2 +- 7 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/plugins/skills/crafting/spinning-wheel-plugin.ts b/src/plugins/skills/crafting/spinning-wheel-plugin.ts index 2c91394c1..7a1d35aa2 100644 --- a/src/plugins/skills/crafting/spinning-wheel-plugin.ts +++ b/src/plugins/skills/crafting/spinning-wheel-plugin.ts @@ -148,7 +148,7 @@ export const buttonClicked: buttonAction = (details) => { // Close the widget as it is no longer needed details.player.closeActiveWidgets(); - if (!details.player.skills.hasSkillLevel(Skill.CRAFTING, product.spinnable.requiredLevel)) { + if (!details.player.skills.hasLevel(Skill.CRAFTING, product.spinnable.requiredLevel)) { details.player.sendMessage(`You need a crafting level of ${product.spinnable.requiredLevel} to craft ${cache.itemDefinitions.get(product.spinnable.output).name.toLowerCase()}.`, true); return; } diff --git a/src/plugins/skills/firemaking-plugin.ts b/src/plugins/skills/firemaking-plugin.ts index 4670686a0..beccfc9cb 100644 --- a/src/plugins/skills/firemaking-plugin.ts +++ b/src/plugins/skills/firemaking-plugin.ts @@ -51,7 +51,7 @@ const lightFire = (player: Player, position: Position, worldItemLog: WorldItem, player.playAnimation(null); player.sendMessage(`The fire catches and the logs begin to burn.`); - player.skills.addExp('firemaking', burnExp); + player.skills.firemaking.addExp(burnExp); if(!player.walkingQueue.moveIfAble(-1, 0)) { if(!player.walkingQueue.moveIfAble(1, 0)) { @@ -93,7 +93,7 @@ const action: itemOnItemAction = (details) => { const worldItemLog = world.spawnWorldItem(log, player.position, player, 300); if(player.metadata['lastFire'] && Date.now() - player.metadata['lastFire'] < 1200 && - canChain(skillInfo.requiredLevel, player.skills.getSkillLevel('firemaking'))) { + canChain(skillInfo.requiredLevel, player.skills.firemaking.level)) { lightFire(player, position, worldItemLog, skillInfo.burnExp); } else { player.sendMessage(`You attempt to light the logs.`); @@ -121,7 +121,7 @@ const action: itemOnItemAction = (details) => { player.playAnimation(animationIds.lightingFire); } - canLightFire = elapsedTicks > 10 && canLight(skillInfo.requiredLevel, player.skills.getSkillLevel('firemaking')); + canLightFire = elapsedTicks > 10 && canLight(skillInfo.requiredLevel, player.skills.firemaking.level); if(!canLightFire && (elapsedTicks === 0 || elapsedTicks % 4 === 0)) { player.playSound(soundIds.lightingFire, 10, 0); diff --git a/src/plugins/skills/smithing/smelting-plugin.ts b/src/plugins/skills/smithing/smelting-plugin.ts index 58c6fce42..bb0368b95 100644 --- a/src/plugins/skills/smithing/smelting-plugin.ts +++ b/src/plugins/skills/smithing/smelting-plugin.ts @@ -180,7 +180,7 @@ const loadSmeltingInterface = (details: ObjectActionDetails) => { // Send the items to the widget. widgetItems.forEach((item) => { details.player.outgoingPackets.setItemOnWidget(widgets.furnace.widgetId, item.slot.modelId, item.bar.barId, 125); - if (!details.player.skills.hasSkillLevel(Skill.SMITHING, item.bar.requiredLevel)) { + if (!details.player.skills.hasLevel(Skill.SMITHING, item.bar.requiredLevel)) { details.player.modifyWidget(widgets.furnace.widgetId, { childId: item.slot.titleId, textColor: colors.red}); } else { details.player.modifyWidget(widgets.furnace.widgetId, { childId: item.slot.titleId, textColor: colors.black}); @@ -204,7 +204,7 @@ const hasIngredients = (details: ButtonActionDetails, ingredients: Item[], inven }; const canSmelt = (details: ButtonActionDetails, bar: Bar): boolean => { - return details.player.skills.hasSkillLevel(Skill.SMITHING, bar.requiredLevel); + return details.player.skills.hasLevel(Skill.SMITHING, bar.requiredLevel); }; const smeltProduct = (details: ButtonActionDetails, bar: Bar, count: number) => { @@ -303,4 +303,4 @@ export default new RunePlugin([ buttonIds: Array.from(widgetButtonIds.keys()), action: buttonClicked } -]); \ No newline at end of file +]); diff --git a/src/world/actor/actor.ts b/src/world/actor/actor.ts index 524917829..2c97a2654 100644 --- a/src/world/actor/actor.ts +++ b/src/world/actor/actor.ts @@ -1,6 +1,6 @@ import { WalkingQueue } from './walking-queue'; import { ItemContainer } from '../items/item-container'; -import { Animation, Graphic, UpdateFlags } from './update-flags'; +import { Animation, DamageType, Graphic, UpdateFlags } from './update-flags'; import { Npc } from './npc/npc'; import { Skills } from '@server/world/actor/skills'; import { Item } from '@server/world/items/item'; @@ -42,6 +42,10 @@ export abstract class Actor { this.pathfinding = new Pathfinding(this); } + public damage(amount: number, damageType: DamageType = DamageType.DAMAGE): void { + + } + public face(face: Position | Actor | null, clearWalkingQueue: boolean = true, autoClear: boolean = true, clearedByWalking: boolean = true): void { if(face === null) { this.clearFaceActor(); diff --git a/src/world/actor/skills.ts b/src/world/actor/skills.ts index 532d1c869..85f83b573 100644 --- a/src/world/actor/skills.ts +++ b/src/world/actor/skills.ts @@ -67,13 +67,74 @@ export const skillDetails: SkillDetail[] = [ export interface SkillValue { exp: number; level: number; + modifiedLevel?: number; } -export class Skills { +export class SkillShortcut { + + public constructor(private skills: Skills, private skillName: SkillName) { + } + + public addExp(exp: number): void { + this.skills.addExp(this.skillName, exp); + } + + public get level(): number { + return this.skills.getLevel(this.skillName); + } + + public get exp(): number { + return this.skills.get(this.skillName).exp; + } + + public get levelForExp(): number { + return this.skills.getLevelForExp(this.exp); + } + +} + +type SkillShortcutMap = { + [skillName in SkillName]: SkillShortcut; +}; + +class SkillShortcuts implements SkillShortcutMap { + agility: SkillShortcut; + attack: SkillShortcut; + construction: SkillShortcut; + cooking: SkillShortcut; + crafting: SkillShortcut; + defence: SkillShortcut; + farming: SkillShortcut; + firemaking: SkillShortcut; + fishing: SkillShortcut; + fletching: SkillShortcut; + herblore: SkillShortcut; + hitpoints: SkillShortcut; + magic: SkillShortcut; + mining: SkillShortcut; + prayer: SkillShortcut; + ranged: SkillShortcut; + runecrafting: SkillShortcut; + slayer: SkillShortcut; + smithing: SkillShortcut; + strength: SkillShortcut; + thieving: SkillShortcut; + woodcutting: SkillShortcut; +} + +export class Skills extends SkillShortcuts { private _values: SkillValue[]; public constructor(private actor: Actor, values?: SkillValue[]) { + super(); + + Object.keys(Skill) + .map(skillName => skillName.toLowerCase()) + .forEach(skillName => + this[skillName] = new SkillShortcut(this, skillName as SkillName) + ); + if(values) { this._values = values; } else { @@ -81,24 +142,14 @@ export class Skills { } } - private defaultValues(): SkillValue[] { - const values: SkillValue[] = []; - skillDetails.forEach(s => values.push({ exp: 0, level: 1 })); - values[Skill.HITPOINTS] = { exp: 1154, level: 10 }; - return values; - } - - /* - * @TODO make an additional field for boostedLevel that this reads from - * Also add a new method to get the unboostedLevel incase it's ever needed - * Then think about some way to reliably and easily fade those boosts out over time - */ - public getSkillLevel(skill: number | SkillName): number { - return this.get(skill).level; + public getLevel(skill: number | SkillName, ignoreLevelModifications: boolean = false): number { + const s = this.get(skill); + return (s.modifiedLevel !== undefined && !ignoreLevelModifications ? s.modifiedLevel : s.level); } - public hasSkillLevel(skill: number | SkillName, level: number): boolean { - return this.get(skill).level >= level; + public hasLevel(skill: number | SkillName, level: number, ignoreLevelModifications: boolean = false): boolean { + const s = this.get(skill); + return (ignoreLevelModifications ? s.level : s.modifiedLevel) >= level; } public getLevelForExp(exp: number): number { @@ -181,16 +232,6 @@ export class Skills { }); } - public setExp(skill: number | SkillName, exp: number): void { - const skillId = this.getSkillId(skill); - this._values[skillId].exp = exp; - } - - public setLevel(skill: number | SkillName, level: number): void { - const skillId = this.getSkillId(skill); - this._values[skillId].level = level; - } - public getSkillId(skill: number | SkillName) : number { if(typeof skill === 'number') { return skill; @@ -209,6 +250,23 @@ export class Skills { } } + private defaultValues(): SkillValue[] { + const values: SkillValue[] = []; + skillDetails.forEach(s => values.push({ exp: 0, level: 1 })); + values[Skill.HITPOINTS] = { exp: 1154, level: 10 }; + return values; + } + + private setExp(skill: number | SkillName, exp: number): void { + const skillId = this.getSkillId(skill); + this._values[skillId].exp = exp; + } + + private setLevel(skill: number | SkillName, level: number): void { + const skillId = this.getSkillId(skill); + this._values[skillId].level = level; + } + public get values(): SkillValue[] { return this._values; } diff --git a/src/world/config/harvest-tool.ts b/src/world/config/harvest-tool.ts index 2be2934a6..be7f95198 100644 --- a/src/world/config/harvest-tool.ts +++ b/src/world/config/harvest-tool.ts @@ -57,7 +57,7 @@ const Axes: HarvestTool[] = [ */ export function getBestPickaxe(player: Player): HarvestTool | null { for (let i = Pickaxes.length - 1; i >= 0; i--) { - if (player.skills.hasSkillLevel(Skill.MINING, Pickaxes[i].level)) { + if (player.skills.hasLevel(Skill.MINING, Pickaxes[i].level)) { if (player.hasItemOnPerson(Pickaxes[i].itemId)) { return Pickaxes[i]; } @@ -72,7 +72,7 @@ export function getBestPickaxe(player: Player): HarvestTool | null { */ export function getBestAxe(player: Player): HarvestTool | null { for (let i = Axes.length - 1; i >= 0; i--) { - if (player.skills.hasSkillLevel(Skill.WOODCUTTING, Axes[i].level)) { + if (player.skills.hasLevel(Skill.WOODCUTTING, Axes[i].level)) { if (player.hasItemOnPerson(Axes[i].itemId)) { return Axes[i]; } diff --git a/src/world/skill-util/harvest-skill.ts b/src/world/skill-util/harvest-skill.ts index 4ed83aa8b..240d49ebc 100644 --- a/src/world/skill-util/harvest-skill.ts +++ b/src/world/skill-util/harvest-skill.ts @@ -34,7 +34,7 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill: // Check player level against the required level - if (!player.skills.hasSkillLevel(skill, target.level)) { + if (!player.skills.hasLevel(skill, target.level)) { switch (skill) { case Skill.MINING: player.sendMessage(`You need a Mining level of ${target.level} to mine this rock.`, true);