Bladeren bron

Set up a more generic system for combat tests

Each combat test can be made up of many Scorers; each Scorer contributes to the total score.
vintage
Fen Dweller 5 jaren geleden
bovenliggende
commit
3e4843d09b
2 gewijzigde bestanden met toevoegingen van 130 en 7 verwijderingen
  1. +17
    -7
      src/game/combat/actions.ts
  2. +113
    -0
      src/game/combat/tests.ts

+ 17
- 7
src/game/combat/actions.ts Bestand weergeven

@@ -1,4 +1,4 @@
import { StatTest, StatVigorTest, StatVigorSizeTest, OpposedStatTest, TestCategory } from './tests'
import { StatTest, StatVigorTest, StatVigorSizeTest, OpposedStatTest, TestCategory, CompositionTest, OpposedStatScorer } from './tests'
import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '../language'
import { Entity } from '../entity'
import { Creature } from "../creature"
@@ -67,13 +67,23 @@ export class AttackAction extends DamageAction {
verb.root.capital,
'Attack the enemy',
damage,
[new StatTest(
Stat.Power,
15,
(user, target) => new LogLine(
`${user.name.capital} ${user.name.conjugate(new Verb('miss', 'misses'))} ${target.name.objective}!`
[
new CompositionTest(
[
new OpposedStatScorer(
{
Power: 1
},
{
Reflexes: 1
}
)
],
(user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb("swing"))} and ${user.name.conjugate(new Verb("miss", "misses"))} ${target.name.objective}.`),
TestCategory.Attack,
0
)
)],
],
[new TogetherCondition()]
)
}


+ 113
- 0
src/game/combat/tests.ts Bestand weergeven

@@ -9,6 +9,88 @@ function logistic (x0: number, L: number, k: number): (x: number) => number {
}
}

/**
* A [[Scorer]] produces a score for a creature in a certain situation
*/
export interface Scorer {
userScore (attacker: Creature): number;
targetScore (defender: Creature): number;
explain(user: Creature, target: Creature): LogEntry;
}

export class OpposedStatScorer implements Scorer {
private maxStatVigorPenalty = 0.5
private maxTotalVigorPenalty = 0.1

constructor (private userStats: Partial<Stats & VoreStats>, private targetStats: Partial<Stats & VoreStats>) {

}

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
}
}),
` 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
}
})
),
new LogLine(
`Score delta: ${this.computeScore(user, this.userStats) - this.computeScore(target, this.targetStats)}`
)
)
}

userScore (attacker: Creature): number {
return this.computeScore(attacker, this.userStats)
}

targetScore (defender: Creature): number {
return this.computeScore(defender, this.targetStats)
}

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

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

return total + value
} else if (stat in VoreStat) {
const value = subject.voreStats[stat as VoreStat] * (frac === undefined ? 0 : frac)

return total + value
} else {
return total
}
}, 0)

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

return modifiedTotal
}
}

// TODO this will need to be able to return a LogEntry at some point

abstract class RandomTest implements CombatTest {
@@ -38,6 +120,37 @@ export enum TestCategory {
Vore = "Vore"
}

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

constructor (
private scorers: Scorer[],
fail: (user: Creature, target: Creature) => LogEntry,
public category: TestCategory,
private bias = 0
) {
super(fail)
this.f = logistic(0, 1, this.k)
}

explain (user: Creature, target: Creature): LogEntry {
return new LogLines(
...this.scorers.map(scorer => scorer.explain(user, target))
)
}

odds (user: Creature, target: Creature): number {
const userScore = this.scorers.reduce((score, scorer) => score + scorer.userScore(user), 0)
const targetScore = this.scorers.reduce((score, scorer) => score + scorer.targetScore(target), 0)

const userMod = user.effects.reduce((score, effect) => score + effect.modTestOffense(user, target, this.category), 0)
const targetMod = target.effects.reduce((score, effect) => score + effect.modTestDefense(target, user, this.category), 0)

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

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


Laden…
Annuleren
Opslaan