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

321 строка
9.1 KiB

  1. import { Creature, POV, Entity } from './entity'
  2. import { POVPair, POVPairArgs } from './language'
  3. import { Container } from './vore'
  4. import { LogEntry, LogLines, CompositeLog } from './interface'
  5. export interface CombatTest {
  6. test: (user: Creature, target: Creature) => boolean;
  7. odds: (user: Creature, target: Creature) => number;
  8. explain: (user: Creature, target: Creature) => LogEntry;
  9. }
  10. function logistic (x0: number, L: number, k: number): (x: number) => number {
  11. return (x: number) => {
  12. return L / (1 + Math.exp(-k * (x - x0)))
  13. }
  14. }
  15. abstract class RandomTest implements CombatTest {
  16. test (user: Creature, target: Creature): boolean {
  17. return Math.random() < this.odds(user, target)
  18. }
  19. abstract odds(user: Creature, target: Creature): number
  20. abstract explain(user: Creature, target: Creature): LogEntry
  21. }
  22. export class StatTest extends RandomTest {
  23. private f: (x: number) => number
  24. constructor (public readonly stat: Stat, k = 0.1) {
  25. super()
  26. this.f = logistic(0, 1, k)
  27. }
  28. odds (user: Creature, target: Creature): number {
  29. return this.f(user.stats[this.stat] - target.stats[this.stat])
  30. }
  31. explain (user: Creature, target: Creature): LogEntry {
  32. const delta: number = user.stats[this.stat] - target.stats[this.stat]
  33. let result: string
  34. if (delta === 0) {
  35. result = 'You and the target have the same ' + this.stat + '.'
  36. } else if (delta < 0) {
  37. result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
  38. } else {
  39. result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
  40. }
  41. result += ' Your odds of success are ' + (100 * this.odds(user, target)) + '%'
  42. return new LogLines(result)
  43. }
  44. }
  45. export class ChanceTest extends RandomTest {
  46. constructor (public readonly chance: number) {
  47. super()
  48. }
  49. odds (user: Creature, target: Creature): number {
  50. return this.chance
  51. }
  52. explain (user: Creature, target: Creature): LogEntry {
  53. return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
  54. }
  55. }
  56. export enum DamageType {
  57. Pierce = "Pierce",
  58. Slash = "Slash",
  59. Crush = "Crush",
  60. Acid = "Acid"}
  61. export interface DamageInstance {
  62. type: DamageType;
  63. amount: number;
  64. }
  65. export class Damage {
  66. readonly damages: DamageInstance[]
  67. constructor (...damages: DamageInstance[]) {
  68. this.damages = damages
  69. }
  70. scale (factor: number): Damage {
  71. const results: Array<DamageInstance> = []
  72. this.damages.forEach(damage => {
  73. results.push({
  74. type: damage.type,
  75. amount: damage.amount * factor
  76. })
  77. })
  78. return new Damage(...results)
  79. }
  80. toString (): string {
  81. return this.damages.map(damage => damage.amount + " " + damage.type).join("/")
  82. }
  83. }
  84. export enum Stat {
  85. STR = 'Strength',
  86. DEX = 'Dexterity',
  87. CON = 'Constitution'
  88. }
  89. export type Stats = {[key in Stat]: number}
  90. export interface Combatant {
  91. actions: Array<Action>;
  92. }
  93. export abstract class Action {
  94. abstract allowed (user: Creature, target: Creature): boolean
  95. abstract execute(user: Creature, target: Creature): LogEntry
  96. constructor (public name: string, public desc: string) {
  97. }
  98. toString (): string {
  99. return this.name
  100. }
  101. }
  102. export interface Actionable {
  103. actions: Array<Action>;
  104. }
  105. abstract class SelfAction extends Action {
  106. allowed (user: Creature, target: Creature) {
  107. return user === target
  108. }
  109. }
  110. abstract class PairAction extends Action {
  111. allowed (user: Creature, target: Creature) {
  112. return user !== target
  113. }
  114. }
  115. abstract class TogetherAction extends PairAction {
  116. allowed (user: Creature, target: Creature) {
  117. if (user.containedIn === target.containedIn) {
  118. return super.allowed(user, target)
  119. } else {
  120. return false
  121. }
  122. }
  123. }
  124. export class AttackAction extends TogetherAction {
  125. private test: StatTest
  126. protected successLines: POVPairArgs<Entity, Entity, { damage: Damage }> = new POVPairArgs([
  127. [[POV.First, POV.Third], (user, target, args) => new LogLines(`You smack ${target.name} for ${args.damage} damage`)],
  128. [[POV.Third, POV.First], (user, target, args) => new LogLines(`${user.name.capital} hits you for ${args.damage} damage`)],
  129. [[POV.Third, POV.Third], (user, target, args) => new LogLines(`${user.name.capital} hits ${target.name.capital} for ${args.damage} damage`)]
  130. ])
  131. protected failLines: POVPair<Entity, Entity> = new POVPair([
  132. [[POV.First, POV.Third], (user, target) => new LogLines(`You try to smack ${target.name}, but you miss`)],
  133. [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} misses you`)],
  134. [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} misses ${target.name}`)]
  135. ])
  136. constructor (protected damage: Damage) {
  137. super('Attack', 'Attack the enemy')
  138. this.test = new StatTest(Stat.STR)
  139. }
  140. execute (user: Creature, target: Creature): LogEntry {
  141. if (this.test.test(user, target)) {
  142. target.takeDamage(this.damage)
  143. return this.successLines.run(user, target, { damage: this.damage })
  144. } else {
  145. return this.failLines.run(user, target)
  146. }
  147. }
  148. }
  149. export class DevourAction extends TogetherAction {
  150. private test: StatTest
  151. protected failLines: POVPair<Entity, Entity> = new POVPair([
  152. [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to make a meal out of ${target.name}`)],
  153. [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to devour you, but fails`)],
  154. [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to swallow ${target.name}`)]
  155. ])
  156. allowed (user: Creature, target: Creature): boolean {
  157. const owner = this.container.owner === user
  158. const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
  159. const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))
  160. if (owner && predOk && preyOk) {
  161. return super.allowed(user, target)
  162. } else {
  163. return false
  164. }
  165. }
  166. constructor (protected container: Container) {
  167. super('Devour', 'Try to consume your foe')
  168. this.name += ` (${container.name})`
  169. this.test = new StatTest(Stat.STR)
  170. }
  171. execute (user: Creature, target: Creature): LogEntry {
  172. if (this.test.test(user, target)) {
  173. return this.container.consume(target)
  174. } else {
  175. return this.failLines.run(user, target)
  176. }
  177. }
  178. }
  179. export class StruggleAction extends PairAction {
  180. private test: StatTest
  181. protected failLines: POVPair<Entity, Entity> = new POVPair([
  182. [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to escape from ${target.name}`)],
  183. [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to escape from you, but fails`)],
  184. [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully struggles within ${target.name}`)]
  185. ])
  186. allowed (user: Creature, target: Creature) {
  187. if (user.containedIn === this.container) {
  188. return super.allowed(user, target)
  189. } else {
  190. return false
  191. }
  192. }
  193. constructor (public container: Container) {
  194. super('Struggle', 'Try to escape your predator')
  195. this.test = new StatTest(Stat.STR)
  196. }
  197. execute (user: Creature, target: Creature): LogEntry {
  198. if (user.containedIn !== null) {
  199. if (this.test.test(user, target)) {
  200. return user.containedIn.release(user)
  201. } else {
  202. return this.failLines.run(user, target)
  203. }
  204. } else {
  205. return new LogLines("Vore's bugged!")
  206. }
  207. }
  208. }
  209. export class DigestAction extends SelfAction {
  210. protected lines: POVPair<Entity, Entity> = new POVPair([])
  211. allowed (user: Creature, target: Creature) {
  212. if (this.container.owner === user && this.container.contents.length > 0) {
  213. return super.allowed(user, target)
  214. } else {
  215. return false
  216. }
  217. }
  218. constructor (protected container: Container) {
  219. super('Digest', 'Digest all of your current prey')
  220. this.name += ` (${container.name})`
  221. }
  222. execute (user: Creature, target: Creature): LogEntry {
  223. const results = new CompositeLog(...user.containers.map(container => container.tick(60)))
  224. return new CompositeLog(this.lines.run(user, target), results)
  225. }
  226. }
  227. export class ReleaseAction extends PairAction {
  228. allowed (user: Creature, target: Creature) {
  229. if (target.containedIn === this.container) {
  230. return super.allowed(user, target)
  231. } else {
  232. return false
  233. }
  234. }
  235. constructor (protected container: Container) {
  236. super('Release', 'Release one of your prey')
  237. this.name += ` (${container.name})`
  238. }
  239. execute (user: Creature, target: Creature): LogEntry {
  240. return this.container.release(target)
  241. }
  242. }
  243. export class TransferAction extends PairAction {
  244. protected lines: POVPair<Entity, Entity> = new POVPair([])
  245. allowed (user: Creature, target: Creature) {
  246. if (target.containedIn === this.from) {
  247. return super.allowed(user, target)
  248. } else {
  249. return false
  250. }
  251. }
  252. constructor (protected from: Container, protected to: Container) {
  253. super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`)
  254. }
  255. execute (user: Creature, target: Creature): LogEntry {
  256. this.from.release(target)
  257. this.to.consume(target)
  258. return this.lines.run(user, target)
  259. }
  260. }