diff --git a/src/game/combat/actions.ts b/src/game/combat/actions.ts index 2514bc4..ef823e4 100644 --- a/src/game/combat/actions.ts +++ b/src/game/combat/actions.ts @@ -1,9 +1,9 @@ -import { StatTest, StatVigorTest, StatVigorSizeTest, OpposedStatTest, TestCategory, CompositionTest, OpposedStatScorer } from './tests' +import { TestCategory, CompositionTest, OpposedStatScorer } from './tests' import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '../language' import { Entity } from '../entity' import { Creature } from "../creature" -import { Damage, DamageFormula, Stat, Vigor, Action, Condition, CombatTest, CompositionAction } from '../combat' -import { LogLine, LogLines, LogEntry, nilLog, Newline } from '../interface' +import { Damage, DamageFormula, Vigor, Action, Condition, CombatTest, CompositionAction } from '../combat' +import { LogLine, LogLines, LogEntry } from '../interface' import { VoreContainer, Container } from '../vore' import { CapableCondition, UserDrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition, ContainsCondition, ContainedByCondition, HasRoomCondition } from './conditions' import { ConsumeConsequence } from './consequences' @@ -108,13 +108,18 @@ export class DevourAction extends CompositionAction { new LiveText(container, x => `Try to ${x.consumeVerb} your foe`), { conditions: [new CapableCondition(), new TogetherCondition(), new HasRoomCondition(container)], - tests: [new OpposedStatTest( - { Power: 1, Charm: 1, Mass: 0.05 }, - { Toughness: 1, Willpower: 1, Bulk: 0.05 }, - (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to ${container.consumeVerb} ${target.name.objective}.`), - TestCategory.Vore, - -5 - )], + tests: [ + new CompositionTest( + [ + new OpposedStatScorer( + { Power: 1, Charm: 1, Mass: 0.05 }, + { Toughness: 1, Willpower: 1, Bulk: 0.05 } + ) + ], + (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to ${container.consumeVerb} ${target.name.objective}.`), + TestCategory.Vore, + -5 + )], consequences: [ new ConsumeConsequence(container) ] @@ -191,9 +196,13 @@ export class StruggleAction extends Action { 'Try to escape from your foe', [new CapableCondition(), new PairCondition(), new ContainedByCondition(container)], [ - new OpposedStatTest( - { Power: 1, Agility: 1, Bulk: 0.05 }, - { Toughness: 1, Reflexes: 1, Mass: 0.05 }, + new CompositionTest( + [ + new OpposedStatScorer( + { Power: 1, Agility: 1, Bulk: 0.05 }, + { Toughness: 1, Reflexes: 1, Mass: 0.05 } + ) + ], (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to escape from ${target.name.possessive} ${container.name}.`), TestCategory.Vore, -5 diff --git a/src/game/combat/tests.ts b/src/game/combat/tests.ts index 3bf9e7e..5817237 100644 --- a/src/game/combat/tests.ts +++ b/src/game/combat/tests.ts @@ -1,7 +1,6 @@ import { CombatTest, Stat, Vigor, Stats, StatToVigor, VoreStats, VoreStat } 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) => { @@ -146,283 +145,8 @@ export class CompositionTest extends RandomTest { const userScore = this.scorers.reduce((score, scorer) => scorer.userScore(user, score), 0) const targetScore = this.scorers.reduce((score, scorer) => scorer.targetScore(target, score), 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 - - // 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, - public readonly targetStats: Partial, - 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.getScoreOffense(user, target, this.userStats) - const targetScore = this.getScoreDefense(target, user, this.targetStats) - 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)}% of `, 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)}% of `, new PropElem(stat as Stat), `, `) - } else { - return nilLog - } - }) - ), - new LogLine( - `${user.name.capital}: ${this.getScoreOffense(user, target, this.userStats)} // ${this.getScoreDefense(target, user, this.targetStats)} :${target.name.capital}` - ), - 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 getScoreDefense (defender: Creature, attacker: Creature, parts: Partial): number { - return this.getScore(defender, parts) + defender.effects.reduce((total, effect) => total + effect.modTestDefense(defender, attacker, this.category), 0) - } - - private getScoreOffense (attacker: Creature, defender: Creature, parts: Partial): number { - return this.getScore(attacker, parts) + attacker.effects.reduce((total, effect) => total + effect.modTestOffense(attacker, defender, this.category), 0) - } - - private getScore (actor: Creature, parts: Partial): number { - const total = Object.entries(parts).reduce((total: number, [stat, frac]) => { - if (stat in Stat) { - 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] - - return total + value - } else if (stat in VoreStat) { - const value = actor.voreStats[stat as VoreStat] * (frac === undefined ? 0 : frac) - - return total + value - } else { - return total - } - }, 0) - - const modifiedTotal = Object.keys(Vigor).reduce( - (total, vigor) => { - return total * (1 - this.maxStatVigorPenalty) + total * this.maxStatVigorPenalty * 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 - - constructor (public readonly stat: Stat, private bias = 0, fail: (user: Creature, target: Creature) => LogEntry) { - super(fail) - this.f = logistic(0, 1, this.k) - } - - odds (user: Creature, target: Creature): number { - let userPercent = 1 - let targetPercent = 1 - - Object.keys(Vigor).forEach(key => { - userPercent *= user.vigors[key as Vigor] / Math.max(1, user.maxVigors[key as Vigor]) - targetPercent *= target.vigors[key as Vigor] / Math.max(1, target.maxVigors[key as Vigor]) - - userPercent = Math.max(0, userPercent) - targetPercent = Math.max(0, targetPercent) - }) - - if (userPercent === 0) { - targetPercent *= 4 - } - - if (targetPercent === 0) { - userPercent *= 4 - } - - const sizeOffset = Math.log2(user.voreStats.Mass / target.voreStats.Mass) - - return this.f(this.bias + sizeOffset * 5 + user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent) - } - - explain (user: Creature, target: Creature): LogEntry { - let result: LogEntry - - let userPercent = 1 - let targetPercent = 1 - - Object.keys(Vigor).forEach(key => { - userPercent *= user.vigors[key as Vigor] / user.maxVigors[key as Vigor] - targetPercent *= target.vigors[key as Vigor] / target.maxVigors[key as Vigor] - - userPercent = Math.max(0, userPercent) - targetPercent = Math.max(0, targetPercent) - }) - - if (userPercent === 0) { - targetPercent *= 4 - } - - if (targetPercent === 0) { - userPercent *= 4 - } - - const sizeOffset = Math.log2(user.voreStats.Mass / target.voreStats.Mass) - - const userMod = user.stats[this.stat] * userPercent - const targetMod = target.stats[this.stat] * targetPercent - const delta = userMod - targetMod + sizeOffset * 5 - - if (delta === 0) { - result = new LogLine('You and the target have the same effective', new PropElem(this.stat), '.') - } else if (delta < 0) { - result = new LogLine('You effectively have ', new PropElem(this.stat, -delta), ' less than your foe.') - } else { - result = new LogLine('You effectively have ', new PropElem(this.stat, delta), ' more than you foe.') - } - - result = new LogLine(result, 'Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%') - - return result - } -} - -export class StatVigorTest extends RandomTest { - private f: (x: number) => number - private k = 0.1 - - constructor (public readonly stat: Stat, private bias = 0, fail: (user: Creature, target: Creature) => LogEntry) { - super(fail) - this.f = logistic(0, 1, this.k) - } - - odds (user: Creature, target: Creature): number { - let userPercent = 1 - let targetPercent = 1 - - Object.keys(Vigor).forEach(key => { - userPercent *= user.vigors[key as Vigor] / Math.max(1, user.maxVigors[key as Vigor]) - targetPercent *= target.vigors[key as Vigor] / Math.max(1, target.maxVigors[key as Vigor]) - - userPercent = Math.max(0, userPercent) - targetPercent = Math.max(0, targetPercent) - }) - - if (userPercent === 0) { - targetPercent *= 4 - } - - if (targetPercent === 0) { - userPercent *= 4 - } - - return this.f(this.bias + user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent) - } - - explain (user: Creature, target: Creature): LogEntry { - let result: LogEntry - - let userPercent = 1 - let targetPercent = 1 - - Object.keys(Vigor).forEach(key => { - userPercent *= user.vigors[key as Vigor] / user.maxVigors[key as Vigor] - targetPercent *= target.vigors[key as Vigor] / target.maxVigors[key as Vigor] - - userPercent = Math.max(0, userPercent) - targetPercent = Math.max(0, targetPercent) - }) - - if (userPercent === 0) { - targetPercent *= 4 - } - - if (targetPercent === 0) { - userPercent *= 4 - } - const userMod = user.stats[this.stat] * userPercent - const targetMod = target.stats[this.stat] * targetPercent - const delta = userMod - targetMod - - if (delta === 0) { - result = new LogLine('You and the target have the same effective', new PropElem(this.stat), '.') - } else if (delta < 0) { - result = new LogLine('You effectively have ', new PropElem(this.stat, -delta), ' less than your foe.') - } else { - result = new LogLine('You effectively have ', new PropElem(this.stat, delta), ' more than you foe.') - } - - result = new LogLine(result, 'Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%') - - return result - } -} - -export class StatTest extends RandomTest { - private f: (x: number) => number - private k = 0.1 - - constructor (public readonly stat: Stat, private bias = 0, fail: (user: Creature, target: Creature) => LogEntry) { - super(fail) - this.f = logistic(0, 1, this.k) - } - - odds (user: Creature, target: Creature): number { - return this.f(this.bias + user.stats[this.stat] - target.stats[this.stat]) - } - - explain (user: Creature, target: Creature): LogEntry { - const delta: number = user.stats[this.stat] - target.stats[this.stat] - let result: LogEntry - - if (delta === 0) { - result = new LogLine('You and the target have the same ', new PropElem(this.stat), '.') - } else if (delta < 0) { - result = new LogLine('You have ', new PropElem(this.stat, -delta), ' less than your foe.') - } else { - result = new LogLine('You have ', new PropElem(this.stat, delta), ' more than you foe.') - } - - result = new LogLine(result, 'Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%') - - return result - } } export class ChanceTest extends RandomTest { diff --git a/src/game/creatures/player.ts b/src/game/creatures/player.ts index eef64d1..0267ec0 100644 --- a/src/game/creatures/player.ts +++ b/src/game/creatures/player.ts @@ -1,14 +1,9 @@ import { Creature } from "../creature" import { ProperNoun, TheyPronouns, ImproperNoun, POV } from '../language' -import { Damage, DamageType, Vigor, ConstantDamageFormula, CompositionAction, UniformRandomDamageFormula, StatDamageFormula, Stat } from '../combat' -import { Stomach, Bowels, VoreType, anyVore, Bladder, Cock, Balls, Breasts, InnerBladder, Slit, Womb, biconnectContainers } from '../vore' +import { Damage, DamageType, Vigor, ConstantDamageFormula } from '../combat' +import { Stomach, Bowels, anyVore, Cock, Balls, Breasts, InnerBladder, Slit, Womb, biconnectContainers } 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' import { RavenousPerk, BellyBulwakPerk, FlauntPerk } from '../combat/perks' -import { Flaunt } from '../words' export class Player extends Creature { constructor () { @@ -57,38 +52,5 @@ export class Player extends Creature { this.perks.push(new RavenousPerk()) this.perks.push(new BellyBulwakPerk()) this.perks.push(new FlauntPerk()) - - 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 - ) - ] - } - ) - ) } } diff --git a/src/game/creatures/shingo.ts b/src/game/creatures/shingo.ts index a120a90..d504cec 100644 --- a/src/game/creatures/shingo.ts +++ b/src/game/creatures/shingo.ts @@ -1,11 +1,10 @@ import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, StatDamageFormula, Stat, Action, DamageFormula, Condition, FractionDamageFormula } from '../combat' -import { ImproperNoun, ProperNoun, FemalePronouns, MalePronouns, Verb, PairLineArgs, PairLine, TextLike } from '../language' -import { VoreType, Stomach, Bowels, NormalContainer, InnerContainer, VoreContainer, Container } from '../vore' +import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, StatDamageFormula, Stat, DamageFormula, Condition, FractionDamageFormula } from '../combat' +import { ImproperNoun, ProperNoun, MalePronouns, Verb, PairLineArgs, TextLike } from '../language' +import { VoreType, Stomach, NormalContainer, Container } from '../vore' import { AttackAction, TransferAction, FeedAction, StruggleAction, DamageAction } from '../combat/actions' -import { LogLine, LogEntry, LogLines } from '../interface' +import { LogLine, LogEntry } from '../interface' import { ContainsCondition, CapableCondition, EnemyCondition, TargetDrainedVigorCondition } from '../combat/conditions' -import { StatTest } from '../combat/tests' export class TrappedAction extends DamageAction { constructor (name: TextLike, desc: TextLike, protected verb: Verb, protected damage: DamageFormula, container: Container, conditions: Condition[] = []) { diff --git a/src/game/creatures/withers.ts b/src/game/creatures/withers.ts index e148bd0..622137d 100644 --- a/src/game/creatures/withers.ts +++ b/src/game/creatures/withers.ts @@ -1,5 +1,5 @@ import { Creature } from "../creature" -import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, CombatTest, Stat, DamageFormula, UniformRandomDamageFormula, Action, DamageInstance, StatDamageFormula, VoreStat } from '../combat' +import { Damage, DamageType, ConstantDamageFormula, Vigor, Side, GroupAction, Stat, DamageFormula, UniformRandomDamageFormula, Action, DamageInstance, StatDamageFormula, VoreStat } from '../combat' import { ImproperNoun, ProperNoun, FemalePronouns, RandomWord, Adjective, Verb, PairLine } from '../language' import { LogLine, LogLines, LogEntry, Newline } from '../interface' import { VoreType, Stomach, VoreContainer, NormalContainer, Container } from '../vore' @@ -7,7 +7,6 @@ import { AttackAction, FeedAction, TransferAction } from '../combat/actions' import { TogetherCondition, ContainsCondition, EnemyCondition, AllyCondition, PairCondition, CapableCondition } from '../combat/conditions' import { InstantKillEffect, DamageTypeResistanceEffect } from '../combat/effects' import * as Words from '../words' -import { StatVigorTest } from '../combat/tests' class LevelDrain extends Action { constructor (private container: Container) {