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

509 строки
16 KiB

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