Parcourir la source

Remove most characters; add Inazuma; start work on event system

master
Fen Dweller il y a 5 ans
Parent
révision
6578931f6d
22 fichiers modifiés avec 290 ajouts et 1838 suppressions
  1. +155
    -9
      src/game/ai.ts
  2. +0
    -111
      src/game/ai/deciders.ts
  3. +2
    -2
      src/game/combat/actions.ts
  4. +16
    -0
      src/game/creature.ts
  5. +4
    -15
      src/game/creatures.ts
  6. +0
    -128
      src/game/creatures/cafat.ts
  7. +39
    -0
      src/game/creatures/characters/inazuma.ts
  8. +0
    -44
      src/game/creatures/dragon.ts
  9. +0
    -202
      src/game/creatures/geta.ts
  10. +0
    -195
      src/game/creatures/goldeneye.ts
  11. +1
    -1
      src/game/creatures/human.ts
  12. +0
    -63
      src/game/creatures/kenzie.ts
  13. +0
    -57
      src/game/creatures/kuro.ts
  14. +2
    -31
      src/game/creatures/player.ts
  15. +0
    -191
      src/game/creatures/shingo.ts
  16. +0
    -103
      src/game/creatures/taluthus.ts
  17. +0
    -99
      src/game/creatures/werewolf.ts
  18. +0
    -362
      src/game/creatures/withers.ts
  19. +0
    -77
      src/game/creatures/wolves.ts
  20. +53
    -0
      src/game/events.ts
  21. +5
    -145
      src/game/maps/town.ts
  22. +13
    -3
      src/game/vore.ts

+ 155
- 9
src/game/ai.ts Voir le fichier

@@ -1,19 +1,32 @@
import { Creature } from '@/game/creature'
import { Encounter, Action } from '@/game/combat'
import { LogEntry } from '@/game/interface'
import { PassAction } from '@/game/combat/actions'
import { NoPassDecider, NoReleaseDecider, ChanceDecider, NoSurrenderDecider, FavorRubDecider } from '@/game/ai/deciders'
import { Encounter, Action, CompositionAction, Consequence } from '@/game/combat'
import { LogEntry, LogLine, nilLog } from '@/game/interface'
import { PassAction, ReleaseAction, RubAction } from '@/game/combat/actions'
import { VoreRelay } from '@/game/events'
import { StatusConsequence } from '@/game/combat/consequences'
import { SurrenderEffect } from '@/game/combat/effects'
import { ToBe, Verb } from '@/game/language'

