Compare commits

..

1 Commits

Author SHA1 Message Date
787b6656b0 docs
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-21 23:53:36 +01:00
43 changed files with 448 additions and 635 deletions

View File

@@ -11,11 +11,9 @@ globals = {
"Print", "Print",
"Input", "Input",
"Audio", "Audio",
"Config",
"Context", "Context",
"Meter", "Meters",
"Minigame", "Minigames",
"Window",
"SplashWindow", "SplashWindow",
"IntroWindow", "IntroWindow",
"MenuWindow", "MenuWindow",

View File

@@ -55,7 +55,7 @@ Based on the analysis of `impostor.lua`, the following regularities and conventi
--- ---
# Impostor Minigame Documentation # Impostor Minigames Documentation
This document provides comprehensive documentation for all three minigames implemented in the Impostor game: Button Mash, Rhythm, and DDR (Dance Dance Revolution). This document provides comprehensive documentation for all three minigames implemented in the Impostor game: Button Mash, Rhythm, and DDR (Dance Dance Revolution).
@@ -74,7 +74,7 @@ This document provides comprehensive documentation for all three minigames imple
The Impostor game includes three interactive minigames that can be triggered during gameplay. Each minigame presents a unique challenge that the player must complete to progress. All minigames feature: The Impostor game includes three interactive minigames that can be triggered during gameplay. Each minigame presents a unique challenge that the player must complete to progress. All minigames feature:
- **Overlay rendering** - Minigame render over the current game window - **Overlay rendering** - Minigames render over the current game window
- **Progress tracking** - Visual indicators show completion status - **Progress tracking** - Visual indicators show completion status
- **Return mechanism** - Automatic return to the calling window upon completion - **Return mechanism** - Automatic return to the calling window upon completion
- **Visual feedback** - Button presses and hits are visually indicated - **Visual feedback** - Button presses and hits are visually indicated

View File

@@ -1,16 +1,10 @@
meta/meta.header.lua meta/meta.header.lua
init/init.module.lua init/init.modules.lua
init/init.config.lua init/init.config.lua
init/init.minigame.lua init/init.minigames.lua
init/init.meter.lua init/init.meters.lua
init/init.window.lua
init/init.context.lua
system/system.util.lua system/system.util.lua
system/system.print.lua init/init.windows.lua
system/system.input.lua
system/system.ui.lua
audio/audio.manager.lua
audio/audio.songs.lua
sprite/sprite.manager.lua sprite/sprite.manager.lua
sprite/sprite.norman.lua sprite/sprite.norman.lua
situation/situation.manager.lua situation/situation.manager.lua
@@ -33,7 +27,12 @@ screen/screen.toilet.lua
screen/screen.walking_to_office.lua screen/screen.walking_to_office.lua
screen/screen.office.lua screen/screen.office.lua
screen/screen.walking_to_home.lua screen/screen.walking_to_home.lua
window/window.manager.lua init/init.context.lua
data/data.songs.lua
system/system.print.lua
system/system.input.lua
system/system.audio.lua
system/system.ui.lua
window/window.splash.lua window/window.splash.lua
window/window.intro.lua window/window.intro.lua
window/window.menu.lua window/window.menu.lua

View File

@@ -107,11 +107,8 @@ Songs = {
} }
} }
--- Converts beats to frames. -- Helper function to calculate frame from beat
-- @param beat number The beat number. -- Usage: frame_from_beat(beat_number, bpm, fps)
-- @param bpm number Beats per minute.
-- @param[opt] fps number Frames per second (default: 60).
-- @return number The corresponding frame number.
function frame_from_beat(beat, bpm, fps) function frame_from_beat(beat, bpm, fps)
fps = fps or 60 fps = fps or 60
local seconds_per_beat = 60 / bpm local seconds_per_beat = 60 / bpm
@@ -119,11 +116,8 @@ function frame_from_beat(beat, bpm, fps)
return math.floor(beat * frames_per_beat) return math.floor(beat * frames_per_beat)
end end
--- Converts beat notation to frame pattern. -- Helper function to convert simple beat notation to frame pattern
-- @param beats table A table of beat data, e.g., {{1, "left"}, {2, "down"}}. -- Usage: beats_to_pattern({{1, "left"}, {2, "down"}}, 120)
-- @param bpm number Beats per minute.
-- @param[opt] fps number Frames per second (default: 60).
-- @return table The generated pattern.
function beats_to_pattern(beats, bpm, fps) function beats_to_pattern(beats, bpm, fps)
fps = fps or 60 fps = fps or 60
local pattern = {} local pattern = {}

View File

@@ -4,4 +4,5 @@ Decision.register({
handle = function() handle = function()
Util.go_to_screen_by_id("home") Util.go_to_screen_by_id("home")
end, end,
condition = function() return true end
}) })

View File

@@ -4,4 +4,5 @@ Decision.register({
handle = function() handle = function()
Util.go_to_screen_by_id("office") Util.go_to_screen_by_id("office")
end, end,
condition = function() return true end
}) })

View File

@@ -4,4 +4,5 @@ Decision.register({
handle = function() handle = function()
Util.go_to_screen_by_id("toilet") Util.go_to_screen_by_id("toilet")
end, end,
condition = function() return true end
}) })

View File

@@ -4,4 +4,5 @@ Decision.register({
handle = function() handle = function()
Util.go_to_screen_by_id("walking_to_home") Util.go_to_screen_by_id("walking_to_home")
end, end,
condition = function() return true end
}) })

View File

@@ -4,4 +4,5 @@ Decision.register({
handle = function() handle = function()
Util.go_to_screen_by_id("walking_to_office") Util.go_to_screen_by_id("walking_to_office")
end, end,
condition = function() return true end
}) })

View File

@@ -2,7 +2,7 @@ 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) Situation.apply("drink_coffee")
Context.game.current_situation = new_situation_id
end, end,
condition = function() return true end
}) })

View File

@@ -1,7 +1,5 @@
local _decisions = {} local _decisions = {}
--- Registers a decision definition.
-- @param decision table The decision data table.
function Decision.register(decision) function Decision.register(decision)
if not decision or not decision.id then if not decision or not decision.id then
PopupWindow.show({"Error: Invalid decision object registered (missing id)!"}) PopupWindow.show({"Error: Invalid decision object registered (missing id)!"})
@@ -24,46 +22,10 @@ function Decision.register(decision)
_decisions[decision.id] = decision _decisions[decision.id] = decision
end end
--- Gets a decision by ID.
-- @param id string The ID of the decision.
-- @return table The decision table or nil.
function Decision.get(id) function Decision.get(id)
return _decisions[id] return _decisions[id]
end end
--- Gets all registered decisions. function Decision.get_all()
-- @return table A table of all registered decisions.
function Decision.get_all_registered()
return _decisions return _decisions
end end
--- Gets decision objects based on a screen's data.
-- @param screen_data table The data for the screen, containing a 'decisions' field (list of decision IDs).
-- @return table A table containing decision objects relevant to the screen.
function Decision.get_for_screen(screen_data)
if not screen_data or not screen_data.decisions then
return {}
end
local screen_decisions = {}
for _, decision_id in ipairs(screen_data.decisions) do
local decision = Decision.get(decision_id)
if decision then
table.insert(screen_decisions, decision)
end
end
return screen_decisions
end
--- Filters a list of decision objects based on their condition function.
-- @param decisions_list table A table of decision objects.
-- @return table A new table containing only the decisions for which condition() is true.
function Decision.filter_available(decisions_list)
local available = {}
for _, decision in ipairs(decisions_list) do
if decision and decision.condition() then
table.insert(available, decision)
end
end
return available
end

View File

@@ -1,5 +1,6 @@
Decision.register({ Decision.register({
id = "play_button_mash", id = "play_button_mash",
label = "Play Button Mash", label = "Play Button Mash",
handle = function() Meter.hide() MinigameButtonMashWindow.start("game") end, handle = function() Meters.hide() MinigameButtonMashWindow.start(WINDOW_GAME) end,
condition = function() return true end
}) })

View File

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

View File

@@ -1,5 +1,6 @@
Decision.register({ Decision.register({
id = "play_rhythm", id = "play_rhythm",
label = "Play Rhythm Game", label = "Play Rhythm Game",
handle = function() Meter.hide() MinigameRhythmWindow.start("game") end, handle = function() Meters.hide() MinigameRhythmWindow.start(WINDOW_GAME) end,
condition = function() return true end
}) })

View File

