Feast 2.0!
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 

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