Feast 2.0!
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 

241 строка
7.8 KiB

  1. import { Damage, Combatant, Stats, Action, Vigor, Side, GroupAction, VisibleStatus, ImplicitStatus, StatusEffect, DamageType, Effective, VoreStat, VoreStats } from './combat'
  2. import { Noun, Pronoun, SoloLine, Verb } from './language'
  3. import { LogEntry, LogLines, LogLine } from './interface'
  4. import { VoreContainer, VoreType, Container } from './vore'
  5. import { Item, EquipmentSlot, Equipment, ItemKind, Currency } from './items'
  6. import { PassAction } from './combat/actions'
  7. import { AI } from './ai'
  8. import { Mortal } from './entity'
  9. import { Perk } from './combat/perks'
  10. export class Creature extends Mortal {
  11. containers: Array<VoreContainer> = []
  12. otherContainers: Array<Container> = []
  13. containedIn: Container | null = null
  14. voreStats: VoreStats
  15. actions: Array<Action> = [];
  16. desc = "Some creature";
  17. get effects (): Array<Effective> {
  18. return (this.statusEffects as Effective[]).concat(
  19. Object.values(this.equipment).filter(item => item !== undefined).flatMap(
  20. item => (item as Equipment).effects
  21. ),
  22. this.perks
  23. )
  24. }
  25. statusEffects: Array<StatusEffect> = [];
  26. perks: Array<Perk> = [];
  27. groupActions: Array<GroupAction> = [];
  28. items: Array<Item> = [];
  29. /* eslint-disable-next-line */
  30. wallet: { [key in Currency]: number } = Object.keys(Currency).reduce((total: any, key) => { total[key] = 0; return total }, {});
  31. otherActions: Array<Action> = [];
  32. side: Side;
  33. title = "Lv. 1 Creature";
  34. equipment: {[key in EquipmentSlot]?: Equipment } = {}
  35. ai: AI|null = null
  36. constructor (name: Noun, kind: Noun, pronouns: Pronoun, stats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, private baseMass: number) {
  37. super(name, kind, pronouns, stats)
  38. this.actions.push(new PassAction())
  39. this.side = Side.Heroes
  40. /* eslint-disable-next-line */
  41. const self = this
  42. this.voreStats = {
  43. get [VoreStat.Bulk] () {
  44. return self.containers.reduce(
  45. (total: number, container: VoreContainer) => {
  46. return total + container.contents.reduce(
  47. (total: number, prey: Creature) => {
  48. return total + prey.voreStats.Bulk
  49. },
  50. 0
  51. ) + container.digested.reduce(
  52. (total: number, prey: Creature) => {
  53. return total + prey.voreStats.Bulk
  54. },
  55. 0
  56. )
  57. },
  58. self.voreStats.Mass
  59. )
  60. },
  61. get [VoreStat.Mass] () {
  62. const base = self.baseMass
  63. const adjusted = self.effects.reduce((scale: number, effect: Effective) => effect.scale(scale), base)
  64. return adjusted
  65. },
  66. // we want to account for anything changing our current size;
  67. // we will assume that the modifiers are all multiplicative
  68. set [VoreStat.Mass] (mass: number) {
  69. const modifier = self.effects.reduce((scale: number, effect: Effective) => effect.scale(scale), 1)
  70. const adjusted = mass / modifier
  71. self.baseMass = adjusted
  72. },
  73. get [VoreStat.PreyCount] () {
  74. return self.containers.reduce(
  75. (total: number, container: VoreContainer) => {
  76. return total + container.contents.concat(container.digested).reduce(
  77. (total: number, prey: Creature) => {
  78. return total + 1 + prey.voreStats[VoreStat.PreyCount]
  79. },
  80. 0
  81. )
  82. },
  83. 0
  84. )
  85. }
  86. }
  87. }
  88. applyEffect (effect: StatusEffect): LogEntry {
  89. this.statusEffects.push(effect)
  90. return effect.onApply(this)
  91. }
  92. /**
  93. * Determines how much damage an attack would do
  94. */
  95. effectiveDamage (damage: Damage): Damage {
  96. const preDamage = this.effects.reduce((modifiedDamage: Damage, effect: Effective) => {
  97. return effect.preDamage(this, modifiedDamage)
  98. }, damage)
  99. return super.effectiveDamage(preDamage)
  100. }
  101. resistanceTo (damageType: DamageType) {
  102. const base = super.resistanceTo(damageType)
  103. const modified = this.effects.reduce((resist, effect) => effect.modResistance(damageType, resist), base)
  104. return modified
  105. }
  106. executeAction (action: Action, target: Creature): LogEntry {
  107. const preActionResults = this.effects.map(effect => effect.preAction(this))
  108. const preReceiveActionResults = target.effects.map(effect => effect.preReceiveAction(target, this))
  109. const blocking = preActionResults.concat(preReceiveActionResults).filter(result => result.prevented)
  110. if (blocking.length > 0) {
  111. return new LogLines(...blocking.map(result => result.log))
  112. } else {
  113. return action.try(this, target)
  114. }
  115. }
  116. removeEffect (effect: StatusEffect): LogEntry {
  117. this.statusEffects = this.statusEffects.filter(eff => eff !== effect)
  118. return effect.onRemove(this)
  119. }
  120. equip (item: Equipment, slot: EquipmentSlot) {
  121. const equipped = this.equipment[slot]
  122. if (equipped !== undefined) {
  123. this.unequip(slot)
  124. }
  125. this.equipment[slot] = item
  126. }
  127. unequip (slot: EquipmentSlot) {
  128. const item = this.equipment[slot]
  129. if (item !== undefined) {
  130. this.items.push(item)
  131. this.equipment[slot] = undefined
  132. }
  133. }
  134. get status (): Array<VisibleStatus> {
  135. const results: Array<VisibleStatus> = []
  136. if (this.vigors[Vigor.Health] <= 0) {
  137. results.push(new ImplicitStatus('Dead', 'Out of health', 'fas fa-heart'))
  138. }
  139. if (this.vigors[Vigor.Stamina] <= 0) {
  140. results.push(new ImplicitStatus('Unconscious', 'Out of stamina', 'fas fa-bolt'))
  141. }
  142. if (this.vigors[Vigor.Resolve] <= 0) {
  143. results.push(new ImplicitStatus('Broken', 'Out of resolve', 'fas fa-brain'))
  144. }
  145. if (this.containedIn !== null) {
  146. results.push(new ImplicitStatus('Eaten', 'Devoured by ' + this.containedIn.owner.name, 'fas fa-drumstick-bite'))
  147. }
  148. this.statusEffects.forEach(effect => {
  149. results.push(effect)
  150. })
  151. return results
  152. }
  153. allActions (target: Creature): Array<Action> {
  154. let choices = ([] as Action[]).concat(
  155. this.actions,
  156. this.containers.flatMap(container => container.actions),
  157. target.otherActions,
  158. this.otherContainers.flatMap(container => container.actions),
  159. Object.values(this.equipment).filter(item => item !== undefined).flatMap(item => (item as Equipment).actions),
  160. this.items.filter(item => item.kind === ItemKind.Consumable && !item.consumed).flatMap(item => item.actions)
  161. )
  162. if (this.containedIn !== null) {
  163. choices = choices.concat(this.containedIn.actions)
  164. }
  165. return choices
  166. }
  167. validActions (target: Creature): Array<Action> {
  168. return this.allActions(target).filter(action => {
  169. return action.allowed(this, target)
  170. })
  171. }
  172. validGroupActions (targets: Array<Creature>): Array<GroupAction> {
  173. const choices = this.groupActions
  174. return choices.filter(action => {
  175. return targets.some(target => action.allowed(this, target))
  176. })
  177. }
  178. destroy (): LogEntry {
  179. const line: SoloLine<Creature> = (victim) => new LogLine(
  180. `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}`
  181. )
  182. const released: Array<Creature> = this.containers.flatMap(container => {
  183. return container.contents.map(prey => {
  184. prey.containedIn = this.containedIn
  185. if (this.containedIn !== null) {
  186. this.containedIn.contents.push(prey)
  187. }
  188. return prey
  189. })
  190. })
  191. const names = released.reduce((list: Array<string>, prey: Creature) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("")
  192. if (released.length > 0) {
  193. if (this.containedIn === null) {
  194. return new LogLines(
  195. line(this),
  196. new LogLine(names + ` spill out!`)
  197. )
  198. } else {
  199. return new LogLines(
  200. line(this),
  201. new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`)
  202. )
  203. }
  204. } else {
  205. return line(this)
  206. }
  207. }
  208. }