Compare commits

..

31 Commits

Author SHA1 Message Date
48d65424a0 Merge branch 'develop' into codegenerator
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-27 22:58:03 +02:00
47e5e0ca17 codegenerator
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-27 22:54:50 +02:00
b5dd0cc686 Merge pull request 'feature/imp-139-gameover-and-meters' (#56) from feature/imp-139-gameover-and-meters into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #56
2026-04-27 20:39:48 +00:00
Zoltan Timar
340d0ff78c Merge branch 'develop' into feature/imp-139-gameover-and-meters
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-27 22:28:08 +02:00
Zoltan Timar
7df42dd2cd feat: added game over screen, fixed bar filling on ddr, applied tamagochi logic to game
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-04-27 22:26:16 +02:00
5d4aa1ee26 Merge pull request 'credit window' (#55) from credits-window into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #55
2026-04-27 19:23:47 +00:00
4ba02c894c credit window
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-27 21:22:38 +02:00
98fd6981cc Merge pull request 'Drawing both sides of the home screen.' (#54) from feature/task106_home_screen into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #54
2026-04-27 16:24:33 +00:00
3ed06353b8 Drawing both sides of the home screen.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-27 18:22:41 +02:00
0b8e2368ec set version to 1.0-beta3
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 21:55:48 +02:00
ff96ca963d Merge pull request 'feature/task110_sprite_silhouette' (#51) from feature/task110_sprite_silhouette into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #51
2026-04-09 19:47:28 +00:00
ae3415b417 main menu color fix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 21:44:52 +02:00
ce819eae2b Merge branch 'develop' into feature/task110_sprite_silhouette
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 21:37:23 +02:00
658e6908b5 Merge pull request '- task #134: randomize placement of characters' (#49) from feature/134-randomized-character-positions into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #49
2026-04-09 19:05:27 +00:00
5ae1eec48a - task #134: randomize placement of characters
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 20:47:59 +02:00
04eb825646 Merge pull request 'IMP-133: re-added have_a_coffee discussion, fixed text readability, fixed centering of texts, new system.ui functions for drawing in contour, fixed ddr types, fixed meter drawing' (#48) from chore/imp-133-improve-readability into develop
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #48
2026-04-09 14:41:43 +00:00
Zoltan Timar
e797377ec1 chore: lint fixes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 16:40:19 +02:00
Zoltan Timar
9d56ca2e7a chore: re-added have_a_coffee discussion, fixed text readability, fixed centering of texts, new system.ui functions for drawing in contour, fixed ddr types, fixed meter drawing
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-04-09 16:36:19 +02:00
9b379d32b3 Corrects the color defects in tiles.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 15:58:54 +02:00
5fc6ae5c14 Draws outlines around the figures.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 15:48:04 +02:00
1e3716196e Removes the setting of the transparent color to fix the linter error.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-09 13:54:43 +02:00
4b5f11969b Changes the Norman's silhouette. Change the background color of the sprites to pink.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-04-09 13:47:24 +02:00
a4a6ad2ab2 Introduces the transparent color. The default is the pink. 2026-04-09 13:46:05 +02:00
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
52 changed files with 1814 additions and 604 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.claude
.local .local
impostor.lua impostor.lua
impostor.original.lua impostor.original.lua
@@ -6,3 +7,4 @@ docs
minify.lua minify.lua
*.tic *.tic
*.zip *.zip
NOTES_*

View File

@@ -10,7 +10,6 @@ globals = {
"Discussion", "Discussion",
"Util", "Util",
"Decision", "Decision",
"Situation",
"Screen", "Screen",
"Sprite", "Sprite",
"UI", "UI",
@@ -25,13 +24,14 @@ globals = {
"Minigame", "Minigame",
"Window", "Window",
"ContinuedWindow", "ContinuedWindow",
"CreditsWindow",
"TTGIntroWindow", "TTGIntroWindow",
"BriefIntroWindow", "BriefIntroWindow",
"TitleIntroWindow", "TitleIntroWindow",
"MenuWindow", "MenuWindow",
"GameWindow", "GameWindow",
"PopupWindow", "PopupWindow",
"ConfigurationWindow", "ControlsWindow",
"AudioTestWindow", "AudioTestWindow",
"MinigameButtonMashWindow", "MinigameButtonMashWindow",
"MinigameRhythmWindow", "MinigameRhythmWindow",
@@ -39,6 +39,11 @@ globals = {
"MysteriousManScreen", "MysteriousManScreen",
"DiscussionWindow", "DiscussionWindow",
"EndWindow", "EndWindow",
"PlayerNameWindow",
"TextInput",
"CodeGenerator",
"CreditsWindow",
"GameOverWindow",
"mset", "mset",
"mget", "mget",
"btnp", "btnp",
@@ -66,6 +71,10 @@ globals = {
"map", "map",
"time", "time",
"RLE", "RLE",
"mouse",
"Mouse",
"print",
"musicator_generate_pattern",
} }

View File

@@ -6,6 +6,8 @@ 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.textinput.lua
system/system.mouse.lua
system/system.asciiart.lua system/system.asciiart.lua
system/system.rle.lua system/system.rle.lua
logic/logic.meter.lua logic/logic.meter.lua
@@ -15,6 +17,7 @@ logic/logic.timer.lua
logic/logic.trigger.lua logic/logic.trigger.lua
logic/logic.minigame.lua logic/logic.minigame.lua
logic/logic.glitch.lua logic/logic.glitch.lua
logic/logic.codegenerator.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
@@ -38,10 +41,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
@@ -50,6 +50,7 @@ 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.have_a_coffee.lua
decision/decision.sumphore_discussion.lua decision/decision.sumphore_discussion.lua
discussion/discussion.sumphore.lua discussion/discussion.sumphore.lua
discussion/discussion.coworker.lua discussion/discussion.coworker.lua
@@ -67,12 +68,13 @@ screen/screen.work.lua
screen/screen.mysterious_man.lua screen/screen.mysterious_man.lua
window/window.manager.lua window/window.manager.lua
window/window.register.lua window/window.register.lua
window/window.gameover.lua
window/window.end.lua window/window.end.lua
window/window.intro.title.lua 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
@@ -80,6 +82,8 @@ window/window.minigame.rhythm.lua
window/window.minigame.ddr.lua window/window.minigame.ddr.lua
window/window.discussion.lua window/window.discussion.lua
window/window.continued.lua window/window.continued.lua
window/window.credits.lua
window/window.player_name.lua
window/window.game.lua window/window.game.lua
system/system.main.lua system/system.main.lua
meta/meta.assets.lua meta/meta.assets.lua

View File

@@ -1,7 +1,23 @@
local function apply_home_toilet_meter_delta()
local max = Meter.get_max()
Meter.add("bm", -math.floor(max * 0.15))
Meter.add("ism", -math.floor(max * 0.10))
Meter.add("wpm", math.floor(max * 0.05))
end
Decision.register({ Decision.register({
id = "go_to_toilet", id = "go_to_toilet",
label = "Go to Toilet", label = "Go to Toilet",
handle = function() handle = function()
if not Context.have_done_work_today and not Context.toilet_meters_today_morning then
apply_home_toilet_meter_delta()
Context.toilet_meters_today_morning = true
elseif Context.have_been_to_office
and Context.have_done_work_today
and not Context.toilet_meters_today_evening then
apply_home_toilet_meter_delta()
Context.toilet_meters_today_evening = true
end
Util.go_to_screen_by_id("toilet") Util.go_to_screen_by_id("toilet")
end, end,
}) })

View File

@@ -2,7 +2,6 @@ Decision.register({
id = "have_a_coffee", id = "have_a_coffee",
label = "Have a Coffee", label = "Have a Coffee",
handle = function() handle = function()
local new_situation_id = Situation.apply("drink_coffee", Context.game.current_screen)
local level = Ascension.get_level() local level = Ascension.get_level()
local disc_id = "coworker_disc_0" local disc_id = "coworker_disc_0"
-- TODO: Add more discussions for levels above 3 -- TODO: Add more discussions for levels above 3
@@ -11,6 +10,5 @@ Decision.register({
disc_id = "coworker_disc" .. suffix disc_id = "coworker_disc" .. suffix
end end
Discussion.start(disc_id, "game") Discussion.start(disc_id, "game")
Context.game.current_situation = new_situation_id
end, end,
}) })

View File

@@ -123,9 +123,13 @@ function Decision.draw(decisions, selected_decision_index)
local selected_decision = decisions[selected_decision_index] local selected_decision = decisions[selected_decision_index]
local decision_label = Decision.get_label(selected_decision) local decision_label = Decision.get_label(selected_decision)
local text_y = bar_y + 4 local text_y = bar_y + 4
Print.text("<", 2, text_y, Config.colors.light_blue) local left_arrow_color = Input.left() and Config.colors.white or Config.colors.orange
Print.text_center(decision_label, Config.screen.width / 2, text_y, Config.colors.item) local right_arrow_color = Input.right() and Config.colors.white or Config.colors.orange
Print.text(">", Config.screen.width - 6, text_y, Config.colors.light_blue) local left_arrow_contour_color = Input.left() and Config.colors.white or Config.colors.black
local right_arrow_contour_color = Input.right() and Config.colors.white or Config.colors.black
Print.text_center_contour("<", 6, text_y, left_arrow_color, false, 1, left_arrow_contour_color)
Print.text_center_contour(decision_label, Config.screen.width / 2, text_y, Config.colors.orange)
Print.text_center_contour(">", Config.screen.width - 6, text_y, right_arrow_color, false, 1, right_arrow_contour_color)
end end
end end
@@ -134,6 +138,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 +147,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

@@ -7,6 +7,9 @@ Decision.register({
focus_center_x = (Config.screen.width / 2) - 22, focus_center_x = (Config.screen.width / 2) - 22,
focus_center_y = (Config.screen.height / 2) - 18, focus_center_y = (Config.screen.height / 2) - 18,
focus_initial_radius = 0, focus_initial_radius = 0,
on_win = function()
Audio.music_play_room_work()
end
}) })
end, end,
}) })

View File

@@ -1,5 +1,6 @@
Discussion.register({ Discussion.register({
id = "coworker_disc_0", id = "coworker_disc_0",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "Good morning Normal, enjoying your coffee as usual, huh?", question = "Good morning Normal, enjoying your coffee as usual, huh?",
@@ -18,6 +19,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "coworker_disc_1", id = "coworker_disc_1",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "Norman, you look confused, what's up?", question = "Norman, you look confused, what's up?",
@@ -36,11 +38,12 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "coworker_disc_asc_1", id = "coworker_disc_asc_1",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?", question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?",
answers = { answers = {
{ label = "Nothing it's just, I noticed some bugs in the simulation, maybe.", next_step = 2 }, { label = "Some bugs I noticed, maybe...", next_step = 2 },
}, },
}, },
{ {
@@ -54,6 +57,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "coworker_disc_2", id = "coworker_disc_2",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "Hey Norman, do you have new socks on? That's a weird color!", question = "Hey Norman, do you have new socks on? That's a weird color!",
@@ -79,6 +83,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "coworker_disc_asc_2", id = "coworker_disc_asc_2",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "Normann, are you ok? You were doing weird things while typing?", question = "Normann, are you ok? You were doing weird things while typing?",
@@ -97,6 +102,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "coworker_disc_3", id = "coworker_disc_3",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "You look so happy, did you catch a bull or something?", question = "You look so happy, did you catch a bull or something?",
@@ -120,6 +126,7 @@ Discussion.register({
}) })
Discussion.register({ Discussion.register({
id = "coworker_disc_asc_3", id = "coworker_disc_asc_3",
on_end = Meter.apply_coworker_discussion_reward,
steps = { steps = {
{ {
question = "Normal, you should take a break, you don't live up to your name today", question = "Normal, you should take a break, you don't live up to your name today",

View File

@@ -1,5 +1,6 @@
Discussion.register({ Discussion.register({
id = "sumphore_disc_asc_1", id = "sumphore_disc_asc_1",
on_end = Meter.apply_sumphore_discussion_reward,
steps = { steps = {
{ {
question = "Are you still seeking the ox?", question = "Are you still seeking the ox?",
@@ -19,6 +20,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "sumphore_disc_asc_2", id = "sumphore_disc_asc_2",
on_end = Meter.apply_sumphore_discussion_reward,
steps = { steps = {
{ {
question = "How's work? Your face looks strange", question = "How's work? Your face looks strange",
@@ -61,6 +63,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "sumphore_disc_asc_3", id = "sumphore_disc_asc_3",
on_end = Meter.apply_sumphore_discussion_reward,
steps = { steps = {
{ {
question = "Do you think it's work you're doing?", question = "Do you think it's work you're doing?",
@@ -88,6 +91,7 @@ Discussion.register({
Discussion.register({ Discussion.register({
id = "homeless_guy", id = "homeless_guy",
on_end = Meter.apply_sumphore_discussion_reward,
steps = { steps = {
{ {
question = "Sup bro, how are you?", question = "Sup bro, how are you?",

View File

@@ -93,7 +93,7 @@ function Ascension.draw(x, y, options)
else else
color = lit_color color = lit_color
end end
print(ch, x + (i - 1) * spacing, y, color, false, 1, true) Print.text_contour(ch, x + (i - 1) * spacing, y, color, false, 1)
end end
end end

View File

@@ -17,7 +17,9 @@ function Config.initial_data()
blue = 3, blue = 3,
white = 4, white = 4,
item = 7, item = 7,
meter_bg = 1 meter_bg = 1,
transparent = 12,
orange = 7
}, },
timing = { timing = {
minigame_win_duration = 180 minigame_win_duration = 180

View File

@@ -23,11 +23,16 @@ Context = {}
--- * 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/>
--- * have_been_to_office (boolean) Whether the player has been to the office.<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/> --- * 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, `current_situation` (string|nil) active situation ID.<br/> --- * toilet_meters_today_morning (boolean) Whether the home (before work) toilet meter delta was already applied this day.<br/>
--- * toilet_meters_today_evening (boolean) Whether the home (after work) toilet meter delta was already applied this day.<br/>
--- * coworker_discussion_meter_applied_today (boolean) Whether the daily coworker discussion meter roll was already applied this day.<br/>
--- * sumphore_discussion_meter_applied_today (boolean) Whether the daily sumphore discussion meter roll was already applied this day.<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,
test_mode = false, test_mode = false,
mouse_trace = false,
popup = { popup = {
show = false, show = false,
content = {} content = {}
@@ -44,11 +49,16 @@ function Context.initial_data()
home_norman_visible = false, home_norman_visible = false,
have_been_to_office = false, have_been_to_office = false,
have_done_work_today = false, have_done_work_today = false,
toilet_meters_today_morning = false,
toilet_meters_today_evening = false,
coworker_discussion_meter_applied_today = false,
sumphore_discussion_meter_applied_today = false,
should_ascend = false, should_ascend = false,
have_met_sumphore = false, have_met_sumphore = false,
office_sprites = {},
walking_to_office_sprites = {},
game = { game = {
current_screen = "home", current_screen = "home",
current_situation = nil,
}, },
day_count = 1, day_count = 1,
delta_time = 0, delta_time = 0,
@@ -100,15 +110,15 @@ function Context.new_game()
MysteriousManScreen.start({ MysteriousManScreen.start({
text = [[ text = [[
Norman was never a bad Norman was never a bad
...
simulation engineer, simulation engineer,
...
but but
...
we need to be careful we need to be careful
...
letting him improve. letting him improve.
...
We need to distract him. We need to distract him.
]], ]],
on_text_complete = function() on_text_complete = function()
@@ -122,8 +132,9 @@ function Context.new_game()
target_points = 100, target_points = 100,
instruction_text = "Wake up Norman!", instruction_text = "Wake up Norman!",
show_progress_text = false, show_progress_text = false,
meter_on_complete = Meter.apply_wakeup_reward,
on_win = function() on_win = function()
Audio.music_play_wakingup() Audio.music_play_room_work()
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 = {}

View File

@@ -0,0 +1,60 @@
--- @section CodeGenerator
CodeGenerator = {}
local SALT = 27471
local BASE36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local NAME_LEN = 3
-- Per-position offsets derived from SALT so each character slot
-- maps to a different region of the 2-char base-36 space.
local SALTS = {
SALT % 36,
math.floor(SALT / 36) % 36,
math.floor(SALT / 1296) % 36,
}
--- Encodes a number (0935) as exactly 2 base-36 characters.
--- @within CodeGenerator
function CodeGenerator.encode_pair(n)
return BASE36:sub(math.floor(n / 36) + 1, math.floor(n / 36) + 1)
.. BASE36:sub(n % 36 + 1, n % 36 + 1)
end
--- Decodes 2 base-36 characters back to a number.
--- @within CodeGenerator
function CodeGenerator.decode_pair(s)
local d1 = BASE36:find(s:sub(1, 1), 1, true) - 1
local d2 = BASE36:find(s:sub(2, 2), 1, true) - 1
return d1 * 36 + d2
end
--- Encrypts a player name into a code twice its length.
--- Each input character (A-Z, value 0-25) is encoded as
--- c + SALTS[i] * 26, producing 2 base-36 output characters.
--- @within CodeGenerator
--- @param text string NAME_LEN-character uppercase player name.
--- @return string Encrypted code (2 * NAME_LEN base-36 characters).
function CodeGenerator.encrypt(text)
local result = ""
for i = 1, NAME_LEN do
local c = math.max(0, (string.byte(text, i) or 65) - 65)
result = result .. CodeGenerator.encode_pair(c + SALTS[i] * 26)
end
return result
end
--- Decrypts a personal code back to the original player name.
--- @within CodeGenerator
--- @param encrypted_text string The code to decrypt (2 * NAME_LEN chars).
--- @return string Original player name, or "???" if the code is invalid.
function CodeGenerator.decrypt(encrypted_text)
local t = encrypted_text:upper()
if #t ~= NAME_LEN * 2 then return "???" end
local result = ""
for i = 1, NAME_LEN do
local pair = CodeGenerator.decode_pair(t:sub((i - 1) * 2 + 1, i * 2))
result = result .. string.char(pair % 26 + 65)
end
return result
end

View File

@@ -8,6 +8,10 @@ function Day.increase()
if Context.day_count == 3 then if Context.day_count == 3 then
Context.should_ascend = true Context.should_ascend = true
end end
if Context.day_count >= 100 and not Ascension.is_complete() then
GameOverWindow.show("days")
return
end
for _, handler in ipairs(_day_increase_handlers) do for _, handler in ipairs(_day_increase_handlers) do
handler() handler()
end end
@@ -27,6 +31,13 @@ Day.register_handler(function()
m.bm = math.max(0, m.bm - METER_DECAY_PER_DAY) m.bm = math.max(0, m.bm - METER_DECAY_PER_DAY)
end) end)
Day.register_handler(function()
Context.toilet_meters_today_morning = false
Context.toilet_meters_today_evening = false
Context.coworker_discussion_meter_applied_today = false
Context.sumphore_discussion_meter_applied_today = false
end)
Day.register_handler(function() Day.register_handler(function()
if Context.should_ascend then if Context.should_ascend then
Ascension.increase() Ascension.increase()

View File

@@ -1,6 +1,8 @@
--- @section Meter --- @section Meter
local METER_MAX = 1000 local METER_MAX = 1000
local METER_DEFAULT = 500 local BM_METER_DEFAULT = 200
local ISM_METER_DEFAULT = 500
local WPM_METER_DEFAULT = 200
local METER_GAIN_PER_CHORE = 100 local METER_GAIN_PER_CHORE = 100
local METER_DECAY_PER_DAY = 20 local METER_DECAY_PER_DAY = 20
local COMBO_BASE_BONUS = 0.02 local COMBO_BASE_BONUS = 0.02
@@ -8,10 +10,11 @@ local COMBO_MAX_BONUS = 0.16
local COMBO_TIMEOUT_FRAMES = 600 local COMBO_TIMEOUT_FRAMES = 600
-- Internal meters for tracking game progress and player stats. -- Internal meters for tracking game progress and player stats.
Meter.COLOR_ISM = Config.colors.red Meter.COLOR_ISM = Config.colors.orange
Meter.COLOR_WPM = Config.colors.blue Meter.COLOR_WPM = Config.colors.blue
Meter.COLOR_BM = Config.colors.black Meter.COLOR_BM = Config.colors.red
Meter.COLOR_BG = Config.colors.meter_bg Meter.COLOR_BG = Config.colors.meter_bg
Meter.COLOR_CONTOUR = Config.colors.white
--- Gets initial meter values. --- Gets initial meter values.
--- @within Meter --- @within Meter
@@ -25,9 +28,9 @@ Meter.COLOR_BG = Config.colors.meter_bg
--- * hidden (boolean) Whether meters are hidden. --- * hidden (boolean) Whether meters are hidden.
function Meter.get_initial() function Meter.get_initial()
return { return {
ism = METER_DEFAULT, ism = ISM_METER_DEFAULT,
wpm = METER_DEFAULT, wpm = WPM_METER_DEFAULT,
bm = METER_DEFAULT, bm = BM_METER_DEFAULT,
combo = 0, combo = 0,
combo_timer = 0, combo_timer = 0,
hidden = false, hidden = false,
@@ -102,22 +105,129 @@ function Meter.add(key, amount)
if not Context or not Context.meters then return end if not Context or not Context.meters then return end
local m = Context.meters local m = Context.meters
if m[key] ~= nil then if m[key] ~= nil then
m[key] = math.min(METER_MAX, m[key] + amount) if amount > 0 and (key == "ism" or key == "bm") and m[key] >= METER_MAX then
GameOverWindow.show(key)
return
end
m[key] = math.max(0, math.min(METER_MAX, m[key] + amount))
end end
end end
--- Called on minigame completion. --- Called on minigame completion.
--- @within Meter --- @within Meter
function Meter.on_minigame_complete() --- @param is_work boolean If true (work-style minigame), apply combo to WPM/ISM/BM and advance combo. DDR uses `Meter.apply_ddr_reward` instead. Otherwise flat equal gain, combo unchanged.
function Meter.on_minigame_complete(is_work)
local m = Context.meters local m = Context.meters
local gain = math.floor(METER_GAIN_PER_CHORE * Meter.get_combo_multiplier()) if is_work then
Meter.add("wpm", gain) local mult = Meter.get_combo_multiplier()
Meter.add("ism", gain) local wpm_delta = math.floor(METER_GAIN_PER_CHORE / mult)
Meter.add("bm", gain) local ism_bm_delta = math.floor(METER_GAIN_PER_CHORE * mult)
Meter.add("wpm", wpm_delta)
Meter.add("ism", ism_bm_delta)
Meter.add("bm", ism_bm_delta)
m.combo = m.combo + 1
m.combo_timer = 0
else
local flat = METER_GAIN_PER_CHORE
Meter.add("wpm", flat)
Meter.add("ism", flat)
Meter.add("bm", flat)
end
end
--- Meter changes after DDR: uses max-meter percentages; combo advances like other work minigames.
--- 0 mistakes: WPM 10%, ISM +5%, BM +5%. 13: WPM 5%, ISM +10%, BM +10%. More than 3: WPM unchanged, ISM +10%, BM +10%.
--- @within Meter
--- @param mistake_count number Total mistakes (missed arrows, wrong inputs, and special-mode rule violations).
function Meter.apply_ddr_reward(mistake_count)
if not Context or not Context.meters then return end
local max = Meter.get_max()
local m = Context.meters
local wpm_pct, ism_pct, bm_pct
if mistake_count == 0 then
wpm_pct, ism_pct, bm_pct = -0.10, 0.05, 0.05
elseif mistake_count <= 3 then
wpm_pct, ism_pct, bm_pct = -0.05, 0.10, 0.10
else
wpm_pct, ism_pct, bm_pct = 0, 0.10, 0.10
end
if wpm_pct ~= 0 then
Meter.add("wpm", math.floor(max * wpm_pct))
end
if ism_pct ~= 0 then
Meter.add("ism", math.floor(max * ism_pct))
end
if bm_pct ~= 0 then
Meter.add("bm", math.floor(max * bm_pct))
end
m.combo = m.combo + 1 m.combo = m.combo + 1
m.combo_timer = 0 m.combo_timer = 0
end end
--- Meter changes for the wake-up button mash: faster completion is better for WPM.
--- Perfect: under 2s — WPM +20%. Good: 23s — WPM +10%, ISM +5%, BM +5%. Bad: over 3s — WPM 5%, ISM +10%, BM +10%.
--- @within Meter
--- @param elapsed_sec number Seconds from minigame start until the bar was filled.
function Meter.apply_wakeup_reward(elapsed_sec)
if not Context or not Context.meters then return end
local max = Meter.get_max()
local wpm_pct, ism_pct, bm_pct
if elapsed_sec < 2 then
wpm_pct, ism_pct, bm_pct = 0.20, 0, 0
elseif elapsed_sec <= 3 then
wpm_pct, ism_pct, bm_pct = 0.10, 0.05, 0.05
else
wpm_pct, ism_pct, bm_pct = -0.05, 0.10, 0.10
end
if wpm_pct ~= 0 then
Meter.add("wpm", math.floor(max * wpm_pct))
end
if ism_pct ~= 0 then
Meter.add("ism", math.floor(max * ism_pct))
end
if bm_pct ~= 0 then
Meter.add("bm", math.floor(max * bm_pct))
end
end
--- Random single meter shift after finishing a coworker discussion: ISM +10%, WPM 10%, or BM +10%.
--- @within Meter
function Meter.apply_coworker_discussion_reward()
if not Context or not Context.meters then return end
if Context.coworker_discussion_meter_applied_today then return end
local max = Meter.get_max()
local delta = math.floor(max * 0.10)
local roll = math.random(1, 3)
if roll == 1 then
Meter.add("ism", delta)
elseif roll == 2 then
Meter.add("wpm", -delta)
else
Meter.add("bm", delta)
end
Context.coworker_discussion_meter_applied_today = true
end
--- After finishing a sumphore discussion: reduce whichever of ISM / WPM / BM is highest by 10% of max (stable tie to ISM, then WPM, then BM).
--- @within Meter
function Meter.apply_sumphore_discussion_reward()
if not Context or not Context.meters then return end
if Context.sumphore_discussion_meter_applied_today then return end
local m = Context.meters
local max = Meter.get_max()
local delta = math.floor(max * 0.10)
local biggest_val_key = "ism"
local biggest_val = m.ism
for _, key in ipairs({ "wpm", "bm" }) do
if m[key] > biggest_val then
biggest_val = m[key]
biggest_val_key = key
end
end
Meter.add(biggest_val_key, -delta)
Context.sumphore_discussion_meter_applied_today = true
end
--- Draws meters. --- Draws meters.
--- @within Meter --- @within Meter
function Meter.draw() function Meter.draw()
@@ -126,16 +236,16 @@ function Meter.draw()
local m = Context.meters local m = Context.meters
local max = Meter.get_max() local max = Meter.get_max()
local bar_w = 44 local screen_w = Config.screen.width
local screen_h = Config.screen.height
local bar_w = screen_w * 0.25
local bar_h = 2 local bar_h = 2
local bar_x = 182 local edge = math.max(2, math.floor(screen_w * 0.03))
local label_x = 228 local bar_x = screen_w - bar_w - edge
local line_h = 5 local line_h = 3
local start_y = 1 local start_y = screen_h * 0.05
local bar_offset = math.floor((line_h - bar_h) / 2)
local meter_list = { local meter_list = {
{ key = "wpm", label = "WPM", color = Meter.COLOR_WPM, row = 0 }, { key = "wpm", label = "WPM", color = Meter.COLOR_WPM, row = 0 },
{ key = "ism", label = "ISM", color = Meter.COLOR_ISM, row = 1 }, { key = "ism", label = "ISM", color = Meter.COLOR_ISM, row = 1 },
@@ -144,15 +254,16 @@ function Meter.draw()
for _, meter in ipairs(meter_list) do for _, meter in ipairs(meter_list) do
local label_y = start_y + meter.row * line_h local label_y = start_y + meter.row * line_h
local bar_y = label_y + bar_offset local bar_y = label_y
local fill_w = math.max(0, math.floor((m[meter.key] / max) * bar_w)) local fill_w = math.max(0, math.floor((m[meter.key] / max) * bar_w))
rect(bar_x - 1, bar_y - 1, bar_w + 2, bar_h + 2, Meter.COLOR_CONTOUR)
rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG) rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG)
if fill_w > 0 then if fill_w > 0 then
rect(bar_x, bar_y, fill_w, bar_h, meter.color) rect(bar_x, bar_y, fill_w, bar_h, meter.color)
end end
print(meter.label, label_x, label_y, meter.color, false, 1, true) ---print(meter.label, label_x, label_y, meter.color, false, 1, true)
end end
local ascension_y = start_y + 3 * line_h + 1 local ascension_y = start_y + 3 * line_h + 1
Ascension.draw(bar_x, ascension_y, { spacing = 5 }) Ascension.draw(bar_x, ascension_y, { spacing = 8 })
end end

View File

@@ -12,8 +12,9 @@ function Minigame.draw_win_overlay(win_text)
local box_h = th + padding * 2 local box_h = th + padding * 2
local box_x = (Config.screen.width - box_w) / 2 local box_x = (Config.screen.width - box_w) / 2
local box_y = (Config.screen.height - box_h) / 2 local box_y = (Config.screen.height - box_h) / 2
local text_x = Config.screen.width / 2
rect(box_x, box_y, box_w, box_h, Config.colors.dark_grey) rect(box_x, box_y, box_w, box_h, Config.colors.dark_grey)
rectb(box_x, box_y, box_w, box_h, Config.colors.white) rectb(box_x, box_y, box_w, box_h, Config.colors.white)
Print.text_center(text, Config.screen.width / 2, box_y + padding, Config.colors.white) Print.text_center_contour(text, text_x, box_y + padding, Config.colors.black, false, 1, Config.colors.white)
end end

View File

@@ -2,7 +2,7 @@
-- 000:0404005f574fc2c3c71d2b53fff1e8ab52367e2553ffa30000875129adff83769c00e436fa77a8ff004dc3c3c7ffccaa -- 000:0404005f574fc2c3c71d2b53fff1e8ab52367e2553ffa30000875129adff83769c00e436fa77a8ff004dc3c3c7ffccaa
-- </PALETTE> -- </PALETTE>
-- <TILES> -- <TILES>
-- 000:00000000c666666006606060c636366006606060c606366006666660c0000000 -- 000:0000000006666660066060600636366006606060060636600666666000000000
-- 001:0000000006666666060600600666666606000600066666660633633300000000 -- 001:0000000006666666060600600666666606000600066666660633633300000000
-- 002:0000000066666666006000066666666660060006666666666333363300000000 -- 002:0000000066666666006000066666666660060006666666666333363300000000
-- 004:1111111111111111111111111111111111111111111111111111111111111111 -- 004:1111111111111111111111111111111111111111111111111111111111111111
@@ -17,7 +17,7 @@
-- 013:1000000004444444044444440444444404444444044444440444444404444444 -- 013:1000000004444444044444440444444404444444044444440444444404444444
-- 014:0000000144444240444424204444424044442420444442404444242044444240 -- 014:0000000144444240444424204444424044442420444442404444242044444240
-- 015:0000000004244444024444440424400002444444042424240242424200000000 -- 015:0000000004244444024444440424400002444444042424240242424200000000
-- 016:000000004444442044444240c004442044444240e424242042424240c0000000 -- 016:0000000044444420444442400004442044444240e42424204242424000000000
-- 017:0000000002424240042424200244444004244440024404400424044002440440 -- 017:0000000002424240042424200244444004244440024404400424044002440440
-- 018:0000000002424240042424200444424004444420044042400440442004404240 -- 018:0000000002424240042424200444424004444420044042400440442004404240
-- 019:0222222200010012055016020150660205501602015066020550160200000000 -- 019:0222222200010012055016020150660205501602015066020550160200000000
@@ -33,7 +33,7 @@
-- 029:0440442004404240044044200444424004444420024242400424242000000000 -- 029:0440442004404240044044200444424004444420024242400424242000000000
-- 030:0000000006666660036060600360666006606060036660600360666003606060 -- 030:0000000006666660036060600360666006606060036660600360666003606060
-- 031:0222222200010010033033030130130103303303013013010330330300000000 -- 031:0222222200010010033033030130130103303303013013010330330300000000
-- 032:22222220c10012203033022030130220303302203013022030330220c0000000 -- 032:2222222001001220303302203013022030330220301302203033022000000000
-- 033:0333011103330111033301110333011103330111033301110333011100000000 -- 033:0333011103330111033301110333011103330111033301110333011100000000
-- 034:1110011111100111111001111110011111100111111001111110011100000000 -- 034:1110011111100111111001111110011111100111111001111110011100000000
-- 035:1110333011103330111033301110333011103330111033301110333000000000 -- 035:1110333011103330111033301110333011103330111033301110333000000000
@@ -49,7 +49,7 @@
-- 045:2222222022222220222222202222222022222220000000001111100011111000 -- 045:2222222022222220222222202222222022222220000000001111100011111000
-- 046:1111111111111111111111111000000002222222022222220222222202222222 -- 046:1111111111111111111111111000000002222222022222220222222202222222
-- 047:1111111111111111111111110000000022222222222222222222222222222222 -- 047:1111111111111111111111110000000022222222222222222222222222222222
-- 048:111111111111111111111111c000100022220444e222044422220444e2220444 -- 048:1111111111111111111111110000100022220444e222044422220444e2220444
-- 049:1111111111111111111111111100001100222201022222200222222002222220 -- 049:1111111111111111111111111100001100222201022222200222222002222220
-- 050:1111111011111110111111101111111011111110111111101111111011110000 -- 050:1111111011111110111111101111111011111110111111101111111011110000
-- 051:1111111111111111111111111111111111111111111111111111111100011111 -- 051:1111111111111111111111111111111111111111111111111111111100011111
@@ -81,7 +81,7 @@
-- 077:0000000011111110000000006611166655555551555555515555555166666111 -- 077:0000000011111110000000006611166655555551555555515555555166666111
-- 078:1111111111111111111100001110919111101010110191011019191000919190 -- 078:1111111111111111111100001110919111101010110191011019191000919190
-- 079:1111111111111111000000009191919000000010111110901111101000000090 -- 079:1111111111111111000000009191919000000010111110901111101000000090
-- 080:09191990c991990209199020c999020209902020c902020200202020c0000000 -- 080:0919199009919902091990200999020209902020090202020020202000000000
-- 081:0000000002020200202020200200022020002220000222202022222002222220 -- 081:0000000002020200202020200200022020002220000222202022222002222220
-- 082:5555555555555055555501056666010155550105555501055555010566610106 -- 082:5555555555555055555501056666010155550105555501055555010566610106
-- 083:5555555555555555000000000222222202222222022222220222222202222222 -- 083:5555555555555555000000000222222202222222022222220222222202222222
@@ -97,7 +97,7 @@
-- 093:0222022002220220022202200222022002220220022202200222022002220220 -- 093:0222022002220220022202200222022002220220022202200222022002220220
-- 094:1033330110333301103333011033330110333301103333011033330110000001 -- 094:1033330110333301103333011033330110333301103333011033330110000001
-- 095:3333101033331090333310103333109011113010111130901111301011113000 -- 095:3333101033331090333310103333109011113010111130901111301011113000
-- 096:00000000c111111100000000c106666601051555c105155501056555c0066611 -- 096:0000000001111111000000000106666601051555010515550105655500066611
-- 097:0000000011111110000000006666101055555010555550105555501066616000 -- 097:0000000011111110000000006666101055555010555550105555501066616000
-- 098:5555555555555550555555056666116655550055555500555555655566666611 -- 098:5555555555555550555555056666116655550055555500555555655566666611
-- 099:0555555500555555050555550661111105500555115005550055555500616666 -- 099:0555555500555555050555550661111105500555115005550055555500616666
@@ -113,7 +113,7 @@
-- 109:1111111011111110111111101111111011111110111111100000000566000066 -- 109:1111111011111110111111101111111011111110111111100000000566000066
-- 110:1111111111111111111111111111010011101044111010441110104400000010 -- 110:1111111111111111111111111111010011101044111010441110104400000010
-- 111:1111111111111111111111110011111144011111440111114401111110000000 -- 111:1111111111111111111111110011111144011111440111114401111110000000
-- 112:11111111111111111111111111111111111111111111111111110111c0004000 -- 112:1111111111111111111111111111111111111111111111111111011100004000
-- 113:3333104433331044333310003333111111113333111133331111333311113333 -- 113:3333104433331044333310003333111111113333111133331111333311113333
-- 114:4403111144031111000311113333111111110000111044441104444411014444 -- 114:4403111144031111000311113333111111110000111044441104444411014444
-- 115:3304440133044401330444013304440100104033440103334440333344103333 -- 115:3304440133044401330444013304440100104033440103334440333344103333
@@ -177,7 +177,7 @@
-- 173:0333333010333330103333301033333010333330100000001111111111111111 -- 173:0333333010333330103333301033333010333330100000001111111111111111
-- 174:111111111111111111111111111111111111111112222222a1a1a1a21a1a1a12 -- 174:111111111111111111111111111111111111111112222222a1a1a1a21a1a1a12
-- 175:1111111111111111111111111111111111111111444444444444444444444444 -- 175:1111111111111111111111111111111111111111444444444444444444444444
-- 176:00000000c111111001101010c131311001101010c101311001111110c0000000 -- 176:0000000001111110011010100131311001101010010131100111111000000000
-- 177:0000000001111111010100100111111101000100011111110133133300000000 -- 177:0000000001111111010100100111111101000100011111110133133300000000
-- 178:0000000011111111001000011111111110010001111111111333313300000000 -- 178:0000000011111111001000011111111110010001111111111333313300000000
-- 179:11111111111111111111111111111111111100001110b161110b100010810101 -- 179:11111111111111111111111111111111111100001110b161110b100010810101
@@ -225,7 +225,7 @@
-- 221:0200000002000000020000000200000002000000020000000211111100000000 -- 221:0200000002000000020000000200000002000000020000000211111100000000
-- 222:0000000000000000000000000000000000000000000000001111111100000000 -- 222:0000000000000000000000000000000000000000000000001111111100000000
-- 223:000000000000000000000000000000000000000000000000111dd11100000000 -- 223:000000000000000000000000000000000000000000000000111dd11100000000
-- 224:00000010c000001000000010c000001000000010c000001011111110c0000000 -- 224:0000001000000010000000100000001000000010000000101111111000000000
-- 225:3333333333333333333333333333333313131313313131311313131331313131 -- 225:3333333333333333333333333333333313131313313131311313131331313131
-- 226:0203333302033333020333330203333302031313020131310203131300013131 -- 226:0203333302033333020333330203333302031313020131310203131300013131
-- 227:3333302033333020333330203333302013131020313130201313102031313000 -- 227:3333302033333020333330203333302013131020313130201313102031313000
@@ -241,7 +241,7 @@
-- 237:1444402044140220444402204144022000402220220222200222222010222220 -- 237:1444402044140220444402204144022000402220220222200222222010222220
-- 238:3333330033333022333302223333022213130222313102221313101231313100 -- 238:3333330033333022333302223333022213130222313102221313101231313100
-- 239:0003010322200103222201032222010322220103222201012210101300013131 -- 239:0003010322200103222201032222010322220103222201012210101300013131
-- 240:02220111c222001102220000c222033302220333c222200002222222c2222222 -- 240:0222011102220011022200000222033302220333022220000222222202222222
-- 241:1002222001202220002022203002222031222220022222202222222022222220 -- 241:1002222001202220002022203002222031222220022222202222222022222220
-- 242:3333333333333330333333033333113313130013313100311313131331313131 -- 242:3333333333333330333333033333113313130013313100311313131331313131
-- 243:0333333300333333030333330331133303100313113001310013131300313131 -- 243:0333333300333333030333330331133303100313113001310013131300313131
@@ -251,149 +251,281 @@
-- 247:0000000011111110000000003333301013131010313130101313101031313000 -- 247:0000000011111110000000003333301013131010313130101313101031313000
-- </TILES> -- </TILES>
-- <SPRITES> -- <SPRITES>
-- 002:00000000000000000000444400044444000444440044ffff004fffff004f3333 -- 000:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 003:0000000000000000441600004242600044241000ff426000fff4100033f26000 -- 001:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 004:00000333000035550003655500365555003555ff00356fff00365f3f00355fff -- 002:cccccccccccc0000ccc04444cc044444cc044444c044ffffc04fffffc04f3333
-- 005:33000000553000005563000055563000ff553000fff53000f3f63000fff53000 -- 003:cccccccc0000cccc44160ccc424260cc442410ccff4260ccfff410cc00f260cc
-- 008:0000005a00005a55000055a50000a55a00005a5700005533000575f30000757f -- 004:ccccc000cccc0555ccc06555cc065555cc0555ffcc056fffcc065f0fcc055fff
-- 009:55a50000a55a50005a55a00057555500fff7a5003f335a00fff3f500fffffa00 -- 005:00cccccc550ccccc5560cccc55560cccff550cccfff50cccf0f60cccfff50ccc
-- 016:0000000000000000000000000000003000000353000035350003535100353535 -- 006:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 017:0000000000000000000000003000000053300000151300005151300015151300 -- 007:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 018:004f99ff000fffff0000ff3300000fff00003666000355550035652503163555 -- 008:cccc0000ccc05a55ccc055a5ccc0a55accc05a57ccc05533cc0575f3ccc0757f
-- 019:99f41000fff26000ff600000f600000063300000555330005555530055535530 -- 009:00000ccca55a50cc5a55a0cc5755550cfff7a50c3f335a0cfff3f50cfffffa0c
-- 020:00356f6f003655f60365511f3653122f3531222f363221220532232203322322 -- 010:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 021:f6f530006f563000f1156300f2215300f2226300221233002232130022121300 -- 011:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 022:000000000000005100000155000055150000157f0000551300057f3f00017fff -- 012:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 023:00000000555000005155000015515000ff7f5100ff31f200fff3ff0033fff200 -- 013:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 024:0000057f00000005000000050000559900055911005599110055119900551199 -- 014:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 025:f3ff5000fff500005f5000004415100077995100449915007711955044119550 -- 015:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 026:00000000000001330001331300031333001333330031f7f7001133390031999f -- 016:cccccccccccccccccccccccccccccc0cccccc050cccc0535ccc05351cc053535
-- 027:0000000013310000131330003333130033333100f7f113003337310099975300 -- 017:cccccccccccccccccccccccc0ccccccc500ccccc1510cccc51510ccc151510cc
-- 028:000000000000000000000a77000057770005777700577777005775a50077a7ff -- 018:c04f99ffcc0fffffccc0ff00cccc0fffcccc0666ccc05555cc056525c0160555
-- 029:0000000000000000777a0000777750007777750077777750a5a57750fff7a770 -- 019:99f410ccfff260ccff600cccf60ccccc600ccccc55500ccc555550cc5550550c
-- 030:000000000000051500001151000555550001517f000557ff0001ff3f0007ff1f -- 020:cc056f6fcc0655f6c065511f0650122f0501222f06022122c5022322c0022322
-- 031:00000000150000005110000055500000f7110000ff750000f3f10000f1f50000 -- 021:f6f50ccc6f560cccf11560ccf22150ccf22260cc221230cc223210cc221210cc
-- 032:0033535100353533003351ff00351f3f0003ff3f0003ffff00003ff3000323ff -- 022:cccccc00ccccc051cccc0155ccc05515ccc0157fccc05513cc057f0fcc017fff
-- 033:5555530033555300ff155300f3f15300f3ff3000ffff30003ff30000ff323000 -- 023:000ccccc5550cccc51550ccc155150ccff7f510cff31f20cfff0ff0c00fff20c
-- 034:036135250316355503613525031633110333331103f333330333333300033333 -- 024:cccc057fccccc005cccc0555ccc05599cc055911c0559911c0551199c0551199
-- 035:55565530555355305556553013335530133333303333ff303333333033330000 -- 025:f3ff50ccfff50ccc5f50cccc441500cc7799510c4499150c7711955044119550
-- 036:00322322003223330033331a003ff31a003ff3a1003333a100003a1a00003a1a -- 026:ccccc000ccc00133cc013313cc031333c0133333c031f7f7c0113339c031999f
-- 037:223213003332130011133300111f300011133000111300001113000011130000 -- 027:0000cccc13310ccc131330cc3333130c3333310cf7f1130c3337310c9997530c
-- 038:000007ff0001117f0016171f0155175501561755155117555f7157555ff15755 -- 028:ccccccccccccc000cccc0a77ccc05777cc057777c0577777c05775a5c077a7ff
-- 039:fffff700333f2000ffff7100555571105555716155557111555575ff555575ff -- 029:cccccccc0000cccc777a0ccc777750cc7777750c77777750a5a57750fff7a770
-- 040:005599110055991100ff533300fff511007fff110007f5110000311300003113 -- 030:ccccc000cccc0515ccc01151cc055555cc01517fcc0557ffcc01ff0fcc07ff1f
-- 041:479915504499155047333f7014113ff011113f70111130003311300003113000 -- 031:00cccccc150ccccc5110cccc5550ccccf7110cccff750cccf0f10cccf1f50ccc
-- 042:0035777f0001776600005777000000550088885508bbb84508bbb8448bb8b844 -- 032:cc035351cc053500cc0351ffcc051f0fccc0ff0fccc0ffffcccc0ff0ccc020ff
-- 043:77775300677500007750000055880000558b8000548bb800228bbb80428b8b80 -- 033:555550cc005550ccff1550ccf0f150ccf0ff0cccffff0ccc0ff0ccccff020ccc
-- 044:005a7f3f0057ff3f07f7ffff07757fdf077a57fd0575357f005a66a70006aa6f -- 034:c0613525c0163555c0613525c0163311c0003311c0f03333cc003333ccc03333
-- 045:ff3f7a50ff3ff750fffff777ffdf7577ddf75a77ff75157577a66a50ff6aa600 -- 035:5556550c5553550c5556550c1330550c1330000c3330ff0c333000cc3330cccc
-- 046:0005ffff00005ff3000005ff0017775500242777002121710024277100212171 -- 036:cc022322cc022333cc03331acc0ff31acc0ff3a1ccc003a1cccc0a1acccc0a1a
-- 047:fff500003f500000f50000005771000077720000171200001772000017120000 -- 037:223210cc333210cc111300cc111f0ccc11100ccc1110cccc1110cccc1110cccc
-- 048:00323123003231430032314300313339000f3333000033300000333000033330 -- 038:ccc007ffcc01117fc016171f0155175501561755055117550f7157550ff15755
-- 049:324303003443030034430300933313003333f000033300000333000003333000 -- 039:fffff70c000f20ccffff710c555571105555716055557110555575f0555575f0
-- 050:0003333000033330000333300003333000053530003311300031113000333330 -- 040:c0559911c0559911c0ff5333c0fff511c07fff11cc07f511ccc03110cccc0110
-- 051:3333000033330000333300003333000035350000311330003111300033333000 -- 041:479915504499155047333f7014113ff011113f701111000c00110cccc0110ccc
-- 052:000031a1000031a100003a1a00003a1a00003333000003f3000003f300000330 -- 042:c035777fcc017766ccc05777cc088855c08bb85508bbb84508bbb8440bb8b844
-- 053:111300001113000011130000111300003333000003f3000003f3000003300000 -- 043:7777530c677500cc7750cccc55880ccc558b80cc548bb80c228bbb80428b8b80
-- 054:5ff7646607f59999000991330003993000099130000133300001221000011100 -- 044:cc0a7f3fc075ff3f0777ffff07757fdf077a57fd0575357fc05a66a7cc06aa6f
-- 055:666646ff999993f7339913000039930000991300001333000012210000111000 -- 045:ff3f7a0cff3ff70cfffff770ffdf7570ddf75a70ff75157077a66a50ff6aa60c
-- 056:0000311300003113000031130000311300003113000033330000165100001111 -- 046:cc05ffffccc05ff3cccc05ffcc007755c0242777c0212171c0242771c0212171
-- 057:0311300003113000031130000311300003113000033330000165100001111000 -- 047:fff50ccc3f50ccccf50ccccc5770cccc77720ccc17120ccc17720ccc17120ccc
-- 058:8888b8445f78b8445f58884405031330000331300003133000053550000577f0 -- 048:cc020123cc020143cc020143cc010309cc0f0300ccc0030ccccc030cccc0000c
-- 059:218b8880428b87f5218885f5031330500331300003133000053550000577f000 -- 049:324030cc344030cc344030cc903010cc0030f0ccc0300cccc030ccccc0000ccc
-- 060:0056aa66001f6aaa001fa666007f6aaa00006aaa0000a666000007d7000001d1 -- 050:ccc0330cccc0330cccc0330cccc0330cccc0350ccc03110ccc01110ccc00000c
-- 061:666aa650aaaa6f106666af10aaaa6f70aaaa60006666a000007d7000001d1000 -- 051:0330cccc0330cccc0330cccc0330cccc0530cccc01130ccc01110ccc00000ccc
-- 062:0024277700ff611100fff333005f533300003333000033300000333000001110 -- 052:cccc01a1cccc01a1cccc0a1acccc0a1acccc0000ccccc0f0ccccc0f0ccccc00c
-- 063:77720000116f0000335ff000333f000033300000333000003330000001110000 -- 053:1110cccc1110cccc1110cccc1110cccc0000ccccc0f0ccccc0f0ccccc00ccccc
-- 064:00000000000000000000011100003311000111170003317f000117ff00553333 -- 054:0ff7646607f59999c0099100cc03990ccc09910ccc01330ccc01220ccc0000cc
-- 065:0000000000000000110000001110000077110000ff710000ff77000013330000 -- 055:666646f0999993f00099100cc03990ccc09910ccc01330ccc01220ccc0000ccc
-- 066:00000000000000330000033300003333000033f7000035ff000535ff0007f333 -- 056:cccc0110cccc0110cccc0110cccc0110cccc0110cccc0330cccc0650cccc0000
-- 067:0000000030000000333300003333000037f30000ffff0000ffff0000f333f000 -- 057:c0110cccc0110cccc0110cccc0110cccc0110cccc0330cccc0650cccc0000ccc
-- 068:000000000000000000000000000000000000000000000122000012440001447f -- 058:0888b8440f78b8440f58884405031300c0c0310cccc0130cccc0350cccc0000c
-- 069:00000000000000000000000000000000000000002210000044210000f7441000 -- 059:218b8880428b87f0218885f000130050c0310c0cc0130cccc0350cccc0000ccc
-- 070:000000000000000000000333000038880003888800388881038388310338835f -- 060:c056aa66c01f6aaac01fa666c07f6aaacc0f6aaaccc0a666cccc07d7cccc0000
-- 071:00000000000000003330000088880000883380003355380016665300f7ff7300 -- 061:666aa650aaaa6f106666af10aaaa6f70aaaa600c6666a0cc007d70cccc0000cc
-- 072:0000000000000000000000550000055500005577000057ff00007f55000fff33 -- 062:c0242777c0ff6111c0fff333c05f5333cc003300cccc030ccccc030ccccc000c
-- 073:0000000000000000555550005555550077777550ffffff507f755f703f333fff -- 063:77720ccc116f0ccc335ff0cc333f0ccc0330cccc030ccccc030ccccc0000cccc
-- 074:0000000000000011000001130000113100011313000131310013137c003131ff -- 064:ccccccccccccc000cccc0111ccc03311cc011117cc03317fcc0117ffc0553333
-- 075:000000001110000013110000313110001313100031313100ffff7100fffff100 -- 065:cccccccc00cccccc110ccccc1110cccc77110cccff710cccff770ccc13330ccc
-- 076:0000000000000055000005550000055500005577000057ff0007ff55000fff99 -- 066:cccccc00ccccc033cccc0333ccc03333ccc033f7ccc035ffcc0535ffcc07f333
-- 077:0000000055550000555550005555500075775500fffff5005f555f703f993ff0 -- 067:0ccccccc3000cccc33330ccc33330ccc37f30cccffff0cccffff0cccf333f0cc
-- 078:0000000000000000000000000000000000001111000014440000144400001555 -- 068:ccccccccccccccccccccccccccccccccccccc000cccc0122ccc01244cc01447f
-- 079:0000000000000000000000000000000011100000441000004410000055100000 -- 069:cccccccccccccccccccccccccccccccc000ccccc2210cccc44210cccf74410cc
-- 080:0077533f000ff77f0005fff500005ff70000015f0005f315005ff3ff00ff3333 -- 070:ccccccccccccccccccccc000cccc0888ccc08888cc088880c0838801c038805f
-- 081:f33f0000f77f00005ff000007f500000f500000053f00000f3f50000333f0000 -- 071:cccccccccccccccc0000cccc88880ccc880080cc0055080c1666530cf7ff730c
-- 082:000fff330005ffff000057f50000007f000333350031313f0031313303113133 -- 072:cccccccccccccc00ccccc055cccc0555ccc05577ccc057ffccc07f55cc0fff33
-- 083:ff33f000ffff100055f10000ff70000055330000ff3130003331300033311300 -- 073:cccccccc00000ccc555550cc5555550c77777550ffffff507f755f703f333ff0
-- 084:000447ff000fffff0007f33f000ff11f000f7fff0000ff750000042400001142 -- 074:cccccccccccccc00ccccc013cccc0131ccc01313ccc03131cc031377cc0131ff
-- 085:ff744000ffff1000f33f7000f11f5000fff7f00057ff00004240000044110000 -- 075:cccccccc000ccccc1310cccc31310ccc13130ccc313130ccffff70ccfffff0cc
-- 086:038383390338833b038383130338337f0383835f003838350003838300003333 -- 076:cccccccccccccc00ccccc055cccc0555ccc05577ccc057ffcc07ff55cc0fff99
-- 087:99539900bb33bb0099513900fffff530ff66f1007fff53001555310033333000 -- 077:cccccccc0000cccc55550ccc555550cc7577550cfffff50c5f555f0c3f993f0c
-- 088:000f7ff300005fff000007ff0000007f0000021f000024410002444400124244 -- 078:cccccccccccccccccccccccccccccccccccc0000cccc0444cccc0444cccc0555
-- 089:fff3ff7ff1ffff50fffff700111f7000ffff1000111142004244442042442410 -- 079:cccccccccccccccccccccccccccccccc000ccccc440ccccc440ccccc550ccccc
-- 090:005f5733005f1f2300015fff0000077f00011117001244210144414201421444 -- 080:c077500fcc0ff77fcc05fff5ccc05ff7cccc015fccc0f315cc0ff3ffc0ff3333
-- 091:ff33f500ff23f500fffff00011ff500077710000112410004442410042441410 -- 081:f00f0cccf77f0ccc5ff0cccc7f50ccccf50ccccc53f0ccccf3f50ccc333f0ccc
-- 092:00005fff000005ff0000001f0000000100000114000018140000181400018114 -- 082:cc0fff00cc05ffffccc057f5cccc007fccc03335cc01313fc031313303113133
-- 093:fffff500111f5000fff500001110000042411000424180004241810042411100 -- 083:ff00f0ccffff10cc55f10cccff70cccc5530ccccff310ccc33310ccc333110cc
-- 094:00001fff00001f3f0000cf3f0000cfff00000cf3000023cc000131ff001f1f11 -- 084:cc0447ffcc0fffffcc07f33fcc0ff11fcc0f7fffccc0ff75cccc0424cccc0142
-- 095:ff1000003f1000003ff00000fff00000fc000000c3200000f13100001f1f1000 -- 085:ff7440ccffff10ccf33f70ccf11f50ccfff7f0cc57ff0ccc4240cccc44110ccc
-- 096:00ff33330033133305f333330ff31333033333330f5313330ff1311107f13333 -- 086:c0838039c038803bc0838013c038307fc083805fcc083805ccc08383cccc0333
-- 097:333f0000333f5000333f5000333ff000333330003335f500111ff5003337f000 -- 087:9953990cbb33bb0c9951390cfffff50cff66f10c7fff50cc15550ccc33330ccc
-- 088:cc0f7ff0ccc05fffcccc07ffccccc07fcccc021fccc02441cc024444c0124244
-- 089:fff0ff70f1ffff50fffff70c111f70ccffff10cc1111420c4244442042442410
-- 090:cc0f5733cc0f1f23ccc00fffcccc077fccc01117cc024421c0444142c0421444
-- 091:ff33f0ccff23f0ccfffff0cc11ff0ccc7770cccc11240ccc444240cc4244140c
-- 092:ccc05fffcccc05ffccccc01fcccccc01ccccc014cccc0814ccc01814ccc08114
-- 093:fffff50c111f50ccfff50ccc1110cccc42410ccc424180cc424180cc424110cc
-- 094:cccc0fffcccc0f3fcccc0f0fcccc0fffccccc0f3cccc0377ccc031ffcc0f1f11
-- 095:ff0ccccc3f0ccccc0f0cccccff0cccccf0cccccc730cccccf130cccc1f1f0ccc
-- 096:c0ff3333c033133305f333330ff31333033333330f5313330ff1311107f13333
-- 097:333f0ccc333f50cc333f50cc333ff0cc333330cc3335f50c111ff50c3337f0cc
-- 098:03113133031131330333313303113131033331320555313107ff313307ff3131 -- 098:03113133031131330333313303113131033331320555313107ff313307ff3131
-- 099:333113003331130033333300213113004233330021317f0033317f5001317500 -- 099:333110cc333110cc333330cc213110cc423330cc21317f0c33317f501131750c
-- 100:0002441400244441024444240242442414424424144144241441442414414424 -- 100:ccc04414cc044441c0444424c042442404424424044144240441442404414424
-- 101:4144210014444200424444204244422042444241424441414244414142444141 -- 101:414420cc1444420c424444204244422042444240424441404244414042444140
-- 102:000333330033a3530032a353003a332303a23333032a333303a23333032a3333 -- 102:ccc03333cc03a353cc02a353cc0a3323c0a23333c02a3333c0a23333c02a3333
-- 103:3333300055353300553533002232323033333130333332303333313333333233 -- 103:33330ccc553530cc553530cc2232320c3333310c3333320c3333313033333230
-- 104:001441440014424400144144001441240017f311001ff133001fff330007f333 -- 104:c0144144c0144244c0144144c0144124c017f311c01ff133c01fff33cc07f330
-- 105:4244142042442410424414204244122012113f7031333ff033333ff030333f00 -- 105:4244142042442410424414204244122012113f7031333ff033333ff000033f0c
-- 106:01421444014414440142144431421444011111110fff1a5a0ff755a505755a51 -- 106:c0421444c0441444c0421444c0421444c0111111c0ff1a5ac0f755a5c0755a50
-- 107:44441410424414104444141042441410111111105a5a55f5a5a5a5f5111a5170 -- 107:4444140c4244140c4444140c4244140c1111110c5a5a55f0a5a5a5f0001a5170
-- 108:0001181400018114000118140001811300011333005fff33000f773300055333 -- 108:ccc01814ccc08114ccc01814ccc08113cc001333c05fff33cc0f7733cc055330
-- 109:424181104241181042418110222318101113310033335ff03333f77033335500 -- 109:4241810c4241180c4241810c2223180c1113310c33335ff03333f7700033550c
-- 112:0553333300033330000333000033330000333300003333000011110000121200 -- 110:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 113:3330000033300000333000003330000033300000333000001111000012120000 -- 111:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 114:05f5313103003131033311310311113103111131003111300003333000012120 -- 112:05533333c0033330ccc0330ccc03330cc03330ccc03330ccc01110ccc0000ccc
-- 115:0131130001311300013113000131130001311300003113000033330000121200 -- 113:33000ccc030ccccc030ccccc030ccccc030ccccc030ccccc0110cccc0000cccc
-- 116:0fff242407f72211005544200002442000024420000244200002222000024440 -- 114:05f53131c0003131c0331131c0111131c0111130cc01110cccc0330cccc0000c
-- 117:424441f1112227f1024425500244200002442000024420000222200002444000 -- 115:113110cc113110cc113110cc113110cc003110cccc0110cccc0330cccc0000cc
-- 118:0333333307ff333307f733330070333000003330000033300000111000001110 -- 116:0fff242407f72211c0552400cc00240cccc0240cccc0240cccc0220cccc0000c
-- 119:33333575333331f1333335750033300000333000003330000011100000111100 -- 117:424441f0112227f000442550c044200cc04420ccc04420ccc02220ccc00000cc
-- 120:0000033300000333000003330000033300000333000003330000033300000111 -- 118:0333333307ff333307f73300c070030ccc0c030ccccc030ccccc010ccccc000c
-- 121:0033300000333000003330000033300000333000003330000033300010111100 -- 119:33333570333331f000033570cc03300ccc030ccccc030ccccc010ccccc0000cc
-- 122:005015a100001a51000015a100001a51000015a1000022510000924900009999 -- 120:ccc00330ccccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccccc000
-- 123:0315a150001a51000015a100001a51000015a100002251000092490000999900 -- 121:cc0300cccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccc0c0000cc
-- 124:0000033300000333000003330000033300000333000003330000033300000111 -- 122:c05015a0cc0c0a50cccc05a0cccc0a50cccc05a0cccc0250cccc0990cccc0000
-- 125:0333000003330000033300000333000003330000033300000333000000111000 -- 123:c015a150cc0a500ccc05a0cccc0a50cccc05a0cccc0250cccc0990cccc0000cc
-- 129:0000000000000000000000000000000000000000011515101151515151515151 -- 124:ccc00330ccccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccccc000
-- 144:0000000500000015000000150000057f000005ff0000017f0000331700073331 -- 125:c03000ccc030ccccc030ccccc030ccccc030ccccc030ccccc030ccccc0000ccc
-- 145:1ffffff7ff0fff0fff1fff1fffffffffff7555fffffffffffff111ff57fffff7 -- 126:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 146:1000000071000000f5000000ff000000ff500000ff5000007513100051333700 -- 127:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 160:0077333303331333333313333331333333313333355133335ff513335ff71111 -- 128:ccccccccccccccccccccccccccccccccccccccccccccccccccccccc0ccccccc0
-- 129:ccccccccccccccccccccccccccccccccc000000c011515101151515151515151
-- 130:cccccccccccccccccccccccccccccccccccccccccccccccc0ccccccc10cccccc
-- 131:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 132:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 133:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 134:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 135:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 136:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 137:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 138:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 139:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 140:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 141:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 142:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 143:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 144:cccccc05ccccc015ccccc015cccc057fcccc05ffcccc017fccc03317cc073331
-- 145:1ffffff7ff0fff0fff0fff0fffffffffff7555fffffffffffff111ff57fffff7
-- 146:10cccccc710cccccf50cccccff0cccccff50ccccff510ccc751310cc5133370c
-- 147:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 148:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 149:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 150:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 151:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 152:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 153:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 154:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 155:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 156:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 157:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 158:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 159:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 160:c077333303331333033313330331333303313333055133330ff513330ff71111
-- 161:1111111133333333333337333333373333333733333337333333373311117711 -- 161:1111111133333333333337333333373333333733333337333333373311117711
-- 162:133337003733133077731330333331333333313333333155333337ff1111175f -- 162:1333370c3733130c7773130c333331303333313033333150333337f011111750
-- 176:0775333300031333000333130000333300001333000033330000033300000111 -- 163:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 177:3335773333333533333333331313131333333333300000033000000310000001 -- 164:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 178:3333357533331000331300001333000033310000333300003331000011110000 -- 165:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 166:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 167:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 168:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 169:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 170:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 171:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 172:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 173:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 174:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 175:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 176:07753333c0031333ccc03313cccc0333cccc0333cccc0333ccccc033ccccc000
-- 177:33357733333335333333333313131313000000000cccccc00cccccc00cccccc0
-- 178:333335703333100c33100ccc1330cccc3330cccc3330cccc3330cccc0000cccc
-- 179:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 180:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 181:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 182:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 183:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 184:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 185:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 186:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 187:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 188:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 189:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 190:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 191:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 192:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 193:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 194:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 195:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 196:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 197:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 198:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 199:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 200:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 201:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 202:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 203:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 204:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 205:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 206:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 207:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 208:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 209:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 210:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 211:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 212:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 213:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 214:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 215:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 216:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 217:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 218:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 219:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 220:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 221:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 222:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 223:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 224:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 225:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 226:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 227:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 228:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 229:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 230:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 231:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 232:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 233:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 234:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 235:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 236:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 237:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 238:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 239:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 240:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 241:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 242:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 243:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 244:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 245:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 246:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 247:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 248:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 249:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 250:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 251:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 252:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 253:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 254:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- 255:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
-- </SPRITES> -- </SPRITES>
-- <MAP> -- <MAP>
-- 000:ffffffffff0010201020102010201020102010201020102000ffffffffff40404040404087f3f3f3f397a7b7c7d7a7e7f70818a7b7c7d7a7b7c7d7a70b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 000:20102010200010201020102010201020102010201020102000102010201040404040404087f3f3f3f397a7b7c7d7a7e7f70818a7b7c7d7a7b7c7d7a70b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 001:ffffffffff0040404040404040404040404040404040404000ffffffffff40404040404087f3f3f3f328a7384858a76878f388a7384858a7384858a70b40403b4b4040404040404040404040404040404040404040404040400b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 001:40404040400040404040404040404040404040404040404000404040404040404040404087f3f3f3f328a7384858a76878f388a7384858a7384858a70b40403b4b4040404040404040404040404040404040404040404040400b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 002:ffffffffff00406070408090a040b0c0d0e0f001f001112100ffffffffff984098409840a8f3f3f3f3b8a7a7a7a7a7c8d8e8f8a7a7a7a7a7a7a7a7a70b405b6b7b4040404040404040404040404040d0e0f001f001f00111210b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 002:408090a04000406070408090a040b0c0d0e0f001f001112100408090a040984098409840a8f3f3f3f3b8a7a7a7a7a7c8d8e8f8a7a7a7a7a7a7a7a7a70b405b6b7b4040404040404040404040404040d0e0f001f001f00111210b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 003:ffffffffff004031414051617140814091a1b1b1b1b1c1d100ffffffffff984098409840a8f3f3f3f3091919191919293949591919191919191919190b8b9babbb4040cbdbebfb0c401c2c2c2c3c4091a14c5c6c6c6c6cc1d10b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 003:4051617140004031414051617140814091a1b1b1b1b1c1d1004051617140984098409840a8f3f3f3f3091919191919293949591919191919191919190b8b9babbb4040cbdbebfb0c401c2c2c2c3c4091a14c5c6c6c6c6cc1d10b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 004:ffffffffffe140f1024012223240814042a15262728292a2e1ffffffffff984098409840a8f3f3f3f369f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f37c8c9cacbc7282ccdcecfc0d401d3030302d4042a13d4d7282728292a27c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 004:4012223240e140f1024012223240814042a15262728292a2e14012223240984098409840a8f3f3f3f369f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f37c8c9cacbc7282ccdcecfc0d401d3030302d4042a13d4d7282728292a27c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 005:ffffffffffb240c2d240e2f203132333435363738393a3b3b2ffffffffff984098409840a8f3f3f3f369f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f35d406d7d40e3958d9dadbdcd40ddedfded0e404353839383938393a3b35d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 005:4040404040b240c2d240e2f203132333435363738393a3b3b24040404040984098409840a8f3f3f3f369f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f35d406d7d40e3958d9dadbdcd40ddedfded0e404353839383938393a3b35d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 006:ffffffffffe1c3d3c3d3e3f30414c3d32434445410201020e1ffffffffff404040404040798989898999a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a97c1e1e1e1e44542e1e1e1e3e1e444e4e4e541e243444544454445444547c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 006:d3c3d3c3d3e1c3d3c3d3e3f30414c3d32434445410201020e1c3d3c3d3c3404040404040798989898999a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a97c1e1e1e1e44542e1e1e1e3e1e444e4e4e541e243444544454445444547c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 007:ffffffffffb264748494a4b4c4d46494649464940040e4f4b2ffffffffff4040404040404040404040404040404040404040404040404040404040405d1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 007:9464354594b264748494a4b4c4d46494649464940040e4f4b264354594644040404040404040404040404040404040404040404040404040404040405d1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 008:ffffffffffe1c30515d325d33545c3d355d3c3d365b17585e1ffffffffff4040404040404098989898404040404040404040404040404040404040407c1e7e8e1e1e1e9eae1ebe1e1e1e1e1e1e1e1e72821ebe1e72821ebe1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 008:d3c3e395d3e1c30515d325d33545c3d355d3c3d365b17585e1c3e395d3254040404040404098989898404040404040404040404040404040404040407c1e7e8e1e1e1e9eae1ebe1e1e1e1e1e1e1e1e72821ebe1e72821ebe1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 009:ffffffffffb264e395a5b594e39564c5d5946494e5b1b1f5b2ffffffffff4040404040404040404040404040404040404040404040404040404040405d1e05151ebe1ecedeeefe1e1e1e1e1e1e1e1ee395eefe1ee395eefe1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 009:9464465694b264e395a5b594e39564c5d5946494e5b1b1f5b264e395a5b54040404040404040404040404040404040404040404040404040404040405d1e05151ebe1ecedeeefe1e1e1e1e1e1e1e1ee395eefe1ee395eefe1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 010:ffffffffffe1c306162636d34656c395d5d3c3d376b1b1b1e1ffffffffff404040404040409898989840b9c9c9d9e9f90a0a0a0a4040400a0a0a0a407c1ee395eefe1e0f1f2f3f1e1e1e1e1e1e1e1ee3952f3f1ee3952f3f1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 010:d3c3d3c3d3e1c306162636d34656c3e3d5d3c3d376b1b1b1e1c3e3952636404040404040409898989840b9c9c9d9e9f90a0a0a0a4040400a0a0a0a407c1ee395eefe1e0f1f2f3f1e1e1e1e1e1e1e1ee3952f3f1ee3952f3f1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 011:ffffffffffb264946494649464948696a694649410201020b2ffffffffff4040404040404040404040401a2a3a4a5a6a7a40404040404040404040405d1ee3952f3f1e4f5f1ebe1e1e1e1e1e1e1e1ee3951ebe1ee3951ebe1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 011:9464946494b264946494649464948696a694649410201020b264e395d3254040404040404040404040401a2a3a4a5a6a7a40404040404040404040405d1ee3952f3f1e4f5f1ebe1e1e1e1e1e1e1e1ee3951ebe1ee3951ebe1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 012:ffffffffffe1c37282d3c3d3c3d3b6c6d6d3c3d300e6f607e1ffffffffff4040404040404098989898408a9aaabaca9ada40404040404040404040407c1e4f5f1ebe1e0515eefe1e1e1e1e1e1e1e1ee395eefe1ee395eefe1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 012:d3c37282d3e1c37282d3c3d3c3d3b6c6d6d3c3d300e6f607e1c3e395a5b54040404040404098989898408a9aaabaca9ada40404040404040404040407c1e4f5f1ebe1e0515eefe1e1e1e1e1e1e1e1ee395eefe1ee395eefe1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 013:ffffffffffb264e395946494649464946494649465172737b2ffffffffffeaeaeaeaeaeaeafafafafaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaea5d1e0515eefe1e6f7f2f3f1e1e1e1e1e1e1e1e6f7f2f3f1e6f7f2f3f1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 013:9464e39594b264e395946494649464946494649465172737b26406162636eaeaeaeaeaeaeafafafafaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaea5d1e0515eefe1e6f7f2f3f1e1e1e1e1e1e1e1e6f7f2f3f1e6f7f2f3f1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 014:ffffffffffe1c34454d3c3d3c3d3c3d3c3d3c3d3e5b14757e1fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f37c1e6f7f2f3f1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 014:d3c34454d3e1c34454d3c3d3c3d3c3d3c3d3c3d3e5b14757e1c3d3c3d3c3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f37c1e6f7f2f3f1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e7c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 015:ffffffffffb2649464946494649464946494649476b1b1b1b2fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f35d1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 015:9464946494b2649464946494649464946494649476b1b1b1b26494649464f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f35d1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e5d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- 016:ffffffffff0010201020766777001020102010201020102000fffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -- 016:201020102000102010207667770010201020102010201020001020102010f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f30b1b2b1b2b7667776777761b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b1b2b0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-- </MAP> -- </MAP>
-- <SFX> -- <SFX>
-- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000 -- 016:00000000000000400040004000700070007000400040004000700070007000c000c000c000c000c000c000c000c000c000c000c000c000c000c000c0470000000000

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: 1.0-beta1 -- version: 1.0-beta3
-- script: lua -- script: lua

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

@@ -6,52 +6,52 @@ local STATE_CHOICE = "choice"
local ASC_01_TEXT = [[ local ASC_01_TEXT = [[
Normann seems to be in line, Normann seems to be in line,
...
and stays seeking for oxes and stays seeking for oxes
...
within the confines. within the confines.
...
Very good. Very good.
]] ]]
local ASC_12_TEXT = [[ local ASC_12_TEXT = [[
We have a problem! We have a problem!
...
Normann formed his first thought. Normann formed his first thought.
...
He saw the tracks. He saw the tracks.
]] ]]
local ASC_23_TEXT = [[ local ASC_23_TEXT = [[
Not good, not terrible. Not good, not terrible.
...
Normann caught his glimpse Normann caught his glimpse
...
of another way of another way
...
- quite literally - - quite literally -
...
if this continues, if this continues,
...
we will lose control. we will lose control.
]] ]]
local ASC_34_TEXT = [[ local ASC_34_TEXT = [[
There is no turning back now for Norman. There is no turning back now for Norman.
...
He caught on. He caught on.
...
I hoped it would never come to this... I hoped it would never come to this...
]] ]]
--[[ Norman speaks for the first time during MM screen ]] --[[ Norman speaks for the first time during MM screen ]]
local ASC_45_TEXT = [[ local ASC_45_TEXT = [[
Wait, who are you? Wait, who are you?
...
*silence* *silence*
...
Why am I seeing this? Why am I seeing this?
...
*silence* *silence*
...
]] ]]
local ascension_texts = { local ascension_texts = {
@@ -132,6 +132,7 @@ function MysteriousManScreen.wake_up()
target_points = 100, target_points = 100,
instruction_text = "Wake up Norman!", instruction_text = "Wake up Norman!",
show_progress_text = false, show_progress_text = false,
meter_on_complete = Meter.apply_wakeup_reward,
on_win = function() on_win = function()
Audio.music_play_wakingup() Audio.music_play_wakingup()
Meter.show() Meter.show()
@@ -240,9 +241,12 @@ Screen.register({
end end
end end
elseif state == STATE_CHOICE then elseif state == STATE_CHOICE then
selected_choice = UI.update_menu(MysteriousManScreen.choices, selected_choice) 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() then if Input.select() or confirmed then
Audio.sfx_select() Audio.sfx_select()
if selected_choice == 1 then if selected_choice == 1 then
MysteriousManScreen.wake_up() MysteriousManScreen.wake_up()
@@ -258,16 +262,18 @@ Screen.register({
end end
if state == STATE_TEXT then if state == STATE_TEXT then
local cx = Config.screen.width / 2 local screen_w = Config.screen.width
local line_y = text_y local line_y = text_y
for line in (text .. "\n"):gmatch("(.-)\n") do for line in (text .. "\n"):gmatch("(.-)\n") do
Print.text_center(line, cx, line_y, Config.colors.light_grey) local line_w = print(line, 0, -6, 0, false, 1)
local line_x = math.floor((screen_w - line_w) / 2)
Print.text_contour(line, (line_x - 8), line_y, Config.colors.black, false, 1)
line_y = line_y + 8 line_y = line_y + 8
end end
elseif state == STATE_DAY then elseif state == STATE_DAY then
MysteriousManScreen.draw_day_switch_background() MysteriousManScreen.draw_day_switch_background()
local day_text = day_text_override or ("Day " .. Context.day_count) local day_text = day_text_override or ("day " .. Context.day_count)
Print.text_center( Print.text_center_contour(
day_text, day_text,
Config.screen.width / 2, Config.screen.width / 2,
Config.screen.height / 2 - 3, Config.screen.height / 2 - 3,

View File

@@ -6,26 +6,45 @@ Screen.register({
"go_to_walking_to_home", "go_to_walking_to_home",
"have_a_coffee", "have_a_coffee",
}, },
situations = {
"drink_coffee",
},
init = function() init = function()
Audio.music_play_room_work() Audio.music_play_room_work()
Context.have_been_to_office = true
local possible_sprites = {
"dev_project_manager",
"dev_hr_girl",
"dev_introvert",
"dev_extrovert",
"dev_guru",
"dev_operator",
{id="dev_buddy", y_correct=1 * 8},
{id="dev_boy", y_correct=1 * 8},
{id="dev_girl", y_correct=1 * 8}
}
local possible_positions = {
{x = 6 * 8, y = 4 * 8},
{x = 10 * 8, y = 11 * 8 + 4},
{x = 12 * 8, y = 4 * 8},
{x = 15 * 8, y = 9 * 8},
{x = 16 * 8, y = 4 * 8},
{x = 17 * 8, y = 8 * 8},
{x = 17 * 8, y = 11 * 8},
{x = 20 * 8, y = 4 * 8},
{x = 23 * 8, y = 5 * 8},
{x = 22 * 8, y = 10 * 8 + 4},
{x = 27 * 8, y = 10 * 8 + 4},
{x = -4 + 5 * 8, y = 9 * 8}
}
Context.office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
end, end,
background = "office", background = "office",
draw = function() draw = function()
if Window.get_current_id() == "game" then if Window.get_current_id() == "game" then
Sprite.draw_at("norman", 13 * 8, 9 * 8) Sprite.draw_at("norman", 13 * 8, 9 * 8)
Sprite.draw_at("dev_buddy", 15 * 8, 9 * 8)
Sprite.draw_at("dev_project_manager", 6 * 8, 4 * 8) Sprite.draw_list(Context.office_sprites)
Sprite.draw_at("dev_hr_girl", 12 * 8, 4 * 8)
Sprite.draw_at("dev_introvert", -4 + 5 * 8, 9 * 8)
Sprite.draw_at("dev_extrovert", 20 * 8, 4 * 8)
Sprite.draw_at("dev_girl", 23 * 8, 5 * 8)
Sprite.draw_at("dev_boy", 10 * 8, 11 * 8 + 4)
Sprite.draw_at("dev_guru", 22 * 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

@@ -16,7 +16,7 @@ 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()
@@ -36,13 +36,13 @@ Screen.register({
Sprite.draw_at("norman", norman_x, norman_y) Sprite.draw_at("norman", norman_x, norman_y)
Print.text_center("day " .. Context.day_count, cx, 10, Config.colors.white) Print.text_center_contour("day " .. Context.day_count, cx, 10, Config.colors.black)
local narrative = "reflecting on my past and present\n...\nboth eventually flushed." local narrative = "reflecting on my past and present...\nboth eventually flushed..."
local wrapped = UI.word_wrap(narrative, 38) local wrapped = UI.word_wrap(narrative, 38)
local text_y = 24 local text_y = 24
for _, line in ipairs(wrapped) do for _, line in ipairs(wrapped) do
Print.text_center(line, cx, text_y, Config.colors.light_grey) Print.text_center_contour(line, cx, text_y, Config.colors.black)
text_y = text_y + 8 text_y = text_y + 8
end end
@@ -51,31 +51,37 @@ Screen.register({
local decay_pct = Meter.get_decay_percentage() local decay_pct = Meter.get_decay_percentage()
local decay_text = string.format("-%d%%", decay_pct) local decay_text = string.format("-%d%%", decay_pct)
local combo_mult = Meter.get_combo_multiplier() local combo_mult = Meter.get_combo_multiplier()
local combo_pct = math.floor((combo_mult - 1) * 100) local ism_bm_combo_pct = math.floor((combo_mult - 1) * 100)
local mult_text = string.format("+%d%%", combo_pct) local wpm_combo_pct = math.floor((1 / combo_mult - 1) * 100 + 0.5)
local meter_start_y = text_y + 10 local meter_start_y = text_y + 10
local meter_list = { local meter_list = {
{ key = "wpm", label = "Work Productivity Meter" }, { key = "wpm", label = "Work Productivity Meter", color = Meter.COLOR_WPM },
{ key = "ism", label = "Impostor Syndrome Meter" }, { key = "ism", label = "Impostor Syndrome Meter", color = Meter.COLOR_ISM },
{ key = "bm", label = "Burnout Meter" }, { key = "bm", label = "Burnout Meter", color = Meter.COLOR_BM },
} }
for i, meter in ipairs(meter_list) do for i, meter in ipairs(meter_list) do
local y = meter_start_y + (i - 1) * 20 local y = meter_start_y + (i - 1) * 20
Print.text_center(meter.label, cx, y, Config.colors.white) Print.text_center_contour(meter.label, cx, y, meter.color, false, 1, Config.colors.white)
local bar_y = y + 8 local bar_y = y + 8
local fill_w = math.max(0, math.floor((m[meter.key] / max_val) * bar_w)) local fill_w = math.max(0, math.floor((m[meter.key] / max_val) * bar_w))
rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG) rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG)
if fill_w > 0 then if fill_w > 0 then
rect(bar_x, bar_y, fill_w, bar_h, Config.colors.blue) rect(bar_x, bar_y, fill_w, bar_h, meter.color)
end end
local mult_text
if meter.key == "wpm" then
mult_text = string.format("%+d%%", wpm_combo_pct)
else
mult_text = string.format("+%d%%", ism_bm_combo_pct)
end
local decay_w = print(decay_text, 0, -6, 0, false, 1) local decay_w = print(decay_text, 0, -6, 0, false, 1)
Print.text(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue) Print.text_contour(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white)
Print.text(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue) Print.text_contour(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white)
end end
if Ascension.get_level() > 0 then if Ascension.get_level() > 0 then

View File

@@ -8,6 +8,28 @@ Screen.register({
}, },
init = function() init = function()
Audio.music_play_room_work() Audio.music_play_room_work()
local possible_sprites = {
"matrix_trinity",
"matrix_neo",
{id="matrix_oraculum", y_correct=1 * 8},
"matrix_architect"
}
local possible_positions = {
{x = 5 * 8, y = 11 * 8},
{x = 7 * 8, y = 11 * 8},
{x = 9 * 8, y = 11 * 8},
{x = 11 * 8, y = 11 * 8},
{x = 13 * 8, y = 11 * 8},
{x = 15 * 8, y = 11 * 8},
{x = 18 * 8, y = 11 * 8},
{x = 21 * 8, y = 11 * 8},
{x = 24 * 8, y = 11 * 8},
{x = 27 * 8, y = 11 * 8},
}
Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
end, end,
background = "street", background = "street",
draw = function() draw = function()
@@ -16,10 +38,8 @@ Screen.register({
Sprite.draw_at("sumphore", 9 * 8, 2 * 8) Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8)
Sprite.draw_at("dev_guard", 22 * 8, 2 * 8) Sprite.draw_at("dev_guard", 22 * 8, 2 * 8)
Sprite.draw_at("matrix_trinity", 5 * 8, 11 * 8)
Sprite.draw_at("matrix_neo", 7 * 8, 11 * 8) Sprite.draw_list(Context.walking_to_office_sprites)
Sprite.draw_at("matrix_oraculum", 9 * 8, 12 * 8)
Sprite.draw_at("matrix_architect", 11 * 8, 11 * 8)
end end
end end
}) })

View File

@@ -1,6 +0,0 @@
Situation.register({
id = "drink_coffee",
handle = function()
Audio.sfx_select()
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

@@ -3,7 +3,7 @@ local _sprites = {}
local _active_sprites = {} local _active_sprites = {}
local function draw_sprite_instance(sprite_data, params) local function draw_sprite_instance(sprite_data, params)
local colorkey = params.colorkey or sprite_data.colorkey or 0 local colorkey = params.colorkey or sprite_data.colorkey or Config.colors.transparent
local scale = params.scale or sprite_data.scale or 1 local scale = params.scale or sprite_data.scale or 1
local flip_x = params.flip_x or sprite_data.flip_x or 0 local flip_x = params.flip_x or sprite_data.flip_x or 0
local flip_y = params.flip_y or sprite_data.flip_y or 0 local flip_y = params.flip_y or sprite_data.flip_y or 0
@@ -73,6 +73,70 @@ function Sprite.generate_table(width, height, starting_s, x_base, y_base, x_step
return sprites return sprites
end end
--- Immediately draws a list of sprites
--- @within Sprite
--- @param sprite_list table An array of tables, each containing: `id` (string) sprite identifier, `x` (number) x-coordinate, `y` (number) y-coordinate, and optional `colorkey`, `scale`, `flip_x`, `flip_y`, `rot` parameters.
function Sprite.draw_list(sprite_list)
for _, sprite_info in ipairs(sprite_list) do
local sprite_data = _sprites[sprite_info.id]
if not sprite_data then
trace("Error: Attempted to draw non-registered sprite with id: " .. sprite_info.id)
else
draw_sprite_instance(sprite_data, sprite_info)
end
end
end
--- Given a list of sprite IDs (or sprite entries with correction offsets) and a list of possible positions, randomly assigns each sprite to a unique position and returns a drawable list.
--- @within Sprite
--- @param sprite_ids table An array of sprite identifier values or tables.
--- Each entry may be either:
--- - string: sprite ID to draw.
--- - table: { sprite_id = string, x_correct = number, y_correct = number }.
--- @param positions table An array of tables, each containing `x` and `y` fields for possible sprite positions.
function Sprite.list_randomize(sprite_ids, positions)
if #sprite_ids > #positions then
trace("Error: More sprite IDs than available positions in Sprite.draw_randomized")
return
end
local shuffled_positions = {}
for i, pos in ipairs(positions) do
shuffled_positions[i] = pos
end
for i = #shuffled_positions, 2, -1 do
local j = math.random(i)
shuffled_positions[i], shuffled_positions[j] = shuffled_positions[j], shuffled_positions[i]
end
local drawable_list = {}
for i, sprite_entry in ipairs(sprite_ids) do
local sprite_id = sprite_entry
local x_correct = 0
local y_correct = 0
if type(sprite_entry) == "table" then
sprite_id = sprite_entry.sprite_id or sprite_entry.id
x_correct = sprite_entry.x_correct or 0
y_correct = sprite_entry.y_correct or 0
end
local sprite_data = _sprites[sprite_id]
if not sprite_data then
trace("Error: Attempted to draw non-registered sprite with id: " .. tostring(sprite_id))
else
local pos = shuffled_positions[i]
table.insert(drawable_list, {
id = sprite_id,
x = pos.x + x_correct,
y = pos.y + y_correct
})
end
end
return drawable_list
end
--- Schedules a sprite for drawing. --- Schedules a sprite for drawing.
--- @within Sprite --- @within Sprite
--- @param id string The unique identifier of the sprite.<br/> --- @param id string The unique identifier of the sprite.<br/>

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,18 @@ 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. --- Checks if Up is pressed or held (with repeat).
--- @within Input --- @within Input
function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end function Input.up_repeat() return btnp(INPUT_KEY_UP, 20, 4) end
--- Checks if Toggle Popup is pressed. --- Checks if Down is pressed or held (with repeat).
--- @within Input --- @within Input
function Input.toggle_popup() return keyp(INPUT_KEY_ENTER) end function Input.down_repeat() return btnp(INPUT_KEY_DOWN, 20, 4) end

View File

@@ -17,6 +17,7 @@ end
--- @within Main --- @within Main
function TIC() function TIC()
init_game() init_game()
Mouse.update()
local now = time() local now = time()
if Context.last_frame_time == 0 then if Context.last_frame_time == 0 then

View File

@@ -0,0 +1,81 @@
--- @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]
-- trace mouse position and tile for testing purposes
if Context.test_mode and Context.mouse_trace then
trace("Mouse: (" .. _mx .. "," .. _my .. "), tile: (" .. math.floor(_mx / 8) .. "," .. math.floor(_my / 8) .. ")")
end
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,29 @@ 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)
end
--- Prints text with a contour (outline) instead of shadow.
--- @within Print
--- @param text string The text to print.<br/>
--- @param x number The x-coordinate.<br/>
--- @param y number The y-coordinate.<br/>
--- @param color number The color of the text.<br/>
--- @param[opt] fixed boolean If true, uses fixed-width font.<br/>
--- @param[opt] scale number The scaling factor (also used for outline thickness).<br/>
--- @param[opt] contour_color number Outline color; defaults to black; if equal to text color, uses white.<br/>
function Print.text_contour(text, x, y, color, fixed, scale, contour_color)
scale = scale or 1
local cc = contour_color
if cc == nil then cc = Config.colors.black end
if color == cc then cc = Config.colors.white end
local ox = { -scale, scale, 0, 0, -scale, scale, -scale, scale }
local oy = { 0, 0, -scale, scale, -scale, -scale, scale, scale }
for i = 1, 8 do
print(text, x + ox[i], y + oy[i], cc, fixed, scale)
end
print(text, x, y, color, fixed, scale) print(text, x, y, color, fixed, scale)
end end
@@ -24,7 +46,23 @@ 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
--- Prints centered text with contour instead of shadow.
--- @within Print
--- @param text string The text to print.<br/>
--- @param x number The x-coordinate for centering.<br/>
--- @param y number The y-coordinate.<br/>
--- @param color number The color of the text.<br/>
--- @param[opt] fixed boolean If true, uses fixed-width font.<br/>
--- @param[opt] scale number The scaling factor.<br/>
--- @param[opt] contour_color number Outline color; defaults to black; if equal to text color, uses white.<br/>
function Print.text_center_contour(text, x, y, color, fixed, scale, contour_color)
scale = scale or 1
local text_width = print(text, 0, -6 * scale, 0, fixed, scale)
local centered_x = x - (text_width / 2)
Print.text_contour(text, centered_x, y, color, fixed, scale, contour_color)
end

View File

@@ -0,0 +1,81 @@
--- @section TextInput
TextInput = {}
local LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local _pos = {}
local _cursor = 1
local _max_len = 3
--- Initialises a new text input session.
--- @within TextInput
--- @param max_len number Maximum character count (default 3).
function TextInput.init(max_len)
_max_len = max_len or 3
_pos = {}
for i = 1, _max_len do _pos[i] = 1 end
_cursor = 1
end
--- Advances to the next letter at the cursor position (wraps Z→A).
--- @within TextInput
function TextInput.next_letter()
_pos[_cursor] = (_pos[_cursor] % #LETTERS) + 1
end
--- Goes back to the previous letter at the cursor position (wraps A→Z).
--- @within TextInput
function TextInput.prev_letter()
_pos[_cursor] = ((_pos[_cursor] - 2) % #LETTERS) + 1
end
--- Confirms the current letter and advances the cursor to the next position.
--- When called on the last position the cursor moves into the done state.
--- @within TextInput
function TextInput.select_letter()
if _cursor <= _max_len then _cursor = _cursor + 1 end
end
--- Moves the cursor one position to the right (stops at last position).
--- @within TextInput
function TextInput.next_position()
if _cursor < _max_len then _cursor = _cursor + 1 end
end
--- Moves the cursor one position to the left (stops at first position).
--- Also steps back out of the done state.
--- @within TextInput
function TextInput.prev_position()
if _cursor > 1 then _cursor = _cursor - 1 end
end
--- Returns the assembled name string.
--- @within TextInput
--- @return string
function TextInput.get_name()
local s = ""
for i = 1, _max_len do s = s .. LETTERS:sub(_pos[i], _pos[i]) end
return s
end
--- Returns the current 1-based cursor position.
--- @within TextInput
--- @return number
function TextInput.get_position()
return _cursor
end
--- Returns the letter at the given 1-based position.
--- @within TextInput
--- @param i number
--- @return string
function TextInput.get_letter(i)
return LETTERS:sub(_pos[i], _pos[i])
end
--- Returns true when all positions have been confirmed.
--- @within TextInput
--- @return boolean
function TextInput.is_done()
return _cursor > _max_len
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.
@@ -68,12 +90,54 @@ end
--- @param[opt] bg_color number The background fill color (default: Config.colors.dark_grey).<br/> --- @param[opt] bg_color number The background fill color (default: Config.colors.dark_grey).<br/>
--- @param[opt] border_color number The border color (default: Config.colors.white).<br/> --- @param[opt] border_color number The border color (default: Config.colors.white).<br/>
--- @param[opt] center_text boolean Whether to center each line inside the box. Defaults to false.<br/> --- @param[opt] center_text boolean Whether to center each line inside the box. Defaults to false.<br/>
local function draw_rounded_rect_fill(x, y, w, h, corner_radius, color)
local inner_w = w - corner_radius * 2
local inner_h = h - corner_radius * 2
if inner_w > 0 and inner_h > 0 then
rect(x + corner_radius, y + corner_radius, inner_w, inner_h, color)
end
if inner_w > 0 and corner_radius > 0 then
rect(x + corner_radius, y, inner_w, corner_radius, color)
rect(x + corner_radius, y + h - corner_radius, inner_w, corner_radius, color)
end
if corner_radius > 0 and inner_h > 0 then
rect(x, y + corner_radius, corner_radius, inner_h, color)
rect(x + w - corner_radius, y + corner_radius, corner_radius, inner_h, color)
end
for row = 0, corner_radius - 1 do
for col = 0, corner_radius - 1 do
if col + row >= corner_radius - 1 then
pix(x + corner_radius - 1 - col, y + corner_radius - 1 - row, color)
pix(x + w - corner_radius + col, y + corner_radius - 1 - row, color)
pix(x + corner_radius - 1 - col, y + h - corner_radius + row, color)
pix(x + w - corner_radius + col, y + h - corner_radius + row, color)
end
end
end
end
local function draw_rounded_rect_border(x, y, w, h, corner_radius, color)
line(x + corner_radius, y, x + w - corner_radius - 1, y, color)
line(x + corner_radius, y + h - 1, x + w - corner_radius - 1, y + h - 1, color)
line(x, y + corner_radius, x, y + h - corner_radius - 1, color)
line(x + w - 1, y + corner_radius, x + w - 1, y + h - corner_radius - 1, color)
pix(x + corner_radius - 1, y + 1, color)
pix(x + 1, y + corner_radius - 1, color)
pix(x + w - corner_radius, y + 1, color)
pix(x + w - 2, y + corner_radius - 1, color)
pix(x + corner_radius - 1, y + h - 2, color)
pix(x + 1, y + h - corner_radius, color)
pix(x + w - corner_radius, y + h - 2, color)
pix(x + w - 2, y + h - corner_radius, color)
end
function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_color, border_color, center_text) function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_color, border_color, center_text)
color = color or Config.colors.white color = color or Config.colors.white
bg_color = bg_color or Config.colors.dark_grey bg_color = bg_color or Config.colors.black
border_color = border_color or Config.colors.white border_color = border_color or Config.colors.white
center_text = center_text or false center_text = center_text or false
local r = 3
local padding = 4 local padding = 4
local line_height = 8 local line_height = 8
local inner_x = box_x + padding local inner_x = box_x + padding
@@ -88,7 +152,7 @@ function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_c
base_y = inner_y + math.floor((visible_height - text_height) / 2) base_y = inner_y + math.floor((visible_height - text_height) / 2)
end end
rect(box_x, box_y, box_w, box_h, bg_color) draw_rounded_rect_fill(box_x, box_y, box_w, box_h, r, bg_color)
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
local ly = base_y + (i - 1) * line_height - scroll_y local ly = base_y + (i - 1) * line_height - scroll_y
@@ -101,7 +165,7 @@ function UI.draw_textbox(text, box_x, box_y, box_w, box_h, scroll_y, color, bg_c
end end
end end
rectb(box_x, box_y, box_w, box_h, border_color) draw_rounded_rect_border(box_x, box_y, box_w, box_h, r, border_color)
end end
--- Wraps text. --- Wraps text.

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

@@ -0,0 +1,197 @@
--- @section CreditsWindow
local _time = 0.0
local _scroll_x = 0.0
local _scroll_total_w = 0
local _scroll_chars = {}
local _title_chars = {}
local _title_total_w = 0
local _stars = {}
local TITLE = "TELETYPE GAMES"
local SCROLL_PARTS = {
"WEB: GAMES.TELETYPE.HU",
"BBS: GAMES.TELETYPE.HU:2323",
"IRC: LIBERA.CHAT #TELETYPEGAMES",
"YOUTUBE.COM/@TELETYPEGAMES",
}
local SCROLL_SEP = " * "
local SCROLL_SPEED = 55.0
local SCROLL_Y = 129
local SCROLL_ZONE_COLS = { 7, 4, 9 }
local TITLE_Y = 4
local TITLE_FALL_DUR = 0.45
local TITLE_DELAY_STEP = 0.18
local RASTER_COLS = { 1, 3, 9, 10, 11, 4, 11, 10, 9, 3, 1 }
local RASTER_Y_TOP = 26
local RASTER_Y_BOT = 110
local AUTHORS = {
"Mr. Zero - Zsolt Tasnadi",
"Mr. One - Balazs Tari",
"Mr. Two - Zoltan Timar",
"Mr. Three - Bela Mezo",
}
local AUTHORS_BASE_Y = 56
local AUTHORS_LINE_H = 12
local AUTHORS_ENTRY_DT = 0.65
local AUTHORS_ENTRY_V = 2.5
local RAINBOW = { 4, 9, 3, 7, 13, 2, 9, 4 }
local NUM_STARS = 40
--- Initialises credits state and pre-computes character metrics.
--- @within CreditsWindow
function CreditsWindow.init()
_time = 0.0
_scroll_x = Config.screen.width + 4.0
_title_chars = {}
_title_total_w = 0
for i = 1, #TITLE do
local ch = TITLE:sub(i, i)
local w = print(ch, 0, -100, 0, false, 2)
_title_chars[i] = { ch = ch, ox = _title_total_w, w = w }
_title_total_w = _title_total_w + w
end
_scroll_chars = {}
_scroll_total_w = 0
local function append_str(str, col)
for i = 1, #str do
local ch = str:sub(i, i)
local w = print(ch, 0, -100, 0, false, 1)
_scroll_chars[#_scroll_chars + 1] = { ch = ch, ox = _scroll_total_w, w = w, col = col }
_scroll_total_w = _scroll_total_w + w
end
end
for _, part in ipairs(SCROLL_PARTS) do
append_str(part, RAINBOW[math.random(#RAINBOW)])
append_str(SCROLL_SEP, Config.colors.white)
end
_stars = {}
for i = 1, NUM_STARS do
_stars[i] = {
x = math.random(0, Config.screen.width - 1) + 0.0,
y = math.random(0, Config.screen.height - 1),
spd = (i % 3 + 1) * 10.0,
col = ({ 1, 2, 4, 4 })[(i % 4) + 1],
}
end
end
local function draw_stars()
for _, s in ipairs(_stars) do
pix(math.floor(s.x), s.y, s.col)
end
end
local function draw_rasters()
local cy = RASTER_Y_TOP + math.floor(math.sin(_time * 1.3) * 6)
for i, col in ipairs(RASTER_COLS) do
local y = cy + i - 1
if y >= 0 and y < Config.screen.height then
line(0, y, Config.screen.width - 1, y, col)
end
end
local cy2 = RASTER_Y_BOT + math.floor(math.sin(_time * 1.7 + 1.5) * 5)
for i, col in ipairs(RASTER_COLS) do
local y = cy2 + i - 1
if y >= 0 and y < Config.screen.height then
line(0, y, Config.screen.width - 1, y, col)
end
end
end
local function bounce_out(p)
local n1, d1 = 7.5625, 2.75
if p < 1 / d1 then
return n1 * p * p
elseif p < 2 / d1 then
p = p - 1.5 / d1; return n1 * p * p + 0.75
elseif p < 2.5 / d1 then
p = p - 2.25 / d1; return n1 * p * p + 0.9375
else
p = p - 2.625 / d1; return n1 * p * p + 0.984375
end
end
local function draw_title()
local sx = math.floor((Config.screen.width - _title_total_w) / 2)
local n = #_title_chars
local max_dist = (n - 1) / 2.0
for i, tc in ipairs(_title_chars) do
local dist = math.abs(i - (n + 1) / 2.0)
local delay = (max_dist - dist) * TITLE_DELAY_STEP
local t = math.max(0, _time - delay)
local p = math.min(1, t / TITLE_FALL_DUR)
local y = math.floor(-14 + bounce_out(p) * (TITLE_Y + 14))
print(tc.ch, sx + tc.ox + 1, y + 1, 0, false, 2)
print(tc.ch, sx + tc.ox, y, Config.colors.light_blue, false, 2)
end
end
local function draw_authors()
local col = Config.colors.light_blue
for i, lbl in ipairs(AUTHORS) do
local enter_t = math.max(0, _time - (i - 1) * AUTHORS_ENTRY_DT)
local slide = math.max(0, 1 - enter_t * AUTHORS_ENTRY_V)
local x_off = math.floor(slide * (Config.screen.width + 40))
local yo = (slide < 0.01) and math.floor(math.sin(_time * 2.0 + i * 1.1) * 2) or 0
Print.text(lbl, 12 + x_off, AUTHORS_BASE_Y + (i - 1) * AUTHORS_LINE_H + yo, col)
end
end
local function draw_scroller()
local third = Config.screen.width / 3
for pass = 0, 1 do
local base = _scroll_x + pass * _scroll_total_w
for _, sc in ipairs(_scroll_chars) do
local x = math.floor(base + sc.ox)
if x >= Config.screen.width then break end
if x + sc.w > 0 then
local zone = math.max(1, math.min(3, math.floor(x / third) + 1))
print(sc.ch, x, SCROLL_Y, SCROLL_ZONE_COLS[zone], false, 1)
end
end
end
end
--- Draws the credits window.
--- @within CreditsWindow
function CreditsWindow.draw()
cls(Config.colors.black)
draw_stars()
draw_rasters()
draw_title()
Print.text_center("Authors", Config.screen.width / 2, 47, Config.colors.light_grey)
draw_authors()
draw_scroller()
end
--- Updates credits window logic.
--- @within CreditsWindow
function CreditsWindow.update()
_time = _time + Context.delta_time
for _, s in ipairs(_stars) do
s.x = s.x + s.spd * Context.delta_time
if s.x >= Config.screen.width then s.x = s.x - Config.screen.width end
end
_scroll_x = _scroll_x - SCROLL_SPEED * Context.delta_time
if _scroll_x <= -_scroll_total_w then
_scroll_x = _scroll_x + _scroll_total_w
end
if Input.back() or Input.select() then
Window.set_current("menu")
end
end

View File

@@ -24,7 +24,7 @@ function DiscussionWindow.draw()
TEXTBOX_W, TEXTBOX_H, TEXTBOX_W, TEXTBOX_H,
Context.discussion.scroll_y, Context.discussion.scroll_y,
Config.colors.white, Config.colors.white,
Config.colors.dark_grey, Config.colors.black,
Config.colors.light_blue, Config.colors.light_blue,
true true
) )
@@ -37,9 +37,13 @@ function DiscussionWindow.draw()
local selected = answers[Context.discussion.selected_answer] local selected = answers[Context.discussion.selected_answer]
local label = selected.label local label = selected.label
local answer_text_y = bar_y + 4 local answer_text_y = bar_y + 4
Print.text("<", 2, answer_text_y, Config.colors.light_blue) local left_arrow_color = Input.left() and Config.colors.white or Config.colors.orange
Print.text_center(label, Config.screen.width / 2, answer_text_y, Config.colors.item) local right_arrow_color = Input.right() and Config.colors.white or Config.colors.orange
Print.text(">", Config.screen.width - 6, answer_text_y, Config.colors.light_blue) local left_arrow_contour_color = Input.left() and Config.colors.white or Config.colors.black
local right_arrow_contour_color = Input.right() and Config.colors.white or Config.colors.black
Print.text_center_contour("<", 6, answer_text_y, left_arrow_color, false, 1, left_arrow_contour_color)
Print.text_center(label, Config.screen.width / 2, answer_text_y, Config.colors.orange)
Print.text_center_contour(">", Config.screen.width - 6, answer_text_y, right_arrow_color, false, 1, right_arrow_contour_color)
end end
end end

View File

@@ -30,9 +30,15 @@ function EndWindow.draw()
Print.text(yes_text, centerX - 40, y, yes_color) Print.text(yes_text, centerX - 40, y, yes_color)
Print.text(no_text, centerX + 10, y, no_color) Print.text(no_text, centerX + 10, y, no_color)
elseif Context._end.state == "ending" then elseif Context._end.state == "ending" then
Print.text_center("Game over -- good ending.", Config.screen.width / 2, 50, Config.colors.light_blue) local cx = Config.screen.width / 2
Print.text_center("Congratulations!", Config.screen.width / 2, 70, Config.colors.white) local name = Context.player_name or "AAA"
Print.text_center("Press Z to return to menu", Config.screen.width / 2, 110, Config.colors.light_grey) local code = CodeGenerator.encrypt(name)
Print.text_center("Game over -- good ending.", cx, 40, Config.colors.light_blue)
Print.text_center("Congrats " .. name .. "!", cx, 54, Config.colors.white)
Print.text_center("Your personal code:", cx, 72, Config.colors.light_grey)
Print.text_center(code, cx, 84, Config.colors.white, false, 3)
Print.text_center("Write it down!", cx, 112, Config.colors.item)
Print.text_center("Press Z to return to menu", cx, 126, Config.colors.dark_grey)
end end
end end
@@ -52,7 +58,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 +75,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

@@ -0,0 +1,59 @@
--- @section GameOverWindow
local GAME_OVER_ART = [[
_###_ __#__ #___# #####
#____ _#_#_ ##_## #____
#_### ##### #_#_# ####_
#___# #___# #___# #____
_###_ #___# #___# #####
_###_ #___# ##### ####_
#___# #___# #____ #___#
#___# _#_#_ ####_ ####_
#___# __#__ #____ #_#__
_###_ __#__ ##### #__##
]]
local REASON_MESSAGES = {
ism = "Your impostor syndrome consumed you.",
bm = "You burned out like a cheap candle.",
days = "100 days passed. The cycle never broke.",
}
--- Shows the game over screen.
--- @within GameOverWindow
--- @param reason string One of "ism", "bm", "days".
function GameOverWindow.show(reason)
GameOverWindow.reason = reason
Context.game_in_progress = false
Glitch.show()
Window.set_current("game_over")
end
--- Draws the game over screen.
--- @within GameOverWindow
function GameOverWindow.draw()
cls(Config.colors.black)
local cx = Config.screen.width / 2
local bounds = AsciiArt.draw(GAME_OVER_ART, {
char_w = 4,
char_h = 6,
line_gap = 1,
word_gap = 10,
color = Config.colors.red,
})
local msg = REASON_MESSAGES[GameOverWindow.reason] or ""
Print.text_center(msg, cx, bounds.bottom + 8, Config.colors.white)
Print.text_center("Press Z to restart", cx, Config.screen.height - 10, Config.colors.light_grey)
end
--- Updates the game over screen logic.
--- @within GameOverWindow
function GameOverWindow.update()
if Input.select() then
Context.reset()
MenuWindow.refresh_menu_items()
Window.set_current("menu")
end
end

View File

@@ -31,7 +31,7 @@ function BriefIntroWindow.update()
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

@@ -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,64 @@
--- @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.black)
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, Config.colors.transparent, 4)
spr(273, nx + 32, ny, Config.colors.transparent, 4)
spr(288, nx, ny + 32, Config.colors.transparent, 4)
spr(289, nx + 32, ny + 32, Config.colors.transparent, 4)
spr(304, nx, ny + 64, Config.colors.transparent, 4)
spr(305, nx + 32, ny + 64, Config.colors.transparent, 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" cls(Config.colors.blue
if Context.test_mode then )
title = title .. " (TEST MODE)" MenuWindow.draw_header()
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 +68,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()
@@ -33,17 +102,13 @@ function MenuWindow.update()
end end
end end
--- Starts a new game from the menu. --- Opens player name entry then starts a new game.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.new_game() function MenuWindow.new_game()
Context.new_game() PlayerNameWindow.init(function()
end Context.new_game()
end)
--- Loads a game from the menu. Window.set_current("player_name")
--- @within MenuWindow
function MenuWindow.load_game()
Context.load_game()
GameWindow.set_state("game")
end end
--- Saves the current game from the menu. --- Saves the current game from the menu.
@@ -64,11 +129,24 @@ 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
--- Opens the player name entry screen (test mode shortcut).
--- @within MenuWindow
function MenuWindow.player_name()
PlayerNameWindow.init()
Window.set_current("player_name")
end
--- Opens the credits screen.
--- @within MenuWindow
function MenuWindow.credits()
CreditsWindow.init()
Window.set_current("credits")
end end
--- Opens the audio test menu. --- Opens the audio test menu.
@@ -85,7 +163,15 @@ function MenuWindow.continued()
GameWindow.set_state("continued") GameWindow.set_state("continued")
end end
--- Opens the minigame ddr test menu. --- Opens the end screen for testing.
--- @within MenuWindow
function MenuWindow.end_screen()
Context._end.state = "ending"
Context._end.selection = 1
GameWindow.set_state("end")
end
--- Opens the DDR minigame test.
--- @within MenuWindow --- @within MenuWindow
function MenuWindow.ddr_test() function MenuWindow.ddr_test()
AudioTestWindow.init() AudioTestWindow.init()
@@ -93,26 +179,36 @@ function MenuWindow.ddr_test()
MinigameDDRWindow.start("menu", "generated", { special_mode = "only_nothing" }) 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 = "Controls", decision = MenuWindow.controls})
table.insert(_menu_items, {label = "Configuration", decision = MenuWindow.configuration}) table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits})
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})
table.insert(_menu_items, {label = "End Screen", decision = MenuWindow.end_screen})
table.insert(_menu_items, {label = "Player Name", decision = MenuWindow.player_name})
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,7 +1,44 @@
--- @section MinigameDDRWindow --- @section MinigameDDRWindow
---@class MinigameDDRState
---@field special_mode string
---@field bar_fill number
---@field max_fill number
---@field fill_per_hit number
---@field miss_penalty number
---@field bar_x number
---@field bar_y number
---@field bar_width number
---@field bar_height number
---@field arrow_size number
---@field arrow_spawn_timer number
---@field arrow_spawn_interval number
---@field arrow_fall_speed number
---@field arrows table
---@field target_y number
---@field target_arrows table
---@field hit_threshold number
---@field button_pressed_timers table
---@field button_press_duration number
---@field input_cooldowns table
---@field input_cooldown_duration number
---@field frame_counter number
---@field current_song table?
---@field pattern_index number
---@field use_pattern boolean
---@field generated_length number
---@field return_window string?
---@field win_timer number
---@field on_win fun(ctx: MinigameDDRState)|nil
---@field total_misses number
---@field total_hits number
---@field special_mode_condition boolean
---@field special_mode_counter number
---@field debug_song_key string|nil
---@field debug_status string|nil
--- Background drawing for DDR minigame. --- Background drawing for DDR minigame.
--- @witin MinigameDDRWindow --- @within MinigameDDRWindow
function MinigameDDRWindow.draw_background() 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_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} 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}
@@ -12,7 +49,7 @@ end
--- Gets initial DDR minigame configuration. --- Gets initial DDR minigame configuration.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @return result table The default DDR minigame configuration. ---@return MinigameDDRState
function MinigameDDRWindow.init_context() function MinigameDDRWindow.init_context()
local arrow_size = 12 local arrow_size = 12
local arrow_spacing = 30 local arrow_spacing = 30
@@ -33,7 +70,7 @@ function MinigameDDRWindow.init_context()
arrow_spawn_interval = 45, arrow_spawn_interval = 45,
arrow_fall_speed = 1.5, arrow_fall_speed = 1.5,
arrows = {}, arrows = {},
target_y = 115, target_y = 120,
target_arrows = { target_arrows = {
{ dir = "left", x = start_x }, { dir = "left", x = start_x },
{ dir = "down", x = start_x + arrow_size + arrow_spacing }, { dir = "down", x = start_x + arrow_size + arrow_spacing },
@@ -60,25 +97,29 @@ function MinigameDDRWindow.init_context()
} }
end end
--- Builds song data (and optional generated pattern) for the minigame.
--- @within MinigameDDRWindow
function MinigameDDRWindow.prepareSong(song, generated_length, special_mode) function MinigameDDRWindow.prepareSong(song, generated_length, special_mode)
local current_song = Util.deepcopy(song) 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 current_song.generated then if special_mode == "only_special" then
local pattern = musicator_generate_pattern(generated_length, current_song.bpm, current_song.spd * 4) for i, _ in ipairs(current_song.pattern) do
current_song.pattern = pattern current_song.pattern[i].special = (i % 5 == 0)
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
end end
end
return current_song return current_song
end end
--- Handles hit feedback and special-mode scoring for one arrow.
--- @within MinigameDDRWindow
---@param game_context MinigameDDRState
function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context) function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context)
local special_mode = game_context.special_mode local special_mode = game_context.special_mode
@@ -89,6 +130,7 @@ function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context)
Audio.sfx_arrowhit(arrow.note) Audio.sfx_arrowhit(arrow.note)
game_context.special_mode_counter = game_context.special_mode_counter + 1 game_context.special_mode_counter = game_context.special_mode_counter + 1
else else
game_context.total_misses = game_context.total_misses + 1
if game_context.special_mode_condition then Audio.sfx_bloop() end if game_context.special_mode_condition then Audio.sfx_bloop() end
game_context.special_mode_condition = false game_context.special_mode_condition = false
end end
@@ -100,22 +142,26 @@ function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context)
game_context.bar_fill = game_context.bar_fill - game_context.fill_per_hit game_context.bar_fill = game_context.bar_fill - game_context.fill_per_hit
end end
else else
game_context.total_misses = game_context.total_misses + 1
if game_context.special_mode_condition then Audio.sfx_bloop() end if game_context.special_mode_condition then Audio.sfx_bloop() end
game_context.special_mode_condition = false game_context.special_mode_condition = false
end end
elseif special_mode == "only_nothing" then elseif special_mode == "only_nothing" then
game_context.total_misses = game_context.total_misses + 1
if game_context.special_mode_condition then Audio.sfx_bloop() end if game_context.special_mode_condition then Audio.sfx_bloop() end
game_context.special_mode_condition = false game_context.special_mode_condition = false
end end
end end
--- Ends the minigame: win timer, sfx, and special-mode pass/fail bookkeeping.
--- @within MinigameDDRWindow
function MinigameDDRWindow.on_end(game_context) function MinigameDDRWindow.on_end(game_context)
Audio.sfx_select() Audio.sfx_select()
game_context.win_timer = Config.timing.minigame_win_duration game_context.win_timer = Config.timing.minigame_win_duration
local num_special = 0 local num_special = 0
for _,v in ipairs(game_context.current_song.pattern) do for _, v in ipairs(game_context.current_song.pattern) do
if game_context.special_mode == "only_left" then if game_context.special_mode == "only_left" then
num_special = num_special + ((v.dir == "left" and 1) or 0) num_special = num_special + ((v.dir == "left" and 1) or 0)
else else
@@ -123,16 +169,16 @@ function MinigameDDRWindow.on_end(game_context)
end end
end end
local sm = game_context.special_mode
local was_ok = true local was_ok = true
if game_context.special_mode == "normal" then if sm == "normal" or sm == "only_special" or sm == "only_left" 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 was_ok = game_context.special_mode_counter == num_special
end end
game_context.special_mode_condition = game_context.special_mode_condition and was_ok game_context.special_mode_condition = game_context.special_mode_condition and was_ok
if game_context.special_mode_condition and sm ~= "normal" then
game_context.bar_fill = game_context.max_fill
end
end end
--- Initializes DDR minigame state. --- Initializes DDR minigame state.
@@ -192,12 +238,14 @@ end
local function spawn_arrow() local function spawn_arrow()
trace("random arrow") trace("random arrow")
---@type MinigameDDRState
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
local y0 = mg.bar_y + mg.bar_height + 10
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, {
dir = target.dir, dir = target.dir,
x = target.x, x = target.x,
y = mg.bar_y + mg.bar_height + 10 y = y0
}) })
end end
@@ -205,13 +253,15 @@ end
--- @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, note, special) local function spawn_arrow_dir(direction, note, special)
---@type MinigameDDRState
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
local y0 = mg.bar_y + mg.bar_height + 10
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 = y0,
note = note, note = note,
special = special special = special
}) })
@@ -225,6 +275,7 @@ end
--- @param arrow table The arrow data. --- @param arrow table The arrow data.
--- @return boolean True if the arrow is hit, false otherwise. --- @return boolean True if the arrow is hit, false otherwise.
local function check_hit(arrow) local function check_hit(arrow)
---@type MinigameDDRState
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
local distance = math.abs(arrow.y - mg.target_y) local distance = math.abs(arrow.y - mg.target_y)
return distance <= mg.hit_threshold return distance <= mg.hit_threshold
@@ -235,34 +286,53 @@ end
--- @param arrow table The arrow data. --- @param arrow table The arrow data.
--- @return boolean True if the arrow is missed, false otherwise. --- @return boolean True if the arrow is missed, false otherwise.
local function check_miss(arrow) local function check_miss(arrow)
---@type MinigameDDRState
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
return arrow.y > mg.target_y + mg.hit_threshold return arrow.y > mg.target_y + mg.hit_threshold
end end
--- Draws an arrow. --- Rotates a point (px, py) around (cx, cy) by a number of 90-degree CW steps.
--- @within MinigameDDRWindow
local function rotate(px, py, cx, cy, steps)
local dx, dy = px - cx, py - cy
for _ = 1, steps % 4 do
dx, dy = dy, -dx
end
return cx + dx, cy + dy
end
local arrow_rotations = { down = 0, right = 1, up = 2, left = 3 }
--- Draws an arrow by rotating the "down" arrow shape.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
--- @param x number The x-coordinate. --- @param x number The x-coordinate.
--- @param y number The y-coordinate. --- @param y number The y-coordinate.
--- @param direction string The direction of the arrow. --- @param direction string The direction of the arrow.
--- @param color number The color of the arrow. --- @param color number The color of the arrow.
local function draw_arrow(x, y, direction, color) local function draw_arrow(x, y, direction, color)
local size = 12 local size = 14
local half = size / 2 local half = size / 2
if direction == "left" then local pivot_x, pivot_y = x + half, y + half
tri(x + half, y, x, y + half, x + half, y + size, color) local steps = arrow_rotations[direction] or 0
rectb(x + half, y + half - 2, half, 4, color)
elseif direction == "right" then local head_left_x, head_left_y = rotate(x, y + half, pivot_x, pivot_y, steps)
tri(x + half, y, x + size, y + half, x + half, y + size, color) local head_tip_x, head_tip_y = rotate(x + half, y + size, pivot_x, pivot_y, steps)
rectb(x, y + half - 2, half, 4, color) local head_right_x, head_right_y = rotate(x + size, y + half, pivot_x, pivot_y, steps)
elseif direction == "up" then
tri(x, y + half, x + half, y, x + size, y + half, color) tri(head_left_x, head_left_y,
rectb(x + half - 2, y + half, 4, half, color) head_tip_x, head_tip_y,
elseif direction == "down" then head_right_x, head_right_y, color)
tri(x, y + half, x + half, y + size, x + size, y + half, color)
rectb(x + half - 2, y, 4, half, color) local stem_top_x, stem_top_y = rotate(x + half - 3, y, pivot_x, pivot_y, steps)
end local stem_bot_x, stem_bot_y = rotate(x + half + 3, y + half, pivot_x, pivot_y, steps)
local stem_x = math.min(stem_top_x, stem_bot_x)
local stem_y = math.min(stem_top_y, stem_bot_y)
local stem_w = math.abs(stem_bot_x - stem_top_x)
local stem_h = math.abs(stem_bot_y - stem_top_y)
rectb(stem_x, stem_y, stem_w, stem_h, color)
end end
--- Updates DDR minigame logic. --- Updates DDR minigame logic.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
function MinigameDDRWindow.update() function MinigameDDRWindow.update()
@@ -272,7 +342,8 @@ function MinigameDDRWindow.update()
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() Audio.music_stop()
Meter.on_minigame_complete() Meter.apply_ddr_reward(mg.total_misses)
if not Context.game_in_progress then return end
if mg.on_win then if mg.on_win then
mg.on_win(mg) mg.on_win(mg)
else else
@@ -323,10 +394,7 @@ function MinigameDDRWindow.update()
arrow.y = arrow.y + mg.arrow_fall_speed arrow.y = arrow.y + mg.arrow_fall_speed
if check_miss(arrow) then if check_miss(arrow) then
table.insert(arrows_to_remove, i) table.insert(arrows_to_remove, i)
mg.bar_fill = mg.bar_fill - mg.miss_penalty mg.bar_fill = math.max(0, mg.bar_fill - mg.miss_penalty)
if mg.bar_fill < 0 then
mg.bar_fill = 0
end
mg.total_misses = mg.total_misses + 1 mg.total_misses = mg.total_misses + 1
end end
end end
@@ -355,6 +423,12 @@ function MinigameDDRWindow.update()
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
@@ -364,20 +438,14 @@ function MinigameDDRWindow.update()
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) MinigameDDRWindow.on_arrow_hit_special(arrow, mg)
mg.bar_fill = mg.bar_fill + mg.fill_per_hit mg.bar_fill = math.min(mg.max_fill, mg.bar_fill + mg.fill_per_hit)
if mg.bar_fill > mg.max_fill then
mg.bar_fill = mg.max_fill
end
table.remove(mg.arrows, i) table.remove(mg.arrows, i)
hit = true hit = true
break break
end end
end end
if not hit then if not hit then
mg.bar_fill = mg.bar_fill - 2 mg.bar_fill = math.max(0, mg.bar_fill - 2)
if mg.bar_fill < 0 then
mg.bar_fill = 0
end
mg.total_misses = mg.total_misses + 1 mg.total_misses = mg.total_misses + 1
end end
end end
@@ -387,6 +455,7 @@ end
--- Draws DDR minigame. --- Draws DDR minigame.
--- @within MinigameDDRWindow --- @within MinigameDDRWindow
function MinigameDDRWindow.draw() function MinigameDDRWindow.draw()
---@type MinigameDDRState|nil
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
if not mg then if not mg then
cls(0) cls(0)
@@ -414,8 +483,6 @@ function MinigameDDRWindow.draw()
end end
rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color) rect(mg.bar_x, mg.bar_y, fill_width, mg.bar_height, bar_color)
end end
local percentage = math.floor((mg.bar_fill / mg.max_fill) * 100)
Print.text_center(percentage .. "%", mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black)
if mg.target_arrows then if mg.target_arrows then
for _, target in ipairs(mg.target_arrows) do for _, target in ipairs(mg.target_arrows) do
local is_pressed = mg.button_pressed_timers[target.dir] and mg.button_pressed_timers[target.dir] > 0 local is_pressed = mg.button_pressed_timers[target.dir] and mg.button_pressed_timers[target.dir] > 0
@@ -429,7 +496,7 @@ function MinigameDDRWindow.draw()
draw_arrow(arrow.x, arrow.y, arrow.dir, arrow_color) 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_contour("Hit the arrows!", Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_blue)
local debug_y = 60 local debug_y = 60
if mg.debug_status then if mg.debug_status then
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)
@@ -453,13 +520,14 @@ function MinigameDDRWindow.draw()
elseif Context.test_mode then 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.special_mode_condition then if mg.win_timer > 0 then
Minigame.draw_win_overlay("SUCCESS...?") if mg.special_mode_condition then
elseif mg.total_hits < 10 then Minigame.draw_win_overlay("SUCCESS...?")
Minigame.draw_win_overlay("MEH...") elseif mg.total_hits < 10 then
else Minigame.draw_win_overlay("MEH...")
Minigame.draw_win_overlay() else
Minigame.draw_win_overlay()
end
end end
end end
end

View File

@@ -1,6 +1,35 @@
--- @section MinigameButtonMashWindow
---@class MinigameButtonMashState
---@field bar_fill number
---@field target_points number
---@field fill_per_press number
---@field base_degradation number
---@field degradation_multiplier number
---@field button_pressed_timer number
---@field button_press_duration number
---@field instruction_text string
---@field show_progress_text boolean
---@field return_window string?
---@field bar_x number
---@field bar_y number
---@field bar_width number
---@field bar_height number
---@field button_x number
---@field button_y number
---@field button_size number
---@field focus_center_x number?
---@field focus_center_y number?
---@field focus_initial_radius number
---@field win_timer number
---@field on_win (fun())?
---@field meter_on_complete (fun(elapsed_sec: number))?
---@field start_ms number?
---@field elapsed_sec number?
--- Gets initial button mash minigame configuration. --- Gets initial button mash minigame configuration.
--- @within MinigameButtonMashWindow --- @within MinigameButtonMashWindow
--- @return result table The default button mash minigame configuration. ---@return MinigameButtonMashState
function MinigameButtonMashWindow.init_context() function MinigameButtonMashWindow.init_context()
return { return {
bar_fill = 0, bar_fill = 0,
@@ -24,7 +53,11 @@ function MinigameButtonMashWindow.init_context()
focus_center_y = nil, focus_center_y = nil,
focus_initial_radius = 0, focus_initial_radius = 0,
win_timer = 0, win_timer = 0,
on_win = nil on_win = nil,
--- If set, called with elapsed_sec instead of Meter.on_minigame_complete()
meter_on_complete = nil,
start_ms = nil,
elapsed_sec = nil,
} }
end end
@@ -49,9 +82,12 @@ end
--- @param return_window string The window ID to return to after the minigame.<br/> --- @param return_window string The window ID to return to after the minigame.<br/>
--- @param[opt] params table Optional parameters for minigame configuration.<br/> --- @param[opt] params table Optional parameters for minigame configuration.<br/>
function MinigameButtonMashWindow.start(return_window, params) function MinigameButtonMashWindow.start(return_window, params)
Audio.music_stop()
MinigameButtonMashWindow.init(params) MinigameButtonMashWindow.init(params)
---@type MinigameButtonMashState
local mg = Context.minigame_button_mash local mg = Context.minigame_button_mash
mg.return_window = return_window or "game" mg.return_window = return_window or "game"
mg.start_ms = time()
if mg.focus_center_x then if mg.focus_center_x then
Focus.start_driven(mg.focus_center_x, mg.focus_center_y, { Focus.start_driven(mg.focus_center_x, mg.focus_center_y, {
initial_radius = mg.focus_initial_radius initial_radius = mg.focus_initial_radius
@@ -63,12 +99,18 @@ end
--- Updates button mash minigame logic. --- Updates button mash minigame logic.
--- @within MinigameButtonMashWindow --- @within MinigameButtonMashWindow
function MinigameButtonMashWindow.update() function MinigameButtonMashWindow.update()
---@type MinigameButtonMashState
local mg = Context.minigame_button_mash local mg = Context.minigame_button_mash
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
Meter.on_minigame_complete() if mg.meter_on_complete then
mg.meter_on_complete(mg.elapsed_sec or 0)
else
Meter.on_minigame_complete(false)
end
if not Context.game_in_progress then return end
if mg.focus_center_x then Focus.stop() end if mg.focus_center_x then Focus.stop() end
Context.home_norman_visible = true Context.home_norman_visible = true
Context.have_done_work_today = false Context.have_done_work_today = false
@@ -83,7 +125,9 @@ 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() Audio.sfx_drum_high()
mg.bar_fill = mg.bar_fill + mg.fill_per_press mg.bar_fill = mg.bar_fill + mg.fill_per_press
@@ -94,6 +138,7 @@ function MinigameButtonMashWindow.update()
end end
if mg.bar_fill >= mg.target_points then if mg.bar_fill >= mg.target_points then
Audio.sfx_select() Audio.sfx_select()
mg.elapsed_sec = (time() - mg.start_ms) / 1000
mg.win_timer = Config.timing.minigame_win_duration mg.win_timer = Config.timing.minigame_win_duration
return return
end end
@@ -113,6 +158,7 @@ end
--- Draws button mash minigame. --- Draws button mash minigame.
--- @within MinigameButtonMashWindow --- @within MinigameButtonMashWindow
function MinigameButtonMashWindow.draw() function MinigameButtonMashWindow.draw()
---@type MinigameButtonMashState
local mg = Context.minigame_button_mash local mg = Context.minigame_button_mash
if mg.return_window == "game" then if mg.return_window == "game" then
GameWindow.draw_with_underlay(function() GameWindow.draw_with_underlay(function()
@@ -143,7 +189,7 @@ function MinigameButtonMashWindow.draw()
circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color) circ(mg.button_x, mg.button_y, mg.button_size - 2, button_color)
end end
Print.text_center("Z", mg.button_x, mg.button_y - 3, button_color) Print.text_center("Z", mg.button_x, mg.button_y - 3, button_color)
Print.text_center(mg.instruction_text, Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_grey) Print.text_center_contour(mg.instruction_text, Config.screen.width / 2, mg.bar_y + mg.bar_height + 10, Config.colors.light_blue)
if mg.show_progress_text then if mg.show_progress_text then
local points_text = math.floor(mg.bar_fill) .. "/" .. mg.target_points local points_text = math.floor(mg.bar_fill) .. "/" .. mg.target_points
Print.text_center(points_text, mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black) Print.text_center(points_text, mg.bar_x + mg.bar_width / 2, mg.bar_y + 2, Config.colors.black)

View File

@@ -53,6 +53,7 @@ end
--- @param return_window string The window ID to return to after the minigame.<br/> --- @param return_window string The window ID to return to after the minigame.<br/>
--- @param[opt] params table Optional parameters for minigame configuration.<br/> --- @param[opt] params table Optional parameters for minigame configuration.<br/>
function MinigameRhythmWindow.start(return_window, params) function MinigameRhythmWindow.start(return_window, params)
Audio.music_stop()
MinigameRhythmWindow.init(params) MinigameRhythmWindow.init(params)
local mg = Context.minigame_rhythm local mg = Context.minigame_rhythm
mg.return_window = return_window or "game" mg.return_window = return_window or "game"
@@ -72,7 +73,8 @@ function MinigameRhythmWindow.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
Meter.on_minigame_complete() Meter.on_minigame_complete(false)
if not Context.game_in_progress then return end
if mg.focus_center_x then Focus.stop() end if mg.focus_center_x then Focus.stop() end
if mg.on_win then if mg.on_win then
mg.on_win() mg.on_win()
@@ -95,7 +97,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)
@@ -144,14 +148,14 @@ function MinigameRhythmWindow.draw()
local target_left = mg.target_center - (mg.target_width / 2) local target_left = mg.target_center - (mg.target_width / 2)
local target_x = mg.bar_x + (target_left * mg.bar_width) local target_x = mg.bar_x + (target_left * mg.bar_width)
local target_width_pixels = mg.target_width * mg.bar_width local target_width_pixels = mg.target_width * mg.bar_width
rect(target_x, mg.bar_y, target_width_pixels, mg.bar_height, Config.colors.light_blue) rect(target_x, mg.bar_y, target_width_pixels, mg.bar_height, Config.colors.orange)
local line_x = mg.bar_x + (mg.line_position * mg.bar_width) local line_x = mg.bar_x + (mg.line_position * mg.bar_width)
rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.item) rect(line_x - 1, mg.bar_y, 2, mg.bar_height, Config.colors.light_blue)
Print.text_center( Print.text_center_contour(
"Sleep Norman ... Sleep!", "Sleep Norman ... Sleep!",
Config.screen.width / 2, Config.screen.width / 2,
mg.bar_y + mg.bar_height + 14, mg.bar_y + mg.bar_height + 14,
Config.colors.light_grey Config.colors.light_blue
) )
local button_color = Config.colors.light_grey local button_color = Config.colors.light_grey
if mg.button_pressed_timer > 0 then if mg.button_pressed_timer > 0 then

View File

@@ -0,0 +1,115 @@
--- @section PlayerNameWindow
local _frame = 0
local _on_confirm = nil
local MAX_LEN = 3
local BOX_W = 24
local BOX_H = 24
local BOX_GAP = 12
local BOX_Y = 50
local WARN_Y = 104
local function box_start_x()
return math.floor((Config.screen.width - (MAX_LEN * BOX_W + (MAX_LEN - 1) * BOX_GAP)) / 2)
end
local function box_x(i)
return box_start_x() + (i - 1) * (BOX_W + BOX_GAP)
end
--- Initialises the player name window.
--- @within PlayerNameWindow
--- @param on_confirm function Called with the entered name when the player saves.
function PlayerNameWindow.init(on_confirm)
_frame = 0
_on_confirm = on_confirm
TextInput.init(MAX_LEN)
end
local function draw_boxes()
local cursor = TextInput.get_position()
local blink = math.floor(_frame / 18) % 2 == 0
for i = 1, MAX_LEN do
local x = box_x(i)
local is_cur = (i == cursor)
local done = TextInput.is_done()
local fill = (is_cur and not done) and Config.colors.blue or Config.colors.black
local border = (is_cur and not done) and Config.colors.white
or done and Config.colors.light_blue
or Config.colors.dark_grey
rect (x, BOX_Y, BOX_W, BOX_H, fill)
rectb(x, BOX_Y, BOX_W, BOX_H, border)
local show = not (is_cur and blink and not done)
if show then
local ch = TextInput.get_letter(i)
local cw = print(ch, 0, -100, 0, false, 2)
local cx = x + math.floor((BOX_W - cw) / 2)
local cy = BOX_Y + math.floor((BOX_H - 11) / 2)
local col = (is_cur and not done) and Config.colors.white or Config.colors.light_grey
print(ch, cx, cy, col, false, 2)
end
end
-- caret arrow below active box
if not TextInput.is_done() then
local cx = box_x(cursor) + math.floor(BOX_W / 2)
local ay = BOX_Y + BOX_H + 4
line(cx - 4, ay, cx, ay + 4, Config.colors.white)
line(cx + 4, ay, cx, ay + 4, Config.colors.white)
end
end
--- Draws the player name window.
--- @within PlayerNameWindow
function PlayerNameWindow.draw()
cls(Config.colors.black)
Print.text_center("Player Name", Config.screen.width / 2, 14, Config.colors.white, false, 2)
draw_boxes()
if TextInput.is_done() then
Print.text_center("Z: save name B: edit", Config.screen.width / 2, BOX_Y + BOX_H + 12, Config.colors.light_blue)
else
Print.text_center("Up/Dn: letter Lft/Rgt: move Z: ok", Config.screen.width / 2, BOX_Y + BOX_H + 12, Config.colors.dark_grey)
end
-- Warning section
rect(0, WARN_Y, Config.screen.width, Config.screen.height - WARN_Y, Config.colors.blue)
rectb(0, WARN_Y, Config.screen.width, Config.screen.height - WARN_Y, Config.colors.light_blue)
Print.text_center("Remember your name!", Config.screen.width / 2, WARN_Y + 8, Config.colors.white)
Print.text_center("You will need it to load the game.", Config.screen.width / 2, WARN_Y + 20, Config.colors.light_grey)
end
--- Updates player name window logic.
--- @within PlayerNameWindow
function PlayerNameWindow.update()
_frame = _frame + 1
if TextInput.is_done() then
if Input.select() then
Context.player_name = TextInput.get_name()
if _on_confirm then _on_confirm() else Window.set_current("menu") end
elseif Input.back() then
TextInput.prev_position()
end
return
end
if Input.up_repeat() then TextInput.next_letter() end
if Input.down_repeat() then TextInput.prev_letter() end
if Input.right() then TextInput.next_position() end
if Input.select() then TextInput.select_letter() end
if Input.left() or Input.back() then
if TextInput.get_position() > 1 then
TextInput.prev_position()
else
Window.set_current("menu")
end
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,6 +31,9 @@ Window.register("minigame_rhythm", MinigameRhythmWindow)
MinigameDDRWindow = {} MinigameDDRWindow = {}
Window.register("minigame_ddr", MinigameDDRWindow) Window.register("minigame_ddr", MinigameDDRWindow)
GameOverWindow = {}
Window.register("game_over", GameOverWindow)
EndWindow = {} EndWindow = {}
Window.register("end", EndWindow) Window.register("end", EndWindow)
@@ -39,3 +42,9 @@ Window.register("discussion", DiscussionWindow)
ContinuedWindow = {} ContinuedWindow = {}
Window.register("continued", ContinuedWindow) Window.register("continued", ContinuedWindow)
CreditsWindow = {}
Window.register("credits", CreditsWindow)
PlayerNameWindow = {}
Window.register("player_name", PlayerNameWindow)