Browse Source

Refactor some combat stuff; add effects

master
Fen Dweller 5 years ago
parent
commit
c1c58d710d
11 changed files with 414 additions and 367 deletions
  1. +9
    -279
      src/game/combat.ts
  2. +258
    -0
      src/game/combat/actions.ts
  3. +27
    -0
      src/game/combat/conditions.ts
  4. +18
    -0
      src/game/combat/effects.ts
  5. +1
    -1
      src/game/combat/tests.ts
  6. +11
    -13
      src/game/creatures/cafat.ts
  7. +2
    -1
      src/game/creatures/player.ts
  8. +5
    -4
      src/game/creatures/wolf.ts
  9. +22
    -9
      src/game/interface.ts
  10. +4
    -4
      src/game/language.ts
  11. +57
    -56
      src/game/vore.ts

+ 9
- 279
src/game/combat.ts View File

@@ -71,6 +71,12 @@ export interface CombatTest {
explain: (user: Creature, target: Creature) => LogEntry;
}

export interface Effect {
name: string;
desc: string;
apply: (target: Creature) => LogEntry;
}

export class Damage {
readonly damages: DamageInstance[]

@@ -172,36 +178,11 @@ export interface Condition {
allowed: (user: Creature, target: Creature) => boolean;
}

class InverseCondition implements Condition {
allowed (user: Creature, target: Creature): boolean {
return !this.condition.allowed(user, target)
}

constructor (private condition: Condition) {

}
}
class CapableCondition implements Condition {
allowed (user: Creature, target: Creature): boolean {
return !user.disabled
}
}

class DrainedVigorCondition implements Condition {
allowed (user: Creature, target: Creature): boolean {
return user.vigors[this.vigor] <= 0
}

constructor (private vigor: Vigor) {

}
}

export interface Actionable {
actions: Array<Action>;
}

abstract class SelfAction extends Action {
export abstract class SelfAction extends Action {
allowed (user: Creature, target: Creature) {
if (user === target) {
return super.allowed(user, target)
@@ -211,7 +192,7 @@ abstract class SelfAction extends Action {
}
}

abstract class PairAction extends Action {
export abstract class PairAction extends Action {
allowed (user: Creature, target: Creature) {
if (user !== target) {
return super.allowed(user, target)
@@ -221,7 +202,7 @@ abstract class PairAction extends Action {
}
}

abstract class TogetherAction extends PairAction {
export abstract class TogetherAction extends PairAction {
allowed (user: Creature, target: Creature) {
if (user.containedIn === target.containedIn) {
return super.allowed(user, target)
@@ -230,254 +211,3 @@ abstract class TogetherAction extends PairAction {
}
}
}

export class AttackAction extends TogetherAction {
protected test: StatTest

protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([
[[POV.First, POV.Third], (user, target, args) => new LogLine(
`You smack ${target.name} for `,
args.damage.renderShort()
)],
[[POV.Third, POV.First], (user, target, args) => new LogLine(
`${user.name.capital} smacks you for `,
args.damage.renderShort()
)],
[[POV.Third, POV.Third], (user, target, args) => new LogLine(
`${user.name.capital} smacks ${target.name} for `,
args.damage.renderShort()
)]
])

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You try to smack ${target.name}, but you miss`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} misses ${target.name}`)]
])

constructor (protected damage: DamageFormula) {
super('Attack', 'Attack the enemy', [new CapableCondition()])
this.test = new StatTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
if (this.test.test(user, target)) {
const damage = this.damage.calc(user, target)
const targetResult = target.takeDamage(damage)
const ownResult = this.successLines.run(user, target, { damage: damage })
return new CompositeLog(ownResult, targetResult)
} else {
return this.failLines.run(user, target)
}
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Attack ${target.name}. `, this.damage.describe(user, target), '. ', this.test.explain(user, target))
}
}

export class DevourAction extends TogetherAction {
private test: StatVigorTest

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You fail to make a meal out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to devour you, but fails`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} unsuccessfully tries to swallow ${target.name}`)]
])

allowed (user: Creature, target: Creature): boolean {
const owner = this.container.owner === user
const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))

if (owner && predOk && preyOk) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super(new DynText('Devour (', new LiveText(container, x => x.name.all), ')'), 'Try to consume your foe', [new CapableCondition()])
this.test = new StatVigorTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
if (this.test.test(user, target)) {
return this.container.consume(target)
} else {
return this.failLines.run(user, target)
}
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Try to consume your opponent, sending them to your ${this.container.name}. `, this.test.explain(user, target))
}
}

export class FeedAction extends TogetherAction {
private test: StatTest

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You fail to feed yourself to ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to feed ${user.pronouns.possessive} to you, but fails`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} unsuccessfully tries to feed ${user.pronouns.possessive} to ${target.name}`)]
])