@@ -1,7 +1,4 @@
Config = {} local DEFAULT_CONFIG = {
function Config.initial_data()
return {
screen = { screen = {
width = 240, width = 240,
height = 136 height = 136
@@ -17,38 +14,40 @@ function Config.initial_data()
item = 12, item = 12,
meter_bg = 12 meter_bg = 12
}, },
player = {
sprite_id = 1
},
timing = { timing = {
splash_duration = 120 splash_duration = 120
} }
} }
end
--- Restores default configuration settings. local Config = {
function Config.reset() screen = DEFAULT_CONFIG.screen,
local initial = Config.initial_data() colors = DEFAULT_CONFIG.colors,
Config.screen = initial.screen player = DEFAULT_CONFIG.player,
Config.colors = initial.colors timing = DEFAULT_CONFIG.timing,
Config.timing = initial.timing }
end
local CONFIG_SAVE_BANK = 7 local CONFIG_SAVE_BANK = 7
local CONFIG_MAGIC_VALUE_ADDRESS = 2 local CONFIG_MAGIC_VALUE_ADDRESS = 2
local CONFIG_SPLASH_DURATION_ADDRESS = 3 local CONFIG_SPLASH_DURATION_ADDRESS = 3
local CONFIG_MAGIC_VALUE = 0xDE local CONFIG_MAGIC_VALUE = 0xDE
--- Saves the current configuration.
function Config.save() function Config.save()
mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK)
mset(Config.timing.splash_duration, CONFIG_SPLASH_DURATION_ADDRESS, CONFIG_SAVE_BANK)
end end
--- Loads saved configuration.
function Config.load() function Config.load()
if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then
Config.timing.splash_duration = mget(CONFIG_SPLASH_DURATION_ADDRESS, CONFIG_SAVE_BANK) Config.timing.splash_duration = mget(CONFIG_SPLASH_DURATION_ADDRESS, CONFIG_SAVE_BANK)
else else
Config.reset() Config.restore_defaults()
end end
end end
function Config.restore_defaults()
Config.timing.splash_duration = DEFAULT_CONFIG.timing.splash_duration
end
Config.load() Config.load()

View File

@@ -2,34 +2,64 @@ local SAVE_GAME_BANK = 6
local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0 local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0
local SAVE_GAME_MAGIC_VALUE = 0xCA local SAVE_GAME_MAGIC_VALUE = 0xCA
--- Global game context. local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
Context = {}
--- Gets initial data for Context. local function get_initial_data()
-- @return table Initial context data.
function Context.initial_data()
return { return {
current_menu_item = 1, active_window = WINDOW_SPLASH,
intro = {
y = Config.screen.height,
speed = 0.5,
text = [[Norman Reds everyday life
seems ordinary: work,
meetings, coffee, and
endless notifications.
But beneath the surface
— within him, or around
him — something is
constantly building, and
it soon becomes clear
that there is more going
on than meets the eye.]]
},
current_screen = 1,
splash_timer = Config.timing.splash_duration, splash_timer = Config.timing.splash_duration,
popup = { popup = {
show = false, show = false,
content = {} content = {}
}, },
player = {
sprite_id = Config.player.sprite_id
},
ground = {
x = 0,
y = Config.screen.height,
w = Config.screen.width,
h = 8
},
menu_items = {},
selected_menu_item = 1,
selected_decision_index = 1,
game_in_progress = false, game_in_progress = false,
minigame_ddr = Minigame.get_default_ddr(), screens = {},
minigame_button_mash = Minigame.get_default_button_mash(), minigame_ddr = Minigames.get_default_ddr(),
minigame_rhythm = Minigame.get_default_rhythm(), minigame_button_mash = Minigames.get_default_button_mash(),
meters = Meter.get_initial(), minigame_rhythm = Minigames.get_default_rhythm(),
game = { meters = Meters.get_initial(),
current_screen = "home", --- Table storing currently active sprites to be drawn.
-- Each entry is a table with `id`, `x`, `y`, and other drawing parameters.
sprites = {},
--- The ID of the currently active situation.
-- Set by `Situation.apply()` and `nil` if no situation is active.
current_situation = nil, current_situation = nil,
} }
}
end end
--- Resets game context to initial state. Context = {}
function Context.reset()
local initial_data = Context.initial_data() local function reset_context_to_initial_state()
local initial_data = get_initial_data()
for k in pairs(Context) do for k in pairs(Context) do
if type(Context[k]) ~= "function" then Context[k] = nil if type(Context[k]) ~= "function" then Context[k] = nil
end end
@@ -38,30 +68,45 @@ function Context.reset()
for k, v in pairs(initial_data) do for k, v in pairs(initial_data) do
Context[k] = v Context[k] = v
end end
Context.screens = {}
Context.screen_indices_by_id = {}
local screen_order = {"home", "toilet", "walking_to_office", "office", "walking_to_home"}
for i, screen_id in ipairs(screen_order) do
local screen_data = Screen.get_by_id(screen_id)
if screen_data then
table.insert(Context.screens, screen_data)
Context.screen_indices_by_id[screen_id] = i
else
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not registered!"})
end
end
end end
--- Starts a new game. reset_context_to_initial_state()
function Context.new_game() function Context.new_game()
Context.reset() reset_context_to_initial_state()
Context.game_in_progress = true Context.game_in_progress = true
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init()
end end
--- Saves the current game state.
function Context.save_game() function Context.save_game()
if not Context.game_in_progress then return end if not Context.game_in_progress then return end
mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK)
mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
end end
--- Loads a saved game state.
function Context.load_game() function Context.load_game()
if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then
Context.new_game() Context.new_game()
return return
end end
Context.reset()
reset_context_to_initial_state()
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
Context.game_in_progress = true Context.game_in_progress = true
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
Screen.get_by_id(Context.game.current_screen).init()
end end

View File

@@ -6,15 +6,12 @@ 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
-- Internal meters for tracking game progress and player stats. Meters.COLOR_ISM = Config.colors.red
Meter.COLOR_ISM = Config.colors.red Meters.COLOR_WPM = Config.colors.blue
Meter.COLOR_WPM = Config.colors.blue Meters.COLOR_BM = Config.colors.black
Meter.COLOR_BM = Config.colors.black Meters.COLOR_BG = Config.colors.meter_bg
Meter.COLOR_BG = Config.colors.meter_bg
--- Gets initial meter values. function Meters.get_initial()
-- @return table A table of initial meter values.
function Meter.get_initial()
return { return {
ism = METER_DEFAULT, ism = METER_DEFAULT,
wpm = METER_DEFAULT, wpm = METER_DEFAULT,
@@ -25,33 +22,26 @@ function Meter.get_initial()
} }
end end
--- Hides meters. function Meters.hide()
function Meter.hide()
if Context and Context.meters then Context.meters.hidden = true end if Context and Context.meters then Context.meters.hidden = true end
end end
--- Shows meters. function Meters.show()
function Meter.show()
if Context and Context.meters then Context.meters.hidden = false end if Context and Context.meters then Context.meters.hidden = false end
end end
--- Gets max meter value. function Meters.get_max()
-- @return number The maximum meter value.
function Meter.get_max()
return METER_MAX return METER_MAX
end end
--- Gets combo multiplier. function Meters.get_combo_multiplier()
-- @return number The current combo multiplier.
function Meter.get_combo_multiplier()
if not Context or not Context.meters then return 1 end if not Context or not Context.meters then return 1 end
local combo = Context.meters.combo local combo = Context.meters.combo
if combo == 0 then return 1 end if combo == 0 then return 1 end
return 1 + math.min(COMBO_MAX_BONUS, COMBO_BASE_BONUS * (2 ^ (combo - 1))) return 1 + math.min(COMBO_MAX_BONUS, COMBO_BASE_BONUS * (2 ^ (combo - 1)))
end end
--- Updates all meters. function Meters.update()
function Meter.update()
if not Context or not Context.game_in_progress or not Context.meters then return end if not Context or not Context.game_in_progress or not Context.meters then return end
local m = Context.meters local m = Context.meters
m.ism = math.max(0, m.ism - METER_DECAY_PER_FRAME) m.ism = math.max(0, m.ism - METER_DECAY_PER_FRAME)
@@ -66,10 +56,7 @@ function Meter.update()
end end
end end
--- Adds amount to a meter. function Meters.add(key, amount)
-- @param key string The meter key (e.g., "wpm", "ism", "bm").
-- @param amount number The amount to add.
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
@@ -77,13 +64,12 @@ function Meter.add(key, amount)
end end
end end
--- Called on minigame completion. function Meters.on_minigame_complete()
function Meter.on_minigame_complete()
local m = Context.meters local m = Context.meters
local gain = math.floor(METER_GAIN_PER_CHORE * Meter.get_combo_multiplier()) local gain = math.floor(METER_GAIN_PER_CHORE * Meters.get_combo_multiplier())
Meter.add("wpm", gain) Meters.add("wpm", gain)
Meter.add("ism", gain) Meters.add("ism", gain)
Meter.add("bm", gain) Meters.add("bm", gain)
m.combo = m.combo + 1 m.combo = m.combo + 1
m.combo_timer = 0 m.combo_timer = 0
end end

View File

