ソースを参照

Add a new test type for general opposed stat tests

The test can compare one or more of the user's stats against
one or more of the target's stats. This is explained by the test.
master
Fen Dweller 5年前
コミット
ec8ccbd53f
4個のファイルの変更143行の追加5行の削除
  1. +1
    -1
      src/components/ActionButton.vue
  2. +14
    -2
      src/game/combat.ts
  3. +90
    -1
      src/game/combat/tests.ts
  4. +38
    -1
      src/game/creatures/player.ts

+ 1
- 1
src/components/ActionButton.vue ファイルの表示

@@ -1,5 +1,5 @@
<template>
<button @focus="describe" @mouseover="describe" @mouseleave="undescribe" class="action-button" @click="execute">
<button @focus="describe" @mouseover="describe" class="action-button" @click="execute">
<div class="action-title">{{ action.name }}</div>
<div class="action-desc">{{ action.desc }}</div>
</button>


+ 14
- 2
src/game/combat.ts ファイルの表示

@@ -1,6 +1,6 @@
import { Creature } from "./creature"
import { TextLike, DynText, ToBe, LiveText, PairLineArgs, PairLine } from './language'
import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog } from './interface'
import { LogEntry, LogLines, FAElem, LogLine, FormatEntry, FormatOpt, PropElem, nilLog, Newline } from './interface'
import { Resistances } from './entity'
import { World } from './world'

