|
- import { Damage, DamageType, Actionable, Action, Vigor, DamageInstance, DamageFormula, ConstantDamageFormula, StatusEffect } from '@/game/combat'
- import { LogLines, LogEntry, LogLine, nilLog, RandomEntry, FormatEntry, FormatOpt } from '@/game/interface'
- import { Noun, ImproperNoun, Verb, RandomWord, Word, Preposition, ToBe, Adjective } from '@/game/language'
- import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction, StruggleMoveAction } from '@/game/combat/actions'
- import * as Words from '@/game/words'
- import * as Onomatopoeia from '@/game/onomatopoeia'
- import { Creature } from '@/game/creature'
- import { VoreRelay } from '@/game/events'
-
- export enum VoreType {
- Oral = "Oral Vore"
- }
-
- export const anyVore = new Set([
- VoreType.Oral
- ])
-
- export type Wall = {
- name: Word;
- texture: Adjective;
- material: Noun;
- color: Adjective;
- }
-
- export type Fluid = {
- name: Word;
- color: Adjective;
- sound: Word;
- sloshVerb: Verb;
- }
-
- export type Gas = {
- name: Word;
- color: Adjective;
- smell: Adjective;
- bubbleVerb: Verb;
- releaseVerb: Verb;
- exit: Noun;
- }
-
- export enum ContainerCapability {
- Consume,
- Release,
- Digest,
- Absorb
- }
-
- export enum ConnectionDirection {
- Deeper,
- Neutral,
- Shallower
- }
-
- export type Connection = {
- destination: Container;
- direction: ConnectionDirection;
- description: (to: Container, from: Container, prey: Creature) => LogEntry;
- }
-
- export interface Container extends Actionable {
- name: Noun;
- owner: Creature;
- voreTypes: Set<VoreType>;
- capabilities: Set<ContainerCapability>;
-
- connections: Array<Connection>;
-
- effects: Array<StatusEffect>;
-
- wall: Wall | null;
- fluid: Fluid | null;
- gas: Gas | null;
-
- voreRelay: VoreRelay;
-
- contents: Array<Creature>;
- digested: Array<Creature>;
-
- damage: DamageFormula;
-
- sound: Word;
-
- capacity: number;
- fullness: number;
-
- consumeVerb: Verb;
- consumePreposition: Preposition;
- releaseVerb: Verb;
- releasePreposition: Preposition;
- struggleVerb: Verb;
- strugglePreposition: Preposition;
-
- canTake (prey: Creature): boolean;
-
- consume (prey: Creature): LogEntry;
- release (prey: Creature): LogEntry;
-
- enter (prey: Creature): LogEntry;
- exit (prey: Creature): LogEntry;
-
- struggle (prey: Creature): LogEntry;
-
- tick (dt: number, victims?: Array<Creature>): LogEntry;
- digest (preys: Creature[]): LogEntry;
- absorb (preys: Creature[]): LogEntry;
-
- onDigest (prey: Creature): LogEntry;
- onAbsorb (prey: Creature): LogEntry;
-
- consumeLine (user: Creature, target: Creature): LogEntry;
- statusLine (user: Creature, target: Creature): LogEntry;
-
- describe (): LogEntry;
- describeDetail (prey: Creature): LogEntry;
-
- connect (dest: Connection): void;
- }
-
- export abstract class DefaultContainer implements Container {
- public name: Noun
- contents: Array<Creature> = []
- actions: Array<Action> = []
-
- wall: Wall | null = null
- fluid: Fluid | null = null
- gas: Gas | null = null
-
- connections: Array<Connection> = []
-
- voreRelay = new VoreRelay()
-
- effects: Array<StatusEffect> = []
-
- consumeVerb = new Verb('devour')
- consumePreposition = new Preposition("into")
- releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
- releasePreposition = new Preposition("out from")
- struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
- strugglePreposition = new Preposition("within")
- fluidColor = "#00ff0088"
-
- digested: Array<Creature> = []
- absorbed: Array<Creature> = []
-
- damage: DamageFormula = new ConstantDamageFormula(new Damage());
-
- sound = new Verb("slosh")
-
- constructor (name: Noun, public owner: Creature, public voreTypes: Set<VoreType>, public capacityFactor: number, public capabilities: Set<ContainerCapability>) {
- this.name = name.all
- if (capabilities.has(ContainerCapability.Consume)) {
- this.actions.push(new DevourAction(this))
- }
- if (capabilities.has(ContainerCapability.Release)) {
- this.actions.push(new ReleaseAction(this))
- this.actions.push(new StruggleAction(this))
- }
- if (capabilities.has(ContainerCapability.Digest)) {
- this.actions.push(new RubAction(this))
- }
- }
-
- connect (connection: Connection): void {
- this.connections.push(connection)
-
- this.actions.push(new TransferAction(this, connection))
- this.actions.push(new StruggleMoveAction(this, connection.destination))
- }
-
- get capacity (): number {
- return this.capacityFactor * this.owner.voreStats.Mass
- }
-
- statusLine (user: Creature, target: Creature): LogEntry {
- return new LogLine(
- `${target.name.capital} ${target.name.conjugate(new ToBe())} ${Words.Stuck} inside ${user.name.possessive} ${this.name}.`
- )
- }
-
- releaseLine (user: Creature, target: Creature): LogEntry {
- return new LogLine(`${user.name.capital} ${user.name.conjugate(this.releaseVerb)} ${target.name.objective} ${this.releasePreposition} ${user.pronouns.possessive} ${this.name}.`)
- }
-
- struggleLine (user: Creature, target: Creature): LogEntry {
- return new LogLine(`${user.name.capital} ${user.name.conjugate(this.struggleVerb)} ${this.strugglePreposition} ${target.name.possessive} ${this.name}.`)
- }
-
- get fullness (): number {
- return Array.from(this.contents.concat(this.digested, this.absorbed).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 {
- const results: Array<LogEntry> = [
- this.enter(prey),
- this.voreRelay.dispatch("onEaten", this, { prey: prey }),
- prey.voreRelay.dispatch("onEaten", this, { prey: prey }),
- this.consumeLine(this.owner, prey)
- ]
-
- this.owner.effects.forEach(effect => results.push(effect.postConsume(this.owner, prey, this)))
-
- return new LogLines(...results)
- }
-
- release (prey: Creature): LogEntry {
- const results = [
- this.exit(prey),
- this.releaseLine(this.owner, prey),
- this.voreRelay.dispatch("onReleased", this, { prey: prey }),
- prey.voreRelay.dispatch("onReleased", this, { prey: prey })
- ]
-
- return new LogLines(...results)
- }
-
- enter (prey: Creature): LogEntry {
- if (prey.containedIn !== null) {
- prey.containedIn.contents = prey.containedIn.contents.filter(item => prey !== item)
- }
- this.contents.push(prey)
- prey.containedIn = this
-
- const effectResults = this.effects.map(effect => prey.applyEffect(effect))
-
- const results = [
- this.voreRelay.dispatch("onEntered", this, { prey: prey }),
- prey.voreRelay.dispatch("onEntered", this, { prey: prey })
- ]
-
- this.owner.effects.forEach(effect => results.push(effect.postEnter(this.owner, prey, this)))
-
- return new LogLines(...results, ...effectResults)
- }
-
- exit (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)
- }
-
- const effectResults = this.effects.map(effect => prey.removeEffect(effect))
-
- const results = [
- this.voreRelay.dispatch("onExited", this, { prey: prey }),
- prey.voreRelay.dispatch("onExited", this, { prey: prey })
- ]
-
- return new LogLines(...results, ...effectResults)
- }
-
- struggle (prey: Creature): LogEntry {
- return this.struggleLine(prey, this.owner)
- }
-
- describe (): LogEntry {
- const lines: Array<string> = []
-
- this.contents.forEach(prey => {
- lines.push(prey.toString())
- })
-
- return new LogLine(...lines)
- }
-
- describeDetail (prey: Creature): LogEntry {
- const lines: Array<LogLine> = []
-
- if (this.gas) {
- lines.push(
- new LogLine(`${this.gas.color.capital} ${this.gas.name.plural} ${this.gas.bubbleVerb} in ${this.owner.name.possessive} ${this.name}.`)
- )
- }
- if (this.fluid) {
- lines.push(
- new LogLine(`${this.fluid.name.capital} ${this.fluid.sloshVerb.singular} around ${prey.name.objective}.`)
- )
- }
- if (this.wall) {
- lines.push(
- new LogLine(`The ${this.wall.color} walls ${prey.name.conjugate(Words.Clench)} over ${prey.name.objective} like a vice.`)
- )
- }
-
- return new LogLine(...lines)
- }
-
- consumeLine (user: Creature, target: Creature) {
- return new RandomEntry(
- new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective}, ${Words.Force.present} ${target.pronouns.objective} ${this.consumePreposition} ${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} ${this.consumePreposition} ${user.pronouns.possessive} ${this.name}.`)
- )
- }
-
- tickLine (user: Creature, target: Creature, args: { damage: Damage }): LogEntry {
- const options = [
- new LogLine(`${user.name.capital} ${Words.Churns.singular} ${target.name.objective} ${this.strugglePreposition} ${user.pronouns.possessive} ${this.name} for `, args.damage.renderShort(), `.`),
- new LogLine(`${user.name.capital.possessive} ${this.name} ${this.name.conjugate(Words.Churns)}, ${Words.Churns.present} ${target.name.objective} for `, args.damage.renderShort(), `.`),
- new LogLine(`${target.name.capital} ${target.name.conjugate(Words.Struggle)} ${this.strugglePreposition} ${user.name.possessive} ${Words.Slick} ${this.name} as it ${Words.Churns.singular} ${target.pronouns.objective} for `, args.damage.renderShort(), `.`)
- ]
-
- if (this.fluid) {
- options.push(new LogLine(`${this.fluid.name.capital} ${this.fluid.sloshVerb.singular} and ${this.fluid.sound.singular} as ${this.owner.name.possessive} ${this.name} steadily ${Words.Digest.singular} ${target.name.objective}.`))
- }
-
- const result: Array<LogEntry> = [
- new RandomEntry(...options)
- ]
-
- if (Math.random() < 0.3) {
- result.push(new FormatEntry(new LogLine(`${Onomatopoeia.Gurgle}`), FormatOpt.Onomatopoeia))
- }
-
- return new LogLines(...result)
- }
-
- digestLine (user: Creature, target: Creature): LogEntry {
- return new LogLine(`${user.name.capital.possessive} ${this.name} finishes ${Words.Digest.present} ${target.name.objective} down, ${target.pronouns.possessive} ${Words.Struggle.singular} fading away as ${target.pronouns.subjective} ${target.pronouns.conjugate(Words.Succumb)}.`)
- }
-
- absorbLine (user: Creature, target: Creature): LogEntry {
- return new LogLine(`${user.name.capital.possessive} ${this.name} ${this.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Absorb.present} ${target.name.objective}, fully claiming ${target.pronouns.objective}.`)
- }
-
- tick (dt: number, victims?: Array<Creature>): LogEntry {
- const justDigested: Array<Creature> = []
- const justAbsorbed: Array<Creature> = []
-
- const damageResults: Array<LogEntry> = []
-
- const tickedEntryList: LogEntry[] = []
-
- if (this.capabilities.has(ContainerCapability.Digest)) {
- this.contents.forEach(prey => {
- if (victims === undefined || victims.indexOf(prey) >= 0) {
- const scaled = this.damage.calc(this.owner, prey).scale(dt / 60)
- const modified = this.owner.effects.reduce((damage, effect) => effect.modDigestionDamage(this.owner, prey, this, damage), scaled)
- if (modified.nonzero()) {
- tickedEntryList.push(this.tickLine(this.owner, prey, { damage: modified }))
- damageResults.push(prey.takeDamage(modified))
- }
-
- 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 => {
- if (victims === undefined || victims.indexOf(prey) >= 0) {
- const scaled = this.damage.calc(this.owner, prey).scale(dt / 60)
- const damageTotal: number = 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 {
- const results = preys.map(prey => this.digestLine(this.owner, prey))
- if (preys.length > 0 && this.gas) {
- results.push(new LogLine(
- `A crass ${this.gas.releaseVerb} escapes ${this.owner.name.possessive} ${this.gas.exit} as ${this.owner.name.possessive} prey is digested, spewing ${this.gas.color} ${this.gas.name}.`
- ))
- }
- return new LogLines(...results)
- }
-
- onAbsorb (prey: Creature): LogEntry {
- return this.voreRelay.dispatch("onAbsorbed", this, { prey: prey })
- }
-
- onDigest (prey: Creature): LogEntry {
- return this.voreRelay.dispatch("onDigested", this, { prey: prey })
- }
- }
-
- export class Stomach extends DefaultContainer {
- fluid = {
- color: new Adjective("green"),
- name: new Noun("chyme"),
- sound: new Verb("gurgle"),
- sloshVerb: new Verb("slosh", "sloshes", "sloshing", "sloshed")
- }
-
- gas = {
- bubbleVerb: new Verb("bubble", "bubbles", "bubbling", "bubbled"),
- color: new Adjective("hazy"),
- name: new Noun("fume", "fumes"),
- releaseVerb: new Verb("belch", "belches", "belching", "belched"),
- smell: new Adjective("acrid"),
- exit: new Noun("jaws")
- }
-
- wall = {
- color: new Adjective("red"),
- material: new Noun("muscle"),
- name: new Noun("wall"),
- texture: new Adjective("slimy")
- }
-
- constructor (owner: Creature, capacityFactor: number, damage: DamageFormula) {
- super(new Noun("stomach"), owner, new Set<VoreType>([VoreType.Oral]), capacityFactor, new Set<ContainerCapability>([
- ContainerCapability.Digest,
- ContainerCapability.Absorb
- ]))
-
- this.voreRelay.subscribe("onEntered", (sender: Container, args: { prey: Creature }) => {
- return new FormatEntry(new LogLine(`${Onomatopoeia.Glunk}`), FormatOpt.Onomatopoeia)
- })
-
- this.damage = damage
- }
- }
-
- export class Throat extends DefaultContainer {
- fluid = {
- color: new Adjective("clear"),
- name: new RandomWord([
- new Noun("saliva"),
- new Noun("drool"),
- new Noun("slobber")
- ]),
- sound: new Verb("squish", "squishes"),
- sloshVerb: new Verb("slosh", "sloshes", "sloshing", "sloshed")
- }
-
- wall = {
- color: new Adjective("red"),
- material: new Noun("muscle"),
- name: new Noun("wall"),
- texture: new Adjective("slimy")
- }
-
- constructor (owner: Creature, capacityFactor: number) {
- super(new Noun("throat"), owner, new Set<VoreType>([VoreType.Oral]), capacityFactor, new Set<ContainerCapability>([
- ContainerCapability.Consume,
- ContainerCapability.Release
- ]))
-
- this.voreRelay.subscribe("onEaten", (sender: Container, args: { prey: Creature }) => {
- return new FormatEntry(new LogLine(`${Onomatopoeia.Swallow}`), FormatOpt.Onomatopoeia)
- })
- }
- }
-
- export function transferDescription (verb: Word, preposition: Preposition): ((from: Container, to: Container, prey: Creature) => LogEntry) {
- return (from: Container, to: Container, prey: Creature) => {
- return new LogLine(`${from.owner.name.capital} ${from.owner.name.conjugate(verb.singular)} ${prey.name.objective} ${preposition} ${to.consumePreposition} ${from.owner.pronouns.possessive} ${to.name}.`)
- }
- }
|