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

225 行
5.2 KiB

  1. import { Stat, Vigor, StatDescs, VigorDescs, StatIcons, VigorIcons, VoreStat, VoreStatIcons, VoreStatDescs } from './combat'
  2. import tippy from 'tippy.js'
  3. /**
  4. * A LogEntry is something that can produce zero or more HTMLElements
  5. */
  6. export interface LogEntry {
  7. render: () => HTMLElement[];
  8. }
  9. /**
  10. * Takes zero or more strings or [[LogEntry]] objects
  11. *
  12. * Produces a list of divs containing each string/object
  13. */
  14. export class LogLines implements LogEntry {
  15. private parts: Array<string|LogEntry>
  16. constructor (...parts: Array<string|LogEntry>) {
  17. this.parts = parts
  18. }
  19. render (): HTMLElement[] {
  20. const div = document.createElement("div")
  21. this.parts.forEach(part => {
  22. if (typeof part === "string") {
  23. const partDiv = document.createElement("div")
  24. partDiv.innerText = part
  25. div.appendChild(partDiv)
  26. } else {
  27. (part as LogEntry).render().forEach(logPart => {
  28. div.appendChild(logPart)
  29. })
  30. }
  31. })
  32. return [div]
  33. }
  34. }
  35. export enum FormatOpt {
  36. Damage = "log-damage",
  37. DamageInst = "damage-instance"
  38. }
  39. /**
  40. * Wraps its LogEntry up in a span with the specified class
  41. */
  42. export class FormatEntry implements LogEntry {
  43. constructor (private entry: LogEntry, private opt: FormatOpt) {
  44. }
  45. render (): HTMLElement[] {
  46. const span = document.createElement("span")
  47. this.entry.render().forEach(elem => {
  48. span.appendChild(elem)
  49. })
  50. span.classList.add(this.opt)
  51. return [span]
  52. }
  53. }
  54. /**
  55. * Wraps a string up in a span with the specified class
  56. *
  57. * This will probably be folded into FormatEntry soon
  58. */
  59. export class FormatText implements LogEntry {
  60. constructor (private opt: FormatOpt, private line: string) {
  61. }
  62. render (): HTMLElement[] {
  63. const span = document.createElement("span")
  64. span.innerText = this.line
  65. span.classList.add(this.opt)
  66. return [span]
  67. }
  68. }
  69. /**
  70. * Like [[LogLines]], but with spans instead of divs
  71. */
  72. export class LogLine implements LogEntry {
  73. private parts: Array<string|LogEntry>
  74. constructor (...parts: Array<string|LogEntry>) {
  75. this.parts = parts
  76. }
  77. render (): HTMLElement[] {
  78. const div = document.createElement("span")
  79. this.parts.forEach(part => {
  80. if (typeof part === "string") {
  81. const partSpan = document.createElement("span")
  82. partSpan.innerText = part
  83. div.appendChild(partSpan)
  84. } else {
  85. (part as LogEntry).render().forEach(logPart => {
  86. div.appendChild(logPart)
  87. })
  88. }
  89. })
  90. return [div]
  91. }
  92. }
  93. /**
  94. * Produces a FontAwesome icon
  95. */
  96. export class FAElem implements LogEntry {
  97. constructor (private name: string) {
  98. }
  99. render (): HTMLElement[] {
  100. const i = document.createElement("i")
  101. this.name.split(" ").map(cls => i.classList.add(cls))
  102. return [i]
  103. }
  104. }
  105. /**
  106. * Produces a representation of a creature's property, such as health or power
  107. *
  108. * Can be just the icon, or include a number as well
  109. *
  110. * A tooltip is attached to the symbol
  111. */
  112. export class PropElem implements LogEntry {
  113. constructor (private prop: Stat | Vigor | VoreStat, private value: number|null = null) {
  114. }
  115. render (): HTMLElement[] {
  116. let cls: string
  117. if (this.prop in Stat) {
  118. cls = StatIcons[this.prop as Stat]
  119. } else if (this.prop in Vigor) {
  120. cls = VigorIcons[this.prop as Vigor]
  121. } else if (this.prop in Vigor) {
  122. cls = VoreStatIcons[this.prop as VoreStat]
  123. } else {
  124. // this shouldn't be possible, given the typing...
  125. cls = "fas fa-exclamation-triangle"
  126. }
  127. const span = document.createElement("span")
  128. span.classList.add("stat-entry")
  129. const tooltipTemplate = document.createElement("div")
  130. const tooltipTitle = document.createElement("div")
  131. tooltipTitle.classList.add("tooltip-title")
  132. const tooltipBody = document.createElement("div")
  133. tooltipBody.classList.add("tooltip-body")
  134. tooltipTemplate.appendChild(tooltipTitle)
  135. tooltipTemplate.appendChild(tooltipBody)
  136. tooltipTitle.textContent = this.prop
  137. if (this.prop in Stat) {
  138. tooltipBody.textContent = StatDescs[this.prop as Stat]
  139. } else if (this.prop in Vigor) {
  140. tooltipBody.textContent = VigorDescs[this.prop as Vigor]
  141. } else if (this.prop in VoreStat) {
  142. tooltipBody.textContent = VoreStatDescs[this.prop as VoreStat]
  143. }
  144. if (this.value !== null) {
  145. const numText = Math.round(this.value).toFixed(0) === this.value.toFixed(0) ? this.value.toFixed(0) : this.value.toFixed(1)
  146. span.textContent = numText + ' '
  147. }
  148. const icon = new FAElem(cls).render()[0]
  149. span.appendChild(icon)
  150. tippy(icon, {
  151. content: tooltipTemplate
  152. })
  153. return [span]
  154. }
  155. }
  156. /**
  157. * Produces an <img>
  158. */
  159. export class ImgElem implements LogEntry {
  160. constructor (private url: string) {
  161. }
  162. render (): HTMLElement[] {
  163. const div = document.createElement("div")
  164. const img = document.createElement("img")
  165. img.src = this.url
  166. div.appendChild(img)
  167. return [div]
  168. }
  169. }
  170. /**
  171. * Directly concatenates zero or more [[LogEntry]] objects, without wrapping them in anything
  172. */
  173. export class CompositeLog implements LogEntry {
  174. entries: LogEntry[]
  175. constructor (...entries: LogEntry[]) {
  176. this.entries = entries
  177. }
  178. render (): HTMLElement[] {
  179. return this.entries.flatMap(e => e.render())
  180. }
  181. }