|  | import { Creature, POV, Entity } from './entity'
import { POVPair, POVPairArgs } from './language'
import { Container } from './vore'
import { LogEntry, LogLines, CompositeLog, FAElem, LogLine } from './interface'
export interface CombatTest {
  test: (user: Creature, target: Creature) => boolean;
  odds: (user: Creature, target: Creature) => number;
  explain: (user: Creature, target: Creature) => LogEntry;
}
function logistic (x0: number, L: number, k: number): (x: number) => number {
  return (x: number) => {
    return L / (1 + Math.exp(-k * (x - x0)))
  }
}
abstract class RandomTest implements CombatTest {
  test (user: Creature, target: Creature): boolean {
    return Math.random() < this.odds(user, target)
  }
  abstract odds(user: Creature, target: Creature): number
  abstract explain(user: Creature, target: Creature): LogEntry
}
export class StatTest extends RandomTest {
  private f: (x: number) => number
  constructor (public readonly stat: Stat, k = 0.1) {
    super()
    this.f = logistic(0, 1, k)
  }
  odds (user: Creature, target: Creature): number {
    return this.f(user.stats[this.stat] - target.stats[this.stat])
  }
  explain (user: Creature, target: Creature): LogEntry {
    const delta: number = user.stats[this.stat] - target.stats[this.stat]
    let result: string
    if (delta === 0) {
      result = 'You and the target have the same ' + this.stat + '.'
    } else if (delta < 0) {
      result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
    } else {
      result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
    }
    result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%'
    return new LogLines(result)
  }
}
export class ChanceTest extends RandomTest {
  constructor (public readonly chance: number) {
    super()
  }
  odds (user: Creature, target: Creature): number {
    return this.chance
  }
  explain (user: Creature, target: Creature): LogEntry {
    return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
  }
}
export enum DamageType {
  Pierce = "Pierce",
  Slash = "Slash",
  Crush = "Crush",
  Acid = "Acid",
  Seduction = "Seduction",
  Dominance = "Dominance"
}
export interface DamageInstance {
  type: DamageType;
  amount: number;
  target: Vigor;
}
export enum Vigor {
  Health = "Health",
  Stamina = "Stamina",
  Willpower = "Willpower"
}
export const VigorIcons: {[key in Vigor]: string} = {
  [Vigor.Health]: "fas fa-heart",
  [Vigor.Stamina]: "fas fa-bolt",
  [Vigor.Willpower]: "fas fa-brain"
}
export type Vigors = {[key in Vigor]: number}
export enum Stat {
    STR = 'Strength',
    DEX = 'Dexterity',
    CON = 'Constitution'
}
export type Stats = {[key in Stat]: number}
export const StatIcons: {[key in Stat]: string} = {
  [Stat.STR]: 'fas fa-fist-raised',
  [Stat.DEX]: 'fas fa-feather',
  [Stat.CON]: 'fas fa-heartbeat'
}
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)
  }
  toString (): string {
    return this.damages.map(damage => damage.amount + " " + damage.type).join("/")
  }
  render (): LogEntry {
    return new LogLine(...this.damages.flatMap(instance => {
      return [instance.amount.toString(), new FAElem(VigorIcons[instance.target]), " " + instance.type]
    }))
  }
  renderShort (): LogEntry {
    const totals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {})
    this.damages.forEach(instance => {
      totals[instance.target] += instance.amount
    })
    return new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [totals[key as Vigor].toString(), new FAElem(VigorIcons[key as Vigor])]))
  }
}
export interface Combatant {
    actions: Array<Action>;
}
export abstract class Action {
  allowed (user: Creature, target: Creature): boolean {
    return this.conditions.every(cond => cond.allowed(user, target))
  }
  abstract execute(user: Creature, target: Creature): LogEntry
  constructor (public name: string, public desc: string, private conditions: Array<Condition> = []) {
  }
  toString (): string {
    return this.name
  }
}
export interface Condition {
  allowed: (user: Creature, target: Creature) => boolean;
}
class InverseCondition implements Condition {
  allowed (user: Creature, target: Creature): boolean {
    return !this.condition.allowed(user, target)
  }
  constructor (private condition: Condition) {
  }
}
class CapableCondition implements Condition {
  allowed (user: Creature, target: Creature): boolean {
    return !user.disabled
  }
}
class DrainedVigorCondition implements Condition {
  allowed (user: Creature, target: Creature): boolean {
    return user.vigors[this.vigor] <= 0
  }
  constructor (private vigor: Vigor) {
  }
}
export interface Actionable {
  actions: Array<Action>;
}
abstract class SelfAction extends Action {
  allowed (user: Creature, target: Creature) {
    if (user === target) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
}
abstract class PairAction extends Action {
  allowed (user: Creature, target: Creature) {
    if (user !== target) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
}
abstract class TogetherAction extends PairAction {
  allowed (user: Creature, target: Creature) {
    if (user.containedIn === target.containedIn) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
}
export class AttackAction extends TogetherAction {
  protected test: StatTest
  protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([
    [[POV.First, POV.Third], (user, target, args) => new LogLine(
      `You smack ${target.name} for `,
      args.damage.renderShort()
    )],
    [[POV.Third, POV.First], (user, target, args) => new LogLine(
      `${user.name.capital} smacks you for `,
      args.damage.renderShort()
    )],
    [[POV.Third, POV.Third], (user, target, args) => new LogLine(
      `${user.name.capital} smacks ${target.name} for `,
      args.damage.renderShort()
    )]
  ])
  protected failLines: POVPair<Entity, Entity> = new POVPair([
    [[POV.First, POV.Third], (user, target) => new LogLines(`You try to smack ${target.name}, but you miss`)],
    [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)],
    [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} misses ${target.name}`)]
  ])
  constructor (protected damage: Damage) {
    super('Attack', 'Attack the enemy', [new CapableCondition()])
    this.test = new StatTest(Stat.STR)
  }
  execute (user: Creature, target: Creature): LogEntry {
    if (this.test.test(user, target)) {
      target.takeDamage(this.damage)
      return this.successLines.run(user, target, { damage: this.damage })
    } else {
      return this.failLines.run(user, target)
    }
  }
}
export class DevourAction extends TogetherAction {
  private test: StatTest
  protected failLines: POVPair<Entity, Entity> = new POVPair([
    [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to make a meal out of ${target.name}`)],
    [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to devour you, but fails`)],
    [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to swallow ${target.name}`)]
  ])
  allowed (user: Creature, target: Creature): boolean {
    const owner = this.container.owner === user
    const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
    const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))
    if (owner && predOk && preyOk) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
  constructor (protected container: Container) {
    super('Devour', 'Try to consume your foe', [new CapableCondition()])
    this.name += ` (${container.name})`
    this.test = new StatTest(Stat.STR)
  }
  execute (user: Creature, target: Creature): LogEntry {
    if (this.test.test(user, target)) {
      return this.container.consume(target)
    } else {
      return this.failLines.run(user, target)
    }
  }
}
export class FeedAction extends TogetherAction {
  private test: StatTest
  protected failLines: POVPair<Entity, Entity> = new POVPair([
    [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to feed yourself to ${target.name}`)],
    [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to feed ${user.pronouns.possessive} to you, but fails`)],
    [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to feed ${user.pronouns.possessive} to ${target.name}`)]
  ])
  allowed (user: Creature, target: Creature): boolean {
    const owner = this.container.owner === target
    const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
    const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))
    if (owner && predOk && preyOk) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
  constructor (protected container: Container) {
    super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Willpower)])
    this.name += ` (${container.name})`
    this.test = new StatTest(Stat.STR)
  }
  execute (user: Creature, target: Creature): LogEntry {
    if (this.test.test(user, target)) {
      return this.container.consume(user)
    } else {
      return this.failLines.run(user, target)
    }
  }
}
export class StruggleAction extends PairAction {
  private test: StatTest
  protected failLines: POVPair<Entity, Entity> = new POVPair([
    [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to escape from ${target.name}`)],
    [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to escape from you, but fails`)],
    [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully struggles within ${target.name}`)]
  ])
  allowed (user: Creature, target: Creature) {
    if (user.containedIn === this.container) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
  constructor (public container: Container) {
    super('Struggle', 'Try to escape your predator', [new CapableCondition()])
    this.test = new StatTest(Stat.STR)
  }
  execute (user: Creature, target: Creature): LogEntry {
    if (user.containedIn !== null) {
      if (this.test.test(user, target)) {
        return user.containedIn.release(user)
      } else {
        return this.failLines.run(user, target)
      }
    } else {
      return new LogLines("Vore's bugged!")
    }
  }
}
export class DigestAction extends SelfAction {
  protected lines: POVPair<Entity, Entity> = new POVPair([])
  allowed (user: Creature, target: Creature) {
    if (this.container.owner === user && this.container.contents.length > 0) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
  constructor (protected container: Container) {
    super('Digest', 'Digest all of your current prey', [new CapableCondition()])
    this.name += ` (${container.name})`
  }
  execute (user: Creature, target: Creature): LogEntry {
    const results = this.container.tick(60)
    return new CompositeLog(results)
  }
}
export class ReleaseAction extends PairAction {
  allowed (user: Creature, target: Creature) {
    if (target.containedIn === this.container) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
  constructor (protected container: Container) {
    super('Release', 'Release one of your prey')
    this.name += ` (${container.name})`
  }
  execute (user: Creature, target: Creature): LogEntry {
    return this.container.release(target)
  }
}
export class TransferAction extends PairAction {
  protected lines: POVPair<Entity, Entity> = new POVPair([])
  allowed (user: Creature, target: Creature) {
    if (target.containedIn === this.from) {
      return super.allowed(user, target)
    } else {
      return false
    }
  }
  constructor (protected from: Container, protected to: Container) {
    super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`, [new CapableCondition()])
  }
  execute (user: Creature, target: Creature): LogEntry {
    this.from.release(target)
    this.to.consume(target)
    return this.lines.run(user, target)
  }
}
 |