allowed (user: Creature, target: Creature): boolean {
const owner = this.container.owner === target
const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))

if (owner && predOk && preyOk) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Resolve)])
this.name += ` (${container.name})`
this.test = new StatTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
return this.container.consume(user)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Your willpower is drained, and you really feel like shoving yourself into ${target.name}'s ${this.container.name}...`)
}
}

export class StruggleAction extends PairAction {
private test: StatVigorTest

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You fail to escape from ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to escape from you, but fails`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} unsuccessfully struggles within ${target.name}`)]
])

allowed (user: Creature, target: Creature) {
if (user.containedIn === this.container && this.container.owner === target) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (public container: Container) {
super(new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition()])
this.test = new StatVigorTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
if (user.containedIn !== null) {
if (this.test.test(user, target)) {
return user.containedIn.release(user)
} else {
return this.failLines.run(user, target)
}
} else {
return new LogLines("Vore's bugged!")
}
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Try to escape from ${target.name}'s ${this.container.name}. `, this.test.explain(user, target))
}
}

export abstract class EatenAction extends PairAction {
protected lines: POVPair<Entity, Entity> = new POVPair([])

allowed (user: Creature, target: Creature) {
if (target.containedIn === this.container) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (public container: Container, name: TextLike, desc: string) {
super(new DynText(name, ' (', new LiveText(container, x => x.name.all), ')'), desc, [new CapableCondition()])
}
}
export class DigestAction extends SelfAction {
protected lines: POVPair<Entity, Entity> = new POVPair([])

allowed (user: Creature, target: Creature) {
if (this.container.owner === user && this.container.contents.length > 0) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super(new DynText('Digest (', new LiveText(container, container => container.name.all), ')'), 'Digest your prey', [new CapableCondition()])
}

execute (user: Creature, target: Creature): LogEntry {
const results = this.container.tick(60)
return new CompositeLog(results)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Digest everyone inside of your ${this.container.name}.`)
}
}

export class ReleaseAction extends PairAction {
allowed (user: Creature, target: Creature) {
if (target.containedIn === this.container && this.container.contents.indexOf(target) >= 0) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super(new DynText('Release (', new LiveText(container, x => x.name.all), ')'), 'Release one of your prey', [new CapableCondition()])
}

execute (user: Creature, target: Creature): LogEntry {
return this.container.release(target)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Release ${target.name} from your ${this.container.name}.`)
}
}

