4
0

Compare commits

...

5 Commits

Author SHA1 Message Date
36abe3a25d reorder object declarations
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-09 08:03:10 +01:00
1a68ef69cb refakt 2025-12-09 07:59:39 +01:00
3ca5adf414 State postfix 2025-12-09 07:45:48 +01:00
6dae70c7da refact 2025-12-09 07:39:43 +01:00
db99ea3b9a look at 2025-12-09 07:32:34 +01:00

View File

@@ -3,7 +3,7 @@
-- desc: Life of a programmer in the Vector -- desc: Life of a programmer in the Vector
-- site: http://teletype.hu -- site: http://teletype.hu
-- license: MIT License -- license: MIT License
-- version: 0.6 -- version: 0.7
-- script: lua -- script: lua
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -34,6 +34,8 @@ local Config = {
jump_power = -5, jump_power = -5,
move_speed = 1.5, move_speed = 1.5,
max_jumps = 2, max_jumps = 2,
interaction_radius_npc = 12, -- New constant
interaction_radius_item = 8 -- New constant
}, },
timing = { timing = {
splash_duration = 120 -- 2 seconds at 60fps splash_duration = 120 -- 2 seconds at 60fps
@@ -54,16 +56,21 @@ local GAME_STATE_INVENTORY_ACTION = 6
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Modules -- Modules
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local Splash = {} -- State Modules (in GAME_STATE order)
local Intro = {} local SplashState = {}
local Menu = {} local IntroState = {}
local Game = {} local MenuState = {}
local GameState = {}
local DialogState = {} -- Used for GAME_STATE_DIALOG and GAME_STATE_INVENTORY_ACTION
local InventoryState = {} -- Used for GAME_STATE_INVENTORY
-- Other Modules
local UI = {} local UI = {}
local Input = {} local Input = {}
local Inventory = {}
local NpcActions = {} local NpcActions = {}
local ItemActions = {} local ItemActions = {}
local MenuActions = {} local MenuActions = {}
local Player = {}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Game State -- Game State
@@ -114,7 +121,7 @@ local State = {
{x = 90, y = 102, name = "Oracle", sprite_id = 3} {x = 90, y = 102, name = "Oracle", sprite_id = 3}
}, },
items = { items = {
{x = 100, y = 128, w=8, h=8, name = "Key", sprite_id = 4} {x = 100, y = 128, w=8, h=8, name = "Key", sprite_id = 4, desc = "A rusty old key. It might open something."}
} }
}, },
{ -- Screen 2 { -- Screen 2
@@ -129,7 +136,7 @@ local State = {
{x = 40, y = 92, name = "Tank", sprite_id = 6} {x = 40, y = 92, name = "Tank", sprite_id = 6}
}, },
items = { items = {
{x = 180, y = 52, w=8, h=8, name = "Potion", sprite_id = 7} {x = 180, y = 52, w=8, h=8, name = "Potion", sprite_id = 7, desc = "A glowing red potion. It looks potent."}
} }
}, },
{ -- Screen 3 { -- Screen 3
@@ -152,7 +159,7 @@ local State = {
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Inventory Module -- Inventory Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function Inventory.draw() function InventoryState.draw()
cls(Config.colors.dark_grey) cls(Config.colors.dark_grey)
UI.draw_top_bar("Inventory") UI.draw_top_bar("Inventory")
@@ -170,25 +177,21 @@ function Inventory.draw()
end end
end end
function Inventory.update() function InventoryState.update()
State.selected_inventory_item = UI.update_menu(State.inventory, State.selected_inventory_item) State.selected_inventory_item = UI.update_menu(State.inventory, State.selected_inventory_item)
if Input.action() and #State.inventory > 0 then if Input.menu_confirm() and #State.inventory > 0 then
local selected_item = State.inventory[State.selected_inventory_item] local selected_item = State.inventory[State.selected_inventory_item]
State.active_entity = selected_item DialogState.show_menu_dialog(selected_item, {
State.dialog_text = selected_item.name
State.game_state = GAME_STATE_INVENTORY_ACTION
State.dialog_menu_items = {
{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 = "Goodbye", action = ItemActions.inventory_goodbye} {label = "Go back", action = ItemActions.go_back_from_inventory_action}
} }, GAME_STATE_INVENTORY_ACTION)
State.selected_dialog_menu_item = 1
end end
if Input.back() then if Input.menu_back() then
State.game_state = GAME_STATE_GAME GameState.set_state(GAME_STATE_GAME)
end end
end end
@@ -203,7 +206,7 @@ function MenuActions.play()
State.player.vy = 0 State.player.vy = 0
State.player.jumps = 0 State.player.jumps = 0
State.current_screen = 1 State.current_screen = 1
State.game_state = GAME_STATE_GAME GameState.set_state(GAME_STATE_GAME)
end end
function MenuActions.exit() function MenuActions.exit()
@@ -221,8 +224,8 @@ State.menu_items = {
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function NpcActions.talk_to() end function NpcActions.talk_to() end
function NpcActions.fight() end function NpcActions.fight() end
function NpcActions.goodbye() function NpcActions.go_back()
State.game_state = GAME_STATE_GAME GameState.set_state(GAME_STATE_GAME)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -230,11 +233,10 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function ItemActions.use() function ItemActions.use()
print("Used item: " .. State.active_entity.name) print("Used item: " .. State.active_entity.name)
State.game_state = GAME_STATE_INVENTORY GameState.set_state(GAME_STATE_INVENTORY)
end end
function ItemActions.look_at() function ItemActions.look_at()
print("Looked at item: " .. State.active_entity.name) DialogState.show_description_dialog(State.active_entity, State.active_entity.desc)
State.game_state = GAME_STATE_INVENTORY
end end
function ItemActions.put_away() function ItemActions.put_away()
-- Add item to inventory -- Add item to inventory
@@ -250,14 +252,14 @@ function ItemActions.put_away()
end end
-- Go back to game -- Go back to game
State.game_state = GAME_STATE_GAME GameState.set_state(GAME_STATE_GAME)
end end
function ItemActions.goodbye() function ItemActions.go_back_from_item_dialog()
State.game_state = GAME_STATE_GAME GameState.set_state(GAME_STATE_GAME)
end end
function ItemActions.inventory_goodbye() function ItemActions.go_back_from_inventory_action()
State.game_state = GAME_STATE_INVENTORY GameState.set_state(GAME_STATE_GAME)
end end
function ItemActions.drop() function ItemActions.drop()
@@ -276,7 +278,7 @@ function ItemActions.drop()
table.insert(currentScreenData.items, State.active_entity) table.insert(currentScreenData.items, State.active_entity)
-- Go back to inventory -- Go back to inventory
State.game_state = GAME_STATE_INVENTORY GameState.set_state(GAME_STATE_INVENTORY)
end end
@@ -287,10 +289,10 @@ function Input.up() return btnp(0) end
function Input.down() return btnp(1) end function Input.down() return btnp(1) end
function Input.left() return btn(2) end function Input.left() return btn(2) end
function Input.right() return btn(3) end function Input.right() return btn(3) end
function Input.jump() return btnp(4) end function Input.player_jump() return btnp(4) end
function Input.action() return btnp(4) end function Input.menu_confirm() return btnp(4) end
function Input.interact() return btnp(5) end -- B button function Input.player_interact() return btnp(5) end -- B button
function Input.back() return btnp(5) end function Input.menu_back() return btnp(5) end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- UI Module -- UI Module
@@ -301,10 +303,36 @@ function UI.draw_top_bar(title)
end end
function UI.draw_dialog() function UI.draw_dialog()
DialogState.draw()
end
function DialogState.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)
print(State.dialog_text, 120 - #State.dialog_text * 2, 45, Config.colors.light_grey)
UI.draw_menu(State.dialog_menu_items, State.selected_dialog_menu_item, 50, 60) -- 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)
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 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)
current_y = current_y + 8 -- Move to the next line (8 pixels for default font height + padding)
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)
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
-- Or we can add a specific "Back" option here.
-- Let's add a "Back" option for explicit return from description.
print("[A] Go Back", 50, current_y + 10, Config.colors.green)
end
end end
function UI.draw_menu(items, selected_item, x, y) function UI.draw_menu(items, selected_item, x, y)
@@ -332,32 +360,74 @@ function UI.update_menu(items, selected_item)
return selected_item return selected_item
end end
function UI.word_wrap(text, max_chars_per_line)
local result_lines = {}
local segments = {}
-- Split the input text by explicit newline characters first
for segment in text:gmatch("([^\n]*)\n?") do
table.insert(segments, segment)
end
-- Process each segment for word wrapping
for _, segment_text in ipairs(segments) do
local current_line = ""
local words = {}
-- Split segment into words
for word in segment_text:gmatch("%S+") do
table.insert(words, word)
end
local i = 1
while i <= #words do
local word = words[i]
if #current_line == 0 then
current_line = word
elseif #current_line + 1 + #word <= max_chars_per_line then
current_line = current_line .. " " .. word
else
table.insert(result_lines, current_line)
current_line = word
end
i = i + 1
end
-- Add the last line of the segment if not empty
if #current_line > 0 then
table.insert(result_lines, current_line)
end
end
return result_lines
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Splash Module -- Splash Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function Splash.draw() function SplashState.draw()
cls(Config.colors.dark_grey) 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 Splash.update() function SplashState.update()
State.splash_timer = State.splash_timer - 1 State.splash_timer = State.splash_timer - 1
if State.splash_timer <= 0 or Input.action() then if State.splash_timer <= 0 or Input.menu_confirm() then
State.game_state = GAME_STATE_INTRO GameState.set_state(GAME_STATE_INTRO)
end end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Intro Module -- Intro Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function Intro.draw() function IntroState.draw()
cls(Config.colors.dark_grey) 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(State.intro.text, x, State.intro.y, Config.colors.green)
end end
function Intro.update() function IntroState.update()
State.intro.y = State.intro.y - State.intro.speed State.intro.y = State.intro.y - State.intro.speed
-- Count lines in intro text to determine when scrolling is done -- Count lines in intro text to determine when scrolling is done
@@ -368,28 +438,28 @@ function Intro.update()
-- When text is off-screen, go to menu -- When text is off-screen, go to menu
if State.intro.y < -lines * 8 then if State.intro.y < -lines * 8 then
State.game_state = GAME_STATE_MENU GameState.set_state(GAME_STATE_MENU)
end end
-- Skip intro by pressing A -- Skip intro by pressing A
if Input.action() then if Input.menu_confirm() then
State.game_state = GAME_STATE_MENU GameState.set_state(GAME_STATE_MENU)
end end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Menu Module -- Menu Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function Menu.draw() function MenuState.draw()
cls(Config.colors.dark_grey) 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(State.menu_items, State.selected_menu_item, 108, 70)
end end
function Menu.update() function MenuState.update()
State.selected_menu_item = UI.update_menu(State.menu_items, State.selected_menu_item) State.selected_menu_item = UI.update_menu(State.menu_items, State.selected_menu_item)
if Input.action() then if Input.menu_confirm() then
local selected_item = State.menu_items[State.selected_menu_item] local selected_item = State.menu_items[State.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()
@@ -400,7 +470,7 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Game Module -- Game Module
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function Game.draw() function GameState.draw()
local currentScreenData = State.screens[State.current_screen] local currentScreenData = State.screens[State.current_screen]
cls(Config.colors.dark_grey) cls(Config.colors.dark_grey)
@@ -425,10 +495,14 @@ function Game.draw()
rect(State.ground.x, State.ground.y, State.ground.w, State.ground.h, Config.colors.dark_grey) rect(State.ground.x, State.ground.y, State.ground.w, State.ground.h, Config.colors.dark_grey)
-- Draw player -- Draw player
Player.draw()
end
function Player.draw()
spr(State.player.sprite_id, State.player.x, State.player.y, 0) spr(State.player.sprite_id, State.player.x, State.player.y, 0)
end end
function Game.update() function Player.update()
-- Handle input -- Handle input
if Input.left() then if Input.left() then
State.player.vx = -Config.physics.move_speed State.player.vx = -Config.physics.move_speed
@@ -438,7 +512,7 @@ function Game.update()
State.player.vx = 0 State.player.vx = 0
end end
if Input.jump() and State.player.jumps < Config.physics.max_jumps then if Input.player_jump() and State.player.jumps < Config.physics.max_jumps then
State.player.vy = Config.physics.jump_power State.player.vy = Config.physics.jump_power
State.player.jumps = State.player.jumps + 1 State.player.jumps = State.player.jumps + 1
end end
@@ -485,20 +559,16 @@ function Game.update()
end end
-- Entity interaction -- Entity interaction
if Input.interact() then if Input.player_interact() then
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) < 12 and math.abs(State.player.y - npc.y) < 12 then 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
State.active_entity = npc DialogState.show_menu_dialog(npc, {
State.dialog_text = npc.name
State.game_state = GAME_STATE_DIALOG
State.dialog_menu_items = {
{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 = "Goodbye", action = NpcActions.goodbye} {label = "Go back", action = NpcActions.go_back}
} }, GAME_STATE_DIALOG)
State.selected_dialog_menu_item = 1
interaction_found = true interaction_found = true
break break
end end
@@ -507,17 +577,13 @@ function Game.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) < 8 and math.abs(State.player.y - item.y) < 8 then 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
State.active_entity = item DialogState.show_menu_dialog(item, {
State.dialog_text = item.name
State.game_state = GAME_STATE_DIALOG
State.dialog_menu_items = {
{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 = "Goodbye", action = ItemActions.goodbye} {label = "Go back", action = ItemActions.go_back_from_item_dialog}
} }, GAME_STATE_DIALOG)
State.selected_dialog_menu_item = 1
interaction_found = true interaction_found = true
break break
end end
@@ -526,24 +592,62 @@ function Game.update()
-- If no interaction happened, open inventory -- If no interaction happened, open inventory
if not interaction_found then if not interaction_found then
State.game_state = GAME_STATE_INVENTORY GameState.set_state(GAME_STATE_INVENTORY)
end end
end end
end end
function Game.update_dialog() function GameState.update()
Player.update() -- Call the encapsulated player update logic
end
function GameState.set_state(new_state)
State.game_state = new_state
-- Add any state-specific initialization/cleanup here later if needed
end
function DialogState.update()
if State.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
end
else
State.selected_dialog_menu_item = UI.update_menu(State.dialog_menu_items, State.selected_dialog_menu_item) State.selected_dialog_menu_item = UI.update_menu(State.dialog_menu_items, State.selected_dialog_menu_item)
if Input.action() then if Input.menu_confirm() then
local selected_item = State.dialog_menu_items[State.selected_dialog_menu_item] local selected_item = State.dialog_menu_items[State.selected_dialog_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.back() then if Input.menu_back() then
State.game_state = GAME_STATE_GAME GameState.set_state(GAME_STATE_GAME)
end 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
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
-- No menu items needed for description dialog
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -551,34 +655,34 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local STATE_HANDLERS = { local STATE_HANDLERS = {
[GAME_STATE_SPLASH] = function() [GAME_STATE_SPLASH] = function()
Splash.update() SplashState.update()
Splash.draw() SplashState.draw()
end, end,
[GAME_STATE_INTRO] = function() [GAME_STATE_INTRO] = function()
Intro.update() IntroState.update()
Intro.draw() IntroState.draw()
end, end,
[GAME_STATE_MENU] = function() [GAME_STATE_MENU] = function()
Menu.update() MenuState.update()
Menu.draw() MenuState.draw()
end, end,
[GAME_STATE_GAME] = function() [GAME_STATE_GAME] = function()
Game.update() GameState.update()
Game.draw() GameState.draw()
end, end,
[GAME_STATE_DIALOG] = function() [GAME_STATE_DIALOG] = function()
Game.draw() -- Draw game behind dialog GameState.draw() -- Draw game behind dialog
UI.draw_dialog() DialogState.draw()
Game.update_dialog() DialogState.update()
end, end,
[GAME_STATE_INVENTORY] = function() [GAME_STATE_INVENTORY] = function()
Inventory.update() InventoryState.update()
Inventory.draw() InventoryState.draw()
end, end,
[GAME_STATE_INVENTORY_ACTION] = function() [GAME_STATE_INVENTORY_ACTION] = function()
Inventory.draw() -- Draw inventory behind dialog InventoryState.draw() -- Draw inventory behind dialog
UI.draw_dialog() DialogState.draw()
Game.update_dialog() DialogState.update()
end, end,
} }