Feast 2.0!
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 

449 行
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-destroyed="combatant.destroyed" :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).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-destroyed="combatant.destroyed" :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)" 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/creature'
  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. @Emit("described")
  91. described (entry: LogEntry) {
  92. const actionDesc = document.querySelector("#action-desc")
  93. if (actionDesc !== null) {
  94. const holder = document.createElement("div")
  95. entry.render().forEach(element => {
  96. holder.appendChild(element)
  97. })
  98. actionDesc.innerHTML = ''
  99. actionDesc.appendChild(holder)
  100. }
  101. }
  102. @Emit("executedLeft")
  103. executedLeft (entry: LogEntry) {
  104. const log = document.querySelector("#log")
  105. if (log !== null) {
  106. const holder = document.createElement("div")
  107. entry.render().forEach(element => {
  108. holder.appendChild(element)
  109. })
  110. holder.classList.add("left-move")
  111. log.appendChild(holder)
  112. log.scrollTo({ top: log.scrollHeight, left: 0 })
  113. }
  114. this.encounter.nextMove()
  115. if (this.encounter.currentMove.side === Side.Heroes) {
  116. this.$data.left = this.encounter.currentMove
  117. this.$el.querySelector("#left-stats ")
  118. } else if (this.encounter.currentMove.side === Side.Monsters) {
  119. this.$data.right = this.encounter.currentMove
  120. }
  121. // scroll to the newly selected creature
  122. this.$nextTick(() => {
  123. const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
  124. if (creature !== null) {
  125. creature.scrollIntoView()
  126. }
  127. })
  128. }
  129. @Emit("executedRight")
  130. executedRight (entry: LogEntry) {
  131. const log = document.querySelector("#log")
  132. if (log !== null) {
  133. const holder = document.createElement("div")
  134. entry.render().forEach(element => {
  135. holder.appendChild(element)
  136. })
  137. holder.classList.add("right-move")
  138. log.appendChild(holder)
  139. log.scrollTo({ top: log.scrollHeight, left: 0 })
  140. }
  141. this.encounter.nextMove()
  142. if (this.encounter.currentMove.side === Side.Heroes) {
  143. this.$data.left = this.encounter.currentMove
  144. } else if (this.encounter.currentMove.side === Side.Monsters) {
  145. this.$data.right = this.encounter.currentMove
  146. }
  147. // scroll to the newly selected creature
  148. this.$nextTick(() => {
  149. const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
  150. if (creature !== null) {
  151. creature.scrollIntoView()
  152. }
  153. })
  154. }
  155. created () {
  156. this.$data.left = this.encounter.combatants.filter(x => x.side === Side.Heroes)[0]
  157. this.$data.right = this.encounter.combatants.filter(x => x.side === Side.Monsters)[0]
  158. this.$data.combatants = this.encounter.combatants
  159. }
  160. mounted () {
  161. const leftStats = this.$el.querySelector("#left-stats")
  162. if (leftStats !== null) {
  163. leftStats.scrollTo(leftStats.getBoundingClientRect().width * 2, 0)
  164. }
  165. this.encounter.nextMove()
  166. if (this.encounter.currentMove.side === Side.Heroes) {
  167. this.$data.left = this.encounter.currentMove
  168. } else if (this.encounter.currentMove.side === Side.Monsters) {
  169. this.$data.right = this.encounter.currentMove
  170. }
  171. // scroll to the newly selected creature
  172. this.$nextTick(() => {
  173. const creature: HTMLElement|null = this.$el.querySelector("[data-current-turn]")
  174. if (creature !== null) {
  175. creature.scrollIntoView()
  176. }
  177. })
  178. }
  179. }
  180. </script>
  181. <!-- Add "scoped" attribute to limit CSS to this component only -->
  182. <style scoped>
  183. .combat-layout {
  184. display: grid;
  185. grid-template-rows: fit-content(50%) 10% [main-row-start] 1fr 20% [main-row-end] ;
  186. grid-template-columns: 20% [main-col-start] 1fr 1fr [main-col-end] 20%;
  187. width: 100%;
  188. height: 100%;
  189. flex: 10;
  190. overflow: hidden;
  191. }
  192. #log {
  193. grid-area: main-row-start / main-col-start / main-row-end / main-col-end;
  194. overflow-y: scroll;
  195. font-size: 12pt;
  196. width: 100%;
  197. max-height: 100%;
  198. align-self: flex-start;
  199. }
  200. #left-stats,
  201. #right-stats {
  202. display: flex;
  203. }
  204. #left-stats {
  205. flex-direction: row;
  206. }
  207. #right-stats {
  208. flex-direction: row;
  209. }
  210. #left-stats {
  211. grid-area: 1 / 1 / 2 / 3
  212. }
  213. #right-stats {
  214. grid-area: 1 / 3 / 2 / 5;
  215. }
  216. .stat-column {
  217. overflow-x: scroll;
  218. overflow-y: auto;
  219. }
  220. .left-fader {
  221. grid-area: 2 / 1 / 4 / 2;
  222. }
  223. .right-fader {
  224. grid-area: 2 / 4 / 4 / 5;
  225. }
  226. .left-fader,
  227. .right-fader {
  228. z-index: 1;
  229. pointer-events: none;
  230. background: linear-gradient(to bottom, #111, #00000000 10%, #00000000 90%, #111 100%);
  231. height: 100%;
  232. width: 100%;
  233. }
  234. .left-actions {
  235. grid-area: 2 / 1 / 4 / 2;
  236. }
  237. .right-actions {
  238. grid-area: 2 / 4 / 4 / 5;
  239. }
  240. .left-actions,
  241. .right-actions {
  242. overflow-y: hidden;
  243. display: flex;
  244. flex-direction: column;
  245. height: 100%;
  246. width: 100%;
  247. }
  248. #action-desc {
  249. grid-area: 2 / main-col-start / main-row-start / main-col-end;
  250. padding: 8pt;
  251. text-align: center;
  252. font-size: 16px;
  253. }
  254. h3 {
  255. margin: 40px 0 0;
  256. }
  257. ul {
  258. list-style-type: none;
  259. padding: 0;
  260. }
  261. li {
  262. display: inline-block;
  263. margin: 0 10px;
  264. }
  265. a {
  266. color: #42b983;
  267. }
  268. .horiz-display {
  269. display: flex;
  270. justify-content: center;
  271. }
  272. .vert-display {
  273. display: flex;
  274. flex-direction: column;
  275. align-items: center;
  276. flex-wrap: nowrap;
  277. justify-content: start;
  278. height: 100%;
  279. overflow-y: auto;
  280. padding: 64px 0 64px;
  281. }
  282. .action-label {
  283. font-size: 200%;
  284. }
  285. </style>
  286. <style>
  287. .log-damage {
  288. font-weight: bold;
  289. }
  290. .damage-instance {
  291. white-space: nowrap;
  292. }
  293. #log > div {
  294. color: #888;
  295. padding-top: 4pt;
  296. padding-bottom: 4pt;
  297. }
  298. div.left-move,
  299. div.right-move {
  300. color: #888;
  301. }
  302. div.left-move {
  303. text-align: start;
  304. margin-right: 25%;
  305. margin-left: 2%;
  306. }
  307. div.right-move {
  308. text-align: end;
  309. margin-left: 25%;
  310. margin-right: 2%;
  311. }
  312. #log img {
  313. width: 75%;
  314. }
  315. #log > div.left-move:nth-last-child(7) {
  316. padding-top: 8pt;
  317. color: #988;
  318. }
  319. #log > div.left-move:nth-last-child(6) {
  320. padding-top: 12pt;
  321. color: #a88;
  322. }
  323. #log > div.left-move:nth-last-child(5) {
  324. padding-top: 16pt;
  325. color: #b88;
  326. }
  327. #log > div.left-move:nth-last-child(4) {
  328. padding-top: 20pt;
  329. color: #c88;
  330. }
  331. #log > div.left-move:nth-last-child(3) {
  332. padding-top: 24pt;
  333. color: #d88;
  334. }
  335. #log > div.left-move:nth-last-child(2) {
  336. padding-top: 28pt;
  337. color: #e88;
  338. }
  339. #log > div.left-move:nth-last-child(1) {
  340. padding-top: 32pt;
  341. color: #f88;
  342. }
  343. #log > div.right-move:nth-last-child(7) {
  344. padding-top: 8pt;
  345. color: #988;
  346. }
  347. #log > div.right-move:nth-last-child(6) {
  348. padding-top: 12pt;
  349. color: #a88;
  350. }
  351. #log > div.right-move:nth-last-child(5) {
  352. padding-top: 16pt;
  353. color: #b88;
  354. }
  355. #log > div.right-move:nth-last-child(4) {
  356. padding-top: 20pt;
  357. color: #c88;
  358. }
  359. #log > div.right-move:nth-last-child(3) {
  360. padding-top: 24pt;
  361. color: #d88;
  362. }
  363. #log > div.right-move:nth-last-child(2) {
  364. padding-top: 28pt;
  365. color: #e88;
  366. }
  367. #log > div.right-move:nth-last-child(1) {
  368. padding-top: 32pt;
  369. color: #f88;
  370. }
  371. .left-selector,
  372. .right-selector {
  373. display: flex;
  374. flex-wrap: wrap;
  375. }
  376. .combatant-picker {
  377. flex: 1 1;
  378. }
  379. </style>