Преглед на файлове

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

Loading…
Отказ
Запис