Also cleans up some old duplicate code that I made, somehow.master
| @@ -1,39 +0,0 @@ | |||||
| <template> | |||||
| <button class="action-button" @click="execute"> | |||||
| {{ action.name }} | |||||
| </button> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Action } from '@/game/combat' | |||||
| import { Creature } from '@/game/entity' | |||||
| @Component({}) | |||||
| export default class ActionButton extends Vue { | |||||
| @Prop() | |||||
| action!: Action | |||||
| @Prop() | |||||
| user!: Creature | |||||
| @Prop() | |||||
| target!: Creature | |||||
| @Emit("execute") | |||||
| execute () { | |||||
| this.$emit('executed', this.action.execute(this.user, this.target)) | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style scoped> | |||||
| .action-button { | |||||
| width: 100px; | |||||
| height: 100px; | |||||
| } | |||||
| </style> | |||||
| @@ -1,237 +0,0 @@ | |||||
| <template> | |||||
| <div class="combat-layout"> | |||||
| <Statblock class="player-stats" :subject="player" /> | |||||
| <Statblock class="enemy-stats" :subject="enemy" /> | |||||
| <div id="log"> | |||||
| </div> | |||||
| <div class="player-actions"> | |||||
| <h2>Your moves</h2> | |||||
| <div class="vert-display"> | |||||
| <ActionButton @executed="executed" v-for="action in player.validActions(enemy)" :key="'player' + action.name" :action="action" :user="player" :target="enemy" /> | |||||
| <ActionButton @executed="executed" v-for="action in player.validActions(player)" :key="'player' + action.name" :action="action" :user="player" :target="enemy" /> | |||||
| </div> | |||||
| <div>{{actionDescription}}</div> | |||||
| </div> | |||||
| <div class="enemy-actions"> | |||||
| <h2>Enemy moves</h2> | |||||
| <div class="vert-display"> | |||||
| <ActionButton @executed="executedEnemy" v-for="action in enemy.validActions(player)" :key="'player' + action.name" :action="action" :user="enemy" :target="player" /> | |||||
| <ActionButton @executed="executedEnemy" v-for="action in enemy.validActions(enemy)" :key="'player' + action.name" :action="action" :user="enemy" :target="player" /> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Creature, POV } from '@/game/entity' | |||||
| import { LogEntry } from '@/game/interface' | |||||
| import Statblock from './Statblock.vue' | |||||
| import ActionButton from './ActionButton.vue' | |||||
| @Component( | |||||
| { | |||||
| components: { Statblock, ActionButton } | |||||
| } | |||||
| ) | |||||
| export default class Combat extends Vue { | |||||
| @Prop({ type: Creature, required: true }) | |||||
| player!: Creature | |||||
| @Prop({ type: Creature, required: true }) | |||||
| enemy!: Creature | |||||
| actionDescription = '' | |||||
| constructor () { | |||||
| super() | |||||
| } | |||||
| @Emit("executed") | |||||
| executed (entry: LogEntry) { | |||||
| const log = document.querySelector("#log") | |||||
| if (log !== null) { | |||||
| const holder = document.createElement("div") | |||||
| entry.render().forEach(element => { | |||||
| holder.appendChild(element) | |||||
| }) | |||||
| holder.classList.add("player-move") | |||||
| log.appendChild(holder) | |||||
| log.scrollTo({ top: 10000000000, left: 0 }) | |||||
| } | |||||
| } | |||||
| @Emit("executedEnemy") | |||||
| executedEnemy (entry: LogEntry) { | |||||
| const log = document.querySelector("#log") | |||||
| if (log !== null) { | |||||
| const holder = document.createElement("div") | |||||
| entry.render().forEach(element => { | |||||
| holder.appendChild(element) | |||||
| }) | |||||
| holder.classList.add("enemy-move") | |||||
| log.appendChild(holder) | |||||
| log.scrollTo({ top: 10000000000, left: 0 }) | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||||
| <style scoped> | |||||
| .combat-layout { | |||||
| display: grid; | |||||
| grid-template-rows: 20% [main-row-start] 30% 30% [main-row-end] 20%; | |||||
| grid-template-columns: 20% [main-col-start] 60% [main-col-end] 20%; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| .player-stats { | |||||
| grid-area: 1 / 1 / 2 / 2 | |||||
| } | |||||
| .enemy-stats { | |||||
| grid-area: 1 / 3 / 2 / 4; | |||||
| } | |||||
| #log { | |||||
| grid-area: main-row-start / main-col-start / main-row-end / main-col-end; | |||||
| overflow-y: auto; | |||||
| font-size: 16pt; | |||||
| width: 100%; | |||||
| max-height: 100%; | |||||
| align-self: end; | |||||
| } | |||||
| .player-actions { | |||||
| grid-area: 3 / 1 / 4 / 2; | |||||
| } | |||||
| .enemy-actions { | |||||
| grid-area: 3 / 3 / 4 / 4; | |||||
| } | |||||
| h3 { | |||||
| margin: 40px 0 0; | |||||
| } | |||||
| ul { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| li { | |||||
| display: inline-block; | |||||
| margin: 0 10px; | |||||
| } | |||||
| a { | |||||
| color: #42b983; | |||||
| } | |||||
| .horiz-display { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| } | |||||
| .vert-display { | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| align-items: center; | |||||
| } | |||||
| </style> | |||||
| <style> | |||||
| .log-damage { | |||||
| font-weight: bold; | |||||
| } | |||||
| #log > div { | |||||
| color: #888; | |||||
| padding-top: 8pt; | |||||
| padding-bottom: 8pt; | |||||
| } | |||||
| div.player-move, | |||||
| div.enemy-move { | |||||
| color: #888; | |||||
| } | |||||
| div.player-move { | |||||
| text-align: start; | |||||
| margin-left: 48pt; | |||||
| } | |||||
| div.enemy-move { | |||||
| text-align: end; | |||||
| margin-right: 48pt; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(7) { | |||||
| color: #988; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(6) { | |||||
| padding-top: 8pt; | |||||
| color: #a88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(5) { | |||||
| padding-top: 16pt; | |||||
| color: #b88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(4) { | |||||
| padding-top: 24pt; | |||||
| color: #c88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(3) { | |||||
| padding-top: 32pt; | |||||
| color: #d88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(2) { | |||||
| padding-top: 40pt; | |||||
| color: #e88; | |||||
| } | |||||
| #log > div.enemy-move:nth-last-child(1) { | |||||
| padding-top: 48pt; | |||||
| color: #f88; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(7) { | |||||
| color: #898; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(6) { | |||||
| padding-top: 8pt; | |||||
| color: #8a8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(5) { | |||||
| padding-top: 16pt; | |||||
| color: #8b8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(4) { | |||||
| padding-top: 24pt; | |||||
| color: #8c8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(3) { | |||||
| padding-top: 32pt; | |||||
| color: #8d8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(2) { | |||||
| padding-top: 40pt; | |||||
| color: #8e8; | |||||
| } | |||||
| #log > div.player-move:nth-last-child(1) { | |||||
| padding-top: 48pt; | |||||
| color: #8f8; | |||||
| } | |||||
| </style> | |||||
| @@ -1,42 +0,0 @@ | |||||
| <template> | |||||
| <div v-if="container.fullness > 0" class="statblock"> | |||||
| <h3>{{container.name}}</h3> | |||||
| <div>Fullness: {{container.fullness}} / {{container.capacity}}</div> | |||||
| <div v-for="(prey, index) in container.contents" :key="'prey-' + index">{{prey.name}}</div> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Creature, POV } from '@/game/entity' | |||||
| import { Stats, Stat } from '@/game/combat' | |||||
| import { Container } from '@/game/vore' | |||||
| @Component | |||||
| export default class ContainerView extends Vue { | |||||
| @Prop({ required: true }) | |||||
| container!: Container | |||||
| constructor () { | |||||
| super() | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||||
| <style scoped> | |||||
| h3 { | |||||
| margin: 40px 0 0; | |||||
| } | |||||
| ul { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| li { | |||||
| display: inline-block; | |||||
| margin: 0 10px; | |||||
| } | |||||
| a { | |||||
| color: #42b983; | |||||
| } | |||||
| </style> | |||||
| @@ -1,29 +0,0 @@ | |||||
| <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> | |||||
| @@ -1,54 +0,0 @@ | |||||
| <template> | |||||
| <div class="statblock"> | |||||
| <h2 v-if="subject.perspective === firstperson">You</h2> | |||||
| <h2 v-if="subject.perspective !== firstperson">{{subject.name.all.capital}}</h2> | |||||
| <div>Health: {{subject.health.toFixed(0)}} / {{subject.maxHealth.toFixed(0)}}</div> | |||||
| <div v-for="stat in Object.keys(subject.stats)" v-bind:key="stat">{{stat}}: {{subject.stats[stat]}}</div> | |||||
| <div>Status: {{subject.status}}</div> | |||||
| <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | |||||
| </div> | |||||
| </template> | |||||
| <script lang="ts"> | |||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||||
| import { Creature, POV } from '@/game/entity' | |||||
| import { Stats, Stat } from '@/game/combat' | |||||
| import ContainerView from './ContainerView.vue' | |||||
| @Component({ | |||||
| components: { | |||||
| ContainerView | |||||
| } | |||||
| }) | |||||
| export default class Statblock extends Vue { | |||||
| @Prop({ type: Creature, required: true }) | |||||
| subject!: Creature | |||||
| firstperson: POV = POV.First | |||||
| constructor () { | |||||
| super() | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||||
| <style scoped> | |||||
| h2 { | |||||
| margin-bottom: 16pt; | |||||
| font-size: 200%; | |||||
| } | |||||
| ul { | |||||
| list-style-type: none; | |||||
| padding: 0; | |||||
| } | |||||
| li { | |||||
| display: inline-block; | |||||
| margin: 0 10px; | |||||
| } | |||||
| a { | |||||
| color: #42b983; | |||||
| } | |||||
| .statblock { | |||||
| margin: 16px; | |||||
| } | |||||
| </style> | |||||
| @@ -8,7 +8,7 @@ | |||||
| <script lang="ts"> | <script lang="ts"> | ||||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | ||||
| import { Action } from '@/game/combat' | |||||
| import { Action, GroupAction } from '@/game/combat' | |||||
| import { Creature } from '@/game/entity' | import { Creature } from '@/game/entity' | ||||
| @Component({}) | @Component({}) | ||||
| @@ -22,14 +22,27 @@ export default class ActionButton extends Vue { | |||||
| @Prop() | @Prop() | ||||
| target!: Creature | target!: Creature | ||||
| @Prop() | |||||
| combatants!: Array<Creature> | |||||
| @Emit("execute") | @Emit("execute") | ||||
| execute () { | execute () { | ||||
| this.$emit('executed', this.action.execute(this.user, this.target)) | |||||
| if ((this.action as GroupAction).executeGroup !== undefined) { | |||||
| const action = (this.action as GroupAction) | |||||
| this.$emit('executed', (this.action as GroupAction).executeGroup(this.user, action.allowedGroup(this.user, this.combatants))) | |||||
| } else { | |||||
| this.$emit('executed', this.action.execute(this.user, this.target)) | |||||
| } | |||||
| } | } | ||||
| @Emit("describe") | @Emit("describe") | ||||
| describe () { | describe () { | ||||
| this.$emit('described', this.action.describe(this.user, this.target)) | |||||
| if ((this.action as GroupAction).describeGroup !== undefined) { | |||||
| const action = (this.action as GroupAction) | |||||
| this.$emit('described', action.describeGroup(this.user, action.allowedGroup(this.user, this.combatants))) | |||||
| } else { | |||||
| this.$emit('described', this.action.describe(this.user, this.target)) | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,28 +1,30 @@ | |||||
| <template> | <template> | ||||
| <div class="combat-layout"> | <div class="combat-layout"> | ||||
| <div id="left-stats"> | |||||
| <Statblock v-on:click.native="left = combatant" class="left-stats" :data-active="combatant === left" v-for="combatant in combatants.filter(c => c.side == Side.Heroes && c.vigors.Health > 0)" v-bind:key="combatant.name" :subject="combatant" /> | |||||
| <div class="stat-column" id="left-stats"> | |||||
| <Statblock v-on:click.native="left = combatant" class="left-stats" :data-active="combatant === left" v-for="combatant in combatants.filter(c => c.side == Side.Heroes && !c.digested)" v-bind:key="combatant.name" :subject="combatant" /> | |||||
| </div> | </div> | ||||
| <div id="right-stats"> | |||||
| <Statblock v-on:click.native="right = combatant" class="right-stats" :data-active="combatant === right" v-for="combatant in combatants.filter(c => c.side == Side.Monsters && c.vigors.Health > 0)" v-bind:key="combatant.name" :subject="combatant" /> | |||||
| <div class="stat-column" id="right-stats"> | |||||
| <Statblock v-on:click.native="right = combatant" class="right-stats" :data-active="combatant === right" v-for="combatant in combatants.filter(c => c.side == Side.Monsters && !c.digested)" v-bind:key="combatant.name" :subject="combatant" /> | |||||
| </div> | </div> | ||||
| <div id="log"> | <div id="log"> | ||||
| </div> | </div> | ||||
| <div class="left-actions"> | <div class="left-actions"> | ||||
| <div class="vert-display"> | <div class="vert-display"> | ||||
| <h2>Moves</h2> | <h2>Moves</h2> | ||||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(right)" :key="'left' + action.name" :action="action" :user="left" :target="right" /> | |||||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(right)" :key="'left' + action.name" :action="action" :user="left" :target="right" :combatants="combatants" /> | |||||
| <h2>Self-moves</h2> | <h2>Self-moves</h2> | ||||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(left)" :key="'left' + action.name" :action="action" :user="left" :target="right" /> | |||||
| <ActionButton @described="described" @executed="executedLeft" v-for="action in left.validActions(left)" :key="'left' + action.name" :action="action" :user="left" :target="right" :combatants="combatants" /> | |||||
| </div> | </div> | ||||
| <div>{{actionDescription}}</div> | <div>{{actionDescription}}</div> | ||||
| </div> | </div> | ||||
| <div class="right-actions"> | <div class="right-actions"> | ||||
| <div class="vert-display"> | <div class="vert-display"> | ||||
| <h2>Group-moves</h2> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validGroupActions(combatants)" :key="'right' + action.name" :action="action" :user="right" :target="left" :combatants="combatants" /> | |||||
| <h2>Moves</h2> | <h2>Moves</h2> | ||||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(left)" :key="'right' + action.name" :action="action" :user="right" :target="left" /> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(left)" :key="'right' + action.name" :action="action" :user="right" :target="left" :combatants="combatants" /> | |||||
| <h2>Self-moves</h2> | <h2>Self-moves</h2> | ||||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(right)" :key="'right' + action.name" :action="action" :user="right" :target="left" /> | |||||
| <ActionButton @described="described" @executed="executedRight" v-for="action in right.validActions(right)" :key="'right' + action.name" :action="action" :user="right" :target="left" :combatants="combatants" /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div id="action-desc"> | <div id="action-desc"> | ||||
| @@ -143,12 +145,16 @@ export default class Combat extends Vue { | |||||
| grid-area: 2 / 3 / 4 / 4; | grid-area: 2 / 3 / 4 / 4; | ||||
| } | } | ||||
| .stat-column { | |||||
| overflow-y: auto; | |||||
| } | |||||
| .left-actions { | .left-actions { | ||||
| grid-area: 5 / 1 / 6 / 2; | |||||
| grid-area: 4 / 1 / 6 / 2; | |||||
| } | } | ||||
| .right-actions { | .right-actions { | ||||
| grid-area: 5 / 3 / 6 / 4; | |||||
| grid-area: 4 / 3 / 6 / 4; | |||||
| } | } | ||||
| #action-desc { | #action-desc { | ||||
| @@ -33,6 +33,7 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div>Status: {{subject.status}}</div> | <div>Status: {{subject.status}}</div> | ||||
| <button @click="subject.perspective = firstperson">First-person</button> | |||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -207,6 +207,7 @@ export enum Side { | |||||
| */ | */ | ||||
| export interface Combatant { | export interface Combatant { | ||||
| actions: Array<Action>; | actions: Array<Action>; | ||||
| groupActions: Array<GroupAction>; | |||||
| side: Side; | side: Side; | ||||
| } | } | ||||
| @@ -218,7 +219,7 @@ export abstract class Action { | |||||
| return this.conditions.every(cond => cond.allowed(user, target)) | return this.conditions.every(cond => cond.allowed(user, target)) | ||||
| } | } | ||||
| abstract execute(user: Creature, target: Creature): LogEntry | |||||
| abstract execute (user: Creature, target: Creature): LogEntry | |||||
| abstract describe (user: Creature, target: Creature): LogEntry | abstract describe (user: Creature, target: Creature): LogEntry | ||||
| @@ -271,3 +272,15 @@ export abstract class TogetherAction extends PairAction { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| export abstract class GroupAction extends PairAction { | |||||
| allowedGroup (user: Creature, targets: Array<Creature>): Array<Creature> { | |||||
| return targets.filter(target => this.allowed(user, target)) | |||||
| } | |||||
| executeGroup (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return new LogLines(...targets.map(target => this.execute(user, target))) | |||||
| } | |||||
| abstract describeGroup (user: Creature, targets: Array<Creature>): LogEntry | |||||
| } | |||||
| @@ -25,3 +25,9 @@ export class DrainedVigorCondition implements Condition { | |||||
| } | } | ||||
| } | } | ||||
| export class TogetherCondition implements Condition { | |||||
| allowed (user: Creature, target: Creature): boolean { | |||||
| return user.containedIn === target.containedIn | |||||
| } | |||||
| } | |||||
| @@ -1,10 +1,12 @@ | |||||
| import { Creature, POV, Entity } from '../entity' | import { Creature, POV, Entity } from '../entity' | ||||
| import { Stat, Damage, DamageType, ConstantDamageFormula, Vigor, Side, PairAction, CombatTest } from '../combat' | |||||
| import { MalePronouns, ImproperNoun, POVPair, POVPairArgs, ProperNoun, TheyPronouns } from '../language' | |||||
| import { Stat, Damage, DamageType, ConstantDamageFormula, Vigor, Side, PairAction, CombatTest, GroupAction } from '../combat' | |||||
| import { MalePronouns, ImproperNoun, POVPair, POVPairArgs, ProperNoun, TheyPronouns, FemalePronouns } from '../language' | |||||
| import { LogLine, LogLines, LogEntry } from '../interface' | import { LogLine, LogLines, LogEntry } from '../interface' | ||||
| import { VoreType, Stomach, Bowels } from '../vore' | |||||
| import { VoreType, Stomach, Bowels, Container } from '../vore' | |||||
| import { StatTest } from '../combat/tests' | import { StatTest } from '../combat/tests' | ||||
| import { AttackAction, TransferAction, FeedAction } from '../combat/actions' | import { AttackAction, TransferAction, FeedAction } from '../combat/actions' | ||||
| import { TogetherCondition } from '../combat/conditions' | |||||
| import { InstantKill } from '../combat/effects' | |||||
| class BiteAction extends AttackAction { | class BiteAction extends AttackAction { | ||||
| constructor () { | constructor () { | ||||
| @@ -13,17 +15,77 @@ class BiteAction extends AttackAction { | |||||
| } | } | ||||
| } | } | ||||
| class StompAction extends GroupAction { | |||||
| lines: POVPair<Creature, Creature> = new POVPair([ | |||||
| [[POV.First, POV.Third], (user: Creature, target: Creature) => new LogLine(`You flatten ${target.name} under your foot!`)], | |||||
| [[POV.Third, POV.First], (user: Creature, target: Creature) => new LogLine(`${user.name.capital} flattens you under ${user.pronouns.possessive} foot!`)], | |||||
| [[POV.Third, POV.Third], (user: Creature, target: Creature) => new LogLine(`${user.name.capital} flattens ${target.name} under ${user.pronouns.possessive} foot!`)] | |||||
| ]) | |||||
| execute (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLines(this.lines.run(user, target), new InstantKill().apply(target)) | |||||
| } | |||||
| describe (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLine('Stomp one sucker') | |||||
| } | |||||
| describeGroup (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return new LogLine('Stomp all ', targets.length.toString(), ' of \'em!') | |||||
| } | |||||
| constructor () { | |||||
| super('Stomp', 'STOMP!', [ | |||||
| new TogetherCondition() | |||||
| ]) | |||||
| } | |||||
| } | |||||
| class DevourAllAction extends GroupAction { | |||||
| lines: POVPair<Creature, Creature> = new POVPair([ | |||||
| [[POV.First, POV.Third], (user: Creature, target: Creature) => new LogLine(`You scoop up ${target.name}!`)], | |||||
| [[POV.Third, POV.First], (user: Creature, target: Creature) => new LogLine(`${user.name.capital} scoops you up!`)], | |||||
| [[POV.Third, POV.Third], (user: Creature, target: Creature) => new LogLine(`${user.name.capital} scoops ${target.name} up!`)] | |||||
| ]) | |||||
| execute (user: Creature, target: Creature): LogEntry { | |||||
| this.container.consume(target) | |||||
| return new LogLines(this.lines.run(user, target)) | |||||
| } | |||||
| describe (user: Creature, target: Creature): LogEntry { | |||||
| return new LogLine('Stomp one sucker') | |||||
| } | |||||
| executeGroup (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return new LogLines(...targets.map(target => this.execute(user, target)).concat( | |||||
| [new LogLine('GULP!')] | |||||
| )) | |||||
| } | |||||
| describeGroup (user: Creature, targets: Array<Creature>): LogEntry { | |||||
| return new LogLine('Eat all ', targets.length.toString(), ' of \'em!') | |||||
| } | |||||
| constructor (private container: Container) { | |||||
| super('Devour All', 'GULP!', [ | |||||
| new TogetherCondition() | |||||
| ]) | |||||
| } | |||||
| } | |||||
| export class Withers extends Creature { | export class Withers extends Creature { | ||||
| constructor () { | constructor () { | ||||
| super( | super( | ||||
| new ProperNoun('Withers'), | new ProperNoun('Withers'), | ||||
| TheyPronouns, | |||||
| FemalePronouns, | |||||
| { Toughness: 60, Power: 100, Speed: 40, Willpower: 60, Charm: 120 }, | { Toughness: 60, Power: 100, Speed: 40, Willpower: 60, Charm: 120 }, | ||||
| new Set(), | new Set(), | ||||
| new Set([VoreType.Oral]), | new Set([VoreType.Oral]), | ||||
| 5000) | 5000) | ||||
| this.actions.push(new BiteAction()) | this.actions.push(new BiteAction()) | ||||
| this.groupActions.push(new StompAction()) | |||||
| this.side = Side.Monsters | this.side = Side.Monsters | ||||
| @@ -35,5 +97,7 @@ export class Withers extends Creature { | |||||
| this.containers.push(stomach) | this.containers.push(stomach) | ||||
| this.otherActions.push(new FeedAction(stomach)) | this.otherActions.push(new FeedAction(stomach)) | ||||
| this.groupActions.push(new DevourAllAction(stomach)) | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat, Side } from './combat' | |||||
| import { DamageType, Damage, Combatant, Stats, Action, Vigor, VoreStats, VoreStat, Stat, Side, GroupAction } from './combat' | |||||
| import { Noun, Pronoun } from './language' | import { Noun, Pronoun } from './language' | ||||
| import { LogEntry, LogLine } from './interface' | import { LogEntry, LogLine } from './interface' | ||||
| import { Vore, Container, VoreType } from './vore' | import { Vore, Container, VoreType } from './vore' | ||||
| @@ -48,6 +48,7 @@ export class Creature extends Vore implements Combatant { | |||||
| perspective: POV = POV.Third | perspective: POV = POV.Third | ||||
| containers: Array<Container> = [] | containers: Array<Container> = [] | ||||
| actions: Array<Action> = []; | actions: Array<Action> = []; | ||||
| groupActions: Array<GroupAction> = []; | |||||
| otherActions: Array<Action> = []; | otherActions: Array<Action> = []; | ||||
| get bulk (): number { | get bulk (): number { | ||||
| @@ -155,6 +156,14 @@ export class Creature extends Vore implements Combatant { | |||||
| }) | }) | ||||
| } | } | ||||
| validGroupActions (targets: Array<Creature>): Array<GroupAction> { | |||||
| const choices = this.groupActions | |||||
| return choices.filter(action => { | |||||
| return targets.some(target => action.allowed(this, target)) | |||||
| }) | |||||
| } | |||||
| destroy (): LogEntry { | destroy (): LogEntry { | ||||
| return super.destroy() | return super.destroy() | ||||
| } | } | ||||
| @@ -30,7 +30,9 @@ export class LogLines implements LogEntry { | |||||
| div.appendChild(partDiv) | div.appendChild(partDiv) | ||||
| } else { | } else { | ||||
| (part as LogEntry).render().forEach(logPart => { | (part as LogEntry).render().forEach(logPart => { | ||||
| div.appendChild(logPart) | |||||
| const partDiv = document.createElement("div") | |||||
| partDiv.appendChild(logPart) | |||||
| div.appendChild(partDiv) | |||||
| }) | }) | ||||
| } | } | ||||
| }) | }) | ||||
| @@ -23,6 +23,7 @@ export abstract class Vore implements Mortal { | |||||
| abstract stats: Stats; | abstract stats: Stats; | ||||
| abstract baseStats: Stats; | abstract baseStats: Stats; | ||||
| abstract status: string; | abstract status: string; | ||||
| digested = false; | |||||
| abstract preyPrefs: Set<VoreType>; | abstract preyPrefs: Set<VoreType>; | ||||
| abstract voreStats: VoreStats; | abstract voreStats: VoreStats; | ||||
| abstract containedIn: Container | null; | abstract containedIn: Container | null; | ||||
| @@ -122,6 +123,7 @@ abstract class NormalContainer implements Container { | |||||
| damageResults.push(prey.takeDamage(scaled)) | damageResults.push(prey.takeDamage(scaled)) | ||||
| if (prey.vigors[Vigor.Health] <= 0) { | if (prey.vigors[Vigor.Health] <= 0) { | ||||
| prey.digested = true | |||||
| this.digested.push(prey) | this.digested.push(prey) | ||||
| justDigested.push(prey) | justDigested.push(prey) | ||||
| } | } | ||||