@@ -4,6 +4,7 @@ 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'
import { Creature } from './creature'
export enum VoreType {
Oral = "Oral Vore",
@@ -19,133 +20,40 @@ export const anyVore = new Set([
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
)
},
self.voreStats.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: Vo re;
owner: Creature;
voreTypes: Set<VoreType>;
capacity: number;
fullness: number;
contents: Array<Vo re>;
contents: Array<Creature>;
describe: () => LogEntry;
canTake: (prey: Vo re) => boolean;
consume: (prey: Vo re) => LogEntry;
release: (prey: Vo re) => LogEntry;
struggle: (prey: Vo re) => LogEntry;
canTake: (prey: Creature) => boolean;
consume: (prey: Creature) => LogEntry;
release: (prey: Creature) => LogEntry;
struggle: (prey: Creature) => LogEntry;
consumeVerb: Verb;
releaseVerb: Verb;
struggleVerb: Verb;
consumeLine: PairLineArgs<Vo re, { container: Container }>;
consumeLine: PairLineArgs<Creature, { container: Container }>;
}
export abstract class NormalContainer implements Container {
public name: Noun
contents: Array<Vo re> = []
contents: Array<Creature> = []
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: Vo re, public voreTypes: Set<VoreType>, public capacityFactor: number) {
constructor (name: Noun, public owner: Creature, public voreTypes: Set<VoreType>, public capacityFactor: number) {
this.name = name.all
this.actions.push(new DevourAction(this))
this.actions.push(new ReleaseAction(this))
@@ -156,23 +64,23 @@ export abstract class NormalContainer implements Container {
return this.capacityFactor * this.owner.voreStats.Mass
}
consumeLine: PairLineArgs<Vo re, { container: Container }> = (user, target, args) => {
consumeLine: PairLineArgs<Creatu re, { 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<Vo re, { container: Container }> = (user, target, args) => {
releaseLine: PairLineArgs<Creatu re, { 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<Vo re, { container: Container }> = (user, target, args) => {
struggleLine: PairLineArgs<Creatu re, { 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: Vo re) => total + prey.voreStats.Bulk, 0)
return Array.from(this.contents.values()).reduce((total: number, prey: Creatu re) => total + prey.voreStats.Bulk, 0)
}
canTake (prey: Vo re): boolean {
canTake (prey: Creatu re): boolean {
const fits = this.capacity - this.fullness >= prey.voreStats.Bulk
const permitted = Array.from(this.voreTypes).every(voreType => {
@@ -182,7 +90,7 @@ export abstract class NormalContainer implements Container {
return fits && permitted
}
consume (prey: Vo re): LogEntry {
consume (prey: Creatu re): LogEntry {
if (prey.containedIn !== null) {
prey.containedIn.contents = prey.containedIn.contents.filter(item => prey !== item)
}
@@ -191,7 +99,7 @@ export abstract class NormalContainer implements Container {
return this.consumeLine(this.owner, prey, { container: this })
}
release (prey: Vo re): LogEntry {
release (prey: Creatu re): LogEntry {
prey.containedIn = this.owner.containedIn
this.contents = this.contents.filter(victim => victim !== prey)
@@ -201,7 +109,7 @@ export abstract class NormalContainer implements Container {
return this.releaseLine(this.owner, prey, { container: this })
}
struggle (prey: Vo re): LogEntry {
struggle (prey: Creatu re): LogEntry {
return this.struggleLine(prey, this.owner, { container: this })
}
@@ -218,7 +126,7 @@ export abstract class NormalContainer implements Container {
export class Hand extends NormalContainer {
consumeVerb = new Verb("grab")
constructor (owner: Vo re, capacity: number) {
constructor (owner: Creatu re, capacity: number) {
super(
new ImproperNoun('hand'),
owner,
@@ -229,7 +137,7 @@ export class Hand extends NormalContainer {
}
export abstract class InnerContainer extends NormalContainer {
constructor (name: Noun, owner: Vo re, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
constructor (name: Noun, owner: Creatu re, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
super(name, owner, voreTypes, capacity)
this.actions = []
@@ -237,7 +145,7 @@ export abstract class InnerContainer extends NormalContainer {
this.actions.push(new StruggleAction(this))
}
release (prey: Vo re): LogEntry {
release (prey: Creatu re): LogEntry {
prey.containedIn = this.escape
this.contents = this.contents.filter(victim => victim !== prey)
return this.releaseLine(this.owner, prey, { container: this })
@@ -245,14 +153,14 @@ export abstract class InnerContainer extends NormalContainer {
}
export interface VoreContainer extends Container {
digested: Array<Vo re>;
digested: Array<Creatu re>;
tick: (dt: number) => LogEntry;
digest: (preys: Vo re[]) => LogEntry;
absorb: (preys: Vo re[]) => LogEntry;
digest: (preys: Creatu re[]) => LogEntry;
absorb: (preys: Creatu re[]) => LogEntry;
fluidColor: string;
onDigest: (prey: Vo re) => LogEntry;
onAbsorb: (prey: Vo re) => LogEntry;
onDigest: (prey: Creatu re) => LogEntry;
onAbsorb: (prey: Creatu re) => LogEntry;
}
export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer {
@@ -261,10 +169,10 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor
struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
fluidColor = "#00ff0088"
digested: Array<Vo re> = []
absorbed: Array<Vo re> = []
digested: Array<Creatu re> = []
absorbed: Array<Creatu re> = []
constructor (name: Noun, owner: Vo re, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) {
constructor (name: Noun, owner: Creatu re, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) {
super(name, owner, voreTypes, capacity)
this.name = name
@@ -273,28 +181,28 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor
}
get fullness (): number {
return Array.from(this.contents.concat(this.digested, this.absorbed).values()).reduce((total: number, prey: Vo re) => total + prey.voreStats.Bulk, 0)
return Array.from(this.contents.concat(this.digested, this.absorbed).values()).reduce((total: number, prey: Creatu re) => total + prey.voreStats.Bulk, 0)
}
consumeLine: PairLineArgs<Vo re, { container: Container }> = (user, target, args) => {
consumeLine: PairLineArgs<Creatu re, { 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<Vo re, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
tickLine: PairLineArgs<Creatu re, { 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<Vo re, { container: VoreContainer }> = (user, target, args) => {
digestLine: PairLineArgs<Creatu re, { 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<Vo re, { container: VoreContainer }> = (user, target, args) => {
absorbLine: PairLineArgs<Creatu re, { 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<Vo re> = []
const justAbsorbed: Array<Vo re> = []
const justDigested: Array<Creatu re> = []
const justAbsorbed: Array<Creatu re> = []
const scaled = this.damage.scale(dt / 60)
@@ -345,25 +253,25 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor
return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries, absorbedEntries)
}
absorb (preys: Vo re[]): LogEntry {
absorb (preys: Creatu re[]): LogEntry {
return new LogLines(...preys.map(prey => this.absorbLine(this.owner, prey, { container: this })))
}
digest (preys: Vo re[]): LogEntry {
digest (preys: Creatu re[]): LogEntry {
return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey, { container: this })))
}
onAbsorb (prey: Vo re): LogEntry {
onAbsorb (prey: Creatu re): LogEntry {
return nilLog
}
onDigest (prey: Vo re): LogEntry {
onDigest (prey: Creatu re): LogEntry {
return nilLog
}
}
export abstract class InnerVoreContainer extends NormalVoreContainer {
constructor (name: Noun, owner: Vo re, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: Container) {
constructor (name: Noun, owner: Creatu re, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: Container) {
super(name, owner, voreTypes, capacity, damage)
this.actions = []
@@ -372,7 +280,7 @@ export abstract class InnerVoreContainer extends NormalVoreContainer {
this.actions.push(new StruggleAction(this))
}
release (prey: Vo re): LogEntry {
release (prey: Creatu re): LogEntry {
prey.containedIn = this.escape
this.contents = this.contents.filter(victim => victim !== prey)
this.escape.consume(prey)
@@ -381,18 +289,18 @@ export abstract class InnerVoreContainer extends NormalVoreContainer {
}
export class Stomach extends NormalVoreContainer {
constructor (owner: Vo re, capacity: number, damage: Damage) {
constructor (owner: Creatu re, capacity: number, damage: Damage) {
super(new ImproperNoun('stomach', 'stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage)
}
digest (preys: Vo re[]): LogEntry {
digest (preys: Creatu re[]): LogEntry {
if (preys.length === 0) {
return super.digest(preys)
}
const heal = new Damage(
{
amount: preys.reduce((total: number, next: Vo re) => total + next.maxVigors.Health / 5, 0),
amount: preys.reduce((total: number, next: Creatu re) => total + next.maxVigors.Health / 5, 0),
type: DamageType.Heal,
target: Vigor.Health
}
@@ -408,17 +316,17 @@ export class Stomach extends NormalVoreContainer {
export class InnerStomach extends InnerVoreContainer {
consumeVerb = new Verb('swallow')
releaseVerb = new Verb('hork')
constructor (owner: Vo re, capacity: number, damage: Damage, escape: VoreContainer) {
constructor (owner: Creatu re, 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: Vo re, capacity: number, damage: Damage) {
constructor (owner: Creatu re, capacity: number, damage: Damage) {
super(new ImproperNoun('bowel', 'bowels').plural.all, owner, new Set([VoreType.Anal]), capacity, damage)
}
tickLine: PairLineArgs<Vo re, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
tickLine: PairLineArgs<Creatu re, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
return new LogLine(`${user.name.capital} ${user.name.conjugate(
new RandomWord([
new Verb('crush', 'crushes'),
@@ -431,7 +339,7 @@ export class Bowels extends NormalVoreContainer {
export class Cock extends NormalVoreContainer {
fluidColor = "#eeeeee66";
constructor (owner: Vo re, capacity: number, damage: Damage) {
constructor (owner: Creatu re, capacity: number, damage: Damage) {
super(
new ImproperNoun('cock').all,
owner,
@@ -441,7 +349,7 @@ export class Cock extends NormalVoreContainer {
)
}
tickLine: PairLineArgs<Vo re, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
tickLine: PairLineArgs<Creatu re, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
return new LogLine(`${user.name.capital} ${user.name.conjugate(
new RandomWord([
new Verb('crush', 'crushes'),
@@ -454,7 +362,7 @@ export class Cock extends NormalVoreContainer {
export class Balls extends InnerVoreContainer {
fluidColor = "#eeeeeecc";
constructor (owner: Vo re, capacity: number, damage: Damage, escape: Container) {
constructor (owner: Creatu re, capacity: number, damage: Damage, escape: Container) {
super(
new ImproperNoun('ball', 'balls').all.plural,
owner,
@@ -469,7 +377,7 @@ export class Balls extends InnerVoreContainer {
export class Slit extends NormalVoreContainer {
fluidColor = "#cccccc99";
constructor (owner: Vo re, capacity: number, damage: Damage) {
constructor (owner: Creatu re, capacity: number, damage: Damage) {
super(
new ImproperNoun('slit').all,
owner,
@@ -483,7 +391,7 @@ export class Slit extends NormalVoreContainer {
export class Womb extends InnerVoreContainer {
fluidColor = "#ddddddbb";
constructor (owner: Vo re, capacity: number, damage: Damage, escape: Container) {
constructor (owner: Creatu re, capacity: number, damage: Damage, escape: Container) {
super(
new ImproperNoun('womb').all,
owner,
@@ -496,7 +404,7 @@ export class Womb extends InnerVoreContainer {
}
export function biconnectContainers (outer: VoreContainer, inner: VoreContainer): void {
outer.onDigest = (prey: Vo re) => {
outer.onDigest = (prey: Creatu re) => {
outer.digested = outer.digested.filter(victim => victim !== prey)
inner.digested.push(prey)
return inner.consumeLine(inner.owner, prey, { container: inner })