Feast 2.0!
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 

383 lines
13 KiB

  1. import { TestCategory, CompositionTest, OpposedStatScorer } from '@/game/combat/tests'
  2. import { DynText, LiveText, TextLike, Verb, PairLine, PairLineArgs } from '@/game/language'
  3. import { Entity } from '@/game/entity'
  4. import { Creature } from "../creature"
  5. import { Damage, DamageFormula, Vigor, Action, Condition, CombatTest, CompositionAction } from '@/game/combat'
  6. import { LogLine, LogLines, LogEntry } from '@/game/interface'
  7. import { Connection, Container } from '@/game/vore'
  8. import { CapableCondition, UserDrainedVigorCondition, TogetherCondition, EnemyCondition, SoloCondition, PairCondition, ContainsCondition, ContainedByCondition, HasRoomCondition } from '@/game/combat/conditions'
  9. import { ConsumeConsequence } from '@/game/combat/consequences'
  10. /**
  11. * The PassAction has no effect.
  12. */
  13. export class PassAction extends Action {
  14. constructor () {
  15. super("Pass", "Do nothing", [new SoloCondition()])
  16. }
  17. execute (user: Creature, target: Creature): LogEntry {
  18. return new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('pass', 'passes'))}`)
  19. }
  20. describe (user: Creature, target: Creature): LogEntry {
  21. return new LogLine("Do nothing.")
  22. }
  23. }
  24. /**
  25. * A generic action that causes damage.
  26. */
  27. export abstract class DamageAction extends Action {
  28. abstract successLine: PairLineArgs<Creature, { damage: Damage }>
  29. constructor (name: TextLike, desc: TextLike, protected damage: DamageFormula, tests: CombatTest[], conditions: Condition[] = []) {
  30. super(
  31. name,
  32. desc,
  33. [new CapableCondition(), new EnemyCondition()].concat(conditions),
  34. tests
  35. )
  36. }
  37. // TODO: remove me or replace the logic for damage prevention
  38. execute (user: Creature, target: Creature): LogEntry {
  39. const damage = this.damage.calc(user, target)
  40. const targetResult = target.takeDamage(damage)
  41. const ownResult = this.successLine(user, target, { damage: damage })
  42. return new LogLines(ownResult, targetResult)
  43. }
  44. }
  45. /**
  46. * Adds flavor text to [[DamageAction]]
  47. */
  48. export class AttackAction extends DamageAction {
  49. constructor (damage: DamageFormula, protected verb: Verb = new Verb('smack')) {
  50. super(
  51. verb.root.capital,
  52. 'Attack the enemy',
  53. damage,
  54. [
  55. new CompositionTest(
  56. [
  57. new OpposedStatScorer(
  58. {
  59. Power: 1
  60. },
  61. {
  62. Reflexes: 1
  63. }
  64. )
  65. ],
  66. (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb("swing"))} and ${user.name.conjugate(new Verb("miss", "misses"))} ${target.name.objective}.`),
  67. TestCategory.Attack,
  68. 0
  69. )
  70. ],
  71. [new TogetherCondition()]
  72. )
  73. }
  74. describe (user: Creature, target: Creature): LogEntry {
  75. return new LogLine(`Attack ${target.baseName}. `, this.damage.describe(user, target), super.describe(user, target))
  76. }
  77. successLine: PairLineArgs<Creature, { damage: Damage }> = (user, target, args) => new LogLine(
  78. `${user.name.capital} ${user.name.conjugate(this.verb)} ${target.name.objective} for `,
  79. target.effectiveDamage(args.damage).renderShort()
  80. )
  81. }
  82. /**
  83. * Devours the target.
  84. */
  85. export class DevourAction extends CompositionAction {
  86. constructor (protected container: Container) {
  87. super(
  88. new DynText(new LiveText(container, x => x.consumeVerb.capital), ' (', new LiveText(container, x => x.name.all), ')'),
  89. new LiveText(container, x => `Try to ${x.consumeVerb} your foe`),
  90. {
  91. conditions: [new CapableCondition(), new TogetherCondition(), new HasRoomCondition(container)],
  92. tests: [
  93. new CompositionTest(
  94. [
  95. new OpposedStatScorer(
  96. { Power: 1, Charm: 1, Mass: 0.05 },
  97. { Toughness: 1, Willpower: 1, Bulk: 0.05 }
  98. )
  99. ],
  100. (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to ${container.consumeVerb} ${target.name.objective}.`),
  101. TestCategory.Vore,
  102. -5
  103. )],
  104. consequences: [
  105. new ConsumeConsequence(container)
  106. ]
  107. }
  108. )
  109. }
  110. allowed (user: Creature, target: Creature): boolean {
  111. const owner = this.container.owner === user
  112. const predOk = Array.from(this.container.voreTypes).every(pref => user.predPrefs.has(pref))
  113. const preyOk = Array.from(this.container.voreTypes).every(pref => target.preyPrefs.has(pref))
  114. if (owner && predOk && preyOk) {
  115. return super.allowed(user, target)
  116. } else {
  117. return false
  118. }
  119. }
  120. describe (user: Creature, target: Creature): LogEntry {
  121. return new LogLine(`Try to ${this.container.consumeVerb} your opponent, sending them to your ${this.container.name}. `, super.describe(user, target))
  122. }
  123. }
  124. /**
  125. * Causes the user to be eaten by the target.
  126. */
  127. export class FeedAction extends Action {
  128. constructor (protected container: Container) {
  129. super(
  130. 'Feed',
  131. 'Feed yourself to your opponent',
  132. [new UserDrainedVigorCondition(Vigor.Resolve), new TogetherCondition()]
  133. )
  134. this.name += ` (${container.name})`
  135. }
  136. allowed (user: Creature, target: Creature): boolean {
  137. const owner = this.container.owner === target
  138. const predOk = Array.from(this.container.voreTypes).every(pref => user.preyPrefs.has(pref))
  139. const preyOk = Array.from(this.container.voreTypes).every(pref => target.predPrefs.has(pref))
  140. if (owner && predOk && preyOk) {
  141. return super.allowed(user, target)
  142. } else {
  143. return false
  144. }
  145. }
  146. execute (user: Creature, target: Creature): LogEntry {
  147. return new LogLines(this.successLine(user, target), this.container.consume(user))
  148. }
  149. describe (user: Creature, target: Creature): LogEntry {
  150. return new LogLine(`Your willpower is drained, and you really feel like shoving yourself into ${target.name.possessive} ${this.container.name}...`)
  151. }
  152. protected successLine: PairLine<Entity> = (user, target) => new LogLine(
  153. `${user.name.capital} ${user.name.conjugate(new Verb('feed'))} ${user.pronouns.reflexive} to ${target.name}. `
  154. )
  155. }
  156. /**
  157. * Tries to escape from the target's container
  158. */
  159. export class StruggleAction extends Action {
  160. constructor (public container: Container) {
  161. super(
  162. new DynText('Struggle (', new LiveText(container, x => x.name.all), ')'),
  163. 'Try to escape from your foe',
  164. [new CapableCondition(), new PairCondition(), new ContainedByCondition(container)],
  165. [
  166. new CompositionTest(
  167. [
  168. new OpposedStatScorer(
  169. { Power: 1, Agility: 1, Bulk: 0.05 },
  170. { Toughness: 1, Reflexes: 1, Mass: 0.05 }
  171. )
  172. ],
  173. (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to escape from ${target.name.possessive} ${container.name}.`),
  174. TestCategory.Vore,
  175. -5
  176. )
  177. ]
  178. )
  179. }
  180. execute (user: Creature, target: Creature): LogEntry {
  181. if (user.containedIn !== null) {
  182. return new LogLines(this.successLine(user, target, { container: this.container }), user.containedIn.release(user))
  183. } else {
  184. return new LogLine("Vore's bugged!")
  185. }
  186. }
  187. describe (user: Creature, target: Creature): LogEntry {
  188. return new LogLine(`Try to escape from ${target.baseName.possessive} ${this.container.name}. `, super.describe(user, target))
  189. }
  190. protected successLine: PairLineArgs<Entity, { container: Container }> = (prey, pred, args) => new LogLine(
  191. `${prey.name.capital} ${prey.name.conjugate(new Verb('escape'))}!`
  192. )
  193. }
  194. /**
  195. * Tries to move between containers
  196. */
  197. export class StruggleMoveAction extends Action {
  198. constructor (public from: Container, public to: Container) {
  199. super(
  200. new DynText('Struggle (', new LiveText(from, x => x.name.all), ' to ', new LiveText(to, x => x.name.all), ')'),
  201. 'Try to escape from your foe',
  202. [new CapableCondition(), new PairCondition(), new ContainedByCondition(from)],
  203. [
  204. new CompositionTest(
  205. [
  206. new OpposedStatScorer(
  207. { Power: 1, Agility: 1, Bulk: 0.05 },
  208. { Toughness: 1, Reflexes: 1, Mass: 0.05 }
  209. )
  210. ],
  211. (user, target) => new LogLine(`${user.name.capital} ${user.name.conjugate(new Verb('fail'))} to escape from ${target.name.possessive} ${from.name}.`),
  212. TestCategory.Vore,
  213. -5
  214. )
  215. ]
  216. )
  217. }
  218. execute (user: Creature, target: Creature): LogEntry {
  219. if (user.containedIn !== null) {
  220. return new LogLines(this.successLine(user, target, { container: this.from }), user.containedIn.release(user))
  221. } else {
  222. return new LogLine("Vore's bugged!")
  223. }
  224. }
  225. describe (user: Creature, target: Creature): LogEntry {
  226. return new LogLine(`Try to escape from ${target.baseName.possessive} ${this.from.name}. `, super.describe(user, target))
  227. }
  228. protected successLine: PairLineArgs<Entity, { container: Container }> = (prey, pred, args) => new LogLine(
  229. `${prey.name.capital} ${prey.name.conjugate(new Verb('escape'))}!`
  230. )
  231. }
  232. export class RubAction extends Action {
  233. constructor (protected container: Container) {
  234. super(
  235. new DynText('Rub (', new LiveText(container, container => container.name.all), ')'),
  236. 'Digest your prey more quickly',
  237. [new CapableCondition(), new SoloCondition()]
  238. )
  239. }
  240. allowed (user: Creature, target: Creature) {
  241. if (this.container.owner === user && this.container.contents.length > 0) {
  242. return super.allowed(user, target)
  243. } else {
  244. return false
  245. }
  246. }
  247. execute (user: Creature, target: Creature): LogEntry {
  248. const results = this.container.tick(60)
  249. return new LogLines(results)
  250. }
  251. describe (user: Creature, target: Creature): LogEntry {
  252. return new LogLine(`Digest everyone inside of your ${this.container.name}.`)
  253. }
  254. }
  255. export class ReleaseAction extends Action {
  256. constructor (protected container: Container) {
  257. super(
  258. new DynText('Release (', new LiveText(container, x => x.name.all), ')'),
  259. 'Release one of your prey',
  260. [new CapableCondition(), new PairCondition(), new ContainsCondition(container)]
  261. )
  262. }
  263. allowed (user: Creature, target: Creature) {
  264. if (target.containedIn === this.container && this.container.contents.indexOf(target) >= 0) {
  265. return super.allowed(user, target)
  266. } else {
  267. return false
  268. }
  269. }
  270. execute (user: Creature, target: Creature): LogEntry {
  271. return this.container.release(target)
  272. }
  273. describe (user: Creature, target: Creature): LogEntry {
  274. return new LogLine(`Release ${target.baseName} from your ${this.container.name}.`)
  275. }
  276. }
  277. export class TransferAction extends Action {
  278. verb: Verb = new Verb('send')
  279. constructor (public from: Container, public to: Connection, name = 'Transfer') {
  280. super(
  281. name,
  282. `${from.name.all.capital} to ${to.destination.name.all}`,
  283. [new CapableCondition(), new PairCondition()]
  284. )
  285. }
  286. line: PairLineArgs<Creature, { from: Container; to: Connection }> = (user, target, args) => new LogLine(
  287. `${user.name.capital} ${user.name.conjugate(this.verb)} ${target.name.objective} from ${user.pronouns.possessive} ${args.from.name} to ${user.pronouns.possessive} ${args.to.destination.name}`
  288. )
  289. allowed (user: Creature, target: Creature) {
  290. if (target.containedIn === this.from && this.from.contents.includes(target)) {
  291. return super.allowed(user, target)
  292. } else {
  293. return false
  294. }
  295. }
  296. execute (user: Creature, target: Creature): LogEntry {
  297. this.from.release(target)
  298. this.to.destination.consume(target)
  299. return this.line(user, target, { from: this.from, to: this.to })
  300. }
  301. describe (user: Creature, target: Creature): LogEntry {
  302. return new LogLine(`Push ${target.baseName} from your ${this.from.name} to your ${this.to.destination.name}`)
  303. }
  304. }
  305. export class WillingTransferAction extends Action {
  306. giveIn: Verb = new Verb('give in', 'gives in', 'gave in', 'giving in')
  307. allow: Verb = new Verb('allow')
  308. verb: Verb = new Verb('send')
  309. constructor (protected from: Container, protected to: Container, name = 'Transfer') {
  310. super(
  311. name,
  312. `${from.name.all.capital} to ${to.name.all}`,
  313. [new CapableCondition(), new PairCondition()]
  314. )
  315. }
  316. line: PairLineArgs<Creature, { from: Container; to: Container }> = (user, target, args) => new LogLine(
  317. `${user.name.capital} ${user.name.conjugate(this.giveIn)} and ${user.name.conjugate(this.allow)} ${target.name.objective} to send ${user.name.objective} from ${target.pronouns.possessive} ${args.from.name} to ${target.pronouns.possessive} ${args.to.name}`
  318. )
  319. allowed (user: Creature, target: Creature) {
  320. if (user.containedIn === this.from && this.from.contents.includes(user)) {
  321. return super.allowed(user, target)
  322. } else {
  323. return false
  324. }
  325. }
  326. execute (user: Creature, target: Creature): LogEntry {
  327. this.from.release(user)
  328. this.to.consume(user)
  329. return this.line(user, target, { from: this.from, to: this.to })
  330. }
  331. describe (user: Creature, target: Creature): LogEntry {
  332. return new LogLine(`Allow ${target.baseName} to pull ${user.pronouns.subjective} ${this.to.consumePreposition} ${target.pronouns.possessive} ${this.to.name}`)
  333. }
  334. }