Selaa lähdekoodia

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.
vintage
Fen Dweller 5 vuotta sitten
vanhempi
commit
ec8ccbd53f
4 muutettua tiedostoa jossa 143 lisäystä ja 5 poistoa
  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 Näytä tiedosto

@@ -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 Näytä tiedosto

@@ -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 Näytä tiedosto

@@ -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 Näytä tiedosto

@@ -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…
Peruuta
Tallenna