4
0

Compare commits

...

7 Commits

Author SHA1 Message Date
a5ed57777a v0.7
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-09 18:46:24 +01:00
d52d7a0cf1 state to window 2025-12-09 18:45:51 +01:00
90192cb6a7 rename entities 2025-12-09 18:32:16 +01:00
243447a1d1 bg color fix 2025-12-09 18:16:31 +01:00
cebd487c12 talk to 2025-12-09 17:51:26 +01:00
0614390b59 Docs: Add agent directive regarding git operations
Added a new section to GEMINI.md to explicitly state that the agent should not perform git add or git commit operations in the future, as this responsibility will be handled by the user.
2025-12-09 17:17:38 +01:00
3086603892 Refactor: Group dialog-related state into a common table
Consolidate dialog-related properties into a single 'dialog' table within the main 'State' object. This improves data organization and clarifies ownership of the dialog state.

The following properties were moved into :
-  ->
-  ->
-  ->
-  ->
-  ->  (previously implicit global)

All references to these properties have been updated throughout the codebase to reflect the new structure.
2025-12-09 17:15:08 +01:00
2 changed files with 325 additions and 188 deletions

View File

@@ -45,4 +45,10 @@ Based on the analysis of `mranderson.lua`, the following regularities and conven
14. **Indentation:** Use consistent indentation, likely 2 spaces, for code blocks to enhance readability. 14. **Indentation:** Use consistent indentation, likely 2 spaces, for code blocks to enhance readability.
15. **Comments:** Employ comments to explain complex logic, delineate code sections, or clarify non-obvious design choices. 15. **Comments:** Employ comments to explain complex logic, delineate code sections, or clarify non-obvious design choices.
16. **Code Sections:** Use comments (e.g., `--- INIT ---`, `--- UPDATE ---`, `--- DRAW ---`, `--- HELPERS ---`) to clearly delineate logical sections of the codebase. 16. **Code Sections:** Use comments (e.g., `--- INIT ---`, `--- UPDATE ---`, `--- DRAW ---`, `--- HELPERS ---`) to clearly delineate logical sections of the codebase.
---
## Agent Directives
- **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user.

View File

