'use strict';
/**
* @module entity/NPC
*/
let Entity = require('./Entity');
/**
*
*
* @param {any} x
* @param {any} y
* @param {any} key
* @constructor NPC
*/
function NPC(x, y, key) {
Entity.call(this, x, y, key);
/**
* NPC Health.
*
* Setting max HP to 100 by default.
*/
this.maxHP = 100;
this.HP = 100;
this.sprintSpeed = 130;
/**
* Type of Entity
*/
this.type = 'npc';
/**
* For all `NPC`s, I am defaulting the people they dislike
* to monsters. This will be overridden in `Monster`.
*/
this.dislike = ['monster'];
/**
* @todo(anand): Fix this hack
* This is a major hack. We are setting all NPCs to IMMOVABLE!!
*/
this.body.immovable = true;
}
NPC.prototype = Object.create(Entity.prototype);
NPC.prototype.constructor = NPC;
/**
* This function tells an entity object to travel to a desired location
*
* @param {number} x
* @param {number} y
* @param {navMesh} navMesh -navigation mesh object
* @param {boolean} sprint if true go fast
* @return {boolean} return true if finished, otherwise false.
*/
NPC.prototype.gotoXY = function(x, y, navMesh, sprint = false) {
// destination point
const p2 = new Phaser.Point(x, y);
// the entities location, respective to the center of its hit box
const trueX = this.x+this.body.width /
2 + this.body.offset.x;
const trueY = this.y+this.body.height /
2 + this.body.offset.y;
const p1 = new Phaser.Point(trueX, trueY);
// cool library magic
const path = navMesh.findPath(p1, p2);
/* 0. up
* 1. right
* 2. down
* 3. left
*/
if (path) {
// check to see if the target location is reached within 5 units
if (path.length === 2 && Math.abs(path[1].x - trueX) < 5
&& Math.abs(path[1].y - trueY) < 5) {
if (this.state !== 'attacking') this.idleHere();
return true;
}
let currentTime = game.time.now;
// limit the amount of direction changes to about 1 per 150 ms
if (currentTime - this.directionLimiter >= 150) {
// confusing code that ram won't understand
Math.abs(path[1].x - trueX) >= Math.abs(path[1].y - trueY) ?
this.moveInDirection(((path[1].x - trueX < 0)*2)+1, sprint) :
this.moveInDirection((path[1].y - trueY > 0)*2, sprint);
this.directionLimiter = currentTime;
}
} else {
// if lost don't move
this.idleHere();
this.destinationX = undefined;
this.destinationY = undefined;
}
return false;
};
/**
* Allow an Entity object to wander
* @param {navMesh} navMesh the maps navigation mesh
* @param {Phaser.Point} topLeft top left cornor of the bounds (x,y) default 0,0
* @param {Phaser.Point} botRight bottom left cornor of the bounds (x,y)
* default map width,hieght
*/
NPC.prototype.wander = function(navMesh,
topLeft = new Phaser.Point(0, 0),
botRight = new Phaser.Point(game.world.width, game.world.height)) {
if (this.state === 'dead') {
return;
}
// check if the npc is still thinking about going somewhere
if (this.idleTimer != 0) {
this.idleTimer -= 1;
return;
}
// check if the npc is en route, otherrwise find a new route
if (!(this.destinationX && this.destinationY)) {
this.destinationX = game.rnd.integerInRange(topLeft.x, botRight.x);
this.destinationY = game.rnd.integerInRange(topLeft.y, botRight.y);
if (Math.sqrt(Math.pow(this.destinationX - this.trueXY().x, 2) +
Math.pow(this.destinationY - this.trueXY().y, 2)) > 256) {
// reset if path is too long
this.destinationX = undefined;
this.destinationY = undefined;
}
}
// if destination is reached, clear current destination.
// add a random timer to wait before wandering elsewhere
// max about 12 seconds
if (this.gotoXY(this.destinationX, this.destinationY, navMesh)) {
this.destinationX = undefined;
this.destinationY = undefined;
this.idleTimer = game.rnd.integerInRange(1, 1200);
}
};
/**
* Make the npc attack a target
* @param {Entity} target target to attack
* @param {navmesh} navMesh the navMesh of the map
* @param {boolean=} sprint the navMesh of the map
* @return {boolean} target no longer exists (probably dead)
*/
NPC.prototype.aggro = function(target, navMesh, sprint = false) {
if (!target) {
console.warn('target does not exist');
this.idleHere();
return true;
}
if (target.state === 'dead') {
return true;
}
// check your location relative to target
if (Math.abs(target.trueXY().x-this.trueXY().x)
>= Math.abs(target.trueXY().y - this.trueXY().y)) {
// approach from left
if (target.trueXY().x-this.trueXY().x < 0) {
if (this.gotoXY(target.trueXY().x+32, target.trueXY().y,
navMesh, sprint)) {
this.setDirection('left');
this.attack();
}
} else {
// approach from right
if (this.gotoXY(target.trueXY().x-32, target.trueXY().y,
navMesh, sprint)) {
this.setDirection('right');
this.attack();
}
}
} else {
// approach from up
if (target.trueXY().y-this.trueXY().y < 0) {
if (this.gotoXY(target.trueXY().x, target.trueXY().y+32,
navMesh, sprint)) {
this.setDirection('up');
this.attack();
}
} else {
// approach from down
if (this.gotoXY(target.trueXY().x, target.trueXY().y-32,
navMesh, sprint)) {
this.setDirection('down');
this.attack();
}
}
}
};
/**
* @param {navmesh} navMesh the navMesh of the map
* @param {Phaser.Point} topLeft top left bounds the entity can wander in.
* @param {Phaser.Point} botRight bottom right bounds the entity can wander in.
* @param {Entity} player the player!
* @param {string} behavior neutral or aggresive for now
*/
NPC.prototype.updateAI = function(navMesh, topLeft,
botRight, player, behavior) {
if (this.state === 'dead') return;
this.aiStatus = behavior;
switch (this.aiStatus) {
case ('neutral'):
this.wander(navMesh, topLeft, botRight);
break;
case ('aggressive'):
if (Math.sqrt(Math.pow(player.trueXY().x - this.trueXY().x, 2) +
Math.pow(player.trueXY().y - this.trueXY().y, 2)) < 192) {
// if in range aggro to player
this.aggro(player, navMesh);
this.aggroStatus = true;
} else {
if (this.aggroStatus) {
// aggro out of range, stop maybe? then continue wandering
this.idleHere();
this.aggroStatus = false;
}
// otherwise wander around
this.wander(navMesh, topLeft, botRight);
}
break;
default:
console.warn('no behavior defined');
break;
}
};
module.exports = NPC;