@@ -1,10 +1,5 @@
-- Manages minigame configurations and initial states. Minigames = {}
Minigame = {}
--- Applies parameters to defaults.
-- @param defaults table The default configuration table.
-- @param params table The parameters to apply.
-- @return table The updated configuration table.
local function apply_params(defaults, params) local function apply_params(defaults, params)
if not params then return defaults end if not params then return defaults end
for k, v in pairs(params) do for k, v in pairs(params) do
@@ -13,9 +8,7 @@ local function apply_params(defaults, params)
return defaults return defaults
end end
--- Gets default DDR minigame configuration. function Minigames.get_default_ddr()
-- @return table The default DDR minigame configuration.
function Minigame.get_default_ddr()
local arrow_size = 12 local arrow_size = 12
local arrow_spacing = 30 local arrow_spacing = 30
local total_width = (4 * arrow_size) + (3 * arrow_spacing) local total_width = (4 * arrow_size) + (3 * arrow_spacing)
@@ -54,9 +47,7 @@ function Minigame.get_default_ddr()
} }
end end
--- Gets default button mash minigame configuration. function Minigames.get_default_button_mash()
-- @return table The default button mash minigame configuration.
function Minigame.get_default_button_mash()
return { return {
bar_fill = 0, bar_fill = 0,
max_fill = 100, max_fill = 100,
@@ -76,9 +67,7 @@ function Minigame.get_default_button_mash()
} }
end end
--- Gets default rhythm minigame configuration. function Minigames.get_default_rhythm()
-- @return table The default rhythm minigame configuration.
function Minigame.get_default_rhythm()
return { return {
line_position = 0, line_position = 0,
line_speed = 0.015, line_speed = 0.015,
@@ -105,23 +94,14 @@ function Minigame.get_default_rhythm()
} }
end end
--- Configures DDR minigame. function Minigames.configure_ddr(params)
-- @param params table Optional parameters to override defaults. return apply_params(Minigames.get_default_ddr(), params)
-- @return table The configured DDR minigame state.
function Minigame.configure_ddr(params)
return apply_params(Minigame.get_default_ddr(), params)
end end
--- Configures button mash minigame. function Minigames.configure_button_mash(params)
-- @param params table Optional parameters to override defaults. return apply_params(Minigames.get_default_button_mash(), params)
-- @return table The configured button mash minigame state.
function Minigame.configure_button_mash(params)
return apply_params(Minigame.get_default_button_mash(), params)
end end
--- Configures rhythm minigame. function Minigames.configure_rhythm(params)
-- @param params table Optional parameters to override defaults. return apply_params(Minigames.get_default_rhythm(), params)
-- @return table The configured rhythm minigame state.
function Minigame.configure_rhythm(params)
return apply_params(Minigame.get_default_rhythm(), params)
end end

View File

@@ -1,13 +0,0 @@
Window = {}
Util = {}
Meter = {}
Minigame = {}
Decision = {}
Situation = {}
Screen = {}
Map = {}
UI = {}
Print = {}
Input = {}
Sprite = {}
Audio = {}

22
inc/init/init.modules.lua Normal file
View File

@@ -0,0 +1,22 @@
SplashWindow = {}
IntroWindow = {}
MenuWindow = {}
GameWindow = {}
PopupWindow = {}
ConfigurationWindow = {}
AudioTestWindow = {}
MinigameButtonMashWindow = {}
MinigameRhythmWindow = {}
MinigameDDRWindow = {}
Util = {}
Meters = {}
Minigames = {}
Decision = {}
Situation = {}
Screen = {}
Map = {}
UI = {}
Print = {}
Input = {}
Sprite = {}
Audio = {}

10
inc/init/init.windows.lua Normal file
View File

@@ -0,0 +1,10 @@
local WINDOW_SPLASH = 0
local WINDOW_INTRO = 1
local WINDOW_MENU = 2
local WINDOW_GAME = 3
local WINDOW_POPUP = 4
local WINDOW_CONFIGURATION = 7
local WINDOW_MINIGAME_BUTTON_MASH = 8
local WINDOW_MINIGAME_RHYTHM = 9
local WINDOW_MINIGAME_DDR = 10
local WINDOW_AUDIOTEST = 9001

View File

@@ -1,9 +1,5 @@
-- Manages game maps.
local _maps = {} local _maps = {}
--- Gets all registered maps as an array.
-- @return table An array of registered map data.
function Map.get_maps_array() function Map.get_maps_array()
local maps_array = {} local maps_array = {}
for _, map_data in pairs(_maps) do for _, map_data in pairs(_maps) do
@@ -12,8 +8,6 @@ function Map.get_maps_array()
return maps_array return maps_array
end end
--- Registers a map definition.
-- @param map_data table The map data table.
function Map.register(map_data) function Map.register(map_data)
if _maps[map_data.id] then if _maps[map_data.id] then
trace("Warning: Overwriting map with id: " .. map_data.id) trace("Warning: Overwriting map with id: " .. map_data.id)
@@ -21,15 +15,10 @@ function Map.register(map_data)
_maps[map_data.id] = map_data _maps[map_data.id] = map_data
end end
--- Gets a map by ID.
-- @param map_id string The ID of the map.
-- @return table The map data table or nil.
function Map.get_by_id(map_id) function Map.get_by_id(map_id)
return _maps[map_id] return _maps[map_id]
end end
--- Draws a map.
-- @param map_id string The ID of the map to draw.
function Map.draw(map_id) function Map.draw(map_id)
local map_data = Map.get_by_id(map_id) local map_data = Map.get_by_id(map_id)
if not map_data then if not map_data then

View File

@@ -1,7 +1,13 @@
local _screens = {} local _screens = {}
--- Registers a screen definition. --- Registers a new screen definition with the Screen manager.
-- @param screen_data table The screen data table. -- Overwrites existing screen if an ID conflict occurs.
-- @param screen_data table A table containing the screen definition.
-- Must include an `id` field (string).
-- Optional fields:
-- - `situations` (table): A table of situation IDs allowed on this screen. Defaults to an empty table.
-- - `init` (function): Function to execute once when the screen becomes active. Defaults to an empty function.
-- - `update` (function): Function to execute each frame while the screen is active. Defaults to an empty function.
function Screen.register(screen_data) 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)
@@ -18,15 +24,9 @@ function Screen.register(screen_data)
_screens[screen_data.id] = screen_data _screens[screen_data.id] = screen_data
end end
--- Gets a screen by ID. --- Retrieves a registered screen by its ID.
-- @param screen_id string The ID of the screen. -- @param screen_id string The ID of the screen to retrieve.
-- @return table The screen table or nil. -- @return table The screen table, or `nil` if not found.
function Screen.get_by_id(screen_id) function Screen.get_by_id(screen_id)
return _screens[screen_id] return _screens[screen_id]
end end
--- Gets all registered screens.
-- @return table A table containing all registered screen data, indexed by their IDs.
function Screen.get_all()
return _screens
end

View File

@@ -1,7 +1,12 @@
local _situations = {} local _situations = {}
--- Registers a situation definition. --- Registers a new situation with the Situation manager.
-- @param situation table The situation data table. -- Overwrites existing situation if an ID conflict occurs.
-- @param situation table A table containing the situation definition.
-- Must include an `id` field (string).
-- Optional fields:
-- - `handle` (function): Function to execute when the situation is applied. Defaults to an empty function.
-- - `update` (function): Function to execute each frame while the situation is active. Defaults to an empty function.
function Situation.register(situation) function Situation.register(situation)
if not situation or not situation.id then if not situation or not situation.id then
PopupWindow.show({"Error: Invalid situation object registered (missing id)!"}) PopupWindow.show({"Error: Invalid situation object registered (missing id)!"})
@@ -19,47 +24,32 @@ function Situation.register(situation)
_situations[situation.id] = situation _situations[situation.id] = situation
end end
--- Gets a situation by ID. --- Retrieves a registered situation by its ID.
-- @param id string The situation ID. -- @param id string The ID of the situation to retrieve.
-- @return table The situation table or nil. -- @return table The situation table, or `nil` if not found.
function Situation.get_by_id(id) function Situation.get(id)
return _situations[id] return _situations[id]
end end
--- Gets all registered situations, optionally filtered by screen ID. --- Applies a situation, making it the current active situation.
-- @param screen_id string Optional. If provided, returns situations associated with this screen ID. -- The situation's `handle` function is executed.
-- @return table A table containing all registered situation data, indexed by their IDs, or filtered by screen_id. -- This function first checks if the situation is valid and if it's allowed
function Situation.get_all(screen_id) -- on the current screen (as defined in `Context.screens[Context.current_screen].situations`).
if screen_id then -- If successful, `Context.current_situation` is updated with the ID of the applied situation.
local filtered_situations = {} -- @param id string The ID of the situation to apply.
for _, situation in pairs(_situations) do function Situation.apply(id)
if situation.screen_id == screen_id then local situation = Situation.get(id)
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.
-- @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 if not situation then
trace("Error: No situation found with id: " .. id) trace("Error: No situation found with id: " .. id)
return nil return
end end
if Util.contains(screen.situations, id) then local current_screen_obj = Screen.get_by_id(Context.current_screen)
if current_screen_obj and not current_screen_obj.situations[id] then
trace("Info: Situation " .. id .. " cannot be applied to current screen (id: " .. Context.current_screen .. ").")
return
end
Context.current_situation = id
situation.handle() situation.handle()
return id
else
trace("Info: Situation " .. id .. " cannot be applied to current screen (id: " .. current_screen_id .. ").")
return nil
end
end end

