Feast 2.0!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

134 lines
4.5 KiB

  1. import { Damage, Combatant, Stats, Action, Vigor, Side, GroupAction, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective } from './combat'
  2. import { Noun, Pronoun } from './language'
  3. import { LogEntry, LogLines } from './interface'
  4. import { Vore, VoreContainer, VoreType } from './vore'
  5. import { Item, EquipmentSlot, Equipment, ItemKind } from './items'
  6. import { PassAction } from './combat/actions'
  7. import { AI, NoAI } from './ai'
  8. export class Creature extends Vore implements Combatant {
  9. actions: Array<Action> = [];
  10. containedIn: VoreContainer | null = null;
  11. desc = "Some creature";
  12. get effects (): Array<Effective> {
  13. return (this.statusEffects as Effective[]).concat(
  14. Object.values(this.equipment).filter(item => item !== undefined).flatMap(
  15. item => (item as Equipment).effects
  16. )
  17. )
  18. }
  19. statusEffects: Array<StatusEffect> = [];
  20. groupActions: Array<GroupAction> = [];
  21. items: Array<Item> = [];
  22. otherActions: Array<Action> = [];
  23. side: Side;
  24. title = "Lv. 1 Creature";
  25. equipment: {[key in EquipmentSlot]?: Equipment } = {}
  26. ai: AI = new NoAI()
  27. constructor (name: Noun, kind: Noun, pronouns: Pronoun, stats: Stats, preyPrefs: Set<VoreType>, predPrefs: Set<VoreType>, mass: number) {
  28. super(name, kind, pronouns, stats, preyPrefs, predPrefs, mass)
  29. this.actions.push(new PassAction())
  30. this.side = Side.Heroes
  31. }
  32. applyEffect (effect: StatusEffect): LogEntry {
  33. this.effects.push(effect)
  34. return effect.onApply(this)
  35. }
  36. /**
  37. * Determines how much damage an attack would do
  38. */
  39. effectiveDamage (damage: Damage): Damage {
  40. const preDamage = this.effects.reduce((modifiedDamage: Damage, effect: Effective) => {
  41. return effect.preDamage(this, modifiedDamage)
  42. }, damage)
  43. return super.effectiveDamage(preDamage)
  44. }
  45. resistanceTo (damageType: DamageType) {
  46. const base = super.resistanceTo(damageType)
  47. const modified = this.effects.reduce((resist, effect) => effect.modResistance(damageType, resist), base)
  48. return modified
  49. }
  50. executeAction (action: Action, target: Creature): LogEntry {
  51. const preActionResults = this.effects.map(effect => effect.preAction(this))
  52. const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this))
  53. const blocking = preActionResults.concat(preReceiveActionResults).filter(result => result.prevented)
  54. if (blocking.length > 0) {
  55. return new LogLines(...blocking.map(result => result.log))
  56. } else {
  57. return action.execute(this, target)
  58. }
  59. }
  60. removeEffect (effect: StatusEffect): LogEntry {
  61. this.statusEffects = this.statusEffects.filter(eff => eff !== effect)
  62. return effect.onRemove(this)
  63. }
  64. equip (item: Equipment, slot: EquipmentSlot) {
  65. const equipped = this.equipment[slot]
  66. if (equipped !== undefined) {
  67. this.items.push(equipped)
  68. }
  69. this.equipment[slot] = item
  70. }
  71. get status (): Array<VisibleStatus> {
  72. const results: Array<VisibleStatus> = []
  73. if (this.vigors[Vigor.Health] <= 0) {
  74. results.push(new ImplicitStatus('Dead', 'Out of health', 'fas fa-heart'))
  75. }
  76. if (this.vigors[Vigor.Stamina] <= 0) {
  77. results.push(new ImplicitStatus('Unconscious', 'Out of stamina', 'fas fa-bolt'))
  78. }
  79. if (this.vigors[Vigor.Resolve] <= 0) {
  80. results.push(new ImplicitStatus('Broken', 'Out of resolve', 'fas fa-brain'))
  81. }
  82. if (this.containedIn !== null) {
  83. results.push(new ImplicitStatus('Eaten', 'Devoured by ' + this.containedIn.owner.name, 'fas fa-drumstick-bite'))
  84. }
  85. this.statusEffects.forEach(effect => {
  86. results.push(effect)
  87. })
  88. return results
  89. }
  90. validActions (target: Creature): Array<Action> {
  91. let choices = ([] as Action[]).concat(
  92. this.actions,
  93. this.containers.flatMap(container => container.actions),
  94. target.otherActions,
  95. this.otherContainers.flatMap(container => container.actions),
  96. Object.values(this.equipment).filter(item => item !== undefined).flatMap(item => (item as Equipment).actions),
  97. this.items.filter(item => item.kind === ItemKind.Consumable && !item.consumed).flatMap(item => item.actions)
  98. )
  99. if (this.containedIn !== null) {
  100. choices = choices.concat(this.containedIn.actions)
  101. }
  102. return choices.filter(action => {
  103. return action.allowed(this, target)
  104. })
  105. }
  106. validGroupActions (targets: Array<Creature>): Array<GroupAction> {
  107. const choices = this.groupActions
  108. return choices.filter(action => {
  109. return targets.some(target => action.allowed(this, target))
  110. })
  111. }
  112. }