tutorial.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. local g = love.graphics
  2. local tween = require 'lib/deps/tween/tween'
  3. Tutorial = class()
  4. function Tutorial:init(active, destination)
  5. self.active = active
  6. self.destination = destination
  7. self.messages = {
  8. muju = 'This is Muju',
  9. move = 'You can move Muju with A and D',
  10. shrine = 'This is Muju\'s shrine. Protect it!',
  11. enemy = 'Enemies will attack your shrine.',
  12. minion = 'Press space to summon a minion.',
  13. battle = 'Your minions will protect your shrine.',
  14. juju = 'Enemies drop Juju when they die.',
  15. realm = 'Juju can be collected in the Juju Realm.',
  16. death = 'Muju enters the Juju Realm when he dies.',
  17. collect = 'Control the ghost with WASD to get the juju.',
  18. goodjob = 'Good job!',
  19. resurrect = 'Muju\'s ghost returns to his body after some time.',
  20. status = 'Here you can see your Juju and the clock.',
  21. hudminions = 'Here you can see information about your minions.',
  22. cost = 'Minions have a cost...',
  23. cooldown = '...and a cooldown.',
  24. upgrade = 'Stand near your shrine and press E to upgrade minions.',
  25. upgradecost = 'All upgrades cost Juju.',
  26. upgradehover = 'Hover over an icon for more information about the upgrade.',
  27. upgradetry = 'To continue, purchase any upgrade by clicking on it.',
  28. upgradeexit = 'Awesome! Press E again to exit the upgrade menu.',
  29. glhf = 'That\'s about it. Have fun!'
  30. }
  31. self.nextMessage = {
  32. muju = 'move',
  33. move = 'shrine',
  34. shrine = 'enemy',
  35. enemy = 'minion',
  36. minion = 'battle',
  37. battle = 'juju',
  38. juju = 'realm',
  39. realm = 'death',
  40. death = 'collect',
  41. collect = 'goodjob',
  42. goodjob = 'resurrect',
  43. resurrect = 'status',
  44. status = 'hudminions',
  45. hudminions = 'cost',
  46. cost = 'cooldown',
  47. cooldown = 'upgrade',
  48. upgrade = 'upgradecost',
  49. upgradecost = 'upgradehover',
  50. upgradehover = 'upgradetry',
  51. upgradetry = 'upgradeexit',
  52. upgradeexit = 'glhf'
  53. }
  54. self.pointerOrientations = {
  55. status = 'top',
  56. hudminions = 'top',
  57. cost = 'top',
  58. cooldown = 'top',
  59. upgradecost = 'none',
  60. upgradehover = 'none',
  61. upgradetry = 'none',
  62. upgradeexit = 'none',
  63. glhf = 'none'
  64. }
  65. self.messageIndex = 1
  66. self.message = 'muju'
  67. self.opened = true
  68. self.time = 0
  69. self.prevTime = self.time
  70. self.maxTime = .1
  71. self.factor = {value = 0}
  72. self.tween = tween.new(self.maxTime, self.factor, {value = 1}, 'inOutBack')
  73. self.delay = self.maxTime
  74. self.x = ctx.hud.u * .5
  75. self.y = 0
  76. self.prevx, self.prevy = self.x, self.y
  77. self.moveTargetX = ctx.map.width * .62
  78. self.jujuX = 0
  79. self.jujuY = 0
  80. if active then
  81. ctx.player.x = ctx.map.width * .4
  82. ctx.player.animation.flipped = true
  83. ctx.player.juju = 100
  84. end
  85. ctx.event:emit('view.register', {object = self, mode = 'gui'})
  86. end
  87. function Tutorial:update()
  88. if not self.active then return end
  89. self.prevTime = self.time
  90. self.prevx = self.x
  91. self.prevy = self.y
  92. local u, v = ctx.hud.u, ctx.hud.v
  93. local height = .02 * v + .01 * u + 10
  94. local x, y = self.x or 0, self.y or 0
  95. local yoffsets = {default = height, top = -height, none = 0}
  96. y = y + (yoffsets[self.pointerOrientations[self.message]] or yoffsets.default)
  97. if self.message == 'muju' then
  98. x, y = ctx.view:screenPoint(ctx.player.x, ctx.player.y - 25)
  99. elseif self.message == 'shrine' then
  100. x, y = ctx.view:screenPoint(ctx.shrine.x, ctx.shrine.y)
  101. elseif self.message == 'move' then
  102. x, y = ctx.view:screenPoint(self.moveTargetX, ctx.map.height - ctx.map.groundHeight - ctx.player.height - 25)
  103. elseif self.message == 'enemy' then
  104. if self.enemy then
  105. x, y = ctx.view:screenPoint(self.enemy.x, self.enemy.y - self.enemy.height)
  106. end
  107. elseif self.message == 'minion' then
  108. x, y = ctx.view:screenPoint(ctx.player.x + 30, self.enemy.y - self.enemy.height)
  109. elseif self.message == 'battle' then
  110. local minion = ctx.units:filter(function(m) return m.player end)[1]
  111. if minion and self.enemy then
  112. x, y = ctx.view:screenPoint((minion.x + self.enemy.x) / 2, self.enemy.y - self.enemy.height - 40)
  113. end
  114. elseif self.message == 'juju' or self.message == 'realm' then
  115. local _, juju = next(ctx.jujus.jujus)
  116. if juju then
  117. x, y = ctx.view:screenPoint(juju.x, juju.y - juju.amount)
  118. end
  119. elseif self.message == 'death' then
  120. x, y = ctx.view:screenPoint(ctx.player.x, ctx.player.y - 65)
  121. elseif self.message == 'collect' then
  122. if ctx.player.ghost then
  123. x, y = ctx.view:screenPoint(ctx.player.ghost.x, ctx.player.ghost.y - 40)
  124. end
  125. elseif self.message == 'goodjob' then
  126. x, y = ctx.view:screenPoint((self.jujuX + ctx.player.ghost.x) / 2, (self.jujuY + ctx.player.ghost.y - 40) / 2)
  127. elseif self.message == 'resurrect' then
  128. x, y = ctx.view:screenPoint(ctx.player.x, ctx.player.y - 65)
  129. elseif self.message == 'status' then
  130. x, y = u * .92, v * .1
  131. elseif self.message == 'hudminions' then
  132. x, y = u * .5, v * .25
  133. elseif self.message == 'cost' then
  134. x, y = u * .445, v * .15
  135. elseif self.message == 'cooldown' then
  136. x, y = u * .5, v * .06
  137. elseif self.message == 'upgrade' then
  138. x, y = ctx.view:screenPoint(ctx.shrine.x, ctx.shrine.y)
  139. elseif self.message == 'upgradecost' or self.message == 'upgradehover' or self.message == 'upgradetry' or self.message == 'upgradeexit' then
  140. x, y = u * .5, v * .3
  141. elseif self.message == 'glhf' then
  142. x, y = u * .5, v * .5
  143. end
  144. y = y - (yoffsets[self.pointerOrientations[self.message]] or yoffsets.default)
  145. self.x = math.lerp(self.x, x, 12 * ls.tickrate)
  146. self.y = math.lerp(self.y, y, 12 * ls.tickrate)
  147. if self.opened then
  148. self.delay = timer.rot(self.delay)
  149. if self.delay == 0 then
  150. if self.time < self.maxTime then
  151. self.time = math.min(self.time + ls.tickrate, self.maxTime)
  152. if self.time == self.maxTime then
  153. self:enter(self.message)
  154. end
  155. end
  156. end
  157. else
  158. self.delay = timer.rot(self.delay)
  159. if self.delay == 0 then
  160. self.time = math.max(self.time - ls.tickrate, 0)
  161. if self.time == 0 then
  162. self.message = self.nextMessage[self.message]
  163. self.messageIndex = self.messageIndex + 1
  164. self.opened = true
  165. end
  166. end
  167. end
  168. if self.opened and self.delay == 0 and self.time == self.maxTime then
  169. if self.message == 'muju' and love.keyboard.isDown('return', ' ') then
  170. self.opened = false
  171. elseif self.message == 'shrine' and love.keyboard.isDown('return', ' ') then
  172. self.opened = false
  173. elseif self.message == 'move' and math.abs(ctx.player.x - self.moveTargetX) < 20 then
  174. self.opened = false
  175. self.delay = .35
  176. elseif self.message == 'enemy' and love.keyboard.isDown('return', ' ') then
  177. self.opened = false
  178. elseif self.message == 'minion' and ctx.player.totalSummoned > 0 then
  179. self.opened = false
  180. elseif self.message == 'battle' then
  181. local _, juju = next(ctx.jujus.jujus)
  182. if juju and math.abs(juju.vy) < 100 then
  183. self.opened = false
  184. end
  185. elseif self.message == 'juju' and love.keyboard.isDown('return', ' ') then
  186. self.opened = false
  187. elseif self.message == 'realm' and love.keyboard.isDown('return', ' ') then
  188. self.opened = false
  189. elseif self.message == 'death' and ctx.player.dead then
  190. self.opened = false
  191. elseif self.message == 'collect' then
  192. local juju = next(ctx.jujus.jujus)
  193. if not juju then
  194. self.opened = false
  195. else
  196. self.jujuX = juju.x
  197. self.jujuY = juju.y
  198. end
  199. elseif self.message == 'goodjob' then
  200. self.delay = 1
  201. self.opened = false
  202. elseif self.message == 'resurrect' and not ctx.player.dead then
  203. self.delay = 2
  204. self.opened = false
  205. elseif (self.message == 'status' or self.message == 'hudminions' or self.message == 'cost') and love.keyboard.isDown('return', ' ') then
  206. self.opened = false
  207. elseif (self.message == 'cooldown' and ctx.player.deck[1].cooldown == 0) then
  208. self.delay = .25
  209. self.opened = false
  210. elseif self.message == 'upgrade' and ctx.hud.upgrades.active then
  211. self.opened = false
  212. elseif (self.message == 'upgradecost' or self.message == 'upgradehover') and love.keyboard.isDown('return', ' ') then
  213. self.opened = false
  214. elseif self.message == 'upgradetry' and data.unit.bruju.cost > 10 then
  215. self.delay = .2
  216. self.opened = false
  217. elseif self.message == 'upgradeexit' and not ctx.hud.upgrades.active then
  218. self.delay = .8
  219. self.opened = false
  220. elseif self.message == 'glhf' and love.keyboard.isDown('return', ' ') then
  221. self.opened = false
  222. end
  223. if not self.opened then
  224. self:exit(self.message)
  225. end
  226. end
  227. end
  228. function Tutorial:gui()
  229. if not self.active then return end
  230. local u, v = ctx.view.frame.width, ctx.view.frame.height
  231. local font = g.setFont('mesmerize', .04 * v)
  232. if self.messages[self.message] then
  233. local str = self.messages[self.message]
  234. local factor, t = self:getFactor()
  235. local alpha = (t / self.maxTime) ^ 3
  236. local x = math.lerp(self.prevx, self.x, ls.accum / ls.tickrate)
  237. local y = math.lerp(self.prevy, self.y, ls.accum / ls.tickrate)
  238. local rx, ry = x, y
  239. local w, h = font:getWidth(str), font:getHeight(str)
  240. local padding = u * .01
  241. rx = math.min(rx, u - w / 2 - 2 * padding)
  242. ry = math.min(ry, v - h / 2 - 2 * padding)
  243. g.setColor(0, 0, 0, 200 * alpha)
  244. g.rectangle('fill', rx - w / 2 - padding, ry - h / 2 - padding, w + 2 * padding, h + 2 * padding)
  245. if self.pointerOrientations[self.message] == 'top' then
  246. g.polygon('fill', x - 10, y - h / 2 - padding, x + 10, y - h / 2 - padding, x, y - h / 2 - padding - 10)
  247. elseif self.pointerOrientations[self.message] ~= 'none' then
  248. g.polygon('fill', x - 10, y + h / 2 + padding, x + 10, y + h / 2 + padding, x, y + h / 2 + padding + 10)
  249. end
  250. g.setColor(255, 255, 255, 255 * alpha)
  251. g.printShadow(str, rx, ry, true)
  252. if self.message == 'muju' then
  253. local str = '(space)'
  254. local font = g.setFont('mesmerize', .02 * v)
  255. g.printShadow(str, rx - w / 2 - padding, ry + h / 2 + padding + 1)
  256. end
  257. end
  258. end
  259. function Tutorial:keypressed(key)
  260. --
  261. end
  262. function Tutorial:getFactor()
  263. local t = math.lerp(self.prevTime, self.time, ls.accum / ls.tickrate)
  264. self.tween:set(t)
  265. return self.factor.value, t
  266. end
  267. function Tutorial:enter(message)
  268. if message == 'move' then
  269. self.moveX = ctx.player.x
  270. elseif message == 'death' then
  271. local minion = ctx.units:filter(function(m) return m.player end)[1]
  272. if minion then
  273. minion:hurt(100000)
  274. end
  275. self.enemy = ctx.units:add('duju', {x = ctx.map.width * .75})
  276. self.enemy.damage = 35
  277. elseif message == 'cooldown' then
  278. ctx.player.deck[1].cooldown = 1
  279. end
  280. end
  281. function Tutorial:exit(message)
  282. if message == 'shrine' then
  283. self.enemy = ctx.units:add('duju', {x = ctx.map.width * .9})
  284. self.enemy.damage = 8
  285. elseif message == 'minion' then
  286. ctx.player.juju = 100
  287. elseif message == 'death' then
  288. self.enemy:hurt(100000)
  289. self.enemy = nil
  290. elseif message == 'glhf' then
  291. Context:add(Menu, ctx.user, ctx.options, {page = self.destination, biome = ctx.biome})
  292. Context:remove(ctx)
  293. end
  294. end
  295. function Tutorial:shouldPlayerMove()
  296. return not self.active or self.message == 'move' or self.message == 'upgrade'
  297. end
  298. function Tutorial:shouldSummon()
  299. return not self.active or self.message == 'minion'
  300. end
  301. function Tutorial:shouldShowHudStatus()
  302. return not self.active or self.messageIndex >= 13
  303. end
  304. function Tutorial:shouldShowHudUnits()
  305. return not self.active or self.messageIndex >= 14
  306. end
  307. function Tutorial:shouldShowHealthbars()
  308. return not self.active or self.messageIndex >= 6
  309. end
  310. function Tutorial:shouldAllowUpgradeToggling()
  311. return not self.active or self.message == 'upgrade' or self.message == 'upgradeexit'
  312. end
  313. function Tutorial:shouldPurchaseUpgrade()
  314. return not self.active or self.message == 'upgradetry'
  315. end
  316. function Tutorial:shouldHighlightShrine()
  317. return not self.active or self.message == 'upgrade' or self.message == 'upgradeexit'
  318. end
  319. function Tutorial:shouldUpdateUnits()
  320. return not self.active or self.message == 'battle' or self.message == 'death' or self.message == 'collect'
  321. end
  322. function Tutorial:shouldDropJuju()
  323. return not self.active or self.message == 'battle'
  324. end
  325. function Tutorial:shouldFloatJuju()
  326. return not self.active or false
  327. end
  328. function Tutorial:shouldDecayGhost()
  329. return not self.active or (self.message ~= 'collect' or not next(ctx.jujus.jujus))
  330. end