Feast 2.0!
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

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