View File

@@ -1,8 +1,15 @@
local _sprites = {} local _sprites = {}
local _active_sprites = {}
--- Registers a sprite definition. --- Registers a new sprite or complex sprite definition with the Sprite manager.
-- Overwrites existing sprite if an ID conflict occurs.
-- @param sprite_data table A table containing the sprite definition. -- @param sprite_data table A table containing the sprite definition.
-- Must include an `id` field (string) and either an `s` field (number, for simple sprites)
-- or a `sprites` field (table, for complex sprites).
-- For complex sprites, `sprites` is a table of sub-sprite definitions, each with:
-- - `s` (number): Sprite index.
-- - `x_offset` (number): X-offset relative to the parent sprite's position.
-- - `y_offset` (number): Y-offset relative to the parent sprite's position.
-- - Optional `colorkey`, `scale`, `flip_x`, `flip_y`, `rot` for individual sub-sprites.
function Sprite.register(sprite_data) function Sprite.register(sprite_data)
if not sprite_data or not sprite_data.id then if not sprite_data or not sprite_data.id then
trace("Error: Invalid sprite object registered (missing id)!") trace("Error: Invalid sprite object registered (missing id)!")
@@ -14,22 +21,25 @@ function Sprite.register(sprite_data)
_sprites[sprite_data.id] = sprite_data _sprites[sprite_data.id] = sprite_data
end end
--- Schedules a sprite for drawing. --- Schedules a registered sprite to be drawn at a specific position with optional transformations.
-- @param id string The unique identifier of the sprite. -- The sprite's parameters are stored in `Context.sprites` for deferred rendering by `Sprite.draw()`.
-- @param x number The x-coordinate. -- If the sprite with the given `id` is already scheduled, its parameters will be updated.
-- @param y number The y-coordinate. -- @param id string The unique identifier of the sprite to show. Must be registered via `Sprite.register`.
-- @param[opt] colorkey number The color index for transparency. -- @param x number The x-coordinate on the screen where the sprite will be drawn.
-- @param[opt] scale number The scaling factor. -- @param y number The y-coordinate on the screen where the sprite will be drawn.
-- @param[opt] flip_x number Set to 1 to flip horizontally. -- @param[opt] colorkey number The color index to be treated as transparent (default: 0).
-- @param[opt] flip_y number Set to 1 to flip vertically. -- @param[opt] scale number The scaling factor for the sprite (default: 1).
-- @param[opt] rot number The rotation in degrees. -- @param[opt] flip_x number Set to 1 to flip the sprite horizontally (default: 0).
-- @param[opt] flip_y number Set to 1 to flip the sprite vertically (default: 0).
-- @param[opt] rot number The rotation of the sprite in degrees (default: 0).
function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot) function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot)
-- Ensure the sprite exists before attempting to show it
if not _sprites[id] then if not _sprites[id] then
trace("Error: Attempted to show non-registered sprite with id: " .. id) trace("Error: Attempted to show non-registered sprite with id: " .. id)
return return
end end
_active_sprites[id] = { Context.sprites[id] = {
id = id, id = id,
x = x, x = x,
y = y, y = y,
@@ -41,28 +51,34 @@ function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot)
} }
end end
--- Hides a displayed sprite. --- Hides a currently displayed sprite by removing it from the `Context.sprites` table.
-- @param id string The unique identifier of the sprite. -- The sprite will no longer be drawn in subsequent frames.
-- @param id string The unique identifier of the sprite to hide.
function Sprite.hide(id) function Sprite.hide(id)
_active_sprites[id] = nil Context.sprites[id] = nil
end end
--- Draws all scheduled sprites. --- Draws all sprites currently scheduled in `Context.sprites`.
-- This function retrieves the registered sprite definitions and applies the stored
-- position and transformation parameters. It handles both simple and complex sprites.
function Sprite.draw() function Sprite.draw()
for id, params in pairs(_active_sprites) do for id, params in pairs(Context.sprites) do
local sprite_data = _sprites[id] local sprite_data = _sprites[id]
if not sprite_data then if not sprite_data then
trace("Error: Sprite id " .. id .. " in _active_sprites is not registered.") trace("Error: Sprite id " .. id .. " in Context.sprites is not registered.")
_active_sprites[id] = nil Context.sprites[id] = nil -- Clean up invalid entry
-- We should probably continue to the next sprite instead of returning
-- so that other valid sprites can still be drawn.
end end
-- Use parameters from Context.sprites, or fall back to sprite_data, then to defaults
local colorkey = params.colorkey or sprite_data.colorkey or 0 local colorkey = params.colorkey or sprite_data.colorkey or 0
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
local rot = params.rot or sprite_data.rot or 0 local rot = params.rot or sprite_data.rot or 0
if sprite_data.sprites then if sprite_data.sprites then -- Complex sprite
for i = 1, #sprite_data.sprites do for i = 1, #sprite_data.sprites do
local sub_sprite = sprite_data.sprites[i] local sub_sprite = sprite_data.sprites[i]
spr( spr(
@@ -76,7 +92,7 @@ function Sprite.draw()
sub_sprite.rot or rot sub_sprite.rot or rot
) )
end end
else else -- Simple sprite
spr(sprite_data.s, params.x, params.y, colorkey, scale, flip_x, flip_y, rot) spr(sprite_data.s, params.x, params.y, colorkey, scale, flip_x, flip_y, rot)
end end
end end

View File

@@ -1,27 +1,14 @@
--- Stops current music.
function Audio.music_stop() music() end function Audio.music_stop() music() end
--- Plays main menu music.
function Audio.music_play_mainmenu() end function Audio.music_play_mainmenu() end
--- Plays waking up music.
function Audio.music_play_wakingup() end function Audio.music_play_wakingup() end
--- Plays room morning music.
function Audio.music_play_room_morning() end function Audio.music_play_room_morning() end
--- Plays room street 1 music.
function Audio.music_play_room_street_1() end function Audio.music_play_room_street_1() end
--- Plays room street 2 music.
function Audio.music_play_room_street_2() end function Audio.music_play_room_street_2() end
--- Plays room music.
function Audio.music_play_room_() end function Audio.music_play_room_() end
--- Plays room work music.
function Audio.music_play_room_work() end function Audio.music_play_room_work() end
--- Plays select sound effect.
function Audio.sfx_select() sfx(17, 'C-7', 30) end function Audio.sfx_select() sfx(17, 'C-7', 30) end
--- Plays deselect sound effect.
function Audio.sfx_deselect() sfx(18, 'C-7', 30) end function Audio.sfx_deselect() sfx(18, 'C-7', 30) end
--- Plays beep sound effect.
function Audio.sfx_beep() sfx(19, 'C-6', 30) end function Audio.sfx_beep() sfx(19, 'C-6', 30) end
--- Plays success sound effect.
function Audio.sfx_success() sfx(16, 'C-7', 60) end function Audio.sfx_success() sfx(16, 'C-7', 60) end
--- Plays bloop sound effect.
function Audio.sfx_bloop() sfx(21, 'C-3', 60) end function Audio.sfx_bloop() sfx(21, 'C-3', 60) end

View File

@@ -7,21 +7,11 @@ local INPUT_KEY_SPACE = 48
local INPUT_KEY_BACKSPACE = 51 local INPUT_KEY_BACKSPACE = 51
local INPUT_KEY_ENTER = 50 local INPUT_KEY_ENTER = 50
--- Checks if Up is pressed.
function Input.up() return btnp(INPUT_KEY_UP) end function Input.up() return btnp(INPUT_KEY_UP) end
--- Checks if Down is pressed.
function Input.down() return btnp(INPUT_KEY_DOWN) end function Input.down() return btnp(INPUT_KEY_DOWN) end
--- Checks if Left is pressed.
function Input.left() return btnp(INPUT_KEY_LEFT) end function Input.left() return btnp(INPUT_KEY_LEFT) end
--- Checks if Right is pressed.
function Input.right() return btnp(INPUT_KEY_RIGHT) end function Input.right() return btnp(INPUT_KEY_RIGHT) end
--- Checks if Select is pressed.
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) end
--- Checks if Menu Confirm is pressed.
function Input.menu_confirm() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_ENTER) end function Input.menu_confirm() return btnp(INPUT_KEY_A) or keyp(INPUT_KEY_ENTER) end
--- Checks if Player Interact is pressed. function Input.player_interact() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_ENTER) end function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
function Input.player_interact() return btnp(INPUT_KEY_B) or keyp(INPUT_KEY_ENTER) end
--- Checks if Menu Back is pressed.
function Input.menu_back() return btnp(INPUT_KEY_Y) or keyp(INPUT_KEY_BACKSPACE) end
--- Checks if Toggle Popup is pressed.
function Input.toggle_popup() return keyp(INPUT_KEY_ENTER) end function Input.toggle_popup() return keyp(INPUT_KEY_ENTER) end

