| @@ -1,19 +1,32 @@ | |||||
| import { Creature } from '@/game/creature' | 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. | * 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 { | 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 { | 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<T extends Consequence> 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<Creature> = 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 | * The VoreAI tries to eat opponents, but only if the odds are good enough | ||||
| */ | */ | ||||
| export class VoreAI extends AI { | export class VoreAI extends AI { | ||||
| constructor () { | |||||
| constructor (owner: Creature) { | |||||
| super( | super( | ||||
| [ | [ | ||||
| new NoReleaseDecider(), | new NoReleaseDecider(), | ||||
| @@ -69,7 +214,8 @@ export class VoreAI extends AI { | |||||
| new NoPassDecider(), | new NoPassDecider(), | ||||
| new ChanceDecider(), | new ChanceDecider(), | ||||
| new FavorRubDecider() | new FavorRubDecider() | ||||
| ] | |||||
| ], | |||||
| owner | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -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<T extends Consequence> 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 | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -195,7 +195,7 @@ export class StruggleAction extends Action { | |||||
| new CompositionTest( | new CompositionTest( | ||||
| [ | [ | ||||
| new OpposedStatScorer( | new OpposedStatScorer( | ||||
| { Power: 1, Agility: 1, Bulk: 0.05 }, | |||||
| { Power: 100, Agility: 1, Bulk: 0.05 }, | |||||
| { Toughness: 1, Reflexes: 1, Mass: 0.05 } | { Toughness: 1, Reflexes: 1, Mass: 0.05 } | ||||
| ) | ) | ||||
| ], | ], | ||||
| @@ -220,7 +220,7 @@ export class StruggleAction extends Action { | |||||
| } | } | ||||
| protected successLine: PairLineArgs<Entity, { container: Container }> = (prey, pred, args) => new LogLine( | protected successLine: PairLineArgs<Entity, { container: Container }> = (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'))}!` | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -7,8 +7,11 @@ import { PassAction } from '@/game/combat/actions' | |||||
| import { AI } from '@/game/ai' | import { AI } from '@/game/ai' | ||||
| import { Entity, Resistances } from '@/game/entity' | import { Entity, Resistances } from '@/game/entity' | ||||
| import { Perk } from '@/game/combat/perks' | import { Perk } from '@/game/combat/perks' | ||||
| import { VoreRelay } from '@/game/events' | |||||
| export class Creature extends Entity { | export class Creature extends Entity { | ||||
| voreRelay: VoreRelay = new VoreRelay() | |||||
| baseResistances: Resistances | baseResistances: Resistances | ||||
| stats: Stats = (Object.keys(Stat) as Array<Stat>).reduce((result: Partial<Stats>, stat: Stat) => { | stats: Stats = (Object.keys(Stat) as Array<Stat>).reduce((result: Partial<Stats>, stat: Stat) => { | ||||
| @@ -202,6 +205,19 @@ export class Creature extends Entity { | |||||
| return effect.onApply(this) | 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 { | executeAction (action: Action, target: Creature): LogEntry { | ||||
| const preActionResults = this.effects.map(effect => effect.preAction(this)) | const preActionResults = this.effects.map(effect => effect.preAction(this)) | ||||
| const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this)) | const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this)) | ||||
| @@ -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 } | |||||
| @@ -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<Creature, { damage: Damage }> = (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<Creature, { damage: Damage }> = (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<Creature, { container: VoreContainer }> = (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)) | |||||
| } | |||||
| } | |||||
| @@ -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 } | |||||
| ]) | |||||
| )) | |||||
| } | |||||
| } | |||||
| @@ -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)) | |||||
| } | |||||
| } | |||||
| @@ -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() | |||||
| ) | |||||
| ] | |||||
| } | |||||
| ) | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -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<Creature, { container: Container }> = (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<Creature>): 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) | |||||
| ) | |||||
| ] | |||||
| } | |||||
| )) | |||||
| } | |||||
| } | |||||
| @@ -6,7 +6,7 @@ import { StatusConsequence } from '@/game/combat/consequences' | |||||
| import { SurrenderEffect } from '@/game/combat/effects' | import { SurrenderEffect } from '@/game/combat/effects' | ||||
| import { SoloCondition } from '@/game/combat/conditions' | import { SoloCondition } from '@/game/combat/conditions' | ||||
| export class Human extends Creature { | |||||
| export default class Human extends Creature { | |||||
| constructor (name: Noun, pronouns: Pronoun, options: { | constructor (name: Noun, pronouns: Pronoun, options: { | ||||
| vigors?: Vigor; | vigors?: Vigor; | ||||
| stats?: Stats; | stats?: Stats; | ||||
| @@ -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') | |||||
| ) | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -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) | |||||
| } | |||||
| } | |||||
| @@ -5,7 +5,7 @@ import { Stomach, Bowels, anyVore, Cock, Balls, Breasts, InnerBladder, Slit, Wom | |||||
| import { AttackAction } from '@/game/combat/actions' | import { AttackAction } from '@/game/combat/actions' | ||||
| import { RavenousPerk, BellyBulwakPerk, FlauntPerk } from '@/game/combat/perks' | import { RavenousPerk, BellyBulwakPerk, FlauntPerk } from '@/game/combat/perks' | ||||
| export class Player extends Creature { | |||||
| export default class Player extends Creature { | |||||
| constructor () { | constructor () { | ||||
| super( | super( | ||||
| new ProperNoun('Player'), | 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 })))) | 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 }))) | 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.perspective = POV.Second | ||||
| this.perks.push(new RavenousPerk()) | |||||
| this.perks.push(new BellyBulwakPerk()) | |||||
| this.perks.push(new FlauntPerk()) | |||||
| } | } | ||||
| } | } | ||||
| @@ -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<Creature, { damage: Damage }> = (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)) | |||||
| } | |||||
| } | |||||
| @@ -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"))}`) | |||||
| } | |||||
| } | |||||
| @@ -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) | |||||
| } | |||||
| } | |||||
| @@ -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<Creature> = (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<LogEntry> = [] | |||||
| 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<Creature> = (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<Creature>): 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<Creature> = (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<DamageInstance> = 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<Creature>): 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<Creature>): 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)) | |||||
| } | |||||
| } | |||||
| @@ -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) | |||||
| } | |||||
| } | |||||
| @@ -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<Sender, EventMap extends { [name: string]: any }> { | |||||
| private subscriptions: { [K in keyof EventMap]: Array<(sender: Sender, args: EventMap[K]) => LogEntry> } | |||||
| constructor (private eventNames: Array<keyof EventMap>) { | |||||
| 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<K extends keyof EventMap> (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<K extends keyof EventMap> (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<ParentEventMap extends EventMap> (parent: Relay<Sender, ParentEventMap>): 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<VoreContainer, VoreMap> { | |||||
| constructor () { | |||||
| super(["onEaten", "onReleased", "onDigested", "onAbsorbed"]) | |||||
| } | |||||
| } | |||||
| @@ -120,112 +120,10 @@ export const Town = (): Place => { | |||||
| "The center of town" | "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 = [ | const bossEncounters = [ | ||||
| new Encounter( | 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) => { | (world) => { | ||||
| const enemy = new Creatures.Human(new ProperNoun("Nerd"), TheyPronouns) | const enemy = new Creatures.Human(new ProperNoun("Nerd"), TheyPronouns) | ||||
| enemy.side = Side.Monsters | enemy.side = Side.Monsters | ||||
| enemy.ai = new VoreAI() | |||||
| enemy.ai = new VoreAI(enemy) | |||||
| enemy.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) | enemy.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) | ||||
| enemy.perks.push(new DeliciousPerk()) | |||||
| enemy.addPerk(new DeliciousPerk()) | |||||
| const encounter = new Encounter( | const encounter = new Encounter( | ||||
| { | { | ||||
| name: "Fight some tasty nerd", | name: "Fight some tasty nerd", | ||||
| @@ -319,7 +217,7 @@ export const Town = (): Place => { | |||||
| (world) => { | (world) => { | ||||
| const ally = new Creatures.Human(new ProperNoun("Ally"), TheyPronouns) | const ally = new Creatures.Human(new ProperNoun("Ally"), TheyPronouns) | ||||
| ally.side = Side.Heroes | ally.side = Side.Heroes | ||||
| ally.ai = new VoreAI() | |||||
| ally.ai = new VoreAI(ally) | |||||
| ally.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) | ally.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) | ||||
| world.party.push(ally) | 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( | square.choices.push( | ||||
| new Choice( | new Choice( | ||||
| "Buy a shiny rock", | "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 => { | bossEncounters.forEach(encounter => { | ||||
| bosses.choices.push( | bosses.choices.push( | ||||
| new Choice( | new Choice( | ||||
| @@ -4,6 +4,7 @@ import { Noun, ImproperNoun, Verb, RandomWord, Word, Preposition } from '@/game/ | |||||
| import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from '@/game/combat/actions' | import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from '@/game/combat/actions' | ||||
| import * as Words from '@/game/words' | import * as Words from '@/game/words' | ||||
| import { Creature } from '@/game/creature' | import { Creature } from '@/game/creature' | ||||
| import { VoreRelay } from '@/game/events' | |||||
| export enum VoreType { | export enum VoreType { | ||||
| Oral = "Oral Vore", | Oral = "Oral Vore", | ||||
| @@ -164,6 +165,8 @@ export abstract class InnerContainer extends NormalContainer { | |||||
| } | } | ||||
| export interface VoreContainer extends Container { | export interface VoreContainer extends Container { | ||||
| voreRelay: VoreRelay; | |||||
| digested: Array<Creature>; | digested: Array<Creature>; | ||||
| tick: (dt: number, victims?: Array<Creature>) => LogEntry; | tick: (dt: number, victims?: Array<Creature>) => LogEntry; | ||||
| digest: (preys: Creature[]) => LogEntry; | digest: (preys: Creature[]) => LogEntry; | ||||
| @@ -177,6 +180,8 @@ export interface VoreContainer extends Container { | |||||
| } | } | ||||
| export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer { | export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer { | ||||
| voreRelay = new VoreRelay() | |||||
| consumeVerb = new Verb('devour') | consumeVerb = new Verb('devour') | ||||
| consumePreposition = new Preposition("into") | consumePreposition = new Preposition("into") | ||||
| releaseVerb = new Verb('release', 'releases', 'releasing', 'released') | 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 logLine = super.consume(prey) | ||||
| const results: Array<LogEntry> = [] | const results: Array<LogEntry> = [] | ||||
| this.owner.effects.forEach(effect => results.push(effect.postConsume(this.owner, prey, this))) | 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 { | onAbsorb (prey: Creature): LogEntry { | ||||
| return nilLog | |||||
| return this.voreRelay.dispatch("onAbsorbed", this, { prey: prey }) | |||||
| } | } | ||||
| onDigest (prey: Creature): LogEntry { | onDigest (prey: Creature): LogEntry { | ||||
| return nilLog | |||||
| return this.voreRelay.dispatch("onDigested", this, { prey: prey }) | |||||
| } | } | ||||
| } | } | ||||