| @@ -0,0 +1,39 @@ | |||||
| <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> | |||||
| @@ -5,21 +5,28 @@ | |||||
| <Statblock :subject="player" /> | <Statblock :subject="player" /> | ||||
| <Statblock :subject="enemy" /> | <Statblock :subject="enemy" /> | ||||
| </div> | </div> | ||||
| <div id="log"></div> | |||||
| <div id="log"> | |||||
| <div v-for="(entry, index) in combatLog" :key="'log' + index"> | |||||
| <div v-for="(line, lineIndex) in entry.render()" :key="index + ' ' + lineIndex"> | |||||
| {{ line }} | |||||
| </div> | |||||
| <br> | |||||
| </div> | |||||
| </div> | |||||
| <div class="horiz-display"> | <div class="horiz-display"> | ||||
| <div> | <div> | ||||
| <h2>Your moves</h2> | <h2>Your moves</h2> | ||||
| <div class="vert-display"> | <div class="vert-display"> | ||||
| <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> | |||||
| <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> | ||||
| <div>{{actionDescription}}</div> | <div>{{actionDescription}}</div> | ||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| <h2>Enemy moves</h2> | <h2>Enemy moves</h2> | ||||
| <div class="vert-display"> | <div class="vert-display"> | ||||
| <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> | |||||
| <ActionButton @executed="executed" v-for="action in enemy.validActions(player)" :key="'player' + action.name" :action="action" :user="enemy" :target="player" /> | |||||
| <ActionButton @executed="executed" v-for="action in enemy.validActions(enemy)" :key="'player' + action.name" :action="action" :user="enemy" :target="player" /> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -29,12 +36,13 @@ | |||||
| <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 { Creature, POV } from '@/game/entity' | import { Creature, POV } from '@/game/entity' | ||||
| import { log, LogEntry } from '@/game/interface' | |||||
| import { LogEntry } from '@/game/interface' | |||||
| import Statblock from './Statblock.vue' | import Statblock from './Statblock.vue' | ||||
| import ActionButton from './ActionButton.vue' | |||||
| @Component( | @Component( | ||||
| { | { | ||||
| components: { Statblock } | |||||
| components: { Statblock, ActionButton } | |||||
| } | } | ||||
| ) | ) | ||||
| export default class Combat extends Vue { | export default class Combat extends Vue { | ||||
| @@ -46,11 +54,16 @@ export default class Combat extends Vue { | |||||
| actionDescription = '' | actionDescription = '' | ||||
| private log: (entry: LogEntry) => void; | |||||
| private combatLog: Array<LogEntry> | |||||
| constructor () { | constructor () { | ||||
| super() | super() | ||||
| this.log = log | |||||
| this.combatLog = [] | |||||
| } | |||||
| @Emit("executed") | |||||
| executed (entry: LogEntry) { | |||||
| this.combatLog.push(entry) | |||||
| } | } | ||||
| } | } | ||||
| </script> | </script> | ||||
| @@ -79,10 +92,6 @@ a { | |||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| } | } | ||||
| .combat-button { | |||||
| width: 100px; | |||||
| height: 100px; | |||||
| } | |||||
| </style> | </style> | ||||
| <style> | <style> | ||||
| @@ -1,10 +1,10 @@ | |||||
| <template> | <template> | ||||
| <div class="statblock"> | <div class="statblock"> | ||||
| <h2 v-if="subject.perspective === firstperson">Player</h2> | |||||
| <h2 v-if="subject.perspective === firstperson">You</h2> | |||||
| <h2 v-if="subject.perspective !== firstperson">{{subject.name.all.capital}}</h2> | <h2 v-if="subject.perspective !== firstperson">{{subject.name.all.capital}}</h2> | ||||
| <div>Health: {{subject.health.toFixed(0)}} / {{subject.maxHealth.toFixed(0)}}</div> | <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 v-for="stat in Object.keys(subject.stats)" v-bind:key="stat">{{stat}}: {{subject.stats[stat]}}</div> | ||||
| <div>Status: {{subject.state}}</div> | |||||
| <div>Status: {{subject.status}}</div> | |||||
| <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | <ContainerView v-for="container in subject.containers" :key="container.name" :container="container" /> | ||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -33,8 +33,9 @@ export default class Statblock extends Vue { | |||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | <style scoped> | ||||
| h3 { | |||||
| margin: 40px 0 0; | |||||
| h2 { | |||||
| margin-bottom: 16pt; | |||||
| font-size: 200%; | |||||
| } | } | ||||
| ul { | ul { | ||||
| list-style-type: none; | list-style-type: none; | ||||
| @@ -225,9 +225,9 @@ export class StruggleAction extends PairAction { | |||||
| private test: StatTest | private test: StatTest | ||||
| protected failLines: POVPair<Entity, Entity> = new POVPair([ | protected failLines: POVPair<Entity, Entity> = new POVPair([ | ||||
| [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to make a meal out of ${target.name}`)], | |||||
| [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to devour you, but fails`)], | |||||
| [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully tries to swallow ${target.name}`)] | |||||
| [[POV.First, POV.Third], (user, target) => new LogLines(`You fail to escape from ${target.name}`)], | |||||
| [[POV.Third, POV.First], (user, target) => new LogLines(`${user.name.capital} tries to escape from you, but fails`)], | |||||
| [[POV.Third, POV.Third], (user, target) => new LogLines(`${target.name} unsuccessfully struggles within ${target.name}`)] | |||||
| ]) | ]) | ||||
| allowed (user: Creature, target: Creature) { | allowed (user: Creature, target: Creature) { | ||||
| @@ -17,6 +17,7 @@ export interface Mortal extends Entity { | |||||
| resistances: Map<DamageType, number>; | resistances: Map<DamageType, number>; | ||||
| takeDamage: (damage: Damage) => void; | takeDamage: (damage: Damage) => void; | ||||
| stats: Stats; | stats: Stats; | ||||
| status: string; | |||||
| } | } | ||||
| export class Creature implements Mortal, Pred, Prey, Combatant { | export class Creature implements Mortal, Pred, Prey, Combatant { | ||||
| @@ -53,6 +54,17 @@ export class Creature implements Mortal, Pred, Prey, Combatant { | |||||
| }) | }) | ||||
| } | } | ||||
| get status (): string { | |||||
| if (this.health < 0) { | |||||
| return "Unconscious" | |||||
| } | |||||
| if (this.containedIn !== null) { | |||||
| return "Devoured" | |||||
| } | |||||
| return "Normal" | |||||
| } | |||||
| validActions (target: Creature): Array<Action> { | validActions (target: Creature): Array<Action> { | ||||
| let choices = this.actions.concat(this.containers.flatMap(container => container.actions)) | let choices = this.actions.concat(this.containers.flatMap(container => container.actions)) | ||||
| @@ -1,5 +1,5 @@ | |||||
| export interface LogEntry { | export interface LogEntry { | ||||
| render: () => HTMLElement[]; | |||||
| render: () => string[]; | |||||
| } | } | ||||
| export class LogLines implements LogEntry { | export class LogLines implements LogEntry { | ||||
| @@ -9,15 +9,8 @@ export class LogLines implements LogEntry { | |||||
| this.lines = lines | this.lines = lines | ||||
| } | } | ||||
| render (): HTMLElement[] { | |||||
| const p = document.createElement('p') | |||||
| this.lines.forEach(line => { | |||||
| const div = document.createElement('div') | |||||
| div.innerText = line | |||||
| p.appendChild(div) | |||||
| }) | |||||
| return [p] | |||||
| render (): string[] { | |||||
| return this.lines | |||||
| } | } | ||||
| } | } | ||||
| @@ -28,13 +21,7 @@ export class CompositeLog implements LogEntry { | |||||
| this.entries = entries | this.entries = entries | ||||
| } | } | ||||
| render (): HTMLElement[] { | |||||
| return this.entries.map(entry => entry.render()).reduce((results: HTMLElement[], next: HTMLElement[]) => results.concat(next), []) | |||||
| render (): string[] { | |||||
| return this.entries.flatMap(e => e.render()) | |||||
| } | } | ||||
| } | } | ||||
| export function log (entry: LogEntry): void { | |||||
| entry.render().forEach(elem => { | |||||
| document.querySelector('#log')!.appendChild(elem) | |||||
| }) | |||||
| } | |||||