hud.lua 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. Hud = class()
  2. local g = love.graphics
  3. local rich = require 'lib/deps/richtext/richtext'
  4. local normalFont = love.graphics.newFont('media/fonts/inglobal.ttf', 14)
  5. local fancyFont = love.graphics.newFont('media/fonts/inglobal.ttf', 24)
  6. local boldFont = love.graphics.newFont('media/fonts/inglobalb.ttf', 14)
  7. local deadFontBig = love.graphics.newFont('media/fonts/inglobal.ttf', 64)
  8. local deadFontSmall = love.graphics.newFont('media/fonts/inglobal.ttf', 44)
  9. Hud.richOptions = {title = fancyFont, bold = boldFont, normal = normalFont, white = {255, 255, 255}, whoCares = {230, 230, 230}, red = {255, 100, 100}, green = {100, 255, 100}}
  10. Hud.upgradeGeometry = {
  11. zuju = {
  12. empower = {161, 207, 28},
  13. fortify = {244, 212, 28},
  14. burst = {326, 208, 28},
  15. siphon = {193.5, 281, 32},
  16. sanctuary = {296, 281, 32}
  17. },
  18. vuju = {
  19. surge = {476, 208, 28},
  20. charge = {559, 212, 28},
  21. condemn = {641, 208, 28},
  22. arc = {508.5, 281, 32},
  23. soak = {611, 281, 32}
  24. },
  25. muju = {
  26. flow = {260, 406, 24},
  27. harvest = {218.5, 459.5, 26},
  28. refresh = {290, 478, 40},
  29. zeal = {400, 391, 20},
  30. absorb = {400, 442, 25},
  31. diffuse = {400, 507.5, 31},
  32. imbue = {537, 407, 24},
  33. mirror = {579, 461, 26},
  34. distort = {508, 478, 40}
  35. }
  36. }
  37. Hud.upgradeDotGeometry = {
  38. zuju = {
  39. empower = {{139, 229, 7}, {149, 235, 7}, {160, 238, 7}, {171, 235, 7}, {181, 229, 7}},
  40. fortify = {{223, 233, 7}, {233, 239, 7}, {244, 241, 7}, {255, 239, 7}, {265, 233, 7}},
  41. burst = {{304, 229, 7}, {314, 235, 7}, {325, 238, 7}, {336, 235, 7}, {346, 229, 7}},
  42. siphon = {{177, 308, 9}, {193, 312, 9}, {209, 308, 9}},
  43. sanctuary = {{280, 308, 9}, {296, 312, 9}, {312, 308, 9}}
  44. },
  45. vuju = {
  46. surge = {{454, 229, 7}, {464, 235, 7}, {475, 238, 7}, {486, 235, 7}, {496, 229, 7}},
  47. charge = {{538, 233, 7}, {548, 239, 7}, {559, 241, 7}, {570, 239, 7}, {580, 233, 7}},
  48. condemn = {{619, 229, 7}, {629, 235, 7}, {640, 238, 7}, {651, 235, 7}, {661, 229, 7}},
  49. arc = {{492, 308, 9}, {508, 312, 9}, {524, 308, 9}},
  50. soak = {{595, 308, 9}, {611, 312, 9}, {627, 308, 9}}
  51. },
  52. muju = {
  53. flow = {{241.5, 423.5, 6}, {250.5, 428.5, 6}, {260.5, 431.5, 6}, {270.5, 428.5, 6}, {279.5, 423.5, 6}},
  54. harvest = {{203.5, 482.5, 8}, {217.5, 485.5, 8}, {231.5, 482.5, 8}},
  55. refresh = {{289, 514, 13}},
  56. zeal = {{386.5, 402.5, 4}, {392.5, 406.5, 4}, {399.5, 407.5, 4}, {406.5, 406.5, 4}, {412.5, 402.5, 4}},
  57. absorb = {{387, 463, 7}, {400, 466, 7}, {413, 463, 7}},
  58. diffuse = {{400, 535, 11}},
  59. imbue = {{517.5, 424.5, 6}, {526.5, 429.5, 6}, {536.5, 432.5, 6}, {546.5, 429.5, 6}, {555.5, 424.5, 6}},
  60. mirror = {{565.5, 483.5, 8}, {578.5, 486.5, 8}, {592.5, 483.5, 8}},
  61. distort = {{508, 514, 13}}
  62. }
  63. }
  64. Hud.upgradePillows = {
  65. {g.newImage('media/graphics/pipe1.png'), 131, 238, check = function() return ctx.upgrades.zuju.empower.level >= 3 end, alpha = 0},
  66. {g.newImage('media/graphics/pipe2.png'), 177, 234, check = function() return ctx.upgrades.zuju.fortify.level >= 3 end, alpha = 0},
  67. {g.newImage('media/graphics/pipe3.png'), 230, 234, check = function() return ctx.upgrades.zuju.fortify.level >= 3 end, alpha = 0},
  68. {g.newImage('media/graphics/pipe4.png'), 284, 237, check = function() return ctx.upgrades.zuju.burst.level >= 3 end, alpha = 0},
  69. {g.newImage('media/graphics/pipe5.png'), 491, 238, check = function() return ctx.upgrades.vuju.surge.level >= 3 end, alpha = 0},
  70. {g.newImage('media/graphics/pipe6.png'), 537, 234, check = function() return ctx.upgrades.vuju.charge.level >= 3 end, alpha = 0},
  71. {g.newImage('media/graphics/pipe7.png'), 590, 234, check = function() return ctx.upgrades.vuju.charge.level >= 3 end, alpha = 0},
  72. {g.newImage('media/graphics/pipe8.png'), 644, 237, check = function() return ctx.upgrades.vuju.condemn.level >= 3 end, alpha = 0},
  73. {g.newImage('media/graphics/pipe9.png'), 205, 452, check = function() return ctx.upgrades.muju.flow.level >= 3 end, alpha = 0},
  74. {g.newImage('media/graphics/pipe10.png'), 216, 492, check = function() return ctx.upgrades.muju.harvest.level >= 1 end, alpha = 0},
  75. {g.newImage('media/graphics/pipe11.png'), 394, 437, check = function() return ctx.upgrades.muju.zeal.level >= 3 end, alpha = 0},
  76. {g.newImage('media/graphics/pipe12.png'), 390, 499, check = function() return ctx.upgrades.muju.absorb.level >= 1 end, alpha = 0},
  77. {g.newImage('media/graphics/pipe13.png'), 567, 452, check = function() return ctx.upgrades.muju.imbue.level >= 3 end, alpha = 0},
  78. {g.newImage('media/graphics/pipe14.png'), 559, 493, check = function() return ctx.upgrades.muju.mirror.level >= 1 end, alpha = 0},
  79. }
  80. function Hud:init()
  81. self.cursorImage = g.newImage('media/graphics/cursor.png')
  82. self.cursorX = g.getWidth() / 2
  83. self.cursorY = g.getHeight() / 2
  84. self.prevCursorX = self.cursorX
  85. self.prevCursorY = self.cursorY
  86. self.cursorSpeed = 0
  87. self.upgrading = false
  88. self.upgradeBg = g.newImage('media/graphics/upgrade-menu.png')
  89. self.upgradeCircles = g.newImage('media/graphics/upgrade-menu-circles.png')
  90. self.upgradeDot = g.newImage('media/graphics/level-icon.png')
  91. self.upgradeDotAlpha = {}
  92. self.lock = g.newImage('media/graphics/lock.png')
  93. self.upgradeAlpha = 0
  94. self.upgradesBought = 0
  95. self.tooltip = nil
  96. self.tooltipRaw = ''
  97. self.jujuIcon = g.newImage('media/graphics/juju-icon.png')
  98. self.jujuIconScale = .75
  99. self.timer = {total = 0, minutes = 0, seconds = 0}
  100. self.particles = Particles()
  101. self.selectBg = {g.newImage('media/graphics/select-zuju.png'), g.newImage('media/graphics/select-vuju.png')}
  102. self.selectFactor = {0, 0}
  103. self.selectExtra = {0, 0}
  104. self.selectQuad = {}
  105. self.selectQuad[1] = g.newQuad(0, 0, self.selectBg[1]:getWidth(), self.selectBg[1]:getHeight(), self.selectBg[1]:getWidth(), self.selectBg[1]:getHeight())
  106. self.selectQuad[2] = g.newQuad(0, 0, self.selectBg[2]:getWidth(), self.selectBg[2]:getHeight(), self.selectBg[2]:getWidth(), self.selectBg[2]:getHeight())
  107. self.deadAlpha = 0
  108. self.deadName = ''
  109. self.deadNameFrame = g.newImage('media/graphics/death-box.png')
  110. self.deadOk = g.newImage('media/graphics/death-ok.png')
  111. self.deadReplay = g.newImage('media/graphics/death-replay.png')
  112. self.deadQuit = g.newImage('media/graphics/death-quit.png')
  113. self.deadScreen = 1
  114. self.pauseAlpha = 0
  115. self.pauseScreen = g.newImage('media/graphics/pause-menu.png')
  116. self.tutorialIndex = 1
  117. self.tutorialTimer = 0
  118. self.tutorialEnabled = true or not love.filesystem.exists('playedBefore')
  119. self.tutorialImages = {
  120. [1] = g.newImage('media/graphics/tutorial-move1.png'),
  121. [2] = g.newImage('media/graphics/tutorial-summon.png'),
  122. [3] = g.newImage('media/graphics/tutorial-move2.png'),
  123. [3.5] = g.newImage('media/graphics/tutorial-juju.png'),
  124. [4] = g.newImage('media/graphics/tutorial-shrine.png'),
  125. [5] = g.newImage('media/graphics/tutorial-minions.png')
  126. }
  127. self.tutorialDirty = {}
  128. self.protectAlpha = 3
  129. love.filesystem.write('playedBefore', 'achievement unlocked.')
  130. ctx.view:register(self, 'gui')
  131. end
  132. function Hud:update()
  133. self.upgradeAlpha = math.lerp(self.upgradeAlpha, self.upgrading and 1 or 0, 12 * tickRate)
  134. self.deadAlpha = math.lerp(self.deadAlpha, ctx.ded and 1 or 0, 12 * tickRate)
  135. self.pauseAlpha = math.lerp(self.pauseAlpha, ctx.paused and 1 or 0, 12 * tickRate)
  136. self.protectAlpha = math.max(self.protectAlpha - tickRate, 0)
  137. self.jujuIconScale = math.lerp(self.jujuIconScale, .75, 12 * tickRate)
  138. for i = 1, #self.selectFactor do
  139. self.selectFactor[i] = math.lerp(self.selectFactor[i], ctx.player.selectedMinion == i and 1 or 0, 18 * tickRate)
  140. self.selectExtra[i] = math.lerp(self.selectExtra[i], 0, 5 * tickRate)
  141. if ctx.player.minions[i] then
  142. local y = self.selectBg[i]:getHeight() * (ctx.player.minioncds[i] / ctx.player.minions[i].cooldown)
  143. self.selectQuad[i]:setViewport(0, y, self.selectBg[i]:getWidth(), self.selectBg[i]:getHeight() - y)
  144. end
  145. end
  146. for i = 1, #self.upgradePillows do
  147. local pillow = self.upgradePillows[i]
  148. if pillow.check() then
  149. pillow.alpha = math.min(pillow.alpha + 2 * tickRate, 1)
  150. end
  151. end
  152. -- Tutorial hooks
  153. if self.tutorialEnabled and (not self.upgrading) and (not ctx.paused) then
  154. self.tutorialTimer = timer.rot(self.tutorialTimer)
  155. if self.tutorialTimer == 0 and tick > 2 / tickRate and not ctx.player.hasMoved and not self.tutorialDirty[1] then
  156. self.tutorialIndex = 1
  157. self.tutorialTimer = 2 * math.pi
  158. self.tutorialDirty[1] = true
  159. end
  160. if self.tutorialTimer == 0 and ctx.player.dead and ctx.player.ghost.first and not self.tutorialDirty[2] then
  161. self.tutorialIndex = 3
  162. self.tutorialTimer = 2 * math.pi
  163. self.tutorialDirty[2] = true
  164. end
  165. if self.tutorialTimer == 0 and tick > 8 / tickRate and ctx.player.summonedMinions == 0 and not ctx.player.dead and not self.tutorialDirty[3] then
  166. self.tutorialIndex = 2
  167. self.tutorialTimer = 2 * math.pi
  168. self.tutorialDirty[3] = true
  169. end
  170. if self.tutorialTimer == 0 and self.upgradesBought == 0 and tick > 35 / tickRate and ctx.player.juju >= 45 and not ctx.player.dead and not self.tutorialDirty[4] then
  171. self.tutorialIndex = 4
  172. self.tutorialTimer = 2 * math.pi
  173. self.tutorialDirty[4] = true
  174. end
  175. if self.tutorialTimer == 0 and #ctx.player.minions > 1 and not ctx.player.dead and not self.tutorialDirty[5] then
  176. self.tutorialIndex = 5
  177. self.tutorialTimer = 2 * math.pi
  178. self.tutorialDirty[5] = true
  179. end
  180. -- Tutorial unhooks
  181. local decay = function() while self.tutorialTimer > math.pi / 2 do self.tutorialTimer = self.tutorialTimer - math.pi / 2 end end
  182. if self.tutorialIndex == 1 and ctx.player.hasMoved then decay() end
  183. if self.tutorialIndex == 2 and (ctx.player.summonedMinions > 0 or ctx.player.dead) then decay() end
  184. if self.tutorialIndex == 3 and not ctx.player.dead then decay() end
  185. if self.tutorialIndex == 4 and self.upgradesBought > 0 then decay() end
  186. if self.tutorialIndex == 5 and ctx.player.selectedMinion == 2 then decay() end
  187. end
  188. -- Update Timer
  189. self:score()
  190. -- Virtual cursor for upgrades
  191. if ctx.player.gamepad then
  192. local vx, vy = 0, 0
  193. local xx, yy = ctx.player.gamepad:getGamepadAxis('leftx'), ctx.player.gamepad:getGamepadAxis('lefty')
  194. local cursorSpeed = 500
  195. local len = (xx * xx + yy * yy) ^ .5
  196. self.prevCursorX = self.cursorX
  197. self.prevCursorY = self.cursorY
  198. self.cursorSpeed = math.lerp(self.cursorSpeed, len > .2 and cursorSpeed or 0, 18 * tickRate)
  199. len = len ^ 4
  200. vx = xx / len
  201. vy = yy / len
  202. vx = math.clamp(vx, -1, 1)
  203. vy = math.clamp(vy, -1, 1)
  204. vx = vx * self.cursorSpeed * len
  205. vy = vy * self.cursorSpeed * len
  206. self.cursorX = self.cursorX + vx * tickRate
  207. self.cursorY = self.cursorY + vy * tickRate
  208. end
  209. for key in pairs(self.upgradeDotAlpha) do
  210. self.upgradeDotAlpha[key] = math.lerp(self.upgradeDotAlpha[key], 1, 5 * tickRate)
  211. if self.upgradeDotAlpha[key] > .999 then
  212. self.upgradeDotAlpha[key] = nil
  213. end
  214. end
  215. if self.upgradeAlpha > .001 then
  216. local mx, my = love.mouse.getPosition()
  217. local hover = false
  218. if ctx.player.gamepad then
  219. mx, my = self.cursorX, self.cursorY
  220. end
  221. for who in pairs(self.upgradeGeometry) do
  222. for what, geometry in pairs(self.upgradeGeometry[who]) do
  223. if math.distance(mx, my, geometry[1], geometry[2]) < geometry[3] then
  224. local str = ctx.upgrades.makeTooltip(who, what)
  225. self.tooltip = rich.new(table.merge({str, 300}, self.richOptions))
  226. self.tooltipRaw = str:gsub('{%a+}', '')
  227. hover = true
  228. break
  229. end
  230. end
  231. end
  232. if math.distance(mx, my, 560, 140) < 38 then
  233. if #ctx.player.minions < 2 then
  234. local color = ctx.player.juju >= 80 and '{green}' or '{red}'
  235. local str = '{white}{title}Vuju{normal}\n{whoCares}Casts chain lightning and hexes enemies.\n\n' .. color .. '{bold}80 juju'
  236. self.tooltip = rich.new(table.merge({str, 300}, self.richOptions))
  237. self.tooltipRaw = str:gsub('{%a+}', '')
  238. hover = true
  239. else
  240. local str = '{white}{title}Vuju{normal}\nUnlocked!'
  241. self.tooltip = rich.new(table.merge({str, 300}, self.richOptions))
  242. self.tooltipRaw = str:gsub('{%a+}', '')
  243. hover = true
  244. end
  245. end
  246. if math.distance(mx, my, 245, 140) < 38 then
  247. local str = '{white}{title}Zuju{normal}\nUnlocked!'
  248. self.tooltip = rich.new(table.merge({str, 300}, self.richOptions))
  249. self.tooltipRaw = str:gsub('{%a+}', '')
  250. hover = true
  251. end
  252. if not hover then self.tooltip = nil end
  253. end
  254. self.particles:update()
  255. if ctx.ded then love.keyboard.setKeyRepeat(true) end
  256. end
  257. function Hud:health(x, y, percent, color, width, thickness)
  258. local g = love.graphics
  259. thickness = thickness or 2
  260. g.setColor(0, 0, 0, 160)
  261. g.rectangle('fill', x, y, width + 1, thickness + 1)
  262. g.setColor(color)
  263. g.rectangle('fill', x, y, percent * width, thickness)
  264. end
  265. function Hud:stackingTable(stackingTable, x, range, delta)
  266. local limit = x + range
  267. for i = x - range, limit, 1 do
  268. if not stackingTable[i] then
  269. stackingTable[i] = 1
  270. else
  271. stackingTable[i] = stackingTable[i] + delta
  272. end
  273. end
  274. end
  275. function Hud:score()
  276. if not self.upgrading and not ctx.paused and not ctx.ded then
  277. self.timer.total = self.timer.total + 1
  278. end
  279. end
  280. function Hud:gui()
  281. local w, h = love.graphics.getDimensions()
  282. if not ctx.ded then
  283. -- Juju icon
  284. g.setFont(boldFont)
  285. if not self.upgrading then
  286. g.setColor(255, 255, 255, 255 * (1 - self.upgradeAlpha))
  287. g.draw(self.jujuIcon, 52, 55, 0, self.jujuIconScale, self.jujuIconScale, self.jujuIcon:getWidth() / 2, self.jujuIcon:getHeight() / 2)
  288. g.setColor(0, 0, 0)
  289. g.printf(math.floor(ctx.player.juju), 16, 18 + self.jujuIcon:getHeight() * .375 - (g.getFont():getHeight() / 2), self.jujuIcon:getWidth() * .75, 'center')
  290. g.setColor(255, 255, 255)
  291. end
  292. -- Timer
  293. local total = self.timer.total * tickRate
  294. self.timer.seconds = math.floor(total % 60)
  295. self.timer.minutes = math.floor(total / 60)
  296. if self.timer.minutes < 10 then
  297. self.timer.minutes = '0' .. self.timer.minutes
  298. end
  299. if self.timer.seconds < 10 then
  300. self.timer.seconds = '0' .. self.timer.seconds
  301. end
  302. local str = self.timer.minutes .. ':' .. self.timer.seconds
  303. g.setColor(255, 255, 255)
  304. g.print(str, w - 25 - g.getFont():getWidth(str), 25)
  305. -- Minion indicator
  306. local yy = 135
  307. for i = 1, #ctx.player.minions do
  308. local bg = self.selectBg[i]
  309. local scale = .75 + (.15 * self.selectFactor[i]) + (.1 * self.selectExtra[i])
  310. local xx = 48 - 10 * (1 - self.selectFactor[i])
  311. local f, cost = g.getFont(), tostring(ctx.player.minions[i]:getCost())
  312. local tx, ty = xx - f:getWidth(cost) / 2 - (bg:getWidth() * .75 / 2) + 4, yy - f:getHeight() / 2 - (bg:getHeight() * .75 / 2) + 4
  313. local alpha = .65 + self.selectFactor[i] * .35
  314. -- Backdrop
  315. g.setColor(255, 255, 255, 80 * alpha)
  316. g.draw(bg, xx, yy, 0, scale, scale, bg:getWidth() / 2, bg:getHeight() / 2)
  317. -- Cooldown
  318. local _, qy = self.selectQuad[i]:getViewport()
  319. g.setColor(255, 255, 255, (150 + (100 * (ctx.player.minioncds[i] == 0 and 1 or 0))) * alpha)
  320. g.draw(bg, self.selectQuad[i], xx, yy + qy * scale, 0, scale, scale, bg:getWidth() / 2, bg:getHeight() / 2)
  321. -- Juice
  322. g.setBlendMode('additive')
  323. g.setColor(255, 255, 255, 60 * self.selectExtra[i])
  324. g.draw(bg, xx, yy, 0, scale + .2 * self.selectExtra[i], scale + .2 * self.selectExtra[i], bg:getWidth() / 2, bg:getHeight() / 2)
  325. g.setBlendMode('alpha')
  326. -- Cost
  327. g.setColor(0, 0, 0, 200 + 55 * self.selectFactor[i])
  328. g.print(cost, tx + 1, ty + 1)
  329. g.setColor(255, 255, 255, 200 + 55 * self.selectFactor[i])
  330. g.print(cost, tx, ty)
  331. yy = yy + self.selectBg[i]:getHeight() * 1
  332. end
  333. -- Health Bars
  334. local px, py = math.lerp(ctx.player.prevx, ctx.player.x, tickDelta / tickRate), math.lerp(ctx.player.prevy, ctx.player.y, tickDelta / tickRate)
  335. local green = {50, 230, 50}
  336. local red = {255, 0, 0}
  337. local purple = {200, 80, 255}
  338. self:health(px - 40, py - 15, ctx.player.healthDisplay / ctx.player.maxHealth, purple, 80, 3)
  339. self:health(ctx.shrine.x - 60, ctx.shrine.y - 65, ctx.shrine.healthDisplay / ctx.shrine.maxHealth, green, 120, 4)
  340. local stackingTable = {}
  341. table.each(ctx.enemies.enemies, function(enemy)
  342. local location = math.floor(enemy.x)
  343. self:stackingTable(stackingTable, location, enemy.width * 2, .5)
  344. self:health(enemy.x - 25, h - ctx.environment.groundHeight - enemy.height - 15 - 15 * stackingTable[location], enemy.healthDisplay / enemy.maxHealth, red, 50, 2)
  345. end)
  346. stackingTable = {}
  347. table.each(ctx.minions.minions, function(minion)
  348. local location = math.floor(minion.x)
  349. self:stackingTable(stackingTable, location, minion.width * 2, .5)
  350. self:health(minion.x - 25, h - ctx.environment.groundHeight - minion.height - 15 * stackingTable[location], minion.healthDisplay / minion.maxHealth, green, 50, 2)
  351. end)
  352. -- Tutorial
  353. if self.tutorialEnabled and self.tutorialTimer > 0 then
  354. g.setColor(255, 255, 255, 255 * math.abs(math.sin(self.tutorialTimer)))
  355. local x, y
  356. local ox, oy = 0, 0
  357. local scale
  358. local img = self.tutorialImages[self.tutorialIndex]
  359. if self.tutorialIndex == 1 then
  360. x, y = math.lerp(ctx.player.prevx, ctx.player.x, tickDelta / tickRate), math.lerp(ctx.player.prevy, ctx.player.y, tickDelta / tickRate) - 50
  361. ox, oy = img:getWidth() / 2, img:getHeight() / 2
  362. scale = .4
  363. elseif self.tutorialIndex == 2 then
  364. x, y = 48 + self.selectBg[1]:getWidth() * .45 + 16, 135 + self.selectBg[1]:getHeight() * .45 / 2 - 8
  365. ox, oy = 1, 56
  366. scale = .4
  367. elseif self.tutorialIndex == 3 then
  368. if not ctx.player.ghost then x, y = -1000, -1000
  369. else
  370. x, y = math.lerp(ctx.player.ghost.prevx, ctx.player.ghost.x, tickDelta / tickRate), math.lerp(ctx.player.ghost.prevy, ctx.player.ghost.y, tickDelta / tickRate) - 80
  371. ox, oy = img:getWidth() / 2, img:getHeight() / 2
  372. scale = .3
  373. end
  374. g.draw(self.tutorialImages[3.5], 100, 90, 0, .45, .45)
  375. elseif self.tutorialIndex == 4 then
  376. ox, oy = 440, 400
  377. x, y = ctx.shrine.x, ctx.shrine.y - 85
  378. scale = .4
  379. elseif self.tutorialIndex == 5 then
  380. x, y = 48 + self.selectBg[1]:getWidth() * .4 + 16, 135
  381. scale = .4
  382. end
  383. g.draw(img, x, y, 0, scale, scale, ox, oy)
  384. end
  385. -- Protect message
  386. if self.protectAlpha > .1 then
  387. g.setFont(deadFontBig)
  388. g.setColor(0, 0, 0, 150 * math.min(self.protectAlpha, 1))
  389. g.printf('Protect Your Shrine!', 2, h * .25 + 2, w, 'center')
  390. g.setColor(253, 238, 65, 255 * math.min(self.protectAlpha, 1))
  391. g.printf('Protect Your Shrine!', 0, h * .25, w, 'center')
  392. g.setFont(boldFont)
  393. end
  394. -- Pause Menu
  395. if self.pauseAlpha > .01 then
  396. g.setColor(0, 0, 0, 128 * self.pauseAlpha)
  397. g.rectangle('fill', 0, 0, g.getDimensions())
  398. g.setColor(255, 255, 255, 255 * self.pauseAlpha)
  399. g.draw(self.pauseScreen, w * .5, h * .5, 0, 1, 1, self.pauseScreen:getWidth() / 2, self.pauseScreen:getHeight() / 2)
  400. end
  401. end
  402. -- Upgrade screen
  403. if self.upgradeAlpha > .001 and not ctx.ded then
  404. local mx, my = love.mouse.getPosition()
  405. local w2, h2 = w / 2, h / 2
  406. g.setColor(255, 255, 255, self.upgradeAlpha * 250)
  407. g.draw(self.upgradeBg, 400, 300, 0, .875, .875, self.upgradeBg:getWidth() / 2, self.upgradeBg:getHeight() / 2)
  408. for i = 1, #self.upgradePillows do
  409. local pillow = self.upgradePillows[i]
  410. if pillow.check() then
  411. g.setColor(255, 255, 255, 255 * pillow.alpha)
  412. local img, x, y = unpack(pillow)
  413. x = ((x - 400) * .875) + 400
  414. y = ((y - 313) * .875) + 300
  415. g.draw(img, x, y, 0, .875, .875)
  416. end
  417. end
  418. g.setColor(255, 255, 255, self.upgradeAlpha * 250)
  419. g.draw(self.upgradeCircles, 400, 300, 0, 1, 1, self.upgradeCircles:getWidth() / 2, self.upgradeCircles:getHeight() / 2)
  420. g.setColor(0, 0, 0, self.upgradeAlpha * 250)
  421. local str = tostring(math.floor(ctx.player.juju))
  422. g.print(str, w2 - g.getFont():getWidth(str) / 2, 65)
  423. for who in pairs(self.upgradeDotGeometry) do
  424. for what in pairs(self.upgradeDotGeometry[who]) do
  425. for i = 1, ctx.upgrades[who][what].level do
  426. local info = self.upgradeDotGeometry[who][what][i]
  427. if info then
  428. local x, y, scale = unpack(info)
  429. local dot = self.upgradeDot
  430. local w, h = dot:getDimensions()
  431. g.setColor(255, 255, 255, (self.upgradeDotAlpha[who .. what .. i] or 1) * 255 * self.upgradeAlpha)
  432. g.draw(dot, x + .5, y + .5, 0, scale / w, scale / h, w / 2, h / 2)
  433. end
  434. end
  435. end
  436. end
  437. g.setColor(255, 255, 255, 220 * self.upgradeAlpha)
  438. local lw, lh = self.lock:getDimensions()
  439. for who in pairs(self.upgradeGeometry) do
  440. for what, geometry in pairs(self.upgradeGeometry[who]) do
  441. if not ctx.upgrades.checkPrerequisites(who, what) then
  442. local scale = math.min(geometry[3] / lw, geometry[3] / lh) + .1
  443. g.draw(self.lock, geometry[1], geometry[2], 0, scale, scale, lw / 2, lh / 2)
  444. end
  445. end
  446. end
  447. if self.tooltip then
  448. local mx, my = love.mouse.getPosition()
  449. if ctx.player.gamepad then
  450. mx, my = math.lerp(self.prevCursorX, self.cursorX, tickDelta / tickRate), math.lerp(self.prevCursorY, self.cursorY, tickDelta / tickRate)
  451. mx, my = math.round(mx), math.round(my)
  452. end
  453. local textWidth, lines = normalFont:getWrap(self.tooltipRaw, 300)
  454. local xx = math.min(mx + 8, love.graphics.getWidth() - textWidth - 24)
  455. local yy = math.min(my + 8, love.graphics.getHeight() - (lines * g.getFont():getHeight() + 16 + 7))
  456. g.setColor(30, 50, 70, 240)
  457. g.rectangle('fill', xx, yy, textWidth + 14, lines * g.getFont():getHeight() + 16 + 5)
  458. g.setColor(10, 30, 50, 255)
  459. g.rectangle('line', xx + .5, yy + .5, textWidth + 14, lines * g.getFont():getHeight() + 16 + 5)
  460. self.tooltip:draw(xx + 8, yy + 4)
  461. end
  462. end
  463. -- Death Screen
  464. if ctx.ded then
  465. if self.deadScreen == 1 then
  466. g.setColor(244, 188, 80, 255 * self.deadAlpha)
  467. g.setFont(deadFontBig)
  468. local str = 'YOUR SHRINE HAS BEEN DESTROYED!'
  469. g.printf(str, 50, 30, 700, 'center')
  470. g.setColor(253, 238, 65, 255 * self.deadAlpha)
  471. g.setFont(deadFontSmall)
  472. str = 'Your Score:'
  473. g.printf(str, 0, h * .325, w, 'center')
  474. g.setColor(240, 240, 240, 255 * self.deadAlpha)
  475. str = tostring(math.floor(self.timer.total * tickRate))
  476. g.printf(str, 0, h * .41, w, 'center')
  477. g.setColor(253, 238, 65, 255 * self.deadAlpha)
  478. str = 'Your Name:'
  479. g.printf(str, 0, h * .51, w, 'center')
  480. g.setColor(255, 255, 255, 255 * self.deadAlpha)
  481. g.draw(self.deadNameFrame, w / 2 - self.deadNameFrame:getWidth() / 2, h * .584)
  482. g.setColor(240, 240, 240, 255 * self.deadAlpha)
  483. local font = g.getFont()
  484. local scale = 1
  485. while font:getWidth(self.deadName) * scale > self.deadNameFrame:getWidth() - 24 do scale = scale - .05 end
  486. local xx = w / 2 - font:getWidth(self.deadName) * scale / 2
  487. local yy = h * .584 + (self.deadNameFrame:getHeight() / 2) - font:getHeight() * scale / 2
  488. g.print(self.deadName, xx, yy, 0, scale, scale)
  489. local cursorx = xx + font:getWidth(self.deadName) * scale + 1
  490. g.line(cursorx, yy, cursorx, yy + font:getHeight() * scale)
  491. g.setColor(255, 255, 255, 255 * self.deadAlpha)
  492. g.draw(self.deadOk, w / 2 - self.deadOk:getWidth() / 2, h * .825)
  493. else
  494. if self.highscores then
  495. g.setColor(253, 238, 65, 255 * self.deadAlpha)
  496. g.setFont(deadFontSmall)
  497. g.printf('Highscores', 0, h * .05, w, 'center')
  498. g.setFont(fancyFont)
  499. g.setColor(255, 255, 255, 255 * self.deadAlpha)
  500. local yy = h * .2
  501. for _, entry in ipairs(self.highscores) do
  502. g.print(entry.who, w * .3, yy)
  503. g.printf(entry.what, 0, yy, w * .7, 'right')
  504. yy = yy + g.getFont():getHeight() + 4
  505. end
  506. g.draw(self.deadReplay, w * .4, h * .825, 0, 1, 1, self.deadReplay:getWidth() / 2)
  507. g.draw(self.deadQuit, w * .6, h * .825, 0, 1, 1, self.deadQuit:getWidth() / 2)
  508. else
  509. g.setColor(253, 238, 65, 255 * self.deadAlpha)
  510. g.setFont(deadFontSmall)
  511. g.printf('Unable to load highscores :[', 0, h * .4, w, 'center')
  512. g.draw(self.deadReplay, w * .4, h * .825, 0, 1, 1, self.deadReplay:getWidth() / 2)
  513. g.draw(self.deadQuit, w * .6, h * .825, 0, 1, 1, self.deadQuit:getWidth() / 2)
  514. end
  515. end
  516. end
  517. if self.upgrading or ctx.paused or ctx.ded then
  518. if ctx.player.gamepad then
  519. local xx, yy = math.lerp(self.prevCursorX, self.cursorX, tickDelta / tickRate), math.lerp(self.prevCursorY, self.cursorY, tickDelta / tickRate)
  520. g.setColor(255, 255, 255)
  521. g.draw(self.cursorImage, xx, yy)
  522. end
  523. end
  524. end
  525. function Hud:keypressed(key)
  526. if (key == 'tab' or key == 'e') and math.abs(ctx.player.x - ctx.shrine.x) < ctx.player.width and not ctx.ded then
  527. self.upgrading = not self.upgrading
  528. return true
  529. end
  530. if key == 'escape' and self.upgrading and not ctx.ded then
  531. self.upgrading = false
  532. end
  533. if ctx.ded and self.deadAlpha > .9 then
  534. if key == 'backspace' then
  535. self.deadName = self.deadName:sub(1, -2)
  536. elseif key == 'return' then
  537. if self.deadScreen == 1 then self:sendScore() end
  538. end
  539. if key == 'escape' then
  540. Context:remove(ctx)
  541. Context:add(Menu)
  542. end
  543. end
  544. end
  545. function Hud:keyreleased(key)
  546. --
  547. end
  548. function Hud:textinput(char)
  549. if ctx.ded then
  550. if #self.deadName < 16 and char:match('%w') then
  551. self.deadName = self.deadName .. char
  552. end
  553. end
  554. end
  555. function Hud:gamepadpressed(gamepad, button)
  556. if gamepad == ctx.player.gamepad and not ctx.ded then
  557. if (button == 'x' or button == 'y') and math.abs(ctx.player.x - ctx.shrine.x) < ctx.player.width then
  558. self.upgrading = not self.upgrading
  559. self.cursorX = g.getWidth() / 2
  560. self.cursorY = g.getHeight() / 2
  561. self.prevCursorX = self.cursorX
  562. self.prevCursorY = self.cursorY
  563. return true
  564. end
  565. if button == 'a' and (self.upgrading or ctx.paused or ctx.ded) then
  566. self:mousepressed(self.cursorX, self.cursorY, 'l')
  567. self:mousereleased(self.cursorX, self.cursorY, 'l')
  568. end
  569. end
  570. end
  571. function Hud:mousepressed(x, y, b)
  572. if not self.upgrading or ctx.ded then return end
  573. if math.inside(x, y, 670, 502, 48, 48) then
  574. self.upgrading = false
  575. end
  576. end
  577. function Hud:mousereleased(x, y, b)
  578. if self.upgrading and b == 'l' and not ctx.ded then
  579. for who in pairs(self.upgradeGeometry) do
  580. for what, geometry in pairs(self.upgradeGeometry[who]) do
  581. if math.distance(x, y, geometry[1], geometry[2]) < geometry[3] then
  582. local upgrade = ctx.upgrades[who][what]
  583. local nextLevel = upgrade.level + 1
  584. local cost = upgrade.costs[nextLevel]
  585. if ctx.upgrades.canBuy(who, what) and ctx.player:spend(cost) then
  586. ctx.upgrades[who][what].level = nextLevel
  587. ctx.sound:play({sound = 'menuClick'})
  588. for i = 1, 80 do
  589. self.particles:add(UpgradeParticle, {x = x, y = y})
  590. end
  591. self.upgradeDotAlpha[who .. what .. nextLevel] = 0
  592. end
  593. end
  594. end
  595. end
  596. if #ctx.player.minions < 2 and math.distance(x, y, 560, 140) < 38 and ctx.player:spend(80) then
  597. table.insert(ctx.player.minions, Vuju)
  598. table.insert(ctx.player.minioncds, 0)
  599. for i = 1, 100 do
  600. self.particles:add(UpgradeParticle, {x = x, y = y})
  601. end
  602. self.upgradesBought = self.upgradesBought + 1
  603. end
  604. end
  605. if b == 'l' and ctx.ded then
  606. if self.deadScreen == 1 then
  607. local img = self.deadOk
  608. local w2 = g.getWidth() / 2
  609. if math.inside(x, y, w2 - img:getWidth() / 2, g.getHeight() * .825, img:getDimensions()) then
  610. self:sendScore()
  611. end
  612. elseif self.deadScreen == 2 then
  613. local img1 = self.deadReplay
  614. local img2 = self.deadQuit
  615. local w = g.getWidth()
  616. local h = g.getHeight()
  617. if math.inside(x, y, w * .4 - img1:getWidth() / 2, h * .825, img1:getDimensions()) then
  618. Context:remove(ctx)
  619. Context:add(Game)
  620. elseif math.inside(x, y, w * .6 - img2:getWidth() / 2, h * .825, img2:getDimensions()) then
  621. Context:remove(ctx)
  622. Context:add(Menu)
  623. end
  624. end
  625. end
  626. if b == 'l' and ctx.paused then
  627. local w, h = g.getDimensions()
  628. if math.inside(x, y, w * .4, h * .4, 155, 60) then
  629. ctx.paused = not ctx.paused
  630. elseif math.inside(x, y, w * .4, h * .51, 155, 60) then
  631. Context:remove(ctx)
  632. Context:add(Menu)
  633. end
  634. end
  635. end
  636. function Hud:sendScore()
  637. self.highscores = nil
  638. if #self.deadName > 0 then
  639. local seconds = math.floor(self.timer.total * tickRate)
  640. local http = require('socket.http')
  641. http.TIMEOUT = 5
  642. local response = http.request('http://plasticsarcastic.com/mujuJuju/score.php?name=' .. self.deadName .. '&score=' .. seconds)
  643. if response then
  644. self.highscores = {}
  645. for who, what, when in response:gmatch('(%w+)%,(%w+)%,(%w+)') do
  646. table.insert(self.highscores, {who = who, what = what, when = when})
  647. end
  648. end
  649. end
  650. self.deadScreen = 2
  651. end