Containers can now offer actions to their contents.master
| @@ -15,6 +15,7 @@ module.exports = { | |||||
| 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | ||||
| 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', | ||||
| 'no-useless-constructor': 'off', | 'no-useless-constructor': 'off', | ||||
| '@typescript-eslint/no-unused-vars': 'off' | |||||
| '@typescript-eslint/no-unused-vars': 'off', | |||||
| 'quotes': 'off' | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,7 +4,8 @@ | |||||
| <meta charset="utf-8"> | <meta charset="utf-8"> | ||||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
| <meta name="viewport" content="width=device-width,initial-scale=1.0"> | <meta name="viewport" content="width=device-width,initial-scale=1.0"> | ||||
| <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |||||
| <link rel="icon" href="https://crux.sexy/images/feast.ico"> | |||||
| <link rel="stylesheet" href="./reset.css"</link> | |||||
| <title><%= htmlWebpackPlugin.options.title %></title> | <title><%= htmlWebpackPlugin.options.title %></title> | ||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| @@ -0,0 +1,48 @@ | |||||
| /* http://meyerweb.com/eric/tools/css/reset/ | |||||
| v2.0 | 20110126 | |||||
| License: none (public domain) | |||||
| */ | |||||
| html, body, div, span, applet, object, iframe, | |||||
| h1, h2, h3, h4, h5, h6, p, blockquote, pre, | |||||
| a, abbr, acronym, address, big, cite, code, | |||||
| del, dfn, em, img, ins, kbd, q, s, samp, | |||||
| small, strike, strong, sub, sup, tt, var, | |||||
| b, u, i, center, | |||||
| dl, dt, dd, ol, ul, li, | |||||
| fieldset, form, label, legend, | |||||
| table, caption, tbody, tfoot, thead, tr, th, td, | |||||
| article, aside, canvas, details, embed, | |||||
| figure, figcaption, footer, header, hgroup, | |||||
| menu, nav, output, ruby, section, summary, | |||||
| time, mark, audio, video { | |||||
| margin: 0; | |||||
| padding: 0; | |||||
| border: 0; | |||||
| font-size: 100%; | |||||
| font: inherit; | |||||
| vertical-align: baseline; | |||||
| } | |||||
| /* HTML5 display-role reset for older browsers */ | |||||
| article, aside, details, figcaption, figure, | |||||
| footer, header, hgroup, menu, nav, section { | |||||
| display: block; | |||||
| } | |||||
| body { | |||||
| line-height: 1; | |||||
| } | |||||
| ol, ul { | |||||
| list-style: none; | |||||
| } | |||||
| blockquote, q { | |||||
| quotes: none; | |||||
| } | |||||
| blockquote:before, blockquote:after, | |||||
| q:before, q:after { | |||||
| content: ''; | |||||
| content: none; | |||||
| } | |||||
| table { | |||||
| border-collapse: collapse; | |||||
| border-spacing: 0; | |||||
| } | |||||
| @@ -1,9 +1,6 @@ | |||||
| <template> | <template> | ||||
| <div id="app"> | <div id="app"> | ||||
| <div> | |||||
| This is the (extremely early alpha of the) new Feast. If you're looking for the old version, <a href="https://classic.feast.crux.sexy">go here!</a> | |||||
| </div> | |||||
| <img alt="Feast logo" id="logo" src="./assets/feast.png"> | |||||
| <Header version="pre-alpha" /> | |||||
| <Combat :player="player" :enemy="enemy" /> | <Combat :player="player" :enemy="enemy" /> | ||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -11,12 +8,13 @@ | |||||
| <script lang="ts"> | <script lang="ts"> | ||||
| import { Component, Vue } from 'vue-property-decorator' | import { Component, Vue } from 'vue-property-decorator' | ||||
| import Combat from './components/Combat.vue' | import Combat from './components/Combat.vue' | ||||
| import Header from './components/Header.vue' | |||||
| import * as Creatures from '@/game/creatures' | import * as Creatures from '@/game/creatures' | ||||
| import { Creature, POV } from '@/game/entity' | import { Creature, POV } from '@/game/entity' | ||||
| @Component({ | @Component({ | ||||
| components: { | components: { | ||||
| Combat | |||||
| Combat, Header | |||||
| } | } | ||||
| }) | }) | ||||
| export default class App extends Vue { | export default class App extends Vue { | ||||
| @@ -28,6 +26,7 @@ export default class App extends Vue { | |||||
| this.player.perspective = POV.First | this.player.perspective = POV.First | ||||
| this.enemy = new Creatures.Wolf() | this.enemy = new Creatures.Wolf() | ||||
| console.log(this.player) | console.log(this.player) | ||||
| console.log(this.enemy) | |||||
| } | } | ||||
| } | } | ||||
| </script> | </script> | ||||
| @@ -35,6 +34,8 @@ export default class App extends Vue { | |||||
| <style> | <style> | ||||
| body, html { | body, html { | ||||
| background: #111; | background: #111; | ||||
| width: 100vw; | |||||
| height: 100vh; | |||||
| } | } | ||||
| #app { | #app { | ||||
| @@ -44,9 +45,5 @@ body, html { | |||||
| text-align: center; | text-align: center; | ||||
| color: #ddd; | color: #ddd; | ||||
| background: #111; | background: #111; | ||||
| margin-top: 60px; | |||||
| } | |||||
| #logo { | |||||
| width: 30vw; | |||||
| } | } | ||||
| </style> | </style> | ||||
| @@ -8,14 +8,14 @@ | |||||
| <div class="horiz-display"> | <div class="horiz-display"> | ||||
| <div> | <div> | ||||
| <h2>Your moves</h2> | <h2>Your moves</h2> | ||||
| <button @mouseleave="actionDescription= ''" @mouseover="actionDescription = action.desc" v-for="action in player.validActions(enemy)" :key="'player-' + action.name" v-on:click="log(action.execute(player, enemy))">{{action.name}}</button> | |||||
| <button @mouseleave="actionDescription= ''" @mouseover="actionDescription = action.desc" v-for="action in player.validActions(player)" :key="'player-' + action.name" v-on:click="log(action.execute(player, player))">{{action.name}}</button> | |||||
| <button class="combat-button" @mouseleave="actionDescription= ''" @mouseover="actionDescription = action.desc" v-for="action in player.validActions(enemy)" :key="'player-' + action.name" v-on:click="log(action.execute(player, enemy))">{{action.name}}</button> | |||||
| <button class="combat-button" @mouseleave="actionDescription= ''" @mouseover="actionDescription = action.desc" v-for="action in player.validActions(player)" :key="'player-' + action.name" v-on:click="log(action.execute(player, player))">{{action.name}}</button> | |||||
| <div>{{actionDescription}}</div> | <div>{{actionDescription}}</div> | ||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| <h2>Enemy moves</h2> | <h2>Enemy moves</h2> | ||||
| <button v-for="action in enemy.validActions(player)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, player))">{{action.name}}</button> | |||||
| <button v-for="action in enemy.validActions(enemy)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, enemy))">{{action.name}}</button> | |||||
| <button class="combat-button" v-for="action in enemy.validActions(player)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, player))">{{action.name}}</button> | |||||
| <button class="combat-button" v-for="action in enemy.validActions(enemy)" :key="'enemy-' + action.name" v-on:click="log(action.execute(enemy, enemy))">{{action.name}}</button> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div id="log"></div> | <div id="log"></div> | ||||
| @@ -71,4 +71,8 @@ a { | |||||
| display: flex; | display: flex; | ||||
| justify-content: center; | justify-content: center; | ||||
| } | } | ||||
| .combat-button { | |||||
| width: 100px; | |||||
| height: 100px; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -0,0 +1,29 @@ | |||||
| <template> | |||||
| <div id="header"> | |||||
| <div> | |||||
| This is the (extremely early alpha of the) new Feast. If you're looking for the old version, <a href="https://classic.feast.crux.sexy">go here!</a> | |||||
| </div> | |||||
| <div>Version: {{version}}</div> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Vue, Prop } from 'vue-property-decorator' | |||||
| @Component({}) | |||||
| export default class Header extends Vue { | |||||
| @Prop() version!: string | |||||
| } | |||||
| </script> | |||||
| <style scoped> | |||||
| #header { | |||||
| width: 100%; | |||||
| background: #222; | |||||
| top: 0%; | |||||
| padding-top: 32pt; | |||||
| padding-bottom: 32pt; | |||||
| } | |||||
| </style> | |||||
| @@ -39,46 +39,42 @@ export enum Stat { | |||||
| export type Stats = {[key in Stat]: number} | export type Stats = {[key in Stat]: number} | ||||
| export enum State { | |||||
| Normal = 'Normal', | |||||
| Grappled = 'Grappled', | |||||
| Grappling = 'Grappling', | |||||
| Eaten = 'Eaten' | |||||
| } | |||||
| export interface Combatant { | export interface Combatant { | ||||
| actions: Array<Action>; | actions: Array<Action>; | ||||
| } | } | ||||
| export abstract class Action { | export abstract class Action { | ||||
| allowed (user: Creature, target: Creature) { | |||||
| return this.userStates.has(user.state) && this.targetStates.has(target.state) | |||||
| } | |||||
| abstract allowed (user: Creature, target: Creature): boolean | |||||
| abstract execute(user: Creature, target: Creature): LogEntry | |||||
| abstract execute(user: Creature, target: Creature): LogEntry | |||||
| constructor (public name: string, public desc: string) { | |||||
| constructor (public name: string, public desc: string, public userStates: Set<State>, public targetStates: Set<State>) { | |||||
| } | |||||
| } | |||||
| toString (): string { | |||||
| return this.name | |||||
| } | |||||
| } | |||||
| toString (): string { | |||||
| return this.name | |||||
| } | |||||
| export interface Actionable { | |||||
| actions: Array<Action>; | |||||
| } | } | ||||
| abstract class SelfAction extends Action { | abstract class SelfAction extends Action { | ||||
| allowed (user: Creature, target: Creature) { | allowed (user: Creature, target: Creature) { | ||||
| if (user === target) { | |||||
| return super.allowed(user, target) | |||||
| } else { | |||||
| return false | |||||
| } | |||||
| return user === target | |||||
| } | } | ||||
| } | } | ||||
| abstract class PairAction extends Action { | abstract class PairAction extends Action { | ||||
| allowed (user: Creature, target: Creature) { | allowed (user: Creature, target: Creature) { | ||||
| if (user !== target) { | |||||
| return user !== target | |||||
| } | |||||
| } | |||||
| abstract class TogetherAction extends PairAction { | |||||
| allowed (user: Creature, target: Creature) { | |||||
| if (user.containedIn === target.containedIn) { | |||||
| return super.allowed(user, target) | return super.allowed(user, target) | ||||
| } else { | } else { | ||||
| return false | return false | ||||
| @@ -86,7 +82,7 @@ abstract class PairAction extends Action { | |||||
| } | } | ||||
| } | } | ||||
| export class AttackAction extends PairAction { | |||||
| export class AttackAction extends TogetherAction { | |||||
| protected lines: POVActionPicker = { | protected lines: POVActionPicker = { | ||||
| [POV.First]: { | [POV.First]: { | ||||
| [POV.First]: (user, target) => new LogLines('You bite...yourself?'), | [POV.First]: (user, target) => new LogLines('You bite...yourself?'), | ||||
| @@ -99,7 +95,7 @@ export class AttackAction extends PairAction { | |||||
| } | } | ||||
| constructor (protected damage: Damage) { | constructor (protected damage: Damage) { | ||||
| super('Attack', 'Attack the enemy', new Set([State.Normal]), new Set([State.Normal])) | |||||
| super('Attack', 'Attack the enemy') | |||||
| } | } | ||||
| execute (user: Creature, target: Creature): LogEntry { | execute (user: Creature, target: Creature): LogEntry { | ||||
| @@ -108,7 +104,7 @@ export class AttackAction extends PairAction { | |||||
| } | } | ||||
| } | } | ||||
| export class DevourAction extends PairAction { | |||||
| export class DevourAction extends TogetherAction { | |||||
| protected lines: POVActionPicker = { | protected lines: POVActionPicker = { | ||||
| [POV.First]: { | [POV.First]: { | ||||
| [POV.First]: (user, target) => new LogLines('You devour...yourself?'), | [POV.First]: (user, target) => new LogLines('You devour...yourself?'), | ||||
| @@ -121,11 +117,11 @@ export class DevourAction extends PairAction { | |||||
| } | } | ||||
| constructor (protected container: Container) { | constructor (protected container: Container) { | ||||
| super('Devour', 'Try to consume your foe', new Set([State.Normal]), new Set([State.Normal])) | |||||
| super('Devour', 'Try to consume your foe') | |||||
| this.name += ` (${container.name})` | |||||
| } | } | ||||
| execute (user: Creature, target: Creature): LogEntry { | execute (user: Creature, target: Creature): LogEntry { | ||||
| target.state = State.Eaten | |||||
| return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), this.container.consume(target)) | return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), this.container.consume(target)) | ||||
| } | } | ||||
| } | } | ||||
| @@ -142,48 +138,116 @@ export class StruggleAction extends PairAction { | |||||
| } | } | ||||
| } | } | ||||
| constructor () { | |||||
| super('Struggle', 'Try to escape your predator', new Set([State.Eaten]), new Set([State.Normal])) | |||||
| allowed (user: Creature, target: Creature) { | |||||
| if (user.containedIn === this.container) { | |||||
| return super.allowed(user, target) | |||||
| } else { | |||||
| return false | |||||
| } | |||||
| } | |||||
| constructor (public container: Container) { | |||||
| super('Struggle', 'Try to escape your predator') | |||||
| } | } | ||||
| execute (user: Creature, target: Creature): LogEntry { | execute (user: Creature, target: Creature): LogEntry { | ||||
| if (user.containedIn) { | if (user.containedIn) { | ||||
| user.state = State.Normal | |||||
| return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), user.containedIn.release(user)) | return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), user.containedIn.release(user)) | ||||
| } else { return new LogLines("The prey wasn't actually eaten...") } | } else { return new LogLines("The prey wasn't actually eaten...") } | ||||
| } | } | ||||
| } | } | ||||
| export class DigestAction extends SelfAction { | export class DigestAction extends SelfAction { | ||||
| protected lines: POVActionPicker = { | |||||
| [POV.First]: { | |||||
| [POV.First]: (user, target) => new LogLines('You rub your stomach'), | |||||
| [POV.Third]: (user, target) => new LogLines("You can't digest for other people...") | |||||
| }, | |||||
| [POV.Third]: { | |||||
| [POV.First]: (user, target) => new LogLines("Other people can't digest for you..."), | |||||
| [POV.Third]: (user, target) => new LogLines(user.name.capital + ' rubs ' + user.pronouns.possessive + ' gut.') | |||||
| } | |||||
| protected lines: POVActionPicker = { | |||||
| [POV.First]: { | |||||
| [POV.First]: (user, target) => new LogLines('You rub your stomach'), | |||||
| [POV.Third]: (user, target) => new LogLines("You can't digest for other people...") | |||||
| }, | |||||
| [POV.Third]: { | |||||
| [POV.First]: (user, target) => new LogLines("Other people can't digest for you..."), | |||||
| [POV.Third]: (user, target) => new LogLines(user.name.capital + ' rubs ' + user.pronouns.possessive + ' gut.') | |||||
| } | } | ||||
| } | |||||
| allowed (user: Creature, target: Creature) { | |||||
| if (user.containers.some(container => { | |||||
| return container.contents.length > 0 | |||||
| })) { | |||||
| return super.allowed(user, target) | |||||
| } else { | |||||
| return false | |||||
| } | |||||
| allowed (user: Creature, target: Creature) { | |||||
| if (this.container.owner === user && this.container.contents.length > 0) { | |||||
| return super.allowed(user, target) | |||||
| } else { | |||||
| return false | |||||
| } | } | ||||
| } | |||||
| constructor (protected container: Container) { | |||||
| super('Digest', 'Digest all of your current prey') | |||||
| this.name += ` (${container.name})` | |||||
| } | |||||
| execute (user: Creature, target: Creature): LogEntry { | |||||
| const results = new CompositeLog(...user.containers.map(container => container.tick(60))) | |||||
| return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results) | |||||
| } | |||||
| } | |||||
| constructor () { | |||||
| super('Digest', 'Digest all of your current prey', new Set([State.Normal]), new Set([State.Normal])) | |||||
| export class ReleaseAction extends PairAction { | |||||
| protected lines: POVActionPicker = { | |||||
| [POV.First]: { | |||||
| [POV.First]: (user, target) => new LogLines(`You can't release yourself`), | |||||
| [POV.Third]: (user, target) => new LogLines(`You hork up ${target.name}}`) | |||||
| }, | |||||
| [POV.Third]: { | |||||
| [POV.First]: (user, target) => new LogLines(`${user.name.capital} horks you up`), | |||||
| [POV.Third]: (user, target) => new LogLines(`${user.name.capital} horks up ${target.name.capital}`) | |||||
| } | } | ||||
| } | |||||
| execute (user: Creature, target: Creature): LogEntry { | |||||
| const results = new CompositeLog(...user.containers.map(container => container.tick(60))) | |||||
| return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results) | |||||
| allowed (user: Creature, target: Creature) { | |||||
| if (target.containedIn === this.container) { | |||||
| return super.allowed(user, target) | |||||
| } else { | |||||
| return false | |||||
| } | } | ||||
| } | |||||
| constructor (protected container: Container) { | |||||
| super('Release', 'Release one of your prey') | |||||
| this.name += ` (${container.name})` | |||||
| } | |||||
| execute (user: Creature, target: Creature): LogEntry { | |||||
| const results = this.container.release(target) | |||||
| return new CompositeLog(this.lines[user.perspective][target.perspective](user, target), results) | |||||
| } | |||||
| } | |||||
| export class TransferAction extends PairAction { | |||||
| protected lines: POVActionPicker = { | |||||
| [POV.First]: { | |||||
| [POV.First]: (user, target) => new LogLines(), | |||||
| [POV.Third]: (user, target) => new LogLines(`You push your prey from your ${this.from.name} to your ${this.to.name}`) | |||||
| }, | |||||
| [POV.Third]: { | |||||
| [POV.First]: (user, target) => new LogLines(`You're shoved from ${user.name}'s ${this.from.name} to their ${this.to.name}`), | |||||
| [POV.Third]: (user, target) => new LogLines(`${user.name.capital} pushes ${target.name} from their ${this.from.name} to their ${this.to.name}`) | |||||
| } | |||||
| } | |||||
| allowed (user: Creature, target: Creature) { | |||||
| if (target.containedIn === this.from) { | |||||
| return super.allowed(user, target) | |||||
| } else { | |||||
| return false | |||||
| } | |||||
| } | |||||
| constructor (protected from: Container, protected to: Container) { | |||||
| super('Transfer', `Shove your prey from your ${from.name} to your ${to.name}`) | |||||
| } | |||||
| execute (user: Creature, target: Creature): LogEntry { | |||||
| this.from.release(target) | |||||
| this.to.consume(target) | |||||
| return this.lines[user.perspective][target.perspective](user, target) | |||||
| } | |||||
| } | } | ||||
| export interface CombatTest { | export interface CombatTest { | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { Creature, POV } from '../entity' | import { Creature, POV } from '../entity' | ||||
| import { Stat, Damage, DamageType, AttackAction, DevourAction, StruggleAction, DigestAction } from '../combat' | |||||
| import { Stat, Damage, DamageType, AttackAction, StruggleAction, TransferAction } from '../combat' | |||||
| import { MalePronouns, ImproperNoun } from '../language' | import { MalePronouns, ImproperNoun } from '../language' | ||||
| import { VoreType, Stomach } from '../vore' | |||||
| import { VoreType, Stomach, Bowels } from '../vore' | |||||
| import { LogLines } from '../interface' | import { LogLines } from '../interface' | ||||
| class BiteAction extends AttackAction { | class BiteAction extends AttackAction { | ||||
| @@ -21,8 +21,10 @@ export class Wolf extends Creature { | |||||
| const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush })) | const stomach = new Stomach(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush })) | ||||
| this.containers.push(stomach) | this.containers.push(stomach) | ||||
| this.actions.push(new DevourAction(stomach)) | |||||
| this.actions.push(new StruggleAction()) | |||||
| this.actions.push(new DigestAction()) | |||||
| const bowels = new Bowels(this, 50, new Damage({ amount: 50, type: DamageType.Acid }, { amount: 500, type: DamageType.Crush })) | |||||
| this.containers.push(bowels) | |||||
| this.actions.push(new TransferAction(bowels, stomach)) | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { DamageType, Damage, Combatant, Stats, State, Action } from './combat' | |||||
| import { DamageType, Damage, Combatant, Stats, Action } from './combat' | |||||
| import { Noun, Pronoun } from './language' | import { Noun, Pronoun } from './language' | ||||
| import { Pred, Prey, Container, VoreType } from './vore' | import { Pred, Prey, Container, VoreType } from './vore' | ||||
| @@ -24,7 +24,6 @@ export class Creature implements Mortal, Pred, Prey, Combatant { | |||||
| maxHealth = 100 | maxHealth = 100 | ||||
| resistances: Map<DamageType, number> = new Map() | resistances: Map<DamageType, number> = new Map() | ||||
| perspective: POV = POV.Third | perspective: POV = POV.Third | ||||
| state: State = State.Normal | |||||
| containers: Array<Container> = [] | containers: Array<Container> = [] | ||||
| actions: Array<Action> = []; | actions: Array<Action> = []; | ||||
| private baseBulk: number; | private baseBulk: number; | ||||
| @@ -55,7 +54,12 @@ export class Creature implements Mortal, Pred, Prey, Combatant { | |||||
| } | } | ||||
| validActions (target: Creature): Array<Action> { | validActions (target: Creature): Array<Action> { | ||||
| return this.actions.filter(action => { | |||||
| let choices = this.actions.concat(this.containers.flatMap(container => container.actions)) | |||||
| if (this.containedIn !== null) { | |||||
| choices = choices.concat(this.containedIn.actions) | |||||
| } | |||||
| return choices.filter(action => { | |||||
| return action.allowed(this, target) | return action.allowed(this, target) | ||||
| }) | }) | ||||
| } | } | ||||
| @@ -1,7 +1,6 @@ | |||||
| import { Entity, Mortal, POV } from './entity' | import { Entity, Mortal, POV } from './entity' | ||||
| import { Damage } from './combat' | |||||
| import { Damage, Actionable, Action, DevourAction, DigestAction, ReleaseAction, StruggleAction } from './combat' | |||||
| import { LogLines, LogEntry, CompositeLog } from './interface' | import { LogLines, LogEntry, CompositeLog } from './interface' | ||||
| import { ProperNoun } from './language' | |||||
| export enum VoreType {Oral} | export enum VoreType {Oral} | ||||
| export interface Prey extends Mortal { | export interface Prey extends Mortal { | ||||
| @@ -15,21 +14,23 @@ export interface Pred extends Entity { | |||||
| containers: Array<Container>; | containers: Array<Container>; | ||||
| } | } | ||||
| export interface Container { | |||||
| name: string; | |||||
| voreTypes: Set<VoreType>; | |||||
| contents: Array<Prey>; | |||||
| capacity: number; | |||||
| fullness: number; | |||||
| canTake: (prey: Prey) => boolean; | |||||
| consume: (prey: Prey) => LogEntry; | |||||
| release: (prey: Prey) => LogEntry; | |||||
| struggle: (prey: Prey) => LogEntry; | |||||
| tick: (dt: number) => LogEntry; | |||||
| describe: () => LogEntry; | |||||
| digest: (prey: Prey) => LogEntry; | |||||
| absorb: (prey: Prey) => LogEntry; | |||||
| dispose: (preys: Prey[]) => LogEntry; | |||||
| export interface Container extends Actionable { | |||||
| name: string; | |||||
| owner: Pred; | |||||
| voreTypes: Set<VoreType>; | |||||
| contents: Array<Prey>; | |||||
| capacity: number; | |||||
| fullness: number; | |||||
| canTake: (prey: Prey) => boolean; | |||||
| consume: (prey: Prey) => LogEntry; | |||||
| release: (prey: Prey) => LogEntry; | |||||
| struggle: (prey: Prey) => LogEntry; | |||||
| tick: (dt: number) => LogEntry; | |||||
| describe: () => LogEntry; | |||||
| digest: (prey: Prey) => LogEntry; | |||||
| absorb: (prey: Prey) => LogEntry; | |||||
| dispose: (preys: Prey[]) => LogEntry; | |||||
| actions: Array<Action>; | |||||
| } | } | ||||
| abstract class NormalContainer implements Container { | abstract class NormalContainer implements Container { | ||||
| @@ -112,14 +113,23 @@ abstract class NormalContainer implements Container { | |||||
| return new LogLines('GLORP') | return new LogLines('GLORP') | ||||
| } | } | ||||
| constructor (public name: string, protected owner: Pred, public voreTypes: Set<VoreType>, public capacity: number, private damage: Damage) { | |||||
| actions: Array<Action> | |||||
| constructor (public name: string, public owner: Pred, public voreTypes: Set<VoreType>, public capacity: number, private damage: Damage) { | |||||
| this.contents = [] | this.contents = [] | ||||
| this.actions = [] | |||||
| } | } | ||||
| } | } | ||||
| export class Stomach extends NormalContainer { | export class Stomach extends NormalContainer { | ||||
| constructor (owner: Pred, capacity: number, damage: Damage) { | constructor (owner: Pred, capacity: number, damage: Damage) { | ||||
| super('Stomach', owner, new Set([VoreType.Oral]), capacity, damage) | super('Stomach', owner, new Set([VoreType.Oral]), capacity, damage) | ||||
| this.actions.push(new DevourAction(this)) | |||||
| this.actions.push(new DigestAction(this)) | |||||
| this.actions.push(new ReleaseAction(this)) | |||||
| this.actions.push(new StruggleAction(this)) | |||||
| } | } | ||||
| consume (prey: Prey): LogEntry { | consume (prey: Prey): LogEntry { | ||||
| @@ -172,3 +182,64 @@ export class Stomach extends NormalContainer { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| export class Bowels extends NormalContainer { | |||||
| constructor (owner: Pred, capacity: number, damage: Damage) { | |||||
| super('Bowels', owner, new Set([VoreType.Oral]), capacity, damage) | |||||
| this.actions.push(new DevourAction(this)) | |||||
| this.actions.push(new DigestAction(this)) | |||||
| this.actions.push(new ReleaseAction(this)) | |||||
| this.actions.push(new StruggleAction(this)) | |||||
| } | |||||
| consume (prey: Prey): LogEntry { | |||||
| super.consume(prey) | |||||
| const predPOV = this.owner.perspective | |||||
| const preyPOV = prey.perspective | |||||
| if (predPOV === POV.First && preyPOV === POV.Third) { | |||||
| return new LogLines(prey.name.capital + ' slides down into your bowels') | |||||
| } else if (predPOV === POV.Third && preyPOV === POV.First) { | |||||
| return new LogLines(this.owner.name.capital + "'s guts swell as you slush down into " + this.owner.pronouns.possessive + ' bowels') | |||||
| } else if (predPOV === POV.Third && preyPOV === POV.Third) { | |||||
| return new LogLines(this.owner.name.capital + "'s belly fills with the struggling form of " + prey.name) | |||||
| } else { | |||||
| return new LogLines('FIX ME!') | |||||
| } | |||||
| } | |||||
| digest (prey: Prey): LogEntry { | |||||
| super.digest(prey) | |||||
| const predPOV = this.owner.perspective | |||||
| const preyPOV = prey.perspective | |||||
| if (predPOV === POV.First && preyPOV === POV.Third) { | |||||
| return new LogLines('Your bowels finishes off ' + prey.name) | |||||
| } else if (predPOV === POV.Third && preyPOV === POV.First) { | |||||
| return new LogLines(this.owner.name.capital + ' digests you') | |||||
| } else if (predPOV === POV.Third && preyPOV === POV.Third) { | |||||
| return new LogLines(this.owner.name.capital + ' finishes digesting ' + prey.name) | |||||
| } else { | |||||
| return new LogLines('FIX ME!') | |||||
| } | |||||
| } | |||||
| absorb (prey: Prey): LogEntry { | |||||
| super.absorb(prey) | |||||
| const predPOV = this.owner.perspective | |||||
| const preyPOV = prey.perspective | |||||
| if (predPOV === POV.First && preyPOV === POV.Third) { | |||||
| return new LogLines("Your bowels melts down what's left of " + prey.name) | |||||
| } else if (predPOV === POV.Third && preyPOV === POV.First) { | |||||
| return new LogLines(this.owner.name.capital + ' finishes absorbing you') | |||||
| } else if (predPOV === POV.Third && preyPOV === POV.Third) { | |||||
| return new LogLines(this.owner.name.capital + ' fully absorbs ' + prey.name) | |||||
| } else { | |||||
| return new LogLines('FIX ME!') | |||||
| } | |||||
| } | |||||
| } | |||||