Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

NPCs! #25

Merged
merged 4 commits into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions 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