Also cleans up some old duplicate code that I made, somehow.vintage
| @@ -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"> | |||
| 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' | |||
| @Component({}) | |||
| @@ -22,14 +22,27 @@ export default class ActionButton extends Vue { | |||
| @Prop() | |||
| target!: Creature | |||
| @Prop() | |||
| combatants!: Array<Creature> | |||
| @Emit("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") | |||
| 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> | |||
| <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 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 id="log"> | |||
| </div> | |||
| <div class="left-actions"> | |||
| <div class="vert-display"> | |||
| <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> | |||
| <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>{{actionDescription}}</div> | |||
| </div> | |||
| <div class="right-actions"> | |||
| <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> | |||
| <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> | |||
| <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 id="action-desc"> | |||
| @@ -143,12 +145,16 @@ export default class Combat extends Vue { | |||
| grid-area: 2 / 3 / 4 / 4; | |||
| } | |||
| .stat-column { | |||
| overflow-y: auto; | |||
| } | |||
| .left-actions { | |||
| grid-area: 5 / 1 / 6 / 2; | |||
| grid-area: 4 / 1 / 6 / 2; | |||
| } | |||
| .right-actions { | |||
| grid-area: 5 / 3 / 6 / 4; | |||
| grid-area: 4 / 3 / 6 / 4; | |||
| } | |||
| #action-desc { | |||
| @@ -33,6 +33,7 @@ | |||
| </div> | |||
| </div> | |||
| <div>Status: {{subject.status}}</div> | |||
| <button @click="subject.perspective = firstperson">First-person</button> | |||
| </div> | |||
| </template> | |||
| @@ -207,6 +207,7 @@ export enum Side { | |||
| */ | |||
| export interface Combatant { | |||
| actions: Array<Action>; | |||
| groupActions: Array<GroupAction>; | |||
| side: Side; | |||
| } | |||
| @@ -218,7 +219,7 @@ export abstract class Action { | |||
| 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 | |||
| @@ -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 { 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 { VoreType, Stomach, Bowels } from '../vore' | |||
| import { VoreType, Stomach, Bowels, Container } from '../vore' | |||
| import { StatTest } from '../combat/tests' | |||
| import { AttackAction, TransferAction, FeedAction } from '../combat/actions' | |||
| import { TogetherCondition } from '../combat/conditions' | |||
| import { InstantKill } from '../combat/effects' | |||
| class BiteAction extends AttackAction { | |||
| 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 { | |||
| constructor () { | |||
| super( | |||
| new ProperNoun('Withers'), | |||
| TheyPronouns, | |||
| FemalePronouns, | |||
| { Toughness: 60, Power: 100, Speed: 40, Willpower: 60, Charm: 120 }, | |||
| new Set(), | |||
| new Set([VoreType.Oral]), | |||
| 5000) | |||
| this.actions.push(new BiteAction()) | |||
| this.groupActions.push(new StompAction()) | |||
| this.side = Side.Monsters | |||
| @@ -35,5 +97,7 @@ export class Withers extends Creature { | |||
| this.containers.push(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 { LogEntry, LogLine } from './interface' | |||
| import { Vore, Container, VoreType } from './vore' | |||
| @@ -48,6 +48,7 @@ export class Creature extends Vore implements Combatant { | |||
| perspective: POV = POV.Third | |||
| containers: Array<Container> = [] | |||
| actions: Array<Action> = []; | |||
| groupActions: Array<GroupAction> = []; | |||
| otherActions: Array<Action> = []; | |||
| 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 { | |||
| return super.destroy() | |||
| } | |||
| @@ -30,7 +30,9 @@ export class LogLines implements LogEntry { | |||
| div.appendChild(partDiv) | |||
| } else { | |||
| (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 baseStats: Stats; | |||
| abstract status: string; | |||
| digested = false; | |||
| abstract preyPrefs: Set<VoreType>; | |||
| abstract voreStats: VoreStats; | |||
| abstract containedIn: Container | null; | |||
| @@ -122,6 +123,7 @@ abstract class NormalContainer implements Container { | |||
| damageResults.push(prey.takeDamage(scaled)) | |||
| if (prey.vigors[Vigor.Health] <= 0) { | |||
| prey.digested = true | |||
| this.digested.push(prey) | |||
| justDigested.push(prey) | |||
| } | |||