|
- 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<DamageType, number>;
- 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<Vigors> {
- 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<StatusEffect> = []
-
- 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<DamageType, number> = new Map()
- perspective: POV = POV.Third
- containers: Array<VoreContainer> = []
- otherContainers: Array<Container> = []
- actions: Array<Action> = []
- groupActions: Array<GroupAction> = []
- otherActions: Array<Action> = []
-
- items: Array<Item> = []
-
- 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<VoreType>, public predPrefs: Set<VoreType>, 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<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.effects.forEach(effect => {
- results.push(effect)
- })
- return results
- }
-
- validActions (target: Creature): Array<Action> {
- 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<Creature>): Array<GroupAction> {
- const choices = this.groupActions
-
- return choices.filter(action => {
- return targets.some(target => action.allowed(this, target))
- })
- }
-
- destroy (): LogEntry {
- return super.destroy()
- }
- }
|