menucampaign.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. local g = love.graphics
  2. MenuCampaign = class()
  3. local function lerpAnimation(code, key, val)
  4. ctx.prevAnimationTransforms[code][key] = ctx.animationTransforms[code][key]
  5. ctx.animationTransforms[code][key] = math.lerp(ctx.animationTransforms[code][key] or val, val, math.min(10 * ls.tickrate, 1))
  6. end
  7. local function lerpRune(rune, key, val)
  8. ctx.campaign.prevRuneTransforms[rune][key] = ctx.campaign.runeTransforms[rune][key]
  9. ctx.campaign.runeTransforms[rune][key] = math.lerp(ctx.campaign.runeTransforms[rune][key] or val, val, math.min(10 * ls.tickrate, 1))
  10. end
  11. function MenuCampaign:init()
  12. self.geometry = setmetatable({}, {__index = function(t, k)
  13. return rawset(t, k, self.geometryFunctions[k]())[k]
  14. end})
  15. self.geometryFunctions = {
  16. minionFrame = function()
  17. local u, v = ctx.u, ctx.v
  18. return {.07 * u - v * .02, .14 * v, self.geometry.runesFrame[3], .45 * v}
  19. end,
  20. minion = function()
  21. local u, v = ctx.u, ctx.v
  22. local size = .1125 * u
  23. local runeSize = .045 * u
  24. local runeInc = runeSize + .02 * v
  25. local x = .07 * u + size / 2 + .04 * v
  26. local y = .35 * v
  27. local runex = x - (runeInc * (3 - 1) / 2)
  28. local runey = y + .18 * v
  29. local res = {x, y, size / 2, {}}
  30. for i = 1, 3 do
  31. table.insert(res[4], {runex - runeSize / 2, runey - runeSize / 2, runeSize, runeSize})
  32. runex = runex + runeInc
  33. end
  34. return res
  35. end,
  36. runes = function()
  37. local u, v = ctx.u, ctx.v
  38. local size = .045 * u
  39. local inc = size + .01 * v
  40. local x = u * .07
  41. local ox = x
  42. local y = v * .675
  43. local res = {}
  44. for i = 1, 33 do
  45. table.insert(res, {math.round(x), math.round(y), size, size})
  46. x = x + inc
  47. if i % 11 == 0 then y = y + inc x = ox end
  48. end
  49. return res
  50. end,
  51. runesLabel = function()
  52. local u, v = ctx.u, ctx.v
  53. return {u * .07, v * .675 - v * .015 - v * .04}
  54. end,
  55. runesFrame = function()
  56. local u, v = ctx.u, ctx.v
  57. local label = self.geometry.runesLabel
  58. local x = label[1] - v * .02
  59. local y = label[2] - v * .01
  60. local w = ((self.geometry.runes[1][4] + .01 * v) * 11) + v * .03
  61. local h = ((self.geometry.runes[1][4] + .01 * v) * 3) + v * .055 + v * .02
  62. return {x, y, w, h}
  63. end,
  64. map = function()
  65. local u, v = ctx.u, ctx.v
  66. local width = .4 * v
  67. local height = width * (10 / 16)
  68. local x = u * .8
  69. local y = .22 * v
  70. return {x - width / 2, y, width, height}
  71. end,
  72. play = function()
  73. local u, v = ctx.u, ctx.v
  74. local frame = self.geometry.minionFrame
  75. local w, h = .2 * u, .13 * v
  76. local midx = (u + frame[1] + frame[3]) / 2
  77. return {midx - w / 2, .5 * v, w, h}
  78. end,
  79. muju = function()
  80. local u, v = ctx.u, ctx.v
  81. local x, y = u * .72, v * .8
  82. local res = {x, y, {}}
  83. local hats = ctx.user.hats or {}
  84. local hatSize = .04 * v
  85. local hatInc = hatSize + .08 * v
  86. local hatX = x + .12 * u
  87. local hatY = .77 * v
  88. for i = 1, math.min(#hats, 4) do
  89. table.insert(res[3], {hatX, hatY, hatSize})
  90. hatX = hatX + hatInc
  91. if i % 2 == 0 then
  92. hatX = x + .12 * u
  93. hatY = hatY + hatInc
  94. end
  95. end
  96. return res
  97. end,
  98. hatsFrame = function()
  99. local u, v = ctx.u, ctx.v
  100. local x, y = unpack(self.geometry.muju)
  101. local hatSize = .04 * v
  102. local hatInc = hatSize + .08 * v
  103. local hatX = x + .12 * u
  104. local hatY = .77 * v
  105. local padding = .04 * v
  106. return {hatX - hatSize / 2 - padding, hatY - hatSize / 2 - padding, 2 * padding + 2 * hatSize + .08 * v, 2 * padding + 2 * hatSize + .08 * v}
  107. end
  108. }
  109. self.play = ctx.gooey:add(Button, 'menu.campaign.play')
  110. self.play.geometry = function() return self.geometry.play end
  111. self.play:on('click', function() ctx.animations.muju:set('death') end)
  112. self.play.text = 'Play'
  113. self.map = MenuMap()
  114. self.drag = MenuCampaignDrag()
  115. end
  116. function MenuCampaign:activate()
  117. table.clear(self.geometry)
  118. -- Initialize runes
  119. self.runeTransforms = {}
  120. self.prevRuneTransforms = {}
  121. table.each(ctx.user.runes, function(list)
  122. table.each(list, function(rune)
  123. self.runeTransforms[rune] = {}
  124. self.prevRuneTransforms[rune] = {}
  125. end)
  126. end)
  127. self.hatHoverFactors = {}
  128. self.prevHatHoverFactors = {}
  129. self.map.active = true
  130. self.drag.active = true
  131. end
  132. function MenuCampaign:deactivate()
  133. self.map.active = false
  134. self.drag.active = false
  135. end
  136. function MenuCampaign:update()
  137. if not self.active then return end
  138. self.map:update()
  139. if not self.biome then return end
  140. local mx, my = love.mouse.getPosition()
  141. local u, v = ctx.u, ctx.v
  142. local minion = config.biomes[self.biome].minion
  143. local runes = self.geometry.minion[4]
  144. for i = 1, 3 do
  145. local rune = ctx.user.runes[minion][i]
  146. if rune and not self.drag:isDragging(minion, i) then
  147. local x, y, w, h = unpack(runes[i])
  148. lerpRune(rune, 'x', x + w / 2)
  149. lerpRune(rune, 'y', y + h / 2)
  150. lerpRune(rune, 'size', h)
  151. if not self.map.focused and math.inside(mx, my, x, y, w, h) then
  152. ctx.tooltip:setRuneTooltip(rune)
  153. end
  154. end
  155. end
  156. local runes = self.geometry.runes
  157. for i = 1, 32 do
  158. local rune = ctx.user.runes.stash[i]
  159. if rune and not self.drag:isDragging('stash', i) then
  160. local x, y, w, h = unpack(runes[i])
  161. lerpRune(rune, 'x', x + w / 2)
  162. lerpRune(rune, 'y', y + h / 2)
  163. lerpRune(rune, 'size', h)
  164. if not self.map.focused and math.inside(mx, my, x, y, w, h) then
  165. ctx.tooltip:setRuneTooltip(rune)
  166. end
  167. end
  168. end
  169. local _, _, hats = unpack(self.geometry.muju)
  170. for i = 1, #ctx.user.hats do
  171. local hat = ctx.user.hats[i]
  172. if hat and hats[i] then
  173. local hover = math.insideCircle(mx, my, unpack(hats[i]))
  174. self.prevHatHoverFactors[hat] = self.hatHoverFactors[hat] or 0
  175. self.hatHoverFactors[hat] = math.lerp(self.hatHoverFactors[hat] or 0, hover and 1 or 0, math.min(10 * ls.tickrate, 1))
  176. end
  177. end
  178. self.drag:update()
  179. end
  180. function MenuCampaign:draw()
  181. if not self.active then return end
  182. if not self.biome then
  183. return self.map:draw()
  184. end
  185. local u, v = ctx.u, ctx.v
  186. local ps = love.window.getPixelScale()
  187. local atlas = data.atlas.hud
  188. local minion = config.biomes[self.biome].minion
  189. -- Medals and rewards
  190. local detailsAlpha = 255
  191. local biome = self.biome
  192. local midx = self.geometry.play[1] + self.geometry.play[3] / 2
  193. local medalSize = u * .0225
  194. local medalInc = (medalSize * 4)
  195. local medalX = midx - medalInc * (3 - 1) / 2
  196. local medalY = .2 * v + medalSize
  197. for i, benchmark in ipairs({'bronze', 'silver', 'gold'}) do
  198. local achieved = ctx.user.campaign.medals[biome][benchmark]
  199. g.setColor(255, 255, 255, (achieved and 1 or .4) * detailsAlpha)
  200. local image = data.media.graphics.menu[benchmark]
  201. local scale = medalSize * 2 / image:getWidth() * (achieved and 1 or .8)
  202. g.draw(image, medalX, medalY, 0, scale, scale, image:getWidth() / 2, image:getHeight() / 2)
  203. if benchmark == 'bronze' then
  204. local qw, qh = atlas:getDimensions('runeBgBroken')
  205. local scale = (medalSize * 2 + (.02 * v) * math.sin(tick / 10) / 8) / qw
  206. g.setColor(achieved and {255, 255, 255} or {0, 0, 0})
  207. g.draw(atlas.texture, atlas.quads.runeBgBroken, medalX, medalY + .14 * v, 0, scale, scale, qw / 2, qh / 2)
  208. elseif benchmark == 'silver' then
  209. local nextMinions = {
  210. forest = 'xuju',
  211. cavern = 'kuju',
  212. tundra = 'thuju'
  213. }
  214. local nextMinion = nextMinions[self.biome]
  215. if nextMinion then
  216. local cw, ch = ctx.unitCanvas:getDimensions()
  217. ctx.unitCanvas:clear(0, 0, 0, 0)
  218. ctx.unitCanvas:renderTo(function()
  219. local animation = ctx.animations[nextMinion]
  220. if not achieved then
  221. animation.spine.skeleton.r = 0
  222. animation.spine.skeleton.g = 0
  223. animation.spine.skeleton.b = 0
  224. end
  225. animation:draw(cw / 2, ch / 2)
  226. animation.spine.skeleton.r = 1
  227. animation.spine.skeleton.g = 1
  228. animation.spine.skeleton.b = 1
  229. end)
  230. local scale = (.1 * v / cw) * 3
  231. g.setColor(255, 255, 255)
  232. g.draw(ctx.unitCanvas, medalX, medalY + .17 * v, 0, scale, scale, cw / 2, ch / 2)
  233. end
  234. elseif benchmark == 'gold' then
  235. local image = data.media.graphics.hats[ctx.user.campaign.hatHistory[self.biome] or 'wizard']
  236. local scale = (medalSize * 2 + (.02 * v) * math.sin(tick / 10) / 8) / image:getWidth()
  237. g.setColor(achieved and {255, 255, 255} or {0, 0, 0})
  238. g.draw(image, medalX, medalY + .14 * v, 0, scale, scale, image:getWidth() / 2, image:getHeight() / 2)
  239. end
  240. if not achieved then
  241. g.setColor(255, 255, 255)
  242. g.setFont('mesmerize', .05 * v)
  243. g.printCenter('?', medalX, medalY + .14 * v)
  244. end
  245. medalX = medalX + medalInc
  246. end
  247. -- Rune Frame
  248. g.setColor(0, 0, 0, 100)
  249. g.rectangle('fill', unpack(self.geometry.runesFrame))
  250. -- Rune Frames
  251. g.setColor(255, 255, 255)
  252. local runes = self.geometry.runes
  253. for i = 1, #runes do
  254. local x, y, w, h = unpack(runes[i])
  255. local scale = w / atlas:getDimensions('frame')
  256. g.setColor(255, 255, 255)
  257. g.draw(atlas.texture, atlas.quads.frame, x, y, 0, scale, scale)
  258. end
  259. -- Runes
  260. for i = 1, #runes do
  261. local x, y, w, h = unpack(runes[i])
  262. if i == 33 then
  263. local image = data.media.graphics.menu.trashcan
  264. local scale = (h - .025 * v) / image:getHeight()
  265. local dragAlpha = math.lerp(self.drag.prevDragAlpha, self.drag.dragAlpha, ls.accum / ls.tickrate)
  266. g.setColor(255, 255, 255, 150 + 100 * dragAlpha)
  267. g.draw(image, x + w / 2, y + h / 2, 0, scale, scale, image:getWidth() / 2, image:getHeight() / 2)
  268. else
  269. local rune = ctx.user.runes.stash[i]
  270. if rune and not self.drag:isDragging('stash', i) then
  271. local lerpd = {}
  272. for k, v in pairs(ctx.campaign.runeTransforms[rune]) do
  273. lerpd[k] = math.lerp(ctx.campaign.prevRuneTransforms[rune][k] or v, v, ls.accum / ls.tickrate)
  274. end
  275. g.setColor(255, 255, 255)
  276. g.drawRune(rune, lerpd.x, lerpd.y, lerpd.size - .015 * v, (lerpd.size - .015 * v) * .5, table.has(ctx.rewards.runes, rune))
  277. end
  278. end
  279. end
  280. -- Rune Label
  281. g.setColor(255, 255, 255)
  282. g.setFont('mesmerize', v * .04)
  283. local x, y = unpack(self.geometry.runesLabel)
  284. g.print('Runes', x, y)
  285. -- Minion Frame
  286. g.setColor(0, 0, 0, 100)
  287. g.rectangle('fill', unpack(self.geometry.minionFrame))
  288. -- Minion Text
  289. local unit = data.unit[minion]
  290. local x = .07 * u + .2 * u
  291. local textWidth = self.geometry.minionFrame[1] + self.geometry.minionFrame[3] - x - .02 * v
  292. g.setColor(255, 255, 255)
  293. g.setFont('mesmerize', .08 * v)
  294. g.printShadow(unit.name, x, .16 * v)
  295. g.setFont('mesmerize', .02 * v)
  296. g.setColor(0, 0, 0)
  297. g.printf(unit.description, x + 1, .26 * v + 1, textWidth)
  298. g.setColor(255, 255, 255)
  299. g.printf(unit.description, x, .26 * v, textWidth)
  300. -- Minion Featured
  301. local _, lines = g.getFont():getWrap(unit.description, textWidth)
  302. local y = .26 * v + lines * g.getFont():getHeight() + .02 * v
  303. local height = self.geometry.minionFrame[2] + self.geometry.minionFrame[4] - y - .01 * v
  304. local h = math.max(height / #unit.featured - .01 * v, 0)
  305. for i = 1, #unit.featured do
  306. local qw, qh = atlas:getDimensions('frame')
  307. local scale = h / qh
  308. g.draw(atlas.texture, atlas.quads.frame, x, y, 0, scale, scale)
  309. local qw, qh = atlas:getDimensions(unit.featured[i][1])
  310. if qw then
  311. local scale = (h * .75) / math.max(qw, qh)
  312. g.draw(atlas.texture, atlas.quads[unit.featured[i][1]], x + h / 2, y + h / 2, 0, scale, scale, qw / 2, qh / 2)
  313. end
  314. g.setFont('mesmerize', .02 * v)
  315. local str = unit.upgrades[unit.featured[i][1]].name .. ': ' .. unit.featured[i][2]
  316. local textWidth = textWidth - h - .01 * v
  317. local _, lines = g.getFont():getWrap(str, textWidth)
  318. local textHeight = g.getFont():getHeight() * lines
  319. g.setColor(0, 0, 0)
  320. g.printf(str, x + h + .01 * v + 1, y + h / 2 - textHeight / 2 + 1, textWidth)
  321. g.setColor(255, 255, 255)
  322. g.printf(str, x + h + .01 * v, y + h / 2 - textHeight / 2, textWidth)
  323. y = y + h + .01 * v
  324. end
  325. -- Minion Stage
  326. local x, y, r, runes = unpack(self.geometry.minion)
  327. if true or v / u < 3 / 4 then
  328. local xoff = .02 * v
  329. local height = .04 * v
  330. g.setColor(0, 0, 0, 100)
  331. g.polygon('fill', x - r - xoff, y + r - height, x + r + xoff, y + r - height, x + r, y + r, x - r, y + r)
  332. end
  333. -- Minion Animation
  334. local cw, ch = ctx.unitCanvas:getDimensions()
  335. ctx.unitCanvas:clear(0, 0, 0, 0)
  336. ctx.unitCanvas:renderTo(function()
  337. ctx.animations[minion]:draw(cw / 2, ch / 2)
  338. end)
  339. local scale = (2 * r / cw) * .9 * 3
  340. g.setColor(255, 255, 255)
  341. g.draw(ctx.unitCanvas, x, y, 0, scale, scale, cw / 2, ch / 2)
  342. -- Minion Rune Frames
  343. for j = 1, #runes do
  344. local x, y, w, h = unpack(runes[j])
  345. local scale = w / atlas:getDimensions('frame')
  346. g.setColor(255, 255, 255)
  347. g.draw(atlas.texture, atlas.quads.frame, x, y, 0, scale, scale)
  348. if self.drag.dragSource == 'stash' and self.drag.dragAlpha > 0 then
  349. local alpha = math.lerp(self.drag.prevDragAlpha, self.drag.dragAlpha, ls.accum / ls.tickrate)
  350. g.setBlendMode('additive')
  351. g.setColor(255, 255, 255, 80 * alpha)
  352. g.draw(atlas.texture, atlas.quads.frame, x, y, 0, scale, scale)
  353. g.setBlendMode('alpha')
  354. end
  355. end
  356. -- Minion Runes
  357. for i = 1, #runes do
  358. local rune = ctx.user.runes[minion][i]
  359. if rune and not self.drag:isDragging(minion, i) then
  360. local x, y, w, h = unpack(runes[i])
  361. local lerpd = {}
  362. for k, v in pairs(ctx.campaign.runeTransforms[rune]) do
  363. lerpd[k] = math.lerp(ctx.campaign.prevRuneTransforms[rune][k] or v, v, ls.accum / ls.tickrate)
  364. end
  365. g.setColor(255, 255, 255)
  366. g.drawRune(rune, lerpd.x, lerpd.y, lerpd.size - .015 * v, (lerpd.size - .015 * v) * .5, table.has(ctx.rewards.runes, rune))
  367. end
  368. end
  369. -- Muju
  370. local color = ctx.user and ctx.user.color or 'purple'
  371. local animation = ctx.animations.muju
  372. -- Muju Robe Color
  373. for _, slot in pairs({'robebottom', 'torso', 'front_upper_arm', 'rear_upper_arm', 'front_bracer', 'rear_bracer'}) do
  374. local slot = animation.spine.skeleton:findSlot(slot)
  375. slot.r, slot.g, slot.b = unpack(config.player.colors[color])
  376. end
  377. -- Muju Hat
  378. animation.spine.skeleton:setSkin(ctx.user.hat or 'santa')
  379. animation.spine.skeleton:findSlot('hat').a = ctx.user.hat and 1 or 0
  380. animation.spine.skeleton:findBone('hat').scaleX = animation.scale
  381. animation.spine.skeleton:findBone('hat').scaleY = animation.scale
  382. -- Draw Muju
  383. local cw, ch = ctx.unitCanvas:getDimensions()
  384. ctx.unitCanvas:clear(0, 0, 0, 0)
  385. ctx.unitCanvas:renderTo(function()
  386. animation:draw(cw / 2, ch / 2)
  387. end)
  388. local scale = (.15 * v / cw) * 1 * 3
  389. g.setColor(255, 255, 255)
  390. local x, y, hats = unpack(self.geometry.muju)
  391. g.draw(ctx.unitCanvas, x, y, 0, scale, scale, cw / 2, ch / 2)
  392. -- Hats Frame
  393. g.setColor(0, 0, 0, 100)
  394. g.rectangle('fill', unpack(self.geometry.hatsFrame))
  395. -- Hats
  396. for i = 1, #hats do
  397. local hat = ctx.user.hats[i]
  398. if hat then
  399. local image = data.media.graphics.hats[hat]
  400. if image then
  401. local x, y, r = unpack(hats[i])
  402. local scale = r * 2 / math.max(image:getWidth(), image:getHeight())
  403. local factor = math.lerp(self.prevHatHoverFactors[hat], self.hatHoverFactors[hat], ls.accum / ls.tickrate)
  404. scale = scale * (.8 + .2 * factor)
  405. g.setColor(255, 255, 255, 180 + (75 * (ctx.user.hat == hat and 1 or 0)))
  406. g.draw(image, x, y, 0, scale, scale, image:getWidth() / 2, image:getHeight() / 2)
  407. end
  408. end
  409. end
  410. -- Modules
  411. self.play:draw()
  412. self.map:draw()
  413. self.drag:draw()
  414. end
  415. function MenuCampaign:keyreleased(key)
  416. if not self.active then return end
  417. if self.map:keyreleased(key) then return true end
  418. if key == 'escape' then
  419. ctx:setPage('start')
  420. return true
  421. end
  422. end
  423. function MenuCampaign:mousepressed(mx, my, b)
  424. if not self.active or self.map.focused then return end
  425. self.drag:mousepressed(mx, my, b)
  426. end
  427. function MenuCampaign:mousereleased(mx, my, b)
  428. if not self.active then return end
  429. self.map:mousereleased(mx, my, b)
  430. if self.map.focused then return end
  431. if ctx.optionsPane.active then return end
  432. self.drag:mousereleased(mx, my, b)
  433. local _, _, hats = unpack(self.geometry.muju)
  434. for i = 1, #hats do
  435. local hat = ctx.user.hats[i]
  436. if hat then
  437. local x, y, r = unpack(hats[i])
  438. if math.insideCircle(mx, my, x, y, r) then
  439. if b == 'l' then
  440. ctx.user.hat = hat
  441. elseif b == 'r' then
  442. ctx.user.hat = nil
  443. end
  444. saveUser(ctx.user)
  445. end
  446. end
  447. end
  448. end
  449. function MenuCampaign:gamepadpressed(gamepad, button)
  450. if not self.active or self.map.focused then return end
  451. if button == 'dpleft' then self:previousBiome()
  452. elseif button == 'dpright' then self:nextBiome()
  453. elseif button == 'start' then ctx.animations.muju:set('death')
  454. elseif button == 'b' then
  455. ctx.page = 'start'
  456. ctx.start.active = true
  457. end
  458. end
  459. function MenuCampaign:resize()
  460. self.map:resize()
  461. table.clear(self.geometry)
  462. end
  463. function MenuCampaign:setBiome(biome)
  464. self.biome = biome
  465. ctx:refreshBackground()
  466. end
  467. function MenuCampaign:mujuDead()
  468. ctx:startGame({mode = 'campaign', biome = self.biome})
  469. end