Feast 2.0!
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 

431 řádky
14 KiB

  1. import { Mortal } from './entity'
  2. import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat, DamageInstance, DamageFormula } 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. import { Creature } from './creature'
  8. export enum VoreType {
  9. Oral = "Oral Vore",
  10. Anal = "Anal Vore",
  11. Cock = "Cock Vore",
  12. Unbirth = "Unbirthing"
  13. }
  14. export const anyVore = new Set([
  15. VoreType.Oral,
  16. VoreType.Anal,
  17. VoreType.Cock,
  18. VoreType.Unbirth
  19. ])
  20. export interface Container extends Actionable {
  21. name: Noun;
  22. owner: Creature;
  23. voreTypes: Set<VoreType>;
  24. capacity: number;
  25. fullness: number;
  26. contents: Array<Creature>;
  27. describe: () => LogEntry;
  28. canTake: (prey: Creature) => boolean;
  29. consume: (prey: Creature) => LogEntry;
  30. release: (prey: Creature) => LogEntry;
  31. struggle: (prey: Creature) => LogEntry;
  32. consumeVerb: Verb;
  33. releaseVerb: Verb;
  34. struggleVerb: Verb;
  35. consumeLine: PairLineArgs<Creature, { container: Container }>;
  36. }
  37. export abstract class NormalContainer implements Container {
  38. public name: Noun
  39. contents: Array<Creature> = []
  40. actions: Array<Action> = []
  41. consumeVerb = new Verb('trap')
  42. releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  43. struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  44. constructor (name: Noun, public owner: Creature, public voreTypes: Set<VoreType>, public capacityFactor: number) {
  45. this.name = name.all
  46. this.actions.push(new DevourAction(this))
  47. this.actions.push(new ReleaseAction(this))
  48. this.actions.push(new StruggleAction(this))
  49. }
  50. get capacity (): number {
  51. return this.capacityFactor * this.owner.voreStats.Mass
  52. }
  53. consumeLine: PairLineArgs<Creature, { container: Container }> = (user, target, args) => {
  54. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  55. }
  56. releaseLine: PairLineArgs<Creature, { container: Container }> = (user, target, args) => {
  57. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.releaseVerb)} ${target.name.objective} up from ${user.pronouns.possessive} ${args.container.name}.`)
  58. }
  59. struggleLine: PairLineArgs<Creature, { container: Container }> = (user, target, args) => {
  60. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.struggleVerb)} within ${target.name.possessive} ${args.container.name}.`)
  61. }
  62. get fullness (): number {
  63. return Array.from(this.contents.values()).reduce((total: number, prey: Creature) => total + prey.voreStats.Bulk, 0)
  64. }
  65. canTake (prey: Creature): boolean {
  66. const fits = this.capacity - this.fullness >= prey.voreStats.Bulk
  67. const permitted = Array.from(this.voreTypes).every(voreType => {
  68. return prey.preyPrefs.has(voreType)
  69. })
  70. return fits && permitted
  71. }
  72. consume (prey: Creature): LogEntry {
  73. if (prey.containedIn !== null) {
  74. prey.containedIn.contents = prey.containedIn.contents.filter(item => prey !== item)
  75. }
  76. this.contents.push(prey)
  77. prey.containedIn = this
  78. return this.consumeLine(this.owner, prey, { container: this })
  79. }
  80. release (prey: Creature): LogEntry {
  81. prey.containedIn = this.owner.containedIn
  82. this.contents = this.contents.filter(victim => victim !== prey)
  83. if (this.owner.containedIn !== null) {
  84. this.owner.containedIn.contents.push(prey)
  85. }
  86. return this.releaseLine(this.owner, prey, { container: this })
  87. }
  88. struggle (prey: Creature): LogEntry {
  89. return this.struggleLine(prey, this.owner, { container: this })
  90. }
  91. describe (): LogEntry {
  92. const lines: Array<string> = []
  93. this.contents.forEach(prey => {
  94. lines.push(prey.toString())
  95. })
  96. return new LogLine(...lines)
  97. }
  98. }
  99. export class Hand extends NormalContainer {
  100. consumeVerb = new Verb("grab")
  101. constructor (owner: Creature, capacity: number) {
  102. super(
  103. new ImproperNoun('hand'),
  104. owner,
  105. new Set(),
  106. capacity
  107. )
  108. }
  109. }
  110. export abstract class InnerContainer extends NormalContainer {
  111. constructor (name: Noun, owner: Creature, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
  112. super(name, owner, voreTypes, capacity)
  113. this.actions = []
  114. this.actions.push(new StruggleAction(this))
  115. }
  116. release (prey: Creature): LogEntry {
  117. prey.containedIn = this.escape
  118. this.contents = this.contents.filter(victim => victim !== prey)
  119. return this.releaseLine(this.owner, prey, { container: this })
  120. }
  121. }
  122. export interface VoreContainer extends Container {
  123. digested: Array<Creature>;
  124. tick: (dt: number) => LogEntry;
  125. digest: (preys: Creature[]) => LogEntry;
  126. absorb: (preys: Creature[]) => LogEntry;
  127. fluidColor: string;
  128. onDigest: (prey: Creature) => LogEntry;
  129. onAbsorb: (prey: Creature) => LogEntry;
  130. }
  131. export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer {
  132. consumeVerb = new Verb('devour')
  133. releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  134. struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  135. fluidColor = "#00ff0088"
  136. digested: Array<Creature> = []
  137. absorbed: Array<Creature> = []
  138. constructor (name: Noun, owner: Creature, voreTypes: Set<VoreType>, capacity: number, private damage: DamageFormula) {
  139. super(name, owner, voreTypes, capacity)
  140. this.name = name
  141. this.actions.push(new DigestAction(this))
  142. }
  143. get fullness (): number {
  144. return Array.from(this.contents.concat(this.digested, this.absorbed).values()).reduce((total: number, prey: Creature) => total + prey.voreStats.Bulk, 0)
  145. }
  146. consumeLine: PairLineArgs<Creature, { container: Container }> = (user, target, args) => {
  147. 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}.`)
  148. }
  149. tickLine: PairLineArgs<Creature, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  150. return new LogLine(`${user.name.capital} ${user.name.conjugate(Words.Churns)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  151. }
  152. digestLine: PairLineArgs<Creature, { container: VoreContainer }> = (user, target, args) => {
  153. 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.`)
  154. }
  155. absorbLine: PairLineArgs<Creature, { container: VoreContainer }> = (user, target, args) => {
  156. 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}.`)
  157. }
  158. tick (dt: number): LogEntry {
  159. const justDigested: Array<Creature> = []
  160. const justAbsorbed: Array<Creature> = []
  161. const damageResults: Array<LogEntry> = []
  162. const tickedEntryList: LogEntry[] = []
  163. this.contents.forEach(prey => {
  164. const scaled = this.damage.calc(this.owner, prey).scale(dt / 60)
  165. tickedEntryList.push(this.tickLine(this.owner, prey, { container: this, damage: scaled }))
  166. damageResults.push(prey.takeDamage(scaled))
  167. if (prey.vigors[Vigor.Health] <= 0) {
  168. prey.destroyed = true
  169. this.digested.push(prey)
  170. justDigested.push(prey)
  171. damageResults.push(this.onDigest(prey))
  172. }
  173. })
  174. const tickedEntries = new LogLines(...tickedEntryList)
  175. this.digested.forEach(prey => {
  176. const scaled = this.damage.calc(this.owner, prey).scale(dt / 60)
  177. const damageTotal: number = prey.effectiveDamage(scaled).damages.filter(instance => instance.target === Vigor.Health).reduce(
  178. (total: number, instance: DamageInstance) => total + instance.amount,
  179. 0
  180. )
  181. const massStolen = Math.min(damageTotal / 100, prey.voreStats.Mass)
  182. prey.voreStats.Mass -= massStolen
  183. this.owner.voreStats.Mass += massStolen
  184. if (prey.voreStats.Mass === 0) {
  185. this.absorbed.push(prey)
  186. justAbsorbed.push(prey)
  187. damageResults.push(this.onAbsorb(prey))
  188. }
  189. })
  190. const digestedEntries = this.digest(justDigested)
  191. const absorbedEntries = this.absorb(justAbsorbed)
  192. this.contents = this.contents.filter(prey => {
  193. return prey.vigors[Vigor.Health] > 0
  194. })
  195. this.digested = this.digested.filter(prey => {
  196. return prey.voreStats.Mass > 0
  197. })
  198. return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries, absorbedEntries)
  199. }
  200. absorb (preys: Creature[]): LogEntry {
  201. return new LogLines(...preys.map(prey => this.absorbLine(this.owner, prey, { container: this })))
  202. }
  203. digest (preys: Creature[]): LogEntry {
  204. return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey, { container: this })))
  205. }
  206. onAbsorb (prey: Creature): LogEntry {
  207. return nilLog
  208. }
  209. onDigest (prey: Creature): LogEntry {
  210. return nilLog
  211. }
  212. }
  213. export abstract class InnerVoreContainer extends NormalVoreContainer {
  214. constructor (name: Noun, owner: Creature, voreTypes: Set<VoreType>, capacity: number, damage: DamageFormula, private escape: Container) {
  215. super(name, owner, voreTypes, capacity, damage)
  216. this.actions = []
  217. this.actions.push(new DigestAction(this))
  218. this.actions.push(new StruggleAction(this))
  219. }
  220. release (prey: Creature): LogEntry {
  221. prey.containedIn = this.escape
  222. this.contents = this.contents.filter(victim => victim !== prey)
  223. this.escape.consume(prey)
  224. return this.releaseLine(this.owner, prey, { container: this })
  225. }
  226. }
  227. export class Stomach extends NormalVoreContainer {
  228. constructor (owner: Creature, capacity: number, damage: DamageFormula) {
  229. super(new ImproperNoun('stomach', 'stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage)
  230. }
  231. digest (preys: Creature[]): LogEntry {
  232. if (preys.length === 0) {
  233. return super.digest(preys)
  234. }
  235. const heal = new Damage(
  236. {
  237. amount: preys.reduce((total: number, next: Creature) => total + next.maxVigors.Health / 5, 0),
  238. type: DamageType.Heal,
  239. target: Vigor.Health
  240. }
  241. )
  242. this.owner.takeDamage(heal)
  243. return new LogLines(
  244. super.digest(preys),
  245. new LogLine(`${this.owner.name.capital} heals for `, this.owner.effectiveDamage(heal).renderShort())
  246. )
  247. }
  248. }
  249. export class InnerStomach extends InnerVoreContainer {
  250. consumeVerb = new Verb('swallow')
  251. releaseVerb = new Verb('hork')
  252. constructor (owner: Creature, capacity: number, damage: DamageFormula, escape: VoreContainer) {
  253. super(new ImproperNoun('inner stomach', 'inner stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage, escape)
  254. }
  255. }
  256. export class Bowels extends NormalVoreContainer {
  257. constructor (owner: Creature, capacity: number, damage: DamageFormula) {
  258. super(new ImproperNoun('bowel', 'bowels').plural.all, owner, new Set([VoreType.Anal]), capacity, damage)
  259. }
  260. tickLine: PairLineArgs<Creature, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  261. return new LogLine(`${user.name.capital} ${user.name.conjugate(
  262. new RandomWord([
  263. new Verb('crush', 'crushes'),
  264. new Verb('clench', 'clenches')
  265. ])
  266. )} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  267. }
  268. }
  269. export class Cock extends NormalVoreContainer {
  270. fluidColor = "#eeeeee66";
  271. constructor (owner: Creature, capacity: number, damage: DamageFormula) {
  272. super(
  273. new ImproperNoun('cock').all,
  274. owner,
  275. new Set([VoreType.Cock]),
  276. capacity,
  277. damage
  278. )
  279. }
  280. tickLine: PairLineArgs<Creature, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  281. return new LogLine(`${user.name.capital} ${user.name.conjugate(
  282. new RandomWord([
  283. new Verb('crush', 'crushes'),
  284. new Verb('clench', 'clenches')
  285. ])
  286. )} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  287. }
  288. }
  289. export class Balls extends InnerVoreContainer {
  290. fluidColor = "#eeeeeecc";
  291. constructor (owner: Creature, capacity: number, damage: DamageFormula, escape: Container) {
  292. super(
  293. new ImproperNoun('ball', 'balls').all.plural,
  294. owner,
  295. new Set([VoreType.Cock]),
  296. capacity,
  297. damage,
  298. escape
  299. )
  300. }
  301. }
  302. export class Slit extends NormalVoreContainer {
  303. fluidColor = "#cccccc99";
  304. constructor (owner: Creature, capacity: number, damage: DamageFormula) {
  305. super(
  306. new ImproperNoun('slit').all,
  307. owner,
  308. new Set([VoreType.Unbirth]),
  309. capacity,
  310. damage
  311. )
  312. }
  313. }
  314. export class Womb extends InnerVoreContainer {
  315. fluidColor = "#ddddddbb";
  316. constructor (owner: Creature, capacity: number, damage: DamageFormula, escape: Container) {
  317. super(
  318. new ImproperNoun('womb').all,
  319. owner,
  320. new Set([VoreType.Unbirth]),
  321. capacity,
  322. damage,
  323. escape
  324. )
  325. }
  326. }
  327. export function biconnectContainers (outer: VoreContainer, inner: VoreContainer): void {
  328. outer.onDigest = (prey: Creature) => {
  329. outer.digested = outer.digested.filter(victim => victim !== prey)
  330. inner.digested.push(prey)
  331. return inner.consumeLine(inner.owner, prey, { container: inner })
  332. }
  333. outer.actions.push(
  334. new TransferAction(
  335. outer,
  336. inner
  337. )
  338. )
  339. inner.actions.push(
  340. new TransferAction(
  341. inner,
  342. outer
  343. )
  344. )
  345. }