Skip to content

Commit

Permalink
Merge pull request #25 from rune-js/npcs
Browse files Browse the repository at this point in the history
NPCs!
  • Loading branch information
TheBlackParade authored Jan 6, 2020
2 parents e216c47 + 1733b5b commit aa96ab6
Show file tree
Hide file tree
Showing 15 changed files with 518 additions and 128 deletions.
4 changes: 4 additions & 0 deletions data/config/npc-spawns.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- npcId: 0
x: 3222
y: 3222
radius: 10
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"ts-node": "^8.4.1",
"tslib": "^1.10.0",
"typescript": "^3.7.2",
"uuid": "^3.3.3",
"yargs": "^15.0.2"
},
"devDependencies": {
Expand All @@ -38,6 +39,7 @@
"@types/hapi__joi": "^16.0.6",
"@types/js-yaml": "^3.12.1",
"@types/node": "^12.12.6",
"@types/uuid": "^3.4.6",
"@types/yargs": "^13.0.4"
}
}
2 changes: 1 addition & 1 deletion src/game-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const GAME_SERVER_PORT = 43594;
export const serverDir = join(__dirname, '../');
export const gameCache = new GameCache(join(serverDir, 'cache'));
export const world = new World();
world.chunkManager.generateCollisionMaps();
world.init();

if(yargs.argv.fakePlayers) {
world.generateFakePlayers();
Expand Down
34 changes: 34 additions & 0 deletions src/world/config/npc-spawn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { logger } from '@runejs/logger/dist/logger';
import { JSON_SCHEMA, safeLoad } from 'js-yaml';
import { readFileSync } from "fs";
import { join } from "path";
import { serverDir } from '../../game-server';
import { Direction } from '../world';

export interface NpcSpawn {
npcId: number;
x: number;
y: number;
level?: number;
radius?: number;
face?: Direction;
}

export function parseNpcSpawns(): NpcSpawn[] {
try {
logger.info('Parsing npc spawns...');

const npcSpawns = safeLoad(readFileSync(join(serverDir, 'data/config/npc-spawns.yaml'), 'utf8'), { schema: JSON_SCHEMA }) as NpcSpawn[];

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

logger.info(`${npcSpawns.length} npc spawns found.`);

return npcSpawns;
} catch(error) {
logger.error('Error parsing npc spawns: ' + error);
return null;
}
}
65 changes: 45 additions & 20 deletions src/world/entity/mob/mob.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Entity } from '../entity';
import { WalkingQueue } from './walking-queue';
import { ItemContainer } from './items/item-container';
import { UpdateFlags } from './update-flags';
import { Npc } from './npc/npc';