View File

@@ -1,23 +1,64 @@
local STATE_HANDLERS = {
[WINDOW_SPLASH] = function()
SplashWindow.update()
SplashWindow.draw()
end,
[WINDOW_INTRO] = function()
IntroWindow.update()
IntroWindow.draw()
end,
[WINDOW_MENU] = function()
MenuWindow.update()
MenuWindow.draw()
end,
[WINDOW_GAME] = function()
GameWindow.update()
GameWindow.draw()
end,
[WINDOW_POPUP] = function()
GameWindow.draw()
PopupWindow.update()
PopupWindow.draw()
end,
[WINDOW_CONFIGURATION] = function()
ConfigurationWindow.update()
ConfigurationWindow.draw()
end,
[WINDOW_AUDIOTEST] = function()
AudioTestWindow.update()
AudioTestWindow.draw()
end,
[WINDOW_MINIGAME_BUTTON_MASH] = function()
MinigameButtonMashWindow.update()
MinigameButtonMashWindow.draw()
end,
[WINDOW_MINIGAME_RHYTHM] = function()
MinigameRhythmWindow.update()
MinigameRhythmWindow.draw()
end,
[WINDOW_MINIGAME_DDR] = function()
MinigameDDRWindow.update()
MinigameDDRWindow.draw()
end,
}
local initialized_game = false local initialized_game = false
--- Initializes game state.
local function init_game() local function init_game()
if initialized_game then return end if initialized_game then return end
Context.reset()
Window.set_current("splash") -- Set initial window using new manager
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
initialized_game = true initialized_game = true
end end
--- Main game loop (TIC-80 callback).
function TIC() function TIC()
init_game() init_game()
cls(Config.colors.black) cls(Config.colors.black)
local handler = Window.get_current_handler() -- Get handler from Window manager local handler = STATE_HANDLERS[Context.active_window]
if handler then if handler then
handler() handler()
end end
Meter.update() Meters.update()
if Context.game_in_progress then if Context.game_in_progress then
UI.draw_meters() UI.draw_meters()
end end

View File

@@ -1,10 +1,4 @@
--- Prints text with shadow.
-- @param text string The text to print.
-- @param x number The x-coordinate.
-- @param y number The y-coordinate.
-- @param color number The color of the text.
-- @param[opt] fixed boolean If true, uses fixed-width font.
-- @param[opt] scale number The scaling factor.
function Print.text(text, x, y, color, fixed, scale) 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
@@ -13,13 +7,6 @@ function Print.text(text, x, y, color, fixed, scale)
print(text, x, y, color, fixed, scale) print(text, x, y, color, fixed, scale)
end end
--- Prints centered text with shadow.
-- @param text string The text to print.
-- @param x number The x-coordinate for centering.
-- @param y number The y-coordinate.
-- @param color number The color of the text.
-- @param[opt] fixed boolean If true, uses fixed-width font.
-- @param[opt] scale number The scaling factor.
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, 0, fixed, scale)

View File

