import { Damage, DamageType, Actionable, Action, Vigor, DamageInstance, DamageFormula } from './combat' import { LogLines, LogEntry, LogLine, nilLog, RandomEntry } from './interface' import { Noun, ImproperNoun, Verb, RandomWord } from './language' import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from './combat/actions' import * as Words from './words' import { Creature } from './creature' 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 interface Container extends Actionable { name: Noun; owner: Creature; voreTypes: Set; capacity: number; fullness: number; contents: Array; describe: () => LogEntry; canTake: (prey: Creature) => boolean; consume: (prey: Creature) => LogEntry; release: (prey: Creature) => LogEntry; struggle: (prey: Creature) => LogEntry; consumeVerb: Verb; releaseVerb: Verb; struggleVerb: Verb; consumeLine (user: Creature, target: Creature): LogEntry; } export abstract class NormalContainer implements Container { public name: Noun contents: Array = [] actions: Array = [] consumeVerb = new Verb('trap') releaseVerb = new Verb('release', 'releases', 'releasing', 'released') struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled') constructor (name: Noun, public owner: Creature, public voreTypes: Set, public capacityFactor: number) { this.name = name.all this.actions.push(new DevourAction(this)) this.actions.push(new ReleaseAction(this)) this.actions.push(new StruggleAction(this)) } get capacity (): number { return this.capacityFactor * this.owner.voreStats.Mass } consumeLine (user: Creature, target: Creature): LogEntry { return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} in ${user.pronouns.possessive} ${this.name}.`) } releaseLine (user: Creature, target: Creature): LogEntry { return new LogLine(`${user.name.capital} ${user.name.conjugate(this.releaseVerb)} ${target.name.objective} up from ${user.pronouns.possessive} ${this.name}.`) } struggleLine (user: Creature, target: Creature): LogEntry { return new LogLine(`${user.name.capital} ${user.name.conjugate(this.struggleVerb)} within ${target.name.possessive} ${this.name}.`) } get fullness (): number { return Array.from(this.contents.values()).reduce((total: number, prey: Creature) => total + prey.voreStats.Bulk, 0) } canTake (prey: Creature): 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: Creature): 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) } release (prey: Creature): 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) } struggle (prey: Creature): LogEntry { return this.struggleLine(prey, this.owner) } describe (): LogEntry { const lines: Array = [] this.contents.forEach(prey => { lines.push(prey.toString()) }) return new LogLine(...lines) } } export class Hand extends NormalContainer { consumeVerb = new Verb("grab") constructor (owner: Creature, capacity: number) { super( new ImproperNoun('hand'), owner, new Set(), capacity ) } } export abstract class InnerContainer extends NormalContainer { constructor (name: Noun, owner: Creature, voreTypes: Set, capacity: number, private escape: Container) { super(name, owner, voreTypes, capacity) this.actions = [] this.actions.push(new StruggleAction(this)) } release (prey: Creature): LogEntry { prey.containedIn = this.escape this.contents = this.contents.filter(victim => victim !== prey) return this.releaseLine(this.owner, prey) } } export interface VoreContainer extends Container { digested: Array; tick: (dt: number) => LogEntry; digest: (preys: Creature[]) => LogEntry; absorb: (preys: Creature[]) => LogEntry; fluidColor: string; onDigest: (prey: Creature) => LogEntry; onAbsorb: (prey: Creature) => 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 = [] absorbed: Array = [] constructor (name: Noun, owner: Creature, voreTypes: Set, capacity: number, private damage: DamageFormula) { super(name, owner, voreTypes, capacity) this.name = name this.actions.push(new RubAction(this)) } get fullness (): number { return Array.from(this.contents.concat(this.digested, this.absorbed).values()).reduce((total: number, prey: Creature) => total + prey.voreStats.Bulk, 0) } consumeLine (user: Creature, target: Creature) { return new RandomEntry( new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective}, forcing ${target.pronouns.objective} into ${user.pronouns.possessive} ${this.name}.`), new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb("pounce"))} on ${target.name.objective} and ${user.name.conjugate(this.consumeVerb)} ${target.pronouns.objective}, ${Words.Force.present} ${target.pronouns.objective} into ${user.pronouns.possessive} ${this.name}.`) ) } tickLine (user: Creature, target: Creature, args: { damage: Damage }): LogEntry { return new RandomEntry( new LogLine(`${user.name.capital} ${user.name.conjugate(Words.Churns)} ${target.name.objective} in ${user.pronouns.possessive} ${this.name} for `, args.damage.renderShort(), `.`), new LogLine(`${user.name.capital.possessive} ${this.name} ${Words.Churns}, stewing ${target.name.objective} for `, args.damage.renderShort(), `.`), new LogLine(`${target.name.capital} ${target.name.conjugate(new Verb("thrash", "thrashes"))} in ${user.name.possessive} ${Words.Slick} ${this.name} as it churns ${target.pronouns.objective} for `, args.damage.renderShort(), `.`) ) } digestLine (user: Creature, target: Creature): LogEntry { return new LogLine(`${user.name.capital.possessive} ${this.name} ${this.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Digests.present} ${target.name.objective} down, ${target.pronouns.possessive} ${Words.Struggles.singular} fading away.`) } absorbLine (user: Creature, target: Creature): LogEntry { return new LogLine(`${user.name.capital.possessive} ${this.name} ${this.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Absorbs.present} ${target.name.objective}, fully claiming ${target.pronouns.objective}.`) } tick (dt: number): LogEntry { const justDigested: Array = [] const justAbsorbed: Array = [] const damageResults: Array = [] const tickedEntryList: LogEntry[] = [] this.contents.forEach(prey => { const scaled = this.damage.calc(this.owner, prey).scale(dt / 60) tickedEntryList.push(this.tickLine(this.owner, prey, { damage: scaled })) 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)) } }) const tickedEntries = new LogLines(...tickedEntryList) this.digested.forEach(prey => { const scaled = this.damage.calc(this.owner, prey).scale(dt / 60) 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: Creature[]): LogEntry { return new LogLines(...preys.map(prey => this.absorbLine(this.owner, prey))) } digest (preys: Creature[]): LogEntry { return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey))) } onAbsorb (prey: Creature): LogEntry { return nilLog } onDigest (prey: Creature): LogEntry { return nilLog } } export abstract class InnerVoreContainer extends NormalVoreContainer { constructor (name: Noun, owner: Creature, voreTypes: Set, capacity: number, damage: DamageFormula, private escape: Container) { super(name, owner, voreTypes, capacity, damage) this.actions = [] this.actions.push(new RubAction(this)) this.actions.push(new StruggleAction(this)) } release (prey: Creature): LogEntry { prey.containedIn = this.escape this.contents = this.contents.filter(victim => victim !== prey) this.escape.consume(prey) return this.releaseLine(this.owner, prey) } } export class Stomach extends NormalVoreContainer { constructor (owner: Creature, capacity: number, damage: DamageFormula) { super(new ImproperNoun('stomach', 'stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage) } digest (preys: Creature[]): LogEntry { if (preys.length === 0) { return super.digest(preys) } const heal = new Damage( { amount: preys.reduce((total: number, next: Creature) => 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} ${this.owner.name.conjugate(new Verb("heal"))} for `, this.owner.effectiveDamage(heal).renderShort()) ) } } export class InnerStomach extends InnerVoreContainer { consumeVerb = new Verb('swallow') releaseVerb = new Verb('hork') constructor (owner: Creature, capacity: number, damage: DamageFormula, 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: Creature, capacity: number, damage: DamageFormula) { super(new ImproperNoun('bowel', 'bowels').plural.all, owner, new Set([VoreType.Anal]), capacity, damage) } tickLine (user: Creature, target: Creature, args: { damage: Damage }) { return new RandomEntry( 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} ${this.name} for `, args.damage.renderShort(), `.`), super.tickLine(user, target, args) ) } } export class Cock extends NormalVoreContainer { fluidColor = "#eeeeee66"; constructor (owner: Creature, capacity: number, damage: DamageFormula) { super( new ImproperNoun('cock').all, owner, new Set([VoreType.Cock]), capacity, damage ) } tickLine (user: Creature, target: Creature, args: { damage: Damage }): LogEntry { 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} ${this.name}.`) } } export class Balls extends InnerVoreContainer { fluidColor = "#eeeeeecc"; constructor (owner: Creature, capacity: number, damage: DamageFormula, 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: Creature, capacity: number, damage: DamageFormula) { super( new ImproperNoun('slit').all, owner, new Set([VoreType.Unbirth]), capacity, damage ) } } export class Womb extends InnerVoreContainer { fluidColor = "#ddddddbb"; constructor (owner: Creature, capacity: number, damage: DamageFormula, 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: Creature) => { outer.digested = outer.digested.filter(victim => victim !== prey) inner.digested.push(prey) return inner.consumeLine(inner.owner, prey) } outer.actions.push( new TransferAction( outer, inner ) ) inner.actions.push( new TransferAction( inner, outer ) ) }