From 6578931f6d73278477c4bee9d63256c9aead9cb0 Mon Sep 17 00:00:00 2001 From: Fen Dweller Date: Sun, 15 Nov 2020 15:34:28 -0500 Subject: [PATCH] Remove most characters; add Inazuma; start work on event system --- src/game/ai.ts | 164 +++++++++- src/game/ai/deciders.ts | 111 ------- src/game/combat/actions.ts | 4 +- src/game/creature.ts | 16 + src/game/creatures.ts | 19 +- src/game/creatures/cafat.ts | 128 -------- src/game/creatures/characters/inazuma.ts | 39 +++ src/game/creatures/dragon.ts | 44 --- src/game/creatures/geta.ts | 202 ------------- src/game/creatures/goldeneye.ts | 195 ------------ src/game/creatures/human.ts | 2 +- src/game/creatures/kenzie.ts | 63 ---- src/game/creatures/kuro.ts | 57 ---- src/game/creatures/player.ts | 33 +-- src/game/creatures/shingo.ts | 191 ------------ src/game/creatures/taluthus.ts | 103 ------- src/game/creatures/werewolf.ts | 99 ------- src/game/creatures/withers.ts | 362 ----------------------- src/game/creatures/wolves.ts | 77 ----- src/game/events.ts | 53 ++++ src/game/maps/town.ts | 150 +--------- src/game/vore.ts | 16 +- 22 files changed, 290 insertions(+), 1838 deletions(-) delete mode 100644 src/game/ai/deciders.ts delete mode 100644 src/game/creatures/cafat.ts create mode 100644 src/game/creatures/characters/inazuma.ts delete mode 100644 src/game/creatures/dragon.ts delete mode 100644 src/game/creatures/geta.ts delete mode 100644 src/game/creatures/goldeneye.ts delete mode 100644 src/game/creatures/kenzie.ts delete mode 100644 src/game/creatures/kuro.ts delete mode 100644 src/game/creatures/shingo.ts delete mode 100644 src/game/creatures/taluthus.ts delete mode 100644 src/game/creatures/werewolf.ts delete mode 100644 src/game/creatures/withers.ts delete mode 100644 src/game/creatures/wolves.ts create mode 100644 src/game/events.ts diff --git a/src/game/ai.ts b/src/game/ai.ts index 67a0fd2..8709acf 100644 --- a/src/game/ai.ts +++ b/src/game/ai.ts @@ -1,19 +1,32 @@ import { Creature } from '@/game/creature' -import { Encounter, Action } from '@/game/combat' -import { LogEntry } from '@/game/interface' -import { PassAction } from '@/game/combat/actions' -import { NoPassDecider, NoReleaseDecider, ChanceDecider, NoSurrenderDecider, FavorRubDecider } from '@/game/ai/deciders' +import { Encounter, Action, CompositionAction, Consequence } from '@/game/combat' +import { LogEntry, LogLine, nilLog } from '@/game/interface' +import { PassAction, ReleaseAction, RubAction } from '@/game/combat/actions' +import { VoreRelay } from '@/game/events' +import { StatusConsequence } from '@/game/combat/consequences' +import { SurrenderEffect } from '@/game/combat/effects' +import { ToBe, Verb } from '@/game/language' /** * A Decider determines how favorable an action is to perform. */ -export interface Decider { - decide: (encounter: Encounter, user: Creature, target: Creature, action: Action) => number; +export abstract class Decider { + // eslint-disable-next-line @typescript-eslint/no-empty-function + attach (ai: AI): void { } ; + abstract decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number; } export class AI { - constructor (public deciders: Decider[]) { + voreRelay = new VoreRelay() + constructor (public deciders: Decider[], owner: Creature) { + this.voreRelay.connect(owner.voreRelay) + deciders.forEach(decider => decider.attach(this)) + } + + addDecider (decider: Decider): void { + this.deciders.push(decider) + decider.attach(this) } decide (actor: Creature, encounter: Encounter): LogEntry { @@ -57,11 +70,143 @@ export class AI { } } +/** + * Specifically avoids using a [[PassAction]] + */ +export class NoPassDecider extends Decider { + decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { + if (action instanceof PassAction) { + return 0 + } else { + return 1 + } + } +} + +/** + * Specifically avoids using a [[ReleaseAction]] + */ +export class NoReleaseDecider extends Decider { + decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { + if (action instanceof ReleaseAction) { + return 0 + } else { + return 1 + } + } +} + +/** + * Weights actions based on how likely they are to succeed + */ +export class ChanceDecider extends Decider { + decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { + return action.odds(user, target) + } +} + +/** + * Adjusts the weights for [[CompositionAction]]s that contain the specified consequence + */ +export class ConsequenceDecider extends Decider { + /* eslint-disable-next-line */ + constructor (private consequenceType: new (...args: any) => T, private weight: number) { + super() + } + + decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { + if (action instanceof CompositionAction) { + if (action.consequences.some( + consequence => consequence instanceof this.consequenceType + )) { + return this.weight + } else { + return 1 + } + } else { + return 1 + } + } +} + +/** + * Adjusts the weights for [[CompositionAction]]s, using the provided function to make the choice. + */ +export class ConsequenceFunctionDecider extends Decider { + constructor (private func: (encounter: Encounter, user: Creature, target: Creature, action: CompositionAction) => number) { + super() + } + + decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { + if (action instanceof CompositionAction) { + return this.func(encounter, user, target, action) + } else { + return 1 + } + } +} + +export class NoSurrenderDecider extends ConsequenceFunctionDecider { + constructor () { + super( + (encounter, user, target, action) => { + return action.consequences.some( + consequence => { + if (consequence instanceof StatusConsequence) { + return (consequence.statusMaker(user, target) instanceof SurrenderEffect) + } + } + ) ? 0 : 1 + } + ) + } +} + +/** + * Favors [[RubAction]]s + */ +export class FavorRubDecider extends Decider { + decide (encounter: Encounter, user: Creature, target: Creature, action: Action) { + if (action instanceof RubAction) { + return 5 + } else { + return 1 + } + } +} + +/** + * Favors creatures that have escaped this creature + */ +export class FavorEscapedPrey extends Decider { + private memory: Set = new Set() + + attach (ai: AI) { + ai.voreRelay.subscribe( + "onReleased", + (sender, args) => { + if (this.memory.has(args.prey)) { + return nilLog + } else { + this.memory.add(args.prey) + return new LogLine( + `${sender.owner.name} ${sender.owner.name.conjugate(new Verb("crave"))} ${args.prey.name.possessive} flavor, and won't be giving ${args.prey.pronouns.objective} up so easily...` + ) + } + } + ) + } + + decide (encounter: Encounter, user: Creature, target: Creature, action: Action) { + return this.memory.has(target) ? 5 : 1 + } +} + /** * The VoreAI tries to eat opponents, but only if the odds are good enough */ export class VoreAI extends AI { - constructor () { + constructor (owner: Creature) { super( [ new NoReleaseDecider(), @@ -69,7 +214,8 @@ export class VoreAI extends AI { new NoPassDecider(), new ChanceDecider(), new FavorRubDecider() - ] + ], + owner ) } } diff --git a/src/game/ai/deciders.ts b/src/game/ai/deciders.ts deleted file mode 100644 index c424a93..0000000 --- a/src/game/ai/deciders.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Decider } from '@/game/ai' -import { Encounter, Action, Consequence, CompositionAction } from '@/game/combat' -import { Creature } from '@/game/creature' -import { PassAction, ReleaseAction, RubAction } from '@/game/combat/actions' -import { StatusConsequence } from '@/game/combat/consequences' -import { SurrenderEffect } from '@/game/combat/effects' - -/** - * Specifically avoids using a [[PassAction]] - */ -export class NoPassDecider implements Decider { - decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { - if (action instanceof PassAction) { - return 0 - } else { - return 1 - } - } -} - -/** - * Specifically avoids using a [[ReleaseAction]] - */ -export class NoReleaseDecider implements Decider { - decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { - if (action instanceof ReleaseAction) { - return 0 - } else { - return 1 - } - } -} - -/** - * Weights actions based on how likely they are to succeed - */ -export class ChanceDecider implements Decider { - decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { - return action.odds(user, target) - } -} - -/** - * Adjusts the weights for [[CompositionAction]]s that contain the specified consequence - */ -export class ConsequenceDecider implements Decider { - /* eslint-disable-next-line */ - constructor (private consequenceType: new (...args: any) => T, private weight: number) { - - } - - decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { - if (action instanceof CompositionAction) { - if (action.consequences.some( - consequence => consequence instanceof this.consequenceType - )) { - return this.weight - } else { - return 1 - } - } else { - return 1 - } - } -} - -/** - * Adjusts the weights for [[CompositionAction]]s, using the provided function to make the choice. - */ -export class ConsequenceFunctionDecider implements Decider { - constructor (private func: (encounter: Encounter, user: Creature, target: Creature, action: CompositionAction) => number) { - - } - - decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number { - if (action instanceof CompositionAction) { - return this.func(encounter, user, target, action) - } else { - return 1 - } - } -} - -export class NoSurrenderDecider extends ConsequenceFunctionDecider { - constructor () { - super( - (encounter, user, target, action) => { - return action.consequences.some( - consequence => { - if (consequence instanceof StatusConsequence) { - return (consequence.statusMaker(user, target) instanceof SurrenderEffect) - } - } - ) ? 0 : 1 - } - ) - } -} - -/** - * Favors [[RubAction]]s - */ -export class FavorRubDecider implements Decider { - decide (encounter: Encounter, user: Creature, target: Creature, action: Action) { - if (action instanceof RubAction) { - return 5 - } else { - return 1 - } - } -} diff --git a/src/game/combat/actions.ts b/src/game/combat/actions.ts index 253597c..afda72d 100644 --- a/src/game/combat/actions.ts +++ b/src/game/combat/actions.ts @@ -195,7 +195,7 @@ export class StruggleAction extends Action { new CompositionTest( [ new OpposedStatScorer( - { Power: 1, Agility: 1, Bulk: 0.05 }, + { Power: 100, Agility: 1, Bulk: 0.05 }, { Toughness: 1, Reflexes: 1, Mass: 0.05 } ) ], @@ -220,7 +220,7 @@ export class StruggleAction extends Action { } protected successLine: PairLineArgs = (prey, pred, args) => new LogLine( - `${prey.name.capital} ${prey.name.conjugate(new Verb('escape'))} from ${pred.name.possessive} ${args.container.name}.` + `${prey.name.capital} ${prey.name.conjugate(new Verb('escape'))}!` ) } diff --git a/src/game/creature.ts b/src/game/creature.ts index 632c651..4ac6552 100644 --- a/src/game/creature.ts +++ b/src/game/creature.ts @@ -7,8 +7,11 @@ import { PassAction } from '@/game/combat/actions' import { AI } from '@/game/ai' import { Entity, Resistances } from '@/game/entity' import { Perk } from '@/game/combat/perks' +import { VoreRelay } from '@/game/events' export class Creature extends Entity { + voreRelay: VoreRelay = new VoreRelay() + baseResistances: Resistances stats: Stats = (Object.keys(Stat) as Array).reduce((result: Partial, stat: Stat) => { @@ -202,6 +205,19 @@ export class Creature extends Entity { return effect.onApply(this) } + addVoreContainer (container: VoreContainer): void { + this.containers.push(container) + this.voreRelay.connect(container.voreRelay) + } + + addOtherContainer (container: Container): void { + this.otherContainers.push(container) + } + + addPerk (perk: Perk): void { + this.perks.push(perk) + } + executeAction (action: Action, target: Creature): LogEntry { const preActionResults = this.effects.map(effect => effect.preAction(this)) const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this)) diff --git a/src/game/creatures.ts b/src/game/creatures.ts index 39ca646..71a1372 100644 --- a/src/game/creatures.ts +++ b/src/game/creatures.ts @@ -1,15 +1,4 @@ -import { Wolf, DireWolf } from '@/game/creatures/wolves' -import { Player } from '@/game/creatures/player' -import { Cafat } from '@/game/creatures/cafat' -import { Human } from '@/game/creatures/human' -import { Withers } from '@/game/creatures/withers' -import { Kenzie } from '@/game/creatures/kenzie' -import { Dragon } from '@/game/creatures/dragon' -import { Shingo } from '@/game/creatures/shingo' -import { Goldeneye } from '@/game/creatures/goldeneye' -import { Kuro } from '@/game/creatures/kuro' -import { Geta } from '@/game/creatures/geta' -import { Werewolf } from '@/game/creatures/werewolf' -import { Taluthus } from '@/game/creatures/taluthus' - -export { Wolf, DireWolf, Player, Cafat, Human, Withers, Kenzie, Dragon, Shingo, Goldeneye, Kuro, Geta, Werewolf, Taluthus } +import Human from '@/game/creatures/human' +import Player from '@/game/creatures/player' +import Inazuma from '@/game/creatures/characters/inazuma' +export { Human, Player, Inazuma } diff --git a/src/game/creatures/cafat.ts b/src/game/creatures/cafat.ts deleted file mode 100644 index b06051b..0000000 --- a/src/game/creatures/cafat.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Creature } from "../creature" -import { Stat, Damage, DamageType, Vigor, ConstantDamageFormula, Side, Action } from '@/game/combat' -import { ProperNoun, TheyPronouns, ImproperNoun, FemalePronouns, Verb, PairLineArgs } from '@/game/language' -import { VoreType, Stomach, InnerStomach, VoreContainer } from '@/game/vore' -import { LogLine, LogLines, LogEntry, FAElem, ImgElem } from '@/game/interface' -import { AttackAction, TransferAction, FeedAction } from '@/game/combat/actions' -import { InstantKillEffect } from '@/game/combat/effects' -import * as Words from '@/game/words' -import { ContainsCondition } from '@/game/combat/conditions' - -class BellyCrushAction extends AttackAction { - constructor (_damage: Damage) { - super({ - calc (user) { return _damage.scale(user.voreStats.Bulk / 25) }, - describe (user) { return new LogLine('Deal ', _damage.scale(user.voreStats.Bulk / 25).renderShort(), ` with your ${user.voreStats.Bulk} `, new FAElem('fas fa-weight-hanging')) }, - explain (user) { return new LogLine('Deal ', _damage.scale(user.voreStats.Bulk / 25).renderShort(), ` with your ${user.voreStats.Bulk} `, new FAElem('fas fa-weight-hanging')) } - }) - this.name = 'Belly Crush' - this.desc = 'Use your weight!' - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Crush ${target.name} under your gut. `, this.damage.describe(user, target)) - } - - successLine: PairLineArgs = (user, target, args) => new LogLines(new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('crush', 'crushes'))} on ${target.name.objective} with ${user.pronouns.possessive} belly for `, - target.effectiveDamage(args.damage).renderShort() - ), new ImgElem('./media/cafat/images/belly-crush.webp')) -} - -class BelchAction extends AttackAction { - constructor (damage: Damage) { - super(new ConstantDamageFormula(damage)) - this.name = 'Belch' - this.desc = 'Drain your foe\'s stats with a solid BELCH' - } - - successLine: PairLineArgs = (user, target, args) => new LogLines( - new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('belch', 'belches'))} on ${target.name.objective} for `, - target.effectiveDamage(args.damage).renderShort() - ), - new ImgElem('./media/cafat/images/belch.webp') - ) -} - -class CrushAction extends Action { - constructor (private container: VoreContainer) { - super( - "Crush", - "Crush 'em!", - [ - new ContainsCondition(container) - ] - ) - this.desc = "Crush somebody in your gut" - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Crush ${target.name} in your ${this.container.name} for massive, unavoidable damage.`) - } - - execute (user: Creature, target: Creature): LogEntry { - return new LogLines(this.line(user, target, { container: this.container }), target.applyEffect(new InstantKillEffect())) - } - - line: PairLineArgs = (user, target, args) => new LogLine( - `${user.name.capital.possessive} ${args.container.name} ${Words.Brutally} ${user.name.conjugate(new Verb('crush', 'crushes'))} ${target.name.objective}; ${user.pronouns.subjective} ${user.pronouns.conjugate(new Verb('belch', 'belches'))} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `, - new ImgElem('./media/cafat/images/crunch.webp') - ) -} - -export class Cafat extends Creature { - constructor () { - super(new ProperNoun('Cafat'), new ImproperNoun('taur', 'taurs'), [TheyPronouns, FemalePronouns][Math.floor(Math.random() * 2)], { - [Stat.Toughness]: 30, - [Stat.Power]: 30, - [Stat.Agility]: 15, - [Stat.Reflexes]: 15, - [Stat.Willpower]: 25, - [Stat.Charm]: 20 - }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 150) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 1, new ConstantDamageFormula(new Damage( - { amount: 20, type: DamageType.Acid, target: Vigor.Health }, - { amount: 10, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 10, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - stomach.name = new ImproperNoun("upper stomach", "upper stomachs").all - - this.containers.push(stomach) - - const lowerStomach = new InnerStomach(this, 1.5, new ConstantDamageFormula(new Damage( - { amount: 40, type: DamageType.Acid, target: Vigor.Health }, - { amount: 20, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 20, type: DamageType.Dominance, target: Vigor.Resolve } - )), stomach) - - lowerStomach.name = new ImproperNoun("lower stomach", "lower stomachs").all - - const crush = new CrushAction(lowerStomach) - - lowerStomach.actions.push(crush) - this.containers.push(lowerStomach) - - const transfer = new TransferAction(stomach, lowerStomach) - - transfer.verb = new Verb('gulp') - - this.actions.push(transfer) - this.actions.push(new TransferAction(lowerStomach, stomach)) - - this.actions.push(new AttackAction(new ConstantDamageFormula(new Damage({ amount: 40, type: DamageType.Crush, target: Vigor.Health })))) - this.actions.push(new BellyCrushAction(new Damage({ amount: 10, type: DamageType.Crush, target: Vigor.Health }, { amount: 10, type: DamageType.Dominance, target: Vigor.Resolve }))) - this.actions.push(new BelchAction(new Damage( - { amount: 10, target: Stat.Toughness, type: DamageType.Acid }, - { amount: 10, target: Stat.Power, type: DamageType.Acid }, - { amount: 10, target: Stat.Agility, type: DamageType.Acid }, - { amount: 10, target: Stat.Willpower, type: DamageType.Acid }, - { amount: 10, target: Stat.Charm, type: DamageType.Acid } - ))) - this.otherActions.push(new FeedAction(stomach)) - } -} diff --git a/src/game/creatures/characters/inazuma.ts b/src/game/creatures/characters/inazuma.ts new file mode 100644 index 0000000..9058629 --- /dev/null +++ b/src/game/creatures/characters/inazuma.ts @@ -0,0 +1,39 @@ +import { FavorEscapedPrey, VoreAI } from '@/game/ai' +import { DamageType, Side, Stat, StatDamageFormula, Vigor } from '@/game/combat' +import { Creature } from '@/game/creature' +import { ImproperNoun, MalePronouns, ProperNoun } from '@/game/language' +import { anyVore, Stomach } from '@/game/vore' + +export default class Inazuma extends Creature { + constructor () { + super( + new ProperNoun("Inazuma"), + new ImproperNoun("zorgoia"), + MalePronouns, + { + Power: 30, + Toughness: 35, + Agility: 45, + Reflexes: 40, + Charm: 60, + Willpower: 50 + }, + new Set(), + anyVore, + 200 + ) + + this.side = Side.Monsters + this.ai = (new VoreAI(this)) + + this.ai.addDecider(new FavorEscapedPrey()) + + this.addVoreContainer(new Stomach( + this, + 2, + new StatDamageFormula([ + { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Acid } + ]) + )) + } +} diff --git a/src/game/creatures/dragon.ts b/src/game/creatures/dragon.ts deleted file mode 100644 index 60a79ed..0000000 --- a/src/game/creatures/dragon.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side } from '@/game/combat' -import { ImproperNoun, FemalePronouns, Verb } from '@/game/language' -import { VoreType, Stomach, Bowels } from '@/game/vore' -import { AttackAction, TransferAction, FeedAction } from '@/game/combat/actions' - -export class Dragon extends Creature { - constructor () { - super(new ImproperNoun('dragon', 'dragons'), new ImproperNoun('wolf', 'wolves'), FemalePronouns, { Toughness: 35, Power: 35, Reflexes: 15, Agility: 15, Willpower: 30, Charm: 20 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 300) - this.actions.push( - new AttackAction( - new ConstantDamageFormula( - new Damage( - { amount: 20, type: DamageType.Pierce, target: Vigor.Health } - ) - ), - new Verb("bite", "bites", "biting", "bit") - ) - ) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 0.5, new ConstantDamageFormula(new Damage( - { amount: 40, type: DamageType.Acid, target: Vigor.Health }, - { amount: 20, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 20, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.containers.push(stomach) - - const bowels = new Bowels(this, 0.5, new ConstantDamageFormula(new Damage( - { amount: 10, type: DamageType.Crush, target: Vigor.Health }, - { amount: 25, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 50, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.containers.push(bowels) - - this.actions.push(new TransferAction(bowels, stomach)) - this.actions.push(new TransferAction(stomach, bowels)) - - this.otherActions.push(new FeedAction(stomach)) - } -} diff --git a/src/game/creatures/geta.ts b/src/game/creatures/geta.ts deleted file mode 100644 index fd57cc6..0000000 --- a/src/game/creatures/geta.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, CompositionAction } from '@/game/combat' -import { MalePronouns, ImproperNoun, ProperNoun, Verb } from '@/game/language' -import { VoreType, Stomach, Bowels, Cock, Balls, biconnectContainers, Hand } from '@/game/vore' -import { TransferAction, FeedAction } from '@/game/combat/actions' -import { StatusConsequence, LogConsequence, ArbitraryConsequence } from '@/game/combat/consequences' -import { SizeEffect, InstantKillEffect } from '@/game/combat/effects' -import { LogLine, nilLog } from '@/game/interface' -import { TogetherCondition, MassRatioCondition, ContainsCondition } from '@/game/combat/conditions' -import { ChanceTest } from '@/game/combat/tests' - -export class Geta extends Creature { - constructor () { - super( - new ProperNoun('Geta'), - new ImproperNoun('fox', 'foxes'), - MalePronouns, - { Toughness: 10, Power: 10, Reflexes: 30, Agility: 30, Willpower: 15, Charm: 40 }, - new Set([VoreType.Oral, VoreType.Anal, VoreType.Cock]), - new Set([VoreType.Oral, VoreType.Anal, VoreType.Cock]), - 40 - ) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 0.25, new ConstantDamageFormula(new Damage( - { amount: 100, type: DamageType.Acid, target: Vigor.Health }, - { amount: 40, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 80, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - this.containers.push(stomach) - - const bowels = new Bowels(this, 0.25, new ConstantDamageFormula(new Damage( - { amount: 30, type: DamageType.Crush, target: Vigor.Health }, - { amount: 90, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 120, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.containers.push(bowels) - - this.actions.push(new TransferAction(bowels, stomach)) - - this.otherActions.push(new FeedAction(stomach)) - - const cock = new class extends Cock { - digestLine (user: Creature, target: Creature) { - return new LogLine(`${user.name.capital.possessive} ${this.name} throbs as it abruptly absorbs ${target.name.objective}, transforming ${target.name.objective} into more of ${user.pronouns.possessive} meaty shaft.`) - } - }(this, 0.25, new ConstantDamageFormula(new Damage( - { amount: 10, type: DamageType.Crush, target: Vigor.Health }, - { amount: 50, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 150, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - cock.actions.push( - new CompositionAction( - "Clench", - "Try to crush the life from your cock-snack", - { - conditions: [ - new ContainsCondition(cock) - ], - tests: [ - new ChanceTest( - 0.5, - (user, target) => new LogLine( - `${user.name.capital.possessive} cock clenches hard around ${target.name.objective}, but ${target.pronouns.subjective} ${target.name.conjugate(new Verb("avoid"))} being crushed.` - ) - ) - ], - consequences: [ - new LogConsequence( - (user, target) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('let'))} out a lustful moan as ${user.pronouns.subjective} crushes the life from ${target.name.possessive} body with a flex of ${user.pronouns.possessive} shaft.` - ) - ), - new StatusConsequence( - () => new InstantKillEffect() - ), - new ArbitraryConsequence( - () => cock.tick(0), - () => nilLog - ) - ] - } - ) - ) - - const balls = new Balls(this, 0.25, new ConstantDamageFormula(new Damage( - { amount: 50, type: DamageType.Acid, target: Vigor.Health }, - { amount: 25, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 50, type: DamageType.Dominance, target: Vigor.Resolve } - )), cock) - - this.containers.push(balls) - this.containers.push(cock) - - biconnectContainers(cock, balls) - - cock.onDigest = () => nilLog - - const shrinkAction = new CompositionAction( - "Shrink", - "Zap!", - { - conditions: [ - new TogetherCondition() - ], - consequences: [ - new LogConsequence( - () => new LogLine(`ZAP!`) - ), - new StatusConsequence( - () => new SizeEffect(0.1) - ) - ] - } - ) - this.actions.push( - shrinkAction - ) - - this.otherActions.push( - shrinkAction - ) - - const crushAction = new CompositionAction( - "Crush", - "Crush them like a bug underfoot", - { - conditions: [ - new TogetherCondition(), - new MassRatioCondition(10) - ], - consequences: [ - new LogConsequence( - (user, target) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb("raise"))} ${user.pronouns.possessive} paw over ${target.name.objective}, stomping down hard and crushing the life from ${user.pronouns.possessive} prey with a sickening CRUNCH.` - ) - ), - new StatusConsequence( - () => new InstantKillEffect() - ) - ] - } - ) - - this.actions.push( - crushAction - ) - - this.otherActions.push( - crushAction - ) - - const hand = new Hand(this, 0.25) - - this.otherContainers.push( - hand - ) - - this.actions.push( - new CompositionAction( - "Devour", - "Pop your prey into your mouth", - { - conditions: [ - new ContainsCondition(hand) - ], - consequences: [ - new ArbitraryConsequence( - (user, target) => stomach.consume(target), - () => new LogLine(`Devours the target.`) - ) - ] - } - ) - ) - - this.actions.push( - new CompositionAction( - "Grip", - "Squeeze your prey like a grape", - { - conditions: [ - new ContainsCondition(hand) - ], - consequences: [ - new LogConsequence( - (user, target) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb("crush", "crushes"))} ${target.name.objective} in ${user.pronouns.possessive} merciless grip, breaking bones and pulping ${user.pronouns.possessive} victim with ease.` - ) - ), - new StatusConsequence( - () => new InstantKillEffect() - ) - ] - } - ) - ) - } -} diff --git a/src/game/creatures/goldeneye.ts b/src/game/creatures/goldeneye.ts deleted file mode 100644 index 4282773..0000000 --- a/src/game/creatures/goldeneye.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, FractionDamageFormula, DamageFormula, UniformRandomDamageFormula, CompositionAction, CompositeDamageFormula } from '@/game/combat' -import { MalePronouns, ImproperNoun, Verb, ProperNoun, ToBe, SoloLineArgs, Noun } from '@/game/language' -import { VoreType, NormalContainer, InnerVoreContainer, Container } from '@/game/vore' -import { TransferAction } from '@/game/combat/actions' -import { LogEntry, LogLine, LogLines } from '@/game/interface' -import { ContainerFullCondition, CapableCondition, EnemyCondition, TogetherCondition } from '@/game/combat/conditions' -import { DazzlingEffect, StunEffect } from '@/game/combat/effects' -import { DamageConsequence, StatusConsequence, LogConsequence } from '@/game/combat/consequences' - -class GoldeneyeCrop extends NormalContainer { - consumeVerb: Verb = new Verb('swallow') - releaseVerb: Verb = new Verb('free') - struggleVerb: Verb = new Verb('struggle', 'struggles', 'struggling', 'struggled') - - constructor (owner: Creature) { - super( - new ImproperNoun('crop').all, - owner, - new Set([VoreType.Oral]), - 300 - ) - } -} - -class Taunt extends GroupAction { - damage: DamageFormula = new UniformRandomDamageFormula( - new Damage( - { amount: 50, target: Vigor.Resolve, type: DamageType.Dominance } - ), - 0.5 - ) - - constructor () { - super( - "Taunt", - "Demoralize your enemies", - [ - new EnemyCondition(), - new CapableCondition() - ] - ) - } - - describeGroup (user: Creature, targets: Creature[]): LogEntry { - return new LogLine(`Demoralize your foes`) - } - - execute (user: Creature, target: Creature): LogEntry { - return target.takeDamage(this.damage.calc(user, target)) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Demoralize your foes`) - } -} -class Flaunt extends GroupAction { - constructor (public container: Container) { - super( - "Flaunt " + container.name, - "Show off your " + container.name, - [new ContainerFullCondition(container), new CapableCondition(), new EnemyCondition(), new TogetherCondition()] - ) - } - - groupLine: SoloLineArgs = (user, args) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('show'))} off ${user.pronouns.possessive} squirming ${args.container.name}, ${user.pronouns.possessive} doomed prey writhing beneath ${user.pronouns.possessive} pelt.` - ) - - describeGroup (user: Creature, targets: Creature[]): LogEntry { - return new LogLine(`Flaunt your bulging ${this.container.name} for all your foes to see`) - } - - execute (user: Creature, target: Creature): LogEntry { - const fracDamage = new FractionDamageFormula([ - { fraction: 0.25, target: Vigor.Resolve, type: DamageType.Dominance } - ]) - const flatDamage = new ConstantDamageFormula( - new Damage( - { amount: 50, target: Vigor.Resolve, type: DamageType.Dominance } - ) - ) - - const damage = fracDamage.calc(user, target).combine(flatDamage.calc(user, target)) - - return new LogLines( - new LogLine(`${target.name.capital} ${target.name.conjugate(new ToBe())} shaken for `, damage.renderShort(), '.'), - target.takeDamage(damage) - ) - } - - executeGroup (user: Creature, targets: Array): LogEntry { - return new LogLines(...[this.groupLine(user, { container: this.container })].concat(targets.map(target => this.execute(user, target)))) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Flaunt your bulging gut for all your foes to see`) - } -} - -class GoldeneyeStomach extends InnerVoreContainer { - fluidName = new Noun("chyme") - - consumeVerb: Verb = new Verb('swallow') - releaseVerb: Verb = new Verb('free') - struggleVerb: Verb = new Verb('struggle', 'struggles', 'struggling', 'struggled') - - constructor (owner: Creature, crop: GoldeneyeCrop) { - super( - new ImproperNoun('stomach').all, - owner, - new Set([VoreType.Oral]), - 900, - new ConstantDamageFormula( - new Damage( - { amount: 1000, target: Vigor.Health, type: DamageType.Acid } - ) - ), - crop - ) - } -} - -export class Goldeneye extends Creature { - constructor () { - super( - new ProperNoun("Goldeneye"), - new ImproperNoun('gryphon', 'gryphons'), - MalePronouns, - { Toughness: 200, Power: 200, Reflexes: 200, Agility: 200, Willpower: 200, Charm: 200 }, - new Set(), - new Set([VoreType.Oral]), - 2000 - ) - - this.title = "Not really a gryphon" - this.desc = "Not really survivable, either." - - this.side = Side.Monsters - this.applyEffect(new DazzlingEffect([ - new EnemyCondition(), - new TogetherCondition() - ])) - - const crop = new GoldeneyeCrop(this) - const stomach = new GoldeneyeStomach(this, crop) - - this.containers.push(stomach) - this.otherContainers.push(crop) - - this.actions.push( - new TransferAction( - crop, - stomach - ) - ) - - this.groupActions.push(new Flaunt(crop)) - this.groupActions.push(new Flaunt(stomach)) - this.groupActions.push(new Taunt()) - - this.actions.push(new CompositionAction( - "Stomp", - "Big step", - { - conditions: [ - new TogetherCondition(), - new EnemyCondition() - ], - consequences: [ - new LogConsequence( - (user, target) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('stomp'))} on ${target.name.objective} with crushing force!` - ) - ), - new DamageConsequence( - new CompositeDamageFormula([ - new FractionDamageFormula([ - { fraction: 0.75, target: Vigor.Health, type: DamageType.Pure } - ]), - new ConstantDamageFormula( - new Damage( - { amount: 50, target: Vigor.Health, type: DamageType.Crush } - ) - ) - ]) - ), - new StatusConsequence( - () => new StunEffect(3) - ) - ] - } - )) - } -} diff --git a/src/game/creatures/human.ts b/src/game/creatures/human.ts index 5e7cf82..e3dd88e 100644 --- a/src/game/creatures/human.ts +++ b/src/game/creatures/human.ts @@ -6,7 +6,7 @@ import { StatusConsequence } from '@/game/combat/consequences' import { SurrenderEffect } from '@/game/combat/effects' import { SoloCondition } from '@/game/combat/conditions' -export class Human extends Creature { +export default class Human extends Creature { constructor (name: Noun, pronouns: Pronoun, options: { vigors?: Vigor; stats?: Stats; diff --git a/src/game/creatures/kenzie.ts b/src/game/creatures/kenzie.ts deleted file mode 100644 index 3ce234c..0000000 --- a/src/game/creatures/kenzie.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Creature } from "../creature" -import { ProperNoun, ImproperNoun, FemalePronouns, Verb } from '@/game/language' -import { VoreType, Stomach } from '@/game/vore' -import { Side, Damage, DamageType, Vigor, StatDamageFormula, Stat, VoreStat, DamageFormula, ConstantDamageFormula } from '@/game/combat' -import { AttackAction, DevourAction } from '@/game/combat/actions' -import { LogEntry, LogLines } from '@/game/interface' -import { StunEffect, PredatorCounterEffect } from '@/game/combat/effects' - -class StompAttack extends AttackAction { - constructor (protected damage: DamageFormula, protected verb: Verb = new Verb('smack')) { - super( - damage, - verb - ) - } - - execute (user: Creature, target: Creature): LogEntry { - const damage = this.damage.calc(user, target) - const targetResult = target.takeDamage(damage) - const ownResult = this.successLine(user, target, { damage: damage }) - const effResult = target.applyEffect(new StunEffect(3)) - return new LogLines(ownResult, targetResult, effResult) - } -} -export class Kenzie extends Creature { - title = "Large Lycanroc" - desc = "Will eat your party" - - constructor () { - super( - new ProperNoun('Kenzie'), - new ImproperNoun('lycanroc', 'lycanrocs'), - FemalePronouns, - { Toughness: 25, Power: 35, Reflexes: 20, Agility: 20, Willpower: 20, Charm: 30 }, - new Set([VoreType.Oral]), - new Set([VoreType.Oral]), - 1000 - ) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 0.1, new ConstantDamageFormula(new Damage( - { amount: 100, type: DamageType.Acid, target: Vigor.Health }, - { amount: 100, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 100, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.applyEffect(new PredatorCounterEffect(new DevourAction(stomach), 0.4)) - - this.containers.push(stomach) - - this.actions.push( - new StompAttack( - new StatDamageFormula([ - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.05, stat: VoreStat.Bulk, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.01, stat: VoreStat.Bulk, target: Stat.Toughness, type: DamageType.Crush } - ]), - new Verb('stomp') - ) - ) - } -} diff --git a/src/game/creatures/kuro.ts b/src/game/creatures/kuro.ts deleted file mode 100644 index ff2303b..0000000 --- a/src/game/creatures/kuro.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side } from '@/game/combat' -import { MalePronouns, ProperNoun } from '@/game/language' -import { VoreType, Stomach, Bowels, Cock, Balls, biconnectContainers } from '@/game/vore' -import { TransferAction, FeedAction } from '@/game/combat/actions' - -export class Kuro extends Creature { - constructor () { - super( - new ProperNoun('Kuro'), - new ProperNoun('Luxray'), - MalePronouns, - { Toughness: 20, Power: 30, Reflexes: 50, Agility: 50, Willpower: 30, Charm: 50 }, - new Set(), - new Set([VoreType.Oral, VoreType.Anal, VoreType.Cock]), - 100 - ) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 0.5, new ConstantDamageFormula(new Damage( - { amount: 100, type: DamageType.Acid, target: Vigor.Health }, - { amount: 40, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 80, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - this.containers.push(stomach) - - const bowels = new Bowels(this, 0.5, new ConstantDamageFormula(new Damage( - { amount: 30, type: DamageType.Crush, target: Vigor.Health }, - { amount: 90, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 120, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.containers.push(bowels) - - this.actions.push(new TransferAction(bowels, stomach)) - - this.otherActions.push(new FeedAction(stomach)) - - const cock = new Cock(this, 0.5, new ConstantDamageFormula(new Damage( - { amount: 10, type: DamageType.Crush, target: Vigor.Health }, - { amount: 30, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 30, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - const balls = new Balls(this, 0.5, new ConstantDamageFormula(new Damage( - { amount: 50, type: DamageType.Acid, target: Vigor.Health }, - { amount: 25, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 150, type: DamageType.Dominance, target: Vigor.Resolve } - )), cock) - - this.containers.push(balls) - this.containers.push(cock) - - biconnectContainers(cock, balls) - } -} diff --git a/src/game/creatures/player.ts b/src/game/creatures/player.ts index a810924..9d4c8e1 100644 --- a/src/game/creatures/player.ts +++ b/src/game/creatures/player.ts @@ -5,7 +5,7 @@ import { Stomach, Bowels, anyVore, Cock, Balls, Breasts, InnerBladder, Slit, Wom import { AttackAction } from '@/game/combat/actions' import { RavenousPerk, BellyBulwakPerk, FlauntPerk } from '@/game/combat/perks' -export class Player extends Creature { +export default class Player extends Creature { constructor () { super( new ProperNoun('Player'), @@ -20,37 +20,8 @@ export class Player extends Creature { this.actions.push(new AttackAction(new ConstantDamageFormula(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }, { type: DamageType.Pierce, amount: 20, target: Vigor.Stamina })))) const stomach = new Stomach(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Acid, target: Vigor.Health }, { amount: 10, type: DamageType.Crush, target: Vigor.Health }))) - this.containers.push(stomach) - - const bowels = new Bowels(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health }))) - this.containers.push(bowels) - - const cock = new Cock(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health }))) - this.containers.push(cock) - - const balls = new Balls(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })), cock) - this.containers.push(balls) - - const slit = new Slit(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health }))) - this.containers.push(slit) - - const womb = new Womb(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })), slit) - this.containers.push(womb) - - const bladder = new InnerBladder(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })), cock) - this.containers.push(bladder) - - const breasts = new Breasts(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health }))) - this.containers.push(breasts) - - biconnectContainers(cock, balls) - biconnectContainers(cock, bladder) - biconnectContainers(slit, womb) + this.addVoreContainer(stomach) this.perspective = POV.Second - - this.perks.push(new RavenousPerk()) - this.perks.push(new BellyBulwakPerk()) - this.perks.push(new FlauntPerk()) } } diff --git a/src/game/creatures/shingo.ts b/src/game/creatures/shingo.ts deleted file mode 100644 index 6fb318f..0000000 --- a/src/game/creatures/shingo.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, StatDamageFormula, Stat, DamageFormula, Condition, FractionDamageFormula } from '@/game/combat' -import { ImproperNoun, ProperNoun, MalePronouns, Verb, PairLineArgs, TextLike } from '@/game/language' -import { VoreType, Stomach, NormalContainer, Container } from '@/game/vore' -import { AttackAction, TransferAction, FeedAction, StruggleAction, DamageAction } from '@/game/combat/actions' -import { LogLine, LogEntry } from '@/game/interface' -import { ContainsCondition, CapableCondition, EnemyCondition, TargetDrainedVigorCondition } from '@/game/combat/conditions' - -export class TrappedAction extends DamageAction { - constructor (name: TextLike, desc: TextLike, protected verb: Verb, protected damage: DamageFormula, container: Container, conditions: Condition[] = []) { - super( - name, - desc, - damage, - [], - [new CapableCondition(), new ContainsCondition(container), new EnemyCondition()].concat(conditions) - ) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Chew on ${target.name}. `, this.damage.describe(user, target), '. ') - } - - successLine: PairLineArgs = (user, target, args) => new LogLine( - `${user.name.capital} ${user.name.conjugate(this.verb)} ${target.name.objective} for `, - target.effectiveDamage(args.damage).renderShort() - ) -} - -class Hand extends NormalContainer { - consumeVerb: Verb = new Verb('grab', 'grabs', 'grabbing', 'grabbed') - releaseVerb: Verb = new Verb('release', 'releases', 'releasing', 'released') - struggleVerb: Verb = new Verb('squirm', 'squirms', 'squirming', 'squirmed') - - constructor (owner: Creature) { - super(new ImproperNoun('hand', 'hands'), owner, new Set(), 0.1) - this.actions.push(new TrappedAction( - "Grip", - "Give your victim the squeeze", - new Verb( - 'squeeze' - ), - new ConstantDamageFormula( - new Damage( - { amount: 200, target: Vigor.Health, type: DamageType.Crush } - ) - ), - this - )) - } - - consumeLine (user: Creature, target: Creature) { - return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} up in ${user.pronouns.possessive} ${this.name}, giving ${target.pronouns.objective} a firm squeeze in ${user.pronouns.possessive} fingers.`) - } -} - -class Paw extends NormalContainer { - consumeVerb: Verb = new Verb('pin', 'pins', 'pinning', 'pinbed') - releaseVerb: Verb = new Verb('release', 'releases', 'releasing', 'released') - struggleVerb: Verb = new Verb('squirm', 'squirms', 'squirming', 'squirmed') - - constructor (owner: Creature) { - super(new ImproperNoun('paw', 'paws'), owner, new Set([VoreType.Oral]), 0.1) - - this.actions.push(new TrappedAction( - "Smother", - "Bury your victim under your toes", - new Verb( - 'smother' - ), - new ConstantDamageFormula( - new Damage( - { amount: 100, target: Vigor.Stamina, type: DamageType.Crush }, - { amount: 3, target: Stat.Toughness, type: DamageType.Crush }, - { amount: 5, target: Stat.Power, type: DamageType.Crush }, - { amount: 10, target: Stat.Agility, type: DamageType.Crush } - ) - ), - this - )) - this.actions.push(new TrappedAction( - "Crush", - "Finish them off", - new Verb( - 'crush', - 'crushes' - ), - new FractionDamageFormula( - [ - { fraction: 1, target: Stat.Toughness, type: DamageType.Pure }, - { fraction: 1, target: Stat.Power, type: DamageType.Pure }, - { fraction: 1, target: Stat.Agility, type: DamageType.Pure }, - { fraction: 1, target: Stat.Willpower, type: DamageType.Pure }, - { fraction: 1, target: Stat.Charm, type: DamageType.Pure } - ] - ), - this, - [new TargetDrainedVigorCondition(Vigor.Stamina)] - )) - } - - consumeLine (user: Creature, target: Creature) { - return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} beneath ${user.pronouns.possessive} ${this.name}.`) - } -} - -class Maw extends NormalContainer { - consumeVerb: Verb = new Verb('grab', 'grabs', 'grabbing', 'grabbed') - releaseVerb: Verb = new Verb('release', 'releases', 'releasing', 'released') - struggleVerb: Verb = new Verb('squirm', 'squirms', 'squirming', 'squirmed') - - constructor (owner: Creature) { - super(new ImproperNoun('maw', 'maws'), owner, new Set([VoreType.Oral]), 100) - - this.actions = [] - this.actions.push(new StruggleAction(this)) - this.actions.push(new TrappedAction( - "Chew", - "Chew on your victim", - new Verb( - 'bite down on', - 'bites down on', - 'biting down on', - 'bit down on' - ), - new ConstantDamageFormula( - new Damage( - { amount: 200, target: Vigor.Health, type: DamageType.Crush } - ) - ), - this - )) - } -} - -export class Shingo extends Creature { - constructor () { - super( - new ProperNoun('Shingo'), - new ImproperNoun('red panda', 'red pandas'), - MalePronouns, - { Toughness: 40, Power: 50, Reflexes: 30, Agility: 30, Willpower: 30, Charm: 60 }, - new Set(), - new Set([VoreType.Oral]), - 3000 - ) - - this.actions.push( - new AttackAction( - new StatDamageFormula( - [ - { fraction: 0.5, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush } - ] - ) - ) - ) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 0.1, new ConstantDamageFormula(new Damage( - { amount: 200, type: DamageType.Acid, target: Vigor.Health }, - { amount: 100, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 100, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.containers.push(stomach) - - this.otherActions.push(new FeedAction(stomach)) - - const hand = new Hand(this) - const maw = new Maw(this) - this.otherContainers.push(hand) - this.otherContainers.push(maw) - - const stuff = new TransferAction(hand, maw) - stuff.verb = new Verb('stuff') - stuff.line = (user, target, args) => new LogLine( - `${user.name.capital} ${user.name.conjugate(stuff.verb)} ${target.name.objective} into ${user.pronouns.possessive} ${args.to.name}!` - ) - this.actions.push(stuff) - - const gulp = new TransferAction(maw, stomach) - gulp.verb = new Verb('gulp') - gulp.line = (user, target, args) => new LogLine( - `${user.name.capital} ${user.name.conjugate(gulp.verb)} ${target.name.objective} down ${user.pronouns.possessive} tight, inescapable gullet, sending ${target.pronouns.objective} down to ${user.pronouns.possessive} ${args.to.name}` - ) - this.actions.push(gulp) - - this.otherContainers.push(new Paw(this)) - } -} diff --git a/src/game/creatures/taluthus.ts b/src/game/creatures/taluthus.ts deleted file mode 100644 index 013900b..0000000 --- a/src/game/creatures/taluthus.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Creature } from "../creature" -import { DamageType, Vigor, Side, StatDamageFormula, Stat } from '@/game/combat' -import { MalePronouns, ProperNoun, Verb } from '@/game/language' -import { Stomach, Bowels, Cock, Balls, anyVore, biconnectContainers, Tail } from '@/game/vore' -import { AttackAction, TransferAction, FeedAction, WillingTransferAction } from '@/game/combat/actions' -import { VoreAI } from '@/game/ai' -import { LogLine } from '@/game/interface' - -export class Taluthus extends Creature { - constructor () { - super( - new ProperNoun('Taluthus'), - new ProperNoun('Taluthus'), - MalePronouns, - { Toughness: 40, Power: 50, Reflexes: 40, Agility: 30, Willpower: 50, Charm: 60 }, - new Set(), - anyVore, - 100 - ) - this.actions.push( - new AttackAction( - new StatDamageFormula([ - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush } - ]), - new Verb("bite") - ) - ) - - this.ai = new VoreAI() - - this.side = Side.Monsters - - const stomach = new Stomach( - this, - 1, - new StatDamageFormula([ - { fraction: 1, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]) - ) - - this.containers.push(stomach) - - const tail = new Tail( - this, - 1.5, - new StatDamageFormula([ - { fraction: 0.05, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 0.1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]) - ) - - this.containers.push(tail) - - this.actions.push(new TransferAction(tail, stomach)) - this.otherActions.push(new WillingTransferAction(tail, stomach)) - - this.otherActions.push(new FeedAction(stomach)) - - const cock = new Cock( - this, - 1.5, - new StatDamageFormula([ - { fraction: 1, stat: Stat.Charm, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Charm, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Charm, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]) - ) - - const balls = new Balls( - this, - 1.5, - new StatDamageFormula([ - { fraction: 1, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Toughness, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1.5, stat: Stat.Toughness, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 1.5, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]), - cock - ) - - this.containers.push(balls) - this.containers.push(cock) - - biconnectContainers(cock, balls) - - this.destroyLine = victim => new LogLine(`${victim.name.capital} ${victim.name.conjugate(new Verb("yeet"))}`) - } -} diff --git a/src/game/creatures/werewolf.ts b/src/game/creatures/werewolf.ts deleted file mode 100644 index f277aa7..0000000 --- a/src/game/creatures/werewolf.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Creature } from "../creature" -import { DamageType, Vigor, Side, StatDamageFormula, Stat } from '@/game/combat' -import { MalePronouns, ImproperNoun, Verb } from '@/game/language' -import { Stomach, Bowels, Cock, Balls, anyVore, biconnectContainers } from '@/game/vore' -import { AttackAction, TransferAction, FeedAction } from '@/game/combat/actions' -import { VoreAI } from '@/game/ai' - -export class Werewolf extends Creature { - constructor () { - super( - new ImproperNoun('werewolf', 'werewolves'), - new ImproperNoun('werewolf', 'werewolves'), - MalePronouns, - { Toughness: 40, Power: 50, Reflexes: 40, Agility: 30, Willpower: 20, Charm: 50 }, - anyVore, - anyVore, - 100 - ) - this.actions.push( - new AttackAction( - new StatDamageFormula([ - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush } - ]), - new Verb("bite") - ) - ) - - this.ai = new VoreAI() - - this.side = Side.Monsters - - const stomach = new Stomach( - this, - 1, - new StatDamageFormula([ - { fraction: 1, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]) - ) - - this.containers.push(stomach) - - const bowels = new Bowels( - this, - 1.5, - new StatDamageFormula([ - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Toughness, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Toughness, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]) - ) - - this.containers.push(bowels) - - this.actions.push(new TransferAction(bowels, stomach)) - - this.otherActions.push(new FeedAction(stomach)) - - const cock = new Cock( - this, - 1.5, - new StatDamageFormula([ - { fraction: 1, stat: Stat.Charm, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.5, stat: Stat.Charm, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 0.5, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Charm, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]) - ) - - const balls = new Balls( - this, - 1.5, - new StatDamageFormula([ - { fraction: 1, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Toughness, target: Vigor.Stamina, type: DamageType.Acid }, - { fraction: 1, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1.5, stat: Stat.Toughness, target: Vigor.Resolve, type: DamageType.Acid }, - { fraction: 1.5, stat: Stat.Power, target: Vigor.Resolve, type: DamageType.Crush } - ]), - cock - ) - - this.containers.push(balls) - this.containers.push(cock) - - biconnectContainers(cock, balls) - } -} diff --git a/src/game/creatures/withers.ts b/src/game/creatures/withers.ts deleted file mode 100644 index 2d5cea3..0000000 --- a/src/game/creatures/withers.ts +++ /dev/null @@ -1,362 +0,0 @@ -import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, Stat, DamageFormula, UniformRandomDamageFormula, Action, DamageInstance, StatDamageFormula, VoreStat } from '@/game/combat' -import { ImproperNoun, ProperNoun, FemalePronouns, RandomWord, Adjective, Verb, PairLine } from '@/game/language' -import { LogLine, LogLines, LogEntry, Newline } from '@/game/interface' -import { VoreType, Stomach, VoreContainer, NormalContainer, Container } from '@/game/vore' -import { AttackAction, FeedAction, TransferAction } from '@/game/combat/actions' -import { TogetherCondition, ContainsCondition, EnemyCondition, AllyCondition, PairCondition, CapableCondition } from '@/game/combat/conditions' -import { InstantKillEffect, DamageTypeResistanceEffect } from '@/game/combat/effects' -import * as Words from '@/game/words' - -class LevelDrain extends Action { - constructor (private container: Container) { - super( - 'Level Drain', - 'Drain energy from your prey', - [ - new ContainsCondition(container), - new CapableCondition() - ] - ) - } - - execute (user: Creature, target: Creature): LogEntry { - const damage: Damage = new Damage(...Object.keys(Stat).map(stat => { - return { - type: DamageType.Acid, - target: stat as Stat, - amount: target.baseStats[stat as Stat] / 5 - } - })) - const heal: Damage = new Damage(...Object.keys(Stat).map(stat => { - return { - type: DamageType.Heal, - target: stat as Stat, - amount: target.baseStats[stat as Stat] / 5 - } - })) - - // TODO make this respect resistances - - user.takeDamage(heal) - const targetResult = target.takeDamage(damage) - - return new LogLines( - new LogLine(`${user.name.capital.possessive} ${this.container.name} drains power from ${target.name.objective}, siphoning `, damage.renderShort(), ` from ${target.pronouns.possessive} body!`), - targetResult - ) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Drain energy from ${target.name}`) - } -} -class HypnotizeAction extends Action { - constructor () { - super( - `Hypnotize`, - `Change their mind!`, - [ - new TogetherCondition(), - new EnemyCondition(), - new CapableCondition() - ] - ) - } - - line: PairLine = (user, target) => new LogLine( - `${user.name.capital.possessive} hypnotic gaze enthralls ${target.name}, putting ${target.pronouns.objective} under ${user.pronouns.possessive} control!` - ) - - execute (user: Creature, target: Creature): LogEntry { - target.side = user.side - return this.line(user, target) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Force your target to fight by your side`) - } -} -class MawContainer extends NormalContainer { - consumeVerb = new Verb('grab', 'grabs', 'grabbing', 'grabbed') - releaseVerb = new Verb('release') - struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled') - - constructor (owner: Creature, stomach: VoreContainer) { - super(new ImproperNoun('maw'), owner, new Set([VoreType.Oral]), 0.05) - - const transfer = new TransferAction(this, stomach) - transfer.verb = new Verb('gulp') - this.actions.push(transfer) - } -} - -class FlexToesAction extends GroupAction { - constructor (private damage: DamageFormula, container: Container) { - super('Flex Toes', 'Flex your toes!', [ - new ContainsCondition(container), - new PairCondition() - ]) - } - -line = (user: Creature, target: Creature, args: { damage: Damage }) => new LogLine(`${user.name.capital.possessive} toes crush ${target.name.objective} for `, args.damage.renderShort(), ` damage!`) - -describeGroup (user: Creature, targets: Creature[]): LogEntry { - return new LogLine(`Flex your toes. `, this.damage.explain(user)) -} - -execute (user: Creature, target: Creature): LogEntry { - const damage = this.damage.calc(user, target) - return new LogLines(target.takeDamage(damage), this.line(user, target, { damage: damage })) -} - -describe (user: Creature, target: Creature): LogEntry { - return new LogLine(`Flex your toes! `, this.damage.describe(user, target)) -} -} - -class BootContainer extends NormalContainer { - consumeVerb = new Verb('trap', 'traps', 'trapped', 'trapping') - releaseVerb = new Verb('dump') - struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled') - - constructor (owner: Creature) { - super(new ImproperNoun('boot'), owner, new Set(), 0.05) - - const flex = new FlexToesAction( - new UniformRandomDamageFormula(new Damage( - { target: Stat.Toughness, type: DamageType.Crush, amount: 10 }, - { target: Stat.Power, type: DamageType.Crush, amount: 10 }, - { target: Stat.Agility, type: DamageType.Crush, amount: 10 }, - { target: Stat.Willpower, type: DamageType.Crush, amount: 30 }, - { target: Stat.Charm, type: DamageType.Crush, amount: 10 } - ), 0.5), - this - ) - - this.actions.push(flex) - } -} - -const huge = new RandomWord([ - new Adjective('massive'), - new Adjective('colossal'), - new Adjective('big ol\''), - new Adjective('heavy'), - new Adjective('crushing'), - new Adjective('huge') -]) - -class BiteAction extends AttackAction { - constructor () { - super( - new ConstantDamageFormula(new Damage({ amount: 50, type: DamageType.Slash, target: Vigor.Health })), - new Verb('bite', 'bites', 'biting', 'bit') - ) - this.name = "Bite" - } -} - -class ChewAction extends GroupAction { - constructor (private damage: DamageFormula, container: Container, private killAction: Action) { - super('Chew', 'Give them the big chew', [ - new ContainsCondition(container) - ]) - } - - line = (user: Creature, target: Creature, args: { damage: Damage }) => new LogLine(`${user.name.capital} chews on ${target.name.objective} for `, args.damage.renderShort(), `!`) - - describeGroup (user: Creature, targets: Creature[]): LogEntry { - return new LogLine('Crunch \'em all. ', this.damage.explain(user)) - } - - execute (user: Creature, target: Creature): LogEntry { - const damage = this.damage.calc(user, target) - const results: Array = [] - results.push(this.line(user, target, { damage: damage })) - results.push(new LogLine(' ')) - results.push(target.takeDamage(damage)) - - if (target.vigors.Health <= 0) { - if (this.killAction.allowed(user, target)) { - results.push(new Newline()) - results.push(this.killAction.execute(user, target)) - } - } - - return new LogLine(...results) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine('Do the crunch') - } -} - -class StompAction extends GroupAction { - constructor () { - super('Stomp', 'STOMP!', [ - new TogetherCondition(), - new EnemyCondition(), - new CapableCondition() - ]) - } - - line: PairLine = (user, target) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('flatten'))} ${target.name.objective} under ${user.pronouns.possessive} ${huge} foot!` - ) - - execute (user: Creature, target: Creature): LogEntry { - return new LogLines(this.line(user, target), target.applyEffect(new InstantKillEffect())) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine('Stomp one sucker') - } - - describeGroup (user: Creature, targets: Array): LogEntry { - return new LogLine('Stomp all ', targets.length.toString(), ' of \'em!') - } -} - -class StompAllyAction extends Action { - constructor () { - super('Stomp Ally', '-1 ally, +1 buff', [ - new TogetherCondition(), - new AllyCondition(), - new CapableCondition() - ]) - } - - line: PairLine = (user, target) => new LogLine( - `${user.name.capital} ${user.name.conjugate(new Verb('flatten'))} ${target.name.objective} under ${user.pronouns.possessive} ${huge} boot!` - ) - - execute (user: Creature, target: Creature): LogEntry { - const damages: Array = Object.keys(Stat).map(stat => ({ - target: stat as Stat, - amount: target.stats[stat as Stat] / 3, - type: DamageType.Heal - })) - - const heal = new Damage( - ...damages - ) - user.takeDamage(heal) - target.destroyed = true - - return new LogLines( - this.line(user, target), - target.applyEffect(new InstantKillEffect()), - user.applyEffect(new DamageTypeResistanceEffect( - [DamageType.Crush, DamageType.Slash, DamageType.Pierce], - 0.5 - )), - new LogLine(`${user.name.capital} absorbs ${target.pronouns.possessive} power, gaining `, heal.renderShort()) - ) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine('Crush an ally to absorb their power') - } -} - -class DevourAllAction extends GroupAction { - constructor (private container: VoreContainer) { - super('Devour All', 'GULP!', [ - new TogetherCondition(), - new EnemyCondition(), - new CapableCondition() - ]) - } - - line = (user: Creature, target: Creature) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('scoop'))} ${target.name} up!`) - - groupLine = (user: Creature, args: { count: number }) => new LogLine(`${Words.SwallowSound.allCaps}! All ${args.count} of ${user.pronouns.possessive} prey pour down ${user.name.possessive} ${Words.Slick} gullet as ${user.pronouns.subjective} ${user.name.conjugate(Words.Swallow)}; they're just ${user.kind.all} chow now`) - - execute (user: Creature, target: Creature): LogEntry { - this.container.consume(target) - return new LogLines(this.line(user, target)) - } - - describe (user: Creature, target: Creature): LogEntry { - return new LogLine('Stomp one sucker') - } - - executeGroup (user: Creature, targets: Array): LogEntry { - return new LogLines(...targets.map(target => this.execute(user, target)).concat( - [ - new Newline(), - this.groupLine(user, { count: targets.length }) - ] - )) - } - - describeGroup (user: Creature, targets: Array): LogEntry { - return new LogLine('Eat all ', targets.length.toString(), ' of \'em!') - } -} - -export class Withers extends Creature { - title = "Huge Hellhound" - desc = "Will eat your party" - - constructor () { - super( - new ProperNoun('Withers'), - new ImproperNoun('hellhound', 'hellhounds'), - FemalePronouns, - { Toughness: 40, Power: 50, Reflexes: 30, Agility: 30, Willpower: 40, Charm: 70 }, - new Set(), - new Set([VoreType.Oral]), - 5000 - ) - - this.actions.push(new BiteAction()) - this.groupActions.push(new StompAction()) - - this.side = Side.Monsters - - const stomach = new Stomach(this, 0.1, new ConstantDamageFormula(new Damage( - { amount: 300, type: DamageType.Acid, target: Vigor.Health }, - { amount: 200, type: DamageType.Crush, target: Vigor.Stamina }, - { amount: 200, type: DamageType.Dominance, target: Vigor.Resolve } - ))) - - this.containers.push(stomach) - this.otherActions.push(new FeedAction(stomach)) - - this.groupActions.push(new DevourAllAction(stomach)) - - const maw = new MawContainer(this, stomach) - - this.otherContainers.push(maw) - - const transfer = new TransferAction(maw, stomach) - transfer.verb = new Verb('gulp') - - this.actions.push(new ChewAction( - new UniformRandomDamageFormula(new Damage( - { target: Vigor.Health, type: DamageType.Crush, amount: 10000 } - ), 0.5), - maw, - transfer - )) - - const boot = new BootContainer(this) - this.otherContainers.push(boot) - this.actions.push(new StompAllyAction()) - - this.actions.push( - new AttackAction( - new StatDamageFormula([ - { fraction: 0.5, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Crush }, - { fraction: 0.05, stat: VoreStat.Bulk, target: Vigor.Health, type: DamageType.Crush } - ]), - new Verb('stomp') - ) - ) - - this.actions.push(new HypnotizeAction()) - this.actions.push(new LevelDrain(stomach)) - } -} diff --git a/src/game/creatures/wolves.ts b/src/game/creatures/wolves.ts deleted file mode 100644 index 091b6fb..0000000 --- a/src/game/creatures/wolves.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Creature } from "../creature" -import { DamageType, Vigor, Side, StatDamageFormula, Stat } from '@/game/combat' -import { MalePronouns, ImproperNoun, FemalePronouns, TheyPronouns, Verb } from '@/game/language' -import { Stomach, anyVore } from '@/game/vore' -import { AttackAction } from '@/game/combat/actions' -import { VoreAI } from '@/game/ai' -import { RavenousPerk } from '@/game/combat/perks' - -export class Wolf extends Creature { - constructor () { - super( - new ImproperNoun('wolf', 'wolves'), - new ImproperNoun('wolf', 'wolves'), - [MalePronouns, FemalePronouns, TheyPronouns][Math.floor(Math.random() * 3)], - { Toughness: 15, Power: 20, Reflexes: 25, Agility: 25, Willpower: 5, Charm: 10 }, - anyVore, - anyVore, - 25 - ) - - this.side = Side.Monsters - this.ai = new VoreAI() - - this.actions.push( - new AttackAction( - new StatDamageFormula([ - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush } - ]), - new Verb("bite") - ) - ) - - const stomach = new Stomach(this, 2, new StatDamageFormula([ - { fraction: 3, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 2, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Charm, target: Vigor.Resolve, type: DamageType.Dominance } - ])) - this.containers.push(stomach) - } -} - -export class DireWolf extends Creature { - constructor () { - super( - new ImproperNoun('dire wolf', 'dire wolves'), - new ImproperNoun('wolf', 'wolves'), - [MalePronouns, FemalePronouns, TheyPronouns][Math.floor(Math.random() * 3)], - { Toughness: 25, Power: 40, Reflexes: 35, Agility: 45, Willpower: 15, Charm: 20 }, - anyVore, - anyVore, - 75 - ) - - this.side = Side.Monsters - this.ai = new VoreAI() - - this.perks.push(new RavenousPerk()) - - this.actions.push( - new AttackAction( - new StatDamageFormula([ - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Pierce }, - { fraction: 1, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush } - ]), - new Verb("bite") - ) - ) - - const stomach = new Stomach(this, 2, new StatDamageFormula([ - { fraction: 3, stat: Stat.Toughness, target: Vigor.Health, type: DamageType.Acid }, - { fraction: 2, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }, - { fraction: 1, stat: Stat.Charm, target: Vigor.Resolve, type: DamageType.Dominance } - ])) - this.containers.push(stomach) - } -} diff --git a/src/game/events.ts b/src/game/events.ts new file mode 100644 index 0000000..7d00e32 --- /dev/null +++ b/src/game/events.ts @@ -0,0 +1,53 @@ +import { TargetDrainedVigorCondition } from '@/game/combat/conditions' +import { Creature } from '@/game/creature' +import { LogEntry, LogLines, nilLog } from "@/game/interface" +import { VoreContainer } from '@/game/vore' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +abstract class Relay { + private subscriptions: { [K in keyof EventMap]: Array<(sender: Sender, args: EventMap[K]) => LogEntry> } + + constructor (private eventNames: Array) { + const partialSubscriptions: Partial<{ [K in keyof EventMap]?: Array<(sender: Sender, args: EventMap[K]) => LogEntry>}> = {} + this.eventNames.forEach(name => { + partialSubscriptions[name] = [] + }) + this.subscriptions = partialSubscriptions as Required<{ [K in keyof EventMap]: Array<(sender: Sender, args: EventMap[K]) => LogEntry> }> + } + + subscribe (name: K, callback: (sender: Sender, args: EventMap[K]) => LogEntry): void { + if (this.subscriptions[name] === undefined) { + this.subscriptions[name] = [] + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.subscriptions[name]!.push(callback) + } + + dispatch (name: K, sender: Sender, args: EventMap[K]): LogEntry { + const subscriptionList = this.subscriptions[name] + if (subscriptionList !== undefined) { + return new LogLines(...subscriptionList.map(sub => sub(sender, args))) + } else { + return nilLog + } + } + + connect (parent: Relay): void { + this.eventNames.forEach(name => { + parent.subscribe(name, (sender, args) => this.dispatch(name, sender, args)) + }) + } +} + +type VoreMap = { + "onEaten": { prey: Creature }; + "onReleased": { prey: Creature }; + "onDigested": { prey: Creature }; + "onAbsorbed": { prey: Creature }; +} + +export class VoreRelay extends Relay { + constructor () { + super(["onEaten", "onReleased", "onDigested", "onAbsorbed"]) + } +} diff --git a/src/game/maps/town.ts b/src/game/maps/town.ts index 1f011a5..241281d 100644 --- a/src/game/maps/town.ts +++ b/src/game/maps/town.ts @@ -120,112 +120,10 @@ export const Town = (): Place => { "The center of town" ) - woods.choices.push( - new Choice( - "Fight a wolf", - "yolo", - (world, executor) => { - world.encounter = new Encounter( - { - name: "You punched a wolf", - intro: () => new LogLine(`You punched a wolf. The wolf is angry.`) - }, - [executor, new Creatures.Wolf()] - ) - - return new LogLine(`FIGHT TIME`) - } - ) - ) - - woods.choices.push( - new Choice( - "Fight a dire wolf", - "yolo", - (world, executor) => { - world.encounter = new Encounter( - { - name: "You punched a dire wolf", - intro: () => new LogLine(`You punched a dire wolf. The wolf is angry.`) - }, - [executor, new Creatures.DireWolf()] - ) - - return new LogLine(`FIGHT TIME`) - } - ) - ) - - woods.choices.push( - new Choice( - "Fight a werewolf", - "yolo", - (world, executor) => { - world.encounter = new Encounter( - { - name: "You punched a werewolf", - intro: () => new LogLine(`You punched a werewolf. The werewolf is angry.`) - }, - [executor, new Creatures.Werewolf()] - ) - - return new LogLine(`FIGHT TIME`) - } - ) - ) - - woods.choices.push( - new Choice( - "Fight a dragon", - "yolo", - (world, executor) => { - world.encounter = new Encounter( - { - name: "You punched a dragon", - intro: () => new LogLine(`You punched a dragon. The dragon is angry.`) - }, - [executor, new Creatures.Dragon()] - ) - - return new LogLine(`FIGHT TIME`) - } - ) - ) - - woods.choices.push( - new Choice( - "Fight Taluthus", - "yolo", - (world, executor) => { - world.encounter = new Encounter( - { - name: "You punched Taluthus", - intro: () => new LogLine(`You punched Tal.`) - }, - [executor, new Creatures.Taluthus()] - ) - - return new LogLine(`FIGHT TIME`) - } - ) - ) - const bossEncounters = [ new Encounter( - { name: "Withers & Kenzie", intro: () => nilLog }, - makeParty().concat([new Creatures.Withers(), new Creatures.Kenzie()]) - ), - new Encounter( - { name: "Goldeneye", intro: () => nilLog }, - makeParty().concat([new Creatures.Goldeneye()]) - ), - new Encounter( - { name: "Large Wah", intro: () => nilLog }, - makeParty().concat([new Creatures.Shingo()]) - ), - new Encounter( - { name: "Cafat", intro: () => nilLog }, - makeParty().concat([new Creatures.Cafat()]) + { name: "Inazuma", intro: () => nilLog }, + makeParty().concat([new Creatures.Inazuma()]) ) ] @@ -296,9 +194,9 @@ export const Town = (): Place => { (world) => { const enemy = new Creatures.Human(new ProperNoun("Nerd"), TheyPronouns) enemy.side = Side.Monsters - enemy.ai = new VoreAI() + enemy.ai = new VoreAI(enemy) enemy.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) - enemy.perks.push(new DeliciousPerk()) + enemy.addPerk(new DeliciousPerk()) const encounter = new Encounter( { name: "Fight some tasty nerd", @@ -319,7 +217,7 @@ export const Town = (): Place => { (world) => { const ally = new Creatures.Human(new ProperNoun("Ally"), TheyPronouns) ally.side = Side.Heroes - ally.ai = new VoreAI() + ally.ai = new VoreAI(ally) ally.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) world.party.push(ally) @@ -328,24 +226,6 @@ export const Town = (): Place => { ) ) - square.choices.push( - new Choice( - "Fight Geta", - "yolo", - (world, executor) => { - world.encounter = new Encounter( - { - name: "You punched Geta", - intro: () => new LogLine(`You punched Geta. Geta is angry.`) - }, - [executor, new Creatures.Geta()] - ) - - return new LogLine(`FIGHT TIME`) - } - ) - ) - square.choices.push( new Choice( "Buy a shiny rock", @@ -365,26 +245,6 @@ export const Town = (): Place => { ) ) - alley.choices.push( - new Choice( - "Kuro", - "Get eaten by a Luxray", - (world) => { - const enemy = new Creatures.Kuro() - enemy.ai = new VoreAI() - const encounter = new Encounter( - { - name: "Luxray time", - intro: () => new LogLine(`Luxray time!`) - }, - [world.player, enemy] - ) - world.encounter = encounter - return nilLog - } - ) - ) - bossEncounters.forEach(encounter => { bosses.choices.push( new Choice( diff --git a/src/game/vore.ts b/src/game/vore.ts index a60a172..90293a8 100644 --- a/src/game/vore.ts +++ b/src/game/vore.ts @@ -4,6 +4,7 @@ import { Noun, ImproperNoun, Verb, RandomWord, Word, Preposition } from '@/game/ import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from '@/game/combat/actions' import * as Words from '@/game/words' import { Creature } from '@/game/creature' +import { VoreRelay } from '@/game/events' export enum VoreType { Oral = "Oral Vore", @@ -164,6 +165,8 @@ export abstract class InnerContainer extends NormalContainer { } export interface VoreContainer extends Container { + voreRelay: VoreRelay; + digested: Array; tick: (dt: number, victims?: Array) => LogEntry; digest: (preys: Creature[]) => LogEntry; @@ -177,6 +180,8 @@ export interface VoreContainer extends Container { } export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer { + voreRelay = new VoreRelay() + consumeVerb = new Verb('devour') consumePreposition = new Preposition("into") releaseVerb = new Verb('release', 'releases', 'releasing', 'released') @@ -302,15 +307,20 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor const logLine = super.consume(prey) const results: Array = [] this.owner.effects.forEach(effect => results.push(effect.postConsume(this.owner, prey, this))) - return new LogLines(...[logLine].concat(results)) + const relayResults = this.voreRelay.dispatch("onEaten", this, { prey: prey }) + return new LogLines(...[logLine].concat(results).concat(relayResults)) + } + + release (prey: Creature): LogEntry { + return new LogLines(super.release(prey), this.voreRelay.dispatch("onReleased", this, { prey: prey })) } onAbsorb (prey: Creature): LogEntry { - return nilLog + return this.voreRelay.dispatch("onAbsorbed", this, { prey: prey }) } onDigest (prey: Creature): LogEntry { - return nilLog + return this.voreRelay.dispatch("onDigested", this, { prey: prey }) } }