fix: another PNG CRC error
[poca-love.git] / menu.lua
blobb2e256e18738287b987d2577f3c642b216c1e9af
1 --[[
2 This file is part of POCA - a puzzle game
4 POCA is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This software is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>.
19 -- create new canvas for menu element, if nonexistent or resized
20 function new_canvas_needed(e)
21 if not (
22 e.canvas and
23 (e.canvas:getWidth() == math.floor(
24 e.w * screen.playfieldsize
28 then
29 -- math.floor probably redundant here, but
30 -- just to be sure, to prevent unnecesary resizing
31 e.canvas = love.graphics.newCanvas(
32 math.floor(e.w * screen.playfieldsize),
33 e.h * screen.playfieldsize
35 return true
36 end
37 end
40 -- menu elements and menus (in corresponding tables)
43 -- reusable
44 menu_elements = { -- relative to playfield size and position
45 -- casette
46 casette_next = {
47 x = .5, y = .5, w = .25, h = .25,
48 content = function()
49 return images.ffwd
50 end,
51 onclick = function()
52 local ml = menus.menu_casette.music_list
53 if not current.played then
54 current.played = backgrounds[level_labels[game.currentlevel]].music or
55 'opening'
56 end
57 for a = 1, #ml do
58 if ml[a] == current.played then
59 a = a % #ml + 1
60 play_music(ml[a], true)
61 menus.menu_casette.play = true
62 break
63 end
64 end
65 end,
67 casette_play = {
68 x = .25, y = .5, w = .25, h = .25,
69 content = function()
70 if current.music and current.music:isPlaying() then
71 return images.stop
72 else
73 return images.play
74 end
75 end,
76 onclick = function()
77 if not current.played then
78 current.played = backgrounds[level_labels[game.currentlevel]].music or
79 'opening'
80 end
81 if current.music and current.music:isPlaying() then
82 current.music:stop()
83 menus.menu_casette.play = nil
84 else
85 play_music(current.played, true)
86 menus.menu_casette.play = true
87 end
88 end,
90 -- wise man
91 wise_man_picture = {
92 x = 0, y = 0, w = .25, h = .25,
93 content = images['N']
95 wise_man_says = {
96 x = .25, y = 0, w = .5, h = .25,
97 content = 'Wise man says:'
99 -- objects
100 object_name = {
101 x = 0, y = 0, w = 1, h = .2,
102 content = function()
103 if #game.trinkets == 0 then return 'You have no objects.' end
104 if not current.trinket then current.trinket = #game.trinkets end
105 return current.trinket .. '. ' .. trinkets[game.trinkets[current.trinket]].name
106 end,
108 big_object = {
109 x = .25, y = .25, w = .5, h = .5,
110 page = 1,
111 content = function(self, dx)
112 -- do nothing if no trinket
113 local t = game.trinkets[current.trinket]
114 if not t then return end
115 -- return text or draw on canvas, then return canvas
116 local _, _, real_w, real_h = get_absolute_dimensions(self)
117 local function drawbmp(c, dx)
118 dx = dx or 0
119 love.graphics.draw(c.bmp,
120 dx + c.offsetx * screen.tilesize, c.offsety * screen.tilesize,
121 0, real_w / c.scale, real_h / c.scale
124 if self.page == 1 then -- trinket image
125 -- draw previous or next image when sweeping
126 if self.sweep_x or new_canvas_needed(self) then
127 love.graphics.setCanvas(self.canvas)
128 love.graphics.clear()
129 dx = dx or 0
130 dx = math.max(- real_w, dx)
131 dx = math.min(real_w, dx)
132 -- draw left, right and center imgs
133 drawbmp(menus.menu_objects.get_object_img(-1), dx - real_w)
134 drawbmp(menus.menu_objects.get_object_img(1), dx + real_w)
135 drawbmp(menus.menu_objects.get_object_img(0), dx)
136 love.graphics.setCanvas()
138 return {
139 bmp = self.canvas, offsetx = 0, offsety = 0,
140 scale = screen.tilesize / self.w
142 elseif self.page == 2 then -- trinket description
143 local d = ''
144 if game.trinkets[current.trinket] == 'cigarettes' then
145 d = '\n\nThere are ' .. game.cigarettes .. ' cigarettes left.'
147 return trinkets[game.trinkets[current.trinket]].description .. d
149 end,
150 onclick = function(self)
151 local t = game.trinkets[current.trinket]
152 if not t then return end
154 trinkets[t].onclick and
155 not ( -- drawing in 'Secrets' is default
156 t == 'pencil' and
157 level_labels[game.currentlevel] == 'Secrets'
159 then
160 trinkets[t].onclick()
161 elseif trinkets[t].description then
162 self.page = self.page % 2 + 1
164 end,
165 onsweep = function(self, dx)
166 local w = self.w * screen.playfieldsize
167 if dx > w / 2 then
168 menu_elements.previous_object:onclick()
169 elseif (dx < - w / 2) then
170 menu_elements.next_object:onclick()
171 elseif math.abs(dx) < w / 8 then
172 self:onclick()
176 -- donate
177 donate_title = {
178 x = 0, y = -.25, w = .5, h = .25,
179 content = 'Select an object to donate',
181 big_object_donate = {
182 x = .25, y = .25, w = .5, h = .5,
183 page = 1, -- show pic, not description
184 content = function(self, dx)
185 -- borrow content from big_object
186 -- uses own canvas, page, dx
187 return menu_elements.big_object.content(self, dx)
188 end,
189 onclick = function(self)
190 menu_elements.donate_title.hidden = true
191 menu_elements.confirm_donate.hidden = false
192 end,
193 onsweep = function(self, dx)
194 local w = self.w * screen.playfieldsize
195 if dx > w / 2 then
196 menu_elements.previous_object_donate:onclick()
197 elseif (dx < - w / 2) then
198 menu_elements.next_object_donate:onclick()
199 elseif math.abs(dx) < w / 8 then
200 self:onclick()
204 previous_object = {
205 x = 0, y = .4, w = .2, h = .2,
206 content = '-',
207 onclick = function()
208 change_screen('menu_objects')
209 current.trinket = select_trinket(-1)
210 end,
212 next_object = {
213 x = .8, y = .4, w = .2, h = .2,
214 content = '+',
215 onclick = function()
216 change_screen('menu_objects')
217 current.trinket = select_trinket(1)
218 end,
220 previous_object_donate = {
221 x = 0, y = .4, w = .2, h = .2,
222 content = '-',
223 onclick = function()
224 change_screen('menu_objects_donate')
225 current.trinket = select_trinket(-1)
226 end,
228 next_object_donate = {
229 x = .8, y = .4, w = .2, h = .2,
230 content = '+',
231 onclick = function()
232 change_screen('menu_objects_donate')
233 current.trinket = select_trinket(1)
234 end,
236 light_cig = {
237 hidden = true,
238 x = 0, y = .8, w = .3, h = .2,
239 content = 'Light a cigarette',
240 onclick = function()
241 game.matches = game.matches - 1
242 if game.matches == 0 then lose_trinket('matches') end
243 change_screen('story_cigarettes')
244 end,
246 light_secret = {
247 hidden = true,
248 x = .35, y = .8, w = .4, h = .2,
249 content = 'Light surroundings',
250 onclick = function()
251 game.matches = game.matches - 1
252 if game.matches == 0 then lose_trinket('matches') end
253 auto_paint()
254 change_screen('playfield')
255 end,
257 confirm_donate = {
258 hidden = true,
259 x = 0, y = -.25, w = .5, h = .25,
260 content = 'Really donate?',
261 onclick = function()
262 table.insert(game.wise_man_has, game.trinkets[current.trinket])
263 if game.trinkets[current.trinket] == 'map' then
264 wise_man_lose('advice_map')
266 lose_trinket(game.trinkets[current.trinket])
267 menus.menu_objects.onload()
268 menus.wise_man_offers.offer_pool = wise_get_offer() -- get list of 3 elements or less
269 change_screen('wise_man_offers')
270 end,
272 -- common
273 play = {
274 x = 0, y = -.25, w = .5, h = .25,
275 content = 'Play',
276 onclick = function() change_screen('playfield') end,
278 exit_to_title = {
279 x = .5, y = -.25, w = .5, h = .25,
280 content = 'Main menu',
281 onclick = function() change_screen('menu_main') end,
283 -- save
284 delete_saved = {
285 x = .75, y = .75, w = .25, h = .25,
286 content = 'Delete all',
287 onclick = function()
288 menu_elements.confirm_delete.hidden = false
289 menu_elements.delete_saved.hidden = true
290 end,
292 confirm_delete = {
293 hidden = true,
294 x = .75, y = .5, w = .25, h = .25,
295 content = 'Are you sure?',
296 onclick = function()
297 for a = 1, 4 do love.filesystem.remove('game' .. a) end
298 menus.menu_save.onload()
299 end,
301 -- load
302 restart_game = {
303 x = .75, y = .75, w = .25, h = .25,
304 content = 'Restart game',
305 onclick = function()
306 menu_elements.confirm_restart.hidden = false
307 menu_elements.restart_game.hidden = true
308 end,
310 confirm_restart = {
311 hidden = true,
312 x = .75, y = .5, w = .25, h = .25,
313 content = 'Are you sure?',
314 onclick = function()
315 menu_elements.restart_game.hidden = false
316 menu_elements.confirm_restart.hidden = true
317 init()
318 setuplevel()
319 end,
322 for a = 1, 4 do
323 menu_elements['save_slot' .. a ] = {
324 x = 0, y = (a - 1) * .25, w = .5, h = .25,
325 content = function()
326 return 'Slot ' .. a .. ': ' .. (menus.save_slots_info[a] or 'empty')
327 end,
328 onclick = function()
329 save(game, 'game' .. a)
330 change_screen('playfield')
331 end,
333 menu_elements['load_slot' .. a ] = {
334 x = 0, y = (a - 1) * .25, w = .5, h = .25,
335 content = function()
336 return 'Slot ' .. a .. ': ' .. (menus.save_slots_info[a] or 'empty')
337 end,
338 onclick = function()
339 load_game('game' .. a)
340 change_screen('playfield')
341 end,
346 menus = {
347 menu_casette = {
348 background = function()
349 inplayfield(function (x, y, p)
350 draw_floor(x - .5, y - .5)
351 end)
352 end;
353 onload = function(self)
354 self.music_list = {}
355 for k, v in pairs(backgrounds) do
356 table.insert(self.music_list, v.music)
358 table.insert(self.music_list, 'stairway')
359 end,
360 menu_elements.play,
361 menu_elements.exit_to_title,
362 { -- return to objects menu
363 x = .75, y = .75, w = .25, h = .25,
364 content = function()
365 return images.eject
366 end,
367 onclick = function()
368 if current.music then
369 current.music:stop()
370 current.music:release()
371 current.music = nil
373 change_screen('menu_objects')
374 end,
376 { -- title
377 x = 0, y = 0, w = .75, h = .25,
378 content = 'POCA Soundtrack'
380 { -- icon
381 x = .75, y = 0, w = .25, h = .25,
382 content = function() return trinkets.casette end,
384 { -- now_playing
385 x = 0, y = .25, w = 1, h = .25,
386 content = function(self, dx)
387 -- return text only when not playing
388 if not settings.music then
389 return 'Music turned off in Options menu.'
390 elseif not current.music or not current.music:isPlaying() then
391 return ''
393 -- return canvas when playing
394 local ml = menus.menu_casette.music_list
395 local order = 0
396 repeat
397 order = order + 1
398 until order > #ml or ml[order] == current.played
399 local title = order .. '/' .. #ml .. '\n' .. current.played
400 local seconds = math.floor(current.music:tell()) .. 's /\n' ..
401 math.floor(current.music:getDuration()) .. 's'
402 local oy = screen.tilesize / 2 - screen.fontheight
403 local snd_pos = current.music:tell('samples') /
404 current.music:getDuration('samples') * screen.playfieldsize
405 local new_pos = snd_pos + dx
406 new_pos = math.max(0, new_pos)
407 new_pos = math.min(screen.playfieldsize, new_pos)
408 -- draw
409 new_canvas_needed(self)
410 love.graphics.setCanvas(self.canvas)
411 love.graphics.clear(.15, .15, .15)
412 love.graphics.setColor(0, .5, 0)
413 love.graphics.rectangle('fill',
414 0, 0,
415 snd_pos, screen.tilesize
417 if new_pos < snd_pos then
418 love.graphics.setColor(.15, .15, .15)
420 love.graphics.polygon('fill',
421 snd_pos, 0,
422 snd_pos, screen.tilesize,
423 new_pos, screen.tilesize / 2
425 love.graphics.setColor(1, 1, 1)
426 love.graphics.printf(title,
427 0, oy, 3 * screen.tilesize, 'center'
429 love.graphics.printf(seconds,
430 3 * screen.tilesize, oy, screen.tilesize, 'center'
432 love.graphics.setCanvas()
433 return {
434 bmp = self.canvas, offsetx = 0, offsety = 0,
435 scale = screen.playfieldsize
437 end,
438 onsweep = function(self, dx)
439 if not current.music then return end
440 -- seeking in samples doesn't work for modules
441 local snd_pos = current.music:tell()
442 local d = current.music:getDuration()
443 local new_pos = snd_pos + dx * d / screen.playfieldsize
444 new_pos = math.max(0, new_pos)
445 new_pos = math.min(d, new_pos)
446 current.music:seek(new_pos)
449 { -- prev
450 x = 0, y = .5, w = .25, h = .25,
451 content = function()
452 return images.rewind
453 end,
454 onclick = function()
455 local ml = menus.menu_casette.music_list
456 if not current.played then
457 current.played = backgrounds[level_labels[game.currentlevel]].music or
458 'opening'
460 for a = 1, #ml do
461 if ml[a] == current.played then
462 a = (a - 2) % #ml + 1
463 play_music(ml[a], true)
464 menus.menu_casette.play = true
465 break
468 end,
470 menu_elements.casette_play,
471 menu_elements.casette_next,
472 { -- repeat
473 x = .75, y = .5, w = .25, h = .25,
474 content = function()
475 -- handle repeat
476 -- better not setLooping, as songs can be skipped
478 menus.menu_casette.play and
479 current.music and not current.music:isPlaying()
480 then
481 if menus.menu_casette.loop == 1 then
482 menu_elements.casette_play.onclick()
483 elseif menus.menu_casette.loop == 2 then
484 menu_elements.casette_next.onclick()
487 -- actual content
488 local l = menus.menu_casette.loop or 0
489 return unpack(
490 {images.loop_none, images.loop_1, images.loop_all},
491 l + 1, l + 1
493 end,
494 onclick = function()
495 menus.menu_casette.loop = ((menus.menu_casette.loop or 0) + 1) % 3
496 if -- repeat all, while music in progress
497 menus.menu_casette.loop > 0 and
498 current.music and current.music:isPlaying()
499 then
500 menus.menu_casette.play = true
501 else
502 menus.menu_casette.play = false
504 end,
507 menu_hints = {
508 { -- hint book description
509 x = .25, y = .25, w = .5, h = .5,
510 content = function(self)
511 return trinkets['hints_book'].description
512 end,
513 onclick = function(self)
514 change_screen('menu_objects')
515 end,
517 { -- open hint book button
518 x = 0, y = .8, w = .4, h = .2,
519 content = 'Open anyway',
520 onclick = function(self)
521 change_screen('menu_show_hint')
522 end,
524 menu_elements.previous_object,
525 menu_elements.next_object,
526 menu_elements.play,
527 menu_elements.exit_to_title,
528 menu_elements.object_name,
530 menu_show_hint = {
531 background = function()
532 -- show plafield in background
533 draw_initial_playfield()
534 end,
535 onload = function()
536 -- get hint text
537 menus.menu_show_hint.text = (
538 hints[levels[game.currentlevel].setup] or
539 "The page is missing."
541 -- possibly destroy book
542 if math.random (1,6) == 1 then -- bad luck
543 menus.menu_show_hint.text = (
544 menus.menu_show_hint.text ..
545 '\n\nThe book crumbles into dust!'
547 lose_trinket('hints_book')
549 end,
550 { -- draw hint book picture
551 x = .375, y = -.25, w = .25, h = .25,
552 content = function(self)
553 return trinkets['hints_book']
554 end,
555 onclick = function() change_screen('playfield') end,
556 transparent = true,
558 { -- print hint text
559 x = 0, y = 0, w = 1, h = 1,
560 content = function(self)
561 return menus.menu_show_hint.text
562 end,
563 onclick = function() change_screen('playfield') end,
564 transparent = true,
567 menu_matches = {
568 onload = function (self)
569 menu_elements.big_object.page = 2
570 menu_elements.light_cig.hidden = not has_trinket('cigarettes')
571 menu_elements.light_secret.hidden = level_labels[game.currentlevel] ~= 'Secrets'
572 end,
573 { -- custom big_object
574 x = .25, y = .25, w = .5, h = .5,
575 content = function(self)
576 return trinkets['matches'].description ..
577 '\n\nThere are ' .. game.matches .. ' matches left.'
579 end,
580 onclick = function(self)
581 change_screen('menu_objects')
582 end,
584 menu_elements.object_name,
585 menu_elements.previous_object,
586 menu_elements.next_object,
587 menu_elements.exit_to_title,
588 menu_elements.play,
589 menu_elements.light_cig,
590 menu_elements.light_secret,
592 menu_objects = {
593 -- shows current, previous or next trinket
594 get_object_img = function(s) -- s can be -1, 0 or 1
595 local t = trinkets[game.trinkets[select_trinket(s)]]
596 if -- show minimap instead of frame in Secrets
597 t.name == 'Scintilating frame' and
598 level_labels[game.currentlevel] == 'Secrets'
599 then
600 t = {
601 bmp = screen.minimap, offsetx = 0, offsety = 0,
602 scale = screen.tilesize * 4/5
605 return t
606 end,
607 onload = function(self)
608 -- hide + and - when objects < 2
609 menu_elements.next_object.hidden = #game.trinkets < 2
610 menu_elements.previous_object.hidden = menu_elements.next_object.hidden
611 if (not current.trinket) or (not game.trinkets[current.trinket]) then
612 current.trinket = #game.trinkets
614 menu_elements.big_object.page = 1
615 menu_elements.big_object.canvas = nil -- force redraw
616 end,
617 menu_elements.big_object,
618 menu_elements.object_name,
619 menu_elements.previous_object,
620 menu_elements.next_object,
621 menu_elements.play,
622 menu_elements.exit_to_title,
625 menu_objects_donate = { -- used only with the wise man
626 onload = function(self)
627 -- hide "donate" if there are no objects
628 menu_elements.big_object_donate.hidden = (#game.trinkets == 0)
629 menu_elements.confirm_donate.hidden = true
630 menu_elements.donate_title.hidden = false
631 -- hide + and - when objects < 2
632 menu_elements.next_object_donate.hidden = #game.trinkets < 2
633 menu_elements.previous_object_donate.hidden = menu_elements.next_object_donate.hidden
634 if (not current.trinket) or (not game.trinkets[current.trinket]) then
635 current.trinket = #game.trinkets
637 menu_elements.big_object_donate.canvas = nil -- force redraw
638 end,
639 menu_elements.big_object_donate,
640 menu_elements.object_name,
641 menu_elements.previous_object_donate,
642 menu_elements.next_object_donate,
643 { -- abort
644 x = .5, y = -.25, w = .5, h = .25,
645 content = 'Abort',
646 onclick = function()
647 if not menu_elements.confirm_donate.hidden then
648 menu_elements.confirm_donate.hidden = true
649 menu_elements.donate_title.hidden = false
650 else
651 change_screen('playfield')
653 end,
655 menu_elements.donate_title,
656 menu_elements.confirm_donate,
659 menu_save = {
660 onload = function()
661 menus.save_slots_info = read_slots()
662 menu_elements.delete_saved.hidden = false
663 menu_elements.confirm_delete.hidden = true
664 end,
665 menu_elements.exit_to_title,
666 menu_elements.save_slot1, menu_elements.save_slot2,
667 menu_elements.save_slot3, menu_elements.save_slot4,
668 menu_elements.delete_saved,
669 menu_elements.confirm_delete,
670 { -- save title
671 x = 0, y = -.25, w = .5, h = .2,
672 content = 'Save where?',
675 menu_load = {
676 onload = function()
677 menus.save_slots_info = read_slots()
678 menu_elements.restart_game.hidden = false
679 menu_elements.confirm_restart.hidden = true
680 for a = 1, 4 do
681 menu_elements['load_slot' .. a].hidden = not menus.save_slots_info[a]
683 end,
684 { -- load title
685 x = 0, y = -.25, w = .5, h = .2,
686 content = 'Load from which slot?',
688 menu_elements.exit_to_title,
689 menu_elements.load_slot1, menu_elements.load_slot2,
690 menu_elements.load_slot3, menu_elements.load_slot4,
691 menu_elements.restart_game,
692 menu_elements.confirm_restart,
694 menu_map = { -- full-playfield map
696 x = 0, y = 0, w = 1, h = 1,
697 content = function() return trinkets.map end,
698 onclick = function() change_screen('menu_objects') end,
700 menu_elements.exit_to_title,
701 menu_elements.play,
703 menu_frame = { -- full-playfield frame
705 x = 0, y = 0, w = 1, h = 1,
706 content = function()
707 if -- show minimap instead of frame in Secrets
708 game.trinkets[current.trinket] == 'frame' and
709 level_labels[game.currentlevel] == 'Secrets'
710 then
711 return {
712 bmp = screen.minimap, offsetx = 0, offsety = 0,
713 scale = screen.tilesize * 4/5
715 else
716 return trinkets[game.trinkets[current.trinket]]
718 end,
719 onclick = function() change_screen('menu_objects') end,
720 transparent = true,
722 menu_elements.exit_to_title,
723 menu_elements.play,
725 menu_settings = {
726 get_newvolume = function(oldvolume, dx)
727 dx = dx or 0
728 local newvolume = oldvolume + dx /
729 screen.playfieldsize / .9
730 newvolume = math.max(0, newvolume)
731 return math.min(1, newvolume)
732 end,
733 -- sets volume or clicks to turn audio on or off
734 -- audio_kind = 'music' or 'sounds'
735 audio_onsweep = function(e, dx, audio_kind)
736 local w = e.w * screen.playfieldsize
737 if math.abs(dx) < w / 90 then
738 e:onclick()
739 else
740 settings[audio_kind .. '_volume'] =
741 menus.menu_settings.get_newvolume(
742 settings[audio_kind .. '_volume'], dx
744 -- turn on or off music or sounds depending on volume
745 settings[audio_kind] = settings[audio_kind .. '_volume'] ~= 0
747 end,
748 -- shows text and volume bar
749 -- audio_kind = 'music' or 'sounds'
750 audio_content = function(e, dx, audio_kind)
751 if e.sweep_x or new_canvas_needed(e) then
752 local _, _, real_w, real_h = get_absolute_dimensions(e)
753 love.graphics.setCanvas(e.canvas)
754 love.graphics.clear()
755 local newvolume = menus.menu_settings.get_newvolume(
756 settings[audio_kind .. '_volume'], dx
758 -- draw image
759 love.graphics.setColor(0, .5, 0)
760 love.graphics.rectangle('fill',
761 real_w * .05, real_h / 2,
762 real_w * .9 * newvolume, real_h / 3
764 love.graphics.setColor(0, 1, 0)
765 love.graphics.rectangle('line',
766 real_w * .05, real_h / 2,
767 real_w * .9, real_h / 3
769 love.graphics.setColor(1, 1, 1)
770 love.graphics.printf(
772 {1, 1, 1},
773 (audio_kind == 'music' and 'Music is ' or 'Sounds are '),
774 (settings[audio_kind] and {1, 1, 1} or {1,1,1, .5}),
775 (settings[audio_kind] and 'on' or 'off') ..
776 '\n\nVolume: ' ..
777 math.floor(newvolume * 100) .. '%'
778 }, 0, screen.tilesize / 10, real_w, 'center'
780 love.graphics.setCanvas()
782 return {
783 bmp = e.canvas, offsetx = 0, offsety = 0,
784 scale = screen.playfieldsize / e.w
786 end,
787 menu_elements.play,
788 menu_elements.exit_to_title,
789 { -- music
790 x = 0, y = 0, w = 1, h = .25,
791 content = function(self, dx)
792 return menus.menu_settings.audio_content(self, dx, 'music')
793 end,
794 onsweep = function(self, dx)
795 menus.menu_settings.audio_onsweep(self, dx, 'music')
796 if current.music then
797 current.music:setVolume(settings.music_volume)
799 end,
800 onclick = function()
801 settings.music = not settings.music
802 if not settings.music and current.music then
803 current.music:pause()
804 elseif current.music then
805 current.music:play(true)
807 end,
809 { -- sounds
810 x = 0, y = .25, w = 1, h = .25,
811 content = function(self, dx)
812 return menus.menu_settings.audio_content(self, dx, 'sounds')
813 end,
814 onsweep = function(self, dx)
815 menus.menu_settings.audio_onsweep(self, dx, 'sounds')
816 load_sounds() -- sets volume
817 play_sound('tone')
818 end,
819 onclick = function()
820 settings.sounds = not settings.sounds
821 end,
823 { -- animations
824 x = 0, y = .5, w = 1, h = .25,
825 content = function()
826 local a = {
827 'at normal speed',
828 'quicker',
829 'disabled'
831 return 'Animations are ' ..
832 a[settings.speed + 1]
833 end,
834 onclick = function()
835 settings.speed = (settings.speed + 1) % 3
836 end,
838 { -- vibration toggle
839 x = 0, y = .75, w = 1, h = .25,
840 content = function()
841 return 'Vibration is ' ..
842 (settings.vibrate and 'on' or 'off')
843 end,
844 onclick = function()
845 settings.vibrate = not settings.vibrate
846 end,
849 story_beginning = {
851 x = 0, y = 0, w = 1, h = 1,
852 content = 'My journey began when arrows invaded every room of my apartment, including my bedroom.',
853 onclick = function() change_screen('playfield') end,
856 story_corpse = {
857 content = '',
858 onload = function(self)
859 self.content = click_corpse()
860 end,
862 x = 0, y = 0, w = 1, h = 1,
863 content = function() return menus.story_corpse.content end,
864 onclick = function() change_screen('playfield') end,
867 story_big_death = {
869 x = 0, y = .25, w = 1, h = .5,
870 content = 'You sense everything got back where it was.',
871 onclick = function() change_screen('playfield') end,
874 story_big_end = {
876 x = 0, y = 0, w = 1, h = 1,
877 content = 'The end. Thanks for playing!',
878 onclick = function() love.event.push('quit') end,
881 story_battle = {
882 menu_elements.wise_man_says,
884 x = 0, y = .25, w = 1, h = .5,
885 content = 'Are you mad? You are going to finish the game!',
886 onclick = function() start_wise_man_battle() end,
889 story_cigarettes = {
890 onload = function()
891 game.cigarettes = game.cigarettes - 1
892 if game.cigarettes == 0 then
893 lose_trinket('cigarettes')
895 end,
897 x = 0, y = 0, w = 1, h = 1,
898 content = function()
899 if game.cigarettes == 2 then
900 return 'You hastily light a cigarette and inhale. You feel somewhat better.'
901 elseif game.cigarettes == 1 then
902 return 'You take your time smoking a cigarette. It feels good.'
903 elseif game.cigarettes == 0 then
904 return 'You nervously smoke a cigarette. It feels slightly nauseating.'
906 end,
907 onclick = function() change_screen('playfield') end,
910 -- wise man
911 wise_man = { -- wise man accepts donations
912 background = function()
913 inplayfield(function (x, y, p)
914 draw_floor(x - .5, y - .5, false, nil, nil)
915 end)
916 end,
917 menu_elements.wise_man_picture,
918 { -- wise man text
919 x = 0, y = .25, w = 1, h = .5,
920 content = 'I am the wise man.\n' ..
921 'Not to talk much is wise.\n\n' ..
922 'I accept your generous donations.',
924 { -- donation button
925 x = 0, y = -.25, w = .5, h = .25,
926 content ='Donate',
927 onclick = function()
928 change_screen('menu_objects_donate')
929 menu_elements.confirm_donate.hidden = true
932 { -- decline button
933 x = .5, y = -.25, w = .5, h = .25,
934 content ='Decline',
935 onclick = function()
936 change_screen('playfield')
940 wise_man_offers = {
941 onload = function(self)
942 -- offer pool created when donate button was pressed
943 if #self.offer_pool == 0 then
944 change_screen('wise_man_thanks')
945 return
947 self.offer = self.offer_pool[#self.offer_pool]
948 self.offer_pool[#self.offer_pool] = nil
949 self.offer_text = (
950 wise_advices[self.offer] and wise_advices[self.offer][1]
951 ) or
952 trinkets[self.offer].description or
953 trinkets[self.offer].name
954 local text_ending = string.sub(self.offer_text, -1)
955 if not (text_ending == '.' or text_ending == '!') then
956 self.offer_text = self.offer_text .. '.'
958 end,
959 { -- Yes button
960 x = .2, y = .75, w = .2, h = .2,
961 content ='Yes',
962 onclick = function()
963 local offer = menus.wise_man_offers.offer
964 -- Wise man loses offered
965 wise_man_lose(offer)
966 -- player gains offered, if trinket
967 if trinkets[offer] then
968 get_trinket(offer)
969 else
970 -- if advice
971 menus.wise_man_thanks.advice = wise_advices[offer]
973 change_screen('wise_man_thanks')
976 { -- No button ; make a new offer
977 x = .6, y = .75, w = .2, h = .2,
978 content ='No',
979 onclick = function() change_screen('wise_man_offers') end,
981 menu_elements.wise_man_picture,
982 { -- wise man text
983 x = 0, y = .25, w = 1, h = .5,
984 content = function()
985 return 'Wise man offers: ' ..
986 menus.wise_man_offers.offer_text ..
987 '\n\nDo you accept?'
988 end,
991 wise_man_thanks = {
992 onload = function(self)
993 if self.advice then
994 self.towrite = 'Hear this ' .. self.advice[1] .. '!\n\n' .. self.advice[2]
995 else
996 self.towrite = nil
998 self.advice = nil -- reset for the next time
999 end,
1000 menu_elements.wise_man_picture,
1001 menu_elements.wise_man_says,
1002 { -- wise man text
1003 x = 0, y = .25, w = 1, h = .5,
1004 content = function()
1005 return menus.wise_man_thanks.towrite or 'Thank you for your donation!'
1008 { -- donation button
1009 x = 0, y = -.25, w = .5, h = .26,
1010 content ='Trade another item',
1011 onclick = function() change_screen('menu_objects_donate') end
1013 { -- play
1014 x = .5, y = -.25, w = .5, h = .25,
1015 content ='Goodbye!',
1016 onclick = function() change_screen('playfield') end
1019 -- about
1020 menu_about = {
1021 background = function()
1022 inplayfield(function (x, y, p)
1023 draw_floor(x - .5, y - .5)
1024 end)
1025 end,
1026 onload = function(self)
1027 self.page = 1
1028 end,
1029 menu_elements.play,
1030 menu_elements.exit_to_title,
1031 { -- copyright
1032 x = .5, y = 0, w = .5, h = .25,
1033 content = 'Copyright 2007, 2019, 2020 Pajo\n<xpio@tut.by>'
1035 { -- po
1036 x = 0, y = 0, w = .25, h = .25,
1037 content = images.po,
1039 { -- ca
1040 x = .25, y = 0, w = .25, h = .25,
1041 content = images.ca,
1043 { -- text - refreshes bg which gets destroyed on resize
1044 x = 0, y = .25, w = 1, h = .5,
1045 content = function()
1046 local page = menus.menu_about.page
1047 if page == 1 then
1048 return 'This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.'
1049 elseif page == 2 then
1050 return 'This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.'
1051 elseif page == 3 then
1052 return 'You should find a copy of the GNU General Public License somewhere in this game. If not, see <https://www.gnu.org/licenses/>.'
1054 end,
1056 { -- button: Copyright
1057 x = 0, y = .75, w = .33, h = .25,
1058 content = 'Copyright',
1059 onclick = function()
1060 menus.menu_about.page = 1
1061 end,
1063 { -- button: Warranty
1064 x = .33, y = .75, w = .34, h = .25,
1065 content = 'Warranty',
1066 onclick = function()
1067 menus.menu_about.page = 2
1068 end,
1070 { -- button: License
1071 x = .67, y = .75, w = .33, h = .25,
1072 content = 'License',
1073 onclick = function()
1074 menus.menu_about.page = 3
1075 end,
1079 -- title
1080 menu_main = {
1081 onload = function()
1082 -- pfcanvas will be used in foreground
1083 screen.pfcanvas:renderTo(draw_title)
1084 end,
1085 menu_elements.play,
1087 x = .5, y = -.25, w = .25, h = .25,
1088 content = 'About',
1089 onclick = function() change_screen('menu_about') end,
1092 x = .75, y = -.25, w = .25, h = .25,
1093 content = 'Exit',
1094 onclick = function() love.event.push('quit') end,
1096 { -- title screen
1097 x = 0, y = 0, w = 1, h = 1,
1098 content = function()
1099 return {
1100 bmp = screen.pfcanvas,
1101 offsetx = 0, offsety = 0,
1102 scale = screen.playfieldsize
1107 x = 0, y = .0, w = .25, h = .25,
1108 content = 'Load',
1109 onclick = function() change_screen('menu_load') end,
1112 x = 0, y = .25, w = .25, h = .25,
1113 content = 'Save',
1114 onclick = function() change_screen('menu_save') end,
1117 x = .75, y = .5, w = .25, h = .25,
1118 content = 'Objects',
1119 onclick = function() change_screen('menu_objects') end,
1122 x = .75, y = .75, w = .25, h = .25,
1123 content = 'Options',
1124 onclick = function() change_screen('menu_settings') end,
1130 -- menu mechanics
1133 function get_absolute_dimensions(e)
1134 return
1135 e.x * screen.playfieldsize + screen.playfieldx,
1136 e.y * screen.playfieldsize + screen.playfieldy,
1137 e.w * screen.playfieldsize,
1138 e.h * screen.playfieldsize
1142 function draw_menu_elements()
1143 local function draw_element(e, selected)
1144 if e.hidden then return end
1145 local x, y, w, h = get_absolute_dimensions(e)
1146 if selected then
1147 love.graphics.setColor(.5, 0, 1)
1148 else
1149 love.graphics.setColor(.1, .1, .1)
1151 if e.onclick and not e.transparent then
1152 love.graphics.rectangle('fill', x, y, w, h)
1153 love.graphics.setColor(.5, .5, .5)
1154 love.graphics.rectangle('line', x + 2, y + 2, w - 4, h - 4)
1156 love.graphics.setColor(1,1,1)
1157 local c
1158 if e.content then
1159 if type(e.content) == 'function' then
1160 local dx = 0
1161 if e.sweep_x then
1162 dx = (love.mouse.getPosition() - e.sweep_x)
1164 c = e:content(dx)
1165 else
1166 c = e.content
1168 if type(c) == 'string' then
1169 local lines = select(2, screen.defaultfont:getWrap(c, w - 4))
1170 local offsety = (h - screen.defaultfont:getHeight() * #lines) / 2
1171 love.graphics.printf(c, x + 2, y + 2 + offsety, w - 4 ,'center')
1172 elseif type(c) == 'table' and c.bmp then
1173 local function drawbmp(c)
1174 love.graphics.draw(c.bmp,
1175 x + c.offsetx * screen.tilesize, y + c.offsety * screen.tilesize,
1176 0, w / c.scale
1179 drawbmp(c)
1183 for a = 1, #menus.current do
1184 draw_element(menus.current[a], menus.current.selected == a)
1189 function draw_menu()
1190 menus.current = menus.current or menus[current.screen]
1191 if menus.current.background then
1192 screen.pfcanvas:renderTo(menus.current.background)
1194 love.graphics.setColor(.5,.5,.5)
1195 if game.shuffled and not menus.current.background then
1196 draw_shuffled()
1197 else
1198 love.graphics.draw(screen.pfcanvas, screen.playfieldx, screen.playfieldy)
1200 draw_menu_elements()
1204 function menu_click(mx, my, sweep_end)
1205 if not menus.current then return end
1206 if sweep_end then
1207 for _, e in ipairs(menus.current) do
1208 if e.sweep_x then
1209 local dx = mx - e.sweep_x
1210 e.sweep_x = nil
1211 e.canvas = nil -- force redraw
1212 e:onsweep(dx)
1215 else -- not sweep_end: start sweep or normal click if no sweep defined
1216 for _, e in ipairs(menus.current) do
1217 if (not e.hidden) and isin(mx, my, get_absolute_dimensions(e)) then
1218 if e.onsweep then
1219 e.sweep_x = mx
1220 elseif e.onclick then
1221 e:onclick()
1229 function menu_select(dx, dy)
1230 -- if nothing selected, select first
1231 local selected = menus.current.selected
1232 if not selected or not dx then
1233 menus.current.selected = 1
1234 return
1237 -- search for suitable element in the desired direction
1238 -- start from the element' center, then check in the desired direction
1239 -- but with deviation along the opposite (passive; pas) axis
1240 local function searchelement(startx, starty, dx, dy)
1241 local RESOLUTION = 50 -- dots per playfield
1242 local DEVIATION = .5
1243 local start_act, start_pas, d_act = startx, starty, dx
1244 if dx == 0 then start_act, start_pas, d_act = starty, startx, dy end
1245 for act = start_act, start_act + d_act, d_act / RESOLUTION do
1246 for pas = 0, math.abs(start_act - act), 1 / RESOLUTION do
1247 for dir = - DEVIATION, DEVIATION, 2 * DEVIATION do
1248 for e = 1, #menus.current do
1249 if not (selected == e) then
1250 local mce = menus.current[e]
1251 local x, y = act, start_pas + pas * dir
1252 if dx == 0 then x, y = y, x end
1254 mce.onclick and not mce.hidden and
1255 isin(x, y, mce.x, mce.y, mce.w, mce.h)
1256 then
1257 return e
1266 -- search from center of current element
1267 local newselected = searchelement(
1268 menus.current[selected].x + menus.current[selected].w / 2,
1269 menus.current[selected].y + menus.current[selected].h / 2,
1270 dx or 0, dy or 0
1273 if newselected then menus.current.selected = newselected end
1277 function menu_press()
1278 for a = 1, #menus.current do
1279 local e = menus.current[a]
1280 if e.onclick and (a == menus.current.selected) and not e.hidden then
1281 e:onclick()
1282 return
1285 -- if nothing selected, execute the first with onclick
1286 for i, e in ipairs(menus.current) do
1287 if e.onclick then
1288 e:onclick()
1289 return
1295 function change_screen(newscreen, only_once)
1296 if only_once and game.shown_screens[newscreen] then return end
1297 game.shown_screens[newscreen] = true
1298 menus.current = nil
1299 current.screen = newscreen
1300 if menus[current.screen] and menus[current.screen].onload then menus[current.screen]:onload() end