Feast 2.0!
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

519 lignes
16 KiB

  1. import { Mortal } from './entity'
  2. import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat, DamageInstance } from './combat'
  3. import { LogLines, LogEntry, LogLine, nilLog } from './interface'
  4. import { Noun, Pronoun, ImproperNoun, TextLike, Verb, SecondPersonPronouns, PronounAsNoun, FirstPersonPronouns, PairLineArgs, SoloLine, POV, RandomWord } from './language'
  5. import { DigestAction, DevourAction, ReleaseAction, StruggleAction, TransferAction } from './combat/actions'
  6. import * as Words from './words'
  7. export enum VoreType {
  8. Oral = "Oral Vore",
  9. Anal = "Anal Vore",
  10. Cock = "Cock Vore",
  11. Unbirth = "Unbirthing"
  12. }
  13. export const anyVore = new Set([
  14. VoreType.Oral,
  15. VoreType.Anal,
  16. VoreType.Cock,
  17. VoreType.Unbirth
  18. ])
  19. export abstract class Vore extends Mortal {
  20. containers: Array<VoreContainer> = []
  21. otherContainers: Array<Container> = []
  22. containedIn: Container | null = null
  23. voreStats: VoreStats
  24. constructor (name: Noun, kind: Noun, pronouns: Pronoun, baseStats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, private baseMass: number) {
  25. super(name, kind, pronouns, baseStats)
  26. const containers = this.containers
  27. // we can't use arrow notation for getters, so we gotta do this
  28. // eslint-disable-next-line
  29. const self = this
  30. this.voreStats = {
  31. get [VoreStat.Bulk] () {
  32. return containers.reduce(
  33. (total: number, container: VoreContainer) => {
  34. return total + container.contents.reduce(
  35. (total: number, prey: Vore) => {
  36. return total + prey.voreStats.Bulk
  37. },
  38. 0
  39. ) + container.digested.reduce(
  40. (total: number, prey: Vore) => {
  41. return total + prey.voreStats.Bulk
  42. },
  43. 0
  44. )
  45. },
  46. self.voreStats.Mass
  47. )
  48. },
  49. get [VoreStat.Mass] () {
  50. return self.baseMass
  51. },
  52. set [VoreStat.Mass] (mass: number) {
  53. self.baseMass = mass
  54. },
  55. get [VoreStat.PreyCount] () {
  56. return containers.reduce(
  57. (total: number, container: VoreContainer) => {
  58. return total + container.contents.concat(container.digested).reduce(
  59. (total: number, prey: Vore) => {
  60. return total + 1 + prey.voreStats[VoreStat.PreyCount]
  61. },
  62. 0
  63. )
  64. },
  65. 0
  66. )
  67. }
  68. }
  69. }
  70. destroy (): LogEntry {
  71. const line: SoloLine<Vore> = (victim) => new LogLine(
  72. `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}`
  73. )
  74. const released: Array<Vore> = this.containers.flatMap(container => {
  75. return container.contents.map(prey => {
  76. prey.containedIn = this.containedIn
  77. if (this.containedIn !== null) {
  78. this.containedIn.contents.push(prey)
  79. }
  80. return prey
  81. })
  82. })
  83. const names = released.reduce((list: Array<string>, prey: Vore) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("")
  84. if (released.length > 0) {
  85. if (this.containedIn === null) {
  86. return new LogLines(
  87. line(this),
  88. new LogLine(names + ` spill out!`)
  89. )
  90. } else {
  91. return new LogLines(
  92. line(this),
  93. new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`)
  94. )
  95. }
  96. } else {
  97. return line(this)
  98. }
  99. }
  100. }
  101. export interface Container extends Actionable {
  102. name: Noun;
  103. owner: Vore;
  104. voreTypes: Set<VoreType>;
  105. capacity: number;
  106. fullness: number;
  107. contents: Array<Vore>;
  108. describe: () => LogEntry;
  109. canTake: (prey: Vore) => boolean;
  110. consume: (prey: Vore) => LogEntry;
  111. release: (prey: Vore) => LogEntry;
  112. struggle: (prey: Vore) => LogEntry;
  113. consumeVerb: Verb;
  114. releaseVerb: Verb;
  115. struggleVerb: Verb;
  116. consumeLine: PairLineArgs<Vore, { container: Container }>;
  117. }
  118. export abstract class NormalContainer implements Container {
  119. public name: Noun
  120. contents: Array<Vore> = []
  121. actions: Array<Action> = []
  122. consumeVerb = new Verb('trap')
  123. releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  124. struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  125. constructor (name: Noun, public owner: Vore, public voreTypes: Set<VoreType>, public capacityFactor: number) {
  126. this.name = name.all
  127. this.actions.push(new DevourAction(this))
  128. this.actions.push(new ReleaseAction(this))
  129. this.actions.push(new StruggleAction(this))
  130. }
  131. get capacity (): number {
  132. return this.capacityFactor * this.owner.voreStats.Mass
  133. }
  134. consumeLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  135. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  136. }
  137. releaseLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  138. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.releaseVerb)} ${target.name.objective} up from ${user.pronouns.possessive} ${args.container.name}.`)
  139. }
  140. struggleLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  141. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.struggleVerb)} within ${target.name.possessive} ${args.container.name}.`)
  142. }
  143. get fullness (): number {
  144. return Array.from(this.contents.values()).reduce((total: number, prey: Vore) => total + prey.voreStats.Bulk, 0)
  145. }
  146. canTake (prey: Vore): boolean {
  147. const fits = this.capacity - this.fullness >= prey.voreStats.Bulk
  148. const permitted = Array.from(this.voreTypes).every(voreType => {
  149. return prey.preyPrefs.has(voreType)
  150. })
  151. return fits && permitted
  152. }
  153. consume (prey: Vore): LogEntry {
  154. if (prey.containedIn !== null) {
  155. prey.containedIn.contents = prey.containedIn.contents.filter(item => prey !== item)
  156. }
  157. this.contents.push(prey)
  158. prey.containedIn = this
  159. return this.consumeLine(this.owner, prey, { container: this })
  160. }
  161. release (prey: Vore): LogEntry {
  162. prey.containedIn = this.owner.containedIn
  163. this.contents = this.contents.filter(victim => victim !== prey)
  164. if (this.owner.containedIn !== null) {
  165. this.owner.containedIn.contents.push(prey)
  166. }
  167. return this.releaseLine(this.owner, prey, { container: this })
  168. }
  169. struggle (prey: Vore): LogEntry {
  170. return this.struggleLine(prey, this.owner, { container: this })
  171. }
  172. describe (): LogEntry {
  173. const lines: Array<string> = []
  174. this.contents.forEach(prey => {
  175. lines.push(prey.toString())
  176. })
  177. return new LogLine(...lines)
  178. }
  179. }
  180. export class Hand extends NormalContainer {
  181. consumeVerb = new Verb("grab")
  182. constructor (owner: Vore, capacity: number) {
  183. super(
  184. new ImproperNoun('hand'),
  185. owner,
  186. new Set(),
  187. capacity
  188. )
  189. }
  190. }
  191. export abstract class InnerContainer extends NormalContainer {
  192. constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
  193. super(name, owner, voreTypes, capacity)
  194. this.actions = []
  195. this.actions.push(new StruggleAction(this))
  196. }
  197. release (prey: Vore): LogEntry {
  198. prey.containedIn = this.escape
  199. this.contents = this.contents.filter(victim => victim !== prey)
  200. return this.releaseLine(this.owner, prey, { container: this })
  201. }
  202. }
  203. export interface VoreContainer extends Container {
  204. digested: Array<Vore>;
  205. tick: (dt: number) => LogEntry;
  206. digest: (preys: Vore[]) => LogEntry;
  207. absorb: (preys: Vore[]) => LogEntry;
  208. fluidColor: string;
  209. onDigest: (prey: Vore) => LogEntry;
  210. onAbsorb: (prey: Vore) => LogEntry;
  211. }
  212. export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer {
  213. consumeVerb = new Verb('devour')
  214. releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  215. struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  216. fluidColor = "#00ff0088"
  217. digested: Array<Vore> = []
  218. absorbed: Array<Vore> = []
  219. constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) {
  220. super(name, owner, voreTypes, capacity)
  221. this.name = name
  222. this.actions.push(new DigestAction(this))
  223. }
  224. get fullness (): number {
  225. return Array.from(this.contents.concat(this.digested, this.absorbed).values()).reduce((total: number, prey: Vore) => total + prey.voreStats.Bulk, 0)
  226. }
  227. consumeLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  228. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective}, forcing ${target.pronouns.objective} into ${user.pronouns.possessive} ${args.container.name}.`)
  229. }
  230. tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  231. return new LogLine(`${user.name.capital} ${user.name.conjugate(Words.Churns)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  232. }
  233. digestLine: PairLineArgs<Vore, { container: VoreContainer }> = (user, target, args) => {
  234. return new LogLine(`${user.name.capital.possessive} ${args.container.name} ${args.container.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Digests.present} ${target.name.objective} down, ${target.pronouns.possessive} ${Words.Struggles.singular} fading away.`)
  235. }
  236. absorbLine: PairLineArgs<Vore, { container: VoreContainer }> = (user, target, args) => {
  237. return new LogLine(`${user.name.capital.possessive} ${args.container.name} ${args.container.name.conjugate(new Verb('finish', 'finishes'))} ${Words.Absorbs.present} ${target.name.objective}, fully claiming ${target.pronouns.objective}.`)
  238. }
  239. tick (dt: number): LogEntry {
  240. const justDigested: Array<Vore> = []
  241. const justAbsorbed: Array<Vore> = []
  242. const scaled = this.damage.scale(dt / 60)
  243. const damageResults: Array<LogEntry> = []
  244. const tickedEntries = new LogLines(...this.contents.map(prey => this.tickLine(this.owner, prey, { container: this, damage: scaled })))
  245. this.contents.forEach(prey => {
  246. damageResults.push(prey.takeDamage(scaled))
  247. if (prey.vigors[Vigor.Health] <= 0) {
  248. prey.destroyed = true
  249. this.digested.push(prey)
  250. justDigested.push(prey)
  251. damageResults.push(this.onDigest(prey))
  252. }
  253. })
  254. this.digested.forEach(prey => {
  255. const damageTotal: number = prey.effectiveDamage(scaled).damages.filter(instance => instance.target === Vigor.Health).reduce(
  256. (total: number, instance: DamageInstance) => total + instance.amount,
  257. 0
  258. )
  259. const massStolen = Math.min(damageTotal / 100, prey.voreStats.Mass)
  260. prey.voreStats.Mass -= massStolen
  261. this.owner.voreStats.Mass += massStolen
  262. if (prey.voreStats.Mass === 0) {
  263. this.absorbed.push(prey)
  264. justAbsorbed.push(prey)
  265. damageResults.push(this.onAbsorb(prey))
  266. }
  267. })
  268. const digestedEntries = this.digest(justDigested)
  269. const absorbedEntries = this.absorb(justAbsorbed)
  270. this.contents = this.contents.filter(prey => {
  271. return prey.vigors[Vigor.Health] > 0
  272. })
  273. this.digested = this.digested.filter(prey => {
  274. return prey.voreStats.Mass > 0
  275. })
  276. return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries, absorbedEntries)
  277. }
  278. absorb (preys: Vore[]): LogEntry {
  279. return new LogLines(...preys.map(prey => this.absorbLine(this.owner, prey, { container: this })))
  280. }
  281. digest (preys: Vore[]): LogEntry {
  282. return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey, { container: this })))
  283. }
  284. onAbsorb (prey: Vore): LogEntry {
  285. return nilLog
  286. }
  287. onDigest (prey: Vore): LogEntry {
  288. return nilLog
  289. }
  290. }
  291. export abstract class InnerVoreContainer extends NormalVoreContainer {
  292. constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: Container) {
  293. super(name, owner, voreTypes, capacity, damage)
  294. this.actions = []
  295. this.actions.push(new DigestAction(this))
  296. this.actions.push(new StruggleAction(this))
  297. }
  298. release (prey: Vore): LogEntry {
  299. prey.containedIn = this.escape
  300. this.contents = this.contents.filter(victim => victim !== prey)
  301. this.escape.consume(prey)
  302. return this.releaseLine(this.owner, prey, { container: this })
  303. }
  304. }
  305. export class Stomach extends NormalVoreContainer {
  306. constructor (owner: Vore, capacity: number, damage: Damage) {
  307. super(new ImproperNoun('stomach', 'stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage)
  308. }
  309. digest (preys: Vore[]): LogEntry {
  310. if (preys.length === 0) {
  311. return super.digest(preys)
  312. }
  313. const heal = new Damage(
  314. {
  315. amount: preys.reduce((total: number, next: Vore) => total + next.maxVigors.Health / 5, 0),
  316. type: DamageType.Heal,
  317. target: Vigor.Health
  318. }
  319. )
  320. this.owner.takeDamage(heal)
  321. return new LogLines(
  322. super.digest(preys),
  323. new LogLine(`${this.owner.name.capital} heals for `, this.owner.effectiveDamage(heal).renderShort())
  324. )
  325. }
  326. }
  327. export class InnerStomach extends InnerVoreContainer {
  328. consumeVerb = new Verb('swallow')
  329. releaseVerb = new Verb('hork')
  330. constructor (owner: Vore, capacity: number, damage: Damage, escape: VoreContainer) {
  331. super(new ImproperNoun('inner stomach', 'inner stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage, escape)
  332. }
  333. }
  334. export class Bowels extends NormalVoreContainer {
  335. constructor (owner: Vore, capacity: number, damage: Damage) {
  336. super(new ImproperNoun('bowel', 'bowels').plural.all, owner, new Set([VoreType.Anal]), capacity, damage)
  337. }
  338. tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  339. return new LogLine(`${user.name.capital} ${user.name.conjugate(
  340. new RandomWord([
  341. new Verb('crush', 'crushes'),
  342. new Verb('clench', 'clenches')
  343. ])
  344. )} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  345. }
  346. }
  347. export class Cock extends NormalVoreContainer {
  348. fluidColor = "#eeeeee66";
  349. constructor (owner: Vore, capacity: number, damage: Damage) {
  350. super(
  351. new ImproperNoun('cock').all,
  352. owner,
  353. new Set([VoreType.Cock]),
  354. capacity,
  355. damage
  356. )
  357. }
  358. tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  359. return new LogLine(`${user.name.capital} ${user.name.conjugate(
  360. new RandomWord([
  361. new Verb('crush', 'crushes'),
  362. new Verb('clench', 'clenches')
  363. ])
  364. )} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  365. }
  366. }
  367. export class Balls extends InnerVoreContainer {
  368. fluidColor = "#eeeeeecc";
  369. constructor (owner: Vore, capacity: number, damage: Damage, escape: Container) {
  370. super(
  371. new ImproperNoun('ball', 'balls').all.plural,
  372. owner,
  373. new Set([VoreType.Cock]),
  374. capacity,
  375. damage,
  376. escape
  377. )
  378. }
  379. }
  380. export class Slit extends NormalVoreContainer {
  381. fluidColor = "#cccccc99";
  382. constructor (owner: Vore, capacity: number, damage: Damage) {
  383. super(
  384. new ImproperNoun('slit').all,
  385. owner,
  386. new Set([VoreType.Unbirth]),
  387. capacity,
  388. damage
  389. )
  390. }
  391. }
  392. export class Womb extends InnerVoreContainer {
  393. fluidColor = "#ddddddbb";
  394. constructor (owner: Vore, capacity: number, damage: Damage, escape: Container) {
  395. super(
  396. new ImproperNoun('womb').all,
  397. owner,
  398. new Set([VoreType.Unbirth]),
  399. capacity,
  400. damage,
  401. escape
  402. )
  403. }
  404. }
  405. export function biconnectContainers (outer: VoreContainer, inner: VoreContainer): void {
  406. outer.onDigest = (prey: Vore) => {
  407. outer.digested = outer.digested.filter(victim => victim !== prey)
  408. inner.digested.push(prey)
  409. return inner.consumeLine(inner.owner, prey, { container: inner })
  410. }
  411. outer.actions.push(
  412. new TransferAction(
  413. outer,
  414. inner
  415. )
  416. )
  417. inner.actions.push(
  418. new TransferAction(
  419. inner,
  420. outer
  421. )
  422. )
  423. }