|
- <template>
- <div class="combat-layout">
- <div>{{ encounter.currentMove.name }}</div>
- <div @wheel="horizWheelLeft" class="stat-column" id="left-stats">
- <Statblock @selectPredator="right = combatant.containedIn.owner" @selectAlly="right = combatant" @select="doSelectLeft(combatant)" class="left-stats" :data-disabled="encounter.currentMove.side === combatant.side && encounter.currentMove !== combatant" :data-current-turn="encounter.currentMove === combatant" :data-active="combatant === left" :data-active-ally="combatant === right" :data-eaten="combatant.containedIn !== null" :data-dead="combatant.vigors.Health <= 0" v-for="(combatant, index) in combatants.filter(c => c.side == Side.Heroes && !c.destroyed).slice().reverse()" v-bind:key="'left-stat-' + index" :subject="combatant" :initiative="encounter.initiatives.get(combatant)" />
- </div>
- <div @wheel="horizWheelRight" class="stat-column" id="right-stats">
- <Statblock @selectPredator="left = combatant.containedIn.owner" @selectAlly="left = combatant" @select="doSelectRight(combatant)" class="right-stats" :data-disabled="encounter.currentMove.side === combatant.side && encounter.currentMove !== combatant" :data-current-turn="encounter.currentMove === combatant" :data-active="combatant === right" :data-active-ally="combatant === left" :data-eaten="combatant.containedIn !== null" :data-dead="combatant.vigors.Health <= 0" v-for="(combatant, index) in combatants.filter(c => c.side == Side.Monsters && !c.destroyed)" v-bind:key="'right-stat-' + index" :subject="combatant" :initiative="encounter.initiatives.get(combatant)" />
- </div>
- <div id="log">
- </div>
- <div class="left-fader">
-
- </div>
- <div class="left-actions">
- <div v-if="encounter.currentMove === left" class="vert-display">
- <i class="action-label fas fa-users" v-if="left.validGroupActions(combatants).length > 0"></i>
- <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left.validGroupActions(combatants)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="right" :combatants="combatants" />
- <i class="action-label fas fa-user-friends" v-if="left.validActions(right).length > 0"></i>
- <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left.validActions(right)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="right" :combatants="combatants" />
- <i class="action-label fas fa-user" v-if="left.validActions(left).length > 0"></i>
- <ActionButton @described="described" @executed="executedLeft" v-for="(action, index) in left.validActions(left)" :key="'left-' + action.name + '-' + index" :action="action" :user="left" :target="left" :combatants="combatants" />
- </div>
- <div>{{actionDescription}}</div>
- </div>
- <div class="right-fader">
-
- </div>
- <div class="right-actions">
- <div v-if="encounter.currentMove === right" class="vert-display">
- <i class="action-label fas fa-users" v-if="right.validGroupActions(combatants).length > 0"></i>
- <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right.validGroupActions(combatants)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="left" :combatants="combatants" />
- <i class="action-label fas fa-user-friends" v-if="right.validActions(left).length > 0"></i>
- <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right.validActions(left)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="left" :combatants="combatants" />
- <i class="action-label fas fa-user" v-if="right.validActions(right).length > 0"></i>
- <ActionButton @described="described" @executed="executedRight" v-for="(action, index) in right.validActions(right)" :key="'right-' + action.name + '-' + index" :action="action" :user="right" :target="right" :combatants="combatants" />
- </div>
- </div>
- <div id="action-desc">
- </div>
- </div>
- </template>
-
- <script lang="ts">
- import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator'
- import { Creature } from '@/game/entity'
- import { POV } from '@/game/language'
- import { LogEntry } from '@/game/interface'
- import Statblock from './Statblock.vue'
- import ActionButton from './ActionButton.vue'
- import { Side, Encounter } from '@/game/combat'
-
- @Component(
- {
- components: { Statblock, ActionButton },
- data () {
- return {
- left: null,
- right: null,
- combatants: null
- }
- },
- methods: {
- horizWheelLeft (event: MouseWheelEvent) {
- const target = this.$el.querySelector("#left-stats")
-
- if (target !== null) {
- target.scrollBy({ top: 0, left: event.deltaY, behavior: 'smooth' })
- }
- },
- horizWheelRight (event: MouseWheelEvent) {
- const target = this.$el.querySelector("#right-stats")
-
- if (target !== null) {
- target.scrollBy({ top: 0, left: event.deltaY, behavior: 'smooth' })
- }
- },
- doSelectLeft (combatant: Creature) {
- if (combatant.side !== this.$props.encounter.currentMove.side) {
- this.$data.left = combatant
- }
- },
- doSelectRight (combatant: Creature) {
- if (combatant.side !== this.$props.encounter.currentMove.side) {
- this.$data.right = combatant
- }
- }
- }
- }
- )
-
- export default class Combat extends Vue {
- @Prop()
- encounter!: Encounter
-
- Side = Side
-
- actionDescription = ''
-
- created () {
- this.$data.left = this.encounter.combatants.filter(x => x.side === Side.Heroes)[0]
- this.$data.right = this.encounter.combatants.filter(x => x.side === Side.Monsters)[0]
- this.$data.combatants = this.encounter.combatants
- }
-
- mounted () {
- const leftStats = this.$el.querySelector("#left-stats")
-
- if (leftStats !== null) {
- leftStats.scrollTo(leftStats.getBoundingClientRect().width * 2, 0)
- }
- }
-
- @Emit("executedLeft")
- executedLeft (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("left-move")
- log.appendChild(holder)
-
- log.scrollTo({ top: log.scrollHeight, left: 0 })
- }
-
- this.encounter.nextMove()
-
- if (this.encounter.currentMove.side === Side.Heroes) {
- this.$data.left = this.encounter.currentMove
- this.$el.querySelector("#left-stats ")
- } else if (this.encounter.currentMove.side === Side.Monsters) {
- this.$data.right = this.encounter.currentMove
- }
-
- // scroll to the newly selected creature
- this.$nextTick(() => {
- const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
- if (creature !== null) {
- creature.scrollIntoView()
- }
- })
- }
-
- @Emit("executedRight")
- executedRight (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("right-move")
- log.appendChild(holder)
-
- log.scrollTo({ top: log.scrollHeight, left: 0 })
- }
-
- this.encounter.nextMove()
-
- if (this.encounter.currentMove.side === Side.Heroes) {
- this.$data.left = this.encounter.currentMove
- } else if (this.encounter.currentMove.side === Side.Monsters) {
- this.$data.right = this.encounter.currentMove
- }
-
- // scroll to the newly selected creature
- this.$nextTick(() => {
- const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
- if (creature !== null) {
- creature.scrollIntoView()
- }
- })
- }
-
- @Emit("described")
- described (entry: LogEntry) {
- const actionDesc = document.querySelector("#action-desc")
-
- if (actionDesc !== null) {
- const holder = document.createElement("div")
- entry.render().forEach(element => {
- holder.appendChild(element)
- })
- actionDesc.innerHTML = ''
- actionDesc.appendChild(holder)
- }
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style scoped>
- .combat-layout {
- display: grid;
- grid-template-rows: fit-content(50%) 10% [main-row-start] 1fr 20% [main-row-end] ;
- grid-template-columns: 20% [main-col-start] 1fr 1fr [main-col-end] 20%;
- width: 100%;
- height: 100%;
- flex: 10;
- overflow: hidden;
- }
-
- #log {
- grid-area: main-row-start / main-col-start / main-row-end / main-col-end;
- overflow-y: scroll;
- font-size: 12pt;
- width: 100%;
- max-height: 100%;
- align-self: flex-start;
- }
-
- #left-stats,
- #right-stats {
- display: flex;
- }
-
- #left-stats {
- flex-direction: row;
- }
-
- #right-stats {
- flex-direction: row;
- }
-
- #left-stats {
- grid-area: 1 / 1 / 2 / 3
- }
-
- #right-stats {
- grid-area: 1 / 3 / 2 / 5;
- }
-
- .stat-column {
- overflow-x: scroll;
- overflow-y: auto;
- }
-
- .left-fader {
- grid-area: 2 / 1 / 4 / 2;
- }
-
- .right-fader {
- grid-area: 2 / 4 / 4 / 5;
- }
-
- .left-fader,
- .right-fader {
- z-index: 1;
- pointer-events: none;
- background: linear-gradient(to bottom, #111, #00000000 10%, #00000000 90%, #111 100%);
- height: 100%;
- width: 100%;
- }
-
- .left-actions {
- grid-area: 2 / 1 / 4 / 2;
- }
-
- .right-actions {
- grid-area: 2 / 4 / 4 / 5;
- }
-
- .left-actions,
- .right-actions {
- overflow-y: hidden;
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
- }
-
- #action-desc {
- grid-area: 2 / main-col-start / main-row-start / main-col-end;
- padding: 8pt;
- text-align: center;
- font-size: 16px;
- }
-
- 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;
- flex-wrap: nowrap;
- justify-content: start;
- height: 100%;
- overflow-y: auto;
- padding: 64px 0 64px;
- }
-
- .action-label {
- font-size: 200%;
- }
- </style>
-
- <style>
-
- .log-damage {
- font-weight: bold;
- }
-
- .damage-instance {
- white-space: nowrap;
- }
-
- #log > div {
- color: #888;
- padding-top: 4pt;
- padding-bottom: 4pt;
- }
-
- div.left-move,
- div.right-move {
- color: #888;
- }
-
- div.left-move {
- text-align: start;
- margin-right: 25%;
- margin-left: 2%;
- }
-
- div.right-move {
- text-align: end;
- margin-left: 25%;
- margin-right: 2%;
- }
-
- #log img {
- width: 75%;
- }
-
- #log > div.left-move:nth-last-child(7) {
- padding-top: 8pt;
- color: #988;
- }
-
- #log > div.left-move:nth-last-child(6) {
- padding-top: 12pt;
- color: #a88;
- }
-
- #log > div.left-move:nth-last-child(5) {
- padding-top: 16pt;
- color: #b88;
- }
-
- #log > div.left-move:nth-last-child(4) {
- padding-top: 20pt;
- color: #c88;
- }
-
- #log > div.left-move:nth-last-child(3) {
- padding-top: 24pt;
- color: #d88;
- }
-
- #log > div.left-move:nth-last-child(2) {
- padding-top: 28pt;
- color: #e88;
- }
-
- #log > div.left-move:nth-last-child(1) {
- padding-top: 32pt;
- color: #f88;
- }
-
- #log > div.right-move:nth-last-child(7) {
- padding-top: 8pt;
- color: #988;
- }
-
- #log > div.right-move:nth-last-child(6) {
- padding-top: 12pt;
- color: #a88;
- }
-
- #log > div.right-move:nth-last-child(5) {
- padding-top: 16pt;
- color: #b88;
- }
-
- #log > div.right-move:nth-last-child(4) {
- padding-top: 20pt;
- color: #c88;
- }
-
- #log > div.right-move:nth-last-child(3) {
- padding-top: 24pt;
- color: #d88;
- }
-
- #log > div.right-move:nth-last-child(2) {
- padding-top: 28pt;
- color: #e88;
- }
-
- #log > div.right-move:nth-last-child(1) {
- padding-top: 32pt;
- color: #f88;
- }
-
- .left-selector,
- .right-selector {
- display: flex;
- flex-wrap: wrap;
- }
-
- .combatant-picker {
- flex: 1 1;
- }
- </style>
|