export class TransferAction extends PairAction {
lines: POVPairArgs<Entity, Entity, { from: Container; to: Container }> = new POVPairArgs([
[[POV.First, POV.Third], (user, target, args) => new LogLine(`You squeeze ${target.name} from your ${args.from.name} to your ${args.to.name}`)],
[[POV.Third, POV.First], (user, target, args) => new LogLine(`You're squeezed from ${user.name}'s ${args.from.name} to ${user.pronouns.possessive} ${args.to.name}`)],
[[POV.Third, POV.Third], (user, target, args) => new LogLine(`${user.name} squeezes ${target.name} from ${user.pronouns.possessive} ${args.from.name} to ${user.pronouns.possessive} ${args.to.name}`)]
])

allowed (user: Creature, target: Creature) {
if (target.containedIn === this.from) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected from: Container, protected to: Container) {
super('Transfer', `Move from your ${from.name} to your ${to.name}`, [new CapableCondition()])
}

execute (user: Creature, target: Creature): LogEntry {
this.from.release(target)
this.to.consume(target)
return this.lines.run(user, target, { from: this.from, to: this.to })
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Push ${target.name} from your ${this.from.name} to your ${this.to.name}`)
}
}

+ 258
- 0
src/game/combat/actions.ts View File

@@ -0,0 +1,258 @@
import { StatTest, StatVigorTest } from './tests'
import { POVPairArgs, POVPair, DynText, LiveText, TextLike } from '../language'
import { Entity, POV, Creature } from '../entity'
import { Damage, DamageFormula, Stat, Vigor, TogetherAction, PairAction, SelfAction } from '../combat'
import { LogLine, LogLines, LogEntry, CompositeLog } from '../interface'
import { Container } from '../vore'
import { CapableCondition, DrainedVigorCondition } from './conditions'

export class AttackAction extends TogetherAction {
protected test: StatTest

protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([
[[POV.First, POV.Third], (user, target, args) => new LogLine(
`You smack ${target.name} for `,
args.damage.renderShort()
)],
[[POV.Third, POV.First], (user, target, args) => new LogLine(
`${user.name.capital} smacks you for `,
args.damage.renderShort()
)],
[[POV.Third, POV.Third], (user, target, args) => new LogLine(
`${user.name.capital} smacks ${target.name} for `,
args.damage.renderShort()
)]
])

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLine(`You try to smack ${target.name}, but you miss`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} misses you`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} misses ${target.name}`)]
])

constructor (protected damage: DamageFormula) {
super('Attack', 'Attack the enemy', [new CapableCondition()])
this.test = new StatTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
if (this.test.test(user, target)) {
const damage = this.damage.calc(user, target)
const targetResult = target.takeDamage(damage)
const ownResult = this.successLines.run(user, target, { damage: damage })
return new CompositeLog(ownResult, targetResult)
} else {
return this.failLines.run(user, target)
}
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Attack ${target.name}. `, this.damage.describe(user, target), '. ', this.test.explain(user, target))
}
}

export class DevourAction extends TogetherAction {
private test: StatVigorTest

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLine(`You fail to make a meal out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} tries to devour you, but fails`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} unsuccessfully tries to swallow ${target.name}`)]
])

allowed (user: Creature, target: Creature): boolean {
const owner = this.container.owner === user
const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))

if (owner && predOk && preyOk) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super(new DynText('Devour (', new LiveText(container, x => x.name.all), ')'), 'Try to consume your foe', [new CapableCondition()])
this.test = new StatVigorTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
if (this.test.test(user, target)) {
return this.container.consume(target)
} else {
return this.failLines.run(user, target)
}
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Try to consume your opponent, sending them to your ${this.container.name}. `, this.test.explain(user, target))
}
}

export class FeedAction extends TogetherAction {
private test: StatTest

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLine(`You fail to feed yourself to ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} tries to feed ${user.pronouns.possessive} to you, but fails`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} unsuccessfully tries to feed ${user.pronouns.possessive} to ${target.name}`)]
])

allowed (user: Creature, target: Creature): boolean {
const owner = this.container.owner === target
const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))

if (owner && predOk && preyOk) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super('Feed', 'Feed yourself to your opponent', [new DrainedVigorCondition(Vigor.Resolve)])
this.name += ` (${container.name})`
this.test = new StatTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
return this.container.consume(user)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Your willpower is drained, and you really feel like shoving yourself into ${target.name}'s ${this.container.name}...`)
}
}

export class StruggleAction extends PairAction {
private test: StatVigorTest

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLine(`You fail to escape from ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} tries to escape from you, but fails`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} unsuccessfully struggles within ${target.name}`)]
])

allowed (user: Creature, target: Creature) {
if (user.containedIn === this.container && this.container.owner === target) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (public container: Container) {
super(new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition()])
this.test = new StatVigorTest(Stat.Power)
}

execute (user: Creature, target: Creature): LogEntry {
if (user.containedIn !== null) {
if (this.test.test(user, target)) {
return user.containedIn.release(user)
} else {
return this.failLines.run(user, target)
}
} else {
return new LogLine("Vore's bugged!")
}
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Try to escape from ${target.name}'s ${this.container.name}. `, this.test.explain(user, target))
}
}

