Feast 2.0!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

334 lines
11 KiB

  1. import { Mortal } from './entity'
  2. import { Damage, DamageType, Stats, Actionable, Action, Vigor, VoreStats, VisibleStatus, VoreStat } from './combat'
  3. import { LogLines, LogEntry, LogLine } from './interface'
  4. import { Noun, Pronoun, ImproperNoun, TextLike, Verb, SecondPersonPronouns, PronounAsNoun, FirstPersonPronouns, PairLineArgs, SoloLine, POV } from './language'
  5. import { DigestAction, DevourAction, ReleaseAction, StruggleAction } 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 abstract class Vore extends Mortal {
  14. containers: Array<VoreContainer> = []
  15. otherContainers: Array<Container> = []
  16. containedIn: Container | null = null
  17. destroyed = false;
  18. voreStats: VoreStats
  19. constructor (name: Noun, kind: Noun, pronouns: Pronoun, baseStats: Stats, public preyPrefs: Set<VoreType>, public predPrefs: Set<VoreType>, public mass: number) {
  20. super(name, kind, pronouns, baseStats)
  21. const containers = this.containers
  22. this.voreStats = {
  23. get [VoreStat.Bulk] () {
  24. return containers.reduce(
  25. (total: number, container: VoreContainer) => {
  26. return total + container.contents.reduce(
  27. (total: number, prey: Vore) => {
  28. return total + prey.voreStats.Bulk
  29. },
  30. 0
  31. ) + container.digested.reduce(
  32. (total: number, prey: Vore) => {
  33. return total + prey.voreStats.Bulk
  34. },
  35. 0
  36. )
  37. },
  38. this.Mass
  39. )
  40. },
  41. [VoreStat.Mass]: mass,
  42. get [VoreStat.PreyCount] () {
  43. return containers.reduce(
  44. (total: number, container: VoreContainer) => {
  45. return total + container.contents.concat(container.digested).reduce(
  46. (total: number, prey: Vore) => {
  47. return total + 1 + prey.voreStats[VoreStat.PreyCount]
  48. },
  49. 0
  50. )
  51. },
  52. 0
  53. )
  54. }
  55. }
  56. }
  57. destroy (): LogEntry {
  58. const line: SoloLine<Vore> = (victim) => new LogLine(
  59. `${victim.name.capital} ${victim.name.conjugate(new Verb('die'))}`
  60. )
  61. const released: Array<Vore> = this.containers.flatMap(container => {
  62. return container.contents.map(prey => {
  63. prey.containedIn = this.containedIn
  64. if (this.containedIn !== null) {
  65. this.containedIn.contents.push(prey)
  66. }
  67. return prey
  68. })
  69. })
  70. const names = released.reduce((list: Array<string>, prey: Vore) => list.concat([prey.name.toString()]), []).joinGeneral(", ", " and ").join("")
  71. if (released.length > 0) {
  72. if (this.containedIn === null) {
  73. return new LogLines(
  74. line(this),
  75. new LogLine(names + ` spill out!`)
  76. )
  77. } else {
  78. return new LogLines(
  79. line(this),
  80. new LogLine(names + ` spill out into ${this.containedIn.owner.name}'s ${this.containedIn.name}!`)
  81. )
  82. }
  83. } else {
  84. return line(this)
  85. }
  86. }
  87. }
  88. export interface Container extends Actionable {
  89. name: Noun;
  90. owner: Vore;
  91. voreTypes: Set<VoreType>;
  92. capacity: number;
  93. fullness: number;
  94. contents: Array<Vore>;
  95. describe: () => LogEntry;
  96. canTake: (prey: Vore) => boolean;
  97. consume: (prey: Vore) => LogEntry;
  98. release: (prey: Vore) => LogEntry;
  99. struggle: (prey: Vore) => LogEntry;
  100. consumeVerb: Verb;
  101. releaseVerb: Verb;
  102. struggleVerb: Verb;
  103. }
  104. export abstract class NormalContainer implements Container {
  105. public name: Noun
  106. contents: Array<Vore> = []
  107. actions: Array<Action> = []
  108. abstract consumeVerb: Verb
  109. abstract releaseVerb: Verb
  110. abstract struggleVerb: Verb
  111. constructor (name: Noun, public owner: Vore, public voreTypes: Set<VoreType>, public capacity: number) {
  112. this.name = name.all
  113. this.actions.push(new DevourAction(this))
  114. this.actions.push(new ReleaseAction(this))
  115. this.actions.push(new StruggleAction(this))
  116. }
  117. consumeLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  118. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.consumeVerb)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  119. }
  120. releaseLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  121. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.releaseVerb)} ${target.name.objective} up from ${user.pronouns.possessive} ${args.container.name}.`)
  122. }
  123. struggleLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  124. return new LogLine(`${user.name.capital} ${user.name.conjugate(this.struggleVerb)} within ${target.name.possessive} ${args.container.name}.`)
  125. }
  126. get fullness (): number {
  127. return Array.from(this.contents.values()).reduce((total: number, prey: Vore) => total + prey.voreStats.Bulk, 0)
  128. }
  129. canTake (prey: Vore): boolean {
  130. const fits = this.capacity - this.fullness >= prey.voreStats.Bulk
  131. const permitted = Array.from(this.voreTypes).every(voreType => {
  132. return prey.preyPrefs.has(voreType)
  133. })
  134. return fits && permitted
  135. }
  136. consume (prey: Vore): LogEntry {
  137. if (prey.containedIn !== null) {
  138. prey.containedIn.contents = prey.containedIn.contents.filter(item => prey !== item)
  139. }
  140. this.contents.push(prey)
  141. prey.containedIn = this
  142. return this.consumeLine(this.owner, prey, { container: this })
  143. }
  144. release (prey: Vore): LogEntry {
  145. prey.containedIn = this.owner.containedIn
  146. this.contents = this.contents.filter(victim => victim !== prey)
  147. if (this.owner.containedIn !== null) {
  148. this.owner.containedIn.contents.push(prey)
  149. }
  150. return this.releaseLine(this.owner, prey, { container: this })
  151. }
  152. struggle (prey: Vore): LogEntry {
  153. return this.struggleLine(prey, this.owner, { container: this })
  154. }
  155. describe (): LogEntry {
  156. const lines: Array<string> = []
  157. this.contents.forEach(prey => {
  158. lines.push(prey.toString())
  159. })
  160. return new LogLine(...lines)
  161. }
  162. }
  163. export abstract class InnerContainer extends NormalContainer {
  164. constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private escape: Container) {
  165. super(name, owner, voreTypes, capacity)
  166. this.actions = []
  167. this.actions.push(new StruggleAction(this))
  168. }
  169. release (prey: Vore): LogEntry {
  170. prey.containedIn = this.escape
  171. this.contents = this.contents.filter(victim => victim !== prey)
  172. return this.releaseLine(this.owner, prey, { container: this })
  173. }
  174. }
  175. export interface VoreContainer extends Container {
  176. digested: Array<Vore>;
  177. tick: (dt: number) => LogEntry;
  178. digest: (preys: Vore[]) => LogEntry;
  179. }
  180. export abstract class NormalVoreContainer extends NormalContainer implements VoreContainer {
  181. consumeVerb = new Verb('devour')
  182. releaseVerb = new Verb('release', 'releases', 'releasing', 'released')
  183. struggleVerb = new Verb('struggle', 'struggles', 'struggling', 'struggled')
  184. digested: Array<Vore> = []
  185. constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, private damage: Damage) {
  186. super(name, owner, voreTypes, capacity)
  187. this.name = name
  188. this.actions.push(new DigestAction(this))
  189. }
  190. consumeLine: PairLineArgs<Vore, { container: Container }> = (user, target, args) => {
  191. 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}.`)
  192. }
  193. tickLine: PairLineArgs<Vore, { container: VoreContainer; damage: Damage }> = (user, target, args) => {
  194. return new LogLine(`${user.name.capital} ${user.name.conjugate(Words.Churns)} ${target.name.objective} in ${user.pronouns.possessive} ${args.container.name}.`)
  195. }
  196. digestLine: PairLineArgs<Vore, { container: VoreContainer }> = (user, target, args) => {
  197. return new LogLine(`${user.name.capital.possessive} ${args.container.name} finishes ${Words.Digests.present} ${target.name.objective} down, ${target.pronouns.possessive} ${Words.Struggles.singular} fading away.`)
  198. }
  199. tick (dt: number): LogEntry {
  200. const justDigested: Array<Vore> = []
  201. const scaled = this.damage.scale(dt / 60)
  202. const damageResults: Array<LogEntry> = []
  203. const tickedEntries = new LogLines(...this.contents.map(prey => this.tickLine(this.owner, prey, { container: this, damage: scaled })))
  204. this.contents.forEach(prey => {
  205. damageResults.push(prey.takeDamage(scaled))
  206. if (prey.vigors[Vigor.Health] <= 0) {
  207. prey.destroyed = true
  208. this.digested.push(prey)
  209. justDigested.push(prey)
  210. }
  211. })
  212. const digestedEntries = this.digest(justDigested)
  213. this.contents = this.contents.filter(prey => {
  214. return prey.vigors[Vigor.Health] > 0
  215. })
  216. return new LogLines(tickedEntries, new LogLines(...damageResults), digestedEntries)
  217. }
  218. digest (preys: Vore[]): LogEntry {
  219. return new LogLines(...preys.map(prey => this.digestLine(this.owner, prey, { container: this })))
  220. }
  221. }
  222. export abstract class InnerVoreContainer extends NormalVoreContainer {
  223. constructor (name: Noun, owner: Vore, voreTypes: Set<VoreType>, capacity: number, damage: Damage, private escape: Container) {
  224. super(name, owner, voreTypes, capacity, damage)
  225. this.actions = []
  226. this.actions.push(new DigestAction(this))
  227. this.actions.push(new StruggleAction(this))
  228. }
  229. release (prey: Vore): LogEntry {
  230. prey.containedIn = this.escape
  231. this.contents = this.contents.filter(victim => victim !== prey)
  232. return this.releaseLine(this.owner, prey, { container: this })
  233. }
  234. }
  235. export class Stomach extends NormalVoreContainer {
  236. constructor (owner: Vore, capacity: number, damage: Damage) {
  237. super(new ImproperNoun('stomach', 'stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage)
  238. }
  239. digest (preys: Vore[]): LogEntry {
  240. if (preys.length === 0) {
  241. return super.digest(preys)
  242. }
  243. const heal = new Damage(
  244. {
  245. amount: preys.reduce((total: number, next: Vore) => total + next.maxVigors.Health / 5, 0),
  246. type: DamageType.Heal,
  247. target: Vigor.Health
  248. }
  249. )
  250. this.owner.takeDamage(heal)
  251. return new LogLines(
  252. super.digest(preys),
  253. new LogLine(`${this.owner.name.capital} heals for `, this.owner.effectiveDamage(heal).renderShort())
  254. )
  255. }
  256. }
  257. export class InnerStomach extends InnerVoreContainer {
  258. consumeVerb = new Verb('swallow')
  259. releaseVerb = new Verb('hork')
  260. constructor (owner: Vore, capacity: number, damage: Damage, escape: VoreContainer) {
  261. super(new ImproperNoun('inner stomach', 'inner stomachs').all, owner, new Set([VoreType.Oral]), capacity, damage, escape)
  262. }
  263. }
  264. export class Bowels extends NormalVoreContainer {
  265. constructor (owner: Vore, capacity: number, damage: Damage) {
  266. super(new ImproperNoun('bowel', 'bowels').plural, owner, new Set([VoreType.Anal]), capacity, damage)
  267. }
  268. }