Feast 2.0!
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

434 linhas
12 KiB

  1. <template>
  2. <div class="combat-layout">
  3. <div>{{ encounter.currentMove.name }}</div>
  4. <div @wheel="horizWheelLeft" class="stat-column" id="left-stats">
  5. <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)" />
  6. </div>
  7. <div @wheel="horizWheelRight" class="stat-column" id="right-stats">
  8. <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)" />
  9. </div>
  10. <div id="log">
  11. </div>
  12. <div class="left-fader">
  13. </div>
  14. <div class="left-actions">
  15. <div v-if="encounter.currentMove === left" class="vert-display">
  16. <i class="action-label fas fa-users" v-if="left.validGroupActions(combatants).length > 0"></i>
  17. <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" />
  18. <i class="action-label fas fa-user-friends" v-if="left.validActions(right).length > 0"></i>
  19. <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" />
  20. <i class="action-label fas fa-user" v-if="left.validActions(left).length > 0"></i>
  21. <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" />
  22. </div>
  23. <div>{{actionDescription}}</div>
  24. </div>
  25. <div class="right-fader">
  26. </div>
  27. <div class="right-actions">
  28. <div v-if="encounter.currentMove === right" class="vert-display">
  29. <i class="action-label fas fa-users" v-if="right.validGroupActions(combatants).length > 0"></i>
  30. <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" />
  31. <i class="action-label fas fa-user-friends" v-if="right.validActions(left).length > 0"></i>
  32. <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" />
  33. <i class="action-label fas fa-user" v-if="right.validActions(right).length > 0"></i>
  34. <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" />
  35. </div>
  36. </div>
  37. <div id="action-desc">
  38. </div>
  39. </div>
  40. </template>
  41. <script lang="ts">
  42. import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator'
  43. import { Creature } from '@/game/entity'
  44. import { POV } from '@/game/language'
  45. import { LogEntry } from '@/game/interface'
  46. import Statblock from './Statblock.vue'
  47. import ActionButton from './ActionButton.vue'
  48. import { Side, Encounter } from '@/game/combat'
  49. @Component(
  50. {
  51. components: { Statblock, ActionButton },
  52. data () {
  53. return {
  54. left: null,
  55. right: null,
  56. combatants: null
  57. }
  58. },
  59. methods: {
  60. horizWheelLeft (event: MouseWheelEvent) {
  61. const target = this.$el.querySelector("#left-stats")
  62. if (target !== null) {
  63. target.scrollBy({ top: 0, left: event.deltaY, behavior: 'smooth' })
  64. }
  65. },
  66. horizWheelRight (event: MouseWheelEvent) {
  67. const target = this.$el.querySelector("#right-stats")
  68. if (target !== null) {
  69. target.scrollBy({ top: 0, left: event.deltaY, behavior: 'smooth' })
  70. }
  71. },
  72. doSelectLeft (combatant: Creature) {
  73. if (combatant.side !== this.$props.encounter.currentMove.side) {
  74. this.$data.left = combatant
  75. }
  76. },
  77. doSelectRight (combatant: Creature) {
  78. if (combatant.side !== this.$props.encounter.currentMove.side) {
  79. this.$data.right = combatant
  80. }
  81. }
  82. }
  83. }
  84. )
  85. export default class Combat extends Vue {
  86. @Prop()
  87. encounter!: Encounter
  88. Side = Side
  89. actionDescription = ''
  90. created () {
  91. this.$data.left = this.encounter.combatants.filter(x => x.side === Side.Heroes)[0]
  92. this.$data.right = this.encounter.combatants.filter(x => x.side === Side.Monsters)[0]
  93. this.$data.combatants = this.encounter.combatants
  94. }
  95. mounted () {
  96. const leftStats = this.$el.querySelector("#left-stats")
  97. if (leftStats !== null) {
  98. leftStats.scrollTo(leftStats.getBoundingClientRect().width * 2, 0)
  99. }
  100. }
  101. @Emit("executedLeft")
  102. executedLeft (entry: LogEntry) {
  103. const log = document.querySelector("#log")
  104. if (log !== null) {
  105. const holder = document.createElement("div")
  106. entry.render().forEach(element => {
  107. holder.appendChild(element)
  108. })
  109. holder.classList.add("left-move")
  110. log.appendChild(holder)
  111. log.scrollTo({ top: log.scrollHeight, left: 0 })
  112. }
  113. this.encounter.nextMove()
  114. if (this.encounter.currentMove.side === Side.Heroes) {
  115. this.$data.left = this.encounter.currentMove
  116. this.$el.querySelector("#left-stats ")
  117. } else if (this.encounter.currentMove.side === Side.Monsters) {
  118. this.$data.right = this.encounter.currentMove
  119. }
  120. // scroll to the newly selected creature
  121. this.$nextTick(() => {
  122. const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
  123. if (creature !== null) {
  124. creature.scrollIntoView()
  125. }
  126. })
  127. }
  128. @Emit("executedRight")
  129. executedRight (entry: LogEntry) {
  130. const log = document.querySelector("#log")
  131. if (log !== null) {
  132. const holder = document.createElement("div")
  133. entry.render().forEach(element => {
  134. holder.appendChild(element)
  135. })
  136. holder.classList.add("right-move")
  137. log.appendChild(holder)
  138. log.scrollTo({ top: log.scrollHeight, left: 0 })
  139. }
  140. this.encounter.nextMove()
  141. if (this.encounter.currentMove.side === Side.Heroes) {
  142. this.$data.left = this.encounter.currentMove
  143. } else if (this.encounter.currentMove.side === Side.Monsters) {
  144. this.$data.right = this.encounter.currentMove
  145. }
  146. // scroll to the newly selected creature
  147. this.$nextTick(() => {
  148. const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
  149. if (creature !== null) {
  150. creature.scrollIntoView()
  151. }
  152. })
  153. }
  154. @Emit("described")
  155. described (entry: LogEntry) {
  156. const actionDesc = document.querySelector("#action-desc")
  157. if (actionDesc !== null) {
  158. const holder = document.createElement("div")
  159. entry.render().forEach(element => {
  160. holder.appendChild(element)
  161. })
  162. actionDesc.innerHTML = ''
  163. actionDesc.appendChild(holder)
  164. }
  165. }
  166. }
  167. </script>
  168. <!-- Add "scoped" attribute to limit CSS to this component only -->
  169. <style scoped>
  170. .combat-layout {
  171. display: grid;
  172. grid-template-rows: fit-content(50%) 10% [main-row-start] 1fr 20% [main-row-end] ;
  173. grid-template-columns: 20% [main-col-start] 1fr 1fr [main-col-end] 20%;
  174. width: 100%;
  175. height: 100%;
  176. flex: 10;
  177. overflow: hidden;
  178. }
  179. #log {
  180. grid-area: main-row-start / main-col-start / main-row-end / main-col-end;
  181. overflow-y: scroll;
  182. font-size: 12pt;
  183. width: 100%;
  184. max-height: 100%;
  185. align-self: flex-start;
  186. }
  187. #left-stats,
  188. #right-stats {
  189. display: flex;
  190. }
  191. #left-stats {
  192. flex-direction: row;
  193. }
  194. #right-stats {
  195. flex-direction: row;
  196. }
  197. #left-stats {
  198. grid-area: 1 / 1 / 2 / 3
  199. }
  200. #right-stats {
  201. grid-area: 1 / 3 / 2 / 5;
  202. }
  203. .stat-column {
  204. overflow-x: scroll;
  205. overflow-y: auto;
  206. }
  207. .left-fader {
  208. grid-area: 2 / 1 / 4 / 2;
  209. }
  210. .right-fader {
  211. grid-area: 2 / 4 / 4 / 5;
  212. }
  213. .left-fader,
  214. .right-fader {
  215. z-index: 1;
  216. pointer-events: none;
  217. background: linear-gradient(to bottom, #111, #00000000 10%, #00000000 90%, #111 100%);
  218. height: 100%;
  219. width: 100%;
  220. }
  221. .left-actions {
  222. grid-area: 2 / 1 / 4 / 2;
  223. }
  224. .right-actions {
  225. grid-area: 2 / 4 / 4 / 5;
  226. }
  227. .left-actions,
  228. .right-actions {
  229. overflow-y: hidden;
  230. display: flex;
  231. flex-direction: column;
  232. height: 100%;
  233. width: 100%;
  234. }
  235. #action-desc {
  236. grid-area: 2 / main-col-start / main-row-start / main-col-end;
  237. padding: 8pt;
  238. text-align: center;
  239. font-size: 16px;
  240. }
  241. h3 {
  242. margin: 40px 0 0;
  243. }
  244. ul {
  245. list-style-type: none;
  246. padding: 0;
  247. }
  248. li {
  249. display: inline-block;
  250. margin: 0 10px;
  251. }
  252. a {
  253. color: #42b983;
  254. }
  255. .horiz-display {
  256. display: flex;
  257. justify-content: center;
  258. }
  259. .vert-display {
  260. display: flex;
  261. flex-direction: column;
  262. align-items: center;
  263. flex-wrap: nowrap;
  264. justify-content: start;
  265. height: 100%;
  266. overflow-y: auto;
  267. padding: 64px 0 64px;
  268. }
  269. .action-label {
  270. font-size: 200%;
  271. }
  272. </style>
  273. <style>
  274. .log-damage {
  275. font-weight: bold;
  276. }
  277. .damage-instance {
  278. white-space: nowrap;
  279. }
  280. #log > div {
  281. color: #888;
  282. padding-top: 4pt;
  283. padding-bottom: 4pt;
  284. }
  285. div.left-move,
  286. div.right-move {
  287. color: #888;
  288. }
  289. div.left-move {
  290. text-align: start;
  291. margin-right: 25%;
  292. margin-left: 2%;
  293. }
  294. div.right-move {
  295. text-align: end;
  296. margin-left: 25%;
  297. margin-right: 2%;
  298. }
  299. #log img {
  300. width: 75%;
  301. }
  302. #log > div.left-move:nth-last-child(7) {
  303. padding-top: 8pt;
  304. color: #988;
  305. }
  306. #log > div.left-move:nth-last-child(6) {
  307. padding-top: 12pt;
  308. color: #a88;
  309. }
  310. #log > div.left-move:nth-last-child(5) {
  311. padding-top: 16pt;
  312. color: #b88;
  313. }
  314. #log > div.left-move:nth-last-child(4) {
  315. padding-top: 20pt;
  316. color: #c88;
  317. }
  318. #log > div.left-move:nth-last-child(3) {
  319. padding-top: 24pt;
  320. color: #d88;
  321. }
  322. #log > div.left-move:nth-last-child(2) {
  323. padding-top: 28pt;
  324. color: #e88;
  325. }
  326. #log > div.left-move:nth-last-child(1) {
  327. padding-top: 32pt;
  328. color: #f88;
  329. }
  330. #log > div.right-move:nth-last-child(7) {
  331. padding-top: 8pt;
  332. color: #988;
  333. }
  334. #log > div.right-move:nth-last-child(6) {
  335. padding-top: 12pt;
  336. color: #a88;
  337. }
  338. #log > div.right-move:nth-last-child(5) {
  339. padding-top: 16pt;
  340. color: #b88;
  341. }
  342. #log > div.right-move:nth-last-child(4) {
  343. padding-top: 20pt;
  344. color: #c88;
  345. }
  346. #log > div.right-move:nth-last-child(3) {
  347. padding-top: 24pt;
  348. color: #d88;
  349. }
  350. #log > div.right-move:nth-last-child(2) {
  351. padding-top: 28pt;
  352. color: #e88;
  353. }
  354. #log > div.right-move:nth-last-child(1) {
  355. padding-top: 32pt;
  356. color: #f88;
  357. }
  358. .left-selector,
  359. .right-selector {
  360. display: flex;
  361. flex-wrap: wrap;
  362. }
  363. .combatant-picker {
  364. flex: 1 1;
  365. }
  366. </style>