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.
 
 
 
 
 

254 lines
7.4 KiB

  1. import { Creature, POV } from './entity'
  2. import { POVActionPicker } from './language'
  3. import { Container } from './vore'
  4. import { LogEntry, LogLines, CompositeLog } from './interface'
  5. export enum DamageType {Pierce, Slash, Crush, Acid}
  6. export interface DamageInstance {
  7. type: DamageType;
  8. amount: number;
  9. }
  10. export class Damage {
  11. readonly damages: DamageInstance[]
  12. constructor (...damages: DamageInstance[]) {
  13. this.damages = damages
  14. }
  15. scale (factor: number): Damage {
  16. const results: Array<DamageInstance> = []
  17. this.damages.forEach(damage => {
  18. results.push({
  19. type: damage.type,
  20. amount: damage.amount * factor
  21. })
  22. })
  23. return new Damage(...results)
  24. }
  25. }
  26. export enum Stat {
  27. STR = 'Strength',
  28. DEX = 'Dexterity',
  29. CON = 'Constitution'
  30. }
  31. export type Stats = {[key in Stat]: number}
  32. export enum State {
  33. Normal = 'Normal',
  34. Grappled = 'Grappled',
  35. Grappling = 'Grappling',
  36. Eaten = 'Eaten'
  37. }
  38. export interface Combatant {
  39. actions: Array<Action>;
  40. }
  41. export abstract class Action {
  42. public name: string;
  43. allowed (user: Creature, target: Creature) {
  44. return this.userStates.has(user.state) && this.targetStates.has(target.state)
  45. }
  46. abstract execute(user: Creature, target: Creature): LogEntry
  47. constructor (name: string, public userStates: Set<State>, public targetStates: Set<State>) {
  48. this.name = name
  49. }
  50. toString (): string {
  51. return this.name
  52. }
  53. }
  54. abstract class SelfAction extends Action {
  55. allowed (user: Creature, target: Creature) {
  56. if (user === target) {
  57. return super.allowed(user, target)
  58. } else {
  59. return false
  60. }
  61. }
  62. }
  63. abstract class PairAction extends Action {
  64. allowed (user: Creature, target: Creature) {
  65. if (user !== target) {
  66. return super.allowed(user, target)
  67. } else {
  68. return false
  69. }
  70. }
  71. }
  72. export class AttackAction extends PairAction {
  73. protected lines: POVActionPicker = {
  74. [POV.First]: {
  75. [POV.First]: (user, target) => new LogLines('You bite...yourself?'),
  76. [POV.Third]: (user, target) => new LogLines('You bite ' + target.name)
  77. },
  78. [POV.Third]: {
  79. [POV.First]: (user, target) => new LogLines(user.name.capital + ' bites you'),
  80. [POV.Third]: (user, target) => new LogLines(user.name.capital + ' bites ' + target.name)
  81. }
  82. }
  83. constructor (protected damage: Damage) {
  84. super('Attack', new Set([State.Normal]), new Set([State.Normal]))
  85. }
  86. execute (user: Creature, target: Creature): LogEntry {
  87. target.takeDamage(this.damage)
  88. return this.lines[user.perspective][target.perspective](user, target)
  89. }
  90. }
  91. export class DevourAction extends PairAction {
  92. protected lines: POVActionPicker = {
  93. [POV.First]: {
  94. [POV.First]: (user, target) => new LogLines('You devour...yourself?'),
  95. [POV.Third]: (user, target) => new LogLines('You devour ' + target.name)
  96. },
  97. [POV.Third]: {
  98. [POV.First]: (user, target) => new LogLines(user.name.capital + ' devours you'),
  99. [POV.Third]: (user, target) => new LogLines(user.name.capital + ' devours ' + target.name)
  100. }
  101. }
  102. constructor (protected container: Container) {
  103. super('Devour', new Set([State.Normal]), new Set([State.Normal]))
  104. }
  105. execute (user: Creature, target: Creature): LogEntry {
  106. target.state = State.Eaten
  107. return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), this.container.consume(target))
  108. }
  109. }
  110. export class StruggleAction extends PairAction {
  111. protected lines: POVActionPicker = {
  112. [POV.First]: {
  113. [POV.First]: (user, target) => new LogLines('You escape from...yourself?'),
  114. [POV.Third]: (user, target) => new LogLines('You escape from ' + target.name)
  115. },
  116. [POV.Third]: {
  117. [POV.First]: (user, target) => new LogLines(user.name.capital + ' escapes from you'),
  118. [POV.Third]: (user, target) => new LogLines(user.name.capital + ' escapes from ' + target.name)
  119. }
  120. }
  121. constructor () {
  122. super('Struggle', new Set([State.Eaten]), new Set([State.Normal]))
  123. }
  124. execute (user: Creature, target: Creature): LogEntry {
  125. if (user.containedIn) {
  126. user.state = State.Normal
  127. return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), user.containedIn.release(user))
  128. } else { return new LogLines("The prey wasn't actually eaten...") }
  129. }
  130. }
  131. export class DigestAction extends SelfAction {
  132. protected lines: POVActionPicker = {
  133. [POV.First]: {
  134. [POV.First]: (user, target) => new LogLines('You rub your stomach'),
  135. [POV.Third]: (user, target) => new LogLines("You can't digest for other people...")
  136. },
  137. [POV.Third]: {
  138. [POV.First]: (user, target) => new LogLines("Other people can't digest for you..."),
  139. [POV.Third]: (user, target) => new LogLines(user.name.capital + ' rubs ' + user.pronouns.possessive + ' gut.')
  140. }
  141. }
  142. allowed (user: Creature, target: Creature) {
  143. if (Array.from(user.containers).some(container => {
  144. return container.contents.size > 0
  145. })) {
  146. return super.allowed(user, target)
  147. } else {
  148. return false
  149. }
  150. }
  151. constructor () {
  152. super('Digest', new Set([State.Normal]), new Set([State.Normal]))
  153. }
  154. execute (user: Creature, target: Creature): LogEntry {
  155. const results = new CompositeLog(...Array.from(user.containers).map(container => container.tick(60)))
  156. return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results)
  157. }
  158. }
  159. export interface CombatTest {
  160. test: (user: Creature, target: Creature) => boolean;
  161. odds: (user: Creature, target: Creature) => number;
  162. explain: (user: Creature, target: Creature) => LogEntry;
  163. }
  164. function logistic (x0: number, L: number, k: number): (x: number) => number {
  165. return (x: number) => {
  166. return L / (1 + Math.exp(-k * (x - x0)))
  167. }
  168. }
  169. abstract class RandomTest implements CombatTest {
  170. test (user: Creature, target: Creature): boolean {
  171. return Math.random() < this.odds(user, target)
  172. }
  173. abstract odds(user: Creature, target: Creature): number
  174. abstract explain(user: Creature, target: Creature): LogEntry
  175. }
  176. export class StatTest extends RandomTest {
  177. private f: (x: number) => number
  178. constructor (public readonly stat: Stat, k = 0.1) {
  179. super()
  180. this.f = logistic(0, 1, k)
  181. }
  182. odds (user: Creature, target: Creature): number {
  183. return this.f(user.stats[this.stat] - target.stats[this.stat])
  184. }
  185. explain (user: Creature, target: Creature): LogEntry {
  186. const delta: number = user.stats[this.stat] - target.stats[this.stat]
  187. let result: string
  188. if (delta === 0) {
  189. result = 'You and the target have the same ' + this.stat + '.'
  190. } else if (delta < 0) {
  191. result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
  192. } else {
  193. result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
  194. }
  195. result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%'
  196. return new LogLines(result)
  197. }
  198. }
  199. export class ChanceTest extends RandomTest {
  200. constructor (public readonly chance: number) {
  201. super()
  202. }
  203. odds (user: Creature, target: Creature): number {
  204. return this.chance
  205. }
  206. explain (user: Creature, target: Creature): LogEntry {
  207. return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
  208. }
  209. }