diff --git a/.luacheckrc b/.luacheckrc index e71c179..0a71b27 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -24,6 +24,7 @@ globals = { "Minigame", "Window", "ContinuedWindow", + "CreditsWindow", "TTGIntroWindow", "BriefIntroWindow", "TitleIntroWindow", @@ -38,10 +39,14 @@ globals = { "MysteriousManScreen", "DiscussionWindow", "EndWindow", +<<<<<<< HEAD "PlayerNameWindow", "TextInput", "CodeGenerator", "CreditsWindow", +======= + "GameOverWindow", +>>>>>>> develop "mset", "mget", "btnp", diff --git a/impostor.inc b/impostor.inc index ddefee6..cc44218 100644 --- a/impostor.inc +++ b/impostor.inc @@ -68,6 +68,7 @@ screen/screen.work.lua screen/screen.mysterious_man.lua window/window.manager.lua window/window.register.lua +window/window.gameover.lua window/window.end.lua window/window.intro.title.lua window/window.intro.ttg.lua diff --git a/inc/decision/decision.go_to_toilet.lua b/inc/decision/decision.go_to_toilet.lua index 1a6d8b9..c28f6ec 100644 --- a/inc/decision/decision.go_to_toilet.lua +++ b/inc/decision/decision.go_to_toilet.lua @@ -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({ id = "go_to_toilet", label = "Go to Toilet", 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") end, }) diff --git a/inc/discussion/discussion.coworker.lua b/inc/discussion/discussion.coworker.lua index 344e04c..a7c4c1c 100644 --- a/inc/discussion/discussion.coworker.lua +++ b/inc/discussion/discussion.coworker.lua @@ -1,5 +1,6 @@ Discussion.register({ id = "coworker_disc_0", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "Good morning Normal, enjoying your coffee as usual, huh?", @@ -18,6 +19,7 @@ Discussion.register({ Discussion.register({ id = "coworker_disc_1", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "Norman, you look confused, what's up?", @@ -36,6 +38,7 @@ Discussion.register({ Discussion.register({ id = "coworker_disc_asc_1", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "Normann you look weird and unfocused. You are usually locked in and not like this, what's up?", @@ -54,6 +57,7 @@ Discussion.register({ Discussion.register({ id = "coworker_disc_2", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "Hey Norman, do you have new socks on? That's a weird color!", @@ -79,6 +83,7 @@ Discussion.register({ Discussion.register({ id = "coworker_disc_asc_2", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "Normann, are you ok? You were doing weird things while typing?", @@ -97,6 +102,7 @@ Discussion.register({ Discussion.register({ id = "coworker_disc_3", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "You look so happy, did you catch a bull or something?", @@ -120,6 +126,7 @@ Discussion.register({ }) Discussion.register({ id = "coworker_disc_asc_3", + on_end = Meter.apply_coworker_discussion_reward, steps = { { question = "Normal, you should take a break, you don't live up to your name today", diff --git a/inc/discussion/discussion.sumphore.lua b/inc/discussion/discussion.sumphore.lua index adc604b..340c8d1 100644 --- a/inc/discussion/discussion.sumphore.lua +++ b/inc/discussion/discussion.sumphore.lua @@ -1,5 +1,6 @@ Discussion.register({ id = "sumphore_disc_asc_1", + on_end = Meter.apply_sumphore_discussion_reward, steps = { { question = "Are you still seeking the ox?", @@ -19,6 +20,7 @@ Discussion.register({ Discussion.register({ id = "sumphore_disc_asc_2", + on_end = Meter.apply_sumphore_discussion_reward, steps = { { question = "How's work? Your face looks strange", @@ -61,6 +63,7 @@ Discussion.register({ Discussion.register({ id = "sumphore_disc_asc_3", + on_end = Meter.apply_sumphore_discussion_reward, steps = { { question = "Do you think it's work you're doing?", @@ -88,6 +91,7 @@ Discussion.register({ Discussion.register({ id = "homeless_guy", + on_end = Meter.apply_sumphore_discussion_reward, steps = { { question = "Sup bro, how are you?", diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 53d4d99..d13c6fb 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -23,6 +23,10 @@ Context = {} --- * have_met_sumphore (boolean) Whether the player has talked to the homeless guy.
--- * have_been_to_office (boolean) Whether the player has been to the office.
--- * have_done_work_today (boolean) Whether the player has done work today.
+--- * toilet_meters_today_morning (boolean) Whether the home (before work) toilet meter delta was already applied this day.
+--- * toilet_meters_today_evening (boolean) Whether the home (after work) toilet meter delta was already applied this day.
+--- * coworker_discussion_meter_applied_today (boolean) Whether the daily coworker discussion meter roll was already applied this day.
+--- * sumphore_discussion_meter_applied_today (boolean) Whether the daily sumphore discussion meter roll was already applied this day.
--- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID
function Context.initial_data() return { @@ -45,6 +49,10 @@ function Context.initial_data() home_norman_visible = false, have_been_to_office = 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, have_met_sumphore = false, office_sprites = {}, @@ -124,6 +132,7 @@ function Context.new_game() target_points = 100, instruction_text = "Wake up Norman!", show_progress_text = false, + meter_on_complete = Meter.apply_wakeup_reward, on_win = function() Audio.music_play_room_work() Meter.show() diff --git a/inc/logic/logic.day.lua b/inc/logic/logic.day.lua index ae9d760..1be8991 100644 --- a/inc/logic/logic.day.lua +++ b/inc/logic/logic.day.lua @@ -8,6 +8,10 @@ function Day.increase() if Context.day_count == 3 then Context.should_ascend = true 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 handler() end @@ -27,6 +31,13 @@ Day.register_handler(function() m.bm = math.max(0, m.bm - METER_DECAY_PER_DAY) 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() if Context.should_ascend then Ascension.increase() diff --git a/inc/logic/logic.meter.lua b/inc/logic/logic.meter.lua index 2607582..552ddd0 100644 --- a/inc/logic/logic.meter.lua +++ b/inc/logic/logic.meter.lua @@ -1,6 +1,8 @@ --- @section Meter 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_DECAY_PER_DAY = 20 local COMBO_BASE_BONUS = 0.02 @@ -26,9 +28,9 @@ Meter.COLOR_CONTOUR = Config.colors.white --- * hidden (boolean) Whether meters are hidden. function Meter.get_initial() return { - ism = METER_DEFAULT, - wpm = METER_DEFAULT, - bm = METER_DEFAULT, + ism = ISM_METER_DEFAULT, + wpm = WPM_METER_DEFAULT, + bm = BM_METER_DEFAULT, combo = 0, combo_timer = 0, hidden = false, @@ -103,22 +105,129 @@ function Meter.add(key, amount) if not Context or not Context.meters then return end local m = Context.meters 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 --- Called on minigame completion. --- @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 gain = math.floor(METER_GAIN_PER_CHORE * Meter.get_combo_multiplier()) - Meter.add("wpm", gain) - Meter.add("ism", gain) - Meter.add("bm", gain) + if is_work then + local mult = Meter.get_combo_multiplier() + local wpm_delta = math.floor(METER_GAIN_PER_CHORE / mult) + 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%. 1–3: 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_timer = 0 end +--- Meter changes for the wake-up button mash: faster completion is better for WPM. +--- Perfect: under 2s — WPM +20%. Good: 2–3s — 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. --- @within Meter function Meter.draw() diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 5a07a4c..e55d11f 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -132,6 +132,7 @@ function MysteriousManScreen.wake_up() target_points = 100, instruction_text = "Wake up Norman!", show_progress_text = false, + meter_on_complete = Meter.apply_wakeup_reward, on_win = function() Audio.music_play_wakingup() Meter.show() diff --git a/inc/screen/screen.toilet.lua b/inc/screen/screen.toilet.lua index 908787f..664ac66 100644 --- a/inc/screen/screen.toilet.lua +++ b/inc/screen/screen.toilet.lua @@ -51,8 +51,8 @@ Screen.register({ local decay_pct = Meter.get_decay_percentage() local decay_text = string.format("-%d%%", decay_pct) local combo_mult = Meter.get_combo_multiplier() - local combo_pct = math.floor((combo_mult - 1) * 100) - local mult_text = string.format("+%d%%", combo_pct) + local ism_bm_combo_pct = math.floor((combo_mult - 1) * 100) + local wpm_combo_pct = math.floor((1 / combo_mult - 1) * 100 + 0.5) local meter_start_y = text_y + 10 local meter_list = { @@ -73,6 +73,12 @@ Screen.register({ rect(bar_x, bar_y, fill_w, bar_h, meter.color) 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) Print.text_contour(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white) Print.text_contour(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white) diff --git a/inc/window/window.credits.lua b/inc/window/window.credits.lua new file mode 100644 index 0000000..174dbd8 --- /dev/null +++ b/inc/window/window.credits.lua @@ -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 diff --git a/inc/window/window.gameover.lua b/inc/window/window.gameover.lua new file mode 100644 index 0000000..f62d1f4 --- /dev/null +++ b/inc/window/window.gameover.lua @@ -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 diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index 8d159f9..6c2b524 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -149,7 +149,6 @@ function MenuWindow.credits() Window.set_current("credits") end - --- Opens the audio test menu. --- @within MenuWindow function MenuWindow.audio_test() diff --git a/inc/window/window.minigame.ddr.lua b/inc/window/window.minigame.ddr.lua index 6a0167c..1231206 100644 --- a/inc/window/window.minigame.ddr.lua +++ b/inc/window/window.minigame.ddr.lua @@ -130,6 +130,7 @@ function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context) Audio.sfx_arrowhit(arrow.note) game_context.special_mode_counter = game_context.special_mode_counter + 1 else + game_context.total_misses = game_context.total_misses + 1 if game_context.special_mode_condition then Audio.sfx_bloop() end game_context.special_mode_condition = false end @@ -141,10 +142,12 @@ function MinigameDDRWindow.on_arrow_hit_special(arrow, game_context) game_context.bar_fill = game_context.bar_fill - game_context.fill_per_hit end else + game_context.total_misses = game_context.total_misses + 1 if game_context.special_mode_condition then Audio.sfx_bloop() end game_context.special_mode_condition = false end 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 game_context.special_mode_condition = false end @@ -173,6 +176,9 @@ function MinigameDDRWindow.on_end(game_context) end 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 --- Initializes DDR minigame state. @@ -336,7 +342,8 @@ function MinigameDDRWindow.update() mg.win_timer = mg.win_timer - 1 if mg.win_timer == 0 then 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 mg.on_win(mg) else diff --git a/inc/window/window.minigame.mash.lua b/inc/window/window.minigame.mash.lua index 467eaf1..7213b59 100644 --- a/inc/window/window.minigame.mash.lua +++ b/inc/window/window.minigame.mash.lua @@ -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. --- @within MinigameButtonMashWindow ---- @return result table The default button mash minigame configuration. +---@return MinigameButtonMashState function MinigameButtonMashWindow.init_context() return { bar_fill = 0, @@ -24,7 +53,11 @@ function MinigameButtonMashWindow.init_context() focus_center_y = nil, focus_initial_radius = 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 @@ -51,8 +84,10 @@ end function MinigameButtonMashWindow.start(return_window, params) Audio.music_stop() MinigameButtonMashWindow.init(params) + ---@type MinigameButtonMashState local mg = Context.minigame_button_mash mg.return_window = return_window or "game" + mg.start_ms = time() if mg.focus_center_x then Focus.start_driven(mg.focus_center_x, mg.focus_center_y, { initial_radius = mg.focus_initial_radius @@ -64,12 +99,18 @@ end --- Updates button mash minigame logic. --- @within MinigameButtonMashWindow function MinigameButtonMashWindow.update() + ---@type MinigameButtonMashState local mg = Context.minigame_button_mash if mg.win_timer > 0 then mg.win_timer = mg.win_timer - 1 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 Context.home_norman_visible = true Context.have_done_work_today = false @@ -97,6 +138,7 @@ function MinigameButtonMashWindow.update() end if mg.bar_fill >= mg.target_points then Audio.sfx_select() + mg.elapsed_sec = (time() - mg.start_ms) / 1000 mg.win_timer = Config.timing.minigame_win_duration return end @@ -116,6 +158,7 @@ end --- Draws button mash minigame. --- @within MinigameButtonMashWindow function MinigameButtonMashWindow.draw() + ---@type MinigameButtonMashState local mg = Context.minigame_button_mash if mg.return_window == "game" then GameWindow.draw_with_underlay(function() diff --git a/inc/window/window.minigame.rhythm.lua b/inc/window/window.minigame.rhythm.lua index d3ba312..dd2eee6 100644 --- a/inc/window/window.minigame.rhythm.lua +++ b/inc/window/window.minigame.rhythm.lua @@ -73,7 +73,8 @@ function MinigameRhythmWindow.update() if mg.win_timer > 0 then mg.win_timer = mg.win_timer - 1 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.on_win then mg.on_win() diff --git a/inc/window/window.register.lua b/inc/window/window.register.lua index afc6fd6..a969b59 100644 --- a/inc/window/window.register.lua +++ b/inc/window/window.register.lua @@ -31,6 +31,9 @@ Window.register("minigame_rhythm", MinigameRhythmWindow) MinigameDDRWindow = {} Window.register("minigame_ddr", MinigameDDRWindow) +GameOverWindow = {} +Window.register("game_over", GameOverWindow) + EndWindow = {} Window.register("end", EndWindow)