Compare commits

..

9 Commits

Author SHA1 Message Date
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
42 changed files with 444 additions and 421 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

@@ -11,6 +11,7 @@ function Audio.music_play_room_street_1() end
--- Plays room street 2 music. --- Plays room street 2 music.
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
function Audio.music_play_room_() end function Audio.music_play_room_() end
--- Plays room work music. --- Plays room work music.
function Audio.music_play_room_work() end function Audio.music_play_room_work() end

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

@@ -27,7 +27,7 @@ end
--- Gets a decision by ID. --- Gets a decision by ID.
-- @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
@@ -36,3 +36,34 @@ end
function Decision.get_all() function Decision.get_all()
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_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.
-- @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,35 @@
local DEFAULT_CONFIG = { 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 = { 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
@@ -38,6 +39,7 @@ local CONFIG_MAGIC_VALUE = 0xDE
--- Saves the current configuration. --- 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. --- Loads saved configuration.
@@ -45,13 +47,8 @@ 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,105 +2,57 @@ 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.
Context = {}
--- Gets initial data for Context. --- Gets initial data for 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() function Context.reset()
local initial_data = get_initial_data() 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.
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.
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.
@@ -109,11 +61,8 @@ function Context.load_game()
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

@@ -7,14 +7,14 @@ 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.
-- @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 +26,24 @@ function Meters.get_initial()
end end
--- Hides meters. --- 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. --- 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. --- Gets max meter value.
-- @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.
-- @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 +51,7 @@ function Meters.get_combo_multiplier()
end end
--- Updates all meters. --- 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)
@@ -69,7 +69,7 @@ end
--- Adds amount to a meter. --- Adds amount to a 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 +78,12 @@ function Meters.add(key, amount)
end end
--- Called on minigame completion. --- 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 * 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,5 +1,4 @@
-- Manages minigame configurations and initial states. -- Manages minigame configurations and initial states.
Minigames = {}
--- Applies parameters to defaults. --- Applies parameters to defaults.
-- @param defaults table The default configuration table. -- @param defaults table The default configuration table.
@@ -15,7 +14,7 @@ end
--- Gets default DDR minigame configuration. --- Gets default DDR minigame configuration.
-- @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)
@@ -56,7 +55,7 @@ end
--- Gets default button mash minigame configuration. --- Gets default button mash minigame configuration.
-- @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,
@@ -78,7 +77,7 @@ end
--- Gets default rhythm minigame configuration. --- Gets default rhythm minigame configuration.
-- @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,
@@ -108,20 +107,20 @@ end
--- Configures DDR minigame. --- Configures DDR 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.
-- @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.
-- @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

@@ -24,3 +24,9 @@ end
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

@@ -22,25 +22,44 @@ end
--- Gets a situation by ID. --- Gets a situation by ID.
-- @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.
-- @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.
-- @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,4 +1,5 @@
local _sprites = {} local _sprites = {}
local _active_sprites = {}
--- Registers a sprite definition. --- Registers a sprite definition.
-- @param sprite_data table A table containing the sprite definition. -- @param sprite_data table A table containing the sprite definition.
@@ -28,7 +29,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,
@@ -43,16 +44,16 @@ end
--- Hides a displayed sprite. --- Hides a displayed 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.
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

@@ -2,7 +2,9 @@ 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

View File

@@ -1,53 +1,10 @@
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. --- 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
@@ -56,11 +13,11 @@ end
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

@@ -123,10 +123,13 @@ 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.
@@ -135,7 +138,7 @@ function UI.draw_meters()
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 +148,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

View File

@@ -1,5 +1,3 @@
--- Utility functions.
Util = {}
--- Safely wraps an index for an array. --- Safely wraps an index for an array.
-- @param array table The array to index. -- @param array table The array to index.
@@ -12,10 +10,23 @@ end
--- Navigates to a screen by its ID. --- Navigates to a screen by its ID.
-- @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.
-- @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

@@ -19,7 +19,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
}, },
@@ -57,7 +57,7 @@ end
--- Navigates back from audio test window. --- Navigates back from audio test window.
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.

View File

@@ -6,13 +6,13 @@ ConfigurationWindow = {
--- Initializes configuration window. --- Initializes configuration window.
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
@@ -21,7 +21,8 @@ end
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 +32,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
@@ -60,7 +62,7 @@ end
--- Updates configuration window logic. --- Updates configuration window logic.
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 +82,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,66 @@
local _available_decisions = {}
local _selected_decision_index = 1
--- Draws the game window. --- Draws the game window.
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.
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. -- @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,38 @@
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.
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.
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,43 @@
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

View File

@@ -1,15 +1,17 @@
local _menu_items = {}
--- Draws the menu window. --- 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(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.
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()
@@ -19,21 +21,24 @@ end
--- Starts a new game from the menu. --- Starts a new game from the menu.
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.
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.
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.
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.
@@ -44,27 +49,28 @@ end
--- Opens the configuration menu. --- Opens the configuration menu.
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.
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.
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,16 @@
--- Initializes DDR minigame state. --- Initializes DDR minigame state.
-- @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. -- @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,7 +25,7 @@ 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.
@@ -99,17 +99,17 @@ end
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
@@ -196,11 +196,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,16 +1,16 @@
--- Initializes button mash minigame state. --- Initializes button mash minigame state.
-- @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. -- @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.
@@ -24,9 +24,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)
@@ -42,7 +42,7 @@ end
--- Draws button mash minigame. --- 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 == 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,16 +1,16 @@
--- Initializes rhythm minigame state. --- Initializes rhythm minigame state.
-- @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. -- @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.
@@ -46,9 +46,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
@@ -59,7 +59,7 @@ end
--- Draws rhythm minigame. --- Draws rhythm minigame.
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

@@ -5,21 +5,27 @@ 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.
-- @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.
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.
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

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,14 @@
--- Draws the splash window. --- 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 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.
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