/**
* Handles a mobile entity within the game world.
*/
export abstract class Mob extends Entity {

private _worldIndex: number;
public readonly updateFlags: UpdateFlags;
private readonly _walkingQueue: WalkingQueue;
private _walkDirection: number;
private _runDirection: number;
Expand All @@ -16,6 +19,7 @@ export abstract class Mob extends Entity {

protected constructor() {
super();
this.updateFlags = new UpdateFlags();
this._walkingQueue = new WalkingQueue(this);
this._walkDirection = -1;
this._runDirection = -1;
Expand All @@ -27,37 +31,56 @@ export abstract class Mob extends Entity {
setInterval(() => {
const movementChance = Math.floor(Math.random() * 10);

if(movementChance < 8) {
if(movementChance < 7) {
return;
}

let px = this.position.x;
let py = this.position.y;
let px: number;
let py: number;
let movementAllowed = false;

const moveXChance = Math.floor(Math.random() * 10);
while(!movementAllowed) {
px = this.position.x;
py = this.position.y;

if(moveXChance > 6) {
const moveXAmount = Math.floor(Math.random() * 5);
const moveXMod = Math.floor(Math.random() * 2);
const moveXChance = Math.floor(Math.random() * 10);

if(moveXMod === 0) {
px -= moveXAmount;
} else {
px += moveXAmount;
if(moveXChance > 6) {
const moveXAmount = Math.floor(Math.random() * 5);
const moveXMod = Math.floor(Math.random() * 2);

if(moveXMod === 0) {
px -= moveXAmount;
} else {
px += moveXAmount;
}
}
}

const moveYChance = Math.floor(Math.random() * 10);
const moveYChance = Math.floor(Math.random() * 10);

if(moveYChance > 6) {
const moveYAmount = Math.floor(Math.random() * 5);
const moveYMod = Math.floor(Math.random() * 2);

if(moveYMod === 0) {
py -= moveYAmount;
} else {
py += moveYAmount;
}
}

let valid = true;

if(moveYChance > 6) {
const moveYAmount = Math.floor(Math.random() * 5);
const moveYMod = Math.floor(Math.random() * 2);
if(this instanceof Npc && (this as Npc).movementRadius) {
const npc = this as Npc;

if(moveYMod === 0) {
py -= moveYAmount;
} else {
py += moveYAmount;
if(px > npc.initialPosition.x + npc.movementRadius || px < npc.initialPosition.x - npc.movementRadius
|| py > npc.initialPosition.y + npc.movementRadius || py < npc.initialPosition.y - npc.movementRadius) {
valid = false;
}
}

movementAllowed = valid;
}

if(px !== this.position.x || py !== this.position.y) {
Expand All @@ -68,6 +91,8 @@ export abstract class Mob extends Entity {
}, 1000);
}

public abstract equals(mob: Mob): boolean;

public get worldIndex(): number {
return this._worldIndex;
}
Expand Down
96 changes: 96 additions & 0 deletions src/world/entity/mob/npc/npc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Mob } from '../mob';
import { NpcSpawn } from '../../../config/npc-spawn';
import { NpcDefinition } from '@runejs/cache-parser';
import uuidv4 from 'uuid/v4';
import { Position } from '../../../position';
import { Direction } from '../../../world';
import { world } from '../../../../game-server';

interface NpcAnimations {
walk: number;
stand: number;
turnAround: number;
turnRight: number;
turnLeft: number;
}

/**
* Represents a non-player character within the game world.
*/
export class Npc extends Mob {

public readonly id: number;
public readonly uuid: string;
private _name: string;
private _combatLevel: number;
private _animations: NpcAnimations;
private _movementRadius: number = 0;
private _initialFaceDirection: Direction = 'NORTH';
public readonly initialPosition: Position;

public constructor(npcSpawn: NpcSpawn, cacheData: NpcDefinition) {
super();
this.id = cacheData.id;
this.uuid = uuidv4();
this._name = cacheData.name;
this._combatLevel = cacheData.combatLevel;
this._animations = cacheData.animations as NpcAnimations;
this.position = new Position(npcSpawn.x, npcSpawn.y, npcSpawn.level);
this.initialPosition = new Position(npcSpawn.x, npcSpawn.y, npcSpawn.level);

if(npcSpawn.radius) {
this._movementRadius = npcSpawn.radius;
}

if(npcSpawn.face) {
this._initialFaceDirection = npcSpawn.face;
}
}

public init(): void {
world.chunkManager.getChunkForWorldPosition(this.position).addNpc(this);
this.initiateRandomMovement();
}

public tick(): Promise<void> {
return new Promise<void>(resolve => {
this.walkingQueue.process();
resolve();
});
}

public reset(): Promise<void> {
return new Promise<void>(resolve => {
this.updateFlags.reset();
resolve();
});
}

public equals(other: Npc): boolean {
if(!other) {
return false;
}

return other.id === this.id && other.uuid === this.uuid;
}

public get name(): string {
return this._name;
}

public get combatLevel(): number {
return this._combatLevel;
}

public get animations(): NpcAnimations {
return this._animations;
}

public get movementRadius(): number {
return this._movementRadius;
}

public get initialFaceDirection(): Direction {
return this._initialFaceDirection;
}
}
11 changes: 7 additions & 4 deletions src/world/entity/mob/player/player.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Socket } from 'net';
import { PacketSender } from './packet/packet-sender';
import { Isaac } from '../../../../net/isaac';
import { PlayerUpdateTask } from './task/player-update-task';
import { PlayerUpdateTask } from './task/updating/player-update-task';
import { Mob } from '../mob';
import { UpdateFlags } from './update-flags';
import { Position } from '../../../position';
import { Skill, skills } from '../skills/skill';
import { world } from '../../../../game-server';
Expand All @@ -19,6 +18,8 @@ import { ActiveInterface, interfaceIds, interfaceSettings } from './game-interfa
import { ContainerUpdateEvent, ItemContainer } from '../items/item-container';
import { EquipmentBonuses, ItemDetails } from '../../../config/item-data';
import { Item } from '../items/item';
import { Npc } from '../npc/npc';
import { NpcUpdateTask } from './task/updating/npc-update-task';

const DEFAULT_TAB_INTERFACES = [
2423, 3917, 638, 3213, 1644, 5608, 1151, -1, 5065, 5715, 2449, 904, 147, 962
Expand All @@ -39,8 +40,9 @@ export class Player extends Mob {
public isLowDetail: boolean;
private readonly _packetSender: PacketSender;
public readonly playerUpdateTask: PlayerUpdateTask;
public readonly updateFlags: UpdateFlags;
public readonly npcUpdateTask: NpcUpdateTask;
public trackedPlayers: Player[];
public trackedNpcs: Npc[];
private _appearance: Appearance;
private _activeGameInterface: ActiveInterface;
private readonly _equipment: ItemContainer;
Expand All @@ -59,8 +61,9 @@ export class Player extends Mob {
this.isLowDetail = isLowDetail;
this._packetSender = new PacketSender(this);
this.playerUpdateTask = new PlayerUpdateTask(this);
this.updateFlags = new UpdateFlags();
this.npcUpdateTask = new NpcUpdateTask(this);
this.trackedPlayers = [];
this.trackedNpcs = [];
this._activeGameInterface = null;
this._carryWeight = 0;
this._equipment = new ItemContainer(14);
Expand Down
Loading

0 comments on commit aa96ab6

Please sign in to comment.