import { Creature, POV, Entity } from './entity' import { POVPair, POVPairArgs } from './language' import { Container } from './vore' import { LogEntry, LogLines, CompositeLog } from './interface' export interface CombatTest { test: (user: Creature, target: Creature) => boolean; odds: (user: Creature, target: Creature) => number; explain: (user: Creature, target: Creature) => LogEntry; } function logistic (x0: number, L: number, k: number): (x: number) => number { return (x: number) => { return L / (1 + Math.exp(-k * (x - x0))) } } abstract class RandomTest implements CombatTest { test (user: Creature, target: Creature): boolean { return Math.random() < this.odds(user, target) } abstract odds(user: Creature, target: Creature): number abstract explain(user: Creature, target: Creature): LogEntry } export class StatTest extends RandomTest { private f: (x: number) => number constructor (public readonly stat: Stat, k = 0.1) { super() this.f = logistic(0, 1, k) } odds (user: Creature, target: Creature): number { return this.f(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: string if (delta === 0) { result = 'You and the target have the same ' + this.stat + '.' } else if (delta < 0) { result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.' } else { result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.' } result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%' return new LogLines(result) } } export class ChanceTest extends RandomTest { constructor (public readonly chance: number) { super() } odds (user: Creature, target: Creature): number { return this.chance } explain (user: Creature, target: Creature): LogEntry { return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.') } } export enum DamageType { Pierce = "Pierce", Slash = "Slash", Crush = "Crush", Acid = "Acid"} export interface DamageInstance { type: DamageType; amount: number; } export class Damage { readonly damages: DamageInstance[] constructor (...damages: DamageInstance[]) { this.damages = damages } scale (factor: number): Damage { const results: Array = [] this.damages.forEach(damage => { results.push({ type: damage.type, amount: damage.amount * factor }) }) return new Damage(...results) } toString (): string { return this.damages.map(damage => damage.amount + " " + damage.type).join("/") } } export enum Stat { STR = 'Strength', DEX = 'Dexterity', CON = 'Constitution' } export type Stats = {[key in Stat]: number} export interface Combatant { actions: Array; } export abstract class Action { abstract allowed (user: Creature, target: Creature): boolean abstract execute(user: Creature, target: Creature): LogEntry constructor (public name: string, public desc: string) { } toString (): string { return this.name } } export interface Actionable { actions: Array; } abstract class SelfAction extends Action { allowed (user: Creature, target: Creature) { return user === target } } abstract class PairAction extends Action { allowed (user: Creature, target: Creature) { return user !== target } } abstract class TogetherAction extends PairAction { allowed (user: Creature, target: Creature) { if (user.containedIn === target.containedIn) { return super.allowed(user, target) } else { return false } } } export class AttackAction extends TogetherAction { private test: StatTest protected successLines: POVPairArgs = new POVPairArgs([ [[POV.First, POV.Third], (user, target, args) => new LogLines(`You smack ${target.name} for ${args.damage} damage`)], [[POV.Third, POV.First], (user, target, args) => new LogLines(`${user.name.capital} hits you for ${args.damage} damage`)], [[POV.Third, POV.Third], (user, target, args) => new LogLines(`${user.name.capital} hits ${target.name.capital} for ${args.damage} damage`)] ]) protected failLines: POVPair = new POVPair([ [[POV.First, POV.Third], (user, target) => new LogLines(`You try to smack ${target.name}, but you miss`)], [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)], [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} misses ${target.name}`)] ]) constructor (protected damage: Damage) { super('Attack', 'Attack the enemy') this.test = new StatTest(Stat.STR) } execute (user: Creature, target: Creature): LogEntry { if (this.test.test(user, target)) { target.takeDamage(this.damage) return this.successLines.run(user, target, { damage: this.damage }) } else { return this.failLines.run(user, target) } } } export class DevourAction extends TogetherAction { private test: StatTest protected failLines: POVPair = new POVPair([ [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to make a meal out of ${target.name}`)], [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to devour you, but fails`)], [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to swallow ${target.name}`)] ]) 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 } } constructor (protected container: Container) { super('Devour', 'Try to consume your foe') this.name += ` (${container.name})` this.test = new StatTest(Stat.STR) } execute (user: Creature, target: Creature): LogEntry { if (this.test.test(user, target)) { return this.container.consume(target) } else { return this.failLines.run(user, target) } } } export class StruggleAction extends PairAction { private test: StatTest protected failLines: POVPair = new POVPair([ [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to escape from ${target.name}`)], [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to escape from you, but fails`)], [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully struggles within ${target.name}`)] ]) allowed (user: Creature, target: Creature) { if (user.containedIn === this.container) { return super.allowed(user, target) } else { return false } } constructor (public container: Container) { super('Struggle', 'Try to escape your predator') this.test = new StatTest(Stat.STR) } execute (user: Creature, target: Creature): LogEntry { if (user.containedIn !== null) { if (this.test.test(user, target)) { return user.containedIn.release(user) } else { return this.failLines.run(user, target) } } else { return new LogLines("Vore's bugged!") } } } export class DigestAction extends SelfAction { protected lines: POVPair = new POVPair([]) allowed (user: Creature, target: Creature) { if (this.container.owner === user && this.container.contents.length > 0) { return super.allowed(user, target) } else { return false } } constructor (protected container: Container) { super('Digest', 'Digest all of your current prey') this.name += ` (${container.name})` } execute (user: Creature, target: Creature): LogEntry { const results = new CompositeLog(...user.containers.map(container => container.tick(60))) return new CompositeLog(this.lines.run(user, target), results) } } export class ReleaseAction extends PairAction { allowed (user: Creature, target: Creature) { if (target.containedIn === this.container) { return super.allowed(user, target) } else { return false } } constructor (protected container: Container) { super('Release', 'Release one of your prey') this.name += ` (${container.name})` } execute (user: Creature, target: Creature): LogEntry { return this.container.release(target) } } export class TransferAction extends PairAction { protected lines: POVPair = new POVPair([]) allowed (user: Creature, target: Creature) { if (target.containedIn === this.from) { return super.allowed(user, target) } else { return false } } constructor (protected from: Container, protected to: Container) { super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`) } execute (user: Creature, target: Creature): LogEntry { this.from.release(target) this.to.consume(target) return this.lines.run(user, target) } }