hud.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. Hud = class()
  2. Hud.bonuses = {
  3. wreckingBall = {
  4. name = 'Wrecking Ball',
  5. description = 'Kill all buildings',
  6. score = 100000,
  7. color = 'silver',
  8. check = function()
  9. local aliveBuilding = false
  10. table.each(ctx.buildings.objects, function(building)
  11. if not building.destroyed then
  12. aliveBuilding = true
  13. end
  14. end)
  15. return not aliveBuilding
  16. end
  17. },
  18. bigBadPigeon = {
  19. name = 'Big Bad Pigeon',
  20. description = 'Kill no buildings',
  21. score = 50000,
  22. color = 'bronze',
  23. check = function()
  24. return ctx.stats.buildingsDestroyed == 0
  25. end
  26. },
  27. genocide = {
  28. name = 'Genocide',
  29. description = 'Kill all people',
  30. score = 250000,
  31. color = 'gold',
  32. check = function()
  33. local aliveBuilding = false
  34. table.each(ctx.buildings.objects, function(building)
  35. if not building.destroyed then
  36. aliveBuilding = true
  37. end
  38. end)
  39. local alivePerson = false
  40. table.each(ctx.enemies.objects, function(person)
  41. if person.state ~= 'dead' then
  42. alivePerson = true
  43. end
  44. end)
  45. return not aliveBuilding and not alivePerson
  46. end
  47. },
  48. hopper = {
  49. name = 'Hopper',
  50. description = 'Don\'t stop jumping',
  51. score = 100000,
  52. color = 'silver',
  53. check = function()
  54. return ctx.pigeon.stepsTaken == 0
  55. end
  56. },
  57. woodpecker = {
  58. name = 'Woodpecker',
  59. description = 'Kill everything by pecking',
  60. score = 250000,
  61. color = 'gold',
  62. check = function()
  63. return ctx.nonPeckKill == false and ctx.pigeon.pecks > 0
  64. end
  65. },
  66. verticallyChallenged = {
  67. name = 'Vertically Challenged',
  68. description = 'Don\'t jump',
  69. score = 25000,
  70. color = 'bronze',
  71. check = function()
  72. return ctx.pigeon.jumps == 0
  73. end
  74. },
  75. stiff = {
  76. name = 'Stiff Neck',
  77. description = 'Don\'t peck',
  78. score = 25000,
  79. color = 'bronze',
  80. check = function()
  81. return ctx.pigeon.pecks == 0
  82. end
  83. },
  84. pacifist = {
  85. name = 'Pacifist',
  86. description = 'Don\'t kill anything',
  87. score = 500000,
  88. color = 'gold',
  89. check = function()
  90. return ctx.stats.buildingsDestroyed == 0 and ctx.stats.peopleKilled == 0
  91. end
  92. },
  93. scrub = {
  94. name = 'Scrub',
  95. description = 'Get a max combo of 25 or less',
  96. score = 50000,
  97. color = 'bronze',
  98. check = function()
  99. return ctx.stats.maxCombo <= 25
  100. end
  101. },
  102. badass = {
  103. name = 'Badass',
  104. description = 'Get a max combo of 100 or higher',
  105. score = 100000,
  106. color = 'silver',
  107. check = function()
  108. return ctx.stats.maxCombo >= 100
  109. end
  110. },
  111. korean = {
  112. name = 'Korean',
  113. description = 'Get the highest possible max combo',
  114. score = 500000,
  115. color = 'gold',
  116. check = function()
  117. return ctx.stats.maxCombo >= ctx.stats.maxMaxCombo
  118. end
  119. },
  120. fabulous = {
  121. name = 'Fabulous',
  122. description = 'End the game in turbo mode',
  123. score = 100000,
  124. color = 'silver',
  125. check = function()
  126. return ctx.pigeon.rainbowShitTimer > 0
  127. end
  128. },
  129. youTried = {
  130. name = 'You Tried',
  131. description = 'Get no medals',
  132. score = 250000,
  133. color = 'silver',
  134. postCheck = function()
  135. return #ctx.hud.win.bonuses == 0
  136. end
  137. }
  138. }
  139. function Hud:init()
  140. ctx.event:emit('view.register', {object = self, mode = 'gui'})
  141. self.score = 0
  142. self.scoreDisplay = self.score
  143. self.startTimer = 3
  144. self.goTimer = 1.5
  145. self.bubble = {}
  146. self:resetBubble()
  147. self.rainbowShitCounter = 0
  148. self.rainbowShitDisplay = 0
  149. self.prevRainbowShitDisplay = 0
  150. self.win = {}
  151. self.win.active = false
  152. self.win.width = 800
  153. self.win.height = 500
  154. self.win.x = -400
  155. self.win.prevx = self.win.x
  156. self.deathBulge = 0
  157. end
  158. function Hud:update()
  159. self.prevRainbowShitDisplay = self.rainbowShitDisplay
  160. self.bubble.prevy = self.bubble.y
  161. self.bubble.prevScale = self.bubble.scale
  162. self.bubble.prevTimer = self.bubble.timer
  163. self.startTimer = timer.rot(self.startTimer, function()
  164. ctx.pigeon:changeState('walk')
  165. ctx.startTick = ls.tick
  166. end)
  167. if self.startTimer == 0 then
  168. self.goTimer = timer.rot(self.goTimer)
  169. end
  170. if self.bubble.active then
  171. self.bubble.y = math.lerp(self.bubble.y, self.bubble.targetY, 12 * ls.tickrate)
  172. self.bubble.scale = math.lerp(self.bubble.scale, self.bubble.targetScale, 12 * ls.tickrate)
  173. self.bubble.amountDisplay = math.lerp(self.bubble.amountDisplay, self.bubble.amount, 12 * ls.tickrate)
  174. self.bubble.timer = timer.rot(self.bubble.timer, function()
  175. self.score = self.score + self.bubble.amount * self.bubble.multiplier
  176. ctx.stats.maxCombo = math.max(ctx.stats.maxCombo, self.bubble.multiplier)
  177. self:resetBubble()
  178. end)
  179. end
  180. if self.win.active then
  181. self.win.prevx = self.win.x
  182. self.win.x = math.lerp(self.win.x, love.graphics.getWidth() / 2, 8 * ls.tickrate)
  183. end
  184. self.rainbowShitDisplay = math.lerp(self.rainbowShitDisplay, self.rainbowShitCounter, 8 * ls.tickrate)
  185. self.scoreDisplay = math.lerp(self.scoreDisplay, self.score, 5 * ls.tickrate)
  186. self.deathBulge = math.lerp(self.deathBulge, 0, 8 * ls.tickrate)
  187. end
  188. function Hud:gui()
  189. local g = love.graphics
  190. local gw, gh = g.getDimensions()
  191. if ctx.debug then
  192. local x, y = ctx.view:worldPoint(love.mouse.getPosition())
  193. g.setFont('media/fonts/handDrawnShapes.ttf', 24)
  194. g.setColor(0, 0, 0)
  195. g.print(x .. ', ' .. y, 9, 9)
  196. g.setColor(255, 255, 255)
  197. g.print(x .. ', ' .. y, 8, 8)
  198. x, y = love.mouse.getPosition()
  199. g.line(x, 0, x, gh)
  200. g.line(0, y, gw, y)
  201. end
  202. if not self.win.active then
  203. g.setFont('media/fonts/handDrawnShapes.ttf', 24)
  204. g.setColor(0, 0, 0, 150)
  205. local str = math.round(self.scoreDisplay)
  206. local w = math.max(g.getFont():getWidth(str), 80)
  207. local h = g.getFont():getHeight()
  208. g.rectangle('fill', 0, 0, w + 16, h + 16)
  209. g.setColor(255, 255, 255)
  210. g.print(str, 8, 8)
  211. end
  212. if self.bubble.active then
  213. local y, scale = math.lerp(self.bubble.prevy, self.bubble.y, ls.accum / ls.tickrate), math.lerp(self.bubble.prevScale, self.bubble.scale, ls.accum / ls.tickrate)
  214. g.setFont('media/fonts/BebasNeueBold.otf', math.max(math.round(scale / .01) * .01 * 24, 1))
  215. local alpha = math.clamp(math.lerp(self.bubble.prevTimer, self.bubble.timer, ls.accum / ls.tickrate), 0, 1)
  216. g.setColor(0, 0, 0, 128 * alpha)
  217. local amount = math.round(self.bubble.amountDisplay)
  218. local str = amount .. (self.bubble.multiplier > 1 and (' X ' .. self.bubble.multiplier) or '')
  219. local textWidth = g.getFont():getWidth(str)
  220. g.print(str, gw / 2 - textWidth / 2 + 1, y + 1)
  221. g.setColor(255, 255, 255, 255 * alpha)
  222. g.print(str, gw / 2 - textWidth / 2, y)
  223. end
  224. local baseWidth = 20
  225. local baseHeight = 100
  226. if ctx.pigeon.rainbowShitTimer > 0 then
  227. love.math.setRandomSeed(math.max(love.timer.getTime() * ctx.pigeon.rainbowShitTimer - self.scoreDisplay, 1))
  228. local prc = math.min(ctx.pigeon.rainbowShitTimer / 5, 1)
  229. g.setColor(128 + love.math.random() * 127, 128 + love.math.random() * 127, 128 + love.math.random() * 127)
  230. g.rectangle('fill', 2, 50 + baseHeight * (1 - prc), baseWidth, baseHeight * prc)
  231. else
  232. g.setColor(255, 0, 0)
  233. end
  234. local baseWidth = 20
  235. local baseHeight = 100
  236. g.rectangle('line', 2, 50, baseWidth, baseHeight)
  237. local prc = math.clamp(math.lerp(self.prevRainbowShitDisplay, self.rainbowShitDisplay, ls.accum / ls.tickrate) / Pigeon.rainbowShitThreshold, 0, 1)
  238. g.setColor(255, 0, 0)
  239. g.rectangle('fill', 2, 50 + baseHeight * (1 - prc), baseWidth, baseHeight * prc)
  240. if self.startTimer > 0 then
  241. local str = math.ceil(self.startTimer)
  242. g.setFont('media/fonts/handDrawnShapes.ttf', 80)
  243. g.setColor(0, 0, 0)
  244. g.print(str, gw / 2 - g.getFont():getWidth(str) / 2 + 2, 200 - g.getFont():getHeight() / 2 + 2)
  245. g.setColor(255, 255, 255)
  246. g.print(str, gw / 2 - g.getFont():getWidth(str) / 2, 200 - g.getFont():getHeight() / 2)
  247. elseif self.goTimer > 0 then
  248. local str = 'GO!'
  249. local alpha = 255 * math.clamp(self.goTimer, 0, 1)
  250. g.setFont('media/fonts/handDrawnShapes.ttf', 80)
  251. g.setColor(0, 0, 0, alpha)
  252. g.print(str, gw / 2 - g.getFont():getWidth(str) / 2 + 2, 200 - g.getFont():getHeight() / 2 + 2)
  253. g.setColor(255, 255, 255, alpha)
  254. g.print(str, gw / 2 - g.getFont():getWidth(str) / 2, 200 - g.getFont():getHeight() / 2)
  255. end
  256. if self.win.active then
  257. g.setColor(0, 0, 0, 100)
  258. local x = math.lerp(self.win.prevx, self.win.x, ls.accum / ls.tickrate)
  259. local w, h = self.win.width, self.win.height
  260. w = gw
  261. g.rectangle('fill', 0, 0, gw, gh)
  262. g.setFont('media/fonts/handDrawnShapes.ttf', 40)
  263. local str = 'Congration you done it!'
  264. local yy = 64
  265. g.setColor(0, 0, 0)
  266. g.print(str, x - g.getFont():getWidth(str) / 2 + 2, yy + 2)
  267. g.setColor(255, 255, 255)
  268. g.print(str, x - g.getFont():getWidth(str) / 2, yy)
  269. yy = yy + g.getFont():getHeight() + 10
  270. local str = math.round(self.scoreDisplay)
  271. g.setColor(0, 0, 0)
  272. g.print('Your score:', x - g.getFont():getWidth('Your score:') / 2 + 2, yy + 2)
  273. g.setColor(255, 255, 255)
  274. g.print('Your score:', x - g.getFont():getWidth('Your score:') / 2, yy)
  275. yy = yy + g.getFont():getHeight() + 15
  276. g.setFont('media/fonts/handDrawnShapes.ttf', 48)
  277. g.setColor(0, 0, 0)
  278. g.print(str, x - g.getFont():getWidth(str) / 2 + 2, yy + 2)
  279. g.setColor(255, 255, 255)
  280. g.print(str, x - g.getFont():getWidth(str) / 2, yy)
  281. yy = yy + g.getFont():getHeight() + 32
  282. g.setFont('media/fonts/runescape.ttf', 16)
  283. local xx = gw / 4 - 100
  284. local d = (gw / 2) - x
  285. local yy = gh * .7
  286. local function printShadow(str, x, y, off)
  287. off = off or 1
  288. g.setColor(0, 0, 0)
  289. g.print(str, x - d + off, y + off)
  290. g.setColor(255, 255, 255)
  291. g.print(str, x - d, y)
  292. end
  293. printShadow('Time:', xx, yy)
  294. local t = math.floor(ctx.stats.time)
  295. local seconds = math.floor(t % 60)
  296. local minutes = math.floor(t / 60)
  297. if minutes < 10 then minutes = '0' .. minutes end
  298. if seconds < 10 then seconds = '0' .. seconds end
  299. printShadow(minutes .. ':' .. seconds, xx + 150, yy)
  300. yy = yy + g.getFont():getHeight() + 6
  301. printShadow('Max Combo:', xx, yy)
  302. printShadow(ctx.stats.maxCombo, xx + 150, yy)
  303. yy = yy + g.getFont():getHeight() + 6
  304. printShadow('People Killed:', xx, yy)
  305. printShadow(ctx.stats.peopleKilled .. ' (' .. (ctx.stats.peoplePercentage * 100) .. '%)', xx + 150, yy)
  306. yy = yy + g.getFont():getHeight() + 6
  307. xx = gw * .75 - 100
  308. yy = gh * .7
  309. printShadow('Buildings Destroyed:', xx, yy)
  310. printShadow(ctx.stats.buildingsDestroyed .. ' (' .. (ctx.stats.buildingPercentage * 100) .. '%)', xx + 150, yy)
  311. yy = yy + g.getFont():getHeight() + 6
  312. printShadow('Pecks:', xx, yy)
  313. printShadow(ctx.pigeon.pecks, xx + 150, yy)
  314. yy = yy + g.getFont():getHeight() + 6
  315. printShadow('Jumps:', xx, yy)
  316. printShadow(ctx.pigeon.jumps, xx + 150, yy)
  317. yy = yy + g.getFont():getHeight() + 6
  318. --
  319. local ct = #self.win.bonuses
  320. local inc = 200
  321. local xx = x - (inc * (ct - 1) / 2)
  322. while inc * ct > gw do
  323. inc = inc - 10
  324. end
  325. local mx, my = love.mouse.getPosition()
  326. local flyoutService = nil
  327. g.setFont('media/fonts/handDrawnShapes.ttf', 24)
  328. for i = 1, ct do
  329. local name = self.win.bonuses[i]
  330. local bonus = self.bonuses[name]
  331. local image = data.media.graphics.ui[bonus.color]
  332. local scale = 80 / image:getHeight()
  333. g.setColor(255, 255, 255)
  334. g.draw(image, xx, gh / 2 - 20, 0, scale, scale, image:getWidth() / 2, image:getHeight() / 2)
  335. local str = bonus.name
  336. g.setColor(0, 0, 0)
  337. g.print(str, xx - g.getFont():getWidth(str) / 2 + 2, gh / 2 - 20 + 60 + 2)
  338. g.setColor(255, 255, 255)
  339. g.print(str, xx - g.getFont():getWidth(str) / 2, gh / 2 - 20 + 60)
  340. if math.inside(mx, my, xx - image:getWidth() * scale / 2, gh / 2 - 20 - image:getHeight() * scale / 2, image:getWidth() * scale, image:getHeight() * scale) then
  341. flyoutService = bonus.description .. '\n' .. '+' .. bonus.score
  342. end
  343. xx = xx + inc
  344. end
  345. g.setFont('media/fonts/runescape.ttf', 16)
  346. if flyoutService then
  347. g.setColor(0, 0, 0, 200)
  348. local w = g.getFont():getWidth(flyoutService) + 8
  349. local h = g.getFont():getHeight() * 2 + 8
  350. g.rectangle('fill', mx + 8, my + 8, w, h)
  351. g.setColor(255, 255, 255)
  352. g.print(flyoutService, mx + 12, my + 12)
  353. end
  354. local alpha = math.abs(math.sin((ls.tick + ls.accum / ls.tickrate) / 25))
  355. local str = 'Press ' .. (joystick and 'A' or 'space') .. ' to continue'
  356. g.setFont('media/fonts/handDrawnShapes.ttf', 30)
  357. g.setColor(0, 0, 0, alpha * 255)
  358. g.print(str, x - g.getFont():getWidth(str) / 2 + 2, gh - 55 + 2)
  359. g.setColor(255, 255, 255, alpha * 255)
  360. g.print(str, x - g.getFont():getWidth(str) / 2, gh - 55)
  361. g.setFont('media/fonts/runescape.ttf', 16)
  362. local str = 'Press T to tweet your score!'
  363. g.setColor(0, 0, 0)
  364. g.print(str, x - g.getFont():getWidth(str) / 2 + 1, gh - 24 + 1)
  365. g.setColor(255, 255, 255)
  366. g.print(str, x - g.getFont():getWidth(str) / 2, gh - 24)
  367. end
  368. if ctx.map.name == 'dinoland' and ctx.map.index == 1 then
  369. local x, y = ctx.view:screenPoint(1067 / 2, ctx.map.height - 40)
  370. local str = 'Up = Jump, Space = Peck'
  371. g.setFont('media/fonts/handDrawnShapes.ttf', 30)
  372. g.setColor(0, 0, 0)
  373. g.print(str, x - g.getFont():getWidth(str) / 2 + 1, y - g.getFont():getHeight() / 2 + 1)
  374. g.setColor(255, 255, 255)
  375. g.print(str, x - g.getFont():getWidth(str) / 2, y - g.getFont():getHeight() / 2)
  376. end
  377. end
  378. function Hud:paused()
  379. self.prevRainbowShitDisplay = self.rainbowShitDisplay
  380. self.bubble.prevy = self.bubble.y
  381. self.bubble.prevScale = self.bubble.scale
  382. self.bubble.prevTimer = self.bubble.timer
  383. if self.win.active then
  384. self.win.prevx = self.win.x
  385. end
  386. end
  387. function Hud:resetBubble()
  388. self.bubble.active = false
  389. self.bubble.amount = 0
  390. self.bubble.amountDisplay = self.bubble.amount
  391. self.bubble.timer = 0
  392. self.bubble.multiplier = 0
  393. self.bubble.targetY = 200
  394. self.bubble.y = self.bubble.targetY
  395. self.bubble.prevy = self.bubble.y
  396. self.bubble.targetScale = 1
  397. self.bubble.scale = self.bubble.targetScale
  398. self.bubble.prevScale = self.bubble.scale
  399. self.rainbowShitCounter = 0
  400. end
  401. function Hud:addScore(amount, kind, cause)
  402. if cause == 'peck' then
  403. amount = amount * 2
  404. end
  405. self.bubble.active = true
  406. self.bubble.timer = 3
  407. self.bubble.amount = self.bubble.amount + amount
  408. self.bubble.multiplier = self.bubble.multiplier + 1
  409. self.bubble.scale = self.bubble.scale + .2
  410. self.bubble.targetScale = self.bubble.targetScale + .1
  411. self.bubble.targetY = math.lerp(self.bubble.targetY, 50, .15)
  412. self.bubble.prevTimer = self.bubble.timer
  413. if kind == 'person' then
  414. self.rainbowShitCounter = self.bubble.multiplier --self.rainbowShitCounter + 1
  415. if self.rainbowShitCounter % 50 == 0 then
  416. --self.rainbowShitCounter = 0
  417. ctx.pigeon:activateRainbowShit()
  418. end
  419. end
  420. end
  421. function Hud:activateWin()
  422. self.score = self.score + self.bubble.amount * self.bubble.multiplier
  423. ctx.stats.maxCombo = math.max(ctx.stats.maxCombo, self.bubble.multiplier)
  424. self:resetBubble()
  425. ctx.stats.peoplePercentage = lume.round(ctx.stats.peopleKilled / ctx.stats.originalPeople, .01)
  426. ctx.stats.buildingPercentage = lume.round(ctx.stats.buildingsDestroyed / ctx.stats.originalBuildings, .01)
  427. ctx.stats.time = (ls.tick - ctx.startTick) * ls.tickrate
  428. self.win.active = true
  429. self.win.bonuses = {}
  430. collectgarbage()
  431. table.each(self.bonuses, function(bonus, name)
  432. if bonus.check and bonus.check() then
  433. table.insert(self.win.bonuses, name)
  434. self.score = self.score + bonus.score
  435. end
  436. end)
  437. table.each(self.bonuses, function(bonus, name)
  438. if bonus.postCheck and bonus.postCheck() then
  439. table.insert(self.win.bonuses, name)
  440. self.score = self.score + bonus.score
  441. end
  442. end)
  443. self.scoreDisplay = 0
  444. end
  445. function Hud:share()
  446. local level = ctx.map.name == 'dinoland' and 1 or 2
  447. level = tostring(level) .. '-' .. tostring(ctx.map.index)
  448. local url = 'http://twitter.com/intent/tweet?text=I%20got%20a%20new%20highscore%20of%20' .. math.round(self.scoreDisplay) .. '%20points%20on%20level%20' .. level .. '%20of%20%23FowlPlay!%20%23RobotPigeon'
  449. love.system.openURL(url)
  450. end