export abstract class EatenAction extends PairAction {
protected lines: POVPair<Entity, Entity> = new POVPair([])

allowed (user: Creature, target: Creature) {
if (target.containedIn === this.container) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (public container: Container, name: TextLike, desc: string) {
super(new DynText(name, ' (', new LiveText(container, x => x.name.all), ')'), desc, [new CapableCondition()])
}
}
export class DigestAction extends SelfAction {
protected lines: POVPair<Entity, Entity> = new POVPair([])

allowed (user: Creature, target: Creature) {
if (this.container.owner === user && this.container.contents.length > 0) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super(new DynText('Digest (', new LiveText(container, container => container.name.all), ')'), 'Digest your prey', [new CapableCondition()])
}

execute (user: Creature, target: Creature): LogEntry {
const results = this.container.tick(60)
return new CompositeLog(results)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Digest everyone inside of your ${this.container.name}.`)
}
}

export class ReleaseAction extends PairAction {
allowed (user: Creature, target: Creature) {
if (target.containedIn === this.container && this.container.contents.indexOf(target) >= 0) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected container: Container) {
super(new DynText('Release (', new LiveText(container, x => x.name.all), ')'), 'Release one of your prey', [new CapableCondition()])
}

execute (user: Creature, target: Creature): LogEntry {
return this.container.release(target)
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Release ${target.name} from your ${this.container.name}.`)
}
}

export class TransferAction extends PairAction {
lines: POVPairArgs<Entity, Entity, { from: Container; to: Container }> = new POVPairArgs([
[[POV.First, POV.Third], (user, target, args) => new LogLine(`You squeeze ${target.name} from your ${args.from.name} to your ${args.to.name}`)],
[[POV.Third, POV.First], (user, target, args) => new LogLine(`You're squeezed from ${user.name}'s ${args.from.name} to ${user.pronouns.possessive} ${args.to.name}`)],
[[POV.Third, POV.Third], (user, target, args) => new LogLine(`${user.name} squeezes ${target.name} from ${user.pronouns.possessive} ${args.from.name} to ${user.pronouns.possessive} ${args.to.name}`)]
])

allowed (user: Creature, target: Creature) {
if (target.containedIn === this.from) {
return super.allowed(user, target)
} else {
return false
}
}

constructor (protected from: Container, protected to: Container) {
super('Transfer', `Move from your ${from.name} to your ${to.name}`, [new CapableCondition()])
}

execute (user: Creature, target: Creature): LogEntry {
this.from.release(target)
this.to.consume(target)
return this.lines.run(user, target, { from: this.from, to: this.to })
}

describe (user: Creature, target: Creature): LogEntry {
return new LogLine(`Push ${target.name} from your ${this.from.name} to your ${this.to.name}`)
}
}

+ 27
- 0
src/game/combat/conditions.ts View File

@@ -0,0 +1,27 @@
import { Condition, Vigor } from "../combat"
import { Creature } from "../entity"

export class InverseCondition implements Condition {
allowed (user: Creature, target: Creature): boolean {
return !this.condition.allowed(user, target)
}

constructor (private condition: Condition) {

}
}
export class CapableCondition implements Condition {
allowed (user: Creature, target: Creature): boolean {
return !user.disabled
}
}

export class DrainedVigorCondition implements Condition {
allowed (user: Creature, target: Creature): boolean {
return user.vigors[this.vigor] <= 0
}

constructor (private vigor: Vigor) {

}
}

+ 18
- 0
src/game/combat/effects.ts View File

@@ -0,0 +1,18 @@
import { Creature, POV } from '../entity'
import { LogEntry, LogLine, FAElem } from '../interface'
import { Effect } from '../combat'
import { POVSolo } from '../language'

export class InstantKill implements Effect {
lines = new POVSolo<Creature>([
[[POV.First], (target: Creature) => new LogLine(`You're killed instantly! `, new FAElem('fas fa-skull'))],
[[POV.Third], (target: Creature) => new LogLine(`${target.name.capital} is killed instantly! `, new FAElem('fas fa-skull'))]
])

name = "Instant Kill"
desc = "Instantly kills its victim"
apply (target: Creature): LogEntry {
target.vigors.Health = Math.min(0, target.vigors.Health)
return this.lines.run(target)
}
}

