소스 검색

Add tests w/ vigor, nice damage displays, and more wolf actions

master
Fen Dweller 5 년 전
부모
커밋
f075dd820e
7개의 변경된 파일184개의 추가작업 그리고 85개의 파일을 삭제
  1. +4
    -0
      src/components/Combat.vue
  2. +105
    -52
      src/game/combat.ts
  3. +2
    -2
      src/game/creatures/player.ts
  4. +12
    -3
      src/game/creatures/wolf.ts
  5. +14
    -5
      src/game/entity.ts
  6. +21
    -2
      src/game/interface.ts
  7. +26
    -21
      src/game/vore.ts

+ 4
- 0
src/components/Combat.vue 파일 보기

@@ -145,6 +145,10 @@ a {
font-weight: bold;
}

.damage-instance {
white-space: nowrap;
}

#log > div {
color: #888;
padding-top: 8pt;


+ 105
- 52
src/game/combat.ts 파일 보기

@@ -1,8 +1,50 @@
import { Creature, POV, Entity } from './entity'
import { POVPair, POVPairArgs } from './language'
import { Container } from './vore'
import { LogEntry, LogLines, CompositeLog, FAElem, LogLine } from './interface'
import { LogEntry, LogLines, CompositeLog, FAElem, LogLine, FormatEntry, FormatOpt } from './interface'

export enum DamageType {
Pierce = "Pierce",
Slash = "Slash",
Crush = "Crush",
Acid = "Acid",
Seduction = "Seduction",
Dominance = "Dominance"
}

export interface DamageInstance {
type: DamageType;
amount: number;
target: Vigor;
}

export enum Vigor {
Health = "Health",
Stamina = "Stamina",
Willpower = "Willpower"
}

export const VigorIcons: {[key in Vigor]: string} = {
[Vigor.Health]: "fas fa-heart",
[Vigor.Stamina]: "fas fa-bolt",
[Vigor.Willpower]: "fas fa-brain"
}

export type Vigors = {[key in Vigor]: number}

export enum Stat {
STR = 'Strength',
DEX = 'Dexterity',
CON = 'Constitution'
}

export type Stats = {[key in Stat]: number}

