|
- import { Damage, Stats, Action, Vigor, Side, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective, VoreStat, VoreStats, DamageInstance, Stat, Vigors, Encounter } from '@/game/combat'
- import { Noun, Pronoun, SoloLine, Verb } from '@/game/language'
- import { LogEntry, LogLines, LogLine } from '@/game/interface'
- import { VoreType, Container } from '@/game/vore'
- import { Item, EquipmentSlot, Equipment, ItemKind, Currency } from '@/game/items'
- import { PassAction } from '@/game/combat/actions'
- import { AI, RandomAI } from '@/game/ai'
- import { Entity, Resistances } from '@/game/entity'
- import { Perk } from '@/game/combat/perks'
- import { VoreRelay } from '@/game/events'
-
- export class Creature extends Entity {
- voreRelay: VoreRelay = new VoreRelay()
-
- baseResistances: Resistances
-
- stats: Stats = (Object.keys(Stat) as Array<Stat>).reduce((result: Partial<Stats>, stat: Stat) => {
- Object.defineProperty(result, stat, {
- get: () => this.effects.reduce((total, effect) => effect.modStat(this, stat, total), this.baseStats[stat]),
- set: (value: number) => { this.baseStats[stat] = value },
- enumerable: true
- })
- return result
- }, {}) as Stats
-
- vigors: {[key in Vigor]: number} = {
- [Vigor.Health]: 100,
- [Vigor.Stamina]: 100,
- [Vigor.Resolve]: 100
- }
-
- destroyed = false;
-
- containers: Array<Container> = []
-
- containedIn: Container | null = null
-
- voreStats: VoreStats
-
- actions: Array<Action> = [];
- desc = "Some creature";
-
- get effects (): Array<Effective> {
- const effects: Array<Effective> = (this.statusEffects as Effective[]).concat(
- Object.values(this.equipment).filter(item => item !== undefined).flatMap(
- item => (item as Equipment).effects
- ),
- this.perks
- )
-
- return effects
- }
-
- statusEffects: Array<StatusEffect> = [];
- perks: Array<Perk> = [];
-
- items: Array<Item> = [];
- /* eslint-disable-next-line */
- wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {});
- otherActions: Array<Action> = [];
- side: Side;
- title = "Lv. 1 Creature";
- equipment: {[key in EquipmentSlot]?: Equipment } = {}
- ai: AI
-
- constructor (name: Noun, kind: Noun, pronouns: Pronoun, public baseStats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, private baseMass: number) {
- super(name, kind, pronouns)
-
- /* eslint-disable-next-line */
- this.baseResistances = Object.keys(DamageType).reduce((resist: any, key) => { resist[key] = 1; return resist }, {})
- Object.entries(this.maxVigors).forEach(([key, val]) => {
- this.vigors[key as Vigor] = val
- })
-
- this.actions.push(new PassAction())
- this.side = Side.Heroes
- /* eslint-disable-next-line */
- const self = this
-
- this.ai = new RandomAI(this)
-
- this.voreStats = {
- get [VoreStat.Bulk] () {
- return self.containers.reduce(
- (total: number, container: Container) => {
- 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.Prey] () {
- return self.containers.reduce(
- (total: number, container: Container) => {
- return total + container.contents.concat(container.digested).reduce(
- (total: number, prey: Creature) => {
- return total + 1 + prey.voreStats[VoreStat.Prey]
- },
- 0
- )
- },
- 0
- )
- }
- }
- }
-
- resistanceTo (damageType: DamageType): number {
- return this.baseResistances[damageType]
- }
-
- get maxVigors (): Readonly<Vigors> {
- return {
- Health: this.stats.Toughness * 10 + this.stats.Power * 5,
- Resolve: this.stats.Willpower * 10 + this.stats.Charm * 5,
- Stamina: this.stats.Agility * 5 + this.stats.Reflexes * 5
- }
- }
-
- get disabled (): boolean {
- return Object.values(this.vigors).some(val => val <= 0)
- }
-
- effectiveDamage (damage: Damage): Damage {
- const newDamages: DamageInstance[] = []
- damage.damages.forEach(instance => {
- const factor = instance.type === DamageType.Heal ? -1 : 1
- const baseResistance: number = this.resistanceTo(instance.type)
- const resistance = baseResistance * factor
- newDamages.push({
- amount: instance.amount * resistance,
- target: instance.target,
- type: instance.type
- })
- })
-
- return new Damage(...newDamages)
- }
-
- 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 => {
- if (instance.target in Vigor) {
- // just deal damage
- this.vigors[instance.target as Vigor] -= instance.amount
- } 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
- const endVigors = this.maxVigors
-
- Object.keys(Vigor).map(vigor => {
- this.vigors[vigor as Vigor] -= startVigors[vigor as Vigor] - endVigors[vigor as Vigor]
- })
- }
- })
-
- Object.keys(Vigor).forEach(vigorStr => {
- const vigor = vigorStr as Vigor
- if (this.vigors[vigor] > this.maxVigors[vigor]) {
- this.vigors[vigor] = this.maxVigors[vigor]
- }
- })
-
- if (this.vigors.Health <= -this.maxVigors.Health) {
- this.destroyed = true
- }
-
- if (this.vigors.Health <= 0 && startHealth > 0) {
- return this.destroy()
- } else {
- return new LogLine()
- }
- }
-
- toString (): string {
- return this.name.toString()
- }
-
- applyEffect (effect: StatusEffect): LogEntry {
- this.statusEffects.push(effect)
- return effect.onApply(this)
- }
-
- addContainer (container: Container): void {
- this.containers.push(container)
- this.voreRelay.connect(container.voreRelay)
- }
-
- addPerk (perk: Perk): void {
- this.perks.push(perk)
- }
-
- // TODO replace the logic for getting blocked or prevented from acting
-
- executeAction (action: Action, targets: Array<Creature>): LogEntry {
- return action.try(this, targets)
- }
-
- 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<VisibleStatus> {
- const results: Array<VisibleStatus> = []
-
- 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<Action> {
- let choices = ([] as Action[]).concat(
- this.actions,
- this.containers.flatMap(container => container.actions),
- target.otherActions,
- 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),
- this.perks.flatMap(perk => perk.actions(this))
- )
-
- if (this.containedIn !== null) {
- choices = choices.concat(this.containedIn.actions)
- }
-
- return choices
- }
-
- validActions (target: Creature): Array<Action> {
- return this.allActions(target).filter(action => {
- return action.allowed(this, target)
- })
- }
-
- validSoloActions (target: Creature, encounter: Encounter): Array<Action> {
- return this.validActions(target).filter(action => action.targets(target, encounter).length === 1)
- }
-
- validGroupActions (target: Creature, encounter: Encounter): Array<Action> {
- return this.validActions(target).filter(action => action.targets(target, encounter).length > 1)
- }
-
- destroyLine: SoloLine<Creature> = (victim) => new LogLine(
- `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}`
- )
-
- destroy (): LogEntry {
- const released: Array<Creature> = 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<string>, prey: Creature) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("")
-
- if (released.length > 0) {
- if (this.containedIn === null) {
- return new LogLines(
- this.destroyLine(this),
- new LogLine(names + ` spill out!`)
- )
- } else {
- return new LogLines(
- this.destroyLine(this),
- new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`)
- )
- }
- } else {
- return this.destroyLine(this)
- }
- }
- }
|