tooltip.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. require 'lib/typo'
  2. local rich = require 'lib/deps/richtext'
  3. local g = love.graphics
  4. Tooltip = class()
  5. Tooltip.maxWidth = .3
  6. function Tooltip:init()
  7. self.richOptions = {
  8. whoCares = {225, 225, 225},
  9. white = {255, 255, 255},
  10. red = {255, 100, 100},
  11. green = {100, 255, 100},
  12. purple = {147, 96, 200}
  13. }
  14. self.active = false
  15. self.tooltip = nil
  16. self.tooltipText = nil
  17. self.textWidth = 0
  18. self.textHeight = 0
  19. self.blurCanvas = g.newCanvas(400, 300)
  20. self.blurBackCanvas = g.newCanvas(400, 300)
  21. self.cursorX, self.cursorY = love.mouse.getPosition()
  22. self.prevCursorX = self.cursorX
  23. self.prevCursorY = self.cursorY
  24. self:resize()
  25. end
  26. function Tooltip:update()
  27. local mx, my
  28. if ctx.view then
  29. mx = ctx.view:frameMouseX()
  30. my = ctx.view:frameMouseY()
  31. else
  32. mx, my = love.mouse.getPosition()
  33. end
  34. self.prevCursorX = self.cursorX
  35. self.prevCursorY = self.cursorY
  36. self.cursorX = math.lerp(self.cursorX, mx, 12 * ls.tickrate)
  37. self.cursorY = math.lerp(self.cursorY, my, 12 * ls.tickrate)
  38. end
  39. function Tooltip:draw()
  40. if not self.active then self.tooltipText = nil return end
  41. local u, v = self:getUV()
  42. local mx = math.lerp(self.prevCursorX, self.cursorX, ls.accum / ls.tickrate)
  43. local my = math.lerp(self.prevCursorY, self.cursorY, ls.accum / ls.tickrate)
  44. local xx = math.min(mx + 16 * love.window.getPixelScale(), u - self.textWidth - 14)
  45. local yy = math.min(my + 16 * love.window.getPixelScale(), v - (self.textHeight + 9))
  46. g.setColor(255, 255, 255, 100)
  47. g.draw(self.blurCanvas, xx - 96, yy - 96, 0, 2, 2)
  48. g.setColor(30, 50, 70, 240)
  49. g.rectangle('fill', xx, yy, self.textWidth + 14, self.textHeight + 9)
  50. g.setColor(10, 30, 50, 255)
  51. g.rectangle('line', xx + .5, yy + .5, self.textWidth + 14, self.textHeight + 9)
  52. self.tooltip:draw(xx + 8, yy + 4)
  53. end
  54. function Tooltip:setTooltip(str)
  55. local u, v = self:getUV()
  56. local raw = str:gsub('{%a+}', '')
  57. if str ~= self.tooltipText then
  58. g.setFont(self.richOptions.normal)
  59. self.tooltip = rich:new({str, u * self.maxWidth, self.richOptions}, {255, 255, 255})
  60. self.tooltipText = str
  61. local raw = self.tooltipText:gsub('{%a+}', '')
  62. local normalFont = self.richOptions.normal
  63. local titleFont = self.richOptions.title
  64. g.setFont(self.richOptions.normal)
  65. local titleLine = raw:sub(1, raw:find('\n'))
  66. local normalText = raw:sub(raw:find('\n') + 1) -- TODO memoize in :setTooltip
  67. local textWidth, lines = normalFont:getWrap(normalText, u * self.maxWidth)
  68. local titleWidth, titleLines = titleFont:getWrap(titleLine, u * self.maxWidth)
  69. self.textWidth = math.max(textWidth, titleWidth)
  70. self.textHeight = titleLines * titleFont:getHeight() + lines * normalFont:getHeight()
  71. g.setCanvas(self.blurCanvas)
  72. self.blurCanvas:clear(0, 0, 0, 0)
  73. self.blurBackCanvas:clear(0, 0, 0, 0)
  74. g.setColor(0, 0, 0)
  75. g.rectangle('fill', 50, 50, (self.textWidth + 14) / 2, (self.textHeight + 9) / 2)
  76. g.setColor(255, 255, 255)
  77. for i = 1, 3 do
  78. data.media.shaders.horizontalBlur:send('amount', .003)
  79. data.media.shaders.verticalBlur:send('amount', .003 * 4/3)
  80. g.setCanvas(self.blurBackCanvas)
  81. g.setShader(data.media.shaders.horizontalBlur)
  82. g.draw(self.blurCanvas)
  83. g.setCanvas(self.blurCanvas)
  84. g.setShader(data.media.shaders.verticalBlur)
  85. g.draw(self.blurBackCanvas)
  86. end
  87. g.setCanvas()
  88. g.setShader()
  89. ctx.sound:play('juju1', function(sound) sound:setPitch(.75) end)
  90. end
  91. self.active = true
  92. end
  93. function Tooltip:setUnitTooltip(class, basic)
  94. if type(class) == 'string' then class = data.unit[class] end
  95. local pieces = {}
  96. table.insert(pieces, '{title}{white}' .. class.name .. '{normal}')
  97. table.insert(pieces, '{whoCares}' .. class.description .. '{white}')
  98. if basic then
  99. table.insert(pieces, '')
  100. return self:setTooltip(table.concat(pieces, '\n'))
  101. end
  102. table.insert(pieces, '')
  103. for _, stat in ipairs({'health', 'damage', 'attackSpeed', 'speed', 'spirit', 'haste'}) do
  104. local base = class[stat]
  105. local actual = Unit.getStat(class, stat)
  106. local label = stat:capitalize()
  107. local extra = ''
  108. local color = '{white}'
  109. if stat == 'attackSpeed' then
  110. label = 'Attack Speed'
  111. if actual > 0 then
  112. color = '{green}'
  113. extra = ' {white}({whoCares}' .. math.round(base * 100) / 100 .. ' + {green}' .. math.round(actual * 100) .. '%{white})'
  114. end
  115. local actual = math.max(base - (base * actual), .4)
  116. table.insert(pieces, '{whoCares}{bold}' .. label .. '{white}{normal}: ' .. color .. math.round(actual * 100) / 100 .. extra)
  117. elseif stat == 'haste' then
  118. if actual > 0 then
  119. color = '{green}'
  120. extra = ' {white}({whoCares}100% + {green}' .. math.round(actual * 100) .. '%{white})'
  121. end
  122. table.insert(pieces, '{whoCares}{bold}' .. label .. '{white}{normal}: ' .. color .. math.round((base + actual) * 100) .. '%' .. extra)
  123. else
  124. if base ~= actual then
  125. color = '{green}'
  126. extra = ' {white}({whoCares}' .. math.round(base) .. ' + {green}' .. math.round(actual - base) .. '{white})'
  127. end
  128. table.insert(pieces, '{whoCares}{bold}' .. label .. '{white}{normal}: ' .. color .. math.round(actual) .. extra)
  129. end
  130. end
  131. table.insert(pieces, '')
  132. return self:setTooltip(table.concat(pieces, '\n'))
  133. end
  134. function Tooltip:setUpgradeTooltip(who, what)
  135. local p = ctx.player
  136. local pieces = {}
  137. local upgrade = data.unit[who].upgrades[what]
  138. table.insert(pieces, '{white}{title}' .. upgrade.name .. '{normal}')
  139. table.insert(pieces, '{whoCares}' .. upgrade.description .. '\n')
  140. local function getValue(level)
  141. local value = ''
  142. if upgrade.values and (type(upgrade.values) == 'function' or upgrade.values[level]) then
  143. value = type(upgrade.values) == 'function' and upgrade.values(upgrade, level, data.unit[who]) or upgrade.values[level]
  144. value = value or ''
  145. end
  146. return value == '' and value or '{normal}: ' .. value
  147. end
  148. table.insert(pieces, '{white}{bold}Level ' .. upgrade.level .. getValue(upgrade.level))
  149. if upgrade.level >= upgrade.maxLevel then
  150. table.insert(pieces, '{whoCares}{normal}Max Level')
  151. else
  152. local value = getValue(upgrade.level + 1)
  153. if value == '' then value = '{normal}: Level ' .. upgrade.level + 1 end
  154. table.insert(pieces, '{white}{bold}Next Level' .. value)
  155. local color = p.juju >= upgrade.costs[upgrade.level + 1] and '{green}' or '{red}'
  156. table.insert(pieces, color .. upgrade.costs[upgrade.level + 1] .. ' juju')
  157. if upgrade.prerequisites then
  158. for name, min in pairs(upgrade.prerequisites) do
  159. local color = data.unit[who].upgrades[name].level >= min and '{green}' or '{red}'
  160. local points = (min == 1) and 'point' or 'points'
  161. table.insert(pieces, color .. min .. ' ' .. points .. ' in ' .. data.unit[who].upgrades[name].name:capitalize())
  162. end
  163. end
  164. end
  165. local bonuses = upgrade.bonuses and upgrade:bonuses(data.unit[who])
  166. if bonuses and type(bonuses) == 'table' and #bonuses > 0 then
  167. table.insert(pieces, '')
  168. table.insert(pieces, '{white}Bonuses')
  169. for i = 1, #bonuses do
  170. local bonus = bonuses[i]
  171. table.insert(pieces, '{white}{bold}' .. bonus[1] .. '{normal}: ' .. '{green}+' .. bonus[2] .. '{white} ' .. bonus[3])
  172. end
  173. end
  174. table.insert(pieces, '')
  175. return self:setTooltip(table.concat(pieces, '\n'))
  176. end
  177. function Tooltip:setMagicShrujuTooltip(shruju)
  178. local pieces = {}
  179. table.insert(pieces, '{purple}{title}' .. shruju.name .. '{white}{normal}')
  180. table.insert(pieces, '{white}' .. shruju.description)
  181. return self:setTooltip(table.concat(pieces, '\n'))
  182. end
  183. function Tooltip:setRuneTooltip(rune)
  184. local formatters = {
  185. percent = function(x, stat)
  186. return '+' .. math.round(x * 100) .. '% ' .. stat
  187. end,
  188. flat = function(x, stat)
  189. return '+' .. math.round(x) .. ' ' .. stat
  190. end,
  191. time = function(x, stat)
  192. return '+' .. math.round(x * 10) / 10 .. 's ' .. stat
  193. end
  194. }
  195. local pieces = {}
  196. table.insert(pieces, '{white}{title}' .. rune.name .. '{normal}')
  197. if rune.attributes then
  198. table.each(rune.attributes, function(amount, attribute)
  199. table.insert(pieces, '+' .. amount .. ' to ' .. attribute:capitalize())
  200. end)
  201. elseif rune.stats then
  202. table.each(rune.stats, function(amount, stat)
  203. if stat == 'attackSpeed' then
  204. table.insert(pieces, '+' .. math.round(amount * 100) .. '% to attack speed')
  205. elseif stat == 'haste' then
  206. table.insert(pieces, '+' .. math.round(amount * 100) .. '% to haste')
  207. else
  208. table.insert(pieces, '+' .. math.round(amount) .. ' to ' .. stat:capitalize())
  209. end
  210. end)
  211. elseif rune.unit and rune.abilities then
  212. table.insert(pieces, rune.unit:capitalize() .. ' only')
  213. local ability = next(rune.abilities)
  214. local stat, amount = next(rune.abilities[ability])
  215. local str = '+' .. math.round(amount) .. ' to ' .. stat
  216. local formatter = config.runes.abilityFormatters
  217. formatter = formatter[rune.unit] and formatter[rune.unit][ability] and formatter[rune.unit][ability][stat]
  218. if formatter then
  219. local key, a, b, c, d = unpack(formatter)
  220. str = formatters[key](amount, a, b, c, d)
  221. end
  222. table.insert(pieces, data.unit[rune.unit].upgrades[ability].name .. ': ' .. str)
  223. end
  224. return self:setTooltip(table.concat(pieces, '\n'))
  225. end
  226. function Tooltip:setAttributeTooltip(attribute, unit)
  227. local p = ctx.player
  228. local pieces = {}
  229. table.insert(pieces, '{white}{title}' .. attribute:capitalize() .. '{normal}')
  230. table.insert(pieces, '{whoCares}' .. config.attributes.descriptions[attribute] .. '{white}')
  231. table.insert(pieces, '')
  232. if unit then
  233. if type(unit) == 'string' then unit = data.unit[unit] end
  234. local level = unit.attributes[attribute]
  235. table.each(config.attributes[attribute], function(amount, stat)
  236. local value = amount
  237. local total = amount * level
  238. if stat == 'attackSpeed' then
  239. stat = 'attack speed'
  240. value = (amount * 100) .. '%'
  241. total = ((amount * level) * 100) .. '%'
  242. elseif stat == 'haste' then
  243. value = (amount * 100) .. '%'
  244. total = ((amount * level) * 100) .. '%'
  245. end
  246. table.insert(pieces, '+' .. value .. ' ' .. stat .. ' per level {green}(' .. (total) .. '){white}')
  247. end)
  248. local cost = unit.attributeCosts[attribute] or 30
  249. local color = p.juju >= cost and '{green}' or '{red}'
  250. table.insert(pieces, color .. cost .. ' juju')
  251. end
  252. table.insert(pieces, '')
  253. return self:setTooltip(table.concat(pieces, '\n'))
  254. end
  255. function Tooltip:setHatTooltip(hat)
  256. local pieces = {}
  257. table.insert(pieces, '{title}{white}' .. hat:capitalize())
  258. table.insert(pieces, '')
  259. return self:setTooltip(table.concat(pieces, '\n'))
  260. end
  261. function Tooltip:resize()
  262. local u, v = self:getUV()
  263. self.richOptions.title = Typo.font('mesmerize', .0376 * v)
  264. self.richOptions.normal = Typo.font('mesmerize', .02 * v)
  265. self.richOptions.bold = Typo.font('mesmerize', .02 * v)
  266. end
  267. function Tooltip:dirty()
  268. self.active = false
  269. end
  270. function Tooltip:getUV()
  271. if isa(ctx, Menu) then return ctx.u, ctx.v
  272. elseif isa(ctx, Game) then
  273. if ctx.hud then
  274. return ctx.hud.u, ctx.hud.v
  275. else
  276. return ctx.view.frame.width, ctx.view.frame.height
  277. end
  278. end
  279. end