refact
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-02-22 18:15:24 +01:00
parent d9febf16e0
commit 83e2000198
10 changed files with 166 additions and 104 deletions

View File

@@ -2,6 +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,
}) })

View File

@@ -33,6 +33,37 @@ end
--- Gets all registered decisions. --- Gets all registered decisions.
-- @return table A table of all registered decisions. -- @return table A table of all registered decisions.
function Decision.get_all() function Decision.get_all_registered()
return _decisions return _decisions
end end
--- Gets decision objects based on a screen's data.
-- @param screen_data table The data for the screen, containing a 'decisions' field (list of decision IDs).
-- @return table A table containing decision objects relevant to the screen.
function Decision.get_for_screen(screen_data)
if not screen_data or not screen_data.decisions then
return {}
end
local screen_decisions = {}
for _, decision_id in ipairs(screen_data.decisions) do
local decision = Decision.get(decision_id)
if decision then
table.insert(screen_decisions, decision)
end
end
return screen_decisions
end
--- Filters a list of decision objects based on their condition function.
-- @param decisions_list table A table of decision objects.
-- @return table A new table containing only the decisions for which condition() is true.
function Decision.filter_available(decisions_list)
local available = {}
for _, decision in ipairs(decisions_list) do
if decision and decision.condition() then
table.insert(available, decision)
end
end
return available
end

View File

@@ -4,6 +4,9 @@ local SAVE_GAME_MAGIC_VALUE = 0xCA
local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6 local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6
-- Define a consistent order for screens for save/load operations
local SCREEN_ID_ORDER = {"home", "toilet", "walking_to_office", "office", "walking_to_home"}
--- 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() local function get_initial_data()
@@ -16,15 +19,13 @@ local function get_initial_data()
seems ordinary: work, seems ordinary: work,
meetings, coffee, and meetings, coffee, and
endless notifications. endless notifications.
But beneath the surface But beneath him, or around
— within him, or around
him — something is him — something is
constantly building, and constantly building, and
it soon becomes clear it soon becomes clear
that there is more going that there is more going
on than meets the eye.]] 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,
@@ -41,18 +42,16 @@ on than meets the eye.]]
}, },
menu_items = {}, menu_items = {},
selected_menu_item = 1, 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 = Minigame.get_default_ddr(),
minigame_button_mash = Minigame.get_default_button_mash(), minigame_button_mash = Minigame.get_default_button_mash(),
minigame_rhythm = Minigame.get_default_rhythm(), minigame_rhythm = Minigame.get_default_rhythm(),
meters = Meter.get_initial(), meters = Meter.get_initial(),
--- Active sprites. game = {
sprites = {}, current_screen = "home",
--- Current situation ID.
current_situation = nil, current_situation = nil,
} }
}
end end
--- Global game context. --- Global game context.
@@ -70,29 +69,15 @@ local function reset_context_to_initial_state()
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() reset_context_to_initial_state()
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.
@@ -100,7 +85,20 @@ 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)
local screen_index_to_save
for i, screen_id in ipairs(SCREEN_ID_ORDER) do
if screen_id == Context.game.current_screen then
screen_index_to_save = i
break
end
end
if screen_index_to_save then
mset(screen_index_to_save, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
else
trace("Error: Current screen ID '" .. Context.game.current_screen .. "' not found in SCREEN_ID_ORDER for saving.")
end
end end
--- Loads a saved game state. --- Loads a saved game state.
@@ -111,9 +109,17 @@ function Context.load_game()
end end
reset_context_to_initial_state() reset_context_to_initial_state()
Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
local loaded_screen_index = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK)
if loaded_screen_index and SCREEN_ID_ORDER[loaded_screen_index] then
Context.game.current_screen = SCREEN_ID_ORDER[loaded_screen_index]
else
trace("Error: Invalid screen index loaded: " .. tostring(loaded_screen_index) .. ". Defaulting to new game state.")
Context.new_game()
return
end
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

@@ -13,7 +13,7 @@ Meter = {}
Minigame = {} Minigame = {}
Decision = {} Decision = {}
Situation = {} Situation = {}
Screen = {} _G.Screen = _G.Screen or {}
Map = {} Map = {}
UI = {} UI = {}
Print = {} Print = {}

View File

@@ -1,3 +1,7 @@
-- Ensure Screen table exists globally, and get a local reference to it
local Screen = _G.Screen or {}
_G.Screen = Screen
local _screens = {} local _screens = {}
--- Registers a screen definition. --- Registers a screen definition.
@@ -24,3 +28,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,46 @@ 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) trace("Screen data for current screen (id: " .. current_screen_id .. "): " .. tostring(screen.situations[1])
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 .. ").") if Util.contains(screen.situations, id) then
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

@@ -48,6 +48,8 @@ local initialized_game = false
local function init_game() local function init_game()
if initialized_game then return end if initialized_game then return end
reset_context_to_initial_state() -- Call it here
MenuWindow.refresh_menu_items() MenuWindow.refresh_menu_items()
initialized_game = true initialized_game = true
end end

View File

@@ -12,10 +12,26 @@ 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 local all_decisions_for_screen = Decision.get_for_screen(screen)
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found or not indexed!"}) Context.game.decisions = Decision.filter_available(all_decisions_for_screen)
Context.game.selected_decision_index = 1
screen.init() -- Initialize the new screen
else
PopupWindow.show({"Error: Screen '" .. screen_id .. "' not found!"})
end end
end end
-- Checks if a table contains a specific value.
-- @param t table The table to check.
-- @param value any The value to look for.
function Util.contains(t, value)
for i = 1, #t do
if t[i] == value then
return true
end
end
return false
end

View File

@@ -1,82 +1,56 @@
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) Map.draw(screen.background)
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 Context.active_window = WINDOW_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 situations (Context.game.current_situation is still present)
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
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