|  | import { Damage, Stats, Action, Vigor, Side, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective, VoreStat, VoreStats, DamageInstance, Stat, Vigors, Encounter } from '@/game/combat'
import { Noun, Pronoun, SoloLine, Verb } from '@/game/language'
import { LogEntry, LogLines, LogLine } from '@/game/interface'
import { VoreType, Container } from '@/game/vore'
import { Item, EquipmentSlot, Equipment, ItemKind, Currency } from '@/game/items'
import { PassAction } from '@/game/combat/actions'
import { AI, RandomAI } from '@/game/ai'
import { Entity, Resistances } from '@/game/entity'
import { Perk } from '@/game/combat/perks'
import { VoreRelay } from '@/game/events'
export class Creature extends Entity {
  voreRelay: VoreRelay = new VoreRelay()
  baseResistances: Resistances
  stats: Stats = (Object.keys(Stat) as Array<Stat>).reduce((result: Partial<Stats>, stat: Stat) => {
    Object.defineProperty(result, stat, {
      get: () => this.effects.reduce((total, effect) => effect.modStat(this, stat, total), this.baseStats[stat]),
      set: (value: number) => { this.baseStats[stat] = value },
      enumerable: true
    })
    return result
  }, {}) as Stats
  vigors: {[key in Vigor]: number} = {
    [Vigor.Health]: 100,
    [Vigor.Stamina]: 100,
    [Vigor.Resolve]: 100
  }
  destroyed = false;
  containers: Array<Container> = []
  containedIn: Container | null = null
  voreStats: VoreStats
  actions: Array<Action> = [];
  desc = "Some creature";
  get effects (): Array<Effective> {
    const effects: Array<Effective> = (this.statusEffects as Effective[]).concat(
      Object.values(this.equipment).filter(item => item !== undefined).flatMap(
        item => (item as Equipment).effects
      ),
      this.perks
    )
    return effects
  }
  statusEffects: Array<StatusEffect> = [];
  perks: Array<Perk> = [];
  items: Array<Item> = [];
  /* eslint-disable-next-line */
  wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {});
  otherActions: Array<Action> = [];
  side: Side;
  title = "Lv. 1 Creature";
  equipment: {[key in EquipmentSlot]?: Equipment } = {}
  ai: AI
  constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, private baseMass: number) {
    super(name, kind, pronouns)
    /* eslint-disable-next-line */
    this.baseResistances = Object.keys(DamageType).reduce((resist: any, key) => { resist[key] = 1; return resist }, {})
    Object.entries(this.maxVigors).forEach(([key, val]) => {
      this.vigors[key as Vigor] = val
    })
    this.actions.push(new PassAction())
    this.side = Side.Heroes
    /* eslint-disable-next-line */
    const self = this
    this.ai = new RandomAI(this)
    this.voreStats = {
      get [VoreStat.Bulk] () {
        return self.containers.reduce(
          (total: number, container: Container) => {
            return total + container.contents.reduce(
              (total: number, prey: Creature) => {
                return total + prey.voreStats.Bulk
              },
              0
            ) + container.digested.reduce(
              (total: number, prey: Creature) => {
                return total + prey.voreStats.Bulk
              },
              0
            )
          },
          self.voreStats.Mass
        )
      },
      get [VoreStat.Mass] () {
        const base = self.baseMass
        const adjusted = self.effects.reduce((scale: number, effect: Effective) => effect.scale(scale), base)
        return adjusted
      },
      // we want to account for anything changing our current size;
      // we will assume that the modifiers are all multiplicative
      set [VoreStat.Mass] (mass: number) {
        const modifier = self.effects.reduce((scale: number, effect: Effective) => effect.scale(scale), 1)
        const adjusted = mass / modifier
        self.baseMass = adjusted
      },
      get [VoreStat.Prey] () {
        return self.containers.reduce(
          (total: number, container: Container) => {
            return total + container.contents.concat(container.digested).reduce(
              (total: number, prey: Creature) => {
                return total + 1 + prey.voreStats[VoreStat.Prey]
              },
              0
            )
          },
          0
        )
      }
    }
  }
  resistanceTo (damageType: DamageType): number {
    return this.baseResistances[damageType]
  }
  get maxVigors (): Readonly<Vigors> {
    return {
      Health: this.stats.Toughness * 10 + this.stats.Power * 5,
      Resolve: this.stats.Willpower * 10 + this.stats.Charm * 5,
      Stamina: this.stats.Agility * 5 + this.stats.Reflexes * 5
    }
  }
  get disabled (): boolean {
    return Object.values(this.vigors).some(val => val <= 0)
  }
  effectiveDamage (damage: Damage): Damage {
    const newDamages: DamageInstance[] = []
    damage.damages.forEach(instance => {
      const factor = instance.type === DamageType.Heal ? -1 : 1
      const baseResistance: number = this.resistanceTo(instance.type)
      const resistance = baseResistance * factor
      newDamages.push({
        amount: instance.amount * resistance,
        target: instance.target,
        type: instance.type
      })
    })
    return new Damage(...newDamages)
  }
  takeDamage (damage: Damage): LogEntry {
    // first, we record health to decide if the entity just died
    const startHealth = this.vigors.Health
    damage = this.effectiveDamage(damage)
    damage.damages.forEach(instance => {
      if (instance.target in Vigor) {
        // just deal damage
        this.vigors[instance.target as Vigor] -= instance.amount
      } else if (instance.target in Stat) {
        // drain the stats, then deal damage to match
        const startVigors = this.maxVigors
        this.stats[instance.target as Stat] -= instance.amount
        const endVigors = this.maxVigors
        Object.keys(Vigor).map(vigor => {
          this.vigors[vigor as Vigor] -= startVigors[vigor as Vigor] - endVigors[vigor as Vigor]
        })
      }
    })
    Object.keys(Vigor).forEach(vigorStr => {
      const vigor = vigorStr as Vigor
      if (this.vigors[vigor] > this.maxVigors[vigor]) {
        this.vigors[vigor] = this.maxVigors[vigor]
      }
    })
    if (this.vigors.Health <= -this.maxVigors.Health) {
      this.destroyed = true
    }
    if (this.vigors.Health <= 0 && startHealth > 0) {
      return this.destroy()
    } else {
      return new LogLine()
    }
  }
  toString (): string {
    return this.name.toString()
  }
  applyEffect (effect: StatusEffect): LogEntry {
    this.statusEffects.push(effect)
    return effect.onApply(this)
  }
  addContainer (container: Container): void {
    this.containers.push(container)
    this.voreRelay.connect(container.voreRelay)
  }
  addPerk (perk: Perk): void {
    this.perks.push(perk)
  }
  // TODO replace the logic for getting blocked or prevented from acting
  executeAction (action: Action, targets: Array<Creature>): LogEntry {
    return action.try(this, targets)
  }
  removeEffect (effect: StatusEffect): LogEntry {
    this.statusEffects = this.statusEffects.filter(eff => eff !== effect)
    return effect.onRemove(this)
  }
  equip (item: Equipment, slot: EquipmentSlot) {
    const equipped = this.equipment[slot]
    if (equipped !== undefined) {
      this.unequip(slot)
    }
    this.equipment[slot] = item
  }
  unequip (slot: EquipmentSlot) {
    const item = this.equipment[slot]
    if (item !== undefined) {
      this.items.push(item)
      this.equipment[slot] = undefined
    }
  }
  get status (): Array<VisibleStatus> {
    const results: Array<VisibleStatus> = []
    if (this.vigors[Vigor.Health] <= 0) {
      results.push(new ImplicitStatus('Dead', 'Out of health', 'fas fa-heart'))
    }
    if (this.vigors[Vigor.Stamina] <= 0) {
      results.push(new ImplicitStatus('Unconscious', 'Out of stamina', 'fas fa-bolt'))
    }
    if (this.vigors[Vigor.Resolve] <= 0) {
      results.push(new ImplicitStatus('Broken', 'Out of resolve', 'fas fa-brain'))
    }
    if (this.containedIn !== null) {
      results.push(new ImplicitStatus('Eaten', 'Devoured by ' + this.containedIn.owner.name, 'fas fa-drumstick-bite'))
    }
    this.statusEffects.forEach(effect => {
      results.push(effect)
    })
    return results
  }
  allActions (target: Creature): Array<Action> {
    let choices = ([] as Action[]).concat(
      this.actions,
      this.containers.flatMap(container => container.actions),
      target.otherActions,
      Object.values(this.equipment).filter(item => item !== undefined).flatMap(item => (item as Equipment).actions),
      this.items.filter(item => item.kind === ItemKind.Consumable && !item.consumed).flatMap(item => item.actions),
      this.perks.flatMap(perk => perk.actions(this))
    )
    if (this.containedIn !== null) {
      choices = choices.concat(this.containedIn.actions)
    }
    return choices
  }
  validActions (target: Creature): Array<Action> {
    return this.allActions(target).filter(action => {
      return action.allowed(this, target)
    })
  }
  validSoloActions (target: Creature, encounter: Encounter): Array<Action> {
    return this.validActions(target).filter(action => action.targets(target, encounter).length === 1)
  }
  validGroupActions (target: Creature, encounter: Encounter): Array<Action> {
    return this.validActions(target).filter(action => action.targets(target, encounter).length > 1)
  }
  destroyLine: SoloLine<Creature> = (victim) => new LogLine(
    `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}`
  )
  destroy (): LogEntry {
    const released: Array<Creature> = this.containers.flatMap(container => {
      return container.contents.map(prey => {
        prey.containedIn = this.containedIn
        if (this.containedIn !== null) {
          this.containedIn.contents.push(prey)
        }
        return prey
      })
    })
    const names = released.reduce((list: Array<string>, prey: Creature) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("")
    if (released.length > 0) {
      if (this.containedIn === null) {
        return new LogLines(
          this.destroyLine(this),
          new LogLine(names + ` spill out!`)
        )
      } else {
        return new LogLines(
          this.destroyLine(this),
          new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`)
        )
      }
    } else {
      return this.destroyLine(this)
    }
  }
}
 |