import { TestCategory, CompositionTest, OpposedStatScorer } from '@/game/combat/tests' import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '@/game/language' import { Entity } from '@/game/entity' import { Creature } from "../creature" import { Damage, DamageFormula, Vigor, Action, Condition, CombatTest, CompositionAction } from '@/game/combat' import { LogLine, LogLines, LogEntry } from '@/game/interface' import { Connection, Container } from '@/game/vore' import { CapableCondition, UserDrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition, ContainsCondition, ContainedByCondition, HasRoomCondition } from '@/game/combat/conditions' import { ConsumeConsequence } from '@/game/combat/consequences' /** * The PassAction has no effect. */ export class PassAction extends Action { constructor () { super("Pass", "Do nothing", [new SoloCondition()]) } execute (user: Creature, target: Creature): LogEntry { return new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('pass', 'passes'))}`) } describe (user: Creature, target: Creature): LogEntry { return new LogLine("Do nothing.") } } /** * A generic action that causes damage. */ export abstract class DamageAction extends Action { abstract successLine: PairLineArgs constructor (name: TextLike, desc: TextLike, protected damage: DamageFormula, tests: CombatTest[], conditions: Condition[] = []) { super( name, desc, [new CapableCondition(), new EnemyCondition()].concat(conditions), tests ) } // TODO: remove me or replace the logic for damage prevention execute (user: Creature, target: Creature): LogEntry { const damage = this.damage.calc(user, target) const targetResult = target.takeDamage(damage) const ownResult = this.successLine(user, target, { damage: damage }) return new LogLines(ownResult, targetResult) } } /** * Adds flavor text to [[DamageAction]] */ export class AttackAction extends DamageAction { constructor (damage: DamageFormula, protected verb: Verb = new Verb('smack')) { super( verb.root.capital, 'Attack the enemy', damage, [ 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()] ) } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Attack ${target.baseName}. `, this.damage.describe(user, target), super.describe(user, target)) } successLine: PairLineArgs = (user, target, args) => new LogLine( `${user.name.capital} ${user.name.conjugate(this.verb)} ${target.name.objective} for `, target.effectiveDamage(args.damage).renderShort() ) } /** * Devours the target. */ export class DevourAction extends CompositionAction { constructor (protected container: Container) { super( new DynText(new LiveText(container, x => x.consumeVerb.capital), ' (', new LiveText(container, x => x.name.all), ')'), new LiveText(container, x => `Try to ${x.consumeVerb} your foe`), { conditions: [new CapableCondition(), new TogetherCondition(), new HasRoomCondition(container)], 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) ] } ) } allowed (user: Creature, target: Creature): boolean { const owner = this.container.owner === user const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref)) const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref)) if (owner && predOk && preyOk) { return super.allowed(user, target) } else { return false } } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Try to ${this.container.consumeVerb} your opponent, sending them to your ${this.container.name}. `, super.describe(user, target)) } } /** * Causes the user to be eaten by the target. */ export class FeedAction extends Action { constructor (protected container: Container) { super( 'Feed', 'Feed yourself to your opponent', [new UserDrainedVigorCondition(Vigor.Resolve), new TogetherCondition()] ) this.name += ` (${container.name})` } allowed (user: Creature, target: Creature): boolean { const owner = this.container.owner === target const predOk = Array.from(this.container.voreTypes).every(pref => user.preyPrefs.has(pref)) const preyOk = Array.from(this.container.voreTypes).every(pref => target.predPrefs.has(pref)) if (owner && predOk && preyOk) { return super.allowed(user, target) } else { return false } } execute (user: Creature, target: Creature): LogEntry { return new LogLines(this.successLine(user, target), this.container.consume(user)) } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Your willpower is drained, and you really feel like shoving yourself into ${target.name.possessive} ${this.container.name}...`) } protected successLine: PairLine = (user, target) => new LogLine( `${user.name.capital} ${user.name.conjugate(new Verb('feed'))} ${user.pronouns.reflexive} to ${target.name}. ` ) } /** * Tries to escape from the target's container */ export class StruggleAction extends Action { constructor (public container: Container) { super( new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition(), new PairCondition(), new ContainedByCondition(container)], [ 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 ) ] ) } execute (user: Creature, target: Creature): LogEntry { if (user.containedIn !== null) { return new LogLines(this.successLine(user, target, { container: this.container }), user.containedIn.release(user)) } else { return new LogLine("Vore's bugged!") } } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Try to escape from ${target.baseName.possessive} ${this.container.name}. `, super.describe(user, target)) } protected successLine: PairLineArgs = (prey, pred, args) => new LogLine( `${prey.name.capital} ${prey.name.conjugate(new Verb('escape'))}!` ) } /** * Tries to move between containers */ export class StruggleMoveAction extends Action { constructor (public from: Container, public to: Container) { super( new DynText('Struggle (', new LiveText(from, x => x.name.all), ' to ', new LiveText(to, x => x.name.all), ')'), 'Try to escape from your foe', [new CapableCondition(), new PairCondition(), new ContainedByCondition(from)], [ 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} ${from.name}.`), TestCategory.Vore, -5 ) ] ) } execute (user: Creature, target: Creature): LogEntry { if (user.containedIn !== null) { return new LogLines(this.successLine(user, target, { container: this.from }), user.containedIn.release(user)) } else { return new LogLine("Vore's bugged!") } } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Try to escape from ${target.baseName.possessive} ${this.from.name}. `, super.describe(user, target)) } protected successLine: PairLineArgs = (prey, pred, args) => new LogLine( `${prey.name.capital} ${prey.name.conjugate(new Verb('escape'))}!` ) } export class RubAction extends Action { constructor (protected container: Container) { super( new DynText('Rub (', new LiveText(container, container => container.name.all), ')'), 'Digest your prey more quickly', [new CapableCondition(), new SoloCondition()] ) } allowed (user: Creature, target: Creature) { if (this.container.owner === user && this.container.contents.length > 0) { return super.allowed(user, target) } else { return false } } execute (user: Creature, target: Creature): LogEntry { const results = this.container.tick(60) return new LogLines(results) } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Digest everyone inside of your ${this.container.name}.`) } } export class ReleaseAction extends Action { constructor (protected container: Container) { super( new DynText('Release (', new LiveText(container, x => x.name.all), ')'), 'Release one of your prey', [new CapableCondition(), new PairCondition(), new ContainsCondition(container)] ) } allowed (user: Creature, target: Creature) { if (target.containedIn === this.container && this.container.contents.indexOf(target) >= 0) { return super.allowed(user, target) } else { return false } } execute (user: Creature, target: Creature): LogEntry { return this.container.release(target) } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Release ${target.baseName} from your ${this.container.name}.`) } } export class TransferAction extends Action { verb: Verb = new Verb('send') constructor (public from: Container, public to: Connection, name = 'Transfer') { super( name, `${from.name.all.capital} to ${to.destination.name.all}`, [new CapableCondition(), new PairCondition()] ) } line: PairLineArgs = (user, target, args) => new LogLine( `${user.name.capital} ${user.name.conjugate(this.verb)} ${target.name.objective} from ${user.pronouns.possessive} ${args.from.name} to ${user.pronouns.possessive} ${args.to.destination.name}` ) allowed (user: Creature, target: Creature) { if (target.containedIn === this.from && this.from.contents.includes(target)) { return super.allowed(user, target) } else { return false } } execute (user: Creature, target: Creature): LogEntry { this.from.release(target) this.to.destination.consume(target) return this.line(user, target, { from: this.from, to: this.to }) } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Push ${target.baseName} from your ${this.from.name} to your ${this.to.destination.name}`) } } export class WillingTransferAction extends Action { giveIn: Verb = new Verb('give in', 'gives in', 'gave in', 'giving in') allow: Verb = new Verb('allow') verb: Verb = new Verb('send') constructor (protected from: Container, protected to: Container, name = 'Transfer') { super( name, `${from.name.all.capital} to ${to.name.all}`, [new CapableCondition(), new PairCondition()] ) } line: PairLineArgs = (user, target, args) => new LogLine( `${user.name.capital} ${user.name.conjugate(this.giveIn)} and ${user.name.conjugate(this.allow)} ${target.name.objective} to send ${user.name.objective} from ${target.pronouns.possessive} ${args.from.name} to ${target.pronouns.possessive} ${args.to.name}` ) allowed (user: Creature, target: Creature) { if (user.containedIn === this.from && this.from.contents.includes(user)) { return super.allowed(user, target) } else { return false } } execute (user: Creature, target: Creature): LogEntry { this.from.release(user) this.to.consume(user) return this.line(user, target, { from: this.from, to: this.to }) } describe (user: Creature, target: Creature): LogEntry { return new LogLine(`Allow ${target.baseName} to pull ${user.pronouns.subjective} ${this.to.consumePreposition} ${target.pronouns.possessive} ${this.to.name}`) } }