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.
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
-- site: https://github.com/rastasi/mranderson
-- license: MIT License
-- version: 0.7
-- version: 0.8
-- script: lua
--------------------------------------------------------------------------------
@@ -43,26 +43,26 @@ local Config = {
}
--------------------------------------------------------------------------------
-- Game States
-- Game Windows
--------------------------------------------------------------------------------
local GAME_STATE_SPLASH = 0
local GAME_STATE_INTRO = 1
local GAME_STATE_MENU = 2
local GAME_STATE_GAME = 3
local GAME_STATE_DIALOG = 4
local GAME_STATE_INVENTORY = 5
local GAME_STATE_INVENTORY_ACTION = 6
local WINDOW_SPLASH = 0
local WINDOW_INTRO = 1
local WINDOW_MENU = 2
local WINDOW_GAME = 3
local WINDOW_POPUP = 4
local WINDOW_INVENTORY = 5
local WINDOW_INVENTORY_ACTION = 6
--------------------------------------------------------------------------------
-- Modules
--------------------------------------------------------------------------------
-- State Modules (in GAME_STATE order)
local SplashState = {}
local IntroState = {}
local MenuState = {}
local GameState = {}
local DialogState = {} -- Used for GAME_STATE_DIALOG and GAME_STATE_INVENTORY_ACTION
local InventoryState = {} -- Used for GAME_STATE_INVENTORY
-- Window Modules (in WINDOW order)
local SplashWindow = {}
local IntroWindow = {}
local MenuWindow = {}
local GameWindow = {}
local PopupWindow = {} -- Manages popups for WINDOW_POPUP and WINDOW_INVENTORY_ACTION
local InventoryWindow = {} -- Used for WINDOW_INVENTORY
-- Other Modules
local UI = {}
@@ -73,10 +73,10 @@ local MenuActions = {}
local Player = {}
--------------------------------------------------------------------------------
-- Game State
-- Game Window
--------------------------------------------------------------------------------
local State = {
game_state = GAME_STATE_SPLASH,
local Context = {
active_window = WINDOW_SPLASH,
inventory = {},
intro = {
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."
},
current_screen = 1,
dialog_text = "",
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 = {
x = Config.player.start_x,
y = Config.player.start_y,
@@ -105,9 +112,6 @@ local State = {
menu_items = {},
selected_menu_item = 1,
selected_inventory_item = 1,
dialog_menu_items = {},
selected_dialog_menu_item = 1,
active_entity = nil,
-- Screen data
screens = {
{ -- Screen 1
@@ -131,13 +135,65 @@ local State = {
x = 180,
y = 82,
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,
y = 102,
name = "Oracle",
sprite_id = 3
sprite_id = 3,
dialog = {}
}
},
items = {
@@ -179,13 +235,45 @@ local State = {
x = 120,
y = 72,
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,
y = 92,
name = "Tank",
sprite_id = 6
sprite_id = 6,
dialog = {}
}
},
items = {
@@ -233,13 +321,15 @@ local State = {
x = 210,
y = 42,
name = "Agent Smith",
sprite_id = 8
sprite_id = 8,
dialog = {}
},
{
x = 160,
y = 62,
name = "Cypher",
sprite_id = 9
sprite_id = 9,
dialog = {}
}
},
items = {}
@@ -250,16 +340,15 @@ local State = {
--------------------------------------------------------------------------------
-- Inventory Module
--------------------------------------------------------------------------------
function InventoryState.draw()
cls(Config.colors.dark_grey)
function InventoryWindow.draw()
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)
else
for i, item in ipairs(State.inventory) do
for i, item in ipairs(Context.inventory) do
local color = Config.colors.light_grey
if i == State.selected_inventory_item then
if i == Context.selected_inventory_item then
color = Config.colors.green
print(">", 60, 20 + i * 10, color)
end
@@ -268,21 +357,21 @@ function InventoryState.draw()
end
end
function InventoryState.update()
State.selected_inventory_item = UI.update_menu(State.inventory, State.selected_inventory_item)
function InventoryWindow.update()
Context.selected_inventory_item = UI.update_menu(Context.inventory, Context.selected_inventory_item)
if Input.menu_confirm() and #State.inventory > 0 then
local selected_item = State.inventory[State.selected_inventory_item]
DialogState.show_menu_dialog(selected_item, {
if Input.menu_confirm() and #Context.inventory > 0 then
local selected_item = Context.inventory[Context.selected_inventory_item]
PopupWindow.show_menu_dialog(selected_item, {
{label = "Use", action = ItemActions.use},
{label = "Drop", action = ItemActions.drop},
{label = "Look at", action = ItemActions.look_at},
{label = "Go back", action = ItemActions.go_back_from_inventory_action}
}, GAME_STATE_INVENTORY_ACTION)
}, WINDOW_INVENTORY_ACTION)
end
if Input.menu_back() then
GameState.set_state(GAME_STATE_GAME)
GameWindow.set_state(WINDOW_GAME)
end
end
@@ -291,13 +380,13 @@ end
--------------------------------------------------------------------------------
function MenuActions.play()
-- Reset player state and screen for a new game
State.player.x = Config.player.start_x
State.player.y = Config.player.start_y
State.player.vx = 0
State.player.vy = 0
State.player.jumps = 0
State.current_screen = 1
GameState.set_state(GAME_STATE_GAME)
Context.player.x = Config.player.start_x
Context.player.y = Config.player.start_y
Context.player.vx = 0
Context.player.vy = 0
Context.player.jumps = 0
Context.current_screen = 1
GameWindow.set_state(WINDOW_GAME)
end
function MenuActions.exit()
@@ -305,7 +394,7 @@ function MenuActions.exit()
end
-- Initialize menu items after actions are defined
State.menu_items = {
Context.menu_items = {
{label = "Play", action = MenuActions.play},
{label = "Exit", action = MenuActions.exit}
}
@@ -313,63 +402,71 @@ State.menu_items = {
--------------------------------------------------------------------------------
-- 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.go_back()
GameState.set_state(GAME_STATE_GAME)
GameWindow.set_state(WINDOW_GAME)
end
--------------------------------------------------------------------------------
-- Item Actions
--------------------------------------------------------------------------------
function ItemActions.use()
print("Used item: " .. State.active_entity.name)
GameState.set_state(GAME_STATE_INVENTORY)
print("Used item: " .. Context.dialog.active_entity.name)
GameWindow.set_state(WINDOW_INVENTORY)
end
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
function ItemActions.put_away()
-- Add item to inventory
table.insert(State.inventory, State.active_entity)
table.insert(Context.inventory, Context.dialog.active_entity)
-- 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
if item == State.active_entity then
if item == Context.dialog.active_entity then
table.remove(currentScreenData.items, i)
break
end
end
-- Go back to game
GameState.set_state(GAME_STATE_GAME)
GameWindow.set_state(WINDOW_GAME)
end
function ItemActions.go_back_from_item_dialog()
GameState.set_state(GAME_STATE_GAME)
GameWindow.set_state(WINDOW_GAME)
end
function ItemActions.go_back_from_inventory_action()
GameState.set_state(GAME_STATE_GAME)
GameWindow.set_state(WINDOW_GAME)
end
function ItemActions.drop()
-- Remove item from inventory
for i, item in ipairs(State.inventory) do
if item == State.active_entity then
table.remove(State.inventory, i)
for i, item in ipairs(Context.inventory) do
if item == Context.dialog.active_entity then
table.remove(Context.inventory, i)
break
end
end
-- Add item to screen
local currentScreenData = State.screens[State.current_screen]
State.active_entity.x = State.player.x
State.active_entity.y = State.player.y
table.insert(currentScreenData.items, State.active_entity)
local currentScreenData = Context.screens[Context.current_screen]
Context.dialog.active_entity.x = Context.player.x
Context.dialog.active_entity.y = Context.player.y
table.insert(currentScreenData.items, Context.dialog.active_entity)
-- Go back to inventory
GameState.set_state(GAME_STATE_INVENTORY)
GameWindow.set_state(WINDOW_INVENTORY)
end
@@ -394,20 +491,20 @@ function UI.draw_top_bar(title)
end
function UI.draw_dialog()
DialogState.draw()
PopupWindow.draw()
end
function DialogState.draw()
function PopupWindow.draw()
rect(40, 40, 160, 80, Config.colors.black)
rectb(40, 40, 160, 80, Config.colors.green)
-- Display the entity's name as the dialog title
if State.active_entity and State.active_entity.name then
print(State.active_entity.name, 120 - #State.active_entity.name * 2, 45, Config.colors.green)
if Context.dialog.active_entity and Context.dialog.active_entity.name then
print(Context.dialog.active_entity.name, 120 - #Context.dialog.active_entity.name * 2, 45, Config.colors.green)
end
-- 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
for _, line in ipairs(wrapped_lines) do
print(line, 50, current_y, Config.colors.light_grey)
@@ -415,8 +512,8 @@ function DialogState.draw()
end
-- Adjust menu position based on the number of wrapped lines
if not State.showing_description then
UI.draw_menu(State.dialog_menu_items, State.selected_dialog_menu_item, 50, current_y + 2)
if not Context.dialog.showing_description then
UI.draw_menu(Context.dialog.menu_items, Context.dialog.selected_menu_item, 50, current_y + 2)
else
-- 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
@@ -456,7 +553,7 @@ function UI.word_wrap(text, max_chars_per_line)
local segments = {}
-- 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)
end
@@ -496,62 +593,59 @@ end
--------------------------------------------------------------------------------
-- Splash Module
--------------------------------------------------------------------------------
function SplashState.draw()
cls(Config.colors.dark_grey)
function SplashWindow.draw()
print("Mr. Anderson's", 78, 60, Config.colors.green)
print("Addventure", 90, 70, Config.colors.green)
end
function SplashState.update()
State.splash_timer = State.splash_timer - 1
if State.splash_timer <= 0 or Input.menu_confirm() then
GameState.set_state(GAME_STATE_INTRO)
function SplashWindow.update()
Context.splash_timer = Context.splash_timer - 1
if Context.splash_timer <= 0 or Input.menu_confirm() then
GameWindow.set_state(WINDOW_INTRO)
end
end
--------------------------------------------------------------------------------
-- Intro Module
--------------------------------------------------------------------------------
function IntroState.draw()
cls(Config.colors.dark_grey)
function IntroWindow.draw()
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
function IntroState.update()
State.intro.y = State.intro.y - State.intro.speed
function IntroWindow.update()
Context.intro.y = Context.intro.y - Context.intro.speed
-- Count lines in intro text to determine when scrolling is done
local lines = 1
for _ in string.gmatch(State.intro.text, "\n") do
for _ in string.gmatch(Context.intro.text, "\n") do
lines = lines + 1
end
-- When text is off-screen, go to menu
if State.intro.y < -lines * 8 then
GameState.set_state(GAME_STATE_MENU)
if Context.intro.y < -lines * 8 then
GameWindow.set_state(WINDOW_MENU)
end
-- Skip intro by pressing A
if Input.menu_confirm() then
GameState.set_state(GAME_STATE_MENU)
GameWindow.set_state(WINDOW_MENU)
end
end
--------------------------------------------------------------------------------
-- Menu Module
--------------------------------------------------------------------------------
function MenuState.draw()
cls(Config.colors.dark_grey)
function MenuWindow.draw()
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
function MenuState.update()
State.selected_menu_item = UI.update_menu(State.menu_items, State.selected_menu_item)
function MenuWindow.update()
Context.selected_menu_item = UI.update_menu(Context.menu_items, Context.selected_menu_item)
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
selected_item.action()
end
@@ -561,10 +655,9 @@ end
--------------------------------------------------------------------------------
-- Game Module
--------------------------------------------------------------------------------
function GameState.draw()
local currentScreenData = State.screens[State.current_screen]
cls(Config.colors.dark_grey)
function GameWindow.draw()
local currentScreenData = Context.screens[Context.current_screen]
UI.draw_top_bar(currentScreenData.name)
-- Draw platforms
@@ -576,77 +669,77 @@ function GameState.draw()
for _, item in ipairs(currentScreenData.items) do
spr(item.sprite_id, item.x, item.y, 0)
end
-- Draw NPCs
for _, npc in ipairs(currentScreenData.npcs) do
spr(npc.sprite_id, npc.x, npc.y, 0)
end
-- 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
Player.draw()
end
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
function Player.update()
-- Handle input
if Input.left() then
State.player.vx = -Config.physics.move_speed
Context.player.vx = -Config.physics.move_speed
elseif Input.right() then
State.player.vx = Config.physics.move_speed
Context.player.vx = Config.physics.move_speed
else
State.player.vx = 0
Context.player.vx = 0
end
if Input.player_jump() and State.player.jumps < Config.physics.max_jumps then
State.player.vy = Config.physics.jump_power
State.player.jumps = State.player.jumps + 1
if Input.player_jump() and Context.player.jumps < Config.physics.max_jumps then
Context.player.vy = Config.physics.jump_power
Context.player.jumps = Context.player.jumps + 1
end
-- Update player position
State.player.x = State.player.x + State.player.vx
State.player.y = State.player.y + State.player.vy
Context.player.x = Context.player.x + Context.player.vx
Context.player.y = Context.player.y + Context.player.vy
-- Screen transition
if State.player.x > Config.screen.width - State.player.w then
if State.current_screen < #State.screens then
State.current_screen = State.current_screen + 1
State.player.x = 0
if Context.player.x > Config.screen.width - Context.player.w then
if Context.current_screen < #Context.screens then
Context.current_screen = Context.current_screen + 1
Context.player.x = 0
else
State.player.x = Config.screen.width - State.player.w
Context.player.x = Config.screen.width - Context.player.w
end
elseif State.player.x < 0 then
if State.current_screen > 1 then
State.current_screen = State.current_screen - 1
State.player.x = Config.screen.width - State.player.w
elseif Context.player.x < 0 then
if Context.current_screen > 1 then
Context.current_screen = Context.current_screen - 1
Context.player.x = Config.screen.width - Context.player.w
else
State.player.x = 0
Context.player.x = 0
end
end
-- 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
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
State.player.y = p.y - State.player.h
State.player.vy = 0
State.player.jumps = 0
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
Context.player.y = p.y - Context.player.h
Context.player.vy = 0
Context.player.jumps = 0
end
end
-- Collision detection with ground
if State.player.y + State.player.h > State.ground.y then
State.player.y = State.ground.y - State.player.h
State.player.vy = 0
State.player.jumps = 0
if Context.player.y + Context.player.h > Context.ground.y then
Context.player.y = Context.ground.y - Context.player.h
Context.player.vy = 0
Context.player.jumps = 0
end
-- Entity interaction
@@ -654,12 +747,12 @@ function Player.update()
local interaction_found = false
-- NPC interaction
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
DialogState.show_menu_dialog(npc, {
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
PopupWindow.show_menu_dialog(npc, {
{label = "Talk to", action = NpcActions.talk_to},
{label = "Fight", action = NpcActions.fight},
{label = "Go back", action = NpcActions.go_back}
}, GAME_STATE_DIALOG)
}, WINDOW_POPUP)
interaction_found = true
break
end
@@ -668,13 +761,13 @@ function Player.update()
if not interaction_found then
-- Item interaction
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
DialogState.show_menu_dialog(item, {
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
PopupWindow.show_menu_dialog(item, {
{label = "Use", action = ItemActions.use},
{label = "Look at", action = ItemActions.look_at},
{label = "Put away", action = ItemActions.put_away},
{label = "Go back", action = ItemActions.go_back_from_item_dialog}
}, GAME_STATE_DIALOG)
}, WINDOW_POPUP)
interaction_found = true
break
end
@@ -683,61 +776,99 @@ function Player.update()
-- If no interaction happened, open inventory
if not interaction_found then
GameState.set_state(GAME_STATE_INVENTORY)
GameWindow.set_state(WINDOW_INVENTORY)
end
end
end
function GameState.update()
function GameWindow.update()
Player.update() -- Call the encapsulated player update logic
end
function GameState.set_state(new_state)
State.game_state = new_state
function GameWindow.set_state(new_state)
Context.active_window = new_state
-- Add any state-specific initialization/cleanup here later if needed
end
function DialogState.update()
if State.showing_description then
function PopupWindow.set_dialog_node(node_key)
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
State.showing_description = false
State.dialog_text = "" -- Clear the description text
-- No need to change game_state, as it remains in GAME_STATE_DIALOG or GAME_STATE_INVENTORY_ACTION
Context.dialog.showing_description = false
Context.dialog.text = "" -- Clear the description text
-- No need to change active_window, as it remains in WINDOW_POPUP or WINDOW_INVENTORY_ACTION
end
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
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
selected_item.action()
end
end
if Input.menu_back() then
GameState.set_state(GAME_STATE_GAME)
GameWindow.set_state(WINDOW_GAME)
end
end
end
function DialogState.show_menu_dialog(entity, menu_items, dialog_game_state)
State.active_entity = entity
State.dialog_text = "" -- Initial dialog text is empty, name is title
GameState.set_state(dialog_game_state or GAME_STATE_DIALOG)
State.showing_description = false
State.dialog_menu_items = menu_items
State.selected_dialog_menu_item = 1
function PopupWindow.show_menu_dialog(entity, menu_items, dialog_active_window)
Context.dialog.active_entity = entity
Context.dialog.text = "" -- Initial dialog text is empty, name is title
GameWindow.set_state(dialog_active_window or WINDOW_POPUP)
Context.dialog.showing_description = false
Context.dialog.menu_items = menu_items
Context.dialog.selected_menu_item = 1
end
function DialogState.show_description_dialog(entity, description_text)
State.active_entity = entity
State.dialog_text = description_text
GameState.set_state(GAME_STATE_DIALOG)
State.showing_description = true
function PopupWindow.show_description_dialog(entity, description_text)
Context.dialog.active_entity = entity
Context.dialog.text = description_text
GameWindow.set_state(WINDOW_POPUP)
Context.dialog.showing_description = true
-- No menu items needed for description dialog
end
@@ -745,40 +876,41 @@ end
-- Main Game Loop
--------------------------------------------------------------------------------
local STATE_HANDLERS = {
[GAME_STATE_SPLASH] = function()
SplashState.update()
SplashState.draw()
[WINDOW_SPLASH] = function()
SplashWindow.update()
SplashWindow.draw()
end,
[GAME_STATE_INTRO] = function()
IntroState.update()
IntroState.draw()
[WINDOW_INTRO] = function()
IntroWindow.update()
IntroWindow.draw()
end,
[GAME_STATE_MENU] = function()
MenuState.update()
MenuState.draw()
[WINDOW_MENU] = function()
MenuWindow.update()
MenuWindow.draw()
end,
[GAME_STATE_GAME] = function()
GameState.update()
GameState.draw()
[WINDOW_GAME] = function()
GameWindow.update()
GameWindow.draw()
end,
[GAME_STATE_DIALOG] = function()
GameState.draw() -- Draw game behind dialog
DialogState.draw()
DialogState.update()
[WINDOW_POPUP] = function()
GameWindow.draw() -- Draw game behind dialog
PopupWindow.draw()
PopupWindow.update()
end,
[GAME_STATE_INVENTORY] = function()
InventoryState.update()
InventoryState.draw()
[WINDOW_INVENTORY] = function()
InventoryWindow.update()
InventoryWindow.draw()
end,
[GAME_STATE_INVENTORY_ACTION] = function()
InventoryState.draw() -- Draw inventory behind dialog
DialogState.draw()
DialogState.update()
[WINDOW_INVENTORY_ACTION] = function()
InventoryWindow.draw() -- Draw inventory behind dialog
PopupWindow.draw()
PopupWindow.update()
end,
}
function TIC()
local handler = STATE_HANDLERS[State.game_state]
cls(Config.colors.black)
local handler = STATE_HANDLERS[Context.active_window]
if handler then
handler()
end
@@ -814,4 +946,3 @@ end
-- <PALETTE>
-- 000:1a1c2c5d275db13e53ef7d57ffcd75a7f07038b76425717929366f3b5dc941a6f673eff7f4f4f494b0c2566c86333c57
-- </PALETTE>