@@ -1,20 +1,12 @@
--- Draws the top bar.
-- @param title string The title text to display.
function UI.draw_top_bar(title) function UI.draw_top_bar(title)
rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey) rect(0, 0, Config.screen.width, 10, Config.colors.dark_grey)
Print.text(title, 3, 2, Config.colors.green) Print.text(title, 3, 2, Config.colors.green)
end end
--- Draws dialog window.
function UI.draw_dialog() function UI.draw_dialog()
PopupWindow.draw() PopupWindow.draw()
end end
--- Draws a menu.
-- @param items table A table of menu items.
-- @param selected_item number The index of the currently selected item.
-- @param x number The x-coordinate for the menu.
-- @param y number The y-coordinate for the menu.
function UI.draw_menu(items, selected_item, x, y) function UI.draw_menu(items, selected_item, x, y)
for i, item in ipairs(items) do for i, item in ipairs(items) do
local current_y = y + (i-1)*10 local current_y = y + (i-1)*10
@@ -25,10 +17,6 @@ function UI.draw_menu(items, selected_item, x, y)
end end
end end
--- Updates menu selection.
-- @param items table A table of menu items.
-- @param selected_item number The current index of the selected item.
-- @return number The updated index of the selected item.
function UI.update_menu(items, selected_item) function UI.update_menu(items, selected_item)
if Input.up() then if Input.up() then
Audio.sfx_beep() Audio.sfx_beep()
@@ -46,10 +34,6 @@ function UI.update_menu(items, selected_item)
return selected_item return selected_item
end end
--- Wraps text.
-- @param text string The text to wrap.
-- @param max_chars_per_line number The maximum characters per line.
-- @return table A table of wrapped lines.
function UI.word_wrap(text, max_chars_per_line) function UI.word_wrap(text, max_chars_per_line)
if text == nil then return {""} end if text == nil then return {""} end
local lines = {} local lines = {}
@@ -79,15 +63,6 @@ function UI.word_wrap(text, max_chars_per_line)
return lines return lines
end end
--- Creates a numeric stepper.
-- @param label string The label for the stepper.
-- @param value_getter function Function to get the current value.
-- @param value_setter function Function to set the current value.
-- @param min number The minimum value.
-- @param max number The maximum value.
-- @param step number The step increment.
-- @param[opt] format string The format string for displaying the value.
-- @return table A numeric stepper control definition.
function UI.create_numeric_stepper(label, value_getter, value_setter, min, max, step, format) function UI.create_numeric_stepper(label, value_getter, value_setter, min, max, step, format)
return { return {
label = label, label = label,
@@ -101,10 +76,6 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max,
} }
end end
--- Creates an action item.
-- @param label string The label for the action item.
-- @param action function The function to execute when the item is selected.
-- @return table An action item control definition.
function UI.create_action_item(label, action) function UI.create_action_item(label, action)
return { return {
label = label, label = label,
@@ -113,9 +84,6 @@ function UI.create_action_item(label, action)
} }
end end
--- Draws decision selector.
-- @param decisions table A table of decision items.
-- @param selected_decision_index number The index of the selected decision.
function UI.draw_decision_selector(decisions, selected_decision_index) function UI.draw_decision_selector(decisions, selected_decision_index)
local bar_height = 16 local bar_height = 16
local bar_y = Config.screen.height - bar_height local bar_y = Config.screen.height - bar_height
@@ -129,13 +97,12 @@ function UI.draw_decision_selector(decisions, selected_decision_index)
Print.text(decision_label, text_x, text_y, Config.colors.item) Print.text(">", Config.screen.width - 6, text_y, Config.colors.green) end Print.text(decision_label, text_x, text_y, Config.colors.item) Print.text(">", Config.screen.width - 6, text_y, Config.colors.green) end
end end
--- Draws meters.
function UI.draw_meters() function UI.draw_meters()
if not Context or not Context.game_in_progress or not Context.meters then return end if not Context or not Context.game_in_progress or not Context.meters then return end
if Context.meters.hidden then return end if Context.meters.hidden then return end
local m = Context.meters local m = Context.meters
local max = Meter.get_max() local max = Meters.get_max()
local bar_w = 44 local bar_w = 44
local bar_h = 2 local bar_h = 2
local bar_x = 182 local bar_x = 182
@@ -145,16 +112,16 @@ function UI.draw_meters()
local bar_offset = math.floor((line_h - bar_h) / 2) 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 = Meters.COLOR_WPM, row = 0 },
{ key = "ism", label = "ISM", color = Meter.COLOR_ISM, row = 1 }, { key = "ism", label = "ISM", color = Meters.COLOR_ISM, row = 1 },
{ key = "bm", label = "BM", color = Meter.COLOR_BM, row = 2 }, { key = "bm", label = "BM", color = Meters.COLOR_BM, row = 2 },
} }
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 + bar_offset
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, bar_y, bar_w, bar_h, Meter.COLOR_BG) rect(bar_x, bar_y, bar_w, bar_h, Meters.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
@@ -162,10 +129,6 @@ function UI.draw_meters()
end end
end end
--- Updates decision selector.
-- @param decisions table A table of decision items.
-- @param selected_decision_index number The current index of the selected decision.
-- @return number The updated index of the selected decision.
function UI.update_decision_selector(decisions, selected_decision_index) function UI.update_decision_selector(decisions, selected_decision_index)
if Input.left() then if Input.left() then
Audio.sfx_beep() Audio.sfx_beep()

View File

@@ -1,37 +1,14 @@
--- Utility functions.
Util = {} Util = {}
--- Safely wraps an index for an array.
-- @param array table The array to index.
-- @param index number The desired index (can be out of bounds).
-- @return number The wrapped index within the array's bounds.
function Util.safeindex(array, index) function Util.safeindex(array, index)
return ((index - 1 + #array) % #array) + 1 return ((index - 1 + #array) % #array) + 1
end end
--- Navigates to a screen by its ID.
-- @param screen_id string The ID of the screen to go to.
function Util.go_to_screen_by_id(screen_id) function Util.go_to_screen_by_id(screen_id)
local screen = Screen.get_by_id(screen_id) local screen_index = Context.screen_indices_by_id[screen_id]
if screen then if screen_index then
Context.game.current_screen = screen_id Context.current_screen = screen_index
local all_decisions_for_screen = Decision.get_for_screen(screen) Context.selected_decision_index = 1 else
Context.game.decisions = Decision.filter_available(all_decisions_for_screen) PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found or not indexed!"})
Context.game.selected_decision_index = 1
screen.init() -- Initialize the new screen
else
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found!"})
end end
end end
-- Checks if a table contains a specific value.
-- @param t table The table to check.
-- @param value any The value to look for.
function Util.contains(t, value)
for i = 1, #t do
if t[i] == value then
return true
end
end
return false
end

View File

@@ -6,10 +6,6 @@ AudioTestWindow = {
last_pressed = false last_pressed = false
} }
--- Generates menu items for audio test.
-- @param list_func table List of audio functions.
-- @param index_func number Current index of selected function.
-- @return table Generated menu items.
function AudioTestWindow.generate_menuitems(list_func, index_func) function AudioTestWindow.generate_menuitems(list_func, index_func)
return { return {
{ {
@@ -38,8 +34,6 @@ function AudioTestWindow.generate_menuitems(list_func, index_func)
} }
end end
--- Generates list of audio functions.
-- @return table A sorted list of audio function names.
function AudioTestWindow.generate_listfunc() function AudioTestWindow.generate_listfunc()
local result = {} local result = {}
@@ -54,13 +48,11 @@ function AudioTestWindow.generate_listfunc()
return result return result
end end
--- Navigates back from audio test window.
function AudioTestWindow.back() function AudioTestWindow.back()
Audio.sfx_deselect() Audio.sfx_deselect()
GameWindow.set_state("menu") GameWindow.set_state(WINDOW_MENU)
end end
--- Initializes audio test window.
function AudioTestWindow.init() function AudioTestWindow.init()
AudioTestWindow.last_pressed = false AudioTestWindow.last_pressed = false
AudioTestWindow.index_menu = 1 AudioTestWindow.index_menu = 1
@@ -71,13 +63,11 @@ function AudioTestWindow.init()
) )
end end
--- Draws audio test window.
function AudioTestWindow.draw() function AudioTestWindow.draw()
UI.draw_top_bar("Audio test") UI.draw_top_bar("Audio test")
UI.draw_menu(AudioTestWindow.menuitems, AudioTestWindow.index_menu, 20, 50) UI.draw_menu(AudioTestWindow.menuitems, AudioTestWindow.index_menu, 20, 50)
end end
--- Updates audio test window logic.
function AudioTestWindow.update() function AudioTestWindow.update()
if Input.up() then if Input.up() then
AudioTestWindow.index_menu = Util.safeindex(AudioTestWindow.menuitems, AudioTestWindow.index_menu - 1) AudioTestWindow.index_menu = Util.safeindex(AudioTestWindow.menuitems, AudioTestWindow.index_menu - 1)

View File

@@ -3,7 +3,6 @@ ConfigurationWindow = {
selected_control = 1, selected_control = 1,
} }
--- Initializes configuration window.
function ConfigurationWindow.init() function ConfigurationWindow.init()
ConfigurationWindow.controls = { ConfigurationWindow.controls = {
UI.create_decision_item( UI.create_decision_item(
@@ -17,7 +16,6 @@ function ConfigurationWindow.init()
} }
end end
--- Draws configuration window.
function ConfigurationWindow.draw() function ConfigurationWindow.draw()
UI.draw_top_bar("Configuration") UI.draw_top_bar("Configuration")
@@ -57,10 +55,9 @@ function ConfigurationWindow.draw()
Print.text("Press B to go back", x_start, 120, Config.colors.light_grey) Print.text("Press B to go back", x_start, 120, Config.colors.light_grey)
end end
--- Updates configuration window logic.
function ConfigurationWindow.update() function ConfigurationWindow.update()
if Input.menu_back() then if Input.menu_back() then
GameWindow.set_state("menu") GameWindow.set_state(WINDOW_MENU)
return return
end end

View File

@@ -1,62 +1,95 @@
local _available_decisions = {} --- Draws the main game window content.
local _selected_decision_index = 1 -- This includes the current screen's background, top bar, decisions, and all active sprites.
-- @function GameWindow.draw
--- Draws the game window.
function GameWindow.draw() function GameWindow.draw()
local screen = Screen.get_by_id(Context.game.current_screen) local screen = Context.screens[Context.current_screen]
Map.draw(screen.background) Map.draw(screen.background)
UI.draw_top_bar(screen.name) UI.draw_top_bar(screen.name)
if #_available_decisions > 0 then if screen and screen.decisions and #screen.decisions > 0 then
UI.draw_decision_selector(_available_decisions, _selected_decision_index) local available_decisions = {}
for _, decision_id in ipairs(screen.decisions) do
local decision = Decision.get(decision_id)
if decision and decision.condition() then
table.insert(available_decisions, decision)
end
end
if #available_decisions > 0 then
UI.draw_decision_selector(available_decisions, Context.selected_decision_index)
end
end end
Sprite.draw() Sprite.draw()
end end
--- Updates the game window logic. --- Updates the logic for the main game window.
-- Handles input, navigates between screens, calls the current screen's and situation's update functions,
-- and processes player decisions.
-- @function GameWindow.update
function GameWindow.update() function GameWindow.update()
local previous_screen_index = Context.current_screen
if Input.menu_back() then if Input.menu_back() then
Context.current_window = "menu" Context.active_window = WINDOW_MENU
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
return return
end end
local screen = Screen.get_by_id(Context.game.current_screen) if Input.up() then
Context.current_screen = Context.current_screen - 1
if Context.current_screen < 1 then
Context.current_screen = #Context.screens
end
Context.selected_decision_index = 1 elseif Input.down() then
Context.current_screen = Context.current_screen + 1
if Context.current_screen > #Context.screens then
Context.current_screen = 1
end
Context.selected_decision_index = 1 end
local screen = Context.screens[Context.current_screen]
screen.update() screen.update()
-- Handle situations (Context.game.current_situation is still present) if previous_screen_index ~= Context.current_screen then
if Context.game.current_situation then screen.init()
local current_situation_obj = Situation.get_by_id(Context.game.current_situation) end
if Context.current_situation then
local current_situation_obj = Situation.get(Context.current_situation)
if current_situation_obj and current_situation_obj.update then if current_situation_obj and current_situation_obj.update then
current_situation_obj.update() current_situation_obj.update()
end end
end end
-- Fetch and filter decisions locally if screen and screen.decisions and #screen.decisions > 0 then
local all_decisions_for_screen = Decision.get_for_screen(screen) local available_decisions = {}
_available_decisions = Decision.filter_available(all_decisions_for_screen) for _, decision_id in ipairs(screen.decisions) do
local decision = Decision.get(decision_id)
if decision and decision.condition() then table.insert(available_decisions, decision)
end
end
if #_available_decisions == 0 then return end if #available_decisions == 0 then return end
local new_selected_decision_index = UI.update_decision_selector( local new_selected_decision_index = UI.update_decision_selector(
_available_decisions, available_decisions,
_selected_decision_index Context.selected_decision_index
) )
if new_selected_decision_index ~= _selected_decision_index then if new_selected_decision_index ~= Context.selected_decision_index then
_selected_decision_index = new_selected_decision_index Context.selected_decision_index = new_selected_decision_index
end end
if Input.select() then if Input.select() then
local selected_decision = _available_decisions[_selected_decision_index] local selected_decision = available_decisions[Context.selected_decision_index]
if selected_decision and selected_decision.handle then if selected_decision and selected_decision.handle then Audio.sfx_select() selected_decision.handle()
Audio.sfx_select() end
selected_decision.handle()
end end
end end
end end
--- Sets the active window. --- Sets the active window state for the game.
-- @param new_state number The ID of the new active window. -- This function is typically called when transitioning between different game states (e.g., to a minigame).
-- @param new_state number The ID of the new active window (e.g., `WINDOW_MENU`, `WINDOW_GAME`).
-- @function GameWindow.set_state
function GameWindow.set_state(new_state) function GameWindow.set_state(new_state)
Context.current_window = new_state Context.active_window = new_state
end end

View File

@@ -1,38 +1,21 @@
IntroWindow.y = Config.screen.height
IntroWindow.speed = 0.5
IntroWindow.text = [[
Norman Reds everyday life
seems ordinary: work,
meetings, coffee, and
endless notifications.
But beneath him, or around
him — something is
constantly building, and
it soon becomes clear
that there is more going
on than meets the eye.
]]
--- Draws the intro window.
function IntroWindow.draw() function IntroWindow.draw()
local x = (Config.screen.width - 132) / 2 local x = (Config.screen.width - 132) / 2
Print.text(IntroWindow.text, x, IntroWindow.y, Config.colors.green) Print.text(Context.intro.text, x, Context.intro.y, Config.colors.green)
end end
--- Updates the intro window logic.
function IntroWindow.update() function IntroWindow.update()
IntroWindow.y = IntroWindow.y - IntroWindow.speed Context.intro.y = Context.intro.y - Context.intro.speed
local lines = 1 local lines = 1
for _ in string.gmatch(IntroWindow.text, "\n") do for _ in string.gmatch(Context.intro.text, "\n") do
lines = lines + 1 lines = lines + 1
end end
if IntroWindow.y < -lines * 8 then if Context.intro.y < -lines * 8 then
GameWindow.set_state("menu") GameWindow.set_state(WINDOW_MENU)
end end
if Input.menu_confirm() then if Input.menu_confirm() then
GameWindow.set_state("menu") GameWindow.set_state(WINDOW_MENU)
end end
end end

View File

@@ -1,64 +0,0 @@
local _windows = {}
--- Registers a window table.
-- @param id string The ID of the window (e.g., "splash", "menu").
-- @param window_table table The actual window module table (e.g., SplashWindow).
function Window.register(id, window_table)
_windows[id] = window_table
end
--- Retrieves a registered window table by its ID.
-- @param id string The ID of the window.
-- @return table The window module table.
function Window.get(id)
return _windows[id]
end
--- Sets the currently active window.
-- @param id string The ID of the window to activate.
function Window.set_current(id)
Context.current_window = id
end
--- Gets the ID of the currently active window.
-- @return string The ID of the active window.
function Window.get_current_id()
return Context.current_window
end
--- Gets the handler function for the currently active window.
-- This function is used by the main game loop to update and draw the active window.
-- @return function A function that updates and draws the current window.
function Window.get_current_handler()
local window_table = Window.get(Context.current_window)
if window_table and window_table.update and window_table.draw then
return function()
window_table.update()
window_table.draw()
end
else
-- Fallback handler for unregistered or incomplete windows
return function() trace("Error: No handler for window: " .. tostring(Context.current_window)) end
end
end
SplashWindow = {}
IntroWindow = {}
MenuWindow = {}
GameWindow = {}
PopupWindow = {}
ConfigurationWindow = {}
AudioTestWindow = {}
MinigameButtonMashWindow = {}
MinigameRhythmWindow = {}
MinigameDDRWindow = {}
-- Registration of all window modules
Window.register("splash", SplashWindow)
Window.register("intro", IntroWindow)
Window.register("menu", MenuWindow)
Window.register("game", GameWindow)
Window.register("popup", PopupWindow)
Window.register("configuration", ConfigurationWindow)
Window.register("audiotest", AudioTestWindow)
Window.register("minigame_button_mash", MinigameButtonMashWindow)
Window.register("minigame_rhythm", MinigameRhythmWindow)
Window.register("minigame_ddr", MinigameDDRWindow)

View File

@@ -1,17 +1,13 @@
local _menu_items = {}
--- Draws the menu window.
function MenuWindow.draw() function MenuWindow.draw()
UI.draw_top_bar("Main Menu") UI.draw_top_bar("Main Menu")
UI.draw_menu(_menu_items, Context.current_menu_item, 108, 70) UI.draw_menu(Context.menu_items, Context.selected_menu_item, 108, 70)
end end
--- Updates the menu window logic.
function MenuWindow.update() function MenuWindow.update()
Context.current_menu_item = UI.update_menu(_menu_items, Context.current_menu_item) Context.selected_menu_item = UI.update_menu(Context.menu_items, Context.selected_menu_item)
if Input.menu_confirm() then if Input.menu_confirm() then
local selected_item = _menu_items[Context.current_menu_item] local selected_item = Context.menu_items[Context.selected_menu_item]
if selected_item and selected_item.decision then if selected_item and selected_item.decision then
Audio.sfx_select() Audio.sfx_select()
selected_item.decision() selected_item.decision()
@@ -19,55 +15,46 @@ function MenuWindow.update()
end end
end end
--- Starts a new game from the menu.
function MenuWindow.new_game() function MenuWindow.new_game()
Context.new_game() GameWindow.set_state("game") Context.new_game() GameWindow.set_state(WINDOW_GAME)
end end
--- Loads a game from the menu.
function MenuWindow.load_game() function MenuWindow.load_game()
Context.load_game() GameWindow.set_state("game") Context.load_game() GameWindow.set_state(WINDOW_GAME)
end end
--- Saves the current game from the menu.
function MenuWindow.save_game() function MenuWindow.save_game()
Context.save_game() end Context.save_game() end
--- Resumes the game from the menu.
function MenuWindow.resume_game() function MenuWindow.resume_game()
GameWindow.set_state("game") GameWindow.set_state(WINDOW_GAME)
end end
--- Exits the game.
function MenuWindow.exit() function MenuWindow.exit()
exit() exit()
end end
--- Opens the configuration menu.
function MenuWindow.configuration() function MenuWindow.configuration()
ConfigurationWindow.init() ConfigurationWindow.init()
GameWindow.set_state("configuration") GameWindow.set_state(WINDOW_CONFIGURATION)
end end
--- Opens the audio test menu.
function MenuWindow.audio_test() function MenuWindow.audio_test()
AudioTestWindow.init() AudioTestWindow.init()
GameWindow.set_state("audiotest") GameWindow.set_state(WINDOW_AUDIOTEST)
end end
--- Refreshes menu items.
function MenuWindow.refresh_menu_items() function MenuWindow.refresh_menu_items()
_menu_items = {} Context.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(Context.menu_items, {label = "Resume Game", decision = MenuWindow.resume_game})
table.insert(_menu_items, {label = "Save Game", decision = MenuWindow.save_game}) table.insert(Context.menu_items, {label = "Save Game", decision = MenuWindow.save_game})
end end
table.insert(_menu_items, {label = "New Game", decision = MenuWindow.new_game}) table.insert(Context.menu_items, {label = "New Game", decision = MenuWindow.new_game})
table.insert(_menu_items, {label = "Load Game", decision = MenuWindow.load_game}) table.insert(Context.menu_items, {label = "Load Game", decision = MenuWindow.load_game})
table.insert(_menu_items, {label = "Configuration", decision = MenuWindow.configuration}) table.insert(Context.menu_items, {label = "Configuration", decision = MenuWindow.configuration})
table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test}) table.insert(Context.menu_items, {label = "Audio Test", decision = MenuWindow.audio_test})
table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit}) table.insert(Context.menu_items, {label = "Exit", decision = MenuWindow.exit})
Context.current_menu_item = 1 Context.selected_menu_item = 1 end
end