/**
* A Decider determines how favorable an action is to perform.
*/
export interface Decider {
decide: (encounter: Encounter, user: Creature, target: Creature, action: Action) => number;
export abstract class Decider {
// eslint-disable-next-line @typescript-eslint/no-empty-function
attach (ai: AI): void { } ;
abstract decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number;
}

export class AI {
constructor (public deciders: Decider[]) {
voreRelay = new VoreRelay()

constructor (public deciders: Decider[], owner: Creature) {
this.voreRelay.connect(owner.voreRelay)
deciders.forEach(decider => decider.attach(this))
}

addDecider (decider: Decider): void {
this.deciders.push(decider)
decider.attach(this)
}

decide (actor: Creature, encounter: Encounter): LogEntry {
@@ -57,11 +70,143 @@ export class AI {
}
}

/**
* Specifically avoids using a [[PassAction]]
*/
export class NoPassDecider extends Decider {
decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number {
if (action instanceof PassAction) {
return 0
} else {
return 1
}
}
}

/**
* Specifically avoids using a [[ReleaseAction]]
*/
export class NoReleaseDecider extends Decider {
decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number {
if (action instanceof ReleaseAction) {
return 0
} else {
return 1
}
}
}

/**
* Weights actions based on how likely they are to succeed
*/
export class ChanceDecider extends Decider {
decide (encounter: Encounter, user: Creature, target: Creature, action: Action): number {
return action.odds(user, target)
}
}

/**
* Adjusts the weights for [[CompositionAction]]s that contain the specified consequence
*/
export class ConsequenceDecider<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
*/
export class VoreAI extends AI {
constructor () {
constructor (owner: Creature) {
super(
[
new NoReleaseDecider(),
@@ -69,7 +214,8 @@ export class VoreAI extends AI {
new NoPassDecider(),
new ChanceDecider(),
new FavorRubDecider()
]
],
owner
)
}
}

+ 0
- 111
src/game/ai/deciders.ts Voir le fichier

@@ -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
}
}
}

+ 2
- 2
src/game/combat/actions.ts Voir le fichier

@@ -195,7 +195,7 @@ export class StruggleAction extends Action {
new CompositionTest(
[
new OpposedStatScorer(
{ Power: 1, Agility: 1, Bulk: 0.05 },
{ Power: 100, Agility: 1, Bulk: 0.05 },
{ Toughness: 1, Reflexes: 1, Mass: 0.05 }
)
],
@@ -220,7 +220,7 @@ export class StruggleAction extends Action {
}

protected successLine: PairLineArgs<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'))}!`
)
}



+ 16
- 0
src/game/creature.ts Voir le fichier

@@ -7,8 +7,11 @@ import { PassAction } from '@/game/combat/actions'
import { AI } from '@/game/ai'
import { Entity, Resistances } from '@/game/entity'
import { Perk } from '@/game/combat/perks'
import { VoreRelay } from '@/game/events'

export class Creature extends Entity {
voreRelay: VoreRelay = new VoreRelay()

baseResistances: Resistances

stats: Stats = (Object.keys(Stat) as Array<Stat>).reduce((result: Partial<Stats>, stat: Stat) => {
@@ -202,6 +205,19 @@ export class Creature extends Entity {
return effect.onApply(this)
}

addVoreContainer (container: VoreContainer): void {
this.containers.push(container)
this.voreRelay.connect(container.voreRelay)
}

addOtherContainer (container: Container): void {
this.otherContainers.push(container)
}

addPerk (perk: Perk): void {
this.perks.push(perk)
}

executeAction (action: Action, target: Creature): LogEntry {
const preActionResults = this.effects.map(effect => effect.preAction(this))
const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this))


+ 4
- 15
src/game/creatures.ts Voir le fichier

@@ -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 }

+ 0
- 128
src/game/creatures/cafat.ts Voir le fichier

@@ -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))
}
}

+ 39
- 0
src/game/creatures/characters/inazuma.ts Voir le fichier

@@ -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 }
])
))
}
}

+ 0
- 44
src/game/creatures/dragon.ts Voir le fichier

@@ -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))
}
}

+ 0
- 202
src/game/creatures/geta.ts Voir le fichier

@@ -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()
)
]
}
)
)
}
}

+ 0
- 195
src/game/creatures/goldeneye.ts Voir le fichier

@@ -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)
)
]
}
))
}
}

+ 1
- 1
src/game/creatures/human.ts Voir le fichier

@@ -6,7 +6,7 @@ import { StatusConsequence } from '@/game/combat/consequences'
import { SurrenderEffect } from '@/game/combat/effects'
import { SoloCondition } from '@/game/combat/conditions'

export class Human extends Creature {
export default class Human extends Creature {
constructor (name: Noun, pronouns: Pronoun, options: {
vigors?: Vigor;
stats?: Stats;


+ 0
- 63
src/game/creatures/kenzie.ts Voir le fichier

@@ -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')
)
)
}
}

+ 0
- 57
src/game/creatures/kuro.ts Voir le fichier

@@ -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)
}
}

+ 2
- 31
src/game/creatures/player.ts Voir le fichier

@@ -5,7 +5,7 @@ import { Stomach, Bowels, anyVore, Cock, Balls, Breasts, InnerBladder, Slit, Wom
import { AttackAction } from '@/game/combat/actions'
import { RavenousPerk, BellyBulwakPerk, FlauntPerk } from '@/game/combat/perks'

export class Player extends Creature {
export default class Player extends Creature {
constructor () {
super(
new ProperNoun('Player'),
@@ -20,37 +20,8 @@ export class Player extends Creature {
this.actions.push(new AttackAction(new ConstantDamageFormula(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }, { type: DamageType.Pierce, amount: 20, target: Vigor.Stamina }))))

const stomach = new Stomach(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Acid, target: Vigor.Health }, { amount: 10, type: DamageType.Crush, target: Vigor.Health })))
this.containers.push(stomach)

const bowels = new Bowels(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })))
this.containers.push(bowels)

