import { Damage, Combatant, Stats, Action, Vigor, Side, GroupAction, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective, VoreStat, VoreStats } from './combat' import { Noun, Pronoun, SoloLine, Verb } from './language' import { LogEntry, LogLines, LogLine } from './interface' import { VoreContainer, VoreType, Container } from './vore' import { Item, EquipmentSlot, Equipment, ItemKind, Currency } from './items' import { PassAction } from './combat/actions' import { AI } from './ai' import { Mortal } from './entity' import { Perk } from './combat/perks' export class Creature extends Mortal { containers: Array = [] otherContainers: Array = [] containedIn: Container | null = null voreStats: VoreStats actions: Array = []; desc = "Some creature"; get effects (): Array { return (this.statusEffects as Effective[]).concat( Object.values(this.equipment).filter(item => item !== undefined).flatMap( item => (item as Equipment).effects ), this.perks ) } statusEffects: Array = []; perks: Array = []; groupActions: Array = []; items: Array = []; /* eslint-disable-next-line */ wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {}); otherActions: Array = []; side: Side; title = "Lv. 1 Creature"; equipment: {[key in EquipmentSlot]?: Equipment } = {} ai: AI|null = null constructor (name: Noun, kind: Noun, pronouns: Pronoun, stats: Stats, public preyPrefs: Set, public predPrefs: Set, private baseMass: number) { super(name, kind, pronouns, stats) this.actions.push(new PassAction()) this.side = Side.Heroes /* eslint-disable-next-line */ const self = this this.voreStats = { get [VoreStat.Bulk] () { return self.containers.reduce( (total: number, container: VoreContainer) => { return total + container.contents.reduce( (total: number, prey: Creature) => { return total + prey.voreStats.Bulk }, 0 ) + container.digested.reduce( (total: number, prey: Creature) => { return total + prey.voreStats.Bulk }, 0 ) }, self.voreStats.Mass ) }, get [VoreStat.Mass] () { const base = self.baseMass const adjusted = self.effects.reduce((scale: number, effect: Effective) => effect.scale(scale), base) return adjusted }, // we want to account for anything changing our current size; // we will assume that the modifiers are all multiplicative set [VoreStat.Mass] (mass: number) { const modifier = self.effects.reduce((scale: number, effect: Effective) => effect.scale(scale), 1) const adjusted = mass / modifier self.baseMass = adjusted }, get [VoreStat.PreyCount] () { return self.containers.reduce( (total: number, container: VoreContainer) => { return total + container.contents.concat(container.digested).reduce( (total: number, prey: Creature) => { return total + 1 + prey.voreStats[VoreStat.PreyCount] }, 0 ) }, 0 ) } } } applyEffect (effect: StatusEffect): LogEntry { this.statusEffects.push(effect) return effect.onApply(this) } /** * Determines how much damage an attack would do */ effectiveDamage (damage: Damage): Damage { const preDamage = this.effects.reduce((modifiedDamage: Damage, effect: Effective) => { return effect.preDamage(this, modifiedDamage) }, damage) return super.effectiveDamage(preDamage) } resistanceTo (damageType: DamageType) { const base = super.resistanceTo(damageType) const modified = this.effects.reduce((resist, effect) => effect.modResistance(damageType, resist), base) return modified } executeAction (action: Action, target: Creature): LogEntry { const preActionResults = this.effects.map(effect => effect.preAction(this)) const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this)) const blocking = preActionResults.concat(preReceiveActionResults).filter(result => result.prevented) if (blocking.length > 0) { return new LogLines(...blocking.map(result => result.log)) } else { return action.try(this, target) } } removeEffect (effect: StatusEffect): LogEntry { this.statusEffects = this.statusEffects.filter(eff => eff !== effect) return effect.onRemove(this) } equip (item: Equipment, slot: EquipmentSlot) { const equipped = this.equipment[slot] if (equipped !== undefined) { this.unequip(slot) } this.equipment[slot] = item } unequip (slot: EquipmentSlot) { const item = this.equipment[slot] if (item !== undefined) { this.items.push(item) this.equipment[slot] = undefined } } 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.statusEffects.forEach(effect => { results.push(effect) }) return results } allActions (target: Creature): Array { let choices = ([] as Action[]).concat( this.actions, this.containers.flatMap(container => container.actions), target.otherActions, this.otherContainers.flatMap(container => container.actions), Object.values(this.equipment).filter(item => item !== undefined).flatMap(item => (item as Equipment).actions), this.items.filter(item => item.kind === ItemKind.Consumable && !item.consumed).flatMap(item => item.actions) ) if (this.containedIn !== null) { choices = choices.concat(this.containedIn.actions) } return choices } validActions (target: Creature): Array { return this.allActions(target).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 { const line: SoloLine = (victim) => new LogLine( `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}` ) const released: Array = this.containers.flatMap(container => { return container.contents.map(prey => { prey.containedIn = this.containedIn if (this.containedIn !== null) { this.containedIn.contents.push(prey) } return prey }) }) const names = released.reduce((list: Array, prey: Creature) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("") if (released.length > 0) { if (this.containedIn === null) { return new LogLines( line(this), new LogLine(names + ` spill out!`) ) } else { return new LogLines( line(this), new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`) ) } } else { return line(this) } } }