import { Mortal } from './entity' import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat } from './combat' import { LogLines, LogEntry, LogLine } from './interface' import { Noun, Pronoun, ImproperNoun, TextLike, Verb, SecondPersonPronouns, PronounAsNoun, FirstPersonPronouns, PairLineArgs, SoloLine, POV } from './language' import { DigestAction, DevourAction, ReleaseAction, StruggleAction } from './combat/actions' import * as Words from './words' export enum VoreType { Oral = "Oral Vore", Anal = "Anal Vore", Cock = "Cock Vore", Unbirth = "Unbirthing" } export abstract class Vore extends Mortal { containers: Array = [] otherContainers: Array = [] containedIn: Container | null = null destroyed = false; voreStats: VoreStats constructor (name: Noun, kind: Noun, pronouns: Pronoun, baseStats: Stats, public preyPrefs: Set, public predPrefs: Set, public mass: number) { super(name, kind, pronouns, baseStats) const containers = this.containers 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 ) }, [VoreStat.Mass]: 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 = (victim) => new LogLine( `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}` ) const released: Array = 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, 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; capacity: number; fullness: number; contents: Array; describe: () => LogEntry; canTake: (prey: Vore) => boolean; consume: (prey: Vore) => LogEntry; release: (prey: Vore) => LogEntry; struggle: (prey: Vore) => LogEntry; consumeVerb: Verb; releaseVerb: Verb; struggleVerb: Verb; } export abstract class NormalContainer implements Container { public name: Noun contents: Array = [] actions: Array = [] abstract consumeVerb: Verb abstract releaseVerb: Verb abstract struggleVerb: Verb constructor (name: Noun, public owner: Vore, public voreTypes: Set, 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 = (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 = (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 = (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 = [] 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, 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; tick: (dt: number) => LogEntry; digest: (preys: 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') digested: Array = [] constructor (name: Noun, owner: Vore, voreTypes: Set, capacity: number, private damage: Damage) { super(name, owner, voreTypes, capacity) this.name = name this.actions.push(new DigestAction(this)) } consumeLine: PairLineArgs = (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 = (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 = (user, target, args) => { return new LogLine(`${user.name.capital.possessive} ${args.container.name} finishes ${Words.Digests.present} ${target.name.objective} down, ${target.pronouns.possessive} ${Words.Struggles.singular} fading away.`) } tick (dt: number): LogEntry { const justDigested: Array = [] const scaled = this.damage.scale(dt / 60) const damageResults: Array = [] 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) } }) const digestedEntries = this.digest(justDigested) this.contents = this.contents.filter(prey => { return prey.vigors[Vigor.Health] > 0 }) return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries) } digest (preys: Vore[]): LogEntry { return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey, { container: this }))) } } export abstract class InnerVoreContainer extends NormalVoreContainer { constructor (name: Noun, owner: Vore, voreTypes: Set, 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) 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, owner, new Set([VoreType.Anal]), capacity, damage) } }