Compare commits

...

40 Commits

Author SHA1 Message Date
8921f02821 mouse handling refact
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-02 22:12:58 +02:00
211af18c26 debug mode fix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-02 21:51:43 +02:00
b337ae8516 main menu tweaks
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-02 19:12:39 +02:00
10316d3075 Controls menu 2026-04-02 18:51:17 +02:00
589b225ab0 remove configuration menu 2026-04-02 18:45:12 +02:00
7697b35336 input remapping + mouse control
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-02 18:39:36 +02:00
6e1cf1db3e remove situation management 2026-04-02 15:32:20 +02:00
020bfd4134 set version to beta2
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-23 07:02:39 +01:00
8e610f14a0 new ddr2 game
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 23:47:12 +01:00
753509b4ea Merge branch 'develop' into rle-screens
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 23:17:45 +01:00
7dbbbb4668 day switch and ddr backgrounds
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 23:13:34 +01:00
3290d0fb89 Merge branch 'feature/ddr_upgrade' into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 22:30:20 +01:00
4cf7df511b Merge branch 'develop' into feature/ddr_upgrade
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 22:27:12 +01:00
8b0bcdbe95 - lint fixes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 22:24:36 +01:00
7c3a011ffc - lint fixes
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-22 22:10:56 +01:00
6ee874655e - lint fix :D 2026-03-22 22:06:35 +01:00
3420694287 - linter fixes
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-22 21:55:12 +01:00
e12a27e4e1 - linter fixes
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-22 21:53:51 +01:00
81b47c3760 rle screens 2026-03-22 21:53:29 +01:00
53ccda7702 - updated decision texts for less grammatical errors
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- fixed for "only_left" game maxing out before special win
- game ends with TO BE CONTINUED at ASC 4
2026-03-22 21:42:13 +01:00
2912d7c482 RLE lib
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 21:21:27 +01:00
284c5aa4c8 time based scroll
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-22 19:03:05 +01:00
93df710d14 asciiart update 2026-03-22 18:56:16 +01:00
4e6590174a - lint fix
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 23:31:40 +01:00
6a33be82e9 - added music and sounds to things
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- hooked up ddr logic into game logic
- TODO: "left_only" ddr needs tweak
2026-03-21 23:20:48 +01:00
mr.one
9a3c9ee28c - ddr special logic seems to be working in solation
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 17:26:39 +01:00
197cb9403b add tic & zip to gitignore
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-21 07:10:09 +01:00
mr.one
c41bf23a45 - musicator: generation + ddr operational
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- TODO: special logic, code cleanup
2026-03-21 01:41:30 +01:00
9d6d2c2c6f Merge pull request 'feature/imp-99-add-discussions' (#44) from feature/imp-99-add-discussions into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: http://git.teletype.hu/games/impostor/pulls/44
2026-03-20 23:19:58 +00:00
Zoltan Timar
fb8ae157b6 fix: lint
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-21 00:18:52 +01:00
Zoltan Timar
803ddc482b Merge branch 'develop' into feature/imp-99-add-discussions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:17:08 +01:00
Zoltan Timar
ed14a86997 fix: lint
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:16:38 +01:00
Zoltan Timar
9263a90961 fix: lint
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:15:18 +01:00
Zoltan Timar
05091c7af2 feat: added sumphore & coworker discussions until asc 3, fixed game flow bugs, added mysterious man screen texts
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-21 00:12:15 +01:00
Zoltan Timar
f79233521d fix: removed mm window, made it into screen
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-20 21:25:37 +01:00
mr.one
46d3ff2ebc - tools/musicator:\n - markov model generator and pattern generator operational\n- DDR sound generation in-progress
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-20 20:18:10 +01:00
Zoltan Timar
3e6fa41021 feat: added mm discussions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-03-20 18:23:17 +01:00
d899a74411 set version to 1.0-beta1
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline failed
2026-03-20 15:33:00 +01:00
4c7d43fea3 set version to 1.0-alpha
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-20 15:30:39 +01:00
94563d32c0 Merge pull request 'test mode' (#42) from feature/test-mode into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
Reviewed-on: http://git.teletype.hu/games/impostor/pulls/42
2026-03-20 06:25:24 +00:00
62 changed files with 2537 additions and 676 deletions

6
.gitignore vendored
View File

@@ -1,6 +1,10 @@
.claude
.local .local
impostor.lua impostor.lua
impostor.original.lua impostor.original.lua
prompts prompts
docs docs
minify.lua minify.lua
*.tic
*.zip
NOTES_*

View File

@@ -10,7 +10,6 @@ globals = {
"Discussion", "Discussion",
"Util", "Util",
"Decision", "Decision",
"Situation",
"Screen", "Screen",
"Sprite", "Sprite",
"UI", "UI",
@@ -31,12 +30,12 @@ globals = {
"MenuWindow", "MenuWindow",
"GameWindow", "GameWindow",
"PopupWindow", "PopupWindow",
"ConfigurationWindow", "ControlsWindow",
"AudioTestWindow", "AudioTestWindow",
"MinigameButtonMashWindow", "MinigameButtonMashWindow",
"MinigameRhythmWindow", "MinigameRhythmWindow",
"MinigameDDRWindow", "MinigameDDRWindow",
"MysteriousManWindow", "MysteriousManScreen",
"DiscussionWindow", "DiscussionWindow",
"EndWindow", "EndWindow",
"mset", "mset",
@@ -64,6 +63,12 @@ globals = {
"index_menu", "index_menu",
"Map", "Map",
"map", "map",
"time",
"RLE",
"mouse",
"Mouse",
"print",
"musicator_generate_pattern",
} }

View File

@@ -30,5 +30,5 @@
"files.associations": { "files.associations": {
"*.conf": "bitbake", "*.conf": "bitbake",
"*.inc": "bitbake" "*.inc": "bitbake"
} },
} }

View File

@@ -6,7 +6,9 @@ init/init.context.lua
system/system.util.lua system/system.util.lua
system/system.print.lua system/system.print.lua
system/system.input.lua system/system.input.lua
system/system.mouse.lua
system/system.asciiart.lua system/system.asciiart.lua
system/system.rle.lua
logic/logic.meter.lua logic/logic.meter.lua
logic/logic.focus.lua logic/logic.focus.lua
logic/logic.day.lua logic/logic.day.lua
@@ -17,6 +19,7 @@ logic/logic.glitch.lua
logic/logic.discussion.lua logic/logic.discussion.lua
system/system.ui.lua system/system.ui.lua
audio/audio.manager.lua audio/audio.manager.lua
audio/audio.generator.lua
audio/audio.songs.lua audio/audio.songs.lua
sprite/sprite.manager.lua sprite/sprite.manager.lua
sprite/sprite.norman.lua sprite/sprite.norman.lua
@@ -36,10 +39,7 @@ sprite/sprite.matrix_architect.lua
sprite/sprite.matrix_neo.lua sprite/sprite.matrix_neo.lua
sprite/sprite.matrix_oraculum.lua sprite/sprite.matrix_oraculum.lua
sprite/sprite.matrix_trinity.lua sprite/sprite.matrix_trinity.lua
situation/situation.manager.lua
situation/situation.drink_coffee.lua
decision/decision.manager.lua decision/decision.manager.lua
decision/decision.have_a_coffee.lua
decision/decision.go_to_home.lua decision/decision.go_to_home.lua
decision/decision.go_to_toilet.lua decision/decision.go_to_toilet.lua
decision/decision.go_to_walking_to_office.lua decision/decision.go_to_walking_to_office.lua
@@ -48,8 +48,9 @@ decision/decision.go_to_end.lua
decision/decision.go_to_walking_to_home.lua decision/decision.go_to_walking_to_home.lua
decision/decision.go_to_sleep.lua decision/decision.go_to_sleep.lua
decision/decision.do_work.lua decision/decision.do_work.lua
decision/decision.start_discussion.lua decision/decision.sumphore_discussion.lua
discussion/discussion.sumphore.lua discussion/discussion.sumphore.lua
discussion/discussion.coworker.lua
map/map.manager.lua map/map.manager.lua
map/map.bedroom.lua map/map.bedroom.lua
map/map.street.lua map/map.street.lua
@@ -69,13 +70,12 @@ window/window.intro.title.lua
window/window.intro.ttg.lua window/window.intro.ttg.lua
window/window.intro.brief.lua window/window.intro.brief.lua
window/window.menu.lua window/window.menu.lua
window/window.configuration.lua window/window.controls.lua
window/window.audiotest.lua window/window.audiotest.lua
window/window.popup.lua window/window.popup.lua
window/window.minigame.mash.lua window/window.minigame.mash.lua
window/window.minigame.rhythm.lua window/window.minigame.rhythm.lua
window/window.minigame.ddr.lua window/window.minigame.ddr.lua
window/window.mysterious_man.lua
window/window.discussion.lua window/window.discussion.lua
window/window.continued.lua window/window.continued.lua
window/window.game.lua window/window.game.lua

View File

@@ -0,0 +1,744 @@
local musicator_markov_model = {
model = {
["...|..."] = {
next = {
["..."] = 0.71111111111111,
["A-4"] = 0.0074074074074074,
["B-4"] = 0.0037037037037037,
["C-3"] = 0.011111111111111,
["C-4"] = 0.037037037037037,
["C-5"] = 0.11111111111111,
["C-6"] = 0.0037037037037037,
["D-4"] = 0.011111111111111,
["D-5"] = 0.018518518518519,
["E-4"] = 0.0074074074074074,
["E-5"] = 0.025925925925926,
["F-5"] = 0.0074074074074074,
["G-3"] = 0.0037037037037037,
["G-4"] = 0.022222222222222,
["G-5"] = 0.018518518518519
},
total = 270
},
["...|A-4"] = {
next = {
["..."] = 0.9,
["C-5"] = 0.1
},
total = 10
},
["...|A-5"] = {
next = {
["..."] = 1
},
total = 8
},
["...|B-4"] = {
next = {
["..."] = 1
},
total = 1
},
["...|B-5"] = {
next = {
["..."] = 1
},
total = 5
},
["...|C-3"] = {
next = {
["..."] = 0.66666666666667,
["C-5"] = 0.33333333333333
},
total = 3
},
["...|C-4"] = {
next = {
["..."] = 0.875,
["D-4"] = 0.083333333333333,
["E-4"] = 0.041666666666667
},
total = 24
},
["...|C-5"] = {
next = {
["..."] = 0.73333333333333,
["B-4"] = 0.033333333333333,
["C-5"] = 0.066666666666667,
["D-5"] = 0.13333333333333,
["E-5"] = 0.033333333333333
},
total = 60
},
["...|C-6"] = {
next = {
["..."] = 1
},
total = 1
},
["...|D-4"] = {
next = {
["..."] = 0.92857142857143,
["D-4"] = 0.071428571428571
},
total = 14
},
["...|D-5"] = {
next = {
["..."] = 0.80645161290323,
["C-5"] = 0.032258064516129,
["D-5"] = 0.032258064516129,
["E-5"] = 0.12903225806452
},
total = 31
},
["...|D-6"] = {
next = {
["..."] = 1
},
total = 3
},
["...|E-4"] = {
next = {
["..."] = 1
},
total = 19
},
["...|E-5"] = {
next = {
["..."] = 0.77777777777778,
["C-5"] = 0.022222222222222,
["D-5"] = 0.13333333333333,
["F-5"] = 0.066666666666667
},
total = 45
},
["...|F-3"] = {
next = {
["..."] = 1
},
total = 3
},
["...|F-4"] = {
next = {
["..."] = 0.8,
["D-4"] = 0.1,
["F-4"] = 0.1
},
total = 10
},
["...|F-5"] = {
next = {
["..."] = 0.66666666666667,
["D-5"] = 0.066666666666667,
["E-5"] = 0.066666666666667,
["G-5"] = 0.2
},
total = 15
},
["...|G-3"] = {
next = {
["..."] = 0.8,
["G-5"] = 0.2
},
total = 5
},
["...|G-4"] = {
next = {
["..."] = 0.95652173913043,
["E-4"] = 0.043478260869565
},
total = 23
},
["...|G-5"] = {
next = {
["..."] = 0.875,
["A-5"] = 0.0625,
["E-5"] = 0.0625
},
total = 16
},
["...|G-6"] = {
next = {
["..."] = 1
},
total = 1
},
["A-4|..."] = {
next = {
["..."] = 0.55555555555556,
["C-5"] = 0.33333333333333,
["D-5"] = 0.11111111111111
},
total = 9
},
["A-4|B-4"] = {
next = {
["C-5"] = 1
},
total = 2
},
["A-4|C-5"] = {
next = {
["..."] = 1
},
total = 1
},
["A-4|G-4"] = {
next = {
["F-4"] = 1
},
total = 2
},
["A-5|..."] = {
next = {
["..."] = 0.2,
["B-5"] = 0.1,
["E-4"] = 0.1,
["E-5"] = 0.4,
["F-5"] = 0.1,
["G-5"] = 0.1
},
total = 10
},
["A-5|G-5"] = {
next = {
["..."] = 0.33333333333333,
["A-5"] = 0.66666666666667
},
total = 3
},
["B-4|..."] = {
next = {
["A-4"] = 1
},
total = 1
},
["B-4|A-4"] = {
next = {
["G-4"] = 1
},
total = 2
},
["B-4|C-5"] = {
next = {
["..."] = 1
},
total = 2
},
["B-5|..."] = {
next = {
["..."] = 0.2,
["A-5"] = 0.2,
["D-6"] = 0.4,
["G-5"] = 0.2
},
total = 5
},
["C-3|..."] = {
next = {
["C-4"] = 1
},
total = 2
},
["C-3|C-5"] = {
next = {
["..."] = 0.33333333333333,
["C-3"] = 0.66666666666667
},
total = 3
},
["C-4|..."] = {
next = {
["..."] = 0.5,
["D-4"] = 0.125,
["E-4"] = 0.041666666666667,
["F-3"] = 0.041666666666667,
["G-3"] = 0.16666666666667,
["G-4"] = 0.083333333333333,
["G-5"] = 0.041666666666667
},
total = 24
},
["C-4|D-4"] = {
next = {
["E-4"] = 1
},
total = 2
},
["C-4|E-4"] = {
next = {
["..."] = 1
},
total = 1
},
["C-5|..."] = {
next = {
["..."] = 0.40677966101695,
["A-4"] = 0.067796610169492,
["C-5"] = 0.050847457627119,
["D-5"] = 0.20338983050847,
["E-5"] = 0.15254237288136,
["F-5"] = 0.016949152542373,
["G-4"] = 0.10169491525424
},
total = 59
},
["C-5|B-4"] = {
next = {
["A-4"] = 1
},
total = 2
},
["C-5|C-3"] = {
next = {
["C-5"] = 1
},
total = 2
},
["C-5|C-5"] = {
next = {
["..."] = 0.8,
["C-5"] = 0.2
},
total = 5
},
["C-5|D-5"] = {
next = {
["..."] = 0.3,
["C-5"] = 0.2,
["D-5"] = 0.1,
["E-5"] = 0.4
},
total = 10
},
["C-5|E-5"] = {
next = {
["..."] = 0.33333333333333,
["D-5"] = 0.33333333333333,
["G-5"] = 0.33333333333333
},
total = 3
},
["C-6|..."] = {
next = {
["A-5"] = 1
},
total = 1
},
["D-4|..."] = {
next = {
["..."] = 0.26666666666667,
["A-5"] = 0.066666666666667,
["C-4"] = 0.2,
["E-4"] = 0.4,
["F-3"] = 0.066666666666667
},
total = 15
},
["D-4|C-4"] = {
next = {
["..."] = 1
},
total = 2
},
["D-4|D-4"] = {
next = {
["..."] = 1
},
total = 1
},
["D-4|E-4"] = {
next = {
["F-4"] = 1
},
total = 2
},
["D-5|..."] = {
next = {
["..."] = 0.2258064516129,
["A-4"] = 0.032258064516129,
["A-5"] = 0.032258064516129,
["C-5"] = 0.2258064516129,
["E-5"] = 0.29032258064516,
["F-5"] = 0.096774193548387,
["G-5"] = 0.096774193548387
},
total = 31
},
["D-5|C-5"] = {
next = {
["..."] = 0.77777777777778,
["D-5"] = 0.22222222222222
},
total = 9
},
["D-5|D-5"] = {
next = {
["..."] = 0.5,
["E-5"] = 0.5
},
total = 2
},
["D-5|E-5"] = {
next = {
["..."] = 0.33333333333333,
["E-5"] = 0.11111111111111,
["F-5"] = 0.55555555555556
},
total = 9
},
["D-5|F-5"] = {
next = {
["E-5"] = 1
},
total = 1
},
["D-6|..."] = {
next = {
["B-5"] = 0.66666666666667,
["G-6"] = 0.33333333333333
},
total = 3
},
["E-4|..."] = {
next = {
["..."] = 0.19047619047619,
["B-5"] = 0.047619047619048,
["C-4"] = 0.14285714285714,
["D-4"] = 0.23809523809524,
["F-4"] = 0.19047619047619,
["G-4"] = 0.19047619047619
},
total = 21
},
["E-4|D-4"] = {
next = {
["C-4"] = 1
},
total = 2
},
["E-4|F-4"] = {
next = {
["G-4"] = 1
},
total = 2
},
["E-5|..."] = {
next = {
["..."] = 0.18604651162791,
["A-5"] = 0.046511627906977,
["C-5"] = 0.27906976744186,
["D-5"] = 0.2093023255814,
["E-5"] = 0.069767441860465,
["F-5"] = 0.093023255813953,
["G-4"] = 0.023255813953488,
["G-5"] = 0.093023255813953
},
total = 43
},
["E-5|C-5"] = {
next = {
["E-5"] = 1
},
total = 1
},
["E-5|D-5"] = {
next = {
["..."] = 0.125,
["C-5"] = 0.75,
["F-5"] = 0.125
},
total = 8
},
["E-5|E-5"] = {
next = {
["F-5"] = 1
},
total = 1
},
["E-5|F-5"] = {
next = {
["..."] = 0.5,
["E-5"] = 0.4,
["G-5"] = 0.1
},
total = 10
},
["E-5|G-5"] = {
next = {
["..."] = 0.5,
["F-5"] = 0.5
},
total = 2
},
["F-3|..."] = {
next = {
["C-4"] = 1
},
total = 3
},
["F-4|..."] = {
next = {
["D-4"] = 0.11111111111111,
["E-4"] = 0.44444444444444,
["G-4"] = 0.44444444444444
},
total = 9
},
["F-4|D-4"] = {
next = {
["..."] = 1
},
total = 1
},
["F-4|E-4"] = {
next = {
["D-4"] = 1
},
total = 2
},
["F-4|F-4"] = {
next = {
["..."] = 1
},
total = 1
},
["F-4|G-4"] = {
next = {
["A-4"] = 1
},
total = 2
},
["F-5|..."] = {
next = {
["..."] = 0.2,
["A-5"] = 0.066666666666667,
["D-5"] = 0.2,
["E-5"] = 0.46666666666667,
["G-5"] = 0.066666666666667
},
total = 15
},
["F-5|A-5"] = {
next = {
["G-5"] = 1
},
total = 1
},
["F-5|D-5"] = {
next = {
["..."] = 1
},
total = 1
},
["F-5|E-5"] = {
next = {
["..."] = 0.5,
["D-5"] = 0.16666666666667,
["F-5"] = 0.16666666666667,
["G-5"] = 0.16666666666667
},
total = 6
},
["F-5|G-5"] = {
next = {
["..."] = 0.75,
["A-5"] = 0.25
},
total = 4
},
["G-3|..."] = {
next = {
["C-4"] = 0.5,
["D-4"] = 0.25,
["F-3"] = 0.25
},
total = 4
},
["G-3|G-5"] = {
next = {
["..."] = 0.5,
["G-3"] = 0.5
},
total = 2
},
["G-4|..."] = {
next = {
["..."] = 0.090909090909091,
["A-4"] = 0.090909090909091,
["C-4"] = 0.045454545454545,
["C-5"] = 0.18181818181818,
["D-5"] = 0.045454545454545,
["E-4"] = 0.22727272727273,
["E-5"] = 0.045454545454545,
["F-4"] = 0.27272727272727
},
total = 22
},
["G-4|A-4"] = {
next = {
["B-4"] = 1
},
total = 2
},
["G-4|E-4"] = {
next = {
["..."] = 1
},
total = 1
},
["G-4|F-4"] = {
next = {
["E-4"] = 1
},
total = 2
},
["G-5|..."] = {
next = {
["..."] = 0.35,
["A-5"] = 0.05,
["B-5"] = 0.05,
["C-5"] = 0.05,
["D-4"] = 0.05,
["E-5"] = 0.25,
["F-5"] = 0.2
},
total = 20
},
["G-5|A-5"] = {
next = {
["..."] = 0.5,
["G-5"] = 0.5
},
total = 4
},
["G-5|E-5"] = {
next = {
["..."] = 1
},
total = 1
},
["G-5|F-5"] = {
next = {
["A-5"] = 1
},
total = 1
},
["G-5|G-3"] = {
next = {
["G-5"] = 1
},
total = 1
},
["G-6|..."] = {
next = {
["D-6"] = 1
},
total = 1
}
},
order = 2
}
local function musicator_unmake_key(k)
local result = {}
for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t
end
return result
end
local function musicator_count_notes(sequence)
local result = 0
for _,v in ipairs(sequence) do
if v ~= "..." then
result = result + 1
end
end
return result
end
local function musicator_generate_sequence(model_data, length)
local order = model_data.order
local model = model_data.model
-- random start key
local model_keys = {}
for k,_ in pairs(model) do
model_keys[#model_keys + 1] = k
end
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
-- sequence starts with the start key
local seq = musicator_unmake_key(start_key)
-- generation loop
while musicator_count_notes(seq) < length do
local current_key = table.concat({table.unpack(seq, #seq - order + 1, #seq)}, "|") -- luacheck: ignore
local chosen = "..."
local key_data = model[current_key]
if key_data then
local r = math.random()
local prob_sum = 0.0
for new_note, new_prob in pairs(key_data.next) do
prob_sum = prob_sum + new_prob
if prob_sum < r then
chosen = new_note
end
end
end
-- print(current_key .. " --> " .. chosen)
seq[#seq+1] = chosen
end
return seq
end
local function musicator_row_to_frame(row, bpm, spd)
local frames_per_row = (150 * spd) / bpm
return math.floor(row * frames_per_row)
end
local function musicator_note_to_direction(note)
local subnote = note:sub(1,1)
local mapping = {
C="left",
D="up",
E="up",
F="right",
G="right",
A="down"
}
return mapping[subnote] or "up"
end
-- converts generated sequence to pattern that the ddr minigame can consume
local function musicator_sequence_to_pattern(sequence, bpm, spd)
local result = {}
for i, note in ipairs(sequence) do
if note ~= "..." then
local at_ms = musicator_row_to_frame(i, bpm, spd)
local direction = musicator_note_to_direction(note)
result[#result + 1] = { frame=at_ms, dir=direction, note=note }
end
end
return result
end
local function musicator_generate_pattern(length, bpm, spd)
return musicator_sequence_to_pattern(musicator_generate_sequence(musicator_markov_model, length), bpm, spd)
end

View File

@@ -1,48 +1,93 @@
--- @section Audio --- @section Audio
Audio = {
music_playing = nil
}
--- Stops current music. --- Stops current music.
--- @within Audio --- @within Audio
function Audio.music_stop() music() end function Audio.music_stop()
music()
Audio.music_playing = nil
end
--- Plays track, doesn't restart if already playing.
function Audio.music_play(track)
if Audio.music_playing ~= track then
music(track)
Audio.music_playing = track
end
end
--- Plays main menu music. --- Plays main menu music.
--- @within Audio --- @within Audio
function Audio.music_play_mainmenu() end function Audio.music_play_mainmenu() end
--- Plays mystery man music.
--- @within Audio
function Audio.music_play_mystery() Audio.music_play(2) end
--- Plays waking up music. --- Plays waking up music.
--- @within Audio --- @within Audio
function Audio.music_play_wakingup() end function Audio.music_play_wakingup() end
--- Plays room morning music. --- Plays room morning music.
--- @within Audio --- @within Audio
function Audio.music_play_room_morning() end function Audio.music_play_room_morning() end
--- Plays room street 1 music. --- Plays room street 1 music.
--- @within Audio --- @within Audio
function Audio.music_play_room_street_1() end function Audio.music_play_room_street_1() end
--- Plays room street 2 music. --- Plays room street 2 music.
--- @within Audio --- @within Audio
function Audio.music_play_room_street_2() end function Audio.music_play_room_street_2() end
--- Plays room music. --- Plays room music.
--- @within Audio --- @within Audio
function Audio.music_play_room_() end function Audio.music_play_room_() end
--- Plays room work music. --- Plays room work music.
--- @within Audio --- @within Audio
function Audio.music_play_room_work() music(0) end function Audio.music_play_room_work() Audio.music_play(0) end
--- Plays activity work music. --- Plays activity work music.
--- @within Audio --- @within Audio
function Audio.music_play_activity_work() music(1) end function Audio.music_play_activity_work() Audio.music_play(1) end
--- Plays select sound effect. --- Plays select sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_select() sfx(17, 'C-7', 30) end function Audio.sfx_select() sfx(17, 'C-7', 30) end
--- Plays deselect sound effect. --- Plays deselect sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_deselect() sfx(18, 'C-7', 30) end function Audio.sfx_deselect() sfx(18, 'C-7', 30) end
--- Plays beep sound effect. --- Plays beep sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_beep() sfx(19, 'C-6', 30) end function Audio.sfx_beep() sfx(19, 'C-6', 30) end
--- Plays success sound effect. --- Plays success sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_success() sfx(16, 'C-7', 60) end function Audio.sfx_success() sfx(16, 'C-7', 60) end
--- Plays bloop sound effect. --- Plays bloop sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_bloop() sfx(21, 'C-3', 60) end function Audio.sfx_bloop() sfx(21, 'C-3', 60) end
--- Plays alarm sound effect.
--- Plays alarm sound effect
--- @within Audio
function Audio.sfx_alarm() sfx(34, "C-5", 240) end
--- Plays drum sound effect.
--- @within Audio --- @within Audio
function Audio.sfx_alarm() sfx(61) end function Audio.sfx_drum_low() sfx(61, "C-2") end
--- Plays drum sound effect.
--- @within Audio
function Audio.sfx_drum_high() sfx(61, "C-6") end
--- Plays sound effect for arrow hit
--- @within Audio
--- @param note string The note for the sound to play
function Audio.sfx_arrowhit(note) sfx(56, note) end

View File

@@ -105,6 +105,15 @@ Songs = {
fps = 60, fps = 60,
end_frame = nil, -- No end frame for random mode end_frame = nil, -- No end frame for random mode
pattern = {} -- Empty, will spawn randomly in game pattern = {} -- Empty, will spawn randomly in game
},
generated = {
name = "Markov Mode",
bpm = 150,
spd = 6,
fps = 60,
end_frame = nil, -- calculated
pattern = {}, -- generated
generated = true
} }
} }

View File

@@ -4,12 +4,31 @@ Decision.register({
handle = function() handle = function()
Meter.hide() Meter.hide()
Util.go_to_screen_by_id("work") Util.go_to_screen_by_id("work")
MinigameDDRWindow.start("game", nil, {
on_win = function() local modes_for_ascension_levels = {}
modes_for_ascension_levels[0] = "normal"
modes_for_ascension_levels[1] = "only_special"
modes_for_ascension_levels[2] = "only_left"
modes_for_ascension_levels[3] = "only_nothing"
MinigameDDRWindow.start("game", "generated", {
on_win = function(game_context)
if (game_context.special_mode_condition and Context.ascension.level == 1) then
Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 2) then
Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 3) then
Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 4) then
Context.should_ascend = true
end
Meter.show() Meter.show()
Util.go_to_screen_by_id("office") Util.go_to_screen_by_id("office")
Window.set_current("game") Window.set_current("game")
Context.have_done_work_today = true
end, end,
special_mode = modes_for_ascension_levels[Ascension.get_level()]
}) })
end, end,
}) })