@@ -3,7 +3,7 @@
-- desc: Life of a programmer in the Vector -- desc: Life of a programmer in the Vector
-- site: https://github.com/rastasi/mranderson -- site: https://github.com/rastasi/mranderson
-- license: MIT License -- license: MIT License
-- version: 0.7 -- version: 0.8
-- script: lua -- script: lua
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -43,26 +43,26 @@ local Config = {
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Game States -- Game Windows
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local GAME_STATE_SPLASH = 0 local WINDOW_SPLASH = 0
local GAME_STATE_INTRO = 1 local WINDOW_INTRO = 1
local GAME_STATE_MENU = 2 local WINDOW_MENU = 2
local GAME_STATE_GAME = 3 local WINDOW_GAME = 3
local GAME_STATE_DIALOG = 4 local WINDOW_POPUP = 4
local GAME_STATE_INVENTORY = 5 local WINDOW_INVENTORY = 5
local GAME_STATE_INVENTORY_ACTION = 6 local WINDOW_INVENTORY_ACTION = 6
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Modules -- Modules
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- State Modules (in GAME_STATE order) -- Window Modules (in WINDOW order)
local SplashState = {} local SplashWindow = {}
local IntroState = {} local IntroWindow = {}
local MenuState = {} local MenuWindow = {}
local GameState = {} local GameWindow = {}
local DialogState = {} -- Used for GAME_STATE_DIALOG and GAME_STATE_INVENTORY_ACTION local PopupWindow = {} -- Manages popups for WINDOW_POPUP and WINDOW_INVENTORY_ACTION
local InventoryState = {} -- Used for GAME_STATE_INVENTORY local InventoryWindow = {} -- Used for WINDOW_INVENTORY
-- Other Modules -- Other Modules
local UI = {} local UI = {}
@@ -73,10 +73,10 @@ local MenuActions = {}
local Player = {} local Player = {}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Game State -- Game Window
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local State = { local Context = {
game_state = GAME_STATE_SPLASH, active_window = WINDOW_SPLASH,
inventory = {}, inventory = {},
intro = { intro = {
y = Config.screen.height, y = Config.screen.height,
@@ -84,8 +84,15 @@ local State = {
text = "Mr. Anderson is an average\nprogrammer. His daily life\nrevolves around debugging,\npull requests, and end-of-sprint\nmeetings, all while secretly\ndreaming of being destined\nfor something more." text = "Mr. Anderson is an average\nprogrammer. His daily life\nrevolves around debugging,\npull requests, and end-of-sprint\nmeetings, all while secretly\ndreaming of being destined\nfor something more."
}, },
current_screen = 1, current_screen = 1,
dialog_text = "",
splash_timer = Config.timing.splash_duration, splash_timer = Config.timing.splash_duration,
dialog = {
text = "",
menu_items = {},
selected_menu_item = 1,
active_entity = nil,
showing_description = false,
current_node_key = nil
},
player = { player = {
x = Config.player.start_x, x = Config.player.start_x,
y = Config.player.start_y, y = Config.player.start_y,
@@ -105,9 +112,6 @@ local State = {
menu_items = {}, menu_items = {},
selected_menu_item = 1, selected_menu_item = 1,
selected_inventory_item = 1, selected_inventory_item = 1,
dialog_menu_items = {},
selected_dialog_menu_item = 1,
active_entity = nil,
-- Screen data -- Screen data
screens = { screens = {
{ -- Screen 1 { -- Screen 1
@@ -131,13 +135,65 @@ local State = {
x = 180, x = 180,
y = 82, y = 82,
name = "Trinity", name = "Trinity",
sprite_id = 2 sprite_id = 2,
dialog = {
start = {
text = "Hello, Neo.",
options = {
{label = "Who are you?", next_node = "who_are_you"},
{label = "My name is not Neo.", next_node = "not_neo"},
{label = "...", next_node = "silent"}
}
},
who_are_you = {
text = "I am Trinity. I've been looking for you.",
options = {
{label = "The famous hacker?", next_node = "famous_hacker"},
{label = "Why me?", next_node = "why_me"}
}
},
not_neo = {
text = "I know. But you will be.",
options = {
{label = "What are you talking about?", next_node = "who_are_you"}
}
},
silent = {
text = "You're not much of a talker, are you?",
options = {
{label = "I guess not.", next_node = "dialog_end"}
}
},
famous_hacker = {
text = "The one and only.",
options = {
{label = "Wow.", next_node = "dialog_end"}
}
},
why_me = {
text = "Morpheus believes you are The One.",
options = {
{label = "The One?", next_node = "the_one"}
}
},
the_one = {
text = "The one who will save us all.",
options = {
{label = "I'm just a programmer.", next_node = "dialog_end"}
}
},
dialog_end = {
text = "We'll talk later.",
options = {} -- No options, ends conversation
}
}
}, },
{ {
x = 90, x = 90,
y = 102, y = 102,
name = "Oracle", name = "Oracle",
sprite_id = 3 sprite_id = 3,
dialog = {}
} }
}, },
items = { items = {
@@ -179,13 +235,45 @@ local State = {
x = 120, x = 120,
y = 72, y = 72,
name = "Morpheus", name = "Morpheus",
sprite_id = 5 sprite_id = 5,
dialog = {
start = {
text = "At last. Welcome, Neo. As you no doubt have guessed, I am Morpheus.",
options = {
{label = "It's an honor to meet you.", next_node = "honor"},
{label = "You've been looking for me.", next_node = "looking_for_me"}
}
},
honor = {
text = "No, the honor is mine.",
options = {
{label = "What is this place?", next_node = "what_is_this_place"}
}
},
looking_for_me = {
text = "I have. For some time.",
options = {
{label = "What is this place?", next_node = "what_is_this_place"}
}
},
what_is_this_place = {
text = "This is the construct. It's our loading program. We can load anything from clothing, to equipment, weapons, training simulations. Anything we need.",
options = {
{label = "Right.", next_node = "dialog_end"}
}
},
dialog_end = {
text = "I've been waiting for you, Neo. We have much to discuss.",
options = {}
}
}
}, },
{ {
x = 40, x = 40,
y = 92, y = 92,
name = "Tank", name = "Tank",
sprite_id = 6 sprite_id = 6,
dialog = {}
} }
}, },
items = { items = {
@@ -233,13 +321,15 @@ local State = {
x = 210, x = 210,
y = 42, y = 42,
name = "Agent Smith", name = "Agent Smith",
sprite_id = 8 sprite_id = 8,
dialog = {}
}, },
{ {
x = 160, x = 160,
y = 62, y = 62,
name = "Cypher", name = "Cypher",
sprite_id = 9 sprite_id = 9,
dialog = {}
} }
}, },
items = {} items = {}
@@ -250,16 +340,15 @@ local State = {
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Inventory Module -- Inventory Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function InventoryState.draw() function InventoryWindow.draw()
cls(Config.colors.dark_grey)
UI.draw_top_bar("Inventory") UI.draw_top_bar("Inventory")
if #State.inventory == 0 then if #Context.inventory == 0 then
print("Inventory is empty.", 70, 70, Config.colors.light_grey) print("Inventory is empty.", 70, 70, Config.colors.light_grey)
else else
for i, item in ipairs(State.inventory) do for i, item in ipairs(Context.inventory) do
local color = Config.colors.light_grey local color = Config.colors.light_grey
if i == State.selected_inventory_item then if i == Context.selected_inventory_item then
color = Config.colors.green color = Config.colors.green
print(">", 60, 20 + i * 10, color) print(">", 60, 20 + i * 10, color)
end end
@@ -268,21 +357,21 @@ function InventoryState.draw()
end end
end end
function InventoryState.update() function InventoryWindow.update()
State.selected_inventory_item = UI.update_menu(State.inventory, State.selected_inventory_item) Context.selected_inventory_item = UI.update_menu(Context.inventory, Context.selected_inventory_item)
if Input.menu_confirm() and #State.inventory > 0 then if Input.menu_confirm() and #Context.inventory > 0 then
local selected_item = State.inventory[State.selected_inventory_item] local selected_item = Context.inventory[Context.selected_inventory_item]
DialogState.show_menu_dialog(selected_item, { PopupWindow.show_menu_dialog(selected_item, {
{label = "Use", action = ItemActions.use}, {label = "Use", action = ItemActions.use},
{label = "Drop", action = ItemActions.drop}, {label = "Drop", action = ItemActions.drop},
{label = "Look at", action = ItemActions.look_at}, {label = "Look at", action = ItemActions.look_at},
{label = "Go back", action = ItemActions.go_back_from_inventory_action} {label = "Go back", action = ItemActions.go_back_from_inventory_action}
}, GAME_STATE_INVENTORY_ACTION) }, WINDOW_INVENTORY_ACTION)
end end
if Input.menu_back() then if Input.menu_back() then
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
end end
@@ -291,13 +380,13 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function MenuActions.play() function MenuActions.play()
-- Reset player state and screen for a new game -- Reset player state and screen for a new game
State.player.x = Config.player.start_x Context.player.x = Config.player.start_x
State.player.y = Config.player.start_y Context.player.y = Config.player.start_y
State.player.vx = 0 Context.player.vx = 0
State.player.vy = 0 Context.player.vy = 0
State.player.jumps = 0 Context.player.jumps = 0
State.current_screen = 1 Context.current_screen = 1
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
function MenuActions.exit() function MenuActions.exit()
@@ -305,7 +394,7 @@ function MenuActions.exit()
end end
-- Initialize menu items after actions are defined -- Initialize menu items after actions are defined
State.menu_items = { Context.menu_items = {
{label = "Play", action = MenuActions.play}, {label = "Play", action = MenuActions.play},
{label = "Exit", action = MenuActions.exit} {label = "Exit", action = MenuActions.exit}
} }
@@ -313,63 +402,71 @@ State.menu_items = {
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- NPC Actions -- NPC Actions
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function NpcActions.talk_to() end function NpcActions.talk_to()
local npc = Context.dialog.active_entity
if npc.dialog and npc.dialog.start then
PopupWindow.set_dialog_node("start")
else
-- if no dialog, go back
GameWindow.set_state(WINDOW_GAME)
end
end
function NpcActions.fight() end function NpcActions.fight() end
function NpcActions.go_back() function NpcActions.go_back()
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Item Actions -- Item Actions
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function ItemActions.use() function ItemActions.use()
print("Used item: " .. State.active_entity.name) print("Used item: " .. Context.dialog.active_entity.name)
GameState.set_state(GAME_STATE_INVENTORY) GameWindow.set_state(WINDOW_INVENTORY)
end end
function ItemActions.look_at() function ItemActions.look_at()
DialogState.show_description_dialog(State.active_entity, State.active_entity.desc) PopupWindow.show_description_dialog(Context.dialog.active_entity, Context.dialog.active_entity.desc)
end end
function ItemActions.put_away() function ItemActions.put_away()
-- Add item to inventory -- Add item to inventory
table.insert(State.inventory, State.active_entity) table.insert(Context.inventory, Context.dialog.active_entity)
-- Remove item from screen -- Remove item from screen
local currentScreenData = State.screens[State.current_screen] local currentScreenData = Context.screens[Context.current_screen]
for i, item in ipairs(currentScreenData.items) do for i, item in ipairs(currentScreenData.items) do
if item == State.active_entity then if item == Context.dialog.active_entity then
table.remove(currentScreenData.items, i) table.remove(currentScreenData.items, i)
break break
end end
end end
-- Go back to game -- Go back to game
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
function ItemActions.go_back_from_item_dialog() function ItemActions.go_back_from_item_dialog()
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
function ItemActions.go_back_from_inventory_action() function ItemActions.go_back_from_inventory_action()
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
function ItemActions.drop() function ItemActions.drop()
-- Remove item from inventory -- Remove item from inventory
for i, item in ipairs(State.inventory) do for i, item in ipairs(Context.inventory) do
if item == State.active_entity then if item == Context.dialog.active_entity then
table.remove(State.inventory, i) table.remove(Context.inventory, i)
break break
end end
end end
-- Add item to screen -- Add item to screen
local currentScreenData = State.screens[State.current_screen] local currentScreenData = Context.screens[Context.current_screen]
State.active_entity.x = State.player.x Context.dialog.active_entity.x = Context.player.x
State.active_entity.y = State.player.y Context.dialog.active_entity.y = Context.player.y
table.insert(currentScreenData.items, State.active_entity) table.insert(currentScreenData.items, Context.dialog.active_entity)
-- Go back to inventory -- Go back to inventory
GameState.set_state(GAME_STATE_INVENTORY) GameWindow.set_state(WINDOW_INVENTORY)
end end
@@ -394,20 +491,20 @@ function UI.draw_top_bar(title)
end end
function UI.draw_dialog() function UI.draw_dialog()
DialogState.draw() PopupWindow.draw()
end end
function DialogState.draw() function PopupWindow.draw()
rect(40, 40, 160, 80, Config.colors.black) rect(40, 40, 160, 80, Config.colors.black)
rectb(40, 40, 160, 80, Config.colors.green) rectb(40, 40, 160, 80, Config.colors.green)
-- Display the entity's name as the dialog title -- Display the entity's name as the dialog title
if State.active_entity and State.active_entity.name then if Context.dialog.active_entity and Context.dialog.active_entity.name then
print(State.active_entity.name, 120 - #State.active_entity.name * 2, 45, Config.colors.green) print(Context.dialog.active_entity.name, 120 - #Context.dialog.active_entity.name * 2, 45, Config.colors.green)
end end
-- Display the dialog content (description for "look at", or initial name/dialog for others) -- Display the dialog content (description for "look at", or initial name/dialog for others)
local wrapped_lines = UI.word_wrap(State.dialog_text, 25) -- Max 25 chars per line local wrapped_lines = UI.word_wrap(Context.dialog.text, 25) -- Max 25 chars per line
local current_y = 55 -- Starting Y position for the first line of content local current_y = 55 -- Starting Y position for the first line of content
for _, line in ipairs(wrapped_lines) do for _, line in ipairs(wrapped_lines) do
print(line, 50, current_y, Config.colors.light_grey) print(line, 50, current_y, Config.colors.light_grey)
@@ -415,8 +512,8 @@ function DialogState.draw()
end end
-- Adjust menu position based on the number of wrapped lines -- Adjust menu position based on the number of wrapped lines
if not State.showing_description then if not Context.dialog.showing_description then
UI.draw_menu(State.dialog_menu_items, State.selected_dialog_menu_item, 50, current_y + 2) UI.draw_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item, 50, current_y + 2)
else else
-- If description is showing, provide a "Go back" option automatically, or close dialog on action -- If description is showing, provide a "Go back" option automatically, or close dialog on action
-- For now, let's just make it implicitly wait for Input.menu_confirm() or Input.menu_back() to close -- For now, let's just make it implicitly wait for Input.menu_confirm() or Input.menu_back() to close
@@ -456,7 +553,7 @@ function UI.word_wrap(text, max_chars_per_line)
local segments = {} local segments = {}
-- Split the input text by explicit newline characters first -- Split the input text by explicit newline characters first
for segment in text:gmatch("([^\n]*)\n?") do for segment in text:gmatch("([^\\n]*)\\n?") do
table.insert(segments, segment) table.insert(segments, segment)
end end
@@ -496,62 +593,59 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Splash Module -- Splash Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function SplashState.draw() function SplashWindow.draw()
cls(Config.colors.dark_grey)
print("Mr. Anderson's", 78, 60, Config.colors.green) print("Mr. Anderson's", 78, 60, Config.colors.green)
print("Addventure", 90, 70, Config.colors.green) print("Addventure", 90, 70, Config.colors.green)
end end
function SplashState.update() function SplashWindow.update()
State.splash_timer = State.splash_timer - 1 Context.splash_timer = Context.splash_timer - 1
if State.splash_timer <= 0 or Input.menu_confirm() then if Context.splash_timer <= 0 or Input.menu_confirm() then
GameState.set_state(GAME_STATE_INTRO) GameWindow.set_state(WINDOW_INTRO)
end end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Intro Module -- Intro Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function IntroState.draw() function IntroWindow.draw()
cls(Config.colors.dark_grey)
local x = (Config.screen.width - 132) / 2 -- Centered text local x = (Config.screen.width - 132) / 2 -- Centered text
print(State.intro.text, x, State.intro.y, Config.colors.green) print(Context.intro.text, x, Context.intro.y, Config.colors.green)
end end
function IntroState.update() function IntroWindow.update()
State.intro.y = State.intro.y - State.intro.speed Context.intro.y = Context.intro.y - Context.intro.speed
-- Count lines in intro text to determine when scrolling is done -- Count lines in intro text to determine when scrolling is done
local lines = 1 local lines = 1
for _ in string.gmatch(State.intro.text, "\n") do for _ in string.gmatch(Context.intro.text, "\n") do
lines = lines + 1 lines = lines + 1
end end
-- When text is off-screen, go to menu -- When text is off-screen, go to menu
if State.intro.y < -lines * 8 then if Context.intro.y < -lines * 8 then
GameState.set_state(GAME_STATE_MENU) GameWindow.set_state(WINDOW_MENU)
end end
-- Skip intro by pressing A -- Skip intro by pressing A
if Input.menu_confirm() then if Input.menu_confirm() then
GameState.set_state(GAME_STATE_MENU) GameWindow.set_state(WINDOW_MENU)
end end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Menu Module -- Menu Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function MenuState.draw() function MenuWindow.draw()
cls(Config.colors.dark_grey)
UI.draw_top_bar("Main Menu") UI.draw_top_bar("Main Menu")
UI.draw_menu(State.menu_items, State.selected_menu_item, 108, 70) UI.draw_menu(Context.menu_items, Context.selected_menu_item, 108, 70)
end end
function MenuState.update() function MenuWindow.update()
State.selected_menu_item = UI.update_menu(State.menu_items, State.selected_menu_item) Context.selected_menu_item = UI.update_menu(Context.menu_items, Context.selected_menu_item)
if Input.menu_confirm() then if Input.menu_confirm() then
local selected_item = State.menu_items[State.selected_menu_item] local selected_item = Context.menu_items[Context.selected_menu_item]
if selected_item and selected_item.action then if selected_item and selected_item.action then
selected_item.action() selected_item.action()
end end
@@ -561,10 +655,9 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Game Module -- Game Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function GameState.draw() function GameWindow.draw()
local currentScreenData = State.screens[State.current_screen] local currentScreenData = Context.screens[Context.current_screen]
cls(Config.colors.dark_grey)
UI.draw_top_bar(currentScreenData.name) UI.draw_top_bar(currentScreenData.name)
-- Draw platforms -- Draw platforms
@@ -576,77 +669,77 @@ function GameState.draw()
for _, item in ipairs(currentScreenData.items) do for _, item in ipairs(currentScreenData.items) do
spr(item.sprite_id, item.x, item.y, 0) spr(item.sprite_id, item.x, item.y, 0)
end end
-- Draw NPCs -- Draw NPCs
for _, npc in ipairs(currentScreenData.npcs) do for _, npc in ipairs(currentScreenData.npcs) do
spr(npc.sprite_id, npc.x, npc.y, 0) spr(npc.sprite_id, npc.x, npc.y, 0)
end end
-- Draw ground -- Draw ground
rect(State.ground.x, State.ground.y, State.ground.w, State.ground.h, Config.colors.dark_grey) rect(Context.ground.x, Context.ground.y, Context.ground.w, Context.ground.h, Config.colors.dark_grey)
-- Draw player -- Draw player
Player.draw() Player.draw()
end end
function Player.draw() function Player.draw()
spr(State.player.sprite_id, State.player.x, State.player.y, 0) spr(Context.player.sprite_id, Context.player.x, Context.player.y, 0)
end end
function Player.update() function Player.update()
-- Handle input -- Handle input
if Input.left() then if Input.left() then
State.player.vx = -Config.physics.move_speed Context.player.vx = -Config.physics.move_speed
elseif Input.right() then elseif Input.right() then
State.player.vx = Config.physics.move_speed Context.player.vx = Config.physics.move_speed
else else
State.player.vx = 0 Context.player.vx = 0
end end
if Input.player_jump() and State.player.jumps < Config.physics.max_jumps then if Input.player_jump() and Context.player.jumps < Config.physics.max_jumps then
State.player.vy = Config.physics.jump_power Context.player.vy = Config.physics.jump_power
State.player.jumps = State.player.jumps + 1 Context.player.jumps = Context.player.jumps + 1
end end
-- Update player position -- Update player position
State.player.x = State.player.x + State.player.vx Context.player.x = Context.player.x + Context.player.vx
State.player.y = State.player.y + State.player.vy Context.player.y = Context.player.y + Context.player.vy
-- Screen transition -- Screen transition
if State.player.x > Config.screen.width - State.player.w then if Context.player.x > Config.screen.width - Context.player.w then
if State.current_screen < #State.screens then if Context.current_screen < #Context.screens then
State.current_screen = State.current_screen + 1 Context.current_screen = Context.current_screen + 1
State.player.x = 0 Context.player.x = 0
else else
State.player.x = Config.screen.width - State.player.w Context.player.x = Config.screen.width - Context.player.w
end end
elseif State.player.x < 0 then elseif Context.player.x < 0 then
if State.current_screen > 1 then if Context.current_screen > 1 then
State.current_screen = State.current_screen - 1 Context.current_screen = Context.current_screen - 1
State.player.x = Config.screen.width - State.player.w Context.player.x = Config.screen.width - Context.player.w
else else
State.player.x = 0 Context.player.x = 0
end end
end end
-- Apply gravity -- Apply gravity
State.player.vy = State.player.vy + Config.physics.gravity Context.player.vy = Context.player.vy + Config.physics.gravity
local currentScreenData = State.screens[State.current_screen] local currentScreenData = Context.screens[Context.current_screen]
-- Collision detection with platforms -- Collision detection with platforms
for _, p in ipairs(currentScreenData.platforms) do for _, p in ipairs(currentScreenData.platforms) do
if State.player.vy > 0 and State.player.y + State.player.h >= p.y and State.player.y + State.player.h <= p.y + p.h and State.player.x + State.player.w > p.x and State.player.x < p.x + p.w then if Context.player.vy > 0 and Context.player.y + Context.player.h >= p.y and Context.player.y + Context.player.h <= p.y + p.h and Context.player.x + Context.player.w > p.x and Context.player.x < p.x + p.w then
State.player.y = p.y - State.player.h Context.player.y = p.y - Context.player.h
State.player.vy = 0 Context.player.vy = 0
State.player.jumps = 0 Context.player.jumps = 0
end end
end end
-- Collision detection with ground -- Collision detection with ground
if State.player.y + State.player.h > State.ground.y then if Context.player.y + Context.player.h > Context.ground.y then
State.player.y = State.ground.y - State.player.h Context.player.y = Context.ground.y - Context.player.h
State.player.vy = 0 Context.player.vy = 0
State.player.jumps = 0 Context.player.jumps = 0
end end
-- Entity interaction -- Entity interaction
@@ -654,12 +747,12 @@ function Player.update()
local interaction_found = false local interaction_found = false
-- NPC interaction -- NPC interaction
for _, npc in ipairs(currentScreenData.npcs) do for _, npc in ipairs(currentScreenData.npcs) do
if math.abs(State.player.x - npc.x) < Config.physics.interaction_radius_npc and math.abs(State.player.y - npc.y) < Config.physics.interaction_radius_npc then if math.abs(Context.player.x - npc.x) < Config.physics.interaction_radius_npc and math.abs(Context.player.y - npc.y) < Config.physics.interaction_radius_npc then
DialogState.show_menu_dialog(npc, { PopupWindow.show_menu_dialog(npc, {
{label = "Talk to", action = NpcActions.talk_to}, {label = "Talk to", action = NpcActions.talk_to},
{label = "Fight", action = NpcActions.fight}, {label = "Fight", action = NpcActions.fight},
{label = "Go back", action = NpcActions.go_back} {label = "Go back", action = NpcActions.go_back}
}, GAME_STATE_DIALOG) }, WINDOW_POPUP)
interaction_found = true interaction_found = true
break break
end end
@@ -668,13 +761,13 @@ function Player.update()
if not interaction_found then if not interaction_found then
-- Item interaction -- Item interaction
for _, item in ipairs(currentScreenData.items) do for _, item in ipairs(currentScreenData.items) do
if math.abs(State.player.x - item.x) < Config.physics.interaction_radius_item and math.abs(State.player.y - item.y) < Config.physics.interaction_radius_item then if math.abs(Context.player.x - item.x) < Config.physics.interaction_radius_item and math.abs(Context.player.y - item.y) < Config.physics.interaction_radius_item then
DialogState.show_menu_dialog(item, { PopupWindow.show_menu_dialog(item, {
{label = "Use", action = ItemActions.use}, {label = "Use", action = ItemActions.use},
{label = "Look at", action = ItemActions.look_at}, {label = "Look at", action = ItemActions.look_at},
{label = "Put away", action = ItemActions.put_away}, {label = "Put away", action = ItemActions.put_away},
{label = "Go back", action = ItemActions.go_back_from_item_dialog} {label = "Go back", action = ItemActions.go_back_from_item_dialog}
}, GAME_STATE_DIALOG) }, WINDOW_POPUP)
interaction_found = true interaction_found = true
break break
end end
@@ -683,61 +776,99 @@ function Player.update()
-- If no interaction happened, open inventory -- If no interaction happened, open inventory
if not interaction_found then if not interaction_found then
GameState.set_state(GAME_STATE_INVENTORY) GameWindow.set_state(WINDOW_INVENTORY)
end end
end end
end end
function GameState.update() function GameWindow.update()
Player.update() -- Call the encapsulated player update logic Player.update() -- Call the encapsulated player update logic
end end
function GameState.set_state(new_state) function GameWindow.set_state(new_state)
State.game_state = new_state Context.active_window = new_state
-- Add any state-specific initialization/cleanup here later if needed -- Add any state-specific initialization/cleanup here later if needed
end end
function DialogState.update() function PopupWindow.set_dialog_node(node_key)
if State.showing_description then local npc = Context.dialog.active_entity
local node = npc.dialog[node_key]
if not node then
GameWindow.set_state(WINDOW_GAME)
return
end
Context.dialog.current_node_key = node_key
Context.dialog.text = node.text
local menu_items = {}
if node.options then
for _, option in ipairs(node.options) do
table.insert(menu_items, {
label = option.label,
action = function()
PopupWindow.set_dialog_node(option.next_node)
end
})
end
end
-- if no options, it's the end of this branch.
if #menu_items == 0 then
table.insert(menu_items, {
label = "Go back",
action = function() GameWindow.set_state(WINDOW_GAME) end
})
end
Context.dialog.menu_items = menu_items
Context.dialog.selected_menu_item = 1
Context.dialog.showing_description = false
GameWindow.set_state(WINDOW_POPUP)
end
function PopupWindow.update()
if Context.dialog.showing_description then
if Input.menu_confirm() or Input.menu_back() then if Input.menu_confirm() or Input.menu_back() then
State.showing_description = false Context.dialog.showing_description = false
State.dialog_text = "" -- Clear the description text Context.dialog.text = "" -- Clear the description text
-- No need to change game_state, as it remains in GAME_STATE_DIALOG or GAME_STATE_INVENTORY_ACTION -- No need to change active_window, as it remains in WINDOW_POPUP or WINDOW_INVENTORY_ACTION
end end
else else
State.selected_dialog_menu_item = UI.update_menu(State.dialog_menu_items, State.selected_dialog_menu_item) Context.dialog.selected_menu_item = UI.update_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item)
if Input.menu_confirm() then if Input.menu_confirm() then
local selected_item = State.dialog_menu_items[State.selected_dialog_menu_item] local selected_item = Context.dialog.menu_items[Context.dialog.selected_menu_item]
if selected_item and selected_item.action then if selected_item and selected_item.action then
selected_item.action() selected_item.action()
end end
end end
if Input.menu_back() then if Input.menu_back() then
GameState.set_state(GAME_STATE_GAME) GameWindow.set_state(WINDOW_GAME)
end end
end end
end end
function DialogState.show_menu_dialog(entity, menu_items, dialog_game_state) function PopupWindow.show_menu_dialog(entity, menu_items, dialog_active_window)
State.active_entity = entity Context.dialog.active_entity = entity
State.dialog_text = "" -- Initial dialog text is empty, name is title Context.dialog.text = "" -- Initial dialog text is empty, name is title
GameState.set_state(dialog_game_state or GAME_STATE_DIALOG) GameWindow.set_state(dialog_active_window or WINDOW_POPUP)
State.showing_description = false Context.dialog.showing_description = false
State.dialog_menu_items = menu_items Context.dialog.menu_items = menu_items
State.selected_dialog_menu_item = 1 Context.dialog.selected_menu_item = 1
end end
function DialogState.show_description_dialog(entity, description_text) function PopupWindow.show_description_dialog(entity, description_text)
State.active_entity = entity Context.dialog.active_entity = entity
State.dialog_text = description_text Context.dialog.text = description_text
GameState.set_state(GAME_STATE_DIALOG) GameWindow.set_state(WINDOW_POPUP)
State.showing_description = true Context.dialog.showing_description = true
-- No menu items needed for description dialog -- No menu items needed for description dialog
end end
@@ -745,40 +876,41 @@ end
-- Main Game Loop -- Main Game Loop
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local STATE_HANDLERS = { local STATE_HANDLERS = {
[GAME_STATE_SPLASH] = function() [WINDOW_SPLASH] = function()
SplashState.update() SplashWindow.update()
SplashState.draw() SplashWindow.draw()
end, end,
[GAME_STATE_INTRO] = function() [WINDOW_INTRO] = function()
IntroState.update() IntroWindow.update()
IntroState.draw() IntroWindow.draw()
end, end,
[GAME_STATE_MENU] = function() [WINDOW_MENU] = function()
MenuState.update() MenuWindow.update()
MenuState.draw() MenuWindow.draw()
end, end,
[GAME_STATE_GAME] = function() [WINDOW_GAME] = function()
GameState.update() GameWindow.update()
GameState.draw() GameWindow.draw()
end, end,
[GAME_STATE_DIALOG] = function() [WINDOW_POPUP] = function()
GameState.draw() -- Draw game behind dialog GameWindow.draw() -- Draw game behind dialog
DialogState.draw() PopupWindow.draw()
DialogState.update() PopupWindow.update()
end, end,
[GAME_STATE_INVENTORY] = function() [WINDOW_INVENTORY] = function()
InventoryState.update() InventoryWindow.update()
InventoryState.draw() InventoryWindow.draw()
end, end,
[GAME_STATE_INVENTORY_ACTION] = function() [WINDOW_INVENTORY_ACTION] = function()
InventoryState.draw() -- Draw inventory behind dialog InventoryWindow.draw() -- Draw inventory behind dialog
DialogState.draw() PopupWindow.draw()
DialogState.update() PopupWindow.update()
end, end,
} }
function TIC() function TIC()
local handler = STATE_HANDLERS[State.game_state] cls(Config.colors.black)
local handler = STATE_HANDLERS[Context.active_window]
if handler then if handler then
handler() handler()
end end
@@ -814,4 +946,3 @@ end
-- <PALETTE> -- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57 -- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE> -- </PALETTE>