const cock = new Cock(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })))
this.containers.push(cock)

const balls = new Balls(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })), cock)
this.containers.push(balls)

const slit = new Slit(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })))
this.containers.push(slit)

const womb = new Womb(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })), slit)
this.containers.push(womb)

const bladder = new InnerBladder(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })), cock)
this.containers.push(bladder)

const breasts = new Breasts(this, 2, new ConstantDamageFormula(new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health })))
this.containers.push(breasts)

biconnectContainers(cock, balls)
biconnectContainers(cock, bladder)
biconnectContainers(slit, womb)
this.addVoreContainer(stomach)

this.perspective = POV.Second

this.perks.push(new RavenousPerk())
this.perks.push(new BellyBulwakPerk())
this.perks.push(new FlauntPerk())
}
}

+ 0
- 191
src/game/creatures/shingo.ts Voir le fichier

@@ -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))
}
}

+ 0
- 103
src/game/creatures/taluthus.ts Voir le fichier

@@ -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"))}`)
}
}

+ 0
- 99
src/game/creatures/werewolf.ts Voir le fichier

@@ -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)
}
}

+ 0
- 362
src/game/creatures/withers.ts Voir le fichier

@@ -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))
}
}

+ 0
- 77
src/game/creatures/wolves.ts Voir le fichier

@@ -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)
}
}

+ 53
- 0
src/game/events.ts Voir le fichier

@@ -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"])
}
}

+ 5
- 145
src/game/maps/town.ts Voir le fichier

@@ -120,112 +120,10 @@ export const Town = (): Place => {
"The center of town"
)

woods.choices.push(
new Choice(
"Fight a wolf",
"yolo",
(world, executor) => {
world.encounter = new Encounter(
{
name: "You punched a wolf",
intro: () => new LogLine(`You punched a wolf. The wolf is angry.`)
},
[executor, new Creatures.Wolf()]
)

return new LogLine(`FIGHT TIME`)
}
)
)

woods.choices.push(
new Choice(
"Fight a dire wolf",
"yolo",
(world, executor) => {
world.encounter = new Encounter(
{
name: "You punched a dire wolf",
intro: () => new LogLine(`You punched a dire wolf. The wolf is angry.`)
},
[executor, new Creatures.DireWolf()]
)

return new LogLine(`FIGHT TIME`)
}
)
)

woods.choices.push(
new Choice(
"Fight a werewolf",
"yolo",
(world, executor) => {
world.encounter = new Encounter(
{
name: "You punched a werewolf",
intro: () => new LogLine(`You punched a werewolf. The werewolf is angry.`)
},
[executor, new Creatures.Werewolf()]
)

return new LogLine(`FIGHT TIME`)
}
)
)

woods.choices.push(
new Choice(
"Fight a dragon",
"yolo",
(world, executor) => {
world.encounter = new Encounter(
{
name: "You punched a dragon",
intro: () => new LogLine(`You punched a dragon. The dragon is angry.`)
},
[executor, new Creatures.Dragon()]
)

return new LogLine(`FIGHT TIME`)
}
)
)

woods.choices.push(
new Choice(
"Fight Taluthus",
"yolo",
(world, executor) => {
world.encounter = new Encounter(
{
name: "You punched Taluthus",
intro: () => new LogLine(`You punched Tal.`)
},
[executor, new Creatures.Taluthus()]
)

return new LogLine(`FIGHT TIME`)
}
)
)

const bossEncounters = [
new Encounter(
{ name: "Withers & Kenzie", intro: () => nilLog },
makeParty().concat([new Creatures.Withers(), new Creatures.Kenzie()])
),
new Encounter(
{ name: "Goldeneye", intro: () => nilLog },
makeParty().concat([new Creatures.Goldeneye()])
),
new Encounter(
{ name: "Large Wah", intro: () => nilLog },
makeParty().concat([new Creatures.Shingo()])
),
new Encounter(
{ name: "Cafat", intro: () => nilLog },
makeParty().concat([new Creatures.Cafat()])
{ name: "Inazuma", intro: () => nilLog },
makeParty().concat([new Creatures.Inazuma()])
)
]

