WTF-Adventure/client/entity/entity.js
import EntityHandler from './entityhandler';
import log from '@/client/lib/log';
/**
* Initialize a new entity:
* - {@link Character}
* - {@link Mob}
* - {@link Npc}
* - {@link Player}
* - {@link Chest}
* - {@link Item}
* - {@link Projectile}
* @class
* @example const myEntity = new Entity(1, 'player');
*/
export default class Entity {
/**
* Constructor to initialize a new entity
* @param {Number} id the entity id
* @param {String} kind the type of entity this is
* @param {String} label a name for the entity this is
*/
constructor(id, kind, label) {
log.debug('Entity - constructor()', id, kind, label);
/**
* Entity id
* @type {Number}
*/
this.id = id;
/**
* Name of the entity
* @type {String}
*/
this.name = kind;
/**
* Label for the entity
* @type {String}
*/
this.label = label;
/**
* X coordinate
* @type {Number}
*/
this.x = 0;
/**
* Y coordinate
* @type {Number}
*/
this.y = 0;
/**
* Grid X position
* @type {Number}
*/
this.gridX = 0;
/**
* Grid Y position
* @type {Number}
*/
this.gridY = 0;
/**
* Sprite
* @type {Sprite}
*/
this.sprite = null;
/**
* Whether or not to flip the sprite on the X axis
* @type {Boolean}
*/
this.spriteFlipX = false;
/**
* Whether or not to flip the sprite on the Y axis
* @type {Boolean}
*/
this.spriteFlipY = false;
/**
* Animations for the sprite
* @type {Animation}
*/
this.animations = null;
/**
* Current animation the sprite should be playing
* @type {String}
*/
this.currentAnimation = null;
/**
* Offset for the shadow on the Y axis
* @type {Number}
*/
this.shadowOffsetY = 0;
/**
* Whether or not the sprite has been loaded
* @type {Boolean}
*/
this.spriteLoaded = false;
/**
* Wehther or not this entity is visible
* @type {Boolean}
*/
this.visible = true;
/**
* Whether or not this is fading
* @type {Boolean}
*/
this.fading = false;
/**
* Handler for the entity
* @type {EntityHandler}
*/
this.handler = new EntityHandler(this);
/**
* [angled description]
* @type {Boolean}
*/
this.angled = false;
/**
* Angle of the entity
* @type {Number}
*/
this.angle = 0;
/**
* Critical health
* @type {Boolean}
*/
this.critical = false;
/**
* Stunned status
* @type {Boolean}
*/
this.stunned = false;
/**
* Terror status
* @type {Boolean}
*/
this.terror = false;
/**
* Non pathable
* @type {Boolean}
*/
this.nonPathable = false;
/**
* Has a counter
* @type {Boolean}
*/
this.hasCounter = false;
/**
* Count down time
* @type {Number}
*/
this.countdownTime = 0;
/**
* Current counter
* @type {Number}
*/
this.counter = 0;
/**
* Has a shadow
* @type {Boolean}
*/
this.shadow = false;
/**
* Path
* @type {Boolean}
*/
this.path = false;
/**
* Rendering data
* @type {Object}
*/
this.renderingData = {
scale: -1,
angle: 0,
};
/**
* Set if the entity is dirty
* @type {Boolean}
*/
this.dirty = false;
/**
* Fading time
* @type {Number}
*/
this.fadingTime = 0;
/**
* Timeout function for blinking
* @type {Function}
*/
this.blinking = null;
/**
* Normal sprite name
* @type {String}
*/
this.normalSprite = null;
/**
* Hurt sprite name
* @type {String}
*/
this.hurtSprite = null;
/**
* CAllback function when the entity is ready
* @type {Function}
*/
this.readyCallback = null;
/**
* Callback for when the entity is dirty
* @type {Function}
*/
this.dirtyCallback = null;
this.loadDirty();
}
/**
* Checks to see if the x,y coordinate is adjacent to the
* entity's current position
*
* @param {Number} x coordinate
* @param {Number} y coordinate
* @param {Boolean} ignoreDiagonals if true, will ignore diagonal
* directions, defaults to false (optional)
* @return {Boolean}
*/
isPositionAdjacent(x, y, ignoreDiagonals = false) {
// north west - diagonal
if (!ignoreDiagonals && x - 1 === this.gridX && y + 1 === this.gridY) {
return true;
}
// north
if (x === this.gridX && y + 1 === this.gridY) {
return true;
}
// north east - diagonal
if (!ignoreDiagonals && x + 1 === this.gridX && y + 1 === this.gridY) {
return true;
}
// west
if (x - 1 === this.gridX && y === this.gridY) {
return true;
}
// east
if (x + 1 === this.gridX && y === this.gridY) {
return true;
}
// south west - diagonal
if (!ignoreDiagonals && x - 1 === this.gridX && y - 1 === this.gridY) {
return true;
}
// south
if (x === this.gridX && y - 1 === this.gridY) {
return true;
}
// south west - diagonal
if (!ignoreDiagonals && x - 1 === this.gridX && y - 1 === this.gridY) {
return true;
}
return false;
}
/**
* This is important for when the client is
* on a mobile screen. So the sprite has to be
* handled differently.
*/
loadDirty() {
this.dirty = true;
if (this.dirtyCallback) {
this.dirtyCallback();
}
}
/**
* Tell this to fade in given the duration
* @param {Number} time the amount of time to fade in
*/
fadeIn(time) {
this.fading = true;
this.fadingTime = time;
}
/**
* Tell this entity to start blinking
* @param {Number} speed the interval time between blinks
*/
blink(speed) {
this.blinking = setInterval(() => {
this.toggleVisibility();
}, speed);
}
/**
* Tell the entity to stop blinking and force it to be visible
*/
stopBlinking() {
if (this.blinking) {
clearInterval(this.blinking);
}
this.setVisible(true);
}
/**
* Set the entity's name
* @param {String} name the name of this entity
*/
setName(name) {
this.name = name;
}
/**
* Set the sprite for this entity
* @param {Sprite} sprite the sprite for this entity
*/
setSprite(sprite) {
if (!sprite || (this.sprite && this.sprite.name === sprite.name)) {
return;
}
if (!sprite.loaded) {
sprite.loadSprite();
}
sprite.name = sprite.id; // eslint-disable-line
this.sprite = sprite;
this.normalSprite = this.sprite;
this.hurtSprite = sprite.getHurtSprite();
this.animations = sprite.createAnimations();
this.spriteLoaded = true;
if (this.readyCallback) {
this.readyCallback();
}
}
/**
* Set the x,y position for the entity
* @param {Number} x the x position on the screen
* @param {[type]} y the y position on the screen
*/
setPosition(x, y) {
this.x = x;
this.y = y;
}
/**
* Set the x,y position of the entity on the tile grid
* @param {Number} x the gridX
* @param {Number} y the gridY
*/
setGridPosition(x, y) {
this.gridX = x;
this.gridY = y;
this.setPosition(x * 16, y * 16);
}
/**
* Set the animation on this entity
* @param {String} name the name of the animation
* @param {Number} speed the animation speed
* @param {Number} count how many times to play the animation
* @param {Number} onEndCount callback to trigger when the animation
* ends after count number of times
*/
setAnimation(name, speed, count, onEndCount) {
if (
!this.spriteLoaded
|| (this.currentAnimation && this.currentAnimation.name === name)
) {
return;
}
const anim = this.getAnimationByName(name);
if (!anim) {
return;
}
this.currentAnimation = anim;
if (name.substr(0, 3) === 'atk') {
this.currentAnimation.reset();
}
this.currentAnimation.setSpeed(speed);
this.currentAnimation.setCount(
count || 0,
onEndCount
|| (() => {
this.idle();
}),
);
}
/**
* Set the count for the count down time
* @param {Number} count sets the counter
*/
setCountdown(count) {
this.counter = count;
this.countdownTime = new Date().getTime();
this.hasCounter = true;
}
/**
* Returns the instance of the weapon this entity has
* @return {Item|Projectile} a weapon or projectile
*/
hasWeapon() {
return this.weapon;
}
/**
* Get the distance from this entity to another entity
* @param {Entity} entity the entity you want to get the distance to
* @return {Number} the furtherst x or y distance from the given entity
*/
getDistance(entity) {
const x = Math.abs(this.gridX - entity.gridX);
const y = Math.abs(this.gridY - entity.gridY);
return x > y ? x : y;
}
/**
* Get the distance from this entity to the given x,y coordinates
* @param {Number} toX the x coordinate
* @param {Number} toY the y coordinate
* @return {Number} the further x or y distance to this coordinate
*/
getCoordDistance(toX, toY) {
const x = Math.abs(this.gridX - toX);
const y = Math.abs(this.gridY - toY);
return x > y ? x : y;
}
/**
* Figure out if this entity is within the attack radius of the given entity
* @param {Entity} entity Player|Mob|Character|NPC
* @return {Boolean} returns true if they are within the entity's attack radius,
* if the entity has no attack radius default to 2 grid cells
*/
inAttackRadius(entity) {
const attackRadius = entity.attackRadius || 2;
return (
entity
&& this.getDistance(entity) < attackRadius
&& !(this.gridX !== entity.gridX && this.gridY !== entity.gridY)
);
}
/**
* Figure out if this entity is within the extended attack radius of the given entity
* @param {Entity} entity Player|Mob|Character|NPC
* @return {Boolean} returns true if they are within the entity's attack radius,
* if the entity has no attack radius default to 3 grid cells
*/
inExtraAttackRadius(entity) {
const attackRadius = (entity.attackRadius + 1) || 3;
return (
entity
&& this.getDistance(entity) < attackRadius
&& !(this.gridX !== entity.gridX && this.gridY !== entity.gridY)
);
}
/**
* Look for an animtions list for this specific animation given it's name
* @param {String} name the name of the animation
* @return {Animation|null} returns the animation object or null if not found
*/
getAnimationByName(name) {
if (name in this.animations) {
return this.animations[name];
}
return null;
}
/**
* Returns the name of the sprite
* @return {String} the name of the sprite
*/
getSprite() {
return this.sprite.name;
}
/**
* Set the visibility on this entity
* @param {Boolean} visible true|false
*/
setVisible(visible) {
this.visible = visible;
}
/**
* Toggle the current visibility of this entity
*/
toggleVisibility() {
this.setVisible(!this.visible);
}
/**
* Check whether or not this entity is visible
* @return {Boolean} returns true if visibile
*/
isVisible() {
return this.visible;
}
/**
* Check whether or not this entity has a shadow
* @return {Object} returns the entity's shadow
*/
hasShadow() {
return this.shadow;
}
/**
* Check if this entity currently has a path
* @return {Object} returns the entity's path
*/
hasPath() {
return this.path;
}
/**
* When this entity's sprite is loaded trigger this callback
* @param {Function} callback the function to call when the sprite is loaded
*/
onReady(callback) {
this.readyCallback = callback;
}
/**
* When this entity's is dirty (mobile) use this callback
* @param {Function} callback the function to call when the sprite isDirty
*/
onDirty(callback) {
this.dirtyCallback = callback;
}
}