View File

@@ -1,16 +1,10 @@
--- Initializes DDR minigame state.
-- @param params table Optional parameters for configuration.
function MinigameDDRWindow.init(params) function MinigameDDRWindow.init(params)
Context.minigame_ddr = Minigame.configure_ddr(params) Context.minigame_ddr = Minigames.configure_ddr(params)
end end
--- Starts the DDR minigame.
-- @param return_window number The window ID to return to after the minigame.
-- @param[opt] song_key string The key of the song to play.
-- @param[opt] params table Optional parameters for minigame configuration.
function MinigameDDRWindow.start(return_window, song_key, params) function MinigameDDRWindow.start(return_window, song_key, params)
MinigameDDRWindow.init(params) MinigameDDRWindow.init(params)
Context.minigame_ddr.return_window = return_window or "game" Context.minigame_ddr.return_window = return_window or WINDOW_GAME
Context.minigame_ddr.debug_song_key = song_key Context.minigame_ddr.debug_song_key = song_key
if song_key and Songs and Songs[song_key] then if song_key and Songs and Songs[song_key] then
Context.minigame_ddr.current_song = Songs[song_key] Context.minigame_ddr.current_song = Songs[song_key]
@@ -25,10 +19,9 @@ function MinigameDDRWindow.start(return_window, song_key, params)
Context.minigame_ddr.debug_status = "Random mode" Context.minigame_ddr.debug_status = "Random mode"
end end
end end
Context.current_window = "minigame_ddr" Context.active_window = WINDOW_MINIGAME_DDR
end end
--- Spawns a random arrow.
local function spawn_arrow() local function spawn_arrow()
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
local target = mg.target_arrows[math.random(1, 4)] local target = mg.target_arrows[math.random(1, 4)]
@@ -39,8 +32,6 @@ local function spawn_arrow()
}) })
end end
--- Spawns an arrow in a specific direction.
-- @param direction string The direction of the arrow ("left", "down", "up", "right").
local function spawn_arrow_dir(direction) local function spawn_arrow_dir(direction)
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
for _, target in ipairs(mg.target_arrows) do for _, target in ipairs(mg.target_arrows) do
@@ -55,28 +46,17 @@ local function spawn_arrow_dir(direction)
end end
end end
--- Checks if an arrow is hit.
-- @param arrow table The arrow data.
-- @return boolean True if the arrow is hit, false otherwise.
local function check_hit(arrow) local function check_hit(arrow)
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
end end
--- Checks if an arrow is missed.
-- @param arrow table The arrow data.
-- @return boolean True if the arrow is missed, false otherwise.
local function check_miss(arrow) local function check_miss(arrow)
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.
-- @param x number The x-coordinate.
-- @param y number The y-coordinate.
-- @param direction string The direction 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 = 12
local half = size / 2 local half = size / 2
@@ -95,21 +75,20 @@ local function draw_arrow(x, y, direction, color)
end end
end end
--- Updates DDR minigame logic.
function MinigameDDRWindow.update() function MinigameDDRWindow.update()
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
if mg.bar_fill >= mg.max_fill then if mg.bar_fill >= mg.max_fill then
Meter.on_minigame_complete() Meters.on_minigame_complete()
Meter.show() Meters.show()
Context.current_window = mg.return_window Context.active_window = mg.return_window
return return
end end
mg.frame_counter = mg.frame_counter + 1 mg.frame_counter = mg.frame_counter + 1
if mg.use_pattern and mg.current_song and mg.current_song.end_frame then if mg.use_pattern and mg.current_song and mg.current_song.end_frame then
if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then
Meter.on_minigame_complete() Meters.on_minigame_complete()
Meter.show() Meters.show()
Context.current_window = mg.return_window Context.active_window = mg.return_window
return return
end end
end end
@@ -188,7 +167,6 @@ function MinigameDDRWindow.update()
end end
end end
--- Draws DDR minigame.
function MinigameDDRWindow.draw() function MinigameDDRWindow.draw()
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
if not mg then if not mg then
@@ -196,11 +174,11 @@ function MinigameDDRWindow.draw()
print("DDR ERROR: Context not initialized", 10, 10, 12) print("DDR ERROR: Context not initialized", 10, 10, 12)
print("Press Z to return", 10, 20, 12) print("Press Z to return", 10, 20, 12)
if Input.select() then if Input.select() then
Context.current_window = "game" Context.active_window = WINDOW_GAME
end end
return return
end end
if mg.return_window == "game" then if mg.return_window == WINDOW_GAME then
GameWindow.draw() GameWindow.draw()
end end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)

