import {assertion} from "./Assertions"; /** The interface managing a character's Stamina and Energy. */ export interface Energy { /** The character's maximum Stamina, which they are returned to when they rest. >= 1, <= 99999. */ readonly maxStamina: number /** The character's current Stamina, which their energy returns to when they complete a battle. >= 0, <= maxStamina. */ readonly currentStamina: number /** The character's current Energy, used for special abilities. >= 0, <= currentStamina. */ readonly currentEnergy: number } /** Formats the given Energy object into a string suitable for logging. Should never throw. */ export function energyToString(energy: Energy): string { return `{Energy ${energy.currentEnergy}/${energy.currentStamina}/${energy.maxStamina}}` } /** * Returns whether the given Energy object is valid, i.e., it has currentEnergy <= currentStamina <= maxStamina, and * all three are positive integers or zero. */ export function isValidEnergy(energy: Energy): boolean { return isValidMaxStamina(energy.maxStamina) && isValidCurrentEnergy(energy.currentStamina, energy.maxStamina) && isValidCurrentEnergy(energy.currentEnergy, energy.currentStamina) } /** Returns whether the given value is valid for max stamina, i.e., it's an integer between 1 and 99999. */ export function isValidMaxStamina(maxStamina: number): boolean { return Number.isSafeInteger(maxStamina) && maxStamina >= 1 && maxStamina <= 99999 } /** Returns whether the given value is valid for current energy or stamina, i.e., it's an integer between 0 and max. */ export function isValidCurrentEnergy(value: number, max: number): boolean { return Number.isSafeInteger(value) && value >= 0 && value <= max } /** Asserts that the given Energy object is valid (see the documentation for isValidEnergy). */ export function checkValidEnergy(energy: Energy): Energy { return assertion.check(energy, isValidEnergy, (energy) => `Invalid energy: ${energyToString(energy)}`) } /** Asserts that the given value is valid for maxStamina. */ export function checkValidMaxStamina(maxStamina: number): number { return assertion.check(maxStamina, isValidMaxStamina, (maxStamina) => `Invalid max stamina: ${maxStamina}`) } /** Asserts that the given value is valid for currentStamina or currentEnergy. */ export function checkValidCurrentEnergy(value: number, max: number): number { return assertion.check(value, (value) => isValidCurrentEnergy(value, max), (value) => `Invalid current energy: ${value}`) } /** * Sets the max stamina value on the given Energy. This may result in the currentStamina (and possibly currentEnergy) * values decreasing to fit under the new ceiling. */ export function setMaxStamina(energy: Energy, maxStamina: number): Energy { checkValidEnergy(energy) checkValidMaxStamina(maxStamina) const currentStamina = Math.min(energy.currentStamina, maxStamina) const currentEnergy = Math.min(energy.currentEnergy, currentStamina) return checkValidEnergy({...energy, maxStamina, currentStamina, currentEnergy}) } /** * Deals damage to the Stamina of the given Energy. currentStamina will be decreased; currentEnergy will also be * decreased if damageEnergy is true, or if the new Stamina is lower than the Energy. Neither can drop below 0 this way. */ export function damageStamina(energy: Energy, damage: number, options: {damageEnergy: boolean}): Energy { checkValidEnergy(energy) assertion.checkPositiveIntegerOrZero(damage, () => `Invalid damage: ${damage}`) const currentStamina = Math.max(0, energy.currentStamina - damage) const currentEnergy = options.damageEnergy ? Math.max(0, energy.currentEnergy - damage) : Math.min(energy.currentEnergy, currentStamina) return checkValidEnergy({ ...energy, currentStamina, currentEnergy, }) } /** * Recovers the Stamina of the given Energy. currentStamina will be increased; currentEnergy will also be increased * if recoverEnergy is true. Neither can exceed maxStamina this way. */ export function recoverStamina(energy: Energy, recovery: number, options: {recoverEnergy: boolean}): Energy { checkValidEnergy(energy) assertion.checkPositiveIntegerOrZero(recovery, () => `Invalid healing: ${recovery}`) const currentStamina = Math.min(energy.maxStamina, energy.currentStamina + recovery) const currentEnergy = options.recoverEnergy ? energy.currentEnergy + (currentStamina - energy.currentStamina) : energy.currentEnergy return checkValidEnergy({ ...energy, currentStamina, currentEnergy, }) } /** * Sets the Stamina of the given Energy. currentEnergy will be decreased if it's lower than the new Stamina. */ export function setCurrentStamina(energy: Energy, value: number): Energy { checkValidEnergy(energy) checkValidCurrentEnergy(value, energy.maxStamina) const currentStamina = value const currentEnergy = Math.min(currentStamina, energy.currentEnergy) return checkValidEnergy({ ...energy, currentStamina, currentEnergy }) } /** * Deals damage to the Energy of the given Energy. It can't drop below 0 this way. */ export function damageEnergy(energy: Energy, damage: number): Energy { checkValidEnergy(energy) assertion.checkPositiveIntegerOrZero(damage, () => `Invalid damage: ${damage}`) const currentEnergy = Math.max(0, energy.currentEnergy - damage) return checkValidEnergy({ ...energy, currentEnergy, }) } /** * Recovers the Energy of the given Energy. It can't exceed currentStamina this way. */ export function recoverEnergy(energy: Energy, recovery: number): Energy { checkValidEnergy(energy) assertion.checkPositiveIntegerOrZero(recovery, () => `Invalid healing: ${recovery}`) const currentEnergy = Math.min(energy.currentStamina, energy.currentEnergy + recovery) return checkValidEnergy({ ...energy, currentEnergy }) } /** * Sets the current Energy of the given Energy. */ export function setCurrentEnergy(energy: Energy, value: number): Energy { checkValidEnergy(energy) checkValidCurrentEnergy(value, energy.currentStamina) return checkValidEnergy({ ...energy, currentEnergy: value }) }