@@ -52,6 +52,15 @@ export enum Stat {

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

export const StatToVigor: {[key in Stat]: Vigor} = {
Toughness: Vigor.Health,
Power: Vigor.Health,
Reflexes: Vigor.Stamina,
Agility: Vigor.Stamina,
Willpower: Vigor.Resolve,
Charm: Vigor.Resolve
}

export const StatIcons: {[key in Stat]: string} = {
Toughness: 'fas fa-heartbeat',
Power: 'fas fa-fist-raised',
@@ -359,7 +368,6 @@ export abstract class Action {
) {

}
// TODO explain the tests in here

allowed (user: Creature, target: Creature): boolean {
return this.conditions.every(cond => cond.allowed(user, target))
@@ -380,6 +388,9 @@ export abstract class Action {

describe (user: Creature, target: Creature): LogEntry {
return new LogLines(
new LogLine(
`Success chance: ${(this.odds(user, target) * 100).toFixed(0)}%`
),
...this.tests.map(test => test.explain(user, target))
)
}
@@ -416,6 +427,7 @@ export class CompositionAction extends Action {
describe (user: Creature, target: Creature): LogEntry {
return new LogLines(
...this.consequences.map(consequence => consequence.describePair(user, target)).concat(
new Newline(),
super.describe(user, target)
)
)


+ 90
- 1
src/game/combat/tests.ts ファイルの表示

@@ -1,6 +1,7 @@
import { CombatTest, Stat, Vigor } from '../combat'
import { CombatTest, Stat, Vigor, Stats, StatToVigor } from '../combat'
import { Creature } from "../creature"
import { LogEntry, LogLines, PropElem, LogLine, nilLog } from '../interface'
import { Verb } from '../language'

function logistic (x0: number, L: number, k: number): (x: number) => number {
return (x: number) => {
@@ -32,6 +33,94 @@ abstract class RandomTest implements CombatTest {
abstract explain(user: Creature, target: Creature): LogEntry
}

export enum TestCategory {
Attack = "Attack",
Vore = "Vore"
}

export class OpposedStatTest extends RandomTest {
private f: (x: number) => number
private k = 0.1

// how much a stat can be reduced by its corresponding vigor being low
private maxStatVigorPenalty = 0.5

// how much the total score can be reduced by each vigor being low
private maxTotalVigorPenalty = 0.1

constructor (
public readonly userStats: Partial<Stats>,
public readonly targetStats: Partial<Stats>,
fail: (user: Creature, target: Creature) => LogEntry,
public category: TestCategory,
private bias = 0
) {
super(fail)
this.f = logistic(0, 1, this.k)
}

odds (user: Creature, target: Creature): number {
const userScore = this.getScore(user, this.userStats)
const targetScore = this.getScore(target, this.targetStats)
console.log(userScore, targetScore)

return this.f(userScore - targetScore + this.bias)
}

explain (user: Creature, target: Creature): LogEntry {
return new LogLines(
new LogLine(
`Pits `,
...Object.entries(this.userStats).map(([stat, frac]) => {
if (frac !== undefined) {
return new LogLine(`${(frac * 100).toFixed(0)}% `, new PropElem(stat as Stat))
} else {
return nilLog
}
}),
` from ${user.name.possessive} stats against `,
...Object.entries(this.targetStats).map(([stat, frac]) => {
if (frac !== undefined) {
return new LogLine(`${(frac * 100).toFixed(0)}% `, new PropElem(stat as Stat))
} else {
return nilLog
}
}),
` from ${target.name.possessive} stats.`
),
new LogLine(
`${user.name.capital.possessive} total score is ${this.getScore(user, this.userStats)}`
),
new LogLine(
`${target.name.capital.possessive} total score is ${this.getScore(target, this.targetStats)}`
),
new LogLine(
`${user.name.capital} ${user.name.conjugate(new Verb("have", "has"))} a ${(this.odds(user, target) * 100).toFixed(0)}% chance of winning this test.`
)
)
}

private getScore (actor: Creature, parts: Partial<Stats>): number {
const total = Object.entries(parts).reduce((total: number, [stat, frac]) => {
let value = actor.stats[stat as Stat] * (frac === undefined ? 0 : frac)

const vigor = StatToVigor[stat as Stat]
value = value * (1 - this.maxStatVigorPenalty) + value * this.maxStatVigorPenalty * actor.vigors[vigor] / actor.maxVigors[vigor]

console.log(value)
return total + value
}, 0)

const modifiedTotal = Object.keys(Vigor).reduce(
(total, vigor) => {
return total * (1 - this.maxStatVigorPenalty) + total * actor.vigors[vigor as Vigor] / actor.maxVigors[vigor as Vigor]
},
total
)
return modifiedTotal
}
}

export class StatVigorSizeTest extends RandomTest {
private f: (x: number) => number
private k = 0.1


+ 38
- 1
src/game/creatures/player.ts ファイルの表示

@@ -1,8 +1,12 @@
import { Creature } from "../creature"
import { ProperNoun, TheyPronouns, ImproperNoun, POV } from '../language'
import { Damage, DamageType, Vigor, ConstantDamageFormula } from '../combat'
import { Damage, DamageType, Vigor, ConstantDamageFormula, CompositionAction, UniformRandomDamageFormula, StatDamageFormula, Stat } from '../combat'
import { Stomach, Bowels, VoreType, anyVore } from '../vore'
import { AttackAction } from '../combat/actions'
import { TogetherCondition } from '../combat/conditions'
import { DamageConsequence } from '../combat/consequences'
import { OpposedStatTest, TestCategory } from '../combat/tests'
import { LogLine } from '../interface'

export class Player extends Creature {
constructor () {
@@ -25,5 +29,38 @@ export class Player extends Creature {

this.containers.push(bowels)
this.perspective = POV.Second

this.actions.push(
new CompositionAction(
"Bite",
"Munch",
{
conditions: [
new TogetherCondition()
],
consequences: [
new DamageConsequence(
new StatDamageFormula([
{ fraction: 2, stat: Stat.Power, target: Vigor.Health, type: DamageType.Crush },
{ fraction: 2, stat: Stat.Power, target: Vigor.Stamina, type: DamageType.Crush }
])
)
],
tests: [
new OpposedStatTest(
{
Power: 1
},
{
Agility: 1
},
(user, target) => new LogLine(`No munch.`),
TestCategory.Attack,
0
)
]
}
)
)
}
}

読み込み中…
キャンセル
保存