View File

@@ -1,19 +1,13 @@
--- Initializes button mash minigame state.
-- @param params table Optional parameters for configuration.
function MinigameButtonMashWindow.init(params) function MinigameButtonMashWindow.init(params)
Context.minigame_button_mash = Minigame.configure_button_mash(params) Context.minigame_button_mash = Minigames.configure_button_mash(params)
end end
--- Starts the button mash minigame.
-- @param return_window number The window ID to return to after the minigame.
-- @param[opt] params table Optional parameters for minigame configuration.
function MinigameButtonMashWindow.start(return_window, params) function MinigameButtonMashWindow.start(return_window, params)
MinigameButtonMashWindow.init(params) MinigameButtonMashWindow.init(params)
Context.minigame_button_mash.return_window = return_window or "game" Context.minigame_button_mash.return_window = return_window or WINDOW_GAME
Context.current_window = "minigame_button_mash" Context.active_window = WINDOW_MINIGAME_BUTTON_MASH
end end
--- Updates button mash minigame logic.
function MinigameButtonMashWindow.update() function MinigameButtonMashWindow.update()
local mg = Context.minigame_button_mash local mg = Context.minigame_button_mash
if Input.select() then if Input.select() then
@@ -24,9 +18,9 @@ function MinigameButtonMashWindow.update()
end end
end end
if mg.bar_fill >= mg.max_fill then if mg.bar_fill >= mg.max_fill then
Meter.on_minigame_complete() Meters.on_minigame_complete()
Meter.show() Meters.show()
Context.current_window = mg.return_window Context.active_window = mg.return_window
return return
end end
local degradation = mg.base_degradation + (mg.bar_fill * mg.degradation_multiplier) local degradation = mg.base_degradation + (mg.bar_fill * mg.degradation_multiplier)
@@ -39,10 +33,9 @@ function MinigameButtonMashWindow.update()
end end
end end
--- Draws button mash minigame.
function MinigameButtonMashWindow.draw() function MinigameButtonMashWindow.draw()
local mg = Context.minigame_button_mash local mg = Context.minigame_button_mash
if mg.return_window == "game" then if mg.return_window == WINDOW_GAME then
GameWindow.draw() GameWindow.draw()
end end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)

View File

@@ -1,19 +1,13 @@
--- Initializes rhythm minigame state.
-- @param params table Optional parameters for configuration.
function MinigameRhythmWindow.init(params) function MinigameRhythmWindow.init(params)
Context.minigame_rhythm = Minigame.configure_rhythm(params) Context.minigame_rhythm = Minigames.configure_rhythm(params)
end end
--- Starts the rhythm minigame.
-- @param return_window number The window ID to return to after the minigame.
-- @param[opt] params table Optional parameters for minigame configuration.
function MinigameRhythmWindow.start(return_window, params) function MinigameRhythmWindow.start(return_window, params)
MinigameRhythmWindow.init(params) MinigameRhythmWindow.init(params)
Context.minigame_rhythm.return_window = return_window or "game" Context.minigame_rhythm.return_window = return_window or WINDOW_GAME
Context.current_window = "minigame_rhythm" Context.active_window = WINDOW_MINIGAME_RHYTHM
end end
--- Updates rhythm minigame logic.
function MinigameRhythmWindow.update() function MinigameRhythmWindow.update()
local mg = Context.minigame_rhythm local mg = Context.minigame_rhythm
mg.line_position = mg.line_position + (mg.line_speed * mg.line_direction) mg.line_position = mg.line_position + (mg.line_speed * mg.line_direction)
@@ -46,9 +40,9 @@ function MinigameRhythmWindow.update()
end end
end end
if mg.score >= mg.max_score then if mg.score >= mg.max_score then
Meter.on_minigame_complete() Meters.on_minigame_complete()
Meter.show() Meters.show()
Context.current_window = mg.return_window Context.active_window = mg.return_window
return return
end end
if mg.button_pressed_timer > 0 then if mg.button_pressed_timer > 0 then
@@ -56,10 +50,9 @@ function MinigameRhythmWindow.update()
end end
end end
--- Draws rhythm minigame.
function MinigameRhythmWindow.draw() function MinigameRhythmWindow.draw()
local mg = Context.minigame_rhythm local mg = Context.minigame_rhythm
if mg.return_window == "game" then if mg.return_window == WINDOW_GAME then
GameWindow.draw() GameWindow.draw()
end end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)

View File

@@ -5,18 +5,14 @@ local POPUP_HEIGHT = 80
local TEXT_MARGIN_X = POPUP_X + 10 local TEXT_MARGIN_X = POPUP_X + 10
local TEXT_MARGIN_Y = POPUP_Y + 10 local TEXT_MARGIN_Y = POPUP_Y + 10
local LINE_HEIGHT = 8 local LINE_HEIGHT = 8
--- Displays a popup window.
-- @param content_strings table A table of strings to display in the popup.
function PopupWindow.show(content_strings) function PopupWindow.show(content_strings)
Context.popup.show = true Context.popup.show = true
Context.popup.content = content_strings or {} GameWindow.set_state("popup") end Context.popup.content = content_strings or {} GameWindow.set_state(WINDOW_POPUP) end
--- Hides the popup window.
function PopupWindow.hide() function PopupWindow.hide()
Context.popup.show = false Context.popup.show = false
Context.popup.content = {} GameWindow.set_state("game") end Context.popup.content = {} GameWindow.set_state(WINDOW_GAME) end
--- Updates popup window logic.
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 PopupWindow.hide() if Input.menu_confirm() or Input.menu_back() then PopupWindow.hide()
@@ -24,7 +20,6 @@ function PopupWindow.update()
end end
end end
--- Draws the popup window.
function PopupWindow.draw() function PopupWindow.draw()
if Context.popup.show then if Context.popup.show then
rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.black) rect(POPUP_X, POPUP_Y, POPUP_WIDTH, POPUP_HEIGHT, Config.colors.black)

View File

@@ -1,4 +1,3 @@
--- Draws the splash window.
function SplashWindow.draw() function SplashWindow.draw()
local txt = "Definitely not an Impostor" local txt = "Definitely not an Impostor"
local w = #txt * 6 local w = #txt * 6
@@ -7,10 +6,9 @@ function SplashWindow.draw()
print(txt, x, y, 12) print(txt, x, y, 12)
end end
--- Updates the splash window logic.
function SplashWindow.update() function SplashWindow.update()
Context.splash_timer = Context.splash_timer - 1 Context.splash_timer = Context.splash_timer - 1
if Context.splash_timer <= 0 or Input.menu_confirm() then if Context.splash_timer <= 0 or Input.menu_confirm() then
GameWindow.set_state("intro") GameWindow.set_state(WINDOW_INTRO)
end end
end end