import { Creature, POV } from './entity' import { POVActionPicker } from './language' import { Container } from './vore' import { LogEntry, LogLines, CompositeLog } from './interface' export enum DamageType {Pierce, Slash, Crush, 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) } } export enum Stat { STR = 'Strength', DEX = 'Dexterity', CON = 'Constitution' } export type Stats = {[key in Stat]: number} export enum State { Normal = 'Normal', Grappled = 'Grappled', Grappling = 'Grappling', Eaten = 'Eaten' } export interface Combatant { actions: Array; } export abstract class Action { public name: string; allowed (user: Creature, target: Creature) { return this.userStates.has(user.state) && this.targetStates.has(target.state) } abstract execute(user: Creature, target: Creature): LogEntry constructor (name: string, public userStates: Set, public targetStates: Set) { this.name = name } toString (): string { return this.name } } abstract class SelfAction extends Action { allowed (user: Creature, target: Creature) { if (user === target) { return super.allowed(user, target) } else { return false } } } abstract class PairAction extends Action { allowed (user: Creature, target: Creature) { if (user !== target) { return super.allowed(user, target) } else { return false } } } export class AttackAction extends PairAction { protected lines: POVActionPicker = { [POV.First]: { [POV.First]: (user, target) => new LogLines('You bite...yourself?'), [POV.Third]: (user, target) => new LogLines('You bite ' + target.name) }, [POV.Third]: { [POV.First]: (user, target) => new LogLines(user.name.capital + ' bites you'), [POV.Third]: (user, target) => new LogLines(user.name.capital + ' bites ' + target.name) } } constructor (protected damage: Damage) { super('Attack', new Set([State.Normal]), new Set([State.Normal])) } execute (user: Creature, target: Creature): LogEntry { target.takeDamage(this.damage) return this.lines[user.perspective][target.perspective](user, target) } } export class DevourAction extends PairAction { protected lines: POVActionPicker = { [POV.First]: { [POV.First]: (user, target) => new LogLines('You devour...yourself?'), [POV.Third]: (user, target) => new LogLines('You devour ' + target.name) }, [POV.Third]: { [POV.First]: (user, target) => new LogLines(user.name.capital + ' devours you'), [POV.Third]: (user, target) => new LogLines(user.name.capital + ' devours ' + target.name) } } constructor (protected container: Container) { super('Devour', new Set([State.Normal]), new Set([State.Normal])) } execute (user: Creature, target: Creature): LogEntry { target.state = State.Eaten return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), this.container.consume(target)) } } export class StruggleAction extends PairAction { protected lines: POVActionPicker = { [POV.First]: { [POV.First]: (user, target) => new LogLines('You escape from...yourself?'), [POV.Third]: (user, target) => new LogLines('You escape from ' + target.name) }, [POV.Third]: { [POV.First]: (user, target) => new LogLines(user.name.capital + ' escapes from you'), [POV.Third]: (user, target) => new LogLines(user.name.capital + ' escapes from ' + target.name) } } constructor () { super('Struggle', new Set([State.Eaten]), new Set([State.Normal])) } execute (user: Creature, target: Creature): LogEntry { if (user.containedIn) { user.state = State.Normal return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), user.containedIn.release(user)) } else { return new LogLines("The prey wasn't actually eaten...") } } } export class DigestAction extends SelfAction { protected lines: POVActionPicker = { [POV.First]: { [POV.First]: (user, target) => new LogLines('You rub your stomach'), [POV.Third]: (user, target) => new LogLines("You can't digest for other people...") }, [POV.Third]: { [POV.First]: (user, target) => new LogLines("Other people can't digest for you..."), [POV.Third]: (user, target) => new LogLines(user.name.capital + ' rubs ' + user.pronouns.possessive + ' gut.') } } allowed (user: Creature, target: Creature) { if (Array.from(user.containers).some(container => { return container.contents.size > 0 })) { return super.allowed(user, target) } else { return false } } constructor () { super('Digest', new Set([State.Normal]), new Set([State.Normal])) } execute (user: Creature, target: Creature): LogEntry { const results = new CompositeLog(...Array.from(user.containers).map(container => container.tick(60))) return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results) } } 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.') } }