export const StatIcons: {[key in Stat]: string} = {
[Stat.STR]: 'fas fa-fist-raised',
[Stat.DEX]: 'fas fa-feather',
[Stat.CON]: 'fas fa-heartbeat'
}
export interface CombatTest {
test: (user: Creature, target: Creature) => boolean;
odds: (user: Creature, target: Creature) => number;
@@ -24,7 +66,7 @@ abstract class RandomTest implements CombatTest {
abstract explain(user: Creature, target: Creature): LogEntry
}

export class StatTest extends RandomTest {
export class StatVigorTest extends RandomTest {
private f: (x: number) => number

constructor (public readonly stat: Stat, k = 0.1) {
@@ -33,7 +75,27 @@ export class StatTest extends RandomTest {
}

odds (user: Creature, target: Creature): number {
return this.f(user.stats[this.stat] - target.stats[this.stat])
let userPercent = 1
let targetPercent = 1

Object.keys(Vigor).forEach(key => {
userPercent *= user.vigors[key as Vigor] / user.maxVigors[key as Vigor]
targetPercent *= target.vigors[key as Vigor] / target.maxVigors[key as Vigor]

userPercent = Math.max(0, userPercent)
targetPercent = Math.max(0, targetPercent)
})

if (userPercent === 0) {
targetPercent *= 4
}

if (targetPercent === 0) {
userPercent *= 4
}

console.log(userPercent, targetPercent, this.f(user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent))
return this.f(user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent)
}

explain (user: Creature, target: Creature): LogEntry {
@@ -54,61 +116,48 @@ export class StatTest extends RandomTest {
}
}

export class ChanceTest extends RandomTest {
constructor (public readonly chance: number) {
export class StatTest extends RandomTest {
private f: (x: number) => number

constructor (public readonly stat: Stat, k = 0.1) {
super()
this.f = logistic(0, 1, k)
}

odds (user: Creature, target: Creature): number {
return this.chance
return this.f(user.stats[this.stat] - target.stats[this.stat])
}

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

export enum DamageType {
Pierce = "Pierce",
Slash = "Slash",
Crush = "Crush",
Acid = "Acid",
Seduction = "Seduction",
Dominance = "Dominance"
}
const delta: number = user.stats[this.stat] - target.stats[this.stat]
let result: string

export interface DamageInstance {
type: DamageType;
amount: number;
target: Vigor;
}
if (delta === 0) {
result = 'You and the target have the same ' + this.stat + '.'
} else if (delta < 0) {
result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
} else {
result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
}

export enum Vigor {
Health = "Health",
Stamina = "Stamina",
Willpower = "Willpower"
}
result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%'

export const VigorIcons: {[key in Vigor]: string} = {
[Vigor.Health]: "fas fa-heart",
[Vigor.Stamina]: "fas fa-bolt",
[Vigor.Willpower]: "fas fa-brain"
return new LogLines(result)
}
}

export type Vigors = {[key in Vigor]: number}

export enum Stat {
STR = 'Strength',
DEX = 'Dexterity',
CON = 'Constitution'
}
export class ChanceTest extends RandomTest {
constructor (public readonly chance: number) {
super()
}

export type Stats = {[key in Stat]: number}
odds (user: Creature, target: Creature): number {
return this.chance
}

export const StatIcons: {[key in Stat]: string} = {
[Stat.STR]: 'fas fa-fist-raised',
[Stat.DEX]: 'fas fa-feather',
[Stat.CON]: 'fas fa-heartbeat'
explain (user: Creature, target: Creature): LogEntry {
return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
}
}

export class Damage {
@@ -149,7 +198,7 @@ export class Damage {
totals[instance.target] += instance.amount
})

return new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [totals[key as Vigor].toString(), new FAElem(VigorIcons[key as Vigor])]))
return new FormatEntry(new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [totals[key as Vigor].toFixed(0).toString(), new FAElem(VigorIcons[key as Vigor])])), FormatOpt.DamageInst)
}
}

@@ -276,7 +325,7 @@ export class AttackAction extends TogetherAction {
}

export class DevourAction extends TogetherAction {
private test: StatTest
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}`)],
@@ -299,7 +348,7 @@ export class DevourAction extends TogetherAction {
constructor (protected container: Container) {
super('Devour', 'Try to consume your foe', [new CapableCondition()])
this.name += ` (${container.name})`
this.test = new StatTest(Stat.STR)
this.test = new StatVigorTest(Stat.STR)
}

execute (user: Creature, target: Creature): LogEntry {
@@ -348,7 +397,7 @@ export class FeedAction extends TogetherAction {
}

export class StruggleAction extends PairAction {
private test: StatTest
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}`)],
@@ -366,7 +415,7 @@ export class StruggleAction extends PairAction {

constructor (public container: Container) {
super('Struggle', 'Try to escape your predator', [new CapableCondition()])
this.test = new StatTest(Stat.STR)
this.test = new StatVigorTest(Stat.STR)
}

execute (user: Creature, target: Creature): LogEntry {
@@ -406,7 +455,7 @@ export class DigestAction extends SelfAction {

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

export class TransferAction extends PairAction {
protected lines: POVPair<Entity, Entity> = new POVPair([])
protected 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 ${target.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) {
@@ -441,6 +494,6 @@ export class TransferAction extends PairAction {
execute (user: Creature, target: Creature): LogEntry {
this.from.release(target)
this.to.consume(target)
return this.lines.run(user, target)
return this.lines.run(user, target, { from: this.from, to: this.to })
}
}

+ 2
- 2
src/game/creatures/player.ts 파일 보기

@@ -5,11 +5,11 @@ import { Stomach, Bowels, VoreType } from '../vore'

export class Player extends Creature {
constructor () {
super(new ProperNoun('The Dude'), TheyPronouns, { [Stat.STR]: 20, [Stat.DEX]: 20, [Stat.CON]: 20 }, new Set([VoreType.Oral]), new Set([VoreType.Oral, VoreType.Anal]), 50)
super(new ProperNoun('The Dude'), TheyPronouns, { [Stat.STR]: 20, [Stat.DEX]: 20, [Stat.CON]: 20 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 50)

this.actions.push(new AttackAction(new Damage({ type: DamageType.Pierce, amount: 20, target: Vigor.Health }, { type: DamageType.Pierce, amount: 20, target: Vigor.Stamina })))

const stomach = new Stomach(this, 100, new Damage({ amount: 100000000000, type: DamageType.Acid, target: Vigor.Health }, { amount: 10, type: DamageType.Crush, target: Vigor.Health }))
const stomach = new Stomach(this, 100, 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, 100, new Damage({ amount: 20, type: DamageType.Crush, target: Vigor.Health }))


+ 12
- 3
src/game/creatures/wolf.ts 파일 보기

@@ -42,14 +42,23 @@ class HypnoAction extends AttackAction {

export class Wolf extends Creature {
constructor () {
super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 15, [Stat.DEX]: 15, [Stat.CON]: 25 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral]), 25)
super(new ImproperNoun('wolf', 'wolves'), MalePronouns, { [Stat.STR]: 15, [Stat.DEX]: 15, [Stat.CON]: 25 }, new Set([VoreType.Oral, VoreType.Anal]), new Set([VoreType.Oral, VoreType.Anal]), 25)
this.actions.push(new BiteAction())
this.actions.push(new HypnoAction())

const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid, target: Vigor.Health }, { amount: 500, type: DamageType.Crush, target: Vigor.Health }))
const stomach = new Stomach(this, 50, 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.Willpower }
))

this.containers.push(stomach)
const bowels = new Bowels(this, 50, new Damage({ amount: 50, type: DamageType.Acid, target: Vigor.Health }, { amount: 500, type: DamageType.Crush, target: Vigor.Health }))

const bowels = new Bowels(this, 50, new Damage(
{ amount: 10, type: DamageType.Crush, target: Vigor.Health },
{ amount: 25, type: DamageType.Crush, target: Vigor.Stamina },
{ amount: 25, type: DamageType.Dominance, target: Vigor.Willpower }
))

this.containers.push(bowels)



+ 14
- 5
src/game/entity.ts 파일 보기

@@ -71,14 +71,23 @@ export class Creature implements Mortal, Pred, Prey, Combatant {
}

get status (): string {
if (this.vigors[Vigor.Health] < 0) {
return "DEAD"
if (this.vigors[Vigor.Health] <= -100) {
return "Dead"
}
if (this.vigors[Vigor.Stamina] < 0) {
if (this.vigors[Vigor.Stamina] <= -100) {
return "Unconscious"
}
if (this.vigors[Vigor.Willpower] < 0) {
return "Too horny"
if (this.vigors[Vigor.Willpower] <= -100) {
return "Broken"
}
if (this.vigors[Vigor.Health] <= 0) {
return "Unconscious"
}
if (this.vigors[Vigor.Stamina] <= 0) {
return "Exhausted"
}
if (this.vigors[Vigor.Willpower] <= 0) {
return "Overpowered"
}
if (this.containedIn !== null) {
return "Devoured"


+ 21
- 2
src/game/interface.ts 파일 보기

@@ -19,11 +19,30 @@ export class LogLines implements LogEntry {
}

export enum FormatOpt {
Damage = "log-damage"
Damage = "log-damage",
DamageInst = "damage-instance"
}

export class FormatEntry implements LogEntry {
constructor (private entry: LogEntry, private opt: FormatOpt) {

}

render (): HTMLElement[] {
const span = document.createElement("span")

this.entry.render().forEach(elem => {
span.appendChild(elem)
})

span.classList.add(this.opt)

return [span]
}
}

export class FormatText implements LogEntry {
constructor (private line: string, private opt: FormatOpt) {
constructor (private opt: FormatOpt, private line: string) {

}



+ 26
- 21
src/game/vore.ts 파일 보기

@@ -1,6 +1,6 @@
import { Entity, Mortal, POV } from './entity'
import { Damage, Actionable, Action, DevourAction, FeedAction, DigestAction, ReleaseAction, StruggleAction, Vigor } from './combat'
import { LogLines, LogEntry, CompositeLog } from './interface'
import { LogLines, LogEntry, CompositeLog, LogLine } from './interface'
import { POVSolo, POVPair, POVPairArgs } from './language'

export enum VoreType {
@@ -46,7 +46,7 @@ abstract class NormalContainer implements Container {
abstract consumeLines: POVPair<Pred, Prey>
abstract releaseLines: POVPair<Pred, Prey>
abstract struggleLines: POVPair<Prey, Pred>
abstract tickLines: POVSolo<Pred>
abstract tickLines: POVPairArgs<Pred, Prey, { damage: Damage }>
abstract digestLines: POVPair<Pred, Prey>
abstract absorbLines: POVPair<Pred, Prey>
abstract disposeLines: POVPair<Pred, Prey>
@@ -85,9 +85,11 @@ abstract class NormalContainer implements Container {
const digested: Array<Prey> = []
const absorbed: Array<Prey> = []

const scaled = this.damage.scale(dt / 60)

this.contents.forEach(prey => {
const start = prey.vigors[Vigor.Health]
prey.takeDamage(this.damage.scale(dt / 3600))
prey.takeDamage(scaled)
const end = prey.vigors[Vigor.Health]

if (start > 0 && end <= 0) {
@@ -99,6 +101,7 @@ abstract class NormalContainer implements Container {
}
})

const tickedEntries = new CompositeLog(...this.contents.map(prey => this.tickLines.run(this.owner, prey, { damage: scaled })))
const digestedEntries = new CompositeLog(...digested.map(prey => this.digest(prey)))
const absorbedEntries = new CompositeLog(...absorbed.map(prey => this.absorb(prey)))

@@ -106,7 +109,7 @@ abstract class NormalContainer implements Container {
return prey.vigors[Vigor.Health] > -100
})

return new CompositeLog(this.tickLines.run(this.owner), digestedEntries, absorbedEntries)
return new CompositeLog(tickedEntries, digestedEntries, absorbedEntries)
}

describe (): LogEntry {
@@ -168,9 +171,10 @@ export class Stomach extends NormalContainer {
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} escapes from the gut of ${target.name}`)]
])

tickLines = new POVSolo([
[[POV.First], (user) => new LogLines(`Your stomach gurgles and churns`)],
[[POV.Third], (user) => new LogLines(`${user.name.capital}'s gut snarls and gurgles`)]
tickLines = new POVPairArgs<Pred, Prey, { damage: Damage }>([
[[POV.First, POV.Third], (user, target, args) => new LogLine(`Your stomach gurgles ${target.name} for `, args.damage.renderShort())],
[[POV.Third, POV.First], (user, target, args) => new LogLine(`${user.name.capital}'s stomach churns you for `, args.damage.renderShort())],
[[POV.Third, POV.Third], (user, target, args) => new LogLine(`${target.name.capital} churns ${user.name} for `, args.damage.renderShort())]
])

digestLines = new POVPair([
@@ -198,32 +202,33 @@ export class Bowels 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.capital}`)]
[[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}`)]
])

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

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.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}`)]
])

tickLines = new POVSolo([
[[POV.First], (user) => new LogLines(`Your stomach gurgles and churns!`)],
[[POV.Third], (user) => new LogLines(`${user.name.capital}'s gut snarls and gurgles`)]
tickLines = new POVPairArgs<Pred, Prey, { damage: Damage }>([
[[POV.First, POV.Third], (user, target, args) => new LogLine(`Your bowels gurgle ${target.name} for `, args.damage.renderShort())],
[[POV.Third, POV.First], (user, target, args) => new LogLine(`${user.name.capital}'s bowels churn you for `, args.damage.renderShort())],
[[POV.Third, POV.Third], (user, target, args) => new LogLine(`${target.name.capital} churns ${user.name} for `, args.damage.renderShort())]
])

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

absorbLines = new POVPair([


불러오는 중...
취소
저장