app/abilities/Ability.js
import Dungeon from '../dungeons/Dungeon.js';
import Creature from '../entities/creatures/Creature.js';
import Tile from '../tiles/Tile.js';
/**
* @abstract
* @desc Represents a non-attack action that a creature can perform
*/
export default class Ability {
/**
* @desc Gives a reason the ability cannot be performed by the given
* creature, if any exists.
* @param {Dungeon} dungeon - The dungeon the creature is in
* @param {Creature} creature - The creature performing the ability
* @param {Tile} [optionalTargetTile] - The tile targetted by the ability, if required
* @param {boolean} [isFree] - If true, will not check whether mana cost is met
* @return {String} - The reason the ability cannot be performed, or null if none
* exists.
*/
getReasonIllegal(dungeon, creature, optionalTargetTile, isFree) {
if(!(dungeon instanceof Dungeon)) {
return 'No dungeon specified';
} else if(!(creature instanceof Creature)) {
return 'No creature specified';
} else if(!isFree && this.getManaCost() > creature.getCurrentMana()) {
return 'Not enough mana';
} else if(this.isTargetted()) {
if(!(optionalTargetTile instanceof Tile)) {
return 'This ability requires a target tile';
} else if(this.mustTargetBeVisible() && !creature.canSee(dungeon, optionalTargetTile)) {
return 'Tile not visible';
} else if(this.isTargetCreature() && !optionalTargetTile.getCreature()) {
return 'Target tile has no creature';
} else if(dungeon.getTile(creature).getEuclideanDistance(optionalTargetTile) > this.getRange()) {
return 'Target not in range';
} else if(!this.canTargetSelf() && optionalTargetTile === dungeon.getTile(creature)) {
return 'Not a self-target ability';
}
} else {
return null;
}
}
/**
* @desc Causes the given creature to perform the ability, modifying the game state
* @param {Dungeon} dungeon - The dungeon the creature is in
* @param {Creature} creature - The creature performing the ability
* @param {Tile} [optionalTargetTile] - The tile targetted by the ability, if required
* @param {boolean} [isFree] - If true, will not check whether mana cost is met
*/
use(dungeon, creature, optionalTargetTile, isFree) {
var reason = this.getReasonIllegal(dungeon, creature, optionalTargetTile, isFree);
if(reason) {
throw new Error(reason);
}
if(!isFree) {
creature.modifyMana(-this.getManaCost());
}
}
/**
* @abstract
* @desc Tells whether the ability targets a tile.
* @return {boolean} - `true` if the ability must target a tile; `false` otherwise.
*/
isTargetted() {
throw new Error('Abstract method not implemented');
}
/**
* @abstract
* @desc For a targetted ability, tells whether the ability requires
* the target tile to contain a creature. This is not called if the ability
* doesn't target.
* @return {boolean} - `true` if the ability must target a creature; `false` otherwise.
*/
isTargetCreature() {
throw new Error('Abstract method not implemented');
}
/**
* @desc Tells whether the ability can cause the creature to move. creatures
* will not be able to use a movement ability if their movement is impaired.
* @return {boolean} - `true` if the ability can cause the user to move; `false` otherwise.
*/
isMovementAbility() {
return false;
}
/**
* @desc For targetted abilities, tells whether or not the target must
* be visible to the creature.
* @return {boolean} - `true` if the ability must target a visible tile; `false` otherwise.
*/
mustTargetBeVisible() {
return true;
}
/**
* @desc For targetted abilities, tells how far away the ability can
* target. 0 means that the ability can only self-target.
* @return {number} - A number indicating how far the ability can target
*/
getRange() {
return 0;
}
/**
* @desc For targetted abilities, tells whether or not the target
* can be the tile the creature is standing on.
* @return {boolean} - `true` if the ability may target the user; `false` otherwise.
*/
canTargetSelf() {
return true;
}
/**
* @abstract
* @desc Gets the amount of mana deducted from the user upon use. If
* this amount exceeds the creature's available mana, the ability cannot be
* used normally.
* @return {number} - The mana required to use the ability
*/
getManaCost() {
throw new Error('Abstract method not implemented');
}
/**
* @desc Gets a formatted name for the ability
* @return {String} - A human-readable name
*/
getName() {
return this.constructor.name.replace(/([^A-Z])([A-Z])/g, '$1 $2');
}
/**
* @abstract
* @desc A short description of the ability
* @return {String} - A human-readable description of the ability's effects
*/
getDescription() {
throw new Error('Abstract method not implemented');
}
/**
* @desc Generic toString method
* @return {String} - A string for debug purposes
*/
toString() {
return this.constructor.name;
}
}