+ 1
- 1
src/game/combat/tests.ts View File

@@ -128,6 +128,6 @@ export class ChanceTest extends RandomTest {
}

explain (user: Creature, target: Creature): LogEntry {
return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
return new LogLine('You have a flat ' + (100 * this.chance) + '% chance.')
}
}

+ 11
- 13
src/game/creatures/cafat.ts View File

@@ -1,8 +1,10 @@
import { Creature, POV, Entity } from '../entity'
import { Stat, Damage, DamageType, TransferAction, Vigor, FeedAction, EatenAction, AttackAction, ConstantDamageFormula } from '../combat'
import { Stat, Damage, DamageType, Vigor, ConstantDamageFormula } from '../combat'
import { ProperNoun, TheyPronouns, ImproperNoun, POVPair, FemalePronouns, POVPairArgs } from '../language'
import { VoreType, Stomach, InnerStomach, Container } from '../vore'
import { LogLine, LogLines, LogEntry, FAElem, CompositeLog, ImgElem } from '../interface'
import { AttackAction, EatenAction, TransferAction, FeedAction } from '../combat/actions'
import { InstantKill } from '../combat/effects'

class BellyCrushAction extends AttackAction {
successLines = new POVPairArgs<Entity, Entity, { damage: Damage }>([
@@ -56,25 +58,21 @@ class BelchAction extends AttackAction {
this.desc = 'Drain your foe\'s willpower with a solid BELCH'
}
}

class CrushAction extends EatenAction {
lines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLine(`You crush ${target.name} `, new FAElem('fas fa-skull'))],
[[POV.Third, POV.First], (user) => new CompositeLog(new LogLine(`${user.name.capital} crushes you; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `, new FAElem('fas fa-skull')), new ImgElem('./media/cafat/images/crunch.webp'))],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} crushes ${target.name}; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `, new FAElem('fas fa-skull'))]
[[POV.First, POV.Third], (user, target) => new LogLine(`You crush ${target.name} `)],
[[POV.Third, POV.First], (user) => new CompositeLog(new LogLine(`${user.name.capital} crushes you; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `), new ImgElem('./media/cafat/images/crunch.webp'))],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} crushes ${target.name}; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `)]
])

private damage: Damage = new Damage(
{ amount: 99, type: DamageType.Crush, target: Vigor.Health }
)

constructor (container: Container) {
super(container, "Crush", "Crush 'em!")
this.desc = "Crush somebody in your gut"
}

execute (user: Creature, target: Creature): LogEntry {
target.takeDamage(this.damage)
return this.lines.run(user, target)
return new LogLines(this.lines.run(user, target), new InstantKill().apply(target))
}