@@ -296,9 +194,9 @@ export const Town = (): Place => {
(world) => {
const enemy = new Creatures.Human(new ProperNoun("Nerd"), TheyPronouns)
enemy.side = Side.Monsters
enemy.ai = new VoreAI()
enemy.ai = new VoreAI(enemy)
enemy.equip(new Items.Sword(), Items.EquipmentSlot.MainHand)
enemy.perks.push(new DeliciousPerk())
enemy.addPerk(new DeliciousPerk())
const encounter = new Encounter(
{
name: "Fight some tasty nerd",
@@ -319,7 +217,7 @@ export const Town = (): Place => {
(world) => {
const ally = new Creatures.Human(new ProperNoun("Ally"), TheyPronouns)
ally.side = Side.Heroes
ally.ai = new VoreAI()
ally.ai = new VoreAI(ally)
ally.equip(new Items.Sword(), Items.EquipmentSlot.MainHand)
world.party.push(ally)

@@ -328,24 +226,6 @@ export const Town = (): Place => {
)
)

square.choices.push(
new Choice(
"Fight Geta",
"yolo",
(world, executor) => {
world.encounter = new Encounter(
{
name: "You punched Geta",
intro: () => new LogLine(`You punched Geta. Geta is angry.`)
},
[executor, new Creatures.Geta()]
)

return new LogLine(`FIGHT TIME`)
}
)
)

square.choices.push(
new Choice(
"Buy a shiny rock",
@@ -365,26 +245,6 @@ export const Town = (): Place => {
)
)

alley.choices.push(
new Choice(
"Kuro",
"Get eaten by a Luxray",
(world) => {
const enemy = new Creatures.Kuro()
enemy.ai = new VoreAI()
const encounter = new Encounter(
{
name: "Luxray time",
intro: () => new LogLine(`Luxray time!`)
},
[world.player, enemy]
)
world.encounter = encounter
return nilLog
}
)
)

bossEncounters.forEach(encounter => {
bosses.choices.push(
new Choice(


+ 13
- 3
src/game/vore.ts Voir le fichier

@@ -4,6 +4,7 @@ import { Noun, ImproperNoun, Verb, RandomWord, Word, Preposition } from '@/game/
import { RubAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from '@/game/combat/actions'
import * as Words from '@/game/words'
import { Creature } from '@/game/creature'
import { VoreRelay } from '@/game/events'

export enum VoreType {
Oral = "Oral Vore",
@@ -164,6 +165,8 @@ export abstract class InnerContainer extends NormalContainer {
}

export interface VoreContainer extends Container {
voreRelay: VoreRelay;

digested: Array<Creature>;
tick: (dt: number, victims?: Array<Creature>) => LogEntry;
digest: (preys: Creature[]) => LogEntry;
@@ -177,6 +180,8 @@ export interface VoreContainer extends Container {
}

export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer {
voreRelay = new VoreRelay()

consumeVerb = new Verb('devour')
consumePreposition = new Preposition("into")
releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
@@ -302,15 +307,20 @@ export abstract class NormalVoreContainer extends NormalContainer implements Vor
const logLine = super.consume(prey)
const results: Array<LogEntry> = []
this.owner.effects.forEach(effect => results.push(effect.postConsume(this.owner, prey, this)))
return new LogLines(...[logLine].concat(results))
const relayResults = this.voreRelay.dispatch("onEaten", this, { prey: prey })
return new LogLines(...[logLine].concat(results).concat(relayResults))
}

release (prey: Creature): LogEntry {
return new LogLines(super.release(prey), this.voreRelay.dispatch("onReleased", this, { prey: prey }))
}

onAbsorb (prey: Creature): LogEntry {
return nilLog
return this.voreRelay.dispatch("onAbsorbed", this, { prey: prey })
}

onDigest (prey: Creature): LogEntry {
return nilLog
return this.voreRelay.dispatch("onDigested", this, { prey: prey })
}
}



Chargement…
Annuler
Enregistrer