Compare commits

...

11 Commits

Author SHA1 Message Date
777c27aa54 section and within annotations for ldoc
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2026-02-25 23:24:53 +01:00
297ee8b622 Merge pull request 'refact by claude' (#14) from claude-refact into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #14
2026-02-23 09:44:07 +00:00
Zsolt Tasnadi
7deeffa8d6 refact by claude
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2026-02-23 10:40:14 +01:00
272a54ea87 fix colors
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-22 21:32:24 +01:00
14b14ffc0c window.register.lua
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-22 21:26:59 +01:00
7e87a78a15 Merge pull request 'feature/context-refactoring' (#13) from feature/context-refactoring into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #13
2026-02-22 19:19:02 +00:00
62d4863a1a refact
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2026-02-22 20:18:40 +01:00
d9febf16e0 remove prular defitions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-22 15:20:10 +01:00
6f5b17147c Merge pull request 'feature/docs' (#12) from feature/docs into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #12
2026-02-21 23:31:08 +00:00
b7791fb9ce Merge pull request 'screen init, update, optional decision condition' (#11) from feature/screen-init-update into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #11
2026-02-21 22:35:23 +00:00
7e1dd28808 screen init, update, optional decision condition
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2026-02-21 23:35:00 +01:00
44 changed files with 619 additions and 423 deletions

View File

@@ -13,8 +13,9 @@ globals = {
"Audio", "Audio",
"Config", "Config",
"Context", "Context",
"Meters", "Meter",
"Minigames", "Minigame",
"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 Minigames Documentation # Impostor Minigame 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** - Minigames render over the current game window - **Overlay rendering** - Minigame 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

@@ -58,8 +58,8 @@ export: build
@ls -lh $(PROJECT)-$(VERSION).* $(PROJECT).tic $(PROJECT).html.zip 2>/dev/null || true @ls -lh $(PROJECT)-$(VERSION).* $(PROJECT).tic $(PROJECT).html.zip 2>/dev/null || true
watch: watch:
make build $(MAKE) build
fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do make build; done fswatch -o $(SRC_DIR) $(ORDER) assets | while read; do $(MAKE) build; done
import_assets: $(OUTPUT) import_assets: $(OUTPUT)
@TIC_CMD="load $(OUTPUT) &"; \ @TIC_CMD="load $(OUTPUT) &"; \
@@ -221,10 +221,3 @@ docs: build
.PHONY: all build export watch import_assets export_assets clean lint ci-version ci-export ci-upload ci-update install_precommit_hook docs .PHONY: all build export watch import_assets export_assets clean lint ci-version ci-export ci-upload ci-update install_precommit_hook docs
#-- <WAVES>
#-- 000:224578acdeeeeddcba95434567653100
#-- </WAVES>
#
#-- <SFX>
#-- 000:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000
#-- </SFX>

View File

@@ -1,10 +1,15 @@
meta/meta.header.lua meta/meta.header.lua
init/init.modules.lua init/init.module.lua
init/init.config.lua init/init.config.lua
init/init.minigames.lua init/init.minigame.lua
init/init.meters.lua init/init.meter.lua
init/init.context.lua
system/system.util.lua system/system.util.lua
init/init.windows.lua system/system.print.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
@@ -27,12 +32,8 @@ 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
init/init.context.lua window/window.manager.lua
data/data.songs.lua window/window.register.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

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

View File

@@ -1,6 +1,7 @@
--- @section Songs
-- DDR Arrow Spawn Patterns -- DDR Arrow Spawn Patterns
-- Each song defines when arrows should spawn, synced to music beats -- Each song defines when arrows should spawn, synced to music beats
Songs = { Songs = {
-- Example song pattern -- Example song pattern
test_song = { test_song = {
@@ -108,6 +109,7 @@ Songs = {
} }
--- Converts beats to frames. --- Converts beats to frames.
--- @within Songs
-- @param beat number The beat number. -- @param beat number The beat number.
-- @param bpm number Beats per minute. -- @param bpm number Beats per minute.
-- @param[opt] fps number Frames per second (default: 60). -- @param[opt] fps number Frames per second (default: 60).
@@ -120,6 +122,7 @@ function frame_from_beat(beat, bpm, fps)
end end
--- Converts beat notation to frame pattern. --- Converts beat notation to frame pattern.
--- @within Songs
-- @param beats table A table of beat data, e.g., {{1, "left"}, {2, "down"}}. -- @param beats table A table of beat data, e.g., {{1, "left"}, {2, "down"}}.
-- @param bpm number Beats per minute. -- @param bpm number Beats per minute.
-- @param[opt] fps number Frames per second (default: 60). -- @param[opt] fps number Frames per second (default: 60).

View File

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

View File

@@ -1,6 +1,8 @@
--- @section Decision
local _decisions = {} local _decisions = {}
--- Registers a decision definition. --- Registers a decision definition.
--- @within Decision
-- @param decision table The decision data table. -- @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
@@ -25,14 +27,49 @@ function Decision.register(decision)
end end
--- Gets a decision by ID. --- Gets a decision by ID.
--- @within Decision
-- @param id string The ID of the decision. -- @param id string The ID of the decision.
-- @return table The decision table or nil. -- @return table The decision table or nil.
function Decision.get(id) function Decision.get_by_id(id)
return _decisions[id] return _decisions[id]
end end
--- Gets all registered decisions. --- Gets all registered decisions.
--- @within Decision
-- @return table A table of all registered decisions. -- @return table A table of all registered decisions.
function Decision.get_all() function Decision.get_all()
return _decisions return _decisions
end end
--- Gets decision objects based on a screen's data.
--- @within Decision
-- @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_by_id(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.
--- @within Decision
-- @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,6 +1,5 @@
Decision.register({ Decision.register({
id = "play_button_mash", id = "play_button_mash",
label = "Play Button Mash", label = "Play Button Mash",
handle = function() Meters.hide() MinigameButtonMashWindow.start(WINDOW_GAME) end, handle = function() Meter.hide() MinigameButtonMashWindow.start("game") end,
condition = function() return true end
}) })

View File

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

View File

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

View File

@@ -1,34 +1,38 @@
local DEFAULT_CONFIG = { Config = {}
--- Return initial data for Config
--- @within Config
function Config.initial_data()
return {
screen = { screen = {
width = 240, width = 240,
height = 136 height = 136
}, },
colors = { colors = {
black = 0, black = 2,
light_grey = 13, light_grey = 13,
dark_grey = 14, dark_grey = 14,
red = 2, red = 0,
green = 6, green = 7,
blue = 9, blue = 9,
white = 12, white = 12,
item = 12, item = 12,
meter_bg = 12 meter_bg = 12
}, },
player = {
sprite_id = 1
},
timing = { timing = {
splash_duration = 120 splash_duration = 120
} }
} }
end
-- Game configuration settings. --- Restores default configuration settings.
Config = { --- @within Config
screen = DEFAULT_CONFIG.screen, function Config.reset()
colors = DEFAULT_CONFIG.colors, local initial = Config.initial_data()
player = DEFAULT_CONFIG.player, Config.screen = initial.screen
timing = DEFAULT_CONFIG.timing, Config.colors = initial.colors
} 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
@@ -36,22 +40,20 @@ local CONFIG_SPLASH_DURATION_ADDRESS = 3
local CONFIG_MAGIC_VALUE = 0xDE local CONFIG_MAGIC_VALUE = 0xDE
--- Saves the current configuration. --- Saves the current configuration.
--- @within Config
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. --- Loads saved configuration.
--- @within Config
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.restore_defaults() Config.reset()
end end
end end
--- Restores default configuration settings.
function Config.restore_defaults()
Config.timing.splash_duration = DEFAULT_CONFIG.timing.splash_duration
end
Config.load() Config.load()

View File

@@ -2,118 +2,73 @@ 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
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6 --- Global game context.
--- @section Context
Context = {}
--- Gets initial data for Context. --- Gets initial data for Context.
--- @within Context
-- @return table Initial context data. -- @return table Initial context data.
local function get_initial_data() function Context.initial_data()
return { return {
active_window = WINDOW_SPLASH, current_menu_item = 1,
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,
screens = {}, minigame_ddr = Minigame.get_default_ddr(),
minigame_ddr = Minigames.get_default_ddr(), minigame_button_mash = Minigame.get_default_button_mash(),
minigame_button_mash = Minigames.get_default_button_mash(), minigame_rhythm = Minigame.get_default_rhythm(),
minigame_rhythm = Minigames.get_default_rhythm(), meters = Meter.get_initial(),
meters = Meters.get_initial(), game = {
--- Active sprites. current_screen = "home",
sprites = {},
--- Current situation ID.
current_situation = nil, current_situation = nil,
} }
}
end end
--- Global game context.
Context = {}
--- Resets game context to initial state. --- Resets game context to initial state.
local function reset_context_to_initial_state() --- @within Context
local initial_data = get_initial_data() function Context.reset()
local initial_data = Context.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
end end
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
reset_context_to_initial_state()
--- Starts a new game. --- Starts a new game.
--- @within Context
function Context.new_game() function Context.new_game()
reset_context_to_initial_state() Context.reset()
Context.game_in_progress = true Context.game_in_progress = true
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
Context.screens[Context.current_screen].init() Screen.get_by_id(Context.game.current_screen).init()
end end
--- Saves the current game state. --- Saves the current game state.
--- @within Context
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. --- Loads a saved game state.
--- @within Context
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()
Context.screens[Context.current_screen].init() Screen.get_by_id(Context.game.current_screen).init()
end end

View File

@@ -1,3 +1,4 @@
--- @section Meter
local METER_MAX = 1000 local METER_MAX = 1000
local METER_DEFAULT = 500 local METER_DEFAULT = 500
local METER_DECAY_PER_FRAME = 0.02 local METER_DECAY_PER_FRAME = 0.02
@@ -7,14 +8,15 @@ local COMBO_MAX_BONUS = 0.16
local COMBO_TIMEOUT_FRAMES = 600 local COMBO_TIMEOUT_FRAMES = 600
-- Internal meters for tracking game progress and player stats. -- Internal meters for tracking game progress and player stats.
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. --- Gets initial meter values.
--- @within Meter
-- @return table A table of initial meter values. -- @return table A table of initial meter values.
function Meters.get_initial() function Meter.get_initial()
return { return {
ism = METER_DEFAULT, ism = METER_DEFAULT,
wpm = METER_DEFAULT, wpm = METER_DEFAULT,
@@ -26,24 +28,28 @@ function Meters.get_initial()
end end
--- Hides meters. --- Hides meters.
function Meters.hide() --- @within Meter
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. --- Shows meters.
function Meters.show() --- @within Meter
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. --- Gets max meter value.
--- @within Meter
-- @return number The maximum meter value. -- @return number The maximum meter value.
function Meters.get_max() function Meter.get_max()
return METER_MAX return METER_MAX
end end
--- Gets combo multiplier. --- Gets combo multiplier.
--- @within Meter
-- @return number The current combo multiplier. -- @return number The current combo multiplier.
function Meters.get_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
@@ -51,7 +57,8 @@ function Meters.get_combo_multiplier()
end end
--- Updates all meters. --- Updates all meters.
function Meters.update() --- @within Meter
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)
@@ -67,9 +74,10 @@ function Meters.update()
end end
--- Adds amount to a meter. --- Adds amount to a meter.
--- @within Meter
-- @param key string The meter key (e.g., "wpm", "ism", "bm"). -- @param key string The meter key (e.g., "wpm", "ism", "bm").
-- @param amount number The amount to add. -- @param amount number The amount to add.
function Meters.add(key, amount) 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
@@ -78,12 +86,13 @@ function Meters.add(key, amount)
end end
--- Called on minigame completion. --- Called on minigame completion.
function Meters.on_minigame_complete() --- @within Meter
function Meter.on_minigame_complete()
local m = Context.meters local m = Context.meters
local gain = math.floor(METER_GAIN_PER_CHORE * Meters.get_combo_multiplier()) local gain = math.floor(METER_GAIN_PER_CHORE * Meter.get_combo_multiplier())
Meters.add("wpm", gain) Meter.add("wpm", gain)
Meters.add("ism", gain) Meter.add("ism", gain)
Meters.add("bm", gain) Meter.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,7 +1,8 @@
-- Manages minigame configurations and initial states. -- Manages minigame configurations and initial states.
Minigames = {} --- @section Minigame
--- Applies parameters to defaults. --- Applies parameters to defaults
--- @within Minigame
-- @param defaults table The default configuration table. -- @param defaults table The default configuration table.
-- @param params table The parameters to apply. -- @param params table The parameters to apply.
-- @return table The updated configuration table. -- @return table The updated configuration table.
@@ -14,8 +15,9 @@ local function apply_params(defaults, params)
end end
--- Gets default DDR minigame configuration. --- Gets default DDR minigame configuration.
--- @within Minigame
-- @return table The default DDR minigame configuration. -- @return table The default DDR minigame configuration.
function Minigames.get_default_ddr() 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)
@@ -55,8 +57,9 @@ function Minigames.get_default_ddr()
end end
--- Gets default button mash minigame configuration. --- Gets default button mash minigame configuration.
--- @within Minigame
-- @return table The default button mash minigame configuration. -- @return table The default button mash minigame configuration.
function Minigames.get_default_button_mash() function Minigame.get_default_button_mash()
return { return {
bar_fill = 0, bar_fill = 0,
max_fill = 100, max_fill = 100,
@@ -77,8 +80,9 @@ function Minigames.get_default_button_mash()
end end
--- Gets default rhythm minigame configuration. --- Gets default rhythm minigame configuration.
--- @within Minigame
-- @return table The default rhythm minigame configuration. -- @return table The default rhythm minigame configuration.
function Minigames.get_default_rhythm() function Minigame.get_default_rhythm()
return { return {
line_position = 0, line_position = 0,
line_speed = 0.015, line_speed = 0.015,
@@ -106,22 +110,25 @@ function Minigames.get_default_rhythm()
end end
--- Configures DDR minigame. --- Configures DDR minigame.
--- @within Minigame
-- @param params table Optional parameters to override defaults. -- @param params table Optional parameters to override defaults.
-- @return table The configured DDR minigame state. -- @return table The configured DDR minigame state.
function Minigames.configure_ddr(params) function Minigame.configure_ddr(params)
return apply_params(Minigames.get_default_ddr(), params) return apply_params(Minigame.get_default_ddr(), params)
end end
--- Configures button mash minigame. --- Configures button mash minigame.
--- @within Minigame
-- @param params table Optional parameters to override defaults. -- @param params table Optional parameters to override defaults.
-- @return table The configured button mash minigame state. -- @return table The configured button mash minigame state.
function Minigames.configure_button_mash(params) function Minigame.configure_button_mash(params)
return apply_params(Minigames.get_default_button_mash(), params) return apply_params(Minigame.get_default_button_mash(), params)
end end
--- Configures rhythm minigame. --- Configures rhythm minigame.
--- @within Minigame
-- @param params table Optional parameters to override defaults. -- @param params table Optional parameters to override defaults.
-- @return table The configured rhythm minigame state. -- @return table The configured rhythm minigame state.
function Minigames.configure_rhythm(params) function Minigame.configure_rhythm(params)
return apply_params(Minigames.get_default_rhythm(), params) return apply_params(Minigame.get_default_rhythm(), params)
end end

13
inc/init/init.module.lua Normal file
View File

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

View File

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

View File

@@ -1,10 +0,0 @@
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,8 +1,11 @@
-- Manages game maps. -- Manages game maps.
--- @section Map
local _maps = {} local _maps = {}
--- Gets all registered maps as an array. --- Gets all registered maps as an array.
--- @within Map
-- @return table An array of registered map data. -- @return table An array of registered map data.
function Map.get_maps_array() function Map.get_maps_array()
local maps_array = {} local maps_array = {}
@@ -13,6 +16,7 @@ function Map.get_maps_array()
end end
--- Registers a map definition. --- Registers a map definition.
--- @within Map
-- @param map_data table The map data table. -- @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
@@ -22,6 +26,7 @@ function Map.register(map_data)
end end
--- Gets a map by ID. --- Gets a map by ID.
--- @within Map
-- @param map_id string The ID of the map. -- @param map_id string The ID of the map.
-- @return table The map data table or nil. -- @return table The map data table or nil.
function Map.get_by_id(map_id) function Map.get_by_id(map_id)
@@ -29,6 +34,7 @@ function Map.get_by_id(map_id)
end end
--- Draws a map. --- Draws a map.
--- @within Map
-- @param map_id string The ID of the map to draw. -- @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)

View File

@@ -1,6 +1,8 @@
--- @section Screen
local _screens = {} local _screens = {}
--- Registers a screen definition. --- Registers a screen definition.
--- @within Screen
-- @param screen_data table The screen data table. -- @param screen_data table The screen data table.
function Screen.register(screen_data) function Screen.register(screen_data)
if _screens[screen_data.id] then if _screens[screen_data.id] then
@@ -19,8 +21,16 @@ function Screen.register(screen_data)
end end
--- Gets a screen by ID. --- Gets a screen by ID.
--- @within Screen
-- @param screen_id string The ID of the screen. -- @param screen_id string The ID of the screen.
-- @return table The screen table or nil. -- @return table The screen table or nil.
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.
--- @within Screen
-- @return table A table containing all registered screen data, indexed by their IDs.
function Screen.get_all()
return _screens
end

View File

@@ -1,6 +1,8 @@
--- @section Situation
local _situations = {} local _situations = {}
--- Registers a situation definition. --- Registers a situation definition.
--- @within Situation
-- @param situation table The situation data table. -- @param situation table The situation data table.
function Situation.register(situation) function Situation.register(situation)
if not situation or not situation.id then if not situation or not situation.id then
@@ -20,27 +22,49 @@ function Situation.register(situation)
end end
--- Gets a situation by ID. --- Gets a situation by ID.
--- @within Situation
-- @param id string The situation ID. -- @param id string The situation ID.
-- @return table The situation table or nil. -- @return table The situation table or nil.
function Situation.get(id) function Situation.get_by_id(id)
return _situations[id] return _situations[id]
end end
--- Applies a situation. --- Gets all registered situations, optionally filtered by screen ID.
--- @within Situation
-- @param screen_id string Optional. If provided, returns situations associated with this screen ID.
-- @return table A table containing all registered situation data, indexed by their IDs, or filtered by screen_id.
function Situation.get_all(screen_id)
if screen_id then
local filtered_situations = {}
for _, situation in pairs(_situations) do
if situation.screen_id == screen_id then
table.insert(filtered_situations, situation)
end
end
return filtered_situations
end
return _situations
end
--- Applies a situation, checking screen compatibility and returning the new situation ID if successful.
--- @within Situation
-- @param id string The situation ID to apply. -- @param id string The situation ID to apply.
function Situation.apply(id) -- @param current_screen_id string The ID of the currently active screen.
local situation = Situation.get(id) -- @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 return nil
end end
local current_screen_obj = Screen.get_by_id(Context.current_screen) if Util.contains(screen.situations, id) then
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,6 +1,9 @@
--- @section Sprite
local _sprites = {} local _sprites = {}
local _active_sprites = {}
--- Registers a sprite definition. --- Registers a sprite definition.
--- @within Sprite
-- @param sprite_data table A table containing the sprite definition. -- @param sprite_data table A table containing the sprite definition.
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
@@ -14,6 +17,7 @@ function Sprite.register(sprite_data)
end end
--- Schedules a sprite for drawing. --- Schedules a sprite for drawing.
--- @within Sprite
-- @param id string The unique identifier of the sprite. -- @param id string The unique identifier of the sprite.
-- @param x number The x-coordinate. -- @param x number The x-coordinate.
-- @param y number The y-coordinate. -- @param y number The y-coordinate.
@@ -28,7 +32,7 @@ function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot)
return return
end end
Context.sprites[id] = { _active_sprites[id] = {
id = id, id = id,
x = x, x = x,
y = y, y = y,
@@ -41,18 +45,20 @@ function Sprite.show(id, x, y, colorkey, scale, flip_x, flip_y, rot)
end end
--- Hides a displayed sprite. --- Hides a displayed sprite.
--- @within Sprite
-- @param id string The unique identifier of the sprite. -- @param id string The unique identifier of the sprite.
function Sprite.hide(id) function Sprite.hide(id)
Context.sprites[id] = nil _active_sprites[id] = nil
end end
--- Draws all scheduled sprites. --- Draws all scheduled sprites.
--- @within Sprite
function Sprite.draw() function Sprite.draw()
for id, params in pairs(Context.sprites) do for id, params in pairs(_active_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 Context.sprites is not registered.") trace("Error: Sprite id " .. id .. " in _active_sprites is not registered.")
Context.sprites[id] = nil _active_sprites[id] = nil
end end
local colorkey = params.colorkey or sprite_data.colorkey or 0 local colorkey = params.colorkey or sprite_data.colorkey or 0

View File

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

View File

@@ -1,66 +1,26 @@
local STATE_HANDLERS = { --- @section Main
[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. --- Initializes game state.
--- @within Main
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). --- Main game loop (TIC-80 callback).
--- @within Main
function TIC() function TIC()
init_game() init_game()
cls(Config.colors.black) cls(Config.colors.black)
local handler = STATE_HANDLERS[Context.active_window] local handler = Window.get_current_handler() -- Get handler from Window manager
if handler then if handler then
handler() handler()
end end
Meters.update() Meter.update()
if Context.game_in_progress then if Context.game_in_progress then
UI.draw_meters() UI.draw_meters()
end end

View File

@@ -1,4 +1,7 @@
--- @section Print
--- Prints text with shadow. --- Prints text with shadow.
--- @within Print
-- @param text string The text to print. -- @param text string The text to print.
-- @param x number The x-coordinate. -- @param x number The x-coordinate.
-- @param y number The y-coordinate. -- @param y number The y-coordinate.
@@ -14,6 +17,7 @@ function Print.text(text, x, y, color, fixed, scale)
end end
--- Prints centered text with shadow. --- Prints centered text with shadow.
--- @within Print
-- @param text string The text to print. -- @param text string The text to print.
-- @param x number The x-coordinate for centering. -- @param x number The x-coordinate for centering.
-- @param y number The y-coordinate. -- @param y number The y-coordinate.

View File

@@ -1,4 +1,7 @@
--- @section UI
--- Draws the top bar. --- Draws the top bar.
--- @within UI
-- @param title string The title text to display. -- @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)
@@ -6,11 +9,13 @@ function UI.draw_top_bar(title)
end end
--- Draws dialog window. --- Draws dialog window.
--- @within UI
function UI.draw_dialog() function UI.draw_dialog()
PopupWindow.draw() PopupWindow.draw()
end end
--- Draws a menu. --- Draws a menu.
--- @within UI
-- @param items table A table of menu items. -- @param items table A table of menu items.
-- @param selected_item number The index of the currently selected item. -- @param selected_item number The index of the currently selected item.
-- @param x number The x-coordinate for the menu. -- @param x number The x-coordinate for the menu.
@@ -26,6 +31,7 @@ function UI.draw_menu(items, selected_item, x, y)
end end
--- Updates menu selection. --- Updates menu selection.
--- @within UI
-- @param items table A table of menu items. -- @param items table A table of menu items.
-- @param selected_item number The current index of the selected item. -- @param selected_item number The current index of the selected item.
-- @return number The updated index of the selected item. -- @return number The updated index of the selected item.
@@ -47,6 +53,7 @@ function UI.update_menu(items, selected_item)
end end
--- Wraps text. --- Wraps text.
--- @within UI
-- @param text string The text to wrap. -- @param text string The text to wrap.
-- @param max_chars_per_line number The maximum characters per line. -- @param max_chars_per_line number The maximum characters per line.
-- @return table A table of wrapped lines. -- @return table A table of wrapped lines.
@@ -80,6 +87,7 @@ function UI.word_wrap(text, max_chars_per_line)
end end
--- Creates a numeric stepper. --- Creates a numeric stepper.
--- @within UI
-- @param label string The label for the stepper. -- @param label string The label for the stepper.
-- @param value_getter function Function to get the current value. -- @param value_getter function Function to get the current value.
-- @param value_setter function Function to set the current value. -- @param value_setter function Function to set the current value.
@@ -102,6 +110,7 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max,
end end
--- Creates an action item. --- Creates an action item.
--- @within UI
-- @param label string The label for the action item. -- @param label string The label for the action item.
-- @param action function The function to execute when the item is selected. -- @param action function The function to execute when the item is selected.
-- @return table An action item control definition. -- @return table An action item control definition.
@@ -114,6 +123,7 @@ function UI.create_action_item(label, action)
end end
--- Draws decision selector. --- Draws decision selector.
--- @within UI
-- @param decisions table A table of decision items. -- @param decisions table A table of decision items.
-- @param selected_decision_index number The index of the selected decision. -- @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)
@@ -123,19 +133,23 @@ function UI.draw_decision_selector(decisions, selected_decision_index)
if #decisions > 0 then if #decisions > 0 then
local selected_decision = decisions[selected_decision_index] local selected_decision = decisions[selected_decision_index]
local decision_label = selected_decision.label local decision_label = selected_decision.label
local text_width = #decision_label * 4 local text_y = bar_y + 4 local text_width = #decision_label * 4
local text_y = bar_y + 4
local text_x = (Config.screen.width - text_width) / 2 local text_x = (Config.screen.width - text_width) / 2
Print.text("<", 2, text_y, Config.colors.green) Print.text("<", 2, text_y, Config.colors.green)
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. --- Draws meters.
--- @within UI
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 = Meters.get_max() local max = Meter.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 +159,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 = Meters.COLOR_WPM, row = 0 }, { key = "wpm", label = "WPM", color = Meter.COLOR_WPM, row = 0 },
{ key = "ism", label = "ISM", color = Meters.COLOR_ISM, row = 1 }, { key = "ism", label = "ISM", color = Meter.COLOR_ISM, row = 1 },
{ key = "bm", label = "BM", color = Meters.COLOR_BM, row = 2 }, { key = "bm", label = "BM", color = Meter.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, Meters.COLOR_BG) rect(bar_x, bar_y, bar_w, bar_h, Meter.COLOR_BG)
if fill_w > 0 then if fill_w > 0 then
rect(bar_x, bar_y, fill_w, bar_h, meter.color) rect(bar_x, bar_y, fill_w, bar_h, meter.color)
end end
@@ -163,6 +177,7 @@ function UI.draw_meters()
end end
--- Updates decision selector. --- Updates decision selector.
--- @within UI
-- @param decisions table A table of decision items. -- @param decisions table A table of decision items.
-- @param selected_decision_index number The current index of the selected decision. -- @param selected_decision_index number The current index of the selected decision.
-- @return number The updated index of the selected decision. -- @return number The updated index of the selected decision.

View File

@@ -1,7 +1,8 @@
--- Utility functions.
Util = {} --- @section Util
--- Safely wraps an index for an array. --- Safely wraps an index for an array.
--- @within Util
-- @param array table The array to index. -- @param array table The array to index.
-- @param index number The desired index (can be out of bounds). -- @param index number The desired index (can be out of bounds).
-- @return number The wrapped index within the array's bounds. -- @return number The wrapped index within the array's bounds.
@@ -10,12 +11,27 @@ function Util.safeindex(array, index)
end end
--- Navigates to a screen by its ID. --- Navigates to a screen by its ID.
--- @within Util
-- @param screen_id string The ID of the screen to go to. -- @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_index = Context.screen_indices_by_id[screen_id] local screen = Screen.get_by_id(screen_id)
if screen_index then if screen then
Context.current_screen = screen_index Context.game.current_screen = screen_id
Context.selected_decision_index = 1 else screen.init()
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found or not indexed!"}) else
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found!"})
end end
end end
--- Checks if a table contains a specific value.
--- @within Util
-- @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

@@ -1,3 +1,4 @@
--- @section AudioTestWindow
AudioTestWindow = { AudioTestWindow = {
index_menu = 1, index_menu = 1,
index_func = 1, index_func = 1,
@@ -7,6 +8,7 @@ AudioTestWindow = {
} }
--- Generates menu items for audio test. --- Generates menu items for audio test.
--- @within AudioTestWindow
-- @param list_func table List of audio functions. -- @param list_func table List of audio functions.
-- @param index_func number Current index of selected function. -- @param index_func number Current index of selected function.
-- @return table Generated menu items. -- @return table Generated menu items.
@@ -19,7 +21,7 @@ function AudioTestWindow.generate_menuitems(list_func, index_func)
if current_func then if current_func then
current_func() current_func()
else else
trace("Invalid Audio function: " .. list_func[index_menu]) trace("Invalid Audio function: " .. list_func[index_func])
end end
end end
}, },
@@ -39,6 +41,7 @@ function AudioTestWindow.generate_menuitems(list_func, index_func)
end end
--- Generates list of audio functions. --- Generates list of audio functions.
--- @within AudioTestWindow
-- @return table A sorted list of audio function names. -- @return table A sorted list of audio function names.
function AudioTestWindow.generate_listfunc() function AudioTestWindow.generate_listfunc()
local result = {} local result = {}
@@ -55,12 +58,14 @@ function AudioTestWindow.generate_listfunc()
end end
--- Navigates back from audio test window. --- Navigates back from audio test window.
--- @within AudioTestWindow
function AudioTestWindow.back() function AudioTestWindow.back()
Audio.sfx_deselect() Audio.sfx_deselect()
GameWindow.set_state(WINDOW_MENU) GameWindow.set_state("menu")
end end
--- Initializes audio test window. --- Initializes audio test window.
--- @within AudioTestWindow
function AudioTestWindow.init() function AudioTestWindow.init()
AudioTestWindow.last_pressed = false AudioTestWindow.last_pressed = false
AudioTestWindow.index_menu = 1 AudioTestWindow.index_menu = 1
@@ -72,12 +77,14 @@ function AudioTestWindow.init()
end end
--- Draws audio test window. --- Draws audio test window.
--- @within AudioTestWindow
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. --- Updates audio test window logic.
--- @within AudioTestWindow
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

@@ -1,27 +1,31 @@
--- @section ConfigurationWindow
ConfigurationWindow = { ConfigurationWindow = {
controls = {}, controls = {},
selected_control = 1, selected_control = 1,
} }
--- Initializes configuration window. --- Initializes configuration window.
--- @within ConfigurationWindow
function ConfigurationWindow.init() function ConfigurationWindow.init()
ConfigurationWindow.controls = { ConfigurationWindow.controls = {
UI.create_decision_item( UI.create_action_item(
"Save", "Save",
function() Config.save() end function() Config.save() end
), ),
UI.create_decision_item( UI.create_action_item(
"Restore Defaults", "Restore Defaults",
function() Config.restore_defaults() end function() Config.reset() end
), ),
} }
end end
--- Draws configuration window. --- Draws configuration window.
--- @within ConfigurationWindow
function ConfigurationWindow.draw() function ConfigurationWindow.draw()
UI.draw_top_bar("Configuration") UI.draw_top_bar("Configuration")
local x_start = 10 local y_start = 40 local x_start = 10
local y_start = 40
local x_value_right_align = Config.screen.width - 10 local x_value_right_align = Config.screen.width - 10
local char_width = 4 local char_width = 4
for i, control in ipairs(ConfigurationWindow.controls) do for i, control in ipairs(ConfigurationWindow.controls) do
@@ -31,18 +35,19 @@ function ConfigurationWindow.draw()
local value = control.get() local value = control.get()
local label_text = control.label local label_text = control.label
local value_text = string.format(control.format, value) local value_text = string.format(control.format, value)
local value_x = x_value_right_align - (#value_text * char_width) local value_x = x_value_right_align - (#value_text * char_width)
if i == ConfigurationWindow.selected_control then if i == ConfigurationWindow.selected_control then
color = Config.colors.item color = Config.colors.item
Print.text("<", x_start - 8, current_y, color) Print.text("<", x_start - 8, current_y, color)
Print.text(label_text, x_start, current_y, color) Print.text(value_text, value_x, current_y, color) Print.text(label_text, x_start, current_y, color)
Print.text(">", x_value_right_align + 4, current_y, color) else Print.text(value_text, value_x, current_y, color)
Print.text(">", x_value_right_align + 4, current_y, color)
else
Print.text(label_text, x_start, current_y, color) Print.text(label_text, x_start, current_y, color)
Print.text(value_text, value_x, current_y, color) Print.text(value_text, value_x, current_y, color)
end end
elseif control.type == "decision_item" then elseif control.type == "action_item" then
local label_text = control.label local label_text = control.label
if i == ConfigurationWindow.selected_control then if i == ConfigurationWindow.selected_control then
color = Config.colors.item color = Config.colors.item
@@ -58,9 +63,10 @@ function ConfigurationWindow.draw()
end end
--- Updates configuration window logic. --- Updates configuration window logic.
--- @within ConfigurationWindow
function ConfigurationWindow.update() function ConfigurationWindow.update()
if Input.menu_back() then if Input.menu_back() then
GameWindow.set_state(WINDOW_MENU) GameWindow.set_state("menu")
return return
end end
@@ -80,14 +86,16 @@ function ConfigurationWindow.update()
if control then if control then
if control.type == "numeric_stepper" then if control.type == "numeric_stepper" then
local current_value = control.get() local current_value = control.get()
if btnp(2) then local new_value = math.max(control.min, current_value - control.step) if Input.left() then
local new_value = math.max(control.min, current_value - control.step)
control.set(new_value) control.set(new_value)
elseif btnp(3) then local new_value = math.min(control.max, current_value + control.step) elseif Input.right() then
local new_value = math.min(control.max, current_value + control.step)
control.set(new_value) control.set(new_value)
end end
elseif control.type == "decision_item" then elseif control.type == "action_item" then
if Input.menu_confirm() then if Input.menu_confirm() then
control.decision() control.action()
end end
end end
end end

View File

@@ -1,88 +1,70 @@
--- @section GameWindow
local _available_decisions = {}
local _selected_decision_index = 1
--- Draws the game window. --- Draws the game window.
--- @within GameWindow
function GameWindow.draw() function GameWindow.draw()
local screen = Context.screens[Context.current_screen] local screen = Screen.get_by_id(Context.game.current_screen)
Map.draw(screen.background) if screen.background then Map.draw(screen.background) end
UI.draw_top_bar(screen.name) UI.draw_top_bar(screen.name)
if screen and screen.decisions and #screen.decisions > 0 then if #_available_decisions > 0 then
local available_decisions = {} UI.draw_decision_selector(_available_decisions, _selected_decision_index)
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 game window logic.
--- @within GameWindow
function GameWindow.update() function GameWindow.update()
local previous_screen_index = Context.current_screen
if Input.menu_back() then if Input.menu_back() then
Context.active_window = WINDOW_MENU Window.set_current("menu")
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
return return
end end
if Input.up() then local screen = Screen.get_by_id(Context.game.current_screen)
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()
if previous_screen_index ~= Context.current_screen then -- Handle current situation updates
screen.init() if Context.game.current_situation then
end local current_situation_obj = Situation.get_by_id(Context.game.current_situation)
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
if screen and screen.decisions and #screen.decisions > 0 then -- Fetch and filter decisions locally
local available_decisions = {} local all_decisions_for_screen = Decision.get_for_screen(screen)
for _, decision_id in ipairs(screen.decisions) do _available_decisions = Decision.filter_available(all_decisions_for_screen)
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
if _selected_decision_index > #_available_decisions then
_selected_decision_index = 1
end
local new_selected_decision_index = UI.update_decision_selector( local new_selected_decision_index = UI.update_decision_selector(
available_decisions, _available_decisions,
Context.selected_decision_index _selected_decision_index
) )
if new_selected_decision_index ~= Context.selected_decision_index then if new_selected_decision_index ~= _selected_decision_index then
Context.selected_decision_index = new_selected_decision_index _selected_decision_index = new_selected_decision_index
end end
if Input.select() then if Input.select() then
local selected_decision = available_decisions[Context.selected_decision_index] local selected_decision = _available_decisions[_selected_decision_index]
if selected_decision and selected_decision.handle then Audio.sfx_select() selected_decision.handle() if selected_decision and selected_decision.handle then
end Audio.sfx_select()
selected_decision.handle()
end end
end end
end end
--- Sets the active window. --- Sets the active window.
-- @param new_state number The ID of the new active window. --- @within GameWindow
-- @param new_state string The ID of the new active window.
function GameWindow.set_state(new_state) function GameWindow.set_state(new_state)
Context.active_window = new_state Window.set_current(new_state)
end end

View File

@@ -1,23 +1,41 @@
--- @section IntroWindow
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. --- Draws the intro window.
--- @within IntroWindow
function IntroWindow.draw() function IntroWindow.draw()
local x = (Config.screen.width - 132) / 2 local x = (Config.screen.width - 132) / 2
Print.text(Context.intro.text, x, Context.intro.y, Config.colors.green) Print.text(IntroWindow.text, x, IntroWindow.y, Config.colors.green)
end end
--- Updates the intro window logic. --- Updates the intro window logic.
--- @within IntroWindow
function IntroWindow.update() function IntroWindow.update()
Context.intro.y = Context.intro.y - Context.intro.speed IntroWindow.y = IntroWindow.y - IntroWindow.speed
local lines = 1 local lines = 1
for _ in string.gmatch(Context.intro.text, "\n") do for _ in string.gmatch(IntroWindow.text, "\n") do
lines = lines + 1 lines = lines + 1
end end
if Context.intro.y < -lines * 8 then if IntroWindow.y < -lines * 8 then
GameWindow.set_state(WINDOW_MENU) Window.set_current("menu")
end end
if Input.menu_confirm() then if Input.menu_confirm() then
GameWindow.set_state(WINDOW_MENU) Window.set_current("menu")
end end
end end

View File

@@ -0,0 +1,49 @@
--- @section Window
local _windows = {}
--- Registers a window table.
--- @within Window
-- @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.
--- @within Window
-- @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.
--- @within 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.
--- @within 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.
--- @within 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

View File

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

View File

@@ -1,16 +1,20 @@
--- @section MinigameDDRWindow
--- Initializes DDR minigame state. --- Initializes DDR minigame state.
--- @within MinigameDDRWindow
-- @param params table Optional parameters for configuration. -- @param params table Optional parameters for configuration.
function MinigameDDRWindow.init(params) function MinigameDDRWindow.init(params)
Context.minigame_ddr = Minigames.configure_ddr(params) Context.minigame_ddr = Minigame.configure_ddr(params)
end end
--- Starts the DDR minigame. --- Starts the DDR minigame.
-- @param return_window number The window ID to return to after the minigame. --- @within MinigameDDRWindow
-- @param return_window string The window ID to return to after the minigame.
-- @param[opt] song_key string The key of the song to play. -- @param[opt] song_key string The key of the song to play.
-- @param[opt] params table Optional parameters for minigame configuration. -- @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 WINDOW_GAME Context.minigame_ddr.return_window = return_window or "game"
Context.minigame_ddr.debug_song_key = song_key Context.minigame_ddr.debug_song_key = song_key
if song_key and Songs and Songs[song_key] then if song_key and Songs and Songs[song_key] then
Context.minigame_ddr.current_song = Songs[song_key] Context.minigame_ddr.current_song = Songs[song_key]
@@ -25,10 +29,11 @@ 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.active_window = WINDOW_MINIGAME_DDR Window.set_current("minigame_ddr")
end end
--- Spawns a random arrow. --- Spawns a random arrow.
--- @within MinigameDDRWindow
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)]
@@ -40,6 +45,7 @@ local function spawn_arrow()
end end
--- Spawns an arrow in a specific direction. --- Spawns an arrow in a specific direction.
--- @within MinigameDDRWindow
-- @param direction string The direction of the arrow ("left", "down", "up", "right"). -- @param direction string The direction of the arrow ("left", "down", "up", "right").
local function spawn_arrow_dir(direction) local function spawn_arrow_dir(direction)
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
@@ -56,6 +62,7 @@ local function spawn_arrow_dir(direction)
end end
--- Checks if an arrow is hit. --- Checks if an arrow is hit.
--- @within MinigameDDRWindow
-- @param arrow table The arrow data. -- @param arrow table The arrow data.
-- @return boolean True if the arrow is hit, false otherwise. -- @return boolean True if the arrow is hit, false otherwise.
local function check_hit(arrow) local function check_hit(arrow)
@@ -65,6 +72,7 @@ local function check_hit(arrow)
end end
--- Checks if an arrow is missed. --- Checks if an arrow is missed.
--- @within MinigameDDRWindow
-- @param arrow table The arrow data. -- @param arrow table The arrow data.
-- @return boolean True if the arrow is missed, false otherwise. -- @return boolean True if the arrow is missed, false otherwise.
local function check_miss(arrow) local function check_miss(arrow)
@@ -73,6 +81,7 @@ local function check_miss(arrow)
end end
--- Draws an arrow. --- Draws an arrow.
--- @within MinigameDDRWindow
-- @param x number The x-coordinate. -- @param x number The x-coordinate.
-- @param y number The y-coordinate. -- @param y number The y-coordinate.
-- @param direction string The direction of the arrow. -- @param direction string The direction of the arrow.
@@ -96,20 +105,21 @@ local function draw_arrow(x, y, direction, color)
end end
--- Updates DDR minigame logic. --- Updates DDR minigame logic.
--- @within MinigameDDRWindow
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
Meters.on_minigame_complete() Meter.on_minigame_complete()
Meters.show() Meter.show()
Context.active_window = mg.return_window Window.set_current(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
Meters.on_minigame_complete() Meter.on_minigame_complete()
Meters.show() Meter.show()
Context.active_window = mg.return_window Window.set_current(mg.return_window)
return return
end end
end end
@@ -189,6 +199,7 @@ function MinigameDDRWindow.update()
end end
--- Draws DDR minigame. --- Draws DDR minigame.
--- @within MinigameDDRWindow
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 +207,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.active_window = WINDOW_GAME Window.set_current("game")
end end
return return
end end
if mg.return_window == WINDOW_GAME then if mg.return_window == "game" then
GameWindow.draw() GameWindow.draw()
end end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)

View File

@@ -1,19 +1,24 @@
--- @section MinigameButtonMashWindow
--- Initializes button mash minigame state. --- Initializes button mash minigame state.
--- @within MinigameButtonMashWindow
-- @param params table Optional parameters for configuration. -- @param params table Optional parameters for configuration.
function MinigameButtonMashWindow.init(params) function MinigameButtonMashWindow.init(params)
Context.minigame_button_mash = Minigames.configure_button_mash(params) Context.minigame_button_mash = Minigame.configure_button_mash(params)
end end
--- Starts the button mash minigame. --- Starts the button mash minigame.
-- @param return_window number The window ID to return to after the minigame. --- @within MinigameButtonMashWindow
-- @param return_window string The window ID to return to after the minigame.
-- @param[opt] params table Optional parameters for minigame configuration. -- @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 WINDOW_GAME Context.minigame_button_mash.return_window = return_window or "game"
Context.active_window = WINDOW_MINIGAME_BUTTON_MASH Window.set_current("minigame_button_mash")
end end
--- Updates button mash minigame logic. --- Updates button mash minigame logic.
--- @within MinigameButtonMashWindow
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 +29,9 @@ function MinigameButtonMashWindow.update()
end end
end end
if mg.bar_fill >= mg.max_fill then if mg.bar_fill >= mg.max_fill then
Meters.on_minigame_complete() Meter.on_minigame_complete()
Meters.show() Meter.show()
Context.active_window = mg.return_window Window.set_current(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)
@@ -40,9 +45,10 @@ function MinigameButtonMashWindow.update()
end end
--- Draws button mash minigame. --- Draws button mash minigame.
--- @within MinigameButtonMashWindow
function MinigameButtonMashWindow.draw() function MinigameButtonMashWindow.draw()
local mg = Context.minigame_button_mash local mg = Context.minigame_button_mash
if mg.return_window == WINDOW_GAME then if mg.return_window == "game" then
GameWindow.draw() GameWindow.draw()
end end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)

View File

@@ -1,19 +1,24 @@
--- @section MinigameRhythmWindow
--- Initializes rhythm minigame state. --- Initializes rhythm minigame state.
--- @within MinigameRhythmWindow
-- @param params table Optional parameters for configuration. -- @param params table Optional parameters for configuration.
function MinigameRhythmWindow.init(params) function MinigameRhythmWindow.init(params)
Context.minigame_rhythm = Minigames.configure_rhythm(params) Context.minigame_rhythm = Minigame.configure_rhythm(params)
end end
--- Starts the rhythm minigame. --- Starts the rhythm minigame.
-- @param return_window number The window ID to return to after the minigame. --- @within MinigameRhythmWindow
-- @param return_window string The window ID to return to after the minigame.
-- @param[opt] params table Optional parameters for minigame configuration. -- @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 WINDOW_GAME Context.minigame_rhythm.return_window = return_window or "game"
Context.active_window = WINDOW_MINIGAME_RHYTHM Window.set_current("minigame_rhythm")
end end
--- Updates rhythm minigame logic. --- Updates rhythm minigame logic.
--- @within MinigameRhythmWindow
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 +51,9 @@ function MinigameRhythmWindow.update()
end end
end end
if mg.score >= mg.max_score then if mg.score >= mg.max_score then
Meters.on_minigame_complete() Meter.on_minigame_complete()
Meters.show() Meter.show()
Context.active_window = mg.return_window Window.set_current(mg.return_window)
return return
end end
if mg.button_pressed_timer > 0 then if mg.button_pressed_timer > 0 then
@@ -57,9 +62,10 @@ function MinigameRhythmWindow.update()
end end
--- Draws rhythm minigame. --- Draws rhythm minigame.
--- @within MinigameRhythmWindow
function MinigameRhythmWindow.draw() function MinigameRhythmWindow.draw()
local mg = Context.minigame_rhythm local mg = Context.minigame_rhythm
if mg.return_window == WINDOW_GAME then if mg.return_window == "game" then
GameWindow.draw() GameWindow.draw()
end end
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black) rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)

View File

@@ -1,3 +1,4 @@
--- @section PopupWindow
local POPUP_X = 40 local POPUP_X = 40
local POPUP_Y = 40 local POPUP_Y = 40
local POPUP_WIDTH = 160 local POPUP_WIDTH = 160
@@ -5,26 +6,36 @@ 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. --- Displays a popup window.
--- @within PopupWindow
-- @param content_strings table A table of strings to display in the popup. -- @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(WINDOW_POPUP) end Context.popup.content = content_strings or {}
GameWindow.set_state("popup")
end
--- Hides the popup window. --- Hides the popup window.
--- @within PopupWindow
function PopupWindow.hide() function PopupWindow.hide()
Context.popup.show = false Context.popup.show = false
Context.popup.content = {} GameWindow.set_state(WINDOW_GAME) end Context.popup.content = {}
GameWindow.set_state("game")
end
--- Updates popup window logic. --- Updates popup window logic.
--- @within PopupWindow
function PopupWindow.update() function PopupWindow.update()
if Context.popup.show then if Context.popup.show then
if Input.menu_confirm() or Input.menu_back() then PopupWindow.hide() if Input.menu_confirm() or Input.menu_back() then
PopupWindow.hide()
end end
end end
end end
--- Draws the popup window. --- Draws the popup window.
--- @within PopupWindow
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

@@ -0,0 +1,29 @@
SplashWindow = {}
Window.register("splash", SplashWindow)
IntroWindow = {}
Window.register("intro", IntroWindow)
MenuWindow = {}
Window.register("menu", MenuWindow)
GameWindow = {}
Window.register("game", GameWindow)
PopupWindow = {}
Window.register("popup", PopupWindow)
ConfigurationWindow = {}
Window.register("configuration", ConfigurationWindow)
AudioTestWindow = {}
Window.register("audiotest", AudioTestWindow)
MinigameButtonMashWindow = {}
Window.register("minigame_button_mash", MinigameButtonMashWindow)
MinigameRhythmWindow = {}
Window.register("minigame_rhythm", MinigameRhythmWindow)
MinigameDDRWindow = {}
Window.register("minigame_ddr", MinigameDDRWindow)

View File

@@ -1,16 +1,18 @@
--- @section SplashWindow
--- Draws the splash window. --- Draws the splash window.
--- @within SplashWindow
function SplashWindow.draw() function SplashWindow.draw()
local txt = "Definitely not an Impostor" local txt = "Definitely not an Impostor"
local w = #txt * 6 local y = (Config.screen.height - 6) / 2
local x = (240 - w) / 2 Print.text_center(txt, Config.screen.width / 2, y, Config.colors.white)
local y = (136 - 6) / 2
print(txt, x, y, 12)
end end
--- Updates the splash window logic. --- Updates the splash window logic.
--- @within SplashWindow
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(WINDOW_INTRO) Window.set_current("intro")
end end
end end