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

257 строки
6.4 KiB

  1. import { Creature, POV, Entity } from './entity'
  2. import { POVPair, POVPairArgs, TextLike, DynText, LiveText } from './language'
  3. import { Container } from './vore'
  4. import { LogEntry, LogLines, CompositeLog, FAElem, LogLine, FormatEntry, FormatOpt, PropElem } from './interface'
  5. import { StatTest, StatVigorTest } from './combat/tests'
  6. export enum DamageType {
  7. Pierce = "Pierce",
  8. Slash = "Slash",
  9. Crush = "Crush",
  10. Acid = "Acid",
  11. Seduction = "Seduction",
  12. Dominance = "Dominance"
  13. }
  14. export interface DamageInstance {
  15. type: DamageType;
  16. amount: number;
  17. target: Vigor;
  18. }
  19. export enum Vigor {
  20. Health = "Health",
  21. Stamina = "Stamina",
  22. Resolve = "Resolve"
  23. }
  24. export const VigorIcons: {[key in Vigor]: string} = {
  25. Health: "fas fa-heart",
  26. Stamina: "fas fa-bolt",
  27. Resolve: "fas fa-brain"
  28. }
  29. export const VigorDescs: {[key in Vigor]: string} = {
  30. Health: "How much damage you can take",
  31. Stamina: "How much energy you have",
  32. Resolve: "How much dominance you can resist"
  33. }
  34. export type Vigors = {[key in Vigor]: number}
  35. export enum Stat {
  36. Toughness = "Toughness",
  37. Power = "Power",
  38. Speed = "Speed",
  39. Willpower = "Willpower",
  40. Charm = "Charm"
  41. }
  42. export type Stats = {[key in Stat]: number}
  43. export const StatIcons: {[key in Stat]: string} = {
  44. Toughness: 'fas fa-heartbeat',
  45. Power: 'fas fa-fist-raised',
  46. Speed: 'fas fa-feather',
  47. Willpower: 'fas fa-book',
  48. Charm: 'fas fa-comments'
  49. }
  50. export const StatDescs: {[key in Stat]: string} = {
  51. Toughness: 'Your physical resistance',
  52. Power: 'Your physical power',
  53. Speed: 'How quickly you can act',
  54. Willpower: 'Your mental resistance',
  55. Charm: 'Your mental power'
  56. }
  57. export enum VoreStat {
  58. Mass = "Mass",
  59. Bulk = "Bulk",
  60. PreyCount = "Prey Count"
  61. }
  62. export type VoreStats = {[key in VoreStat]: number}
  63. export const VoreStatIcons: {[key in VoreStat]: string} = {
  64. [VoreStat.Mass]: "fas fa-weight",
  65. [VoreStat.Bulk]: "fas fa-weight-hanging",
  66. [VoreStat.PreyCount]: "fas fa-utensils"
  67. }
  68. export const VoreStatDescs: {[key in VoreStat]: string} = {
  69. [VoreStat.Mass]: "How much you weigh",
  70. [VoreStat.Bulk]: "Your weight, plus the weigh of your prey",
  71. [VoreStat.PreyCount]: "How many creatures you've got inside of you"
  72. }
  73. export interface CombatTest {
  74. test: (user: Creature, target: Creature) => boolean;
  75. odds: (user: Creature, target: Creature) => number;
  76. explain: (user: Creature, target: Creature) => LogEntry;
  77. }
  78. export interface Effect {
  79. name: string;
  80. desc: string;
  81. apply: (target: Creature) => LogEntry;
  82. }
  83. /**
  84. * An instance of damage. Contains zero or more [[DamageInstance]] objects
  85. */
  86. export class Damage {
  87. readonly damages: DamageInstance[]
  88. constructor (...damages: DamageInstance[]) {
  89. this.damages = damages
  90. }
  91. scale (factor: number): Damage {
  92. const results: Array<DamageInstance> = []
  93. this.damages.forEach(damage => {
  94. results.push({
  95. type: damage.type,
  96. amount: damage.amount * factor,
  97. target: damage.target
  98. })
  99. })
  100. return new Damage(...results)
  101. }
  102. toString (): string {
  103. return this.damages.map(damage => damage.amount + " " + damage.type).join("/")
  104. }
  105. render (): LogEntry {
  106. return new LogLine(...this.damages.flatMap(instance => {
  107. return [instance.amount.toString(), new FAElem(VigorIcons[instance.target]), " " + instance.type]
  108. }))
  109. }
  110. renderShort (): LogEntry {
  111. const totals: Vigors = Object.keys(Vigor).reduce((total: any, key) => { total[key] = 0; return total }, {})
  112. this.damages.forEach(instance => {
  113. totals[instance.target] += instance.amount
  114. })
  115. return new FormatEntry(new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [new PropElem(key as Vigor, totals[key as Vigor]), ' '])), FormatOpt.DamageInst)
  116. }
  117. }
  118. /**
  119. * Computes damage given the source and target of the damage.
  120. */
  121. export interface DamageFormula {
  122. calc (user: Creature, target: Creature): Damage;
  123. describe (user: Creature, target: Creature): LogEntry;
  124. }
  125. /**
  126. * Simply returns the damage it was given.
  127. */
  128. export class ConstantDamageFormula implements DamageFormula {
  129. calc (user: Creature, target: Creature): Damage {
  130. return this.damage
  131. }
  132. constructor (private damage: Damage) {
  133. }
  134. describe (user: Creature, target: Creature): LogEntry {
  135. return new LogLine('Deal ', this.damage.renderShort(), ' damage')
  136. }
  137. }
  138. /**
  139. * Randomly scales the damage it was given with a factor of (1-x) to (1+x)
  140. */
  141. export class UniformRandomDamageFormula implements DamageFormula {
  142. calc (user: Creature, target: Creature): Damage {
  143. return this.damage.scale(Math.random() * this.variance * 2 - this.variance + 1)
  144. }
  145. constructor (private damage: Damage, private variance: number) {
  146. }
  147. describe (user: Creature, target: Creature): LogEntry {
  148. return new LogLine('Deal between ', this.damage.scale(1 - this.variance).renderShort(), ' and ', this.damage.scale(1 + this.variance).renderShort(), ' damage.')
  149. }
  150. }
  151. /**
  152. * A Combatant has a list of possible actions to take.
  153. *
  154. * This may be merged with [[Actionable]]
  155. */
  156. export interface Combatant {
  157. actions: Array<Action>;
  158. }
  159. /**
  160. * An Action is anything that can be done by a [[Creature]] to a [[Creature]].
  161. */
  162. export abstract class Action {
  163. allowed (user: Creature, target: Creature): boolean {
  164. return this.conditions.every(cond => cond.allowed(user, target))
  165. }
  166. abstract execute(user: Creature, target: Creature): LogEntry
  167. abstract describe (user: Creature, target: Creature): LogEntry
  168. constructor (public name: TextLike, public desc: TextLike, private conditions: Array<Condition> = []) {
  169. }
  170. toString (): string {
  171. return this.name.toString()
  172. }
  173. }
  174. /**
  175. * A Condition describes whether or not something is permissible between two [[Creature]]s
  176. */
  177. export interface Condition {
  178. allowed: (user: Creature, target: Creature) => boolean;
  179. }
  180. export interface Actionable {
  181. actions: Array<Action>;
  182. }
  183. export abstract class SelfAction extends Action {
  184. allowed (user: Creature, target: Creature) {
  185. if (user === target) {
  186. return super.allowed(user, target)
  187. } else {
  188. return false
  189. }
  190. }
  191. }
  192. export abstract class PairAction extends Action {
  193. allowed (user: Creature, target: Creature) {
  194. if (user !== target) {
  195. return super.allowed(user, target)
  196. } else {
  197. return false
  198. }
  199. }
  200. }
  201. export abstract class TogetherAction extends PairAction {
  202. allowed (user: Creature, target: Creature) {
  203. if (user.containedIn === target.containedIn) {
  204. return super.allowed(user, target)
  205. } else {
  206. return false
  207. }
  208. }
  209. }