|
- <template>
- <div @click="$emit('select')" class="statblock">
- <div class="statblock-shader statblock-shader-hover"></div>
- <div class="statblock-shader statblock-shader-selected marching-ants"></div>
- <div class="statblock-shader statblock-shader-selected-ally marching-ants"></div>
- <div class="statblock-shader statblock-shader-current-turn marching-ants"></div>
- <div class="statblock-shader statblock-shader-dead">
- <i class="fas fa-skull-crossbones" />
- </div>
- <div class="statblock-shader statblock-shader-eaten"></div>
- <div class="statblock-content">
- <h2 class="name">
- {{subject.name.all.capital.objective}}
- <div class="tooltip-template">
- <div class="tooltip-title">{{ subject.title }}</div>
- <div class="tooltip-body">{{ subject.desc }}</div>
- </div>
- </h2>
- <div> Initiative: {{ (initiative).toFixed(0) }}%</div>
- <div class="statblock-status-icons">
- <i :class="status.icon" v-for="(status, index) in subject.status" :key="'status' + index">
- <div class="statblock-status-icon-topleft">{{ status.topLeft }}</div>
- <div class="statblock-status-icon-bottomright">{{ status.bottomRight }}</div>
- <div class="tooltip-template">
- <div class="tooltip-title">{{ status.name }}</div>
- <div class="tooltip-body">{{ status.desc }}</div>
- </div>
- </i>
- </div>
- <div class="stat-entry" v-for="vigor in Object.keys(subject.vigors)" v-bind:key="vigor">
- <div class="healthbar" v-bind:style="{'--fullness': (subject.vigors[vigor]/subject.maxVigors[vigor]*100) + '%', '--color': vigorColor(subject.vigors[vigor], subject.maxVigors[vigor]) }">
- <i :class="vigorIcons[vigor]" />
- <div class="healthbar-value"> {{ subject.vigors[vigor].toFixed(0) + '/' + subject.maxVigors[vigor].toFixed(0) }}</div>
- </div>
- <div class="tooltip-template">
- <div class="tooltip-title">{{ vigor }}</div>
- <div class="tooltip-body">{{ vigorDescs[vigor] }}</div>
- </div>
- </div>
- <div class="stat-line stats">
- <div :class="statClass(subject.stats[stat], subject.baseStats[stat])" v-for="stat in Object.keys(subject.stats)" v-bind:key="stat">
- <i :class="statIcons[stat]" />
- <div class="stat-value">{{subject.stats[stat].toFixed(0)}}</div>
- <div class="tooltip-template">
- <div class="tooltip-title">{{ stat }}</div>
- <div class="tooltip-body">{{ statDescs[stat] }}</div>
- </div>
- </div>
- </div>
- <div class="stat-line vore-stats">
- <div class="stat-entry" v-for="stat in Object.keys(subject.voreStats)" v-bind:key="stat">
- <i :class="voreStatIcons[stat]" />
- <div class="stat-value">{{subject.voreStats[stat].toFixed(0)}}</div>
- <div class="tooltip-template">
- <div class="tooltip-title">{{ stat }}</div>
- <div class="tooltip-body">{{ voreStatDescs[stat] }}</div>
- </div>
- </div>
- </div>
- <button @click.stop="$emit('selectPredator')" v-if="subject.containedIn !== null">Select predator</button>
- <button v-if="subject.perspective === POV.Third" @click.stop="subject.perspective = POV.Second">Second-person</button>
- <button v-if="subject.perspective === POV.First" @click.stop="subject.perspective = POV.Third">Third-person</button>
- <button v-if="subject.perspective === POV.Second" @click.stop="subject.perspective = POV.First">First-person</button>
- </div>
- </div>
- </template>
-
- <script lang="ts">
- import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator'
- import { Creature } from '@/game/creature'
- import { POV } from '@/game/language'
- import { Stats, Stat, StatIcons, StatDescs, Vigor, VigorIcons, VigorDescs, VoreStatDescs, VoreStatIcons, VisibleStatus } from '@/game/combat'
- import ContainerView from './ContainerView.vue'
- import tippy, { delegate, createSingleton } from 'tippy.js'
- import 'tippy.js/dist/tippy.css'
-
- @Component({
- components: {
- ContainerView
- },
- data () {
- return {
- POV: POV
- }
- },
- methods: {
- vigorColor (value: number, max: number) {
- if (value * 5 <= max) {
- return 'red'
- } else if (value * 3 <= max) {
- return 'yellow'
- } else {
- return 'green'
- }
- },
- statClass (value: number, normal: number) {
- if (value < normal) {
- return 'stat-entry crit'
- } else if (value > normal) {
- return 'stat-entry buff'
- } else {
- return 'stat-entry'
- }
- }
- }
- })
- export default class Statblock extends Vue {
- @Prop({ type: Creature, required: true })
- subject!: Creature
-
- @Prop()
- initiative!: number
-
- firstperson: POV = POV.First
- thirdperson: POV = POV.Third
-
- private vigorIcons = VigorIcons
- private statIcons = StatIcons
- private voreStatIcons = VoreStatIcons
- private vigorDescs = VigorDescs
- private statDescs = StatDescs
- private voreStatDescs = VoreStatDescs
- private vigor = Vigor
-
- @Watch('subject.status')
- private statusChanged (a: Array<VisibleStatus>) {
- this.$nextTick(() => {
- const icons = Array.from(this.$el.querySelectorAll(".statblock-status-icons i")) as Array<HTMLElement>
-
- icons.map(elem => {
- const tooltip = elem.querySelector(".tooltip-template") as HTMLElement
- console.log(elem, tooltip)
-
- return tippy(elem, {
- content: tooltip
- })
- })
- })
- }
-
- mounted () {
- const statEntries = Array.from(this.$el.querySelectorAll(".stat-entry"))
- const name = Array.from(this.$el.querySelectorAll(".name"))
- const tippyInstances = statEntries.concat(name).map(elem => {
- const tooltip = elem.querySelector(".tooltip-template") as HTMLElement
-
- return tippy(elem, {
- content: tooltip
- })
- })
- createSingleton(tippyInstances, { delay: 500 })
-
- this.statusChanged([])
- }
- }
- </script>
-
- <!-- Add "scoped" attribute to limit CSS to this component only -->
- <style scoped>
- h2 {
- margin-bottom: 8pt;
- font-size: 200%;
- }
- ul {
- list-style-type: none;
- padding: 0;
- }
- li {
- display: inline-block;
- margin: 0 10px;
- }
- a {
- color: #42b983;
- }
-
- .statblock {
- flex: 1 0;
- flex-basis: 100pt;
- margin: 0pt 4pt 0pt;
- user-select: none;
- position: relative;
- overflow: hidden;
- background: none;
- border-radius: 10px;
- }
-
- .stat-line {
- width: 100%;
- display: flex;
- justify-content: space-evenly;
- flex-wrap: wrap;
- }
-
- .stat-entry {
- position: relative;
- font-size: 10pt;
- padding-top: 2pt;
- padding-bottom: 2pt;
- display: flex;
- flex-direction: column;
- justify-content: space-evenly;
- align-items: center;
- user-select: none;
- text-align: center;
- }
-
- .stat-value {
- position: absolute;
- transform: translate(0, 8pt);
- padding-top: 4pt;
- padding-bottom: 4pt;
- }
-
- .healthbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- --color: green;
- --fullness: 100%;
- position: relative;
- width: 90%;
- margin: 0% 5% 0%;
- height: 14pt;
- border-radius: 2pt;
- border-width: 2pt;
- border-color: gray;
- border-style: outset;
- background: linear-gradient(90deg, var(--color) var(--fullness), black var(--fullness), black);
- }
-
- .stat-entry .healthbar i {
- flex: 0 1;
- flex-basis: 20pt;
- font-size: 14pt;
- }
-
- .healthbar .healthbar-value {
- flex: 1 0;
- font-size: 12pt;
- color: #bbb;
- }
-
- .stat-entry > i {
- font-size: 16pt;
- width: 16pt;
- margin-bottom: 18pt;
- }
-
- .stat-entry.low {
- color: yellow;
- }
-
- .stat-entry.crit {
- color: red;
- }
-
- .stat-entry.buff {
- color: green;
- }
-
- .statblock-content {
- position: relative;
- width: 100%;
- height: 100%;
- background: none;
- }
-
- .statblock-shader {
- position: absolute;
- width: 100%;
- height: 100%;
- opacity: 0%;
- pointer-events: none;
- z-index: 0;
- }
-
- .statblock[data-destroyed] .statblock-content,
- .statblock[data-destroyed] .stat-entry {
- animation: destroyed 1s;
- animation-fill-mode: both;
- overflow: hidden;
- }
-
- @keyframes destroyed {
- from {
- opacity: 1;
- }
-
- to {
- opacity: 0.25;
- }
- }
-
- .statblock[data-eaten] .statblock-shader-eaten {
- background: repeating-linear-gradient(45deg, transparent, transparent 20px, green 20px, green 40px, transparent 40px);
- opacity: 0.5;
- }
-
- /* yoinked from https://codepen.io/danichk/pen/PPRxrR?editors=0110 */
-
- .marching-ants {
- background: linear-gradient(#111, #111) padding-box, repeating-linear-gradient(-45deg, var(--ants-color) 0, var(--ants-color) 25%, transparent 0, transparent 50%) 0 / 20px 20px;
- animation: marching-ants 12s infinite linear;
- border: 4px solid transparent;
- }
- .statblock[data-active] .statblock-shader-selected {
- --ants-color: #f88;
- border-radius: 8px;
- width: calc(100% - 8px);
- height: calc(100% - 8px);
- opacity: 0.75;
- }
-
- .statblock[data-active-ally] .statblock-shader-selected-ally {
- --ants-color: #88f;
- border: 4px solid transparent;
- border-radius: 8px;
- width: calc(100% - 8px);
- height: calc(100% - 8px);
- opacity: 0.75;
- }
-
- .statblock[data-current-turn] .statblock-shader-current-turn {
- --ants-color: #fff;
- border: 4px solid transparent;
- border-radius: 8px;
- width: calc(100% - 8px);
- height: calc(100% - 8px);
- opacity: 0.75;
- }
-
- @keyframes marching-ants {
- to {
- background-position: 100% 100%;
- }
- }
-
- .statblock-shader-dead i {
- font-size: 100px;
- position: absolute;
- top: 50%;
- transform: translate(-50%, -50%);
- }
-
- .statblock[data-dead] .statblock-shader-dead {
- background: repeating-linear-gradient(45deg, red, red 20px, transparent 20px, transparent 40px, red 40px);
- opacity: 0.50;
- }
-
- .statblock:hover[data-active] .statblock-shader-hover {
- opacity: 0;
- }
-
- .statblock:hover .statblock-shader-hover {
- background: white;
- opacity: 0.20;
- }
-
- .statblock[data-active] .if-not-selected {
- display: none;
- }
-
- .statblock .if-ally {
- display: none;
- }
-
- .statblock[data-ally] .if-ally {
- display: block;
- }
-
- .statblock-status-icons {
- display: flex;
- justify-content: space-evenly;
- min-height: 16pt;
- }
-
- .statblock-status-icons > i {
- font-size: 16pt;
- position: relative;
- }
-
- .statblock-status-icon-topleft,
- .statblock-status-icon-bottomright {
- position: absolute;
- font-family: sans-serif;
- font-weight: 300;
- color: #999;
- }
-
- .statblock-status-icon-topleft {
- top: -12pt;
- }
-
- .statblock-status-icon-bottomright {
- top: 4pt;
- right: -12pt;
- }
- </style>
-
- <style>
-
- .left-stats .stat-entry::after {
- transform: translate(calc(0% + 16pt), -100%);
- }
- .left-stats .stat-entry::before {
- transform: translate(calc(0% + 16pt), calc(-100% + 18pt + 16pt));
- }
- .right-stats .stat-entry::after {
- transform: translate(calc(-100% + 16pt), -100%);
- }
- .right-stats .stat-entry::before {
- transform: translate(calc(-100% + 16pt), calc(-100% + 18pt + 16pt));
- }
-
- </style>
|