import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat, Side, GroupAction, Vigors, VisibleStatus, ImplicitStatus, StatusEffect } from './combat' import { Noun, Pronoun, TextLike, POV } from './language' import { LogEntry, LogLine, LogLines } from './interface' import { Vore, VoreContainer, VoreType, Container } from './vore' import { Item } from './items' import { PassAction } from './combat/actions' export interface Entity { name: Noun; pronouns: Pronoun; baseName: Noun; basePronouns: Pronoun; title: TextLike; desc: TextLike; perspective: POV; } export interface Mortal extends Entity { kind: Noun; vigors: {[key in Vigor]: number}; maxVigors: Readonly<{[key in Vigor]: number}>; disabled: boolean; resistances: Map; takeDamage: (damage: Damage) => void; stats: Stats; baseStats: Stats; status: VisibleStatus[]; destroy: () => LogEntry; } export class Creature extends Vore implements Combatant { title = "Lv. 1 Creature" desc = "Some creature" vigors = { [Vigor.Health]: 100, [Vigor.Stamina]: 100, [Vigor.Resolve]: 100 } get maxVigors (): Readonly { return { Health: this.stats.Toughness * 10 + this.stats.Power * 5, Resolve: this.stats.Willpower * 10 + this.stats.Charm * 5, Stamina: this.stats.Speed * 10 + this.stats.Power * 2.5 + this.stats.Charm * 2.5 } } baseStats: Stats voreStats: VoreStats side: Side effects: Array = [] applyEffect (effect: StatusEffect): LogEntry { this.effects.push(effect) return effect.onApply(this) } removeEffect (effect: StatusEffect): LogEntry { this.effects = this.effects.filter(eff => eff !== effect) return effect.onRemove(this) } executeAction (action: Action, target: Creature): LogEntry { const effectResults = this.effects.map(effect => effect.preAction(this)) const blocking = effectResults.filter(result => result.prevented) if (blocking.length > 0) { return new LogLines(...blocking.map(result => result.log)) } else { return action.execute(this, target) } } get disabled (): boolean { return Object.values(this.vigors).some(val => val <= 0) } resistances: Map = new Map() perspective: POV = POV.Third containers: Array = [] otherContainers: Array = [] actions: Array = [] groupActions: Array = [] otherActions: Array = [] items: Array = [] get bulk (): number { return this.voreStats.Mass + this.containers.reduce((total, conatiner) => { return total + conatiner.contents.reduce((total, prey) => total + prey.voreStats.Bulk, 0) }, 0) } containedIn: VoreContainer|null = null; constructor (public baseName: Noun, public kind: Noun, public basePronouns: Pronoun, public stats: Stats, public preyPrefs: Set, public predPrefs: Set, mass: number) { super() const containers = this.containers this.actions.push(new PassAction()) Object.entries(this.maxVigors).forEach(([key, val]) => { this.vigors[key as Vigor] = val }) this.baseStats = Object.keys(Stat).reduce((base: any, key) => { base[key] = stats[key as Stat]; return base }, {}) this.side = Side.Heroes this.voreStats = { get [VoreStat.Bulk] () { return containers.reduce( (total: number, container: VoreContainer) => { return total + container.contents.reduce( (total: number, prey: Vore) => { return total + prey.voreStats.Bulk }, 0 ) + container.digested.reduce( (total: number, prey: Vore) => { return total + prey.voreStats.Bulk }, 0 ) }, this.Mass ) }, [VoreStat.Mass]: mass, get [VoreStat.PreyCount] () { return containers.reduce( (total: number, container: VoreContainer) => { return total + container.contents.reduce( (total: number, prey: Vore) => { return total + 1 + prey.voreStats[VoreStat.PreyCount] }, 0 ) }, 0 ) } } } toString (): string { return this.name.toString() } /** * Determines how much damage an attack would do */ effectiveDamage (damage: Damage): Damage { return this.effects.reduce((modifiedDamage: Damage, effect: StatusEffect) => { return effect.preDamage(this, modifiedDamage) }, damage) } takeDamage (damage: Damage): LogEntry { // first, we record health to decide if the entity just died const startHealth = this.vigors.Health damage = this.effectiveDamage(damage) damage.damages.forEach(instance => { const factor = instance.type === DamageType.Heal ? -1 : 1 const effectiveResistance: number|undefined = this.resistances.get(instance.type) const resistance = effectiveResistance === undefined ? factor : effectiveResistance * factor if (instance.target in Vigor) { // just deal damage this.vigors[instance.target as Vigor] -= instance.amount * resistance } else if (instance.target in Stat) { // drain the stats, then deal damage to match const startVigors = this.maxVigors this.stats[instance.target as Stat] -= instance.amount * resistance const endVigors = this.maxVigors Object.keys(Vigor).map(vigor => { this.vigors[vigor as Vigor] -= startVigors[vigor as Vigor] - endVigors[vigor as Vigor] }) } }) if (this.vigors.Health <= 0 && startHealth > 0) { return this.destroy() } else { return new LogLine() } } get status (): Array { const results: Array = [] if (this.vigors[Vigor.Health] <= 0) { results.push(new ImplicitStatus('Dead', 'Out of health', 'fas fa-heart')) } if (this.vigors[Vigor.Stamina] <= 0) { results.push(new ImplicitStatus('Unconscious', 'Out of stamina', 'fas fa-bolt')) } if (this.vigors[Vigor.Resolve] <= 0) { results.push(new ImplicitStatus('Broken', 'Out of resolve', 'fas fa-brain')) } if (this.containedIn !== null) { results.push(new ImplicitStatus('Eaten', 'Devoured by ' + this.containedIn.owner.name, 'fas fa-drumstick-bite')) } this.effects.forEach(effect => { results.push(effect) }) return results } validActions (target: Creature): Array { let choices = this.actions.concat( this.containers.flatMap(container => container.actions) ).concat( target.otherActions.concat( this.otherContainers.flatMap(container => container.actions).concat( this.items.flatMap(item => item.actions) ) ) ) if (this.containedIn !== null) { choices = choices.concat(this.containedIn.actions) } return choices.filter(action => { return action.allowed(this, target) }) } validGroupActions (targets: Array): Array { const choices = this.groupActions return choices.filter(action => { return targets.some(target => action.allowed(this, target)) }) } destroy (): LogEntry { return super.destroy() } }