View File

@@ -1,6 +1,9 @@
Decision.register({ Decision.register({
id = "go_to_home", id = "go_to_home",
label = "Go to Home", label = "Go Home",
condition = function()
return Context.have_been_to_office and Context.have_done_work_today
end,
handle = function() handle = function()
Util.go_to_screen_by_id("home") Util.go_to_screen_by_id("home")
end, end,

View File

@@ -1,6 +1,9 @@
Decision.register({ Decision.register({
id = "go_to_sleep", id = "go_to_sleep",
label = "Go to Sleep", label = "Go to Sleep",
condition = function()
return Context.have_been_to_office and Context.have_done_work_today
end,
handle = function() handle = function()
Meter.hide() Meter.hide()
Day.increase() Day.increase()
@@ -10,7 +13,11 @@ Decision.register({
focus_initial_radius = 0, focus_initial_radius = 0,
on_win = function() on_win = function()
local ascended = Ascension.consume_increase() local ascended = Ascension.consume_increase()
MysteriousManWindow.start({ skip_text = not ascended }) local level = Ascension.get_level()
MysteriousManScreen.start({
skip_text = not ascended,
text = ascended and MysteriousManScreen.get_text_for_level(level) or nil,
})
end, end,
}) })
end, end,

View File

@@ -1,6 +1,6 @@
Decision.register({ Decision.register({
id = "go_to_walking_to_home", id = "go_to_walking_to_home",
label = "Walking to home", label = "Walk home",
handle = function() handle = function()
Util.go_to_screen_by_id("walking_to_home") Util.go_to_screen_by_id("walking_to_home")
end, end,

View File

@@ -1,6 +1,6 @@
Decision.register({ Decision.register({
id = "go_to_walking_to_office", id = "go_to_walking_to_office",
label = "Walking to office", label = "Walk to office",
handle = function() handle = function()
Util.go_to_screen_by_id("walking_to_office") Util.go_to_screen_by_id("walking_to_office")
end, end,

View File

@@ -1,8 +0,0 @@
Decision.register({
id = "have_a_coffee",
label = "Have a Coffee",
handle = function()
local new_situation_id = Situation.apply("drink_coffee", Context.game.current_screen)
Context.game.current_situation = new_situation_id
end,
})

View File

@@ -134,6 +134,7 @@ end
--- @param decisions table A table of decision items.<br/> --- @param decisions table A table of decision items.<br/>
--- @param selected_decision_index number The current index of the selected decision.<br/> --- @param selected_decision_index number The current index of the selected decision.<br/>
--- @return number selected_decision_index The updated index of the selected decision. --- @return number selected_decision_index The updated index of the selected decision.
--- @return boolean mouse_confirmed True if the user clicked the center to confirm.
function Decision.update(decisions, selected_decision_index) function Decision.update(decisions, selected_decision_index)
if Input.left() then if Input.left() then
Audio.sfx_beep() Audio.sfx_beep()
@@ -142,5 +143,22 @@ function Decision.update(decisions, selected_decision_index)
Audio.sfx_beep() Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index + 1) selected_decision_index = Util.safeindex(decisions, selected_decision_index + 1)
end end
return selected_decision_index
local bar_h = 16
local bar_y = Config.screen.height - bar_h
local prev_zone = { x = 0, y = bar_y, w = 15, h = bar_h }
local next_zone = { x = Config.screen.width-15, y = bar_y, w = 15, h = bar_h }
local confirm_zone = { x = 15, y = bar_y, w = Config.screen.width-30, h = bar_h }
if Mouse.zone(prev_zone) then
Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index - 1)
elseif Mouse.zone(next_zone) then
Audio.sfx_beep()
selected_decision_index = Util.safeindex(decisions, selected_decision_index + 1)
elseif Mouse.zone(confirm_zone) then
return selected_decision_index, true
end
return selected_decision_index, false
end end

View File

@@ -1,5 +1,8 @@
Decision.register({ Decision.register({
id = "play_ddr", id = "play_ddr",
label = "Play DDR (Random)", label = "Play DDR (Random)",
handle = function() Meter.hide() MinigameDDRWindow.start("game", nil) end, handle = function()
Meter.hide()
MinigameDDRWindow.start("game", nil)
end,
}) })

View File

@@ -1,5 +1,5 @@
Decision.register({ Decision.register({
id = "start_discussion", id = "sumphore_discussion",
label = function() label = function()
if Context.have_met_sumphore then if Context.have_met_sumphore then
return "Talk to Sumphore" return "Talk to Sumphore"
@@ -9,10 +9,15 @@ Decision.register({
handle = function() handle = function()
if not Context.have_met_sumphore then if not Context.have_met_sumphore then
Discussion.start("homeless_guy", "game") Discussion.start("homeless_guy", "game")
elseif Ascension.get_level() == 0 then return
Discussion.start("homeless_guy", "game", 4) end
local level = Ascension.get_level()
-- TODO: Add more discussions for levels above 3
if level >= 1 and level <= 3 then
Discussion.start("sumphore_disc_asc_" .. level, "game")
else else
Discussion.start("sumphore_day_3", "game") Discussion.start("homeless_guy", "game", 4)
end end
end, end,
}) })

View File

@@ -0,0 +1,137 @@
Discussion.register({
id = "coworker_disc_0",
steps = {
{
question = "Good morning Normal, enjoying your coffee as usual, huh?",
answers = {
{ label = "The name is Norman, not Normal", next_step = 2 },
},
},
{
question = "Can't work without some good coffee, no? ",
answers = {
{ label = "Mhmm", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_1",
steps = {
{
question = "Norman, you look confused, what's up?",
answers = {
{ label = "Just some bugs I noticed", next_step = 2 },
},
},
{
question = "Your coffee also seems whiter than usual!",
answers = {
{ label = "I feel like latte today", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_asc_1",
steps = {
{
question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?",
answers = {
{ label = "Nothing it's just, I noticed some bugs in the simulation, maybe.", next_step = 2 },
},
},
{
question = "Are you fixing bugs nobody noticed before?",
answers = {
{ label = "Maybe", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_2",
steps = {
{
question = "Hey Norman, do you have new socks on? That's a weird color!",
answers = {
{ label = "Huh? True ...", next_step = 2 },
},
},
{
question = "You look strange today, Normal, you put your tie on backwards, is everything ok? ",
answers = {
{ label = "Get it right, Norman ... NORMAN!", next_step = 3 },
},
},
{
question = "Yo Normann, text goes from left to right, not right to left, these parts!",
answers = {
{ label = "Uhm...why?", next_step = nil },
},
},
},
});
Discussion.register({
id = "coworker_disc_asc_2",
steps = {
{
question = "Normann, are you ok? You were doing weird things while typing?",
answers = {
{ label = "Naw", next_step = 2 },
},
},
{
question = "Oh, it's ok, I'm not wathcing you. Noone really is. *giggle*",
answers = {
{ label = "Huh ?", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_3",
steps = {
{
question = "You look so happy, did you catch a bull or something?",
answers = {
{ label = "What do you mean?", next_step = 2 },
},
},
{
question = "Most people catch colds! You are so strange!",
answers = {
{ label = "An apple a day keeps the cold away", next_step = 3 },
},
},
{
question = "You look like you don't really want to work today, are you ok?",
answers = {
{ label = "Oh brother", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_3",
steps = {
{
question = "Normal, you should take a break, you don't live up to your name today",
answers = {
{ label = "Norman is the name ...", next_step = 2 },
},
},
{
question = "You aren't as enthusiastic as you were before!",
answers = {
{ label = "Burnout comes for everyone", next_step = nil },
},
},
},
})

View File

@@ -1,5 +1,5 @@
Discussion.register({ Discussion.register({
id = "sumphore_day_3", id = "sumphore_disc_asc_1",
steps = { steps = {
{ {
question = "Are you still seeking the ox?", question = "Are you still seeking the ox?",
@@ -17,6 +17,74 @@ Discussion.register({
}, },
}) })
Discussion.register({
id = "sumphore_disc_asc_2",
steps = {
{
question = "How's work? Your face looks strange",
answers = {
{ label = "I just really need to take a break.", next_step = 2 },
{ label = "Not sure what you mean.", next_step = nil },
},
},
{
question = "Are you seeing things?",
answers = {
{ label = "How did you know ?", next_step = 3 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "Come have a drink, I could tell you some stories.",
answers = {
{ label = "No, drink makes you stupid and I need to be in top shape.", next_step = 4, on_select = function()
Meter.add("ism", 10)
end },
{ label = "I could use a drink.", next_step = nil, on_select = function()
Meter.add("bm", 10)
end },
},
},
{
question = "Always trying to do the right thing, huh? What if you did the left thing instead?",
answers = {
{ label = "I've never thought of that up till now.", next_step = nil, on_select = function()
Meter.add("ism", 5)
end },
{ label = "Silly wordgames, I like them.", next_step = nil, on_select = function()
Meter.add("wpm", 10)
end },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_3",
steps = {
{
question = "Do you think it's work you're doing?",
answers = {
{ label = "... that sounds like it's from a movie.", next_step = 2 },
{ label = "Are you drunk, old man?", next_step = nil },
},
},
{
question = "You might just be trying too hard, why dont you just flow for a while?",
answers = {
{ label = "Flow where ?", next_step = 3 },
{ label = "I'm not sure what you mean.", next_step = nil },
},
},
{
question = "Flow carelessly, without any effort",
answers = {
{ label = "Consuming sth other than alcohol ?", next_step = nil },
{ label = "Deja vu", next_step = nil },
},
},
},
})
Discussion.register({ Discussion.register({
id = "homeless_guy", id = "homeless_guy",

View File

@@ -21,7 +21,9 @@ Context = {}
--- * triggers (table) Active trigger runtime state, keyed by trigger ID.<br/> --- * triggers (table) Active trigger runtime state, keyed by trigger ID.<br/>
--- * stat_screen_active (boolean) Whether the stat screen overlay is currently shown.<br/> --- * stat_screen_active (boolean) Whether the stat screen overlay is currently shown.<br/>
--- * have_met_sumphore (boolean) Whether the player has talked to the homeless guy.<br/> --- * have_met_sumphore (boolean) Whether the player has talked to the homeless guy.<br/>
--- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID, `current_situation` (string|nil) active situation ID.<br/> --- * have_been_to_office (boolean) Whether the player has been to the office.<br/>
--- * have_done_work_today (boolean) Whether the player has done work today.<br/>
--- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID<br/>
function Context.initial_data() function Context.initial_data()
return { return {
current_menu_item = 1, current_menu_item = 1,
@@ -40,12 +42,16 @@ function Context.initial_data()
timer = Timer.get_initial(), timer = Timer.get_initial(),
triggers = {}, triggers = {},
home_norman_visible = false, home_norman_visible = false,
have_been_to_office = false,
have_done_work_today = false,
should_ascend = false,
have_met_sumphore = false, have_met_sumphore = false,
game = { game = {
current_screen = "home", current_screen = "home",
current_situation = nil,
}, },
day_count = 1, day_count = 1,
delta_time = 0,
last_frame_time = 0,
glitch = { glitch = {
enabled = false, enabled = false,
state = "active", state = "active",
@@ -90,13 +96,19 @@ function Context.new_game()
Context.game_in_progress = true Context.game_in_progress = true
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init() Screen.get_by_id(Context.game.current_screen).init()
MysteriousManWindow.start({ MysteriousManScreen.start({
text = [[ text = [[
Norman was never a bad Norman was never a bad
simulation engineer, but ...
we need to be careful in simulation engineer,
letting him improve. We ...
need to distract him. but
...
we need to be careful
...
letting him improve.
...
We need to distract him.
]], ]],
on_text_complete = function() on_text_complete = function()
Audio.sfx_alarm() Audio.sfx_alarm()
@@ -111,7 +123,6 @@ function Context.new_game()
show_progress_text = false, show_progress_text = false,
on_win = function() on_win = function()
Audio.music_play_wakingup() Audio.music_play_wakingup()
Context.home_norman_visible = true
Meter.show() Meter.show()
Window.set_current("game") Window.set_current("game")
end, end,

View File

@@ -3,12 +3,12 @@ Util = {}
Meter = {} Meter = {}
Minigame = {} Minigame = {}
Decision = {} Decision = {}
Situation = {}
Screen = {} Screen = {}
Map = {} Map = {}
UI = {} UI = {}
Print = {} Print = {}
Input = {} Input = {}
Mouse = {}
Sprite = {} Sprite = {}
Audio = {} Audio = {}
Focus = {} Focus = {}
@@ -16,5 +16,7 @@ Day = {}
Timer = {} Timer = {}
Trigger = {} Trigger = {}
Discussion = {} Discussion = {}
RLE = {}
AsciiArt = {} AsciiArt = {}
Ascension = {} Ascension = {}
MysteriousManScreen = {}

View File

@@ -5,6 +5,9 @@ local _day_increase_handlers = {}
--- @within Day --- @within Day
function Day.increase() function Day.increase()
Context.day_count = Context.day_count + 1 Context.day_count = Context.day_count + 1
if Context.day_count == 3 then
Context.should_ascend = true
end
for _, handler in ipairs(_day_increase_handlers) do for _, handler in ipairs(_day_increase_handlers) do
handler() handler()
end end
@@ -25,7 +28,8 @@ Day.register_handler(function()
end) end)
Day.register_handler(function() Day.register_handler(function()
if Context.day_count == 3 then if Context.should_ascend then
Ascension.increase() Ascension.increase()
end end
Context.should_ascend = false
end) end)

View File

@@ -3,8 +3,8 @@
--- Draws a unified win message overlay. --- Draws a unified win message overlay.
--- @within Minigame --- @within Minigame
function Minigame.draw_win_overlay() function Minigame.draw_win_overlay(win_text)
local text = "SUCCESS" local text = win_text or "SUCCESS"
local tw = #text * 6 local tw = #text * 6
local th = 6 local th = 6
local padding = 4 local padding = 4

View File

@@ -396,26 +396,27 @@
-- 016:ffffffffff0010201020766777001020102010201020102000fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 016:ffffffffff0010201020766777001020102010201020102000fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </MAP> -- </MAP>
-- <SFX> -- <SFX>
-- 000:060006400600064006000640060006400600060006000600060006000600060006000600060006000600060006000600060006000600060006000600300000000900
-- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000 -- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000
-- 017:000000000000000000000000006000600060006000600060106020c030c050c060c080c0a0c0b0c0c0c0c0c0d0c0d0c0e0c0f0c0f0c0f0c0f0c0f0c0400000000000 -- 017:030003000300030003000300036003600360036003600360136023c033c053c063c083c0a3c0b3c0c3c0c3c0d3c0d3c0e3c0f3c0f3c0f3c0f3c0f3c0400000000000
-- 018:00c000c000c000c000c000c0006000600060006000600060200030005000600080009000a000b000c000d000d000e000e000e000f000f000f000f000500000000000 -- 018:03c003c003c003c003c003c0036003600360036003600360230033005300630083009300a300b300c300d300d300e300e300e300f300f300f300f300400000000000
-- 019:0000000000000000000000d010d010d020d030d050d070d090d0b0d0c0d0e0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0f0d0500000000000 -- 019:0300030003000300030003d013d013d023d033d053d073d093d0b3d0c3d0e3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0f3d0400000000000
-- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000 -- 020:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900500000000000
-- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130580000000000 -- 021:01000100010001000100f10001100110011001100110f11001200120012001200120f1201130113011302130213021302130313041308130a130d130580000000000
-- 022:03b003100300030003000300130063009300b300c300d300d300e300e300e300f300f300f300f300f300f300f300f300f300f300f300f300f300f300400000000000
-- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100400000000800 -- 032:010001100100011001000110010001100100010001000100010001000100010001000100010001000100010001000100010001000100010001000100400000000800
-- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004 -- 033:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c40000000004
-- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006004600000f0f00 -- 034:02000240020002000200020002000200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f200f2004700000f0200
-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000004600000f0f00 -- 044:0600f6000620f6000600f6000610f600f600f6000600f600f600f600f6000600060006000600060006000600060006000600060006000600060006001600000f0f00
-- 045:0000f0000020f0000000f0000010f000f000f0000000f000f000f000f0000000000000000000000000000000000000000000000000000000000000005600000f0f00
-- 048:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900400000000000 -- 048:090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900090009000900400000000000
-- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f10058a000000600 -- 056:4100510061406140717081709100b100c100d100e100e100e100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100f100480000000600
-- 057:000000010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d40000000004 -- 057:000000010002000300020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000840000000004
-- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100500000080800 -- 058:41004110410041104100411041004110c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100c100003000080800
-- 059:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000 -- 059:03000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030003000300030000b000000000
-- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200100000000000 -- 060:220022002200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200820082008200500000000000
-- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00400000000000 -- 061:9f009f00bf00df00df00ef00ef00ef00ef00ef00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00100000000000
-- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100484000000000 -- 062:00000100010001000100510081008100910091009100a100a100a100a100a100b100b100b100b100c100c100c100d100d100d100e100e100e100f100580000000000
-- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000200000000000 -- 063:00b000100000000000000000100060009000b000c000d000d000e000e000e000f000f000f000f000f000f000f000f000f000f000f000f000f000f000500000000000
-- </SFX> -- </SFX>
-- <WAVES> -- <WAVES>
-- 000:bcceefceedddddc84333121268abaa99 -- 000:bcceefceedddddc84333121268abaa99
@@ -433,10 +434,13 @@
-- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000 -- 000:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000008c10000000008c10000000000000000000000000000000000000000000000000000000000000000004008b50000000000000000001008c10000004008b50000001008c10000000008c1000000e008b30000004008b50000001008c10000000008c10000000008c10000000008c10000000008c10000000008c1000000000000000000000000000000
-- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000 -- 001:4008b50000000000000000001008c10000004008b50000001008c1000000000000000000e008b30000004008b50000001008c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007008b50000007008b50000001008c10000007008b50000001008c10000000008c10000007008b50000009008b50000001008c10000009008b50000001008c10000009008b50000009008b50000001008c10000009008b50000001008c1000000
-- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000 -- 003:4008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000000000000000004008d90000000000000000000000000000000000000000004008d30000000000000000000000000000004008d30000004008d9000000000000000000000000000000000000000000
-- 004:49998d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000 -- 004:43398d000000e0088b000000b0088b000881e0088b00000040088d000000e0088b000881b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088d000000e0088b000000b0088b000000e0088b00000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e0088900000040088b000000e00889000000b00889000000e00889000000
-- 005:400881000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000 -- 005:455981000000000881000000000881000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000400881000000000000000000000000000000000000000000400883000000000000000000000000000000000000000000
-- 008:4aa9b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005008b30000000000000000000000000000000000000000000008b10000000000000000000008910000000000000000004008b30000000000000000000000000000000000000000000000000000000000000000000008b1000000000000000000f008b1000000000000000000000000000000000000000000000891000000000000000000000000000000000000000000
-- 009:4779d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d30000000000000000004008d30000000000000000004008d30000000000000000004008db0000000000000000004008d3000000000000000000
-- </PATTERNS> -- </PATTERNS>
-- <TRACKS> -- <TRACKS>
-- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff -- 000:1000012000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff
-- 001:581000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 001:581000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:900082000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </TRACKS> -- </TRACKS>

View File

@@ -4,5 +4,5 @@
-- desc: Life of a programmer -- desc: Life of a programmer
-- site: https://git.teletype.hu/games/impostor -- site: https://git.teletype.hu/games/impostor
-- license: MIT License -- license: MIT License
-- version: 0.8 -- version: 1.0-beta2
-- script: lua -- script: lua

View File

@@ -7,6 +7,9 @@ Screen.register({
"go_to_sleep", "go_to_sleep",
"go_to_end", "go_to_end",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "bedroom", background = "bedroom",
draw = function() draw = function()
if Context.home_norman_visible and Window.get_current_id() == "game" then if Context.home_norman_visible and Window.get_current_id() == "game" then

View File

@@ -8,7 +8,6 @@ local _screens = {}
--- @param screen_data.name string Display name of the screen. --- @param screen_data.name string Display name of the screen.
--- @param screen_data.decisions table Array of decision ID strings available on this screen. --- @param screen_data.decisions table Array of decision ID strings available on this screen.
--- @param screen_data.background string Map ID used as background. --- @param screen_data.background string Map ID used as background.
--- @param[opt] screen_data.situations table Array of situation ID strings. Defaults to {}.
--- @param[opt] screen_data.init function Called when the screen is entered. Defaults to noop. --- @param[opt] screen_data.init function Called when the screen is entered. Defaults to noop.
--- @param[opt] screen_data.update function Called each frame while screen is active. Defaults to noop. --- @param[opt] screen_data.update function Called each frame while screen is active. Defaults to noop.
--- @param[opt] screen_data.draw function Called after the focus overlay to draw screen-specific overlays. Defaults to noop. --- @param[opt] screen_data.draw function Called after the focus overlay to draw screen-specific overlays. Defaults to noop.
@@ -16,9 +15,6 @@ function Screen.register(screen_data)
if _screens[screen_data.id] then if _screens[screen_data.id] then
trace("Warning: Overwriting screen with id: " .. screen_data.id) trace("Warning: Overwriting screen with id: " .. screen_data.id)
end end
if not screen_data.situations then
screen_data.situations = {}
end
if not screen_data.init then if not screen_data.init then
screen_data.init = function() end screen_data.init = function() end
end end
@@ -43,7 +39,6 @@ end
--- * name (string) Display name.<br/> --- * name (string) Display name.<br/>
--- * decisions (table) Array of decision ID strings.<br/> --- * decisions (table) Array of decision ID strings.<br/>
--- * background (string) Map ID used as background.<br/> --- * background (string) Map ID used as background.<br/>
--- * situations (table) Array of situation ID strings.<br/>
--- * init (function) Called when the screen is entered.<br/> --- * init (function) Called when the screen is entered.<br/>
--- * update (function) Called each frame while screen is active. --- * update (function) Called each frame while screen is active.
function Screen.get_by_id(screen_id) function Screen.get_by_id(screen_id)
@@ -58,7 +53,6 @@ end
--- * name (string) Display name of the screen.<br/> --- * name (string) Display name of the screen.<br/>
--- * decisions (table) Array of decision ID strings available on this screen.<br/> --- * decisions (table) Array of decision ID strings available on this screen.<br/>
--- * background (string) Map ID used as background.<br/> --- * background (string) Map ID used as background.<br/>
--- * situations (table) Array of situation ID strings.<br/>
--- * init (function) Called when the screen is entered.<br/> --- * init (function) Called when the screen is entered.<br/>
--- * update (function) Called each frame while screen is active.<br/> --- * update (function) Called each frame while screen is active.<br/>
function Screen.get_all() function Screen.get_all()

View File

@@ -1,20 +1,285 @@
--- Draws the mysterious screen with the mysterious man and the glowing TV. --- @section MysteriousManScreen
function Screen.draw_the_mysterious_screen()
local img_values = {0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,2,1,4,2,1,4,1,0,2,4,1,0,1,4,2,1,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,1,2,1,0,1,4,1,0,2,4,2,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,4,1,0,2,4,2,0,1,2,4,1,0,2,4,1,0,1,0,4,1,0,2,4,0,1,0,1,0,1,0,4,1,0,2,4,0,1,4,1,0,2,4,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,2,1,0,1,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,1,2,0,1,0,1,0,1,2,1,2,1,0,2,4,1,0,2,4,0,1,0,1,2,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,4,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,0,1,4,1,0,2,4,1,0,1,0,1,2,0,1,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,4,1,0,2,4,2,4,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,4,1,0,2,4,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,2,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,1,0,4,1,0,2,4,0,1,0,1,0,2,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,2,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,2,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,0,1,0,1,4,1,0,2,4,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,4,2,1,0,1,2,1,2,1,0,1,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,1,2,0,1,0,2,1,0,1,2,0,1,0,2,0,1,0,1,0,1,2,0,1,0,1,0,1,0,2,0,1,0,1,0,1,0,2,0,1,0,1,0,2,1,0,1,0,1,2,0,1,0,2,0,2,1,0,1,2,0,2,1,0,1,0,1,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,2,1,0,1,0,1,0,1,0,1,0,2,1,0,1,2,0,1,0,1,2,1,0,1,0,1,0,1,0,2,1,0,2,0,1,0,1,0,2,0,1,0,1,0,2,0,1,0,2,0,1,0,1,0,2,1,0,1,2,0,1,0,1,0,1,0,1,0,2,0,1,0,2,0,1,0,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0} local STATE_TEXT = "text"
local STATE_DAY = "day"
local img_runs = {1480,1,151,1,87,1,1,150,1,1,86,1,2,148,1,2,87,2,148,2,88,2,148,2,88,2,148,2,88,2,148,2,88,2,70,1,2,9,1,2,63,2,88,2,69,1,2,3,5,1,1,1,1,1,1,1,61,2,88,2,68,1,1,3,1,2,1,3,2,3,2,61,2,88,2,68,1,6,1,4,1,5,1,1,60,2,88,2,67,1,1,17,2,60,2,88,2,67,1,19,1,1,59,2,88,2,67,1,19,1,1,59,2,88,2,67,4,1,11,1,1,1,3,59,2,88,2,67,21,1,59,2,88,2,67,21,1,59,2,88,2,66,1,21,2,58,2,88,2,66,1,21,1,1,58,2,88,2,66,4,17,2,1,58,2,88,2,65,2,5,1,15,2,58,2,88,2,63,1,1,2,22,4,55,2,88,2,57,2,2,34,2,2,49,2,88,2,55,1,2,39,3,1,47,2,88,2,55,5,1,3,2,2,1,25,1,1,1,1,1,1,1,47,2,88,2,57,1,2,1,29,1,4,1,1,1,1,49,2,88,2,59,1,2,8,15,9,2,52,2,88,2,62,2,1,1,1,24,1,1,1,54,2,88,2,66,1,22,1,58,2,88,2,66,1,21,1,59,2,88,2,66,2,20,1,59,2,88,2,65,3,20,2,58,2,88,2,63,2,2,1,20,1,1,2,56,2,88,2,61,2,4,1,20,1,2,2,55,2,88,2,59,1,1,6,1,19,1,4,2,54,2,88,2,58,2,7,2,17,1,1,6,2,52,3,87,2,56,1,2,9,1,26,2,51,3,87,2,55,1,1,40,1,50,2,88,2,54,2,41,1,50,2,88,2,53,2,42,2,49,2,88,2,52,2,44,2,48,2,88,2,51,2,46,2,47,2,88,2,50,2,16,1,31,1,47,2,88,2,50,3,1,14,1,29,1,1,2,46,2,88,2,50,18,1,29,1,2,1,46,2,88,2,50,4,14,1,33,1,45,2,88,2,53,1,3,11,2,15,1,13,1,2,46,2,88,2,56,1,1,11,1,15,1,10,2,1,49,2,88,2,55,2,3,10,2,12,2,8,8,46,2,88,2,50,1,1,4,4,1,9,2,11,2,8,2,4,2,1,2,1,1,42,2,88,2,48,2,3,8,1,9,2,9,3,7,2,8,1,1,2,1,1,40,2,88,2,45,3,2,1,1,1,1,8,1,7,1,1,3,6,4,6,1,8,1,1,1,1,4,2,38,2,88,2,45,1,2,2,22,4,4,4,6,1,10,1,1,2,1,2,1,2,37,2,88,2,42,1,1,1,4,1,2,19,1,1,4,1,5,21,1,2,6,35,2,88,2,42,2,1,1,1,1,15,2,8,3,4,3,6,1,17,1,1,2,1,1,1,34,2,88,2,41,3,1,1,15,2,9,3,5,2,8,1,21,1,1,34,2,88,2,41,1,1,1,16,2,11,2,5,2,9,2,18,2,1,34,2,88,2,41,1,2,1,14,2,13,1,5,1,12,1,17,1,2,34,2,88,2,41,1,16,1,17,1,2,1,14,2,17,1,1,33,2,88,2,41,1,2,1,12,1,17,1,3,2,15,1,14,1,1,1,1,33,2,88,2,41,15,1,22,1,17,1,1,11,1,3,1,33,2,88,2,41,1,14,2,40,1,15,1,33,2,88,2,41,1,15,2,35,1,3,1,10,1,4,1,33,2,88,2,41,1,3,1,12,2,36,2,11,1,4,1,33,2,88,2,40,1,1,18,1,35,1,12,1,4,1,1,32,2,88,2,40,2,4,1,14,1,33,1,12,1,6,1,32,2,88,2,40,1,6,1,14,1,31,1,13,1,6,1,32,2,88,2,39,2,22,2,28,1,21,1,1,31,2,88,2,39,2,24,1,26,1,22,1,1,31,2,88,2,39,2,25,1,24,1,23,1,1,31,2,88,2,39,1,27,1,22,2,23,1,1,31,2,88,2,39,1,49,2,24,3,30,2,88,2,39,1,48,1,27,1,1,30,2,88,2,39,1,47,2,27,1,1,30,2,88,2,37,1,1,47,2,28,1,1,30,2,88,2,37,1,1,46,1,30,2,1,29,2,88,2,37,1,47,1,30,2,30,2,88,2,37,1,1,2,43,1,33,30,3,87,2,37,1,45,1,31,1,1,2,29,3,87,2,37,45,1,35,1,29,1,1,88,2,1,35,1,1,80,2,1,26,1,3,86,1,1,2,1,21,1,4,4,2,1,3,80,30,1,2,125,2,80,2,156,2,79,2,1,156,2,1,77,4,156,1,3,76,3,1,140,1,15,1,2,2,74,2,1,1,1,5,3,129,1,18,1,82,1,7,1,129,1,18,1,82,1,9,1,123,3,20,1,1,80,2,11,1,1,119,1,22,1,1,1,2,76,4,1,154,2,2,63,1,14,3,1,126,1,27,2,65,1,17,1,17,2,132,4,66,1,17,4,113,39,66,2,16,31,191,1,1,1,15,1,153,2,15,3,48,2,16,2,152,1,16,1,1,1,48,2,16,2,152,1,1,2,2,1,10,1,1,1,48,1,13,6,153,1,3,13,1,1,48,1,15,2,1,1,153,3,64,1,16,2,154,1,1,83,1,154,1,17,1,66,1,154,1,17,1,66,1,155,1,82,1,156,1,82,1,156,1,1,80,1,1,157,2,78,2,158,2,78,2,158,1,80,1,158,1,79,2,158,1,79,1,159,1,79,1,160,1,77,2,160,1,77,1,162,1,74,1,1,1,162,1,75,1,239,1,165,1,72,1,166,1,240,1,69,1,412,1,63,1,175,1,63,2,238,1,175,1,63,1,239,1,807} local STATE_CHOICE = "choice"
local val_i=0 local ASC_01_TEXT = [[
local run=0 Normann seems to be in line,
for y=0,136-1 do ...
for x=0,240-1 do and stays seeking for oxes
if run==0 then ...
val_i=val_i+1 within the confines.
run=img_runs[val_i] ...
end Very good.
run=run-1 ]]
pix(x,y,img_values[val_i])
end local ASC_12_TEXT = [[
end We have a problem!
end ...
Normann formed his first thought.
...
He saw the tracks.
]]
local ASC_23_TEXT = [[
Not good, not terrible.
...
Normann caught his glimpse
...
of another way
...
- quite literally -
...
if this continues,
...
we will lose control.
]]
local ASC_34_TEXT = [[
There is no turning back now for Norman.
...
He caught on.
...
I hoped it would never come to this...
]]
--[[ Norman speaks for the first time during MM screen ]]
local ASC_45_TEXT = [[
Wait, who are you?
...
*silence*
...
Why am I seeing this?
...
*silence*
...
]]
local ascension_texts = {
[1] = ASC_01_TEXT,
[2] = ASC_12_TEXT,
[3] = ASC_23_TEXT,
[4] = ASC_34_TEXT,
[5] = ASC_45_TEXT,
}
function MysteriousManScreen.get_text_for_level(level)
return ascension_texts[level] or ASC_01_TEXT
end
local state = STATE_TEXT
local text_y = Config.screen.height
local text_speed = 12 -- pixels per second
local day_timer = 0
local day_display_seconds = 2
local text_done = false
local text_done_timer = 0
local TEXT_DONE_HOLD_SECONDS = 2
local selected_choice = 1
local text = ASC_01_TEXT
local day_text_override = nil
local on_text_complete = nil
local show_mysterious_screen = true
local trigger_flash_on_wake = false
MysteriousManScreen.choices = {
{
label = "Wake Up",
},
{
label = "Stay in Bed",
},
}
-- Draws the background image
-- @within MysteriousManScreen
function MysteriousManScreen.draw_background()
local img_values = {0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,1,0,2,4,2,1,4,2,1,4,1,0,2,4,1,0,1,4,2,1,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,1,2,1,0,1,4,1,0,2,4,2,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,4,1,0,2,4,2,0,1,2,4,1,0,2,4,1,0,1,0,4,1,0,2,4,0,1,0,1,0,1,0,4,1,0,2,4,0,1,4,1,0,2,4,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,2,1,0,1,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,2,1,0,1,2,4,1,0,2,4,1,2,0,1,0,1,0,1,2,1,2,1,0,2,4,1,0,2,4,0,1,0,1,2,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,4,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,2,1,0,1,0,2,0,1,4,1,0,2,4,1,0,1,0,1,2,0,1,4,1,0,2,4,2,1,0,1,0,1,4,1,0,2,4,2,1,0,2,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,0,1,4,1,0,2,4,2,4,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,0,1,0,1,0,1,0,1,0,1,2,1,4,2,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,4,1,0,2,4,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,2,1,4,1,0,2,4,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,2,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,2,0,1,0,1,0,4,1,0,2,4,0,1,0,1,0,2,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,0,2,0,1,4,1,0,2,4,0,1,0,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,2,0,1,0,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,0,4,1,0,2,4,1,0,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,1,0,1,0,1,2,4,1,0,2,4,2,0,1,0,1,4,1,0,2,4,2,0,1,0,1,0,4,1,0,2,4,1,0,1,0,1,0,1,4,1,0,2,4,0,1,0,1,4,2,1,0,1,2,1,2,1,0,1,2,1,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,1,2,0,1,0,2,1,0,1,2,0,1,0,2,0,1,0,1,0,1,2,0,1,0,1,0,1,0,2,0,1,0,1,0,1,0,2,0,1,0,1,0,2,1,0,1,0,1,2,0,1,0,2,0,2,1,0,1,2,0,2,1,0,1,0,1,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,0,2,1,0,1,0,1,0,1,0,1,0,2,1,0,1,2,0,1,0,1,2,1,0,1,0,1,0,1,0,2,1,0,2,0,1,0,1,0,2,0,1,0,1,0,2,0,1,0,2,0,1,0,1,0,2,1,0,1,2,0,1,0,1,0,1,0,1,0,2,0,1,0,2,0,1,0,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0}
local img_runs = {1480,1,151,1,87,1,1,150,1,1,86,1,2,148,1,2,87,2,148,2,88,2,148,2,88,2,148,2,88,2,148,2,88,2,70,1,2,9,1,2,63,2,88,2,69,1,2,3,5,1,1,1,1,1,1,1,61,2,88,2,68,1,1,3,1,2,1,3,2,3,2,61,2,88,2,68,1,6,1,4,1,5,1,1,60,2,88,2,67,1,1,17,2,60,2,88,2,67,1,19,1,1,59,2,88,2,67,1,19,1,1,59,2,88,2,67,4,1,11,1,1,1,3,59,2,88,2,67,21,1,59,2,88,2,67,21,1,59,2,88,2,66,1,21,2,58,2,88,2,66,1,21,1,1,58,2,88,2,66,4,17,2,1,58,2,88,2,65,2,5,1,15,2,58,2,88,2,63,1,1,2,22,4,55,2,88,2,57,2,2,34,2,2,49,2,88,2,55,1,2,39,3,1,47,2,88,2,55,5,1,3,2,2,1,25,1,1,1,1,1,1,1,47,2,88,2,57,1,2,1,29,1,4,1,1,1,1,49,2,88,2,59,1,2,8,15,9,2,52,2,88,2,62,2,1,1,1,24,1,1,1,54,2,88,2,66,1,22,1,58,2,88,2,66,1,21,1,59,2,88,2,66,2,20,1,59,2,88,2,65,3,20,2,58,2,88,2,63,2,2,1,20,1,1,2,56,2,88,2,61,2,4,1,20,1,2,2,55,2,88,2,59,1,1,6,1,19,1,4,2,54,2,88,2,58,2,7,2,17,1,1,6,2,52,3,87,2,56,1,2,9,1,26,2,51,3,87,2,55,1,1,40,1,50,2,88,2,54,2,41,1,50,2,88,2,53,2,42,2,49,2,88,2,52,2,44,2,48,2,88,2,51,2,46,2,47,2,88,2,50,2,16,1,31,1,47,2,88,2,50,3,1,14,1,29,1,1,2,46,2,88,2,50,18,1,29,1,2,1,46,2,88,2,50,4,14,1,33,1,45,2,88,2,53,1,3,11,2,15,1,13,1,2,46,2,88,2,56,1,1,11,1,15,1,10,2,1,49,2,88,2,55,2,3,10,2,12,2,8,8,46,2,88,2,50,1,1,4,4,1,9,2,11,2,8,2,4,2,1,2,1,1,42,2,88,2,48,2,3,8,1,9,2,9,3,7,2,8,1,1,2,1,1,40,2,88,2,45,3,2,1,1,1,1,8,1,7,1,1,3,6,4,6,1,8,1,1,1,1,4,2,38,2,88,2,45,1,2,2,22,4,4,4,6,1,10,1,1,2,1,2,1,2,37,2,88,2,42,1,1,1,4,1,2,19,1,1,4,1,5,21,1,2,6,35,2,88,2,42,2,1,1,1,1,15,2,8,3,4,3,6,1,17,1,1,2,1,1,1,34,2,88,2,41,3,1,1,15,2,9,3,5,2,8,1,21,1,1,34,2,88,2,41,1,1,1,16,2,11,2,5,2,9,2,18,2,1,34,2,88,2,41,1,2,1,14,2,13,1,5,1,12,1,17,1,2,34,2,88,2,41,1,16,1,17,1,2,1,14,2,17,1,1,33,2,88,2,41,1,2,1,12,1,17,1,3,2,15,1,14,1,1,1,1,33,2,88,2,41,15,1,22,1,17,1,1,11,1,3,1,33,2,88,2,41,1,14,2,40,1,15,1,33,2,88,2,41,1,15,2,35,1,3,1,10,1,4,1,33,2,88,2,41,1,3,1,12,2,36,2,11,1,4,1,33,2,88,2,40,1,1,18,1,35,1,12,1,4,1,1,32,2,88,2,40,2,4,1,14,1,33,1,12,1,6,1,32,2,88,2,40,1,6,1,14,1,31,1,13,1,6,1,32,2,88,2,39,2,22,2,28,1,21,1,1,31,2,88,2,39,2,24,1,26,1,22,1,1,31,2,88,2,39,2,25,1,24,1,23,1,1,31,2,88,2,39,1,27,1,22,2,23,1,1,31,2,88,2,39,1,49,2,24,3,30,2,88,2,39,1,48,1,27,1,1,30,2,88,2,39,1,47,2,27,1,1,30,2,88,2,37,1,1,47,2,28,1,1,30,2,88,2,37,1,1,46,1,30,2,1,29,2,88,2,37,1,47,1,30,2,30,2,88,2,37,1,1,2,43,1,33,30,3,87,2,37,1,45,1,31,1,1,2,29,3,87,2,37,45,1,35,1,29,1,1,88,2,1,35,1,1,80,2,1,26,1,3,86,1,1,2,1,21,1,4,4,2,1,3,80,30,1,2,125,2,80,2,156,2,79,2,1,156,2,1,77,4,156,1,3,76,3,1,140,1,15,1,2,2,74,2,1,1,1,5,3,129,1,18,1,82,1,7,1,129,1,18,1,82,1,9,1,123,3,20,1,1,80,2,11,1,1,119,1,22,1,1,1,2,76,4,1,154,2,2,63,1,14,3,1,126,1,27,2,65,1,17,1,17,2,132,4,66,1,17,4,113,39,66,2,16,31,191,1,1,1,15,1,153,2,15,3,48,2,16,2,152,1,16,1,1,1,48,2,16,2,152,1,1,2,2,1,10,1,1,1,48,1,13,6,153,1,3,13,1,1,48,1,15,2,1,1,153,3,64,1,16,2,154,1,1,83,1,154,1,17,1,66,1,154,1,17,1,66,1,155,1,82,1,156,1,82,1,156,1,1,80,1,1,157,2,78,2,158,2,78,2,158,1,80,1,158,1,79,2,158,1,79,1,159,1,79,1,160,1,77,2,160,1,77,1,162,1,74,1,1,1,162,1,75,1,239,1,165,1,72,1,166,1,240,1,69,1,412,1,63,1,175,1,63,2,238,1,175,1,63,1,239,1,807}
RLE.draw(img_values, img_runs)
end
function MysteriousManScreen.draw_day_switch_background()
local img_values = {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1}
local img_runs = {86,2,238,2,225,1,10,5,224,1,8,8,221,4,5,2,1,8,219,6,2,13,217,1,2,19,1,1,213,2,4,19,2,1,211,2,5,19,3,1,208,1,7,20,4,1,204,2,7,2,1,19,5,1,201,2,7,2,3,19,6,1,198,2,7,2,5,9,2,1,2,5,7,1,195,2,7,2,7,5,7,7,8,1,192,2,7,2,9,5,8,6,9,1,188,3,7,2,11,5,9,5,10,1,186,2,7,2,13,5,9,5,11,1,182,2,7,3,15,5,9,5,12,1,179,2,8,2,17,5,9,5,13,1,176,2,7,3,19,5,9,5,14,1,173,2,7,3,21,5,9,5,15,1,169,3,7,2,24,5,9,5,16,1,166,2,8,2,26,5,9,5,3,1,177,2,8,3,27,5,9,5,4,1,13,1,159,3,7,3,30,5,8,6,5,1,170,3,7,3,32,5,7,7,6,1,13,1,153,2,8,2,35,19,7,1,164,2,7,3,37,19,8,1,160,3,7,3,39,19,9,1,156,3,8,3,41,19,10,1,153,3,8,2,44,19,25,1,136,3,8,2,46,19,160,2,8,3,48,19,157,3,7,4,50,7,6,6,15,1,139,2,9,3,52,6,7,6,16,1,12,1,122,3,8,3,55,6,7,6,17,1,132,4,7,3,57,6,7,6,159,2,60,6,7,6,32,1,123,3,62,6,7,6,33,1,119,4,64,6,7,6,22,1,128,3,67,6,7,6,150,2,1,1,67,6,7,6,153,1,67,6,7,6,25,1,11,1,115,1,67,6,7,6,26,1,126,1,67,6,7,6,28,1,124,1,67,6,7,6,29,1,11,1,111,1,67,6,7,6,30,1,11,1,110,1,66,7,6,8,42,1,109,1,66,21,32,1,10,1,71,4,33,1,66,21,115,1,2,1,33,1,66,21,119,1,32,1,66,21,36,1,115,1,66,21,48,1,66,1,2,1,33,1,66,21,38,1,10,1,65,4,33,1,66,21,152,1,66,21,152,1,66,8,6,7,152,1,66,7,7,7,114,6,32,1,66,7,7,7,114,6,32,1,66,7,7,7,57,1,56,2,2,2,32,1,66,7,7,7,58,1,18,1,35,7,32,1,12,1,49,1,3,7,7,7,19,1,1,1,2,2,24,1,8,2,46,1,4,8,32,1,66,8,6,7,12,1,1,1,4,3,2,2,2,2,2,1,18,2,7,3,44,1,3,9,32,1,12,1,1,1,51,8,6,7,14,1,1,2,1,1,1,1,2,3,1,3,1,1,3,1,2,2,11,4,1,1,2,5,41,1,3,10,32,1,12,3,51,8,5,8,7,3,2,1,1,1,1,2,1,1,1,1,1,4,1,6,1,2,2,2,1,2,1,3,1,2,1,6,1,2,1,4,39,3,2,3,1,6,32,1,8,1,2,6,2,1,4,1,2,1,7,1,30,8,5,8,2,2,3,1,1,1,6,2,1,11,1,6,2,2,1,2,1,6,1,15,24,5,1,3,1,7,2,3,1,10,28,1,14,1,9,2,1,1,38,8,1,1,2,9,1,3,10,1,1,6,10,1,2,2,1,1,3,9,1,4,1,6,1,7,8,47,26,1,27,1,7,1,18,1,4,1,4,13,1,9,1,3,3,1,1,1,1,4,1,1,2,3,2,5,2,7,1,2,1,6,1,7,1,16,1,53,25,1,66,8,1,1,3,8,1,3,3,1,1,2,3,1,1,1,1,4,2,2,2,1,7,1,2,1,1,1,1,4,1,3,1,31,7,2,1,15,6,2,1,7,1,2,25,1,66,7,7,7,29,1,2,1,3,1,6,1,16,1,2,3,6,14,5,16,2,6,10,2,25,1,66,7,7,7,57,1,7,6,1,36,9,8,1,1,25,1,66,7,7,7,68,35,7,11,5,1,25,1,66,7,7,7,69,2,1,3,1,22,7,16,2,1,2,1,25,1,66,8,6,7,75,19,6,21,2,1,2,1,92,21,79,4,1,7,4,26,2,1,2,1,2,1,89,21,91,12,9,1,1,7,2,1,28,1,66,21,87,12,13,9,98,21,84,11,19,6,99,21,80,12,22,2,3,1,32,1,66,21,70,1,5,13,25,6,99,21,73,13,28,6,2,1,29,1,66,21,69,14,31,7,31,1,66,21,58,2,6,14,39,1,99,8,4,9,55,1,6,15,42,1,99,8,5,8,51,2,6,15,145,8,5,8,48,2,5,17,147,8,5,8,43,3,6,17,150,8,5,8,40,2,6,18,153,8,5,8,36,3,5,20,155,8,5,8,32,4,5,19,1,2,156,8,5,8,29,3,6,19,95,1,66,8,5,8,25,4,5,21,97,1,66,8,5,8,22,4,5,21,167,8,5,8,18,5,5,21,170,8,5,8,14,6,4,23,105,1,66,8,5,8,11,4,6,23,108,1,66,8,5,8,8,4,6,23,111,1,66,8,5,8,4,4,6,24,114,1,66,8,5,14,7,23,116,1,66,8,6,8,2,1,4,25,119,1,66,8,5,8,3,1,2,24,122,1,66,8,3,38,124,1,65,24,1,22,127,1,63,1,1,44,130,1,63,7,1,5,1,30,132,1,58,3,1,42,135,1,58,14,2,27,138,1,56,10,1,32,140,1,53,8,4,27,1,3,143,1,49,9,1,1,2,1,1,24,2,2,147,1,47,7,5,32,148,1,30,1,12,8,5,32,151,1,28,1,1,1,3,1,4,4,1,3,6,35,151,1,22,10,1,10,1,1,4,39,151,1,20,25,2,41,151,1,18,47,1,22,151,1,18,70,148,2,1,1,17,71,148,4,17,71,148,4,14,61,2,10,149,6,10,63,2,10,149,8,4,66,4,9,149,80,2,9,149,91,149,92,78,3,67,92,148,80,3,9,148,92,148,92,148,92,148,92,74,1,3,2,68,92,73,3,2,3,67,92,69,1,3,3,2,2,68,94,66,2,1,5,2,3,67,95,58,15,2,1,69,96,55,21,68}
-- pal = {184,167,183,121,70,74}
-- pal = b8a7b779464a
RLE.draw(img_values, img_runs)
end
-- Transitions from the text phase to the day display phase, starting the timer for how long the day label is shown.
-- @within MysteriousManScreen
function MysteriousManScreen.go_to_day_state()
if on_text_complete then
on_text_complete()
on_text_complete = nil
end
if Context.game.current_screen ~= "mysterious_man" then
return
end
state = STATE_DAY
day_timer = day_display_seconds
end
-- Norman chooses to wake up, starting the button mash minigame and flash, then going to the next day.
-- @within MysteriousManScreen
function MysteriousManScreen.wake_up()
Context.home_norman_visible = false
Util.go_to_screen_by_id("home")
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
target_points = 100,
instruction_text = "Wake up Norman!",
show_progress_text = false,
on_win = function()
Audio.music_play_wakingup()
Meter.show()
if trigger_flash_on_wake then
trigger_flash_on_wake = false
Ascension.start_flash()
end
Window.set_current("game")
end,
})
end
-- Norman chooses to stay in bed, skipping the minigame and flash, and going straight to the next day.
-- @within MysteriousManScreen
function MysteriousManScreen.stay_in_bed()
Day.increase()
state = STATE_DAY
day_timer = day_display_seconds
end
--- Starts the mysterious man screen.
--- @param[opt] options table Optional configuration.</br>
--- Fields: </br>
--- * text (string) Override for the scrolling text.<br/>
--- * day_text (string) Override for the centered day label.<br/>
--- * on_text_complete (function) Callback fired once when the text phase ends.<br/>
--- * skip_text (boolean) If true, skip the text phase and go straight to day display.<br/>
function MysteriousManScreen.start(options)
options = options or {}
day_timer = 0
text_done = false
text_done_timer = 0
selected_choice = 1
text = options.text or ASC_01_TEXT
text_y = Config.screen.height
day_text_override = options.day_text
on_text_complete = options.on_text_complete
Meter.hide()
trigger_flash_on_wake = not options.skip_text
if options.skip_text then
show_mysterious_screen = false
state = STATE_DAY
day_timer = day_display_seconds
else
show_mysterious_screen = true
state = STATE_TEXT
end
Util.go_to_screen_by_id("mysterious_man")
Window.set_current("game")
end
--- Sets the scrolling text content.
--- @param new_text string The text to display.
function MysteriousManScreen.set_text(new_text)
text = new_text
end
Screen.register({
id = "mysterious_man",
name = "Mysterious Man",
decisions = {},
background_color = Config.colors.black,
init = function()
Audio.music_play_mystery()
end,
exit = function()
Audio.music_stop()
end,
update = function()
if state == STATE_TEXT then
if not text_done then
text_y = text_y - (text_speed * Context.delta_time)
local lines = 1
for _ in string.gmatch(text, "\n") do
lines = lines + 1
end
if text_y < -lines * 8 or Input.select() then
text_done = true
text_done_timer = TEXT_DONE_HOLD_SECONDS
-- If skipped by user, go to day state immediately
if Input.select() then
MysteriousManScreen.go_to_day_state()
end
end
else
text_done_timer = text_done_timer - Context.delta_time
if text_done_timer <= 0 or Input.select() then
MysteriousManScreen.go_to_day_state()
-- to be continued
if 4 <= Ascension.get_level() then
Window.set_current("continued")
end
end
end
elseif state == STATE_DAY then
day_timer = day_timer - Context.delta_time
if day_timer <= 0 or Input.select() then
if trigger_flash_on_wake or Ascension.get_level() < 1 then
MysteriousManScreen.wake_up()
else
state = STATE_CHOICE
selected_choice = 1
end
end
elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
local confirmed
selected_choice, confirmed = UI.update_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y)
if Input.select() or confirmed then
Audio.sfx_select()
if selected_choice == 1 then
MysteriousManScreen.wake_up()
else
MysteriousManScreen.stay_in_bed()
end
end
end
end,
draw = function()
if show_mysterious_screen then
MysteriousManScreen.draw_background()
end
if state == STATE_TEXT then
local cx = Config.screen.width / 2
local line_y = text_y
for line in (text .. "\n"):gmatch("(.-)\n") do
Print.text_center(line, cx, line_y, Config.colors.light_grey)
line_y = line_y + 8
end
elseif state == STATE_DAY then
MysteriousManScreen.draw_day_switch_background()
local day_text = day_text_override or ("Day " .. Context.day_count)
Print.text_center(
day_text,
Config.screen.width / 2,
Config.screen.height / 2 - 3,
Config.colors.white
)
elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
UI.draw_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y)
end
end,
})

View File

@@ -6,9 +6,9 @@ Screen.register({
"go_to_walking_to_home", "go_to_walking_to_home",
"have_a_coffee", "have_a_coffee",
}, },
situations = { init = function()
"drink_coffee", Audio.music_play_room_work()
}, end,
background = "office", background = "office",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then
@@ -23,5 +23,6 @@ Screen.register({
Sprite.draw_at("dev_guru", 22 * 8, 10 * 8 + 4) Sprite.draw_at("dev_guru", 22 * 8, 10 * 8 + 4)
Sprite.draw_at("dev_operator", 27 * 8, 10 * 8 + 4) Sprite.draw_at("dev_operator", 27 * 8, 10 * 8 + 4)
end end
Context.have_been_to_office = true
end end
}) })

View File

@@ -6,6 +6,7 @@ Screen.register({
}, },
background = "bedroom", background = "bedroom",
init = function() init = function()
Audio.music_play_mystery()
Context.stat_screen_active = true Context.stat_screen_active = true
Meter.hide() Meter.hide()
local cx = Config.screen.width * 0.75 local cx = Config.screen.width * 0.75
@@ -15,10 +16,11 @@ Screen.register({
end, end,
update = function() update = function()
if not Context.stat_screen_active then return end if not Context.stat_screen_active then return end
if Input.select() or Input.player_interact() then if Input.select() or Input.select() then
Focus.stop() Focus.stop()
Context.stat_screen_active = false Context.stat_screen_active = false
Meter.show() Meter.show()
Util.go_to_screen_by_id("home")
end end
end, end,
draw = function() draw = function()

View File

@@ -5,6 +5,9 @@ Screen.register({
"go_to_home", "go_to_home",
"go_to_office", "go_to_office",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "street", background = "street",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then

View File

@@ -4,8 +4,11 @@ Screen.register({
decisions = { decisions = {
"go_to_home", "go_to_home",
"go_to_office", "go_to_office",
"start_discussion", "sumphore_discussion",
}, },
init = function()
Audio.music_play_room_work()
end,
background = "street", background = "street",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then

View File

@@ -1,7 +0,0 @@
Situation.register({
id = "drink_coffee",
handle = function()
Audio.sfx_select()
Sprite.show("norman", 100, 100)
end,
})

View File

@@ -1,84 +0,0 @@
--- @section Situation
local _situations = {}
--- Registers a situation definition.
--- @within Situation
--- @param situation table The situation data table.
--- @param situation.id string Unique situation identifier.<br/>
--- @param[opt] situation.screen_id string ID of the screen this situation belongs to.<br/>
--- @param[opt] situation.handle function Called when the situation is applied. Defaults to noop.<br/>
--- @param[opt] situation.update function Called each frame while situation is active. Defaults to noop.<br/>
function Situation.register(situation)
if not situation or not situation.id then
PopupWindow.show({"Error: Invalid situation object registered (missing id)!"})
return
end
if not situation.handle then
situation.handle = function() end
end
if not situation.update then
situation.update = function() end
end
if _situations[situation.id] then
trace("Warning: Overwriting situation with id: " .. situation.id)
end
_situations[situation.id] = situation
end
--- Gets a situation by ID.
--- @within Situation
--- @param id string The situation ID.
--- @return result table The situation table or nil. </br>
--- Fields: </br>
--- * id (string) Unique situation identifier.<br/>
--- * screen_id (string) ID of the screen this situation belongs to.<br/>
--- * handle (function) Called when the situation is applied.<br/>
--- * update (function) Called each frame while situation is active.<br/>
function Situation.get_by_id(id)
return _situations[id]
end
--- Gets all registered situations, optionally filtered by screen ID.
--- @within Situation
--- @param screen_id string Optional. If provided, returns situations associated with this screen ID.
--- @return result table A table containing all registered situation data, indexed by their IDs, or an array filtered by screen_id. </br>
--- Fields: </br>
--- * id (string) Unique situation identifier.<br/>
--- * screen_id (string) ID of the screen this situation belongs to.<br/>
--- * handle (function) Called when the situation is applied.<br/>
--- * update (function) Called each frame while situation is active.<br/>
function Situation.get_all(screen_id)
if screen_id then
local filtered_situations = {}
for _, situation in pairs(_situations) do
if situation.screen_id == screen_id then
table.insert(filtered_situations, situation)
end
end
return filtered_situations
end
return _situations
end
--- Applies a situation, checking screen compatibility and returning the new situation ID if successful.
--- @within Situation
--- @param id string The situation ID to apply.
--- @param current_screen_id string The ID of the currently active screen.
--- @return string|nil The ID of the applied situation if successful, otherwise nil.
function Situation.apply(id, current_screen_id)
local situation = Situation.get_by_id(id)
local screen = Screen.get_by_id(current_screen_id)
if not situation then
trace("Error: No situation found with id: " .. id)
return nil
end
if Util.contains(screen.situations, id) then
situation.handle()
return id
else
trace("Info: Situation " .. id .. " cannot be applied to current screen (id: " .. current_screen_id .. ").")
return nil
end
end

View File

@@ -44,7 +44,7 @@ function AsciiArt.draw(text, options)
for j = 1, #line do for j = 1, #line do
local char = line:sub(j, j) local char = line:sub(j, j)
if char == "#" then if char == "#" then
rect(x_offset + (j - 1) * char_w, current_y, char_w - 1, char_h - 1, color) rect(x_offset + (j - 1) * char_w, current_y, char_w, char_h, color)
end end
end end
current_y = current_y + char_h + line_gap current_y = current_y + char_h + line_gap

View File

@@ -5,10 +5,9 @@ local INPUT_KEY_LEFT = 2
local INPUT_KEY_RIGHT = 3 local INPUT_KEY_RIGHT = 3
local INPUT_KEY_A = 4 local INPUT_KEY_A = 4
local INPUT_KEY_B = 5 local INPUT_KEY_B = 5
local INPUT_KEY_Y = 7
local INPUT_KEY_SPACE = 48 local INPUT_KEY_SPACE = 48
local INPUT_KEY_BACKSPACE = 51
local INPUT_KEY_ENTER = 50 local INPUT_KEY_ENTER = 50
local INPUT_KEY_BACKSPACE = 51
--- Checks if Up is pressed. --- Checks if Up is pressed.
--- @within Input --- @within Input
@@ -22,22 +21,12 @@ function Input.left() return btnp(INPUT_KEY_LEFT) end
--- Checks if Right is pressed. --- Checks if Right is pressed.
--- @within Input --- @within Input
function Input.right() return btnp(INPUT_KEY_RIGHT) end function Input.right() return btnp(INPUT_KEY_RIGHT) end
--- Checks if Space is pressed.
--- @within Input
function Input.space() return keyp(INPUT_KEY_SPACE) end
--- Checks if Select is pressed. --- Checks if Select is pressed.
--- @within Input --- @within Input
function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) end function Input.select() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_SPACE) or Mouse.clicked() end
--- Checks if Menu Confirm is pressed. --- Checks if Back is pressed.
--- @within Input --- @within Input
function Input.menu_confirm() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_ENTER) end function Input.back() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_BACKSPACE) end
--- Checks if Player Interact is pressed. --- Checks if Enter is pressed.
--- @within Input --- @within Input
function Input.player_interact() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_ENTER) end function Input.enter() return keyp(INPUT_KEY_ENTER) end
--- Checks if Menu Back is pressed.
--- @within Input
function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
--- Checks if Toggle Popup is pressed.
--- @within Input
function Input.toggle_popup() return keyp(INPUT_KEY_ENTER) end

View File

@@ -17,6 +17,16 @@ end
--- @within Main --- @within Main
function TIC() function TIC()
init_game() init_game()
Mouse.update()
local now = time()
if Context.last_frame_time == 0 then
Context.delta_time = 0
else
Context.delta_time = (now - Context.last_frame_time) / 1000
end
Context.last_frame_time = now
cls(Config.colors.black) cls(Config.colors.black)
local handler = Window.get_current_handler() -- Get handler from Window manager local handler = Window.get_current_handler() -- Get handler from Window manager
if handler then if handler then

View File

@@ -0,0 +1,76 @@
--- @section Mouse
local _mx, _my = 0, 0
local _mleft, _mleft_prev = false, false
local _consumed = false
--- Updates mouse state. Call once per frame.
--- @within Mouse
function Mouse.update()
_mleft_prev = _mleft
_consumed = false
local mt = {mouse()}
_mx, _my, _mleft = mt[1], mt[2], mt[3]
end
--- Returns current mouse X position.
--- @within Mouse
function Mouse.x() return _mx end
--- Returns current mouse Y position.
--- @within Mouse
function Mouse.y() return _my end
--- Returns true if the mouse button was just pressed this frame (and not yet consumed).
--- @within Mouse
function Mouse.clicked() return _mleft and not _mleft_prev and not _consumed end
--- Returns true if the mouse button is held down.
--- @within Mouse
function Mouse.held() return _mleft end
--- Marks the current click as consumed so Mouse.clicked() won't fire again this frame.
--- @within Mouse
function Mouse.consume() _consumed = true end
--- Returns true if the mouse is within the given rectangle.
--- @within Mouse
--- @param x number Left edge.
--- @param y number Top edge.
--- @param w number Width.
--- @param h number Height.
function Mouse.in_rect(x, y, w, h)
return _mx >= x and _mx < x + w and _my >= y and _my < y + h
end
--- Returns true if the mouse is within the given circle.
--- @within Mouse
--- @param cx number Center x.
--- @param cy number Center y.
--- @param r number Radius.
function Mouse.in_circle(cx, cy, r)
local dx = _mx - cx
local dy = _my - cy
return (dx * dx + dy * dy) <= (r * r)
end
--- Returns true if the mouse was clicked inside the given rectangle, and consumes the click.
--- @within Mouse
--- @param rect table A table with fields: x, y, w, h.
function Mouse.zone(rect)
if Mouse.clicked() and Mouse.in_rect(rect.x, rect.y, rect.w, rect.h) then
Mouse.consume()
return true
end
return false
end
--- Returns true if the mouse was clicked inside the given circle, and consumes the click.
--- @within Mouse
--- @param circle table A table with fields: x, y, r.
function Mouse.zone_circle(circle)
if Mouse.clicked() and Mouse.in_circle(circle.x, circle.y, circle.r) then
Mouse.consume()
return true
end
return false
end

View File

@@ -10,7 +10,7 @@ function Print.text(text, x, y, color, fixed, scale)
local shadow_color = Config.colors.black local shadow_color = Config.colors.black
if color == shadow_color then shadow_color = Config.colors.light_grey end if color == shadow_color then shadow_color = Config.colors.light_grey end
scale = scale or 1 scale = scale or 1
print(text, x + 1, y + 1, shadow_color, fixed, scale) print(text, x + scale, y + scale, shadow_color, fixed, scale)
print(text, x, y, color, fixed, scale) print(text, x, y, color, fixed, scale)
end end
@@ -24,7 +24,7 @@ end
--- @param[opt] scale number The scaling factor.<br/> --- @param[opt] scale number The scaling factor.<br/>
function Print.text_center(text, x, y, color, fixed, scale) function Print.text_center(text, x, y, color, fixed, scale)
scale = scale or 1 scale = scale or 1
local text_width = print(text, 0, -6, 0, fixed, scale) local text_width = print(text, 0, -6 * scale, 0, fixed, scale)
local centered_x = x - (text_width / 2) local centered_x = x - (text_width / 2)
Print.text(text, centered_x, y, color, fixed, scale) Print.text(text, centered_x, y, color, fixed, scale)
end end

22
inc/system/system.rle.lua Normal file
View File

@@ -0,0 +1,22 @@
--- @section RLE
RLE = {}
--- Draws an RLE-encoded image.
--- @param runs table Array of run lengths.
--- @param values table Array of pixel values (colors).
function RLE.draw(img_values, img_runs)
local SCREEN_WIDTH=240
local SCREEN_HEIGHT=136
local val_i=0
local run=0
for y=0,SCREEN_HEIGHT-1 do
for x=0,SCREEN_WIDTH-1 do
if run==0 then
val_i=val_i+1
run=img_runs[val_i]
end
run=run-1
pix(x,y,img_values[val_i])
end
end
end

View File

@@ -38,8 +38,12 @@ end
--- @within UI --- @within UI
--- @param items table A table of menu items.<br/> --- @param items table A table of menu items.<br/>
--- @param selected_item number The current index of the selected item.<br/> --- @param selected_item number The current index of the selected item.<br/>
--- @param[opt] x number Menu x position (required for mouse support).<br/>
--- @param[opt] y number Menu y position (required for mouse support).<br/>
--- @param[opt] centered boolean Whether the menu is centered horizontally.<br/>
--- @return number selected_item The updated index of the selected item. --- @return number selected_item The updated index of the selected item.
function UI.update_menu(items, selected_item) --- @return boolean mouse_confirmed True if the user clicked on a menu item.
function UI.update_menu(items, selected_item, x, y, centered)
if Input.up() then if Input.up() then
Audio.sfx_beep() Audio.sfx_beep()
selected_item = selected_item - 1 selected_item = selected_item - 1
@@ -53,7 +57,25 @@ function UI.update_menu(items, selected_item)
selected_item = 1 selected_item = 1
end end
end end
return selected_item
if x ~= nil and y ~= nil then
local menu_x = x
if centered then
local max_w = 0
for _, item in ipairs(items) do
local w = print(item.label, 0, -10, 0, false, 1, false)
if w > max_w then max_w = w end
end
menu_x = (Config.screen.width - max_w) / 2
end
for i, _ in ipairs(items) do
if Mouse.zone({ x = menu_x - 8, y = y + (i-1) * 10, w = Config.screen.width, h = 10 }) then
return i, true
end
end
end
return selected_item, false
end end
--- Draws a bordered textbox with scrolling text. --- Draws a bordered textbox with scrolling text.

View File

@@ -39,4 +39,31 @@ function Util.contains(t, value)
end end
end end
return false return false
end end
--- Deep copies tables
--- @within Util
--- @param orig any The value to deep copy.
--- @param seen any Used for recursive calls to handle loops
--- @return any any The copied object
function Util.deepcopy(orig, seen)
if type(orig) ~= "table" then
return orig
end
if seen and seen[orig] then
return seen[orig] -- handle cycles / shared refs
end
local copy = {}
seen = seen or {}
seen[orig] = copy
for k, v in pairs(orig) do
local new_k = Util.deepcopy(k, seen)
local new_v = Util.deepcopy(v, seen)
copy[new_k] = new_v
end
return setmetatable(copy, getmetatable(orig))
end

View File

@@ -107,9 +107,9 @@ function AudioTestWindow.update()
AudioTestWindow.menuitems = AudioTestWindow.generate_menuitems( AudioTestWindow.menuitems = AudioTestWindow.generate_menuitems(
AudioTestWindow.list_func, AudioTestWindow.index_func AudioTestWindow.list_func, AudioTestWindow.index_func
) )
elseif Input.menu_confirm() then elseif Input.select() then
AudioTestWindow.menuitems[AudioTestWindow.index_menu].decision() AudioTestWindow.menuitems[AudioTestWindow.index_menu].decision()
elseif Input.menu_back() then elseif Input.back() then
AudioTestWindow.back() AudioTestWindow.back()
end end
end end

View File

@@ -1,102 +0,0 @@
--- @section ConfigurationWindow
ConfigurationWindow.controls = {}
ConfigurationWindow.selected_control = 1
--- Initializes configuration window.
--- @within ConfigurationWindow
function ConfigurationWindow.init()
ConfigurationWindow.controls = {
{
label = "Save",
action = function() Config.save() end,
type = "action_item"
},
{
label = "Restore Defaults",
action = function() Config.reset() end,
type = "action_item"
},
}
end
--- Draws configuration window.
--- @within ConfigurationWindow
function ConfigurationWindow.draw()
UI.draw_top_bar("Configuration")
local x_start = 10
local y_start = 40
local x_value_right_align = Config.screen.width - 10
local char_width = 4
for i, control in ipairs(ConfigurationWindow.controls) do
local current_y = y_start + (i - 1) * 12
local color = Config.colors.light_blue
if control.type == "numeric_stepper" then
local value = control.get()
local label_text = control.label
local value_text = string.format(control.format, value)
local value_x = x_value_right_align - (#value_text * char_width)
if i == ConfigurationWindow.selected_control then
color = Config.colors.item
Print.text("<", x_start - 8, current_y, color)
Print.text(label_text, x_start, current_y, color)
Print.text(value_text, value_x, current_y, color)
Print.text(">", x_value_right_align + 4, current_y, color)
else
Print.text(label_text, x_start, current_y, color)
Print.text(value_text, value_x, current_y, color)
end
elseif control.type == "action_item" then
local label_text = control.label
if i == ConfigurationWindow.selected_control then
color = Config.colors.item
Print.text("<", x_start - 8, current_y, color)
Print.text(label_text, x_start, current_y, color)
Print.text(">", x_start + 8 + (#label_text * char_width) + 4, current_y, color)
else
Print.text(label_text, x_start, current_y, color)
end
end
end
Print.text("Press B to go back", x_start, 120, Config.colors.light_grey)
end
--- Updates configuration window logic.
--- @within ConfigurationWindow
function ConfigurationWindow.update()
if Input.menu_back() then
GameWindow.set_state("menu")
return
end
if Input.up() then
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1
if ConfigurationWindow.selected_control < 1 then
ConfigurationWindow.selected_control = #ConfigurationWindow.controls
end
elseif Input.down() then
ConfigurationWindow.selected_control = ConfigurationWindow.selected_control + 1
if ConfigurationWindow.selected_control > #ConfigurationWindow.controls then
ConfigurationWindow.selected_control = 1
end
end
local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control]
if control then
if control.type == "numeric_stepper" then
local current_value = control.get()
if Input.left() then
local new_value = math.max(control.min, current_value - control.step)
control.set(new_value)
elseif Input.right() then
local new_value = math.min(control.max, current_value + control.step)
control.set(new_value)
end
elseif control.type == "action_item" then
if Input.menu_confirm() then
control.action()
end
end
end
end

View File

@@ -26,7 +26,7 @@ end
--- @within ContinuedWindow --- @within ContinuedWindow
function ContinuedWindow.update() function ContinuedWindow.update()
ContinuedWindow.timer = ContinuedWindow.timer - 1 ContinuedWindow.timer = ContinuedWindow.timer - 1
if ContinuedWindow.timer <= 0 or Input.select() or Input.menu_confirm() then if ContinuedWindow.timer <= 0 or Input.select() or Input.select() then
Window.set_current("menu") Window.set_current("menu")
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
end end

View File

@@ -0,0 +1,44 @@
--- @section ControlsWindow
local _controls = {
{ action = "Navigate", keyboard = "Arrow keys", gamepad = "D-pad" },
{ action = "Select / OK", keyboard = "Space", gamepad = "Z button" },
{ action = "Back", keyboard = "Backspace", gamepad = "B button" },
{ action = "Click", keyboard = "Mouse", gamepad = "" },
}
--- Draws the controls window.
--- @within ControlsWindow
function ControlsWindow.draw()
UI.draw_top_bar("Controls")
local col_action = 4
local col_keyboard = 80
local col_gamepad = 170
local row_h = 10
local y_header = 18
local y_start = 30
Print.text("Action", col_action, y_header, Config.colors.light_grey)
Print.text("Keyboard", col_keyboard, y_header, Config.colors.light_grey)
Print.text("Gamepad", col_gamepad, y_header, Config.colors.light_grey)
line(col_action, y_header + 8, Config.screen.width - 4, y_header + 8, Config.colors.dark_grey)
for i, entry in ipairs(_controls) do
local y = y_start + (i - 1) * row_h
Print.text(entry.action, col_action, y, Config.colors.white)
Print.text(entry.keyboard, col_keyboard, y, Config.colors.light_blue)
if entry.gamepad ~= "" then
Print.text(entry.gamepad, col_gamepad, y, Config.colors.light_blue)
end
end
Print.text("Space / Z button or click to go back", col_action, Config.screen.height - 10, Config.colors.light_grey)
end
--- Updates the controls window logic.
--- @within ControlsWindow
function ControlsWindow.update()
if Input.back() or Input.select() then
Window.set_current("menu")
end
end

View File

@@ -52,7 +52,7 @@ function EndWindow.update()
end end
end end
if Input.menu_confirm() then if Input.select() then
Audio.sfx_select() Audio.sfx_select()
if Context._end.selection == 1 then if Context._end.selection == 1 then
Context._end.state = "ending" Context._end.state = "ending"
@@ -69,7 +69,7 @@ function EndWindow.update()
end end
end end
elseif Context._end.state == "ending" then elseif Context._end.state == "ending" then
if Input.menu_confirm() then if Input.select() then
Window.set_current("menu") Window.set_current("menu")
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
end end

View File

@@ -38,7 +38,7 @@ end
--- @within GameWindow --- @within GameWindow
function GameWindow.update() function GameWindow.update()
Focus.update() Focus.update()
if Input.menu_back() then if Input.back() then
Window.set_current("menu") Window.set_current("menu")
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
return return
@@ -48,14 +48,6 @@ function GameWindow.update()
if not screen or not screen.update then return end if not screen or not screen.update then return end
screen.update() screen.update()
-- Handle current situation updates
if Context.game.current_situation then
local current_situation_obj = Situation.get_by_id(Context.game.current_situation)
if current_situation_obj and type(current_situation_obj.update) == "function" then
current_situation_obj.update()
end
end
if Context.stat_screen_active then return end if Context.stat_screen_active then return end
-- Fetch and filter decisions locally -- Fetch and filter decisions locally
@@ -68,7 +60,7 @@ function GameWindow.update()
_selected_decision_index = 1 _selected_decision_index = 1
end end
local new_selected_decision_index = Decision.update( local new_selected_decision_index, mouse_confirmed = Decision.update(
_available_decisions, _available_decisions,
_selected_decision_index _selected_decision_index
) )
@@ -77,7 +69,7 @@ function GameWindow.update()
_selected_decision_index = new_selected_decision_index _selected_decision_index = new_selected_decision_index
end end
if Input.select() then if Input.select() or mouse_confirmed then
local selected_decision = _available_decisions[_selected_decision_index] local selected_decision = _available_decisions[_selected_decision_index]
if selected_decision and selected_decision.handle then if selected_decision and selected_decision.handle then
Audio.sfx_select() Audio.sfx_select()

View File

@@ -1,6 +1,6 @@
--- @section BriefIntroWindow --- @section BriefIntroWindow
BriefIntroWindow.y = Config.screen.height BriefIntroWindow.y = Config.screen.height
BriefIntroWindow.speed = 0.5 BriefIntroWindow.speed = 30 -- pixels per second
BriefIntroWindow.text = [[ BriefIntroWindow.text = [[
Norman Reds everyday life Norman Reds everyday life
seems ordinary: work, seems ordinary: work,
@@ -24,14 +24,14 @@ end
--- Updates the brief intro window logic. --- Updates the brief intro window logic.
--- @within BriefIntroWindow --- @within BriefIntroWindow
function BriefIntroWindow.update() function BriefIntroWindow.update()
BriefIntroWindow.y = BriefIntroWindow.y - BriefIntroWindow.speed BriefIntroWindow.y = BriefIntroWindow.y - (BriefIntroWindow.speed * Context.delta_time)
local lines = 1 local lines = 1
for _ in string.gmatch(BriefIntroWindow.text, "\n") do for _ in string.gmatch(BriefIntroWindow.text, "\n") do
lines = lines + 1 lines = lines + 1
end end
if BriefIntroWindow.y < -lines * 8 or Input.select() or Input.menu_confirm() then if BriefIntroWindow.y < -lines * 8 or Input.select() or Input.select() then
Window.set_current("menu") Window.set_current("menu")
end end
end end

View File

@@ -1,11 +1,11 @@
--- @section TitleIntroWindow --- @section TitleIntroWindow
TitleIntroWindow.timer = 180 -- 3 seconds at 60fps TitleIntroWindow.timer = 180 -- 3 seconds at 60fps
TitleIntroWindow.text = [[ TitleIntroWindow.text = [[
## ### ### ### ### ### ### ### ## # # ## ### ### ### ### ### ### ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ### # # # # # ### # # # # # ### ### # # # # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
## ### # ### # # ### # ### ## # ## ### # ### # # ### # ### ## #
# # ### ### ## # # # # ### ### ## # #
## # # # # # # ## # ## # # # # # # ## #
@@ -30,7 +30,7 @@ end
--- @within TitleIntroWindow --- @within TitleIntroWindow
function TitleIntroWindow.update() function TitleIntroWindow.update()
TitleIntroWindow.timer = TitleIntroWindow.timer - 1 TitleIntroWindow.timer = TitleIntroWindow.timer - 1
if TitleIntroWindow.timer <= 0 or Input.select() or Input.menu_confirm() then if TitleIntroWindow.timer <= 0 or Input.select() or Input.select() then
Window.set_current("intro_ttg") Window.set_current("intro_ttg")
end end
end end

View File

@@ -27,13 +27,13 @@ function TTGIntroWindow.update()
TTGIntroWindow.glitch_started = true TTGIntroWindow.glitch_started = true
end end
-- Count menu_back presses during the intro -- Count enter presses during the intro
if Input.menu_back() then if Input.enter() then
TTGIntroWindow.space_count = TTGIntroWindow.space_count + 1 TTGIntroWindow.space_count = TTGIntroWindow.space_count + 1
end end
TTGIntroWindow.timer = TTGIntroWindow.timer - 1 TTGIntroWindow.timer = TTGIntroWindow.timer - 1
if TTGIntroWindow.timer <= 0 or Input.menu_confirm() then if TTGIntroWindow.timer <= 0 or Input.select() then
-- Evaluate exactly 3 presses at the end of the intro -- Evaluate exactly 3 presses at the end of the intro
if TTGIntroWindow.space_count == 3 then if TTGIntroWindow.space_count == 3 then
Context.test_mode = true Context.test_mode = true

View File

@@ -1,18 +1,62 @@
--- @section MenuWindow --- @section MenuWindow
local _menu_items = {} local _menu_items = {}
local _click_timer = 0
local _anim = 0
local _menu_max_w = 0
local ANIM_SPEED = 2.5
local HEADER_H = 28
--- Calculates the animated x position of the menu block.
--- @within MenuWindow
--- @return number x The left edge x coordinate for the menu.
function MenuWindow.calc_menu_x()
local center_start = Config.screen.width / 2
local center_end = Config.screen.width * 0.72
local center = center_start + _anim * (center_end - center_start)
return math.floor(center - _menu_max_w / 2)
end
--- Draws the header with title and separator.
--- @within MenuWindow
function MenuWindow.draw_header()
rect(0, 0, Config.screen.width, HEADER_H, Config.colors.dark_grey)
rect(0, HEADER_H - 2, Config.screen.width, 2, Config.colors.light_blue)
local cx = Config.screen.width / 2
local subtitle = "Definitely not an"
if Context.test_mode then subtitle = subtitle .. " [TEST]" end
local sub_w = print(subtitle, 0, -6, 0, false, 1, true)
print(subtitle, math.floor(cx - sub_w / 2) + 1, 5, Config.colors.dark_grey, false, 1, true)
print(subtitle, math.floor(cx - sub_w / 2), 4, Config.colors.light_grey, false, 1, true)
Print.text_center("IMPOSTOR", cx, 12, Config.colors.item, false, 2)
end
--- Draws the 4x scaled Norman sprite on the left side of the screen.
--- @within MenuWindow
function MenuWindow.draw_norman()
local nx = math.floor(Config.screen.width * 0.45 / 2) - 32
local ny = HEADER_H + math.floor((Config.screen.height - HEADER_H - 96) / 2)
spr(272, nx, ny, 0, 4)
spr(273, nx + 32, ny, 0, 4)
spr(288, nx, ny + 32, 0, 4)
spr(289, nx + 32, ny + 32, 0, 4)
spr(304, nx, ny + 64, 0, 4)
spr(305, nx + 32, ny + 64, 0, 4)
end
--- Draws the menu window. --- Draws the menu window.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.draw() function MenuWindow.draw()
local title = "Definitely not an Impostor" MenuWindow.draw_header()
if Context.test_mode then
title = title .. " (TEST MODE)" if _anim > 0 then
MenuWindow.draw_norman()
end end
UI.draw_top_bar(title)
local menu_h = #_menu_items * 10 local menu_h = #_menu_items * 10
local y = 10 + (Config.screen.height - 10 - 10 - menu_h) / 2 local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2)
UI.draw_menu(_menu_items, Context.current_menu_item, 0, y, true) UI.draw_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false)
local ttg_text = "TTG" local ttg_text = "TTG"
local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false) local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false)
@@ -22,9 +66,32 @@ end
--- Updates the menu window logic. --- Updates the menu window logic.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.update() function MenuWindow.update()
Context.current_menu_item = UI.update_menu(_menu_items, Context.current_menu_item) if _anim < 1 then
_anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time)
end
if Input.menu_confirm() then local menu_h = #_menu_items * 10
local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2)
if _click_timer > 0 then
_click_timer = _click_timer - Context.delta_time
if _click_timer <= 0 then
_click_timer = 0
local selected_item = _menu_items[Context.current_menu_item]
if selected_item and selected_item.decision then
selected_item.decision()
end
end
return
end
local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false)
Context.current_menu_item = new_item
if mouse_confirmed then
Audio.sfx_select()
_click_timer = 0.5
elseif Input.select() then
local selected_item = _menu_items[Context.current_menu_item] local selected_item = _menu_items[Context.current_menu_item]
if selected_item and selected_item.decision then if selected_item and selected_item.decision then
Audio.sfx_select() Audio.sfx_select()
@@ -64,11 +131,10 @@ function MenuWindow.exit()
exit() exit()
end end
--- Opens the configuration menu. --- Opens the controls screen.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.configuration() function MenuWindow.controls()
ConfigurationWindow.init() Window.set_current("controls")
GameWindow.set_state("configuration")
end end
--- Opens the audio test menu. --- Opens the audio test menu.
@@ -85,34 +151,42 @@ function MenuWindow.continued()
GameWindow.set_state("continued") GameWindow.set_state("continued")
end end
--- Opens the minigame ddr test menu. --- Opens the DDR minigame test.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.ddr_test() function MenuWindow.ddr_test()
AudioTestWindow.init() AudioTestWindow.init()
GameWindow.set_state("minigame_ddr") GameWindow.set_state("minigame_ddr")
MinigameDDRWindow.start("menu", nil) MinigameDDRWindow.start("menu", "generated", { special_mode = "only_nothing" })
end end
--- Refreshes menu items. --- Refreshes the list of menu items based on current game state.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.refresh_menu_items() function MenuWindow.refresh_menu_items()
_menu_items = {} _menu_items = {}
if Context.game_in_progress then if Context.game_in_progress then
table.insert(_menu_items, {label = "Resume Game", decision = MenuWindow.resume_game}) table.insert(_menu_items, {label = "Resume Game", decision = MenuWindow.resume_game})
table.insert(_menu_items, {label = "Save Game", decision = MenuWindow.save_game}) table.insert(_menu_items, {label = "Save Game", decision = MenuWindow.save_game})
end end
table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game}) table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game})
table.insert(_menu_items, {label = "Load Game", decision = MenuWindow.load_game}) table.insert(_menu_items, {label = "Load Game", decision = MenuWindow.load_game})
table.insert(_menu_items, {label = "Configuration", decision = MenuWindow.configuration}) table.insert(_menu_items, {label = "Controls", decision = MenuWindow.controls})
if Context.test_mode then if Context.test_mode then
table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test}) table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued}) table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued})
table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test}) table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test})
end end
table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit}) table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit})
_menu_max_w = 0
for _, item in ipairs(_menu_items) do
local w = print(item.label, 0, -10, 0, false, 1, false)
if w > _menu_max_w then _menu_max_w = w end
end
Context.current_menu_item = 1 Context.current_menu_item = 1
_click_timer = 0
_anim = 0
end end

View File

@@ -1,5 +1,15 @@
--- @section MinigameDDRWindow --- @section MinigameDDRWindow
--- Background drawing for DDR minigame.
--- @witin MinigameDDRWindow
function MinigameDDRWindow.draw_background()
local img_values = {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1}
local img_runs = {809,40,5,26,178,42,4,7,30,10,127,103,11,1,116,124,116,124,115,18,60,47,115,10,105,10,115,9,108,9,114,9,108,9,114,9,108,9,114,9,33,31,44,9,114,9,34,28,46,9,114,9,108,9,114,9,108,9,114,9,108,9,114,9,108,9,114,9,109,8,114,9,109,8,114,9,109,9,112,10,105,1,3,9,111,11,104,2,3,9,111,11,101,5,3,9,111,11,101,5,3,9,111,9,103,5,3,9,111,9,99,1,2,6,3,9,111,9,99,1,2,8,1,9,111,9,3,1,88,1,3,1,2,11,1,9,111,9,3,1,88,1,3,14,1,9,111,9,3,1,88,1,3,1,2,11,1,9,111,9,3,1,88,1,5,12,1,9,111,9,3,1,88,1,3,14,1,9,111,9,3,1,88,3,1,1,2,11,1,9,111,9,3,1,88,3,1,14,1,9,111,9,3,1,88,3,2,13,2,8,111,9,3,1,88,3,3,12,3,8,110,9,3,1,88,3,3,12,3,9,109,9,3,1,88,3,1,14,3,9,109,9,3,1,90,1,1,7,3,4,3,9,109,9,3,1,90,1,1,5,6,3,3,9,108,10,3,1,44,1,4,1,40,1,1,5,7,2,3,9,108,10,3,1,44,1,4,1,38,3,1,5,7,3,2,9,108,10,3,1,48,1,39,3,1,5,7,2,3,9,108,10,3,1,88,3,1,5,6,3,3,9,108,10,3,1,32,5,4,4,3,2,38,3,1,15,2,9,108,10,3,1,41,5,2,2,2,4,32,3,1,15,2,9,108,10,2,2,41,9,2,5,3,1,3,1,23,3,1,9,1,5,2,9,108,10,2,2,41,12,35,3,1,7,4,4,2,9,108,10,2,2,32,26,30,3,1,5,7,3,2,9,108,10,2,2,36,21,31,9,7,3,2,9,108,10,2,2,35,23,30,10,6,3,2,9,108,10,2,2,35,23,30,11,4,4,2,9,108,10,2,2,34,24,30,19,2,9,108,10,2,2,33,25,30,19,2,9,108,10,2,2,32,28,28,19,2,9,108,10,2,2,32,30,26,19,2,9,108,10,2,2,33,31,24,19,2,9,108,10,2,4,37,17,32,19,2,10,107,10,2,5,85,19,2,10,107,10,2,109,2,10,107,12,1,107,3,10,107,133,107,133,107,133,107,133,107,133,107,133,107,133,118,111,129,6,63,3,28,10,121,13,98,6,142,7,76,6,129,10,13,5,77,15,109,4,31,4,78,4,24,7,75,173,66,176,64,177,62,178,62,178,62,56,31,2,7,2,2,3,2,1,2,1,61,8,62,56,114,8,62,56,114,8,62,56,114,8,62,56,114,8,62,9,8,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,114,9,61,8,9,39,115,8,61,8,8,40,115,8,61,56,115,8,61,13,2,1,1,1,1,16,1,1,2,17,115,8,61,13,2,1,1,1,2,1,1,10,2,1,1,1,2,1,2,7,1,6,115,8,60,14,2,1,1,1,5,9,2,1,1,1,2,1,2,6,2,6,115,8,59,15,2,1,1,1,5,9,2,1,1,1,2,1,2,6,2,6,115,8,59,15,2,1,1,1,6,8,2,1,1,1,2,1,2,14,115,8,59,6,1,11,1,1,7,10,1,1,2,1,2,14,115,8,59,6,1,11,1,1,7,10,1,1,2,1,2,1,1,12,115,9,58,6,1,1,2,10,9,8,1,1,2,1,2,1,1,12,115,9,58,6,1,1,2,8,1,1,9,8,1,1,2,1,2,1,1,11,116,9,58,6,1,1,2,1,2,7,9,8,1,1,2,1,2,1,1,11,116,9,58,6,1,1,2,1,2,7,9,10,2,1,2,1,1,11,116,9,58,57,116,9,58,58,115,9,102,13,116,9,58,48,2,10,1,3,22,84,5,7,58,158,7,5,6,6,75,3,7,2,7,1,7,2,6,1,6,2,6,2,4,4,6,2,6,1,6,2,6,3,6,1,6,5,7,8,7,9,11,1,9,2,50,1,22,1,14,5,3,5,3,4,3,6,3,5,2,16,2,5,3,14,2,6,2,6,5,5,11,4,30,1,50,1,37,6,3,5,3,5,2,6,3,5,3,6,1,7,2,6,2,15,2,6,2,5,6,5,12,1,15,1,106,93,2,8,10,5,2,6,2,5,4,4,100,7,3,13,2,65,3,8,3,5,2,5,4,4,3,5,4,4,100,94,2,5,3,5,4,4,6,5,2,5,105,86,2,13,3,5,4,4,6,5,2,1,1,3,102,4,2,24,2,62,1,1,2,7,3,5,4,5,4,5,2,8,4,2,92,5,2,24,2,61,5,5,7,2,5,6,3,4,3,7,102,88,6,7,12,8,6,1,3,7,103,87,6,7,11,9,6,1,3,7,110,1,9,9,5,1,2,1,3,47,162,1,10,6,27,34,162,1,10,6,27,34,97,3,57,14,2,7,26,66,9,33,5,2,17,223,17,223,18,222,47,193,53,1,3,163,19,1,85,109,14,29,36,151,784}
-- pal = {220,220,220,90,90,90}
-- pal = dcdcdc5a5a5a
RLE.draw(img_values, img_runs)
end
--- Gets initial DDR minigame configuration. --- Gets initial DDR minigame configuration.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @return result table The default DDR minigame configuration. --- @return result table The default DDR minigame configuration.
@@ -9,6 +19,7 @@ function MinigameDDRWindow.init_context()
local total_width = (4 * arrow_size) + (3 * arrow_spacing) local total_width = (4 * arrow_size) + (3 * arrow_spacing)
local start_x = (Config.screen.width - total_width) / 2 local start_x = (Config.screen.width - total_width) / 2
return { return {
special_mode = "normal", -- "normal", "only_special", "only_left", "only_nothing"
bar_fill = 0, bar_fill = 0,
max_fill = 100, max_fill = 100,
fill_per_hit = 10, fill_per_hit = 10,
@@ -38,12 +49,92 @@ function MinigameDDRWindow.init_context()
current_song = nil, current_song = nil,
pattern_index = 1, pattern_index = 1,
use_pattern = false, use_pattern = false,
generated_length = 30,
return_window = nil, return_window = nil,
win_timer = 0, win_timer = 0,
on_win = nil on_win = nil,
total_misses = 0,
total_hits = 0,
special_mode_condition = true,
special_mode_counter = 0
} }
end end
function MinigameDDRWindow.prepareSong(song, generated_length, special_mode)
local current_song = Util.deepcopy(song)
if current_song.generated then
local pattern = musicator_generate_pattern(generated_length, current_song.bpm, current_song.spd * 4)
current_song.pattern = pattern
current_song.end_frame = pattern[#pattern].frame
if special_mode == "only_special" then
for i, _ in ipairs(current_song.pattern) do
current_song.pattern[i].special = (i % 5 == 0)
end
end
end
return current_song
end
function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context)
local special_mode = game_context.special_mode
if special_mode == "normal" then
Audio.sfx_arrowhit(arrow.note)
elseif special_mode == "only_special" then
if arrow.special then
Audio.sfx_arrowhit(arrow.note)
game_context.special_mode_counter = game_context.special_mode_counter + 1
else
if game_context.special_mode_condition then Audio.sfx_bloop() end
game_context.special_mode_condition = false
end
elseif special_mode == "only_left" then
if arrow.dir == "left" then
Audio.sfx_arrowhit(arrow.note)
game_context.special_mode_counter = game_context.special_mode_counter + 1
if game_context.max_fill <= game_context.bar_fill + game_context.fill_per_hit then
game_context.bar_fill = game_context.bar_fill - game_context.fill_per_hit
end
else
if game_context.special_mode_condition then Audio.sfx_bloop() end
game_context.special_mode_condition = false
end
elseif special_mode == "only_nothing" then
if game_context.special_mode_condition then Audio.sfx_bloop() end
game_context.special_mode_condition = false
end
end
function MinigameDDRWindow.on_end(game_context)
Audio.sfx_select()
game_context.win_timer = Config.timing.minigame_win_duration
local num_special = 0
for _,v in ipairs(game_context.current_song.pattern) do
if game_context.special_mode == "only_left" then
num_special = num_special + ((v.dir == "left" and 1) or 0)
else
num_special = num_special + ((v.special and 1) or 0)
end
end
local was_ok = true
if game_context.special_mode == "normal" then
was_ok = game_context.special_mode_counter == num_special
elseif game_context.special_mode == "only_special" then
was_ok = game_context.special_mode_counter == num_special
elseif game_context.special_mode == "only_left" then
was_ok = game_context.special_mode_counter == num_special
end
game_context.special_mode_condition = game_context.special_mode_condition and was_ok
end
--- Initializes DDR minigame state. --- Initializes DDR minigame state.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @param params table Optional parameters for configuration.<br/> --- @param params table Optional parameters for configuration.<br/>
@@ -64,13 +155,22 @@ end
--- @param[opt] params table Optional parameters for minigame configuration.</br> --- @param[opt] params table Optional parameters for minigame configuration.</br>
function MinigameDDRWindow.start(return_window, song_key, params) function MinigameDDRWindow.start(return_window, song_key, params)
MinigameDDRWindow.init(params) MinigameDDRWindow.init(params)
Audio.music_play_activity_work()
Context.minigame_ddr.return_window = return_window or "game" Context.minigame_ddr.return_window = return_window or "game"
Context.minigame_ddr.debug_song_key = song_key Context.minigame_ddr.debug_song_key = song_key
if song_key and Songs and Songs[song_key] then if song_key and Songs and Songs[song_key] then
Context.minigame_ddr.current_song = Songs[song_key]
Context.minigame_ddr.use_pattern = true Context.minigame_ddr.use_pattern = true
Context.minigame_ddr.pattern_index = 1 Context.minigame_ddr.pattern_index = 1
Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key Context.minigame_ddr.debug_status = "Pattern loaded: " .. song_key
Context.minigame_ddr.current_song = MinigameDDRWindow.prepareSong(
Songs[song_key],
Context.minigame_ddr.generated_length,
Context.minigame_ddr.special_mode
)
else else
Context.minigame_ddr.use_pattern = false Context.minigame_ddr.use_pattern = false
if song_key then if song_key then
@@ -79,12 +179,19 @@ function MinigameDDRWindow.start(return_window, song_key, params)
Context.minigame_ddr.debug_status = "Random mode" Context.minigame_ddr.debug_status = "Random mode"
end end
end end
if not Context.test_mode then
Context.minigame_ddr.debug_status = ""
end
Window.set_current("minigame_ddr") Window.set_current("minigame_ddr")
end end
--- Spawns a random arrow. --- Spawns a random arrow.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
local function spawn_arrow() local function spawn_arrow()
trace("random arrow")
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
local target = mg.target_arrows[math.random(1, 4)] local target = mg.target_arrows[math.random(1, 4)]
table.insert(mg.arrows, { table.insert(mg.arrows, {
@@ -97,14 +204,16 @@ end
--- Spawns an arrow in a specific direction. --- Spawns an arrow in a specific direction.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @param direction string The direction of the arrow ("left", "down", "up", "right"). --- @param direction string The direction of the arrow ("left", "down", "up", "right").
local function spawn_arrow_dir(direction) local function spawn_arrow_dir(direction, note, special)
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
for _, target in ipairs(mg.target_arrows) do for _, target in ipairs(mg.target_arrows) do
if target.dir == direction then if target.dir == direction then
table.insert(mg.arrows, { table.insert(mg.arrows, {
dir = direction, dir = direction,
x = target.x, x = target.x,
y = mg.bar_y + mg.bar_height + 10 y = mg.bar_y + mg.bar_height + 10,
note = note,
special = special
}) })
break break
end end
@@ -162,9 +271,10 @@ function MinigameDDRWindow.update()
if mg.win_timer > 0 then if mg.win_timer > 0 then
mg.win_timer = mg.win_timer - 1 mg.win_timer = mg.win_timer - 1
if mg.win_timer == 0 then if mg.win_timer == 0 then
Audio.music_stop()
Meter.on_minigame_complete() Meter.on_minigame_complete()
if mg.on_win then if mg.on_win then
mg.on_win() mg.on_win(mg)
else else
Meter.show() Meter.show()
Window.set_current(mg.return_window) Window.set_current(mg.return_window)
@@ -174,22 +284,26 @@ function MinigameDDRWindow.update()
end end
if mg.bar_fill >= mg.max_fill then if mg.bar_fill >= mg.max_fill then
mg.win_timer = Config.timing.minigame_win_duration MinigameDDRWindow.on_end(mg)
return return
end end
mg.frame_counter = mg.frame_counter + 1 mg.frame_counter = mg.frame_counter + 1
if mg.use_pattern and mg.current_song and mg.current_song.end_frame then if mg.use_pattern and mg.current_song and mg.current_song.end_frame then
if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then
mg.win_timer = Config.timing.minigame_win_duration MinigameDDRWindow.on_end(mg)
return return
end end
end end
if mg.use_pattern and mg.current_song and mg.current_song.pattern then if mg.use_pattern and mg.current_song and mg.current_song.pattern then
local pattern = mg.current_song.pattern local pattern = mg.current_song.pattern
while mg.pattern_index <= #pattern do while mg.pattern_index <= #pattern do
local spawn_entry = pattern[mg.pattern_index] local spawn_entry = pattern[mg.pattern_index]
if mg.frame_counter >= spawn_entry.frame then if mg.frame_counter >= spawn_entry.frame then
spawn_arrow_dir(spawn_entry.dir) spawn_arrow_dir(spawn_entry.dir, spawn_entry.note, spawn_entry.special)
mg.pattern_index = mg.pattern_index + 1 mg.pattern_index = mg.pattern_index + 1
else else
break break
@@ -202,6 +316,8 @@ function MinigameDDRWindow.update()
mg.arrow_spawn_timer = 0 mg.arrow_spawn_timer = 0
end end
end end
-- move arrow downwards
local arrows_to_remove = {} local arrows_to_remove = {}
for i, arrow in ipairs(mg.arrows) do for i, arrow in ipairs(mg.arrows) do
arrow.y = arrow.y + mg.arrow_fall_speed arrow.y = arrow.y + mg.arrow_fall_speed
@@ -211,28 +327,40 @@ function MinigameDDRWindow.update()
if mg.bar_fill < 0 then if mg.bar_fill < 0 then
mg.bar_fill = 0 mg.bar_fill = 0
end end
mg.total_misses = mg.total_misses + 1
end end
end end
-- iterate backwards to avoid index shift issues -- iterate backwards to avoid index shift issues
for i = #arrows_to_remove, 1, -1 do for i = #arrows_to_remove, 1, -1 do
table.remove(mg.arrows, arrows_to_remove[i]) table.remove(mg.arrows, arrows_to_remove[i])
end end
for dir, _ in pairs(mg.input_cooldowns) do for dir, _ in pairs(mg.input_cooldowns) do
if mg.input_cooldowns[dir] > 0 then if mg.input_cooldowns[dir] > 0 then
mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1 mg.input_cooldowns[dir] = mg.input_cooldowns[dir] - 1
end end
end end
for dir, _ in pairs(mg.button_pressed_timers) do for dir, _ in pairs(mg.button_pressed_timers) do
if mg.button_pressed_timers[dir] > 0 then if mg.button_pressed_timers[dir] > 0 then
mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1 mg.button_pressed_timers[dir] = mg.button_pressed_timers[dir] - 1
end end
end end
local input_map = { local input_map = {
left = Input.left(), left = Input.left(),
down = Input.down(), down = Input.down(),
up = Input.up(), up = Input.up(),
right = Input.right() right = Input.right()
} }
for _, target in ipairs(mg.target_arrows) do
if Mouse.zone({ x = target.x, y = mg.target_y, w = mg.arrow_size, h = mg.arrow_size }) then
input_map[target.dir] = true
end
end
for dir, pressed in pairs(input_map) do for dir, pressed in pairs(input_map) do
if pressed and mg.input_cooldowns[dir] == 0 then if pressed and mg.input_cooldowns[dir] == 0 then
mg.input_cooldowns[dir] = mg.input_cooldown_duration mg.input_cooldowns[dir] = mg.input_cooldown_duration
@@ -240,6 +368,8 @@ function MinigameDDRWindow.update()
local hit = false local hit = false
for i, arrow in ipairs(mg.arrows) do for i, arrow in ipairs(mg.arrows) do
if arrow.dir == dir and check_hit(arrow) then if arrow.dir == dir and check_hit(arrow) then
MinigameDDRWindow.on_arrow_hit_special(arrow, mg)
mg.bar_fill = mg.bar_fill + mg.fill_per_hit mg.bar_fill = mg.bar_fill + mg.fill_per_hit
if mg.bar_fill > mg.max_fill then if mg.bar_fill > mg.max_fill then
mg.bar_fill = mg.max_fill mg.bar_fill = mg.max_fill
@@ -254,6 +384,7 @@ function MinigameDDRWindow.update()
if mg.bar_fill < 0 then if mg.bar_fill < 0 then
mg.bar_fill = 0 mg.bar_fill = 0
end end
mg.total_misses = mg.total_misses + 1
end end
end end
end end
@@ -272,13 +403,14 @@ function MinigameDDRWindow.draw()
end end
return return
end end
if mg.return_window == "game" then -- if mg.return_window == "game" then
GameWindow.draw() -- GameWindow.draw()
end -- end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey) rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey)
rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey) rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey)
local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width
MinigameDDRWindow.draw_background()
if fill_width > 0 then if fill_width > 0 then
local bar_color = Config.colors.light_blue local bar_color = Config.colors.light_blue
if mg.bar_fill > 66 then if mg.bar_fill > 66 then
@@ -299,7 +431,8 @@ function MinigameDDRWindow.draw()
end end
if mg.arrows then if mg.arrows then
for _, arrow in ipairs(mg.arrows) do for _, arrow in ipairs(mg.arrows) do
draw_arrow(arrow.x, arrow.y, arrow.dir, Config.colors.blue) local arrow_color = arrow.special and Config.colors.white or Config.colors.blue
draw_arrow(arrow.x, arrow.y, arrow.dir, arrow_color)
end end
end end
Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) Print.text_center("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey)
@@ -308,7 +441,7 @@ function MinigameDDRWindow.draw()
Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item) Print.text_center(mg.debug_status, Config.screen.width / 2, debug_y, Config.colors.item)
debug_y = debug_y + 10 debug_y = debug_y + 10
end end
if mg.use_pattern then if mg.use_pattern and Context.test_mode then
Print.text_center( Print.text_center(
"PATTERN MODE - Frame:" .. mg.frame_counter, "PATTERN MODE - Frame:" .. mg.frame_counter,
Config.screen.width / 2, Config.screen.width / 2,
@@ -323,10 +456,16 @@ function MinigameDDRWindow.draw()
Config.colors.light_blue Config.colors.light_blue
) )
end end
else elseif Context.test_mode then
Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue) Print.text_center("RANDOM MODE", Config.screen.width / 2, debug_y, Config.colors.blue)
end end
if mg.win_timer > 0 then if mg.win_timer > 0 then
Minigame.draw_win_overlay() if mg.special_mode_condition then
Minigame.draw_win_overlay("SUCCESS...?")
elseif mg.total_hits < 10 then
Minigame.draw_win_overlay("MEH...")
else
Minigame.draw_win_overlay()
end
end
end end
end

View File

@@ -70,6 +70,9 @@ function MinigameButtonMashWindow.update()
if mg.win_timer == 0 then if mg.win_timer == 0 then
Meter.on_minigame_complete() Meter.on_minigame_complete()
if mg.focus_center_x then Focus.stop() end if mg.focus_center_x then Focus.stop() end
Context.home_norman_visible = true
Context.have_done_work_today = false
Context.have_been_to_office = false
if mg.on_win then if mg.on_win then
mg.on_win() mg.on_win()
else else
@@ -80,7 +83,11 @@ function MinigameButtonMashWindow.update()
return return
end end
if Input.select() then local mouse_on_button = Mouse.zone_circle({ x = mg.button_x, y = mg.button_y, r = mg.button_size })
if Input.select() or mouse_on_button then
Audio.sfx_drum_high()
mg.bar_fill = mg.bar_fill + mg.fill_per_press mg.bar_fill = mg.bar_fill + mg.fill_per_press
mg.button_pressed_timer = mg.button_press_duration mg.button_pressed_timer = mg.button_press_duration
if mg.bar_fill > mg.target_points then if mg.bar_fill > mg.target_points then
@@ -88,6 +95,7 @@ function MinigameButtonMashWindow.update()
end end
end end
if mg.bar_fill >= mg.target_points then if mg.bar_fill >= mg.target_points then
Audio.sfx_select()
mg.win_timer = Config.timing.minigame_win_duration mg.win_timer = Config.timing.minigame_win_duration
return return
end end

View File

@@ -95,7 +95,9 @@ function MinigameRhythmWindow.update()
if mg.press_cooldown > 0 then if mg.press_cooldown > 0 then
mg.press_cooldown = mg.press_cooldown - 1 mg.press_cooldown = mg.press_cooldown - 1
end end
if Input.select() and mg.press_cooldown == 0 then local mouse_on_button = Mouse.zone_circle({ x = mg.button_x, y = mg.button_y, r = mg.button_size })
if (Input.select() or mouse_on_button) and mg.press_cooldown == 0 then
mg.button_pressed_timer = mg.button_press_duration mg.button_pressed_timer = mg.button_press_duration
mg.press_cooldown = mg.press_cooldown_duration mg.press_cooldown = mg.press_cooldown_duration
local target_left = mg.target_center - (mg.target_width / 2) local target_left = mg.target_center - (mg.target_width / 2)
@@ -114,6 +116,7 @@ function MinigameRhythmWindow.update()
end end
end end
if mg.score >= mg.max_score then if mg.score >= mg.max_score then
Audio.sfx_select()
mg.win_timer = Config.timing.minigame_win_duration mg.win_timer = Config.timing.minigame_win_duration
return return
end end

View File

@@ -1,198 +0,0 @@
--- @section MysteriousManWindow
local STATE_TEXT = "text"
local STATE_DAY = "day"
local STATE_CHOICE = "choice"
local DEFAULT_TEXT = [[
Misterious man appears
during your sleep.
He says nothing.
He doesn't need to.
He says nothing.
]]
local state = STATE_TEXT
local text_y = Config.screen.height
local text_speed = 0.2
local day_timer = 0
local day_display_frames = 120
local text_done = false
local text_done_timer = 0
local TEXT_DONE_HOLD_FRAMES = 120
local selected_choice = 1
local text = DEFAULT_TEXT
local day_text_override = nil
local on_text_complete = nil
local show_mysterious_screen = true
local trigger_flash_on_wake = false
local choices = {
{
label = "Wake Up",
},
{
label = "Stay in Bed",
},
}
--- Sets the scrolling text content.
--- @within MysteriousManWindow
--- @param new_text string The text to display.
function MysteriousManWindow.set_text(new_text)
text = new_text
end
--- Starts the mysterious man window.
--- @within MysteriousManWindow
--- @param[opt] options table Optional window configuration.</br>
--- Fields: </br>
--- * text (string) Override for the scrolling text.<br/>
--- * day_text (string) Override for the centered day label.<br/>
--- * on_text_complete (function) Callback fired once when the text phase ends.<br/>
--- * skip_text (boolean) If true, skip the text phase and go straight to day display.<br/>
function MysteriousManWindow.start(options)
options = options or {}
day_timer = 0
text_done = false
text_done_timer = 0
selected_choice = 1
text = options.text or DEFAULT_TEXT
local line_count = 1
for _ in string.gmatch(text, "\n") do line_count = line_count + 1 end
local text_block_h = line_count * 8
text_y = math.floor((Config.screen.height - text_block_h) / 2)
day_text_override = options.day_text
on_text_complete = options.on_text_complete
Meter.hide()
trigger_flash_on_wake = not options.skip_text
if options.skip_text then
show_mysterious_screen = false
state = STATE_DAY
day_timer = day_display_frames
else
show_mysterious_screen = true
state = STATE_TEXT
end
Window.set_current("mysterious_man")
end
local function go_to_day_state()
if on_text_complete then
on_text_complete()
on_text_complete = nil
end
if Window.get_current_id() ~= "mysterious_man" then
return
end
state = STATE_DAY
day_timer = day_display_frames
end
local function wake_up()
Context.home_norman_visible = false
Util.go_to_screen_by_id("home")
MinigameButtonMashWindow.start("game", {
focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0,
target_points = 100,
instruction_text = "Wake up Norman!",
show_progress_text = false,
on_win = function()
Audio.music_play_wakingup()
Context.home_norman_visible = true
Meter.show()
if trigger_flash_on_wake then
trigger_flash_on_wake = false
Ascension.start_flash()
end
Window.set_current("game")
end,
})
end
local function stay_in_bed()
Day.increase()
state = STATE_DAY
day_timer = day_display_frames
end
--- Updates the mysterious man window logic.
--- @within MysteriousManWindow
function MysteriousManWindow.update()
if state == STATE_TEXT then
if not text_done then
text_y = text_y - text_speed
local lines = 1
for _ in string.gmatch(text, "\n") do
lines = lines + 1
end
if text_y < -lines * 8 then
text_done = true
text_done_timer = TEXT_DONE_HOLD_FRAMES
end
else
text_done_timer = text_done_timer - 1
if text_done_timer <= 0 then
go_to_day_state()
end
end
elseif state == STATE_DAY then
day_timer = day_timer - 1
if day_timer <= 0 or Input.select() then
if trigger_flash_on_wake or Ascension.get_level() < 1 then
wake_up()
else
state = STATE_CHOICE
selected_choice = 1
end
end
elseif state == STATE_CHOICE then
selected_choice = UI.update_menu(choices, selected_choice)
if Input.select() then
Audio.sfx_select()
if selected_choice == 1 then
wake_up()
else
stay_in_bed()
end
end
end
end
--- Draws the mysterious man window.
--- @within MysteriousManWindow
function MysteriousManWindow.draw()
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
if show_mysterious_screen then
Screen.draw_the_mysterious_screen()
end
if state == STATE_TEXT then
local cx = Config.screen.width / 2
local line_y = text_y
for line in (text .. "\n"):gmatch("(.-)\n") do
Print.text_center(line, cx, line_y, Config.colors.light_grey)
line_y = line_y + 8
end
elseif state == STATE_DAY then
local day_text = day_text_override or ("Day " .. Context.day_count)
Print.text_center(
day_text,
Config.screen.width / 2,
Config.screen.height / 2 - 3,
Config.colors.white
)
elseif state == STATE_CHOICE then
local menu_x = (Config.screen.width - 60) / 2
local menu_y = (Config.screen.height - 20) / 2
UI.draw_menu(choices, selected_choice, menu_x, menu_y)
end
end

View File

@@ -28,7 +28,7 @@ end
--- @within PopupWindow --- @within PopupWindow
function PopupWindow.update() function PopupWindow.update()
if Context.popup.show then if Context.popup.show then
if Input.menu_confirm() or Input.menu_back() then if Input.select() or Input.back() then
PopupWindow.hide() PopupWindow.hide()
end end
end end

View File

@@ -16,8 +16,8 @@ Window.register("game", GameWindow)
PopupWindow = {} PopupWindow = {}
Window.register("popup", PopupWindow) Window.register("popup", PopupWindow)
ConfigurationWindow = {} ControlsWindow = {}
Window.register("configuration", ConfigurationWindow) Window.register("controls", ControlsWindow)
AudioTestWindow = {} AudioTestWindow = {}
Window.register("audiotest", AudioTestWindow) Window.register("audiotest", AudioTestWindow)
@@ -31,9 +31,6 @@ Window.register("minigame_rhythm", MinigameRhythmWindow)
MinigameDDRWindow = {} MinigameDDRWindow = {}
Window.register("minigame_ddr", MinigameDDRWindow) Window.register("minigame_ddr", MinigameDDRWindow)
MysteriousManWindow = {}
Window.register("mysterious_man", MysteriousManWindow)
EndWindow = {} EndWindow = {}
Window.register("end", EndWindow) Window.register("end", EndWindow)

View File

@@ -0,0 +1,46 @@
from mido import MidiFile
MIDI_FILE = "/tmp/teletype_impostor_musicator/maestro-v3.0.0/2018/MIDI-Unprocessed_Schubert7-9_MID--AUDIO_16_R2_2018_wav.midi"
# resolution: rows per beat (e.g. 4 = 16th notes)
ROWS_PER_BEAT = 4
names = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
def note_name(n):
octave = n // 12 - 1
return f"{names[n % 12]}-{octave}"
mid = MidiFile(MIDI_FILE)
tpb = mid.ticks_per_beat
row_ticks = tpb // ROWS_PER_BEAT
time = 0
rows = {}
for msg in mid:
time += msg.time
if msg.type == "note_on" and msg.velocity > 0:
row = int(time // row_ticks)
rows.setdefault(row, []).append(msg.note)
# build monophonic sequence (highest note wins)
max_row = max(rows.keys())
sequence = []
for r in range(max_row + 1):
if r in rows:
n = max(rows[r])
sequence.append(note_name(n))
else:
sequence.append("...")
# trim (optional)
sequence = sequence[:512]
# output as Lua
print("sequence = {")
for n in sequence:
print(f' "{n}",')
print("}")

View File

@@ -1,130 +1,127 @@
unpack = unpack or table.unpack -- key separator: |
-- empty note: "..."
function build_markov_model(sequence, order) math.randomseed(os.time())
local function make_key(tbl)
return table.concat(tbl, "|") local unpack = unpack or table.unpack
local function make_key(tbl)
return table.concat(tbl, "|")
end
local function unmake_key(k)
local result = {}
for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t
end end
local function unmake_key(k) return result
local result = {} end
for t in string.gmatch(k, "[^|]+") do
result[#result + 1] = t local function add_key(str, value)
return str .. "|" .. value
end
local function split_last(full)
local i = full:match(".*()|")
return full:sub(1, i-1), full:sub(i+1)
end
local function has_value (tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end end
return result return false
end end
local function add_key(str, value) -- helper: split key into parts
return str .. "|" .. value local function split(k)
local t = {}
for part in string.gmatch(k, "[^|]+") do
t[#t+1] = part
end end
return t
end
local function split_last(full) function build_markov_model(sequence, order)
local i = full:match(".*()|") -- TODO: add {"..." x order} to beginning?
return full:sub(1, i-1), full:sub(i+1)
end
local counts = {} local model = { }
local totals = {}
-- count -- count
for i = 1, #sequence - order do for i = 1, #sequence - order do
local notes = make_key({unpack(sequence, i, i + order - 1)}) local key = make_key({unpack(sequence, i, i + order - 1)})
totals[notes] = (totals[notes] or 0) + 1 local next_note = sequence[i + order]
local notes_full = add_key(notes, sequence[i + order]) local data = model[key] or { next={}, total=0 }
counts[notes_full] = (counts[notes_full] or 0) + 1
data.next[next_note] = (data.next[next_note] or 0) + 1
data.total = data.total + 1
model[key] = data
end end
-- build model -- normalize
local model = {} for temp_key,temp_data in pairs(model) do
for temp_note, temp_count in pairs(temp_data.next) do
for notes_full,count in pairs(counts) do model[temp_key].next[temp_note] = temp_count / temp_data.total
local notes, _ = split_last(notes_full) end
model[notes_full] = count[notes_full] / total[notes]
end end
--[[
for k,v in pairs(model) do
print("-----" .. k)
for k2,v2 in pairs(v.next) do
print(k2, v2)
end
end
--]]
return { return {
order = order, order = order,
model = model, model = model
counts = counts -- keep raw counts (useful!)
} }
end end
function generate_sequence(model_data, length) function generate_sequence(model_data, length)
local model = model_data.model
local order = model_data.order local order = model_data.order
local model_data = model_data.model
-- helper: split key into parts -- random start key
local function split(k) local model_keys = {}
local t = {}
for part in string.gmatch(k, "[^|]+") do
t[#t+1] = part
end
return t
end
-- pick random starting state
local start_key
for k,_ in pairs(model) do for k,_ in pairs(model) do
start_key = k model_keys[#model_keys + 1] = k
break
end end
local start_key = model_keys[math.ceil(math.random() * #model_keys)]
-- (optional: better random start) -- sequence starts with the start key
for k,_ in pairs(model) do local seq = unmake_key(start_key)
if math.random() < 0.1 then
start_key = k
end
end
local parts = split(start_key)
-- initial sequence = first `order` items
local seq = {}
for i = 1, order do
seq[i] = parts[i]
end
-- generation loop -- generation loop
while #seq < length do while #seq < length do
-- build current state key local current_key = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
local state = table.concat({unpack(seq, #seq - order + 1, #seq)}, "|")
-- collect matching transitions local chosen = "..."
local matches = {}
for full,prob in pairs(model) do local key_data = model[current_key]
if full:sub(1, #state) == state and full:sub(#state+1, #state+1) == "|" then if key_data then
matches[#matches+1] = {key=full, prob=prob} local r = math.random()
local prob_sum = 0.0
for new_note, new_prob in pairs(key_data.next) do
prob_sum = prob_sum + new_prob
if prob_sum < r then
chosen = new_note
end
end end
end end
if #matches == 0 then break end -- print(current_key .. " --> " .. chosen)
-- weighted pick seq[#seq+1] = chosen
local r = math.random()
local sum = 0
local chosen
for _,m in ipairs(matches) do
sum = sum + m.prob
if r <= sum then
chosen = m.key
break
end
end
if not chosen then
chosen = matches[#matches].key
end
-- extract next symbol (after last '|')
local next_symbol = chosen:match("|([^|]+)$")
seq[#seq+1] = next_symbol
end end
return seq return seq
end end
-- todo: feed samples

View File

@@ -1 +0,0 @@
-- todo

View File

@@ -0,0 +1,42 @@
import sys
import re
ROWS_PER_BEAT = 4 # keep consistent with your MIDI extraction
SOUND = "piano"
def parse_sequence(text):
return re.findall(r'"([^"]+)"', text)
def to_strudel_notes(seq):
out = []
for n in seq:
if n == "..." or n == "---":
out.append("~")
else:
# C-5 → c5, C#5 → c#5
note = n.replace("-", "")
out.append(note.lower())
return out
def chunk(seq, size):
for i in range(0, len(seq), size):
yield seq[i:i+size]
# read from stdin
text = sys.stdin.read()
sequence = parse_sequence(text)
notes = to_strudel_notes(sequence)
# group into musical lines (4 beats)
lines = []
for group in chunk(notes, ROWS_PER_BEAT * 4):
lines.append(" ".join(group))
pattern = "\n".join(lines)
print("note(`")
print(pattern)
print(f"`).sound(\"{SOUND}\")")
# npm install -g strudel-cli

414
tools/musicator/teach.lua Normal file
View File

@@ -0,0 +1,414 @@
-- teach the musicator
-- uses samples from: https://magenta.tensorflow.org/datasets/maestro#v300
--require("luarocks.loader")
--require("luamidi")
require("./musicator")
local inspect = require("inspect")
math.randomseed(os.time())
function flatten(v)
local res = {}
local function flatten(v)
if type(v) ~= "table" then
table.insert(res, v)
return
end
for _, v in ipairs(v) do
flatten(v)
end
end
flatten(v)
return res
end
local training_data = {
-- simple ascending phrase
{
"C-4","...","D-4","...","E-4","...","G-4","...",
"E-4","...","D-4","...","C-4","...","...","..."
},
-- descending answer
{
"G-4","...","F-4","...","E-4","...","D-4","...",
"C-4","...","D-4","...","E-4","...","...","..."
},
-- arpeggio major
{
"C-4","...","E-4","...","G-4","...","C-5","...",
"G-4","...","E-4","...","C-4","...","...","..."
},
-- arpeggio minor
{
"A-4","...","C-5","...","E-5","...","A-5","...",
"E-5","...","C-5","...","A-4","...","...","..."
},
-- stepwise melody (folk-like)
{
"D-4","...","E-4","...","F-4","...","G-4","...",
"F-4","...","E-4","...","D-4","...","...","..."
},
-- repeated note rhythm
{
"C-5","C-5","...","C-5","...","C-5","C-5","...",
"D-5","...","E-5","...","...","...","...","..."
},
-- bounce pattern
{
"C-5","...","G-4","...","C-5","...","G-4","...",
"D-5","...","A-4","...","D-5","...","...","..."
},
-- scale run up
{
"C-4","D-4","E-4","F-4","G-4","A-4","B-4","C-5",
"...","...","...","...","...","...","...","..."
},
-- scale run down
{
"C-5","B-4","A-4","G-4","F-4","E-4","D-4","C-4",
"...","...","...","...","...","...","...","..."
},
-- syncopated feel
{
"C-5","...","...","D-5","...","...","E-5","...",
"C-5","...","...","G-4","...","...","...","..."
},
-- triplet-ish feel (simulated)
{
"E-5","D-5","C-5","...","E-5","D-5","C-5","...",
"G-4","...","...","...","...","...","...","..."
},
-- small jumps
{
"C-5","...","E-5","...","D-5","...","F-5","...",
"E-5","...","C-5","...","...","...","...","..."
},
-- call
{
"G-4","...","A-4","...","C-5","...","A-4","...",
"...","...","...","...","...","...","...","..."
},
-- response
{
"E-4","...","F-4","...","G-4","...","F-4","...",
"D-4","...","...","...","...","...","...","..."
},
-- denser pattern (DDR-like)
{
"C-5","D-5","E-5","...","D-5","E-5","F-5","...",
"E-5","D-5","C-5","...","...","...","...","..."
},
-- alternating pattern (good for gameplay)
{
"C-5","...","E-5","...","C-5","...","E-5","...",
"D-5","...","F-5","...","D-5","...","...","..."
},
-- higher register variant
{
"G-5","...","A-5","...","B-5","...","D-6","...",
"B-5","...","A-5","...","G-5","...","...","..."
},
-- low register grounding
{
"C-4","...","G-3","...","C-4","...","G-3","...",
"F-3","...","C-4","...","...","...","...","..."
},
-- variant of ascending with offset
{
"...","C-4","...","D-4","...","E-4","...","G-4",
"...","E-4","...","D-4","...","C-4","...","..."
},
-- staggered rhythm
{
"C-4","...","...","D-4","...","E-4","...","...",
"G-4","...","E-4","...","D-4","...","...","..."
},
-- broken arpeggio (different spacing)
{
"C-4","E-4","...","G-4","...","C-5","...",
"G-4","E-4","...","C-4","...","...","...","..."
},
-- minor variation (shifted)
{
"...","A-4","C-5","...","E-5","...","A-5","...",
"E-5","...","C-5","...","A-4","...","...","..."
},
-- repeated + variation
{
"D-4","D-4","...","E-4","...","F-4","F-4","...",
"G-4","...","F-4","...","E-4","...","...","..."
},
-- zig-zag motion
{
"C-5","...","E-5","...","D-5","...","F-5","...",
"E-5","...","G-5","...","F-5","...","...","..."
},
-- alternating step/jump
{
"C-5","...","D-5","...","G-5","...","F-5","...",
"E-5","...","C-5","...","D-5","...","...","..."
},
-- denser burst pattern
{
"C-5","D-5","E-5","F-5","...","E-5","D-5","C-5",
"...","...","...","...","...","...","...","..."
},
-- rolling pattern
{
"E-5","...","D-5","...","C-5","...","D-5","...",
"E-5","...","G-5","...","E-5","...","...","..."
},
-- syncopation variant
{
"...","C-5","...","...","E-5","...","...","G-5",
"...","E-5","...","C-5","...","...","...","..."
},
-- low-high interplay
{
"C-4","...","G-4","...","C-5","...","G-4","...",
"E-4","...","C-4","...","...","...","...","..."
},
-- descending but staggered
{
"C-5","...","...","B-4","...","A-4","...","...",
"G-4","...","F-4","...","E-4","...","...","..."
},
-- small trill-like feel
{
"E-5","F-5","E-5","...","E-5","F-5","E-5","...",
"D-5","...","C-5","...","...","...","...","..."
},
-- call variant (shifted timing)
{
"...","G-4","...","A-4","...","C-5","...","A-4",
"...","...","...","...","...","...","...","..."
},
-- response variant
{
"...","E-4","...","F-4","...","G-4","...","F-4",
"D-4","...","...","...","...","...","...","..."
},
-- dense DDR-ish alternating
{
"C-5","...","D-5","...","C-5","...","D-5","...",
"E-5","...","F-5","...","E-5","...","...","..."
},
-- higher variation arpeggio
{
"G-5","...","B-5","...","D-6","...","G-6","...",
"D-6","...","B-5","...","G-5","...","...","..."
},
-- low groove pattern
{
"C-3","...","C-4","...","G-3","...","C-4","...",
"F-3","...","C-4","...","...","...","...","..."
},
-- slightly chaotic (good for branching)
{
"C-5","...","E-5","D-5","...","G-5","...","F-5",
"...","D-5","...","C-5","...","...","...","..."
},
-- mixed density
{
"C-5","D-5","...","E-5","...","F-5","G-5","...",
"E-5","...","D-5","C-5","...","...","...","..."
},
-- offset staircase up
{
"...","C-4","D-4","E-4","F-4","G-4","A-4","B-4",
"C-5","...","...","...","...","...","...","..."
},
-- offset staircase down
{
"...","C-5","B-4","A-4","G-4","F-4","E-4","D-4",
"C-4","...","...","...","...","...","...","..."
},
-- dense zigzag
{
"C-5","E-5","D-5","F-5","E-5","G-5","F-5","A-5",
"G-5","...","...","...","...","...","...","..."
},
-- jack pattern (DDR classic)
{
"C-5","C-5","C-5","...","C-5","C-5","...","...",
"D-5","D-5","...","...","...","...","...","..."
},
-- alternating two-note burst
{
"C-5","D-5","C-5","D-5","C-5","D-5","...","...",
"E-5","F-5","E-5","F-5","...","...","...","..."
},
-- wide jumps
{
"C-4","...","G-5","...","D-4","...","A-5","...",
"E-4","...","B-5","...","...","...","...","..."
},
-- rolling triplet-ish
{
"C-5","E-5","G-5","...","E-5","C-5","E-5","...",
"G-5","...","...","...","...","...","...","..."
},
-- syncopated dense
{
"...","C-5","D-5","...","E-5","...","F-5","G-5",
"...","E-5","...","C-5","...","...","...","..."
},
-- mirrored pattern
{
"C-5","D-5","E-5","F-5","E-5","D-5","C-5","...",
"...","...","...","...","...","...","...","..."
},
-- chord-outline arpeggio feel
{
"C-4","...","G-4","...","E-5","...","G-4","...",
"C-4","...","...","...","...","...","...","..."
},
-- broken rhythm variant
{
"C-5","...","D-5","E-5","...","F-5","...","G-5",
"E-5","...","D-5","...","C-5","...","...","..."
},
-- fast burst then rest
{
"C-5","D-5","E-5","F-5","G-5","A-5","...","...",
"...","...","...","...","...","...","...","..."
},
-- low-high bounce fast
{
"C-3","C-5","C-3","C-5","C-3","C-5","...","...",
"G-3","G-5","G-3","G-5","...","...","...","..."
},
-- repeated with shift
{
"...","E-5","...","E-5","...","E-5","...","...",
"D-5","...","C-5","...","...","...","...","..."
},
-- clustered mid
{
"D-5","E-5","F-5","...","E-5","D-5","C-5","...",
"D-5","...","...","...","...","...","...","..."
},
-- broken descending
{
"C-6","...","A-5","...","F-5","...","D-5","...",
"C-5","...","...","...","...","...","...","..."
},
-- chaotic jumpy (important for branching)
{
"C-5","...","F-5","D-5","...","A-5","...","E-5",
"...","G-5","...","C-5","...","...","...","..."
},
-- double-step pattern
{
"C-5","D-5","D-5","E-5","E-5","F-5","...","...",
"G-5","...","...","...","...","...","...","..."
},
-- uneven spacing
{
"C-5","...","...","D-5","E-5","...","...","F-5",
"G-5","...","...","...","...","...","...","..."
},
-- fast alternating high
{
"G-5","A-5","G-5","A-5","G-5","A-5","...","...",
"F-5","E-5","...","...","...","...","...","..."
},
-- low groove with variation
{
"C-3","...","C-4","...","G-3","...","D-4","...",
"F-3","...","C-4","...","...","...","...","..."
},
-- semi-random filler (very useful)
{
"C-5","...","E-5","...","D-5","...","G-5","...",
"F-5","...","A-5","...","E-5","...","...","..."
},
-- near-repetition (state collision booster)
{
"C-5","...","D-5","...","E-5","...","C-5","...",
"D-5","...","E-5","...","C-5","...","...","..."
},
-- same but shifted (VERY important)
{
"...","C-5","...","D-5","...","E-5","...","C-5",
"...","D-5","...","E-5","...","C-5","...","..."
},
}
local model = build_markov_model(flatten(training_data), 2)
print(inspect(model))
--[[
local generated = generate_sequence(model, 100)
for i,v in ipairs(generated) do
print(v)
end
--]]