|
- import { Creature } from "./creature"
- import { TextLike } from "@/game/language"
- import {
- LogEntry,
- LogLines,
- FAElem,
- LogLine,
- FormatEntry,
- FormatOpt,
- PropElem,
- nilLog,
- Newline
- } from "@/game/interface"
- import { World } from "@/game/world"
- import { TestCategory } from "@/game/combat/tests"
- import { Container } from "@/game/vore"
- import { SoloTargeter } from "@/game/combat/targeters"
-
- export enum DamageType {
- Pierce = "Pierce",
- Slash = "Slash",
- Crush = "Crush",
- Acid = "Acid",
- Seduction = "Seduction",
- Dominance = "Dominance",
- Heal = "Heal",
- Pure = "Pure"
- }
-
- export interface DamageInstance {
- type: DamageType;
- amount: number;
- target: Vigor | Stat;
- }
-
- export enum Vigor {
- Health = "Health",
- Stamina = "Stamina",
- Resolve = "Resolve"
- }
-
- export const VigorIcons: { [key in Vigor]: string } = {
- Health: "fas fa-heart",
- Stamina: "fas fa-bolt",
- Resolve: "fas fa-brain"
- }
-
- export const VigorDescs: { [key in Vigor]: string } = {
- Health: "How much damage you can take",
- Stamina: "How much energy you have",
- Resolve: "How much dominance you can resist"
- }
-
- export type Vigors = { [key in Vigor]: number }
-
- export enum Stat {
- Toughness = "Toughness",
- Power = "Power",
- Reflexes = "Reflexes",
- Agility = "Agility",
- Willpower = "Willpower",
- Charm = "Charm"
- }
-
- export type Stats = { [key in Stat]: number }
-
- export const StatToVigor: { [key in Stat]: Vigor } = {
- Toughness: Vigor.Health,
- Power: Vigor.Health,
- Reflexes: Vigor.Stamina,
- Agility: Vigor.Stamina,
- Willpower: Vigor.Resolve,
- Charm: Vigor.Resolve
- }
-
- export const StatIcons: { [key in Stat]: string } = {
- Toughness: "fas fa-heartbeat",
- Power: "fas fa-fist-raised",
- Reflexes: "fas fa-stopwatch",
- Agility: "fas fa-feather",
- Willpower: "fas fa-book",
- Charm: "fas fa-comments"
- }
-
- export const StatDescs: { [key in Stat]: string } = {
- Toughness: "Your brute resistance",
- Power: "Your brute power",
- Reflexes: "Your ability to dodge",
- Agility: "Your ability to move quickly",
- Willpower: "Your mental resistance",
- Charm: "Your mental power"
- }
-
- export enum VoreStat {
- Mass = "Mass",
- Bulk = "Bulk",
- Prey = "Prey"
- }
-
- export type VoreStats = { [key in VoreStat]: number }
-
- export const VoreStatIcons: { [key in VoreStat]: string } = {
- [VoreStat.Mass]: "fas fa-weight",
- [VoreStat.Bulk]: "fas fa-weight-hanging",
- [VoreStat.Prey]: "fas fa-utensils"
- }
-
- export const VoreStatDescs: { [key in VoreStat]: string } = {
- [VoreStat.Mass]: "How much you weigh",
- [VoreStat.Bulk]: "Your weight, plus the weight of your prey",
- [VoreStat.Prey]: "How many creatures you've got inside of you"
- }
-
- export interface CombatTest {
- test: (user: Creature, target: Creature) => boolean;
- odds: (user: Creature, target: Creature) => number;
- explain: (user: Creature, target: Creature) => LogEntry;
- fail: (user: Creature, target: Creature) => LogEntry;
- }
-
- export interface Targeter {
- targets(primary: Creature, encounter: Encounter): Array<Creature>;
- }
-
- /**
- * An instance of damage. Contains zero or more [[DamageInstance]] objects
- */
- export class Damage {
- readonly damages: DamageInstance[]
-
- constructor (...damages: DamageInstance[]) {
- this.damages = damages
- }
-
- scale (factor: number): Damage {
- const results: Array<DamageInstance> = []
-
- this.damages.forEach(damage => {
- results.push({
- type: damage.type,
- amount: damage.amount * factor,
- target: damage.target
- })
- })
-
- return new Damage(...results)
- }
-
- // TODO make this combine damage instances when appropriate
- combine (other: Damage): Damage {
- return new Damage(...this.damages.concat(other.damages))
- }
-
- toString (): string {
- return this.damages
- .map(damage => damage.amount + " " + damage.type)
- .join("/")
- }
-
- render (): LogEntry {
- return new LogLine(
- ...this.damages.flatMap(instance => {
- if (instance.target in Vigor) {
- return [
- instance.amount.toString(),
- new FAElem(VigorIcons[instance.target as Vigor]),
- " " + instance.type
- ]
- } else if (instance.target in Stat) {
- return [
- instance.amount.toString(),
- new FAElem(StatIcons[instance.target as Stat]),
- " " + instance.type
- ]
- } else {
- // this should never happen!
- return []
- }
- })
- )
- }
-
- // TODO is there a way to do this that will satisfy the typechecker?
- renderShort (): LogEntry {
- /* eslint-disable-next-line */
- const vigorTotals: Vigors = Object.keys(Vigor).reduce((total: any, key) => {
- total[key] = 0
- return total
- }, {})
- /* eslint-disable-next-line */
- const statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => {
- total[key] = 0
- return total
- }, {})
- this.damages.forEach(instance => {
- const factor = instance.type === DamageType.Heal ? -1 : 1
- if (instance.target in Vigor) {
- vigorTotals[instance.target as Vigor] += factor * instance.amount
- } else if (instance.target in Stat) {
- statTotals[instance.target as Stat] += factor * instance.amount
- }
- })
-
- const vigorEntries = Object.keys(Vigor).flatMap(key =>
- vigorTotals[key as Vigor] === 0
- ? []
- : [new PropElem(key as Vigor, vigorTotals[key as Vigor]), " "])
- const statEntries = Object.keys(Stat).flatMap(key =>
- statTotals[key as Stat] === 0
- ? []
- : [new PropElem(key as Stat, statTotals[key as Stat]), " "])
- return new FormatEntry(
- new LogLine(...vigorEntries.concat(statEntries)),
- FormatOpt.DamageInst
- )
- }
-
- nonzero (): boolean {
- /* eslint-disable-next-line */
- const vigorTotals: Vigors = Object.keys(Vigor).reduce((total: any, key) => {
- total[key] = 0
- return total
- }, {})
- /* eslint-disable-next-line */
- const statTotals: Stats = Object.keys(Stat).reduce((total: any, key) => {
- total[key] = 0
- return total
- }, {})
- this.damages.forEach(instance => {
- const factor = instance.type === DamageType.Heal ? -1 : 1
- if (instance.target in Vigor) {
- vigorTotals[instance.target as Vigor] += factor * instance.amount
- } else if (instance.target in Stat) {
- statTotals[instance.target as Stat] += factor * instance.amount
- }
- })
- return (
- Object.values(vigorTotals).some(v => v !== 0) ||
- Object.values(statTotals).some(v => v !== 0)
- )
- }
- }
-
- /**
- * Computes damage given the source and target of the damage.
- */
- export interface DamageFormula {
- calc(user: Creature, target: Creature): Damage;
- describe(user: Creature, target: Creature): LogEntry;
- explain(user: Creature): LogEntry;
- }
-
- export class CompositeDamageFormula implements DamageFormula {
- constructor (private formulas: DamageFormula[]) {}
-
- calc (user: Creature, target: Creature): Damage {
- return this.formulas.reduce(
- (total: Damage, next: DamageFormula) =>
- total.combine(next.calc(user, target)),
- new Damage()
- )
- }
-
- describe (user: Creature, target: Creature): LogEntry {
- return new LogLines(
- ...this.formulas.map(formula => formula.describe(user, target))
- )
- }
-
- explain (user: Creature): LogEntry {
- return new LogLines(...this.formulas.map(formula => formula.explain(user)))
- }
- }
-
- /**
- * Simply returns the damage it was given.
- */
- export class ConstantDamageFormula implements DamageFormula {
- constructor (private damage: Damage) {}
-
- calc (user: Creature, target: Creature): Damage {
- return this.damage
- }
-
- describe (user: Creature, target: Creature): LogEntry {
- return this.explain(user)
- }
-
- explain (user: Creature): LogEntry {
- return new LogLine("Deal ", this.damage.renderShort())
- }
- }
-
- /**
- * Randomly scales the damage it was given with a factor of (1-x) to (1+x)
- */
- export class UniformRandomDamageFormula implements DamageFormula {
- constructor (private damage: Damage, private variance: number) {}
-
- calc (user: Creature, target: Creature): Damage {
- return this.damage.scale(
- Math.random() * this.variance * 2 - this.variance + 1
- )
- }
-
- describe (user: Creature, target: Creature): LogEntry {
- return this.explain(user)
- }
-
- explain (user: Creature): LogEntry {
- return new LogLine(
- "Deal between ",
- this.damage.scale(1 - this.variance).renderShort(),
- " and ",
- this.damage.scale(1 + this.variance).renderShort(),
- "."
- )
- }
- }
-
- /**
- * A [[DamageFormula]] that uses the attacker's stats
- */
- export class StatDamageFormula implements DamageFormula {
- constructor (
- private factors: Array<{
- stat: Stat | VoreStat;
- fraction: number;
- type: DamageType;
- target: Vigor | Stat;
- }>
- ) {}
-
- calc (user: Creature, target: Creature): Damage {
- const instances: Array<DamageInstance> = this.factors.map(factor => {
- if (factor.stat in Stat) {
- return {
- amount: factor.fraction * user.stats[factor.stat as Stat],
- target: factor.target,
- type: factor.type
- }
- } else if (factor.stat in VoreStat) {
- return {
- amount: factor.fraction * user.voreStats[factor.stat as VoreStat],
- target: factor.target,
- type: factor.type
- }
- } else {
- // should be impossible; .stat is Stat|VoreStat
- return {
- amount: 0,
- target: Vigor.Health,
- type: DamageType.Heal
- }
- }
- })
-
- return new Damage(...instances)
- }
-
- describe (user: Creature, target: Creature): LogEntry {
- return new LogLine(
- this.explain(user),
- `, for a total of `,
- this.calc(user, target).renderShort()
- )
- }
-
- explain (user: Creature): LogEntry {
- return new LogLine(
- `Deal `,
- ...this.factors
- .map(
- factor =>
- new LogLine(
- `${factor.fraction * 100}% of your `,
- new PropElem(factor.stat),
- ` as `,
- new PropElem(factor.target)
- )
- )
- .joinGeneral(new LogLine(`, `), new LogLine(` and `))
- )
- }
- }
-
- /**
- * Deals a percentage of the target's current vigors/stats
- */
- export class FractionDamageFormula implements DamageFormula {
- constructor (
- private factors: Array<{
- fraction: number;
- target: Vigor | Stat;
- type: DamageType;
- }>
- ) {}
-
- calc (user: Creature, target: Creature): Damage {
- const instances: Array<DamageInstance> = this.factors.map(factor => {
- if (factor.target in Stat) {
- return {
- amount: Math.max(
- 0,
- factor.fraction * target.stats[factor.target as Stat]
- ),
- target: factor.target,
- type: factor.type
- }
- } else if (factor.target in Vigor) {
- return {
- amount: Math.max(
- factor.fraction * target.vigors[factor.target as Vigor]
- ),
- target: factor.target,
- type: factor.type
- }
- } else {
- // should be impossible; .target is Stat|Vigor
- return {
- amount: 0,
- target: Vigor.Health,
- type: DamageType.Heal
- }
- }
- })
-
- return new Damage(...instances)
- }
-
- describe (user: Creature, target: Creature): LogEntry {
- return this.explain(user)
- }
-
- explain (user: Creature): LogEntry {
- return new LogLine(
- `Deal damage equal to `,
- ...this.factors
- .map(
- factor =>
- new LogLine(
- `${factor.fraction * 100}% of your target's `,
- new PropElem(factor.target)
- )
- )
- .joinGeneral(new LogLine(`, `), new LogLine(` and `))
- )
- }
- }
-
- export enum Side {
- Heroes,
- Monsters
- }
-
- /**
- * A Combatant has a list of possible actions to take, as well as a side.
- */
- export interface Combatant {
- actions: Array<Action>;
- side: Side;
- }
-
- /**
- * An Action is anything that can be done by a [[Creature]] to a [[Creature]].
- */
- export abstract class Action {
- constructor (
- public name: TextLike,
- public desc: TextLike,
- public conditions: Array<Condition> = [],
- public tests: Array<CombatTest> = []
- ) {}
-
- allowed (user: Creature, target: Creature): boolean {
- return this.conditions.every(cond => cond.allowed(user, target))
- }
-
- toString (): string {
- return this.name.toString()
- }
-
- try (user: Creature, targets: Array<Creature>): LogEntry {
- // Check if any pre-action effect will cancel this action.
- const preActionResults = user.effects.mapUntil(effect => effect.preAction(user), result => result.prevented)
- const preActionLogs = new LogLines(...preActionResults.map(result => result.log))
-
- if (preActionResults.some(result => result.prevented)) {
- return preActionLogs
- }
-
- // Check if any pre-receive-action effect will cancel this action.
- const preReceiveActionResults = targets.mapUntil(target => {
- const outcome = target.effects.mapUntil(effect => effect.preReceiveAction(user, target), result => result.prevented)
-
- return outcome
- }, results => results.some(result => result.prevented))
-
- console.log(preReceiveActionResults)
-
- const preReceiveActionLogs = new LogLines(...preReceiveActionResults.flatMap(
- target => target.map(result => result.log)
- ))
-
- if (preReceiveActionResults.some(results => results.some(result => result.prevented))) {
- return new LogLines(
- preActionLogs,
- preReceiveActionLogs
- )
- }
-
- const results = targets.map(target => {
- const failReason = this.tests.find(test => !test.test(user, target))
- if (failReason !== undefined) {
- return {
- failed: true,
- target: target,
- log: failReason.fail(user, target)
- }
- } else {
- return {
- failed: false,
- target: target,
- log: this.execute(user, target)
- }
- }
- })
-
- return new LogLines(
- preActionLogs,
- preReceiveActionLogs,
- ...results.map(result => result.log),
- this.executeAll(
- user,
- results.filter(result => !result.failed).map(result => result.target)
- )
- )
- }
-
- describe (user: Creature, target: Creature, verbose = true): LogEntry {
- return new LogLines(
- ...(verbose
- ? this.conditions
- .map(condition => condition.explain(user, target))
- .concat([new Newline()])
- : []),
- new LogLine(
- `Success chance: ${(this.odds(user, target) * 100).toFixed(0)}%`
- ),
- new Newline(),
- ...this.tests.map(test => test.explain(user, target))
- )
- }
-
- odds (user: Creature, target: Creature): number {
- return this.tests.reduce(
- (total, test) => total * test.odds(user, target),
- 1
- )
- }
-
- targets (primary: Creature, encounter: Encounter): Array<Creature> {
- return [primary]
- }
-
- executeAll (user: Creature, targets: Array<Creature>): LogEntry {
- return nilLog
- }
-
- abstract execute(user: Creature, target: Creature): LogEntry
- }
-
- export class CompositionAction extends Action {
- public consequences: Array<Consequence>
- public groupConsequences: Array<GroupConsequence>
- public targeters: Array<Targeter>
-
- constructor (
- name: TextLike,
- desc: TextLike,
- properties: {
- conditions?: Array<Condition>;
- consequences?: Array<Consequence>;
- groupConsequences?: Array<GroupConsequence>;
- tests?: Array<CombatTest>;
- targeters?: Array<Targeter>;
- }
- ) {
- super(name, desc, properties.conditions ?? [], properties.tests ?? [])
- this.consequences = properties.consequences ?? []
- this.groupConsequences = properties.groupConsequences ?? []
- this.targeters = properties.targeters ?? [new SoloTargeter()]
- }
-
- execute (user: Creature, target: Creature): LogEntry {
- return new LogLines(
- ...this.consequences
- .filter(consequence => consequence.applicable(user, target))
- .map(consequence => consequence.apply(user, target))
- )
- }
-
- executeAll (user: Creature, targets: Array<Creature>): LogEntry {
- return new LogLines(
- ...this.groupConsequences.map(consequence =>
- consequence.apply(
- user,
- targets.filter(target => consequence.applicable(user, target))
- ))
- )
- }
-
- describe (user: Creature, target: Creature): LogEntry {
- return new LogLines(
- ...this.consequences
- .map(consequence => consequence.describe(user, target))
- .concat(new Newline(), super.describe(user, target))
- )
- }
-
- targets (primary: Creature, encounter: Encounter) {
- return this.targeters
- .flatMap(targeter => targeter.targets(primary, encounter))
- .unique()
- }
- }
-
- /**
- * A Condition describes whether or not something is permissible between two [[Creature]]s
- */
- export interface Condition {
- allowed: (user: Creature, target: Creature) => boolean;
- explain: (user: Creature, target: Creature) => LogEntry;
- }
-
- export interface Actionable {
- actions: Array<Action>;
- }
-
- /**
- * Individual status effects, items, etc. should override some of these hooks.
- * Some hooks just produce a log entry.
- * Some hooks return results along with a log entry.
- */
- export class Effective {
- /**
- * Executes when the effect is initially applied
- */
- onApply (creature: Creature): LogEntry {
- return nilLog
- }
-
- /**
- * Executes when the effect is removed
- */
- onRemove (creature: Creature): LogEntry {
- return nilLog
- }
-
- /**
- * Executes before the creature tries to perform an action
- */
- preAction (creature: Creature): { prevented: boolean; log: LogEntry } {
- return {
- prevented: false,
- log: nilLog
- }
- }
-
- /**
- * Executes before another creature tries to perform an action that targets this creature
- */
- preReceiveAction (
- creature: Creature,
- attacker: Creature
- ): { prevented: boolean; log: LogEntry } {
- return {
- prevented: false,
- log: nilLog
- }
- }
-
- /**
- * Executes before the creature receives damage (or healing)
- */
- preDamage (creature: Creature, damage: Damage): Damage {
- return damage
- }
-
- /**
- * Executes before the creature is attacked
- */
- preAttack (
- creature: Creature,
- attacker: Creature
- ): { prevented: boolean; log: LogEntry } {
- return {
- prevented: false,
- log: nilLog
- }
- }
-
- /**
- * Executes when a creature's turn starts
- */
- preTurn (creature: Creature): { prevented: boolean; log: LogEntry } {
- return {
- prevented: false,
- log: nilLog
- }
- }
-
- /**
- * Modifies the effective resistance to a certain damage type
- */
- modResistance (type: DamageType, factor: number): number {
- return factor
- }
-
- /**
- * Called when a test is about to resolve. Decides if the creature should automatically fail.
- */
-
- failTest (
- creature: Creature,
- opponent: Creature
- ): { failed: boolean; log: LogEntry } {
- return {
- failed: false,
- log: nilLog
- }
- }
-
- /**
- * Changes a creature's size. This represents the change in *mass*
- */
- scale (scale: number): number {
- return scale
- }
-
- /**
- * Additively modifies a creature's score for an offensive test
- */
- modTestOffense (
- attacker: Creature,
- defender: Creature,
- kind: TestCategory
- ): number {
- return 0
- }
-
- /**
- * Additively modifies a creature's score for a defensive test
- */
- modTestDefense (
- defender: Creature,
- attacker: Creature,
- kind: TestCategory
- ): number {
- return 0
- }
-
- /**
- * Affects digestion damage
- */
- modDigestionDamage (
- predator: Creature,
- prey: Creature,
- container: Container,
- damage: Damage
- ): Damage {
- return damage
- }
-
- /**
- * Triggers after consumption
- */
- postConsume (
- predator: Creature,
- prey: Creature,
- container: Container
- ): LogEntry {
- return nilLog
- }
-
- /**
- * Triggers after prey enters a container
- */
- postEnter (
- predator: Creature,
- prey: Creature,
- container: Container
- ): LogEntry {
- return nilLog
- }
-
- /**
- * Affects a stat
- */
- modStat (creature: Creature, stat: Stat, current: number): number {
- return current
- }
-
- /**
- * Provides actions
- */
- actions (user: Creature): Array<Action> {
- return []
- }
- }
-
- /**
- * A displayable status effect
- */
- export interface VisibleStatus {
- name: TextLike;
- desc: TextLike;
- icon: TextLike;
- topLeft: string;
- bottomRight: string;
- }
-
- /**
- * This kind of status is never explicitly applied to an entity -- e.g., a dead entity will show
- * a status indicating that it is dead, but entities cannot be "given" the dead effect
- */
- export class ImplicitStatus implements VisibleStatus {
- topLeft = ""
- bottomRight = ""
-
- constructor (
- public name: TextLike,
- public desc: TextLike,
- public icon: string
- ) {}
- }
-
- /**
- * This kind of status is explicitly given to a creature.
- */
- export abstract class StatusEffect extends Effective implements VisibleStatus {
- constructor (
- public name: TextLike,
- public desc: TextLike,
- public icon: string
- ) {
- super()
- }
-
- get topLeft () {
- return ""
- }
-
- get bottomRight () {
- return ""
- }
- }
-
- export type EncounterDesc = {
- name: TextLike;
- intro: (world: World) => LogEntry;
- }
-
- /**
- * An Encounter describes a fight: who is in it and whose turn it is
- */
- export class Encounter {
- initiatives: Map<Creature, number>
- currentMove: Creature
- turnTime = 100
-
- constructor (public desc: EncounterDesc, public combatants: Creature[]) {
- this.initiatives = new Map()
-
- combatants.forEach(combatant => this.initiatives.set(combatant, 0))
- this.currentMove = combatants[0]
-
- this.nextMove()
- }
-
- nextMove (totalTime = 0): LogEntry {
- this.initiatives.set(this.currentMove, 0)
- const times = new Map<Creature, number>()
-
- this.combatants.forEach(combatant => {
- // this should never be undefined
- const currentProgress = this.initiatives.get(combatant) ?? 0
- const remaining =
- (this.turnTime - currentProgress) /
- Math.sqrt(Math.max(combatant.stats.Agility, 1))
- times.set(combatant, remaining)
- })
-
- this.currentMove = this.combatants.reduce((closest, next) => {
- const closestTime = times.get(closest) ?? 0
- const nextTime = times.get(next) ?? 0
-
- return closestTime <= nextTime ? closest : next
- }, this.combatants[0])
- const closestRemaining =
- (this.turnTime - (this.initiatives.get(this.currentMove) ?? 0)) /
- Math.sqrt(Math.max(this.currentMove.stats.Agility, 1))
-
- this.combatants.forEach(combatant => {
- // still not undefined...
- const currentProgress = this.initiatives.get(combatant) ?? 0
- this.initiatives.set(
- combatant,
- currentProgress +
- closestRemaining * Math.sqrt(Math.max(combatant.stats.Agility, 1))
- )
- })
-
- // TODO: still let the creature use drained-vigor moves
-
- if (this.currentMove.disabled) {
- return this.nextMove(closestRemaining + totalTime)
- } else {
- // applies digestion every time combat advances
- const tickResults = this.combatants.flatMap(combatant =>
- combatant.containers.map(container =>
- container.tick(5 * (closestRemaining + totalTime))))
- const effectResults = this.currentMove.effects
- .map(effect => effect.preTurn(this.currentMove))
- .filter(effect => effect.prevented)
-
- if (effectResults.some(result => result.prevented)) {
- const parts = effectResults
- .map(result => result.log)
- .concat([this.nextMove()])
-
- return new LogLines(...parts, ...tickResults)
- } else {
- return new LogLines(...tickResults)
- }
- }
-
- return nilLog
- }
-
- /**
- * Combat is won once one side is completely disabled
- */
- get winner (): null | Side {
- const remaining: Set<Side> = new Set(
- this.combatants
- .filter(combatant => !combatant.disabled)
- .map(combatant => combatant.side)
- )
-
- if (remaining.size === 1) {
- return Array.from(remaining)[0]
- } else {
- return null
- }
- }
-
- /**
- * Combat is completely won once one side is completely destroyed
- */
- get totalWinner (): null | Side {
- const remaining: Set<Side> = new Set(
- this.combatants
- .filter(combatant => !combatant.destroyed)
- .map(combatant => combatant.side)
- )
-
- if (remaining.size === 1) {
- return Array.from(remaining)[0]
- } else {
- return null
- }
- }
- }
-
- export abstract class Consequence {
- constructor (public conditions: Condition[]) {}
-
- applicable (user: Creature, target: Creature): boolean {
- return this.conditions.every(cond => cond.allowed(user, target))
- }
-
- abstract describe(user: Creature, target: Creature): LogEntry
- abstract apply(user: Creature, target: Creature): LogEntry
- }
-
- export abstract class GroupConsequence {
- constructor (public conditions: Condition[]) {}
-
- applicable (user: Creature, target: Creature): boolean {
- return this.conditions.every(cond => cond.allowed(user, target))
- }
-
- abstract describe(user: Creature, targets: Array<Creature>): LogEntry
- abstract apply(user: Creature, targets: Array<Creature>): LogEntry
- }
|