diff --git a/src/game/combat/actions.ts b/src/game/combat/actions.ts index 1978e6a..9ca22d4 100644 --- a/src/game/combat/actions.ts +++ b/src/game/combat/actions.ts @@ -1,4 +1,4 @@ -import { StatTest, StatVigorTest, StatVigorSizeTest } from './tests' +import { StatTest, StatVigorTest, StatVigorSizeTest, OpposedStatTest, TestCategory } from './tests' import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '../language' import { Entity } from '../entity' import { Creature } from "../creature" @@ -98,10 +98,12 @@ 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 StatVigorSizeTest( - Stat.Power, - -5, - (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to ${container.consumeVerb} ${target.name.objective}.`) + 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 )], consequences: [ new ConsumeConsequence(container) @@ -178,11 +180,15 @@ export class StruggleAction extends Action { new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition(), new PairCondition(), new ContainedByCondition(container)], - [new StatVigorSizeTest( - Stat.Power, - -5, - (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to escape from ${target.name.possessive} ${container.name}.`) - )] + [ + new OpposedStatTest( + { 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/perks.ts b/src/game/combat/perks.ts index ed820da..d8b494e 100644 --- a/src/game/combat/perks.ts +++ b/src/game/combat/perks.ts @@ -55,3 +55,20 @@ export class RavenousPerk extends Perk { } } } + +export class DeliciousPerk extends Perk { + constructor () { + super( + "Delicious", + "-20 to all defensive vore checks" + ) + } + + modTestDefense (defender: Creature, attacker: Creature, kind: TestCategory): number { + if (kind === TestCategory.Vore) { + return -20 + } else { + return 0 + } + } +} diff --git a/src/game/combat/tests.ts b/src/game/combat/tests.ts index 8dd94df..d76e470 100644 --- a/src/game/combat/tests.ts +++ b/src/game/combat/tests.ts @@ -1,4 +1,4 @@ -import { CombatTest, Stat, Vigor, Stats, StatToVigor } from '../combat' +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' @@ -49,8 +49,8 @@ export class OpposedStatTest extends RandomTest { private maxTotalVigorPenalty = 0.1 constructor ( - public readonly userStats: Partial, - public readonly targetStats: Partial, + public readonly userStats: Partial, + public readonly targetStats: Partial, fail: (user: Creature, target: Creature) => LogEntry, public category: TestCategory, private bias = 0 @@ -60,8 +60,8 @@ export class OpposedStatTest extends RandomTest { } odds (user: Creature, target: Creature): number { - const userScore = this.getScore(user, this.userStats) + user.effects.reduce((total, effect) => total + effect.modTestOffense(user, target, this.category), 0) - const targetScore = this.getScore(target, this.targetStats) + target.effects.reduce((total, effect) => total + effect.modTestDefense(target, user, this.category), 0) + const userScore = this.getScoreOffense(user, target, this.userStats) + const targetScore = this.getScoreDefense(target, user, this.targetStats) return this.f(userScore - targetScore + this.bias) } @@ -72,23 +72,22 @@ export class OpposedStatTest extends RandomTest { `Pits `, ...Object.entries(this.userStats).map(([stat, frac]) => { if (frac !== undefined) { - return new LogLine(`${(frac * 100).toFixed(0)}% `, new PropElem(stat as Stat)) + return new LogLine(`${(frac * 100).toFixed(0)}% of `, new PropElem(stat as Stat), `, `) } else { return nilLog } }), - ` from ${user.name.possessive} stats against `, + ` against `, ...Object.entries(this.targetStats).map(([stat, frac]) => { if (frac !== undefined) { - return new LogLine(`${(frac * 100).toFixed(0)}% `, new PropElem(stat as Stat)) + return new LogLine(`${(frac * 100).toFixed(0)}% of `, new PropElem(stat as Stat), `, `) } else { return nilLog } - }), - ` from ${target.name.possessive} stats.` + }) ), new LogLine( - `${user.name.capital}: ${this.getScore(user, this.userStats)} // ${this.getScore(target, this.targetStats)} :${target.name.capital}` + `${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.` @@ -96,22 +95,39 @@ export class OpposedStatTest extends RandomTest { ) } + 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]) => { - let value = actor.stats[stat as Stat] * (frac === undefined ? 0 : 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] - 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 + return total + value + } else { + return total + } }, 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] + return total * (1 - this.maxStatVigorPenalty) + total * this.maxStatVigorPenalty * actor.vigors[vigor as Vigor] / actor.maxVigors[vigor as Vigor] }, total ) + return modifiedTotal } } diff --git a/src/game/maps/town.ts b/src/game/maps/town.ts index ef8d739..c3a1437 100644 --- a/src/game/maps/town.ts +++ b/src/game/maps/town.ts @@ -9,6 +9,7 @@ import { DevourAction } from '../combat/actions' import { SurrenderEffect } from '../combat/effects' import moment from 'moment' import { VoreAI } from '../ai' +import { DeliciousPerk } from '../combat/perks' function makeParty (): Creature[] { const fighter = new Creatures.Human(new ProperNoun("Redgar"), MalePronouns, { @@ -265,9 +266,10 @@ export const Town = (): Place => { enemy.side = Side.Monsters enemy.ai = new VoreAI() enemy.equip(new Items.Sword(), Items.EquipmentSlot.MainHand) + enemy.perks.push(new DeliciousPerk()) const encounter = new Encounter( { - name: "Fight some nerd", + name: "Fight some tasty nerd", intro: world => new LogLine(`You find some nerd to fight.`) }, [world.player, enemy]