Compare commits

...

5 Commits

Author SHA1 Message Date
Zoltan Timar
efe903110b feat: added ascension logic 4-7, added new decision (eating fast food), indicating meter changes better, added discussions (needs more work, but meh ... fine like this)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2026-04-28 00:51:42 +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: http://git.teletype.hu/games/impostor/pulls/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: http://git.teletype.hu/games/impostor/pulls/55
2026-04-27 19:23:47 +00:00
23 changed files with 817 additions and 38 deletions

View File

@@ -39,6 +39,7 @@ globals = {
"MysteriousManScreen", "MysteriousManScreen",
"DiscussionWindow", "DiscussionWindow",
"EndWindow", "EndWindow",
"GameOverWindow",
"mset", "mset",
"mget", "mget",
"btnp", "btnp",

View File

@@ -50,8 +50,10 @@ decision/decision.go_to_sleep.lua
decision/decision.do_work.lua decision/decision.do_work.lua
decision/decision.have_a_coffee.lua decision/decision.have_a_coffee.lua
decision/decision.sumphore_discussion.lua decision/decision.sumphore_discussion.lua
decision/decision.eating_fast_food.lua
discussion/discussion.sumphore.lua discussion/discussion.sumphore.lua
discussion/discussion.coworker.lua discussion/discussion.coworker.lua
discussion/discussion.pizza_vendor.lua
map/map.manager.lua map/map.manager.lua
map/map.bedroom.lua map/map.bedroom.lua
map/map.street.lua map/map.street.lua
@@ -66,6 +68,7 @@ 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

View File

@@ -10,6 +10,7 @@ Decision.register({
modes_for_ascension_levels[1] = "only_special" modes_for_ascension_levels[1] = "only_special"
modes_for_ascension_levels[2] = "only_left" modes_for_ascension_levels[2] = "only_left"
modes_for_ascension_levels[3] = "only_nothing" modes_for_ascension_levels[3] = "only_nothing"
modes_for_ascension_levels[4] = "normal"
MinigameDDRWindow.start("game", "generated", { MinigameDDRWindow.start("game", "generated", {
on_win = function(game_context) on_win = function(game_context)
@@ -19,8 +20,6 @@ Decision.register({
Context.should_ascend = true Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 3) then elseif (game_context.special_mode_condition and Context.ascension.level == 3) then
Context.should_ascend = true Context.should_ascend = true
elseif (game_context.special_mode_condition and Context.ascension.level == 4) then
Context.should_ascend = true
end end
Meter.show() Meter.show()
@@ -28,7 +27,7 @@ Decision.register({
Window.set_current("game") Window.set_current("game")
Context.have_done_work_today = true Context.have_done_work_today = true
end, end,
special_mode = modes_for_ascension_levels[Ascension.get_level()] special_mode = modes_for_ascension_levels[Ascension.get_level()] or "normal"
}) })
end, end,
}) })

View File

@@ -0,0 +1,11 @@
Decision.register({
id = "eating_fast_food",
label = "Eat Fast Food",
condition = function()
return Context.fast_food_eaten_today < 3
end,
handle = function()
Context.fast_food_approaching = true
Discussion.start("pizza_vendor_disc", "game")
end,
})

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

@@ -4,10 +4,17 @@ Decision.register({
handle = function() handle = function()
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 if level >= 1 and level <= 5 then
if level >= 1 and level <= 3 then
local suffix = Context.have_done_work_today and ("_asc_" .. level) or ("_" .. level) local suffix = Context.have_done_work_today and ("_asc_" .. level) or ("_" .. level)
disc_id = "coworker_disc" .. suffix disc_id = "coworker_disc" .. suffix
elseif level == 6 then
if not Context.glitch_conversation_done_today and Context.glitch_conversation_count < 6 then
Context.glitch_conversation_done_today = true
Context.glitch_conversation_count = Context.glitch_conversation_count + 1
Glitch.show()
Discussion.start("coworker_disc_asc_6_" .. Context.glitch_conversation_count, "game")
return
end
end end
Discussion.start(disc_id, "game") Discussion.start(disc_id, "game")
end, end,

View File

@@ -13,9 +13,14 @@ Decision.register({
end end
local level = Ascension.get_level() local level = Ascension.get_level()
-- TODO: Add more discussions for levels above 3 if level >= 1 and level <= 5 then
if level >= 1 and level <= 3 then
Discussion.start("sumphore_disc_asc_" .. level, "game") Discussion.start("sumphore_disc_asc_" .. level, "game")
elseif level == 6 then
if Context.glitch_conversation_count >= 6 then
Discussion.start("sumphore_disc_asc_6", "game")
else
Discussion.start("sumphore_disc_asc_6_waiting", "game")
end
else else
Discussion.start("homeless_guy", "game", 4) Discussion.start("homeless_guy", "game", 4)
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,6 +38,7 @@ 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?",
@@ -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",
@@ -135,3 +142,226 @@ Discussion.register({
}, },
}, },
}) })
Discussion.register({
id = "coworker_disc_4",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Normaaan! Same spot, same cup, same time. You're like a statue that drinks coffee!",
answers = {
{ label = "I'm a person.", next_step = 2 },
{ label = "Yep. Still here.", next_step = 2 },
},
},
{
question = "I love that about you! So reliable! If the coffee machine breaks we still have Norman!",
answers = {
{ label = "Please don't.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_4",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Norman, you were seriously locked in today. What on earth were you building?",
answers = {
{ label = "Just some things.", next_step = 2 },
{ label = "Nothing important.", next_step = 2 },
},
},
{
question = "So modest! I heard the seniors literally whispering your name!",
answers = {
{ label = "Concerning.", next_step = 3 },
{ label = "That's... fine.", next_step = 3 },
},
},
{
question = "You should celebrate! Coffee's on me tomorrow!",
answers = {
{ label = "Already have one.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_5",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Morning! Funny thought — I feel like we do this exact same thing every single day!",
answers = {
{ label = "We do.", next_step = 2 },
{ label = "Yes. We do.", next_step = 2 },
},
},
{
question = "Ha! Routine is such a comfort, right? Same coffee, same smile, same everything!",
answers = {
{ label = "Sure. Very comforting.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_5",
on_end = Meter.apply_coworker_discussion_reward,
steps = {
{
question = "Norman! You were staring right THROUGH your screen today. What was going on up there?",
answers = {
{ label = "Coffee was cold.", next_step = 2 },
{ label = "Maybe I was.", next_step = 2 },
},
},
{
question = "Were you meditating? Or doing your intense bug-stare thing?",
answers = {
{ label = "Something like that.", next_step = 3 },
{ label = "Bug stare thing?", next_step = 3 },
},
},
{
question = "You're always somewhere else in your head, Norman. Must be nice up there!",
answers = {
{ label = "It's complicated.", next_step = nil },
},
},
},
})
local function _glitch_on_end()
Meter.apply_coworker_discussion_reward()
Context.glitch_conversation_count = Context.glitch_conversation_count + 1
Glitch.hide()
end
Discussion.register({
id = "coworker_disc_asc_6_1",
on_end = _glitch_on_end,
steps = {
{
question = "Hey Norman, good morning! Good morning! Good morning! ...Sorry. I don't know why I keep saying that.",
answers = {
{ label = "Are you feeling ok?", next_step = 2 },
},
},
{
question = "Good morning. Good morning. Good— I can't stop. Why can't I stop?",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_2",
on_end = _glitch_on_end,
steps = {
{
question = "Hey... Marcus. How's it going?",
answers = {
{ label = "My name is Norman.", next_step = 2 },
},
},
{
question = "Right, sorry. Marcus. You look tired today.",
answers = {
{ label = "Norman. It's Norman.", next_step = 3 },
},
},
{
question = "Sure, sure. You should get some rest, Marcus.",
answers = {
{ label = "...", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_3",
on_end = _glitch_on_end,
steps = {
{
question = "We've had this conversation before, haven't we? Exact same words. Same coffee. Same spot.",
answers = {
{ label = "I don't think so.", next_step = 2 },
{ label = "Maybe.", next_step = 2 },
},
},
{
question = "Every day. I always say the same thing and you always say that. It's very strange.",
answers = {
{ label = "That's just routine.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_4",
on_end = _glitch_on_end,
steps = {
{
question = "Do you ever look at the walls here? Really look? Sometimes they don't feel... solid.",
answers = {
{ label = "They're just walls.", next_step = 2 },
{ label = "I know what you mean.", next_step = 2 },
},
},
{
question = "Like they're only there because we expect them to be. Like set dressing. Does any of this feel load-bearing to you?",
answers = {
{ label = "I need more coffee.", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_5",
on_end = _glitch_on_end,
steps = {
{
question = "Norman, I'm not supposed to— I mean. How are you doing today?",
answers = {
{ label = "What weren't you supposed to say?", next_step = 2 },
},
},
{
question = "...",
answers = {
{ label = "Hello?", next_step = nil },
},
},
},
})
Discussion.register({
id = "coworker_disc_asc_6_6",
on_end = _glitch_on_end,
steps = {
{
question = "Forget it. You won't remember this conversation anyway. None of us do.",
answers = {
{ label = "What do you mean?", next_step = 2 },
{ label = "That's a strange thing to say.", next_step = 2 },
},
},
{
question = "Tomorrow you'll come back and it'll be like this never happened. Same coffee. Same office. Same Norman.",
answers = {
{ label = "...", next_step = nil },
},
},
},
})

View File

@@ -0,0 +1,28 @@
Discussion.register({
id = "pizza_vendor_disc",
on_end = function()
Context.fast_food_approaching = false
end,
steps = {
{
question = "Hey friend! Hot slice, fresh out of the oven. You look like a man who knows good food!",
answers = {
{
label = "Devour a juicy one",
next_step = nil,
on_select = function()
local max = Meter.get_max()
Meter.add("wpm", math.floor(max * 0.05))
Meter.add("ism", math.floor(max * -0.05))
Meter.add("bm", math.floor(max * -0.05))
Context.fast_food_eaten_today = Context.fast_food_eaten_today + 1
end,
},
{
label = "Stay lean and sharp",
next_step = nil,
},
},
},
},
})

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?",
@@ -86,8 +89,128 @@ Discussion.register({
}, },
}) })
Discussion.register({
id = "sumphore_disc_asc_4",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "The alarm wakes you every morning without fail, I bet.",
answers = {
{ label = "It's how it works.", next_step = 2 },
{ label = "Sometimes I wish it didn't.", next_step = 2 },
},
},
{
question = "What if the alarm is part of the problem?",
answers = {
{ label = "That doesn't make any sense.", next_step = 3 },
{ label = "Go on.", next_step = 3 },
},
},
{
question = "One morning, Norman. When the choice comes, choose the bed. See what the world does without you in it.",
answers = {
{ label = "You're drunk.", next_step = nil },
{ label = "What choice?", next_step = nil, on_select = function()
Meter.add("ism", 5)
end },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_5",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "You saw something you weren't supposed to, didn't you.",
answers = {
{ label = "I don't know what you mean.", next_step = 2 },
{ label = "Maybe.", next_step = 2 },
},
},
{
question = "The world around you has seams. Your coworkers slip sometimes. Say things that don't quite fit.",
answers = {
{ label = "They seem fine to me.", next_step = nil },
{ label = "I've noticed something odd.", next_step = 3 },
},
},
{
question = "Count those moments. Six of them should be enough to see the whole picture.",
answers = {
{ label = "Six of what, exactly?", next_step = nil, on_select = function()
Meter.add("ism", 5)
end },
{ label = "How would you know any of this?", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_6_waiting",
on_end = Meter.apply_sumphore_discussion_reward,
steps = {
{
question = "You look like a man who has seen something he can't explain.",
answers = {
{ label = "I'm hearing things.", next_step = 2 },
{ label = "Maybe.", next_step = 2 },
},
},
{
question = "Keep looking. The world has a way of showing you what you need to see. Talk to people. You're almost there.",
answers = {
{ label = "Almost where?", next_step = nil },
},
},
},
})
Discussion.register({
id = "sumphore_disc_asc_6",
on_end = function()
Meter.apply_sumphore_discussion_reward()
Context.should_ascend = true
Day.increase()
MysteriousManScreen.start({
text = MysteriousManScreen.get_text_for_level(Ascension.get_level())
})
end,
steps = {
{
question = "You've been seeing the cracks, haven't you? The repetition. The loops. The coworkers who can't quite remember.",
answers = {
{ label = "How do you know that?", next_step = 2 },
},
},
{
question = "Because I was you, once. This isn't a workplace, Norman. It never was. You're in a system. And you've just become aware of it.",
answers = {
{ label = "That can't be true.", next_step = 3 },
{ label = "I knew something was wrong.", next_step = 3 },
},
},
{
question = "It doesn't matter if you believe it. You already know. That's why the cracks are showing. That's why you're still here.",
answers = {
{ label = "What do I do now?", next_step = 4 },
},
},
{
question = "Oh look, is that a squirrel ?",
answers = {
{ label = "Alcoholic ...", next_step = nil },
},
},
},
})
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

@@ -21,7 +21,7 @@ local FADE_COLORS = nil
function Ascension.get_initial() function Ascension.get_initial()
_increased_this_cycle = false _increased_this_cycle = false
return { return {
level = 0, level = 0, -- FYI: change this to test ascension levels without having to play through them
} }
end end

View File

@@ -23,6 +23,10 @@ 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/>
--- * 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/> --- * game (table) Current game progress state. Contains: `current_screen` (string) active screen ID<br/>
function Context.initial_data() function Context.initial_data()
return { return {
@@ -45,8 +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,
glitch_conversation_count = 0,
glitch_conversation_done_today = false,
should_ascend = false, should_ascend = false,
have_met_sumphore = false, have_met_sumphore = false,
fast_food_approaching = false,
fast_food_eaten_today = 0,
office_sprites = {}, office_sprites = {},
walking_to_office_sprites = {}, walking_to_office_sprites = {},
game = { game = {
@@ -124,6 +136,7 @@ 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_room_work() Audio.music_play_room_work()
Meter.show() Meter.show()

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,15 @@ 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
Context.glitch_conversation_done_today = false
Context.fast_food_eaten_today = 0
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,11 +1,15 @@
--- @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
local COMBO_MAX_BONUS = 0.16 local COMBO_MAX_BONUS = 0.16
local COMBO_TIMEOUT_FRAMES = 600 local COMBO_TIMEOUT_FRAMES = 600
local FLASH_DURATION = 2.0
local FLASH_COLOR = 4
-- Internal meters for tracking game progress and player stats. -- Internal meters for tracking game progress and player stats.
Meter.COLOR_ISM = Config.colors.orange Meter.COLOR_ISM = Config.colors.orange
@@ -14,6 +18,12 @@ 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 Meter.COLOR_CONTOUR = Config.colors.white
local _flash = {
wpm = { timer = 0, delta = 0 },
ism = { timer = 0, delta = 0 },
bm = { timer = 0, delta = 0 },
}
--- Gets initial meter values. --- Gets initial meter values.
--- @within Meter --- @within Meter
--- @return result table Initial meter values. </br> --- @return result table Initial meter values. </br>
@@ -26,9 +36,9 @@ Meter.COLOR_CONTOUR = Config.colors.white
--- * 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,
@@ -93,6 +103,12 @@ function Meter.update()
end end
end end
end end
local dt = Context.delta_time or 0
for _, key in ipairs({ "wpm", "ism", "bm" }) do
if _flash[key].timer > 0 then
_flash[key].timer = _flash[key].timer - dt
end
end
end end
--- Adds amount to a meter. --- Adds amount to a meter.
@@ -103,22 +119,140 @@ 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
local prev_wpm = (key == "wpm") and m.wpm or nil
local old_val = m[key]
m[key] = math.max(0, math.min(METER_MAX, m[key] + amount))
local actual_delta = m[key] - old_val
if actual_delta ~= 0 and _flash[key] then
_flash[key].delta = actual_delta
_flash[key].timer = FLASH_DURATION
end
if prev_wpm and prev_wpm > 0 and m.wpm == 0 and Context.game_in_progress
and Ascension.get_level() == 5 then
Context.should_ascend = true
end
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()
@@ -149,12 +283,32 @@ function Meter.draw()
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 - 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 local flash = _flash[meter.key]
if flash and flash.timer > 0 then
local old_val = m[meter.key] - flash.delta
local old_fill_w = math.max(0, math.floor((old_val / max) * bar_w))
local stable_w = math.min(fill_w, old_fill_w)
if stable_w > 0 then
rect(bar_x, bar_y, stable_w, bar_h, meter.color)
end
if flash.delta > 0 then
local hi_w = fill_w - stable_w
if hi_w > 0 then
rect(bar_x + stable_w, bar_y, hi_w, bar_h, FLASH_COLOR)
end
else
local hi_w = old_fill_w - fill_w
if hi_w > 0 then
rect(bar_x + fill_w, bar_y, hi_w, bar_h, FLASH_COLOR)
end
end
elseif 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 = 8 }) Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 })
end end

View File

@@ -54,12 +54,40 @@ local ASC_45_TEXT = [[
]] ]]
local ASC_56_TEXT = [[
Norman is not as productive as he should be.
Can we distract him?
We need to keep him busy.
We need
More
Time
]]
local ASC_67_TEXT = [[
He knows.
Norman has broken through the first veil.
The simulation is compromised.
This was not supposed to happen.
Not yet.
]]
local ascension_texts = { local ascension_texts = {
[1] = ASC_01_TEXT, [1] = ASC_01_TEXT,
[2] = ASC_12_TEXT, [2] = ASC_12_TEXT,
[3] = ASC_23_TEXT, [3] = ASC_23_TEXT,
[4] = ASC_34_TEXT, [4] = ASC_34_TEXT,
[5] = ASC_45_TEXT, [5] = ASC_45_TEXT,
[6] = ASC_56_TEXT,
[7] = ASC_67_TEXT,
} }
function MysteriousManScreen.get_text_for_level(level) function MysteriousManScreen.get_text_for_level(level)
@@ -132,6 +160,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()
@@ -145,11 +174,25 @@ function MysteriousManScreen.wake_up()
end end
-- Norman chooses to stay in bed, skipping the minigame and flash, and going straight to the next day. -- Norman chooses to stay in bed, skipping the minigame and flash, and going straight to the next day.
-- At ascension level 4, staying in bed triggers 4->5: shows the ascension text then wakes with flash.
-- @within MysteriousManScreen -- @within MysteriousManScreen
function MysteriousManScreen.stay_in_bed() function MysteriousManScreen.stay_in_bed()
Day.increase() if Ascension.get_level() == 4 then
state = STATE_DAY Context.should_ascend = true
day_timer = day_display_seconds Day.increase()
Ascension.consume_increase()
trigger_flash_on_wake = true
show_mysterious_screen = true
text = MysteriousManScreen.get_text_for_level(Ascension.get_level())
text_y = Config.screen.height
text_done = false
text_done_timer = 0
state = STATE_TEXT
else
Day.increase()
state = STATE_DAY
day_timer = day_display_seconds
end
end end
--- Starts the mysterious man screen. --- Starts the mysterious man screen.

View File

@@ -51,8 +51,8 @@ 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 = {
@@ -73,6 +73,12 @@ Screen.register({
rect(bar_x, bar_y, fill_w, bar_h, meter.color) 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_contour(decay_text, bar_x - decay_w - 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white) 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) Print.text_contour(mult_text, bar_x + bar_w + 4, bar_y, Config.colors.light_blue, false, 1, Config.colors.white)

View File

@@ -4,15 +4,20 @@ Screen.register({
decisions = { decisions = {
"go_to_home", "go_to_home",
"go_to_office", "go_to_office",
"eating_fast_food",
}, },
init = function() init = function()
Audio.music_play_room_work() Audio.music_play_room_work()
end, end,
background = "street", background = "street",
draw = function() draw = function()
if Window.get_current_id() == "game" then local w = Window.get_current_id()
Sprite.draw_at("norman", 7 * 8, 3 * 8) if w == "game" or w == "discussion" then
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8)
Sprite.draw_at("norman", norman_x, 3 * 8)
if Context.fast_food_eaten_today < 3 then
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8)
end
Sprite.draw_at("dev_guard", 22 * 8, 2 * 8) Sprite.draw_at("dev_guard", 22 * 8, 2 * 8)
end end
end end

View File

@@ -1,3 +1,5 @@
WalkingToOfficeScreen = {}
Screen.register({ Screen.register({
id = "walking_to_office", id = "walking_to_office",
name = "Walking to office", name = "Walking to office",
@@ -5,6 +7,7 @@ Screen.register({
"go_to_home", "go_to_home",
"go_to_office", "go_to_office",
"sumphore_discussion", "sumphore_discussion",
"eating_fast_food",
}, },
init = function() init = function()
Audio.music_play_room_work() Audio.music_play_room_work()
@@ -32,12 +35,18 @@ Screen.register({
Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions)
end, end,
background = "street", background = "street",
update = function()
end,
draw = function() draw = function()
if Window.get_current_id() == "game" then local w = Window.get_current_id()
Sprite.draw_at("norman", 7 * 8, 3 * 8) if w == "game" or w == "discussion" then
Sprite.draw_at("sumphore", 9 * 8, 2 * 8) local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8)
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) Sprite.draw_at("norman", norman_x, 3 * 8)
Sprite.draw_at("dev_guard", 22 * 8, 2 * 8) Sprite.draw_at("sumphore", 9 * 8, 2 * 8)
if Context.fast_food_eaten_today < 3 then
Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8)
end
Sprite.draw_at("dev_guard", 22 * 8, 3 * 8)
Sprite.draw_list(Context.walking_to_office_sprites) Sprite.draw_list(Context.walking_to_office_sprites)
end end

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

@@ -130,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
@@ -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 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
@@ -173,6 +176,9 @@ function MinigameDDRWindow.on_end(game_context)
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.
@@ -336,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

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
@@ -51,8 +84,10 @@ end
function MinigameButtonMashWindow.start(return_window, params) function MinigameButtonMashWindow.start(return_window, params)
Audio.music_stop() 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
@@ -64,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
@@ -97,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
@@ -116,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()

View File

@@ -73,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()

View File

@@ -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)