|  | import { Mortal } from './entity'
import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat, DamageInstance } from './combat'
import { LogLines, LogEntry, LogLine, nilLog } from './interface'
import { Noun, Pronoun, ImproperNoun, TextLike, Verb, SecondPersonPronouns, PronounAsNoun, FirstPersonPronouns, PairLineArgs, SoloLine, POV, RandomWord } from './language'
import { DigestAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from './combat/actions'
import * as Words from './words'
export enum VoreType {
  Oral = "Oral Vore",
  Anal = "Anal Vore",
  Cock = "Cock Vore",
  Unbirth = "Unbirthing"
}
export const anyVore = new Set([
  VoreType.Oral,
  VoreType.Anal,
  VoreType.Cock,
  VoreType.Unbirth
])
export abstract class Vore extends Mortal {
  containers: Array<VoreContainer> = []
  otherContainers: Array<Container> = []
  containedIn: Container | null = null
  voreStats: VoreStats
  constructor (name: Noun, kind: Noun, pronouns: Pronoun, baseStats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, private baseMass: number) {
    super(name, kind, pronouns, baseStats)
    const containers = this.containers
    // we can't use arrow notation for getters, so we gotta do this
    // eslint-disable-next-line
    const self = this
    this.voreStats = {
      get [VoreStat.Bulk] () {
        return containers.reduce(
          (total: number, container: VoreContainer) => {
            return total + container.contents.reduce(
              (total: number, prey: Vore) => {
                return total + prey.voreStats.Bulk
              },
              0
            ) + container.digested.reduce(
              (total: number, prey: Vore) => {
                return total + prey.voreStats.Bulk
              },
              0
            )
          },
          this.Mass
        )
      },
      get [VoreStat.Mass] () {
        return self.baseMass
      },
      set [VoreStat.Mass] (mass: number) {
        self.baseMass = mass
      },
      get [VoreStat.PreyCount] () {
        return containers.reduce(
          (total: number, container: VoreContainer) => {
            return total + container.contents.concat(container.digested).reduce(
              (total: number, prey: Vore) => {
                return total + 1 + prey.voreStats[VoreStat.PreyCount]
              },
              0
            )
          },
          0
        )
      }
    }
  }
  destroy (): LogEntry {
    const line: SoloLine<Vore> = (victim) => new LogLine(
      `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}`
    )
    const released: Array<Vore> = 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: Vore) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("")
    if (released.length > 0) {
      if (this.containedIn === null) {
        return new LogLines(
          line(this),
          new LogLine(names + ` spill out!`)
        )
      } else {
        return new LogLines(
          line(this),
          new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`)
        )
      }
    } else {
      return line(this)
    }
  }
}
export interface Container extends Actionable {
  name: Noun;
  owner: Vore;
  voreTypes: Set<VoreType>;
  capacity: number;
  fullness: number;
  contents: Array<Vore>;
  describe: () => LogEntry;
  canTake: (prey: Vore) => boolean;
  consume: (prey: Vore) => LogEntry;
  release: (prey: Vore) => LogEntry;
  struggle: (prey: Vore) => LogEntry;
  consumeVerb: Verb;
  releaseVerb: Verb;
  struggleVerb: Verb;
  consumeLine: PairLineArgs<Vore, { container: Container }>;
}
export abstract class NormalContainer implements Container {
  public name: Noun
  contents: Array<Vore> = []
  actions: Array<Action> = []
  consumeVerb = new Verb('trap')
  releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  constructor (name: Noun, public owner: Vore, public voreTypes: Set<VoreType>, public capacity: number) {
    this.name = name.all
    this.actions.push(new DevourAction(this))
    this.actions.push(new ReleaseAction(this))
    this.actions.push(new StruggleAction(this))
  }
  consumeLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  }
  releaseLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(this.releaseVerb)} ${target.name.objective} up from ${user.pronouns.possessive} ${args.container.name}.`)
  }
  struggleLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(this.struggleVerb)} within ${target.name.possessive} ${args.container.name}.`)
  }
  get fullness (): number {
    return Array.from(this.contents.values()).reduce((total: number, prey: Vore) => total + prey.voreStats.Bulk, 0)
  }
  canTake (prey: Vore): boolean {
    const fits = this.capacity - this.fullness >= prey.voreStats.Bulk
    const permitted = Array.from(this.voreTypes).every(voreType => {
      return prey.preyPrefs.has(voreType)
    })
    return fits && permitted
  }
  consume (prey: Vore): LogEntry {
    if (prey.containedIn !== null) {
      prey.containedIn.contents = prey.containedIn.contents.filter(item => prey !== item)
    }
    this.contents.push(prey)
    prey.containedIn = this
    return this.consumeLine(this.owner, prey, { container: this })
  }
  release (prey: Vore): LogEntry {
    prey.containedIn = this.owner.containedIn
    this.contents = this.contents.filter(victim => victim !== prey)
    if (this.owner.containedIn !== null) {
      this.owner.containedIn.contents.push(prey)
    }
    return this.releaseLine(this.owner, prey, { container: this })
  }
  struggle (prey: Vore): LogEntry {
    return this.struggleLine(prey, this.owner, { container: this })
  }
  describe (): LogEntry {
    const lines: Array<string> = []
    this.contents.forEach(prey => {
      lines.push(prey.toString())
    })
    return new LogLine(...lines)
  }
}
export abstract class InnerContainer extends NormalContainer {
  constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
    super(name, owner, voreTypes, capacity)
    this.actions = []
    this.actions.push(new StruggleAction(this))
  }
  release (prey: Vore): LogEntry {
    prey.containedIn = this.escape
    this.contents = this.contents.filter(victim => victim !== prey)
    return this.releaseLine(this.owner, prey, { container: this })
  }
}
export interface VoreContainer extends Container {
  digested: Array<Vore>;
  tick: (dt: number) => LogEntry;
  digest: (preys: Vore[]) => LogEntry;
  absorb: (preys: Vore[]) => LogEntry;
  fluidColor: string;
  onDigest: (prey: Vore) => LogEntry;
  onAbsorb: (prey: Vore) => LogEntry;
}
export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer {
  consumeVerb = new Verb('devour')
  releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  fluidColor = "#00ff0088"
  digested: Array<Vore> = []
  absorbed: Array<Vore> = []
  constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) {
    super(name, owner, voreTypes, capacity)
    this.name = name
    this.actions.push(new DigestAction(this))
  }
  get fullness (): number {
    return Array.from(this.contents.concat(this.digested, this.absorbed).values()).reduce((total: number, prey: Vore) => total + prey.voreStats.Bulk, 0)
  }
  consumeLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective}, forcing ${target.pronouns.objective} into ${user.pronouns.possessive} ${args.container.name}.`)
  }
  tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(Words.Churns)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  }
  digestLine: PairLineArgs<Vore, { container: VoreContainer }> = (user, target, args) => {
    return new LogLine(`${user.name.capital.possessive} ${args.container.name} ${args.container.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Digests.present} ${target.name.objective} down, ${target.pronouns.possessive} ${Words.Struggles.singular} fading away.`)
  }
  absorbLine: PairLineArgs<Vore, { container: VoreContainer }> = (user, target, args) => {
    return new LogLine(`${user.name.capital.possessive} ${args.container.name} ${args.container.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Absorbs.present} ${target.name.objective}, fully claiming ${target.pronouns.objective}.`)
  }
  tick (dt: number): LogEntry {
    const justDigested: Array<Vore> = []
    const justAbsorbed: Array<Vore> = []
    const scaled = this.damage.scale(dt / 60)
    const damageResults: Array<LogEntry> = []
    const tickedEntries = new LogLines(...this.contents.map(prey => this.tickLine(this.owner, prey, { container: this, damage: scaled })))
    this.contents.forEach(prey => {
      damageResults.push(prey.takeDamage(scaled))
      if (prey.vigors[Vigor.Health] <= 0) {
        prey.destroyed = true
        this.digested.push(prey)
        justDigested.push(prey)
        damageResults.push(this.onDigest(prey))
      }
    })
    this.digested.forEach(prey => {
      const damageTotal: number = prey.effectiveDamage(scaled).damages.filter(instance => instance.target === Vigor.Health).reduce(
        (total: number, instance: DamageInstance) => total + instance.amount,
        0
      )
      const massStolen = Math.min(damageTotal / 100, prey.voreStats.Mass)
      prey.voreStats.Mass -= massStolen
      this.owner.voreStats.Mass += massStolen
      if (prey.voreStats.Mass === 0) {
        this.absorbed.push(prey)
        justAbsorbed.push(prey)
        damageResults.push(this.onAbsorb(prey))
      }
    })
    const digestedEntries = this.digest(justDigested)
    const absorbedEntries = this.absorb(justAbsorbed)
    this.contents = this.contents.filter(prey => {
      return prey.vigors[Vigor.Health] > 0
    })
    this.digested = this.digested.filter(prey => {
      return prey.voreStats.Mass > 0
    })
    return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries, absorbedEntries)
  }
  absorb (preys: Vore[]): LogEntry {
    return new LogLines(...preys.map(prey => this.absorbLine(this.owner, prey, { container: this })))
  }
  digest (preys: Vore[]): LogEntry {
    return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey, { container: this })))
  }
  onAbsorb (prey: Vore): LogEntry {
    return nilLog
  }
  onDigest (prey: Vore): LogEntry {
    return nilLog
  }
}
export abstract class InnerVoreContainer extends NormalVoreContainer {
  constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: Container) {
    super(name, owner, voreTypes, capacity, damage)
    this.actions = []
    this.actions.push(new DigestAction(this))
    this.actions.push(new StruggleAction(this))
  }
  release (prey: Vore): LogEntry {
    prey.containedIn = this.escape
    this.contents = this.contents.filter(victim => victim !== prey)
    this.escape.consume(prey)
    return this.releaseLine(this.owner, prey, { container: this })
  }
}
export class Stomach extends NormalVoreContainer {
  constructor (owner: Vore, capacity: number, damage: Damage) {
    super(new ImproperNoun('stomach', 'stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage)
  }
  digest (preys: Vore[]): LogEntry {
    if (preys.length === 0) {
      return super.digest(preys)
    }
    const heal = new Damage(
      {
        amount: preys.reduce((total: number, next: Vore) => total + next.maxVigors.Health / 5, 0),
        type: DamageType.Heal,
        target: Vigor.Health
      }
    )
    this.owner.takeDamage(heal)
    return new LogLines(
      super.digest(preys),
      new LogLine(`${this.owner.name.capital} heals for `, this.owner.effectiveDamage(heal).renderShort())
    )
  }
}
export class InnerStomach extends InnerVoreContainer {
  consumeVerb = new Verb('swallow')
  releaseVerb = new Verb('hork')
  constructor (owner: Vore, capacity: number, damage: Damage, escape: VoreContainer) {
    super(new ImproperNoun('inner stomach', 'inner stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage, escape)
  }
}
export class Bowels extends NormalVoreContainer {
  constructor (owner: Vore, capacity: number, damage: Damage) {
    super(new ImproperNoun('bowel', 'bowels').plural.all, owner, new Set([VoreType.Anal]), capacity, damage)
  }
  tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(
      new RandomWord([
        new Verb('crush', 'crushes'),
        new Verb('clench', 'clenches')
      ])
    )} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  }
}
export class Cock extends NormalVoreContainer {
  fluidColor = "#eeeeee66";
  constructor (owner: Vore, capacity: number, damage: Damage) {
    super(
      new ImproperNoun('cock').all,
      owner,
      new Set([VoreType.Cock]),
      capacity,
      damage
    )
  }
  tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
    return new LogLine(`${user.name.capital} ${user.name.conjugate(
      new RandomWord([
        new Verb('crush', 'crushes'),
        new Verb('clench', 'clenches')
      ])
    )} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  }
}
export class Balls extends InnerVoreContainer {
  fluidColor = "#eeeeeecc";
  constructor (owner: Vore, capacity: number, damage: Damage, escape: Container) {
    super(
      new ImproperNoun('ball', 'balls').all.plural,
      owner,
      new Set([VoreType.Cock]),
      capacity,
      damage,
      escape
    )
  }
}
export class Slit extends NormalVoreContainer {
  fluidColor = "#cccccc99";
  constructor (owner: Vore, capacity: number, damage: Damage) {
    super(
      new ImproperNoun('slit').all,
      owner,
      new Set([VoreType.Unbirth]),
      capacity,
      damage
    )
  }
}
export class Womb extends InnerVoreContainer {
  fluidColor = "#ddddddbb";
  constructor (owner: Vore, capacity: number, damage: Damage, escape: Container) {
    super(
      new ImproperNoun('womb').all,
      owner,
      new Set([VoreType.Unbirth]),
      capacity,
      damage,
      escape
    )
  }
}
export function biconnectContainers (outer: VoreContainer, inner: VoreContainer): void {
  outer.onDigest = (prey: Vore) => {
    outer.digested = outer.digested.filter(victim => victim !== prey)
    inner.digested.push(prey)
    return inner.consumeLine(inner.owner, prey, { container: inner })
  }
  outer.actions.push(
    new TransferAction(
      outer,
      inner
    )
  )
  inner.actions.push(
    new TransferAction(
      inner,
      outer
    )
  )
}
 |