Procházet zdrojové kódy

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 před 5 roky
rodič
revize
ec8ccbd53f
4 změnil soubory, kde provedl 143 přidání a 5 odebrání
  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 Zobrazit soubor

@@ -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 Zobrazit soubor

@@ -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 Zobrazit soubor

@@ -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 Zobrazit soubor

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

Načítá se…
Zrušit
Uložit