UNPKG

@warriorjs/core

Version:
420 lines (377 loc) 10.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _Logger = require('./Logger'); var _Logger2 = _interopRequireDefault(_Logger); var _Space = require('./Space'); var _Space2 = _interopRequireDefault(_Space); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** Class representing a unit. */ class Unit { /** * Creates a unit. * * @param {string} name The name of the unit. * @param {string} character The character of the unit. * @param {string} color The color of the unit. * @param {number} maxHealth The max health in HP. * @param {number} reward The number of points to reward when interacting. * @param {boolean} enemy Whether the unit is an enemy or not. * @param {boolean} bound Whether the unit is bound or not. */ constructor(name, character, color, maxHealth, reward = null, enemy = true, bound = false) { this.name = name; this.character = character; this.color = color; this.maxHealth = maxHealth; this.reward = reward === null ? maxHealth : reward; this.enemy = enemy; this.bound = bound; this.position = null; this.health = maxHealth; this.score = 0; this.abilities = new Map(); this.effects = new Map(); this.turn = {}; } /** * Returns the next turn to be played. * * @returns {Object} The next turn. */ getNextTurn() { const turn = { action: null }; this.abilities.forEach((ability, name) => { if (ability.action) { // This defines a new method in the turn named after the action. When // this new method is called on the turn, the `action` property of the // turn is set to an array with the name of the action and the args // passed to the action method. Object.defineProperty(turn, name, { value: (...args) => { // If the action was already set, calling this other action will // throw as only one action can be performed per turn. if (turn.action) { throw new Error('Only one action can be performed per turn.'); } turn.action = [name, args]; } }); } else { // This defines a new method in the turn named after the sense. Whe // this new method is called on the turn, the sense is performed // immediately. Object.defineProperty(turn, name, { value: (...args) => ability.perform(...args) }); } }); return turn; } /** * Prepares the next turn to be played. * * This is when methods of the turn instance that correspond to abilities are * executed. The senses will be executed immediately, whereas the action is * stored in a property of the turn instance for deferred execution (when * performing the turn later). */ prepareTurn() { this.turn = this.getNextTurn(); this.playTurn(this.turn); } /** * Performs the prepared turn. */ performTurn() { if (this.isAlive()) { this.effects.forEach(effect => effect.passTurn()); if (this.turn.action && !this.isBound()) { const [name, args] = this.turn.action; this.abilities.get(name).perform(...args); } } } /** * Adds the given amount of health in HP. * * @param {number} amount The amount of HP to add. */ heal(amount) { const revisedAmount = this.health + amount > this.maxHealth ? this.maxHealth - this.health : amount; this.health += revisedAmount; this.log(`receives ${amount} health, up to ${this.health} health`); } /** * Subtracts the given amount of health in HP. * * @param {number} amount The amount of HP to subtract. */ takeDamage(amount) { if (this.isBound()) { this.unbind(); } const revisedAmount = this.health - amount < 0 ? this.health : amount; this.health -= revisedAmount; this.log(`takes ${amount} damage, ${this.health} health power left`); if (this.health === 0) { this.vanish(); this.log('dies'); } } /** * Damages another unit. * * @param {Unit} receiver The unit to damage. * @param {number} amount The amount of HP to inflict. */ damage(receiver, amount) { receiver.takeDamage(amount); if (!receiver.isAlive()) { if (receiver.as(this).isEnemy()) { this.earnPoints(receiver.reward); } else { this.losePoints(receiver.reward); } } } /** * Checks if the unit is alive. * * A unit is alive if it has a position. * * @returns {boolean} Whether the unit is alive or not. */ isAlive() { return this.position !== null; } /** * Releases (unbinds) another unit. * * @param {Unit} receiver The unit to release. */ release(receiver) { if (!receiver.as(this).isEnemy()) { receiver.vanish(); } receiver.unbind(); if (!receiver.isAlive()) { this.earnPoints(receiver.reward); } } /** * Unbinds the unit. */ unbind() { this.bound = false; this.log('released from bonds'); } /** * Binds the unit. */ bind() { this.bound = true; } /** * Checks if the unit is bound. * * @returns {boolean} Whether the unit is bound or not. */ isBound() { return this.bound; } /** * Adds the given points to the score. * * @param {number} points The points to earn. */ earnPoints(points) { this.score += points; } /** * Subtracts the given points from the score. * * @param {number} points The points to lose. */ losePoints(points) { this.score -= points; } /** * Adds an ability to the unit. * * @param {string} name The name of the ability. * @param {Object} ability The ability to add. */ addAbility(name, ability) { this.abilities.set(name, ability); } /** * Adds an effect to the unit. * * @param {string} name The name of the effect. * @param {Object} effect The effect to add. */ addEffect(name, effect) { this.effects.set(name, effect); } /** * Triggers the given effect. * * @param {string} name The name of the effect. */ triggerEffect(name) { const effect = this.effects.get(name); if (effect) { effect.trigger(); } } /** * Checks if the unit is under the given effect. * * @param {string} name The name of the effect. * * @returns {boolean} Whether the unit is under the effect or not. */ isUnderEffect(name) { return this.effects.has(name); } /** * Returns the units in the floor minus this unit. * * @returns {Unit[]} The other units in the floor. */ getOtherUnits() { return this.position.floor.getUnits().filter(unit => unit !== this); } /** * Returns the space where this unit is located. * * @returns {Space} The space this unit is located at. */ getSpace() { return this.position.getSpace(); } /** * Returns the sensed space located at the direction and number of spaces. * * @param {string} direction The direction. * @param {number} forward The number of spaces forward. * @param {number} right The number of spaces to the right. * * @returns {SensedSpace} The sensed space. */ getSensedSpaceAt(direction, forward = 1, right = 0) { return this.getSpaceAt(direction, forward, right).as(this); } /** * Returns the space located at the direction and number of spaces. * * @param {string} direction The direction. * @param {number} forward The number of spaces forward. * @param {number} right The number of spaces to the right. * * @returns {Space} The space. */ getSpaceAt(direction, forward = 1, right = 0) { return this.position.getRelativeSpace(direction, [forward, right]); } /** * Returns the direction of the stairs with reference to this unit. * * @returns {string} The relative direction of the stairs. */ getDirectionOfStairs() { return this.position.getRelativeDirectionOf(this.position.floor.getStairsSpace()); } /** * Returns the direction of the given space, with reference to this unit. * * @param {SensedSpace} sensedSpace The space to get the direction of. * * @returns {string} The relative direction of the space. */ getDirectionOf(sensedSpace) { const space = _Space2.default.from(sensedSpace, this); return this.position.getRelativeDirectionOf(space); } /** * Returns the distance between the given space and this unit. * * @param {SensedSpace} sensedSpace The space to calculate the distance of. * * @returns {number} The distance of the space. */ getDistanceOf(sensedSpace) { const space = _Space2.default.from(sensedSpace, this); return this.position.getDistanceOf(space); } /** * Moves the unit in the given direction and number of spaces. * * @param {string} direction The direction. * @param {number} forward The number of spaces forward. * @param {number} right The number of spaces to the right. */ move(direction, forward = 1, right = 0) { this.position.move(direction, [forward, right]); } /** * Rotates the unit in a given direction. * * @param {string} direction The direction in which to rotate. */ rotate(direction) { this.position.rotate(direction); } /** * Vanishes the unit from the floor. */ vanish() { this.position = null; } /** * Logs a message with the accompanying unit. * * @param {string} message The message to log. */ log(message) { _Logger2.default.unit(this, message); } /** * Returns this unit as sensed by the given unit. * * @param {Unit} unit The unit sensing this unit. * * @returns {SensedUnit} The sensed unit. */ as(unit) { return { isBound: this.isBound.bind(this), isEnemy: () => this.enemy !== unit.enemy, isUnderEffect: this.isUnderEffect.bind(this) }; } /** * Returns the string representation of this unit. * * @returns {string} The string representation. */ toString() { return this.name; } /** * Customizes the JSON stringification behavior of the unit. * * @returns {Object} The value to be serialized. */ toJSON() { return { name: this.name, color: this.color, maxHealth: this.maxHealth }; } } exports.default = Unit; module.exports = exports.default;