'use strict'; /** * @module entity/Entity */ const _ = require('lodash'); const uuid = require('../util/uuid'); let h; let w; let offx; let offy; /** * A lookup table for directions */ const DIRECTIONS = ['up', 'right', 'down', 'left']; /** * The `Entity` class is the base class for all game entities. * It needs to have to following properties * * - Health (Upto the child class to define) * - Weapon * - Inventory @todo * - Factions @todo * * It also cannot step outside world bounds. * * @param {number} x - The x coordinate of `Entity` on the canvas * @param {number} y - The y coordinate of `Entity` on the canvas * @param {string} key - The key to the loaded spritesheet * @constructor Entity * @see Phaser.Sprite */ function Entity(x, y, key) { Phaser.Sprite.call(this, game, x, y, key); game.add.existing(this); game.physics.enable(this, Phaser.Physics.ARCADE); this.body.collideWorldBounds = true; /** * Direction initialized to down. * Must be changed only when new direction is chosen. */ this.direction = 'down'; /** * Begin with no weapon. * * @todo: The weapons can be stored as an object with attributes. */ this.weapon = null; /** * Miscellaneous attributes. */ this.speed = 65; this.sprintSpeed = 170; // Set the default animations this.setAnimations(); game.physics.enable(this, Phaser.Physics.ARCADE); /** * hitbox fix */ this.body.height = this.body.height / 2; this.body.width = this.body.width / 2; this.body.offset.x += this.body.width / 2; this.body.offset.y += this.body.height; // /** // * New Hitbox fix // */ // this.collideBox = game.add.sprite(0, 0, 'as'); // game.physics.enable(this.collideBox, Phaser.Physics.ARCADE); // this.collideBox.anchor.setTo(-0.5); // // this.collideBox.visible = false; // // this.collideBox.frame = 12; // // this.collideBox.body.setSize(this.body.height/2, this.body.width/2); // this.collideBox.body.height = this.body.height/2; // this.collideBox.body.width = this.body.width/2; // this.addChild(this.collideBox); // Set size constants h = this.body.height; w = this.body.width; offx = this.body.offset.x; offy = this.body.offset.y; /** * States. * State can be 'idling', 'walking', 'attacking' */ this.state = 'idling'; this.idleTimer = 0; this.directionLimiter = 0; /** * Type and ID */ this.type = 'generic'; this.id = uuid(); /** * Reputation and Gossip */ this.reputation = 0; this.information = []; this.dislike = []; } Entity.prototype = Object.create(Phaser.Sprite.prototype); Entity.prototype.constructor = Entity; /** * Set the animations of the `Entity`. * * * @param {object} frames - Object containing the animation frames */ Entity.prototype.setAnimations = function(frames) { if (frames !== undefined) { // Do something with frames }; // Else set to default animations /** * Adding animations for the `Entity`. * * 1 sprite sheet contains every movement. * You target sections of the sprite sheet by using array[0...n], * where 0 is the top left corner of the image and n is the bottom * right corner of the image. Spritesheets and their corresponding integers * count left to right, top to bottom. */ this.animations.add('idle_up', [104], 10, true); this.animations.add('idle_right', [143], 10, true); this.animations.add('idle_down', [130], 10, true); this.animations.add('idle_left', [117], 10, true); this.animations.add('die', [260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 271, 272], 25, false); this.animations.add('walk_up', [105, 106, 107, 108, 109, 110, 111, 112], 10, true); this.animations.add('walk_down', [131, 132, 133, 134, 135, 136, 137, 138], 10, true); this.animations.add('walk_left', [118, 119, 120, 121, 122, 123, 124, 125], 10, true); this.animations.add('walk_right', [144, 145, 146, 147, 148, 149, 150, 151], 10, true); this.animations.add('slash_up', [156, 157, 158, 159, 160, 161], 10, true); this.animations.add('slash_down', [182, 183, 184, 185, 186, 187], 10, true); this.animations.add('slash_left', [169, 170, 171, 172, 173, 174], 10, true); this.animations.add('slash_right', [195, 196, 197, 198, 199, 200], 10, true); }; /** * Method to move any `Entity` * * The parameter `direction` has to be one of 'up', 'down', 'left' or 'right'. * * Another option is to use: * * 1. UP * 2. RIGHT * 3. DOWN * 4. LEFT * * @param {string|number} direction * @param {Boolean} sprint - Whether to sprint or not */ Entity.prototype.moveInDirection = function(direction, sprint) { if (this.state !== 'attacking') { this.state = 'walking'; let speed = this.speed; let animSpeed = 10; if (sprint) { speed = this.sprintSpeed; animSpeed = 30; } this.animations.currentAnim.speed = animSpeed; let dir = ''; if (_.isString(direction) && _.includes(DIRECTIONS, direction)) { dir = direction.toLowerCase(); } else if (_.isNumber(direction) && _.inRange(direction, 0, 4)) { dir = DIRECTIONS[direction]; } else { console.error(direction); console.error('Invalid direction'); return; } switch (dir) { case 'up': this.body.velocity.y = -speed; this.body.velocity.x = 0; this.direction = 'up'; break; case 'down': this.body.velocity.y = speed; this.body.velocity.x = 0; this.direction = 'down'; break; case 'right': this.body.velocity.x = speed; this.body.velocity.y = 0; this.direction = 'right'; break; case 'left': this.body.velocity.x = -speed; this.body.velocity.y = 0; this.direction = 'left'; break; default: console.error('Invalid direction'); return; } this.animations.play('walk_' + dir, animSpeed, true); this.adjustHitbox('walk'); } }; Entity.prototype.idleHere = function() { this.state = 'idling'; this.body.velocity.x = 0; this.body.velocity.y = 0; this.animations.play('idle_' + this.direction, 1, false); this.adjustHitbox('idle'); }; Entity.prototype.attack = function() { self = this; // console.log('attacking'); this.state = 'attacking'; this.body.velocity.x = 0; this.body.velocity.y = 0; this.animations.play('slash_' + this.direction, 20, false).onComplete.add(function() { // this.animations.frame // console.log('attack finished'); self.idleHere(); }); this.adjustHitbox('slash'); }; Entity.prototype.die = function() { // const self = this; this.state = 'dead'; this.body.velocity.x = 0; this.body.velocity.y = 0; this.alive = false; // this.exists = false; this.animations.play('die', 10, false); const self = this; setTimeout(function() { self.kill(); }, 5000); }; /* * This function changes the size of the Entity's hit box based on what * action they are performing and what direction they are facing. */ Entity.prototype.adjustHitbox = function(state) { switch (state) { case ('walk'): this.body.height = h; this.body.width = w; this.body.offset.y = offy; this.body.offset.x = offx; break; case ('idle'): this.body.height = h; this.body.width = w; this.body.offset.y = offy; this.body.offset.x = offx; break; case ('slash'): switch (this.direction) { case ('up'): this.body.height = 1.5 * h; this.body.offset.y = h / 2; break; case ('down'): this.body.height = 1.5 * h; break; case ('right'): this.body.width = 1.5 * w; break; case ('left'): this.body.width = 1.5 * w; this.body.offset.x = offx - (w / 2); break; } break; } }; /** * Set the direction of the sprite * * @param {string|number} direction */ Entity.prototype.setDirection= function(direction) { if (_.isString(direction) && _.includes(DIRECTIONS, direction)) { this.direction = direction.toLowerCase(); } else if (_.isNumber(direction) && _.inRange(direction, 1, 5)) { this.direction = DIRECTIONS[direction]; } else { console.error('Invalid direction'); } }; Entity.prototype.serialize = function() { let obj = {}; obj.id = this.id; obj.x = this.x; obj.y = this.y; obj.key = this.key; obj.alive = this.alive; obj.type = this.type; obj.information = this.information; obj.reputation = this.reputation; if (this.type === 'player') { obj.score = this.score; obj.daysSurvived = this.daysSurvived; } return obj; }; Entity.prototype.deserialize = function(obj) { this.id = obj.id; this.x = obj.x; this.y = obj.y; this.key = obj.key; this.alive = obj.alive; this.type = obj.type; this.information = obj.information; this.reputation = obj.reputation; if (this.type === 'player') { this.score = obj.score; this.daysSurvived = obj.daysSurvived; } }; /** * Return the Name of the function. * This is a hack and should be used only for debugging. * * @return {string} */ Entity.prototype.toString = function() { let funcNameRegex = /function (.{1,})\(/; let results = (funcNameRegex).exec((this).constructor.toString()); return (results && results.length > 1) ? results[1] : ''; }; /** * Get the center of the Hitbox of the entity * * @return {Object} - Point with x and y */ Entity.prototype.trueXY = function() { const self = this; return { x: self.x + self.body.width/2 + self.body.offset.x, y: self.y + self.body.height/2 + self.body.offset.y, }; }; /** * * * @param {Object} rumor */ Entity.prototype.learnInfo = function(rumor) { if (!this.alive) return; if (this.information.some((e) => e.id === rumor.id)) { /** * Do nothing if we already know this information. */ return; } console.info('[' + this.id +'] Learning something new....') this.information.push(rumor); switch (rumor.action) { case 'kill': if (rumor.targetType === this.type) { /** * If the type that was killed was the same as * the current `Entity`'s type, reputation drop by * 0.1. */ this.reputation = Math.max(-1, this.reputation - 0.1); this.converse('That person sucks!'); } else if (this.dislike.includes(rumor.targetType)) { /** * If the current entity dislikes the type of entity * that was killed, * rep increases by 0.1 */ this.reputation = Math.min(1, this.reputation + 0.25); this.converse('I LOVE THAT PERSON!'); } } }; Entity.prototype.converse = function(text) { let chat = game.add.text(32, 0, text); chat.anchor.setTo(0.5); chat.font = 'Press Start 2P'; chat.fill = '#ffff00'; chat.fontSize = '1.5em'; chat.stroke = 'black'; chat.strokeThickness = '4'; chat.align = 'center'; chat.lifespan = 3000; // milliseconds this.addChild(chat); }; /** * Entity module. * @module: entity/Entity */ module.exports = Entity;