describe (user: Creature, target: Creature): LogEntry {
@@ -118,9 +116,9 @@ export class Cafat extends Creature {
lowerStomach.name = new ImproperNoun("lower stomach", "lower stomachs").all

stomach.consumeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user) => new CompositeLog(new LogLines(`${user.name.capital} devours you`), new ImgElem('./media/cafat/images/stomach.webp'))],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} munches ${target.name.capital}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user) => new CompositeLog(new LogLine(`${user.name.capital} devours you`), new ImgElem('./media/cafat/images/stomach.webp'))],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} munches ${target.name.capital}`)]
])
const crush = new CrushAction(lowerStomach)



+ 2
- 1
src/game/creatures/player.ts View File

@@ -1,7 +1,8 @@
import { Creature, POV } from '../entity'
import { ProperNoun, TheyPronouns } from '../language'
import { Stat, Damage, AttackAction, DamageType, Vigor, ConstantDamageFormula } from '../combat'
import { Stat, Damage, DamageType, Vigor, ConstantDamageFormula } from '../combat'
import { Stomach, Bowels, VoreType } from '../vore'
import { AttackAction } from '../combat/actions'

export class Player extends Creature {
constructor () {


+ 5
- 4
src/game/creatures/wolf.ts View File

@@ -1,9 +1,10 @@
import { Creature, POV, Entity } from '../entity'
import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor, FeedAction, ConstantDamageFormula } from '../combat'
import { Stat, Damage, DamageType, ConstantDamageFormula, Vigor } from '../combat'
import { MalePronouns, ImproperNoun, POVPair, POVPairArgs } from '../language'
import { LogLine, LogLines } from '../interface'
import { VoreType, Stomach, Bowels } from '../vore'
import { StatTest } from '../combat/tests'
import { AttackAction, TransferAction, FeedAction } from '../combat/actions'

class BiteAction extends AttackAction {
constructor () {
@@ -29,9 +30,9 @@ class HypnoAction extends AttackAction {
])

protected failLines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You try to hypnotize ${target.name}, but you miss`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} misses ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You try to hypnotize ${target.name}, but you miss`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} misses you`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${target.name} misses ${target.name}`)]
])

constructor () {


+ 22
- 9
src/game/interface.ts View File

@@ -5,18 +5,28 @@ export interface LogEntry {
}

export class LogLines implements LogEntry {
lines: string[]
private parts: Array<string|LogEntry>

constructor (...lines: string[]) {
this.lines = lines
constructor (...parts: Array<string|LogEntry>) {
this.parts = parts
}

render (): HTMLElement[] {
return this.lines.map(line => {
const div = document.createElement("div")
div.innerText = line
return div
const div = document.createElement("div")

this.parts.forEach(part => {
if (typeof part === "string") {
const partDiv = document.createElement("span")
partDiv.innerText = part
div.appendChild(partDiv)
} else {
(part as LogEntry).render().forEach(logPart => {
div.appendChild(logPart)
})
}
})

return [div]
}
}

@@ -114,8 +124,11 @@ export class PropElem implements LogEntry {
const span = document.createElement("span")
span.classList.add("stat-entry")

span.dataset.tooltip = this.prop

if (this.prop in Stat) {
span.dataset.tooltip = this.prop
} else if (this.prop in Vigor) {
span.dataset.tooltip = this.prop
}
if (this.prop in Stat) {
span.dataset.tooltipFull = StatDescs[this.prop as Stat]
} else if (this.prop in Vigor) {


+ 4
- 4
src/game/language.ts View File

@@ -1,12 +1,12 @@
import { Entity, POV } from './entity'
import { LogEntry, LogLines } from './interface'
import { LogEntry, LogLines, LogLine } from './interface'

export class POVPair<K extends Entity, V extends Entity> {
run (user: K, target: V): LogEntry {
const choice = this.options.find(element => element[0][0] === user.perspective && element[0][1] === target.perspective)

if (choice === undefined) {
return new LogLines("Fen didn't write any text for this...")
return new LogLine("Fen didn't write any text for this...")
} else {
return choice[1](user, target)
}
@@ -22,7 +22,7 @@ export class POVPairArgs<K extends Entity, V extends Entity, U> {
const choice = this.options.find(element => element[0][0] === user.perspective && element[0][1] === target.perspective)

if (choice === undefined) {
return new LogLines("Fen didn't write any text for this...")
return new LogLine("Fen didn't write any text for this...")
} else {
return choice[1](user, target, args)
}
@@ -37,7 +37,7 @@ export class POVSolo<K extends Entity> {
const choice = this.options.find(element => element[0][0] === user.perspective)

if (choice === undefined) {
return new LogLines("Fen didn't write any text for this...")
return new LogLine("Fen didn't write any text for this...")
} else {
return choice[1](user, args)
}


+ 57
- 56
src/game/vore.ts View File

@@ -1,7 +1,8 @@
import { Entity, Mortal, POV, Creature } from './entity'
import { Damage, DamageType, Stats, Actionable, Action, DevourAction, FeedAction, DigestAction, ReleaseAction, StruggleAction, Vigor } from './combat'
import { Damage, DamageType, Stats, Actionable, Action, Vigor } from './combat'
import { LogLines, LogEntry, CompositeLog, LogLine } from './interface'
import { Noun, Pronoun, POVPair, POVPairArgs, ImproperNoun } from './language'
import { DigestAction, DevourAction, ReleaseAction, StruggleAction } from './combat/actions'

export enum VoreType {
Oral = "Oral Vore",
@@ -142,7 +143,7 @@ abstract class NormalContainer implements Container {
lines.push(prey.toString())
})

return new LogLines(...lines)
return new LogLine(...lines)
}

digest (prey: Vore): LogEntry {
@@ -196,21 +197,21 @@ export class Stomach extends NormalContainer {
}

consumeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} munches you`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} munches ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} munches you`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} munches ${target.name}`)]
])

releaseLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You hork up ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} horks you up`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} horks up ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You hork up ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} horks you up`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} horks up ${target.name}`)]
])

struggleLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You claw your way out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} forces ${user.pronouns.possessive} way up your throat!`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} escapes from the gut of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You claw your way out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} forces ${user.pronouns.possessive} way up your throat!`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} escapes from the gut of ${target.name}`)]
])

tickLines = new POVPairArgs<Vore, Vore, { damage: Damage }>([
@@ -220,21 +221,21 @@ export class Stomach extends NormalContainer {
])

digestLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your stomach overwhelms ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s stomach finishes you off`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name.capital}'s squirms fade, overwhelmed by the stomach of ${user.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your stomach overwhelms ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s stomach finishes you off`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${target.name.capital}'s squirms fade, overwhelmed by the stomach of ${user.name}`)]
])

absorbLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
])

disposeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
])
}

@@ -244,21 +245,21 @@ export class InnerStomach extends InnerContainer {
}

consumeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} munches you`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} munches ${target.name.capital}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} munches you`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} munches ${target.name.capital}`)]
])

releaseLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You hork up ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} horks you up`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} horks up ${target.name.capital}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You hork up ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} horks you up`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} horks up ${target.name.capital}`)]
])

struggleLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You claw your way out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} forces ${user.pronouns.possessive} way up your throat!`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} escapes from the gut of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You claw your way out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} forces ${user.pronouns.possessive} way up your throat!`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} escapes from the gut of ${target.name}`)]
])

tickLines = new POVPairArgs<Vore, Vore, { damage: Damage }>([
@@ -268,21 +269,21 @@ export class InnerStomach extends InnerContainer {
])

digestLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your stomach overwhelms ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s stomach finishes you off`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name.capital}'s squirms fade, overwhelmed by the stomach of ${user.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your stomach overwhelms ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s stomach finishes you off`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${target.name.capital}'s squirms fade, overwhelmed by the stomach of ${user.name}`)]
])

absorbLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
])

disposeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
])
}

@@ -292,21 +293,21 @@ export class Bowels extends NormalContainer {
}

consumeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You force ${target.name} into your bowels`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} works you into ${user.pronouns.possessive} ass`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} anal-vores ${target.name.capital}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You force ${target.name} into your bowels`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} works you into ${user.pronouns.possessive} ass`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} anal-vores ${target.name.capital}`)]
])

releaseLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You let out ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} lets you out `)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} lets out ${target.name.capital}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You let out ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} lets you out `)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} lets out ${target.name.capital}`)]
])

struggleLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You claw your way out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} forces ${user.pronouns.possessive} way out your rump!`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} escapes from the bowels of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`You claw your way out of ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital} forces ${user.pronouns.possessive} way out your rump!`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} escapes from the bowels of ${target.name}`)]
])

tickLines = new POVPairArgs<Vore, Vore, { damage: Damage }>([
@@ -316,20 +317,20 @@ export class Bowels extends NormalContainer {
])

digestLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your bowels overwhelm ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s bowels finish you off`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name.capital}'s squirms fade, overwhelmed by the bowels of ${user.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your bowels overwhelm ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s bowels finish you off`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${target.name.capital}'s squirms fade, overwhelmed by the bowels of ${user.name}`)]
])

absorbLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
])

disposeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
[[POV.First, POV.Third], (user, target) => new LogLine(`Your guts completely absorb ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new LogLine(`${user.name.capital}'s guts soak you up like water in a sponge`)],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} finishes absorbing the remains of ${target.name}`)]
])
}

Loading…
Cancel
Save