From 34340d9664b1920dc48dc311ef7f7b05ef5c4e13 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Thu, 11 Dec 2025 20:00:43 +0100 Subject: [PATCH] save/load functionality (WIP) --- inc/init/init.config.lua | 43 +- inc/init/init.context.lua | 911 +++++++++++++++------------- inc/meta/meta.header.lua | 2 +- inc/system/system.input.lua | 6 +- inc/system/system.main.lua | 11 + inc/system/system.ui.lua | 8 + inc/window/window.configuration.lua | 78 ++- inc/window/window.game.lua | 5 + inc/window/window.menu.lua | 41 +- 9 files changed, 644 insertions(+), 461 deletions(-) diff --git a/inc/init/init.config.lua b/inc/init/init.config.lua index e8dbebc..b6f77ba 100644 --- a/inc/init/init.config.lua +++ b/inc/init/init.config.lua @@ -1,4 +1,4 @@ -local Config = { +local DEFAULT_CONFIG = { screen = { width = 240, height = 136 @@ -30,3 +30,44 @@ local Config = { splash_duration = 120 } } + +local Config = { + -- Copy default values initially + screen = DEFAULT_CONFIG.screen, + colors = DEFAULT_CONFIG.colors, + player = DEFAULT_CONFIG.player, + physics = DEFAULT_CONFIG.physics, + timing = DEFAULT_CONFIG.timing, +} + +local CONFIG_SAVE_BANK = 7 +local CONFIG_SAVE_ADDRESS_MOVE_SPEED = 0 +local CONFIG_SAVE_ADDRESS_MAX_JUMPS = 1 +local CONFIG_MAGIC_VALUE_ADDRESS = 2 +local CONFIG_MAGIC_VALUE = 0xDE -- A magic number to check if config is saved + +function Config.save() + -- Save physics settings + mset(Config.physics.move_speed * 10, CONFIG_SAVE_ADDRESS_MOVE_SPEED, CONFIG_SAVE_BANK) + mset(Config.physics.max_jumps, CONFIG_SAVE_ADDRESS_MAX_JUMPS, CONFIG_SAVE_BANK) + mset(CONFIG_MAGIC_VALUE, CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) -- Mark as saved +end + +function Config.load() + -- Check if config has been saved before using a magic value + if mget(CONFIG_MAGIC_VALUE_ADDRESS, CONFIG_SAVE_BANK) == CONFIG_MAGIC_VALUE then + Config.physics.move_speed = mget(CONFIG_SAVE_ADDRESS_MOVE_SPEED, CONFIG_SAVE_BANK) / 10 + Config.physics.max_jumps = mget(CONFIG_SAVE_ADDRESS_MAX_JUMPS, CONFIG_SAVE_BANK) + else + Config.restore_defaults() + end +end + +function Config.restore_defaults() + Config.physics.move_speed = DEFAULT_CONFIG.physics.move_speed + Config.physics.max_jumps = DEFAULT_CONFIG.physics.max_jumps + -- Any other configurable items should be reset here +end + +-- Load configuration on startup +Config.load() diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 1b71c2a..2b904f2 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -1,432 +1,515 @@ -local Context = { - active_window = WINDOW_SPLASH, - inventory = {}, - intro = { - y = Config.screen.height, - speed = 0.5, - 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, - splash_timer = Config.timing.splash_duration, - dialog = { - text = "", +local SAVE_GAME_BANK = 6 +local SAVE_GAME_MAGIC_VALUE_ADDRESS = 0 +local SAVE_GAME_MAGIC_VALUE = 0xCA + +local SAVE_GAME_PLAYER_X_ADDRESS = 1 +local SAVE_GAME_PLAYER_Y_ADDRESS = 2 +local SAVE_GAME_PLAYER_VX_ADDRESS = 3 +local SAVE_GAME_PLAYER_VY_ADDRESS = 4 +local SAVE_GAME_PLAYER_JUMPS_ADDRESS = 5 +local SAVE_GAME_CURRENT_SCREEN_ADDRESS = 6 + +local VX_VY_OFFSET = 128 -- Offset for negative velocities + +-- Helper for deep copying tables +local function clone_table(t) + local copy = {} + for k, v in pairs(t) do + if type(v) == "table" then + copy[k] = clone_table(v) + else + copy[k] = v + end + end + return copy +end + +local function create_initial_context() + return { + active_window = WINDOW_SPLASH, + inventory = {}, + intro = { + y = Config.screen.height, + speed = 0.5, + 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, + 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, + w = Config.player.w, + h = Config.player.h, + vx = 0, + vy = 0, + jumps = 0, + sprite_id = Config.player.sprite_id + }, + ground = { + x = 0, + y = Config.screen.height, + w = Config.screen.width, + h = 8 + }, menu_items = {}, selected_menu_item = 1, - active_entity = nil, - showing_description = false, - current_node_key = nil - }, - player = { - x = Config.player.start_x, - y = Config.player.start_y, - w = Config.player.w, - h = Config.player.h, - vx = 0, - vy = 0, - jumps = 0, - sprite_id = Config.player.sprite_id - }, - ground = { - x = 0, - y = Config.screen.height, - w = Config.screen.width, - h = 8 - }, - menu_items = {}, - selected_menu_item = 1, - selected_inventory_item = 1, - -- Screen data - screens = { - { - -- Screen 1 - name = "Screen 1", - platforms = { - { - x = 80, - y = 110, - w = 40, - h = 8 + selected_inventory_item = 1, + game_in_progress = false, -- New flag + -- Screen data (deep copy to ensure new game resets correctly) + screens = clone_table({ + { + -- Screen 1 + name = "Screen 1", + platforms = { + { + x = 80, + y = 110, + w = 40, + h = 8 + }, + { + x = 160, + y = 90, + w = 40, + h = 8 + } }, - { - x = 160, - y = 90, - w = 40, - h = 8 - } - }, - npcs = { - { - x = 180, - y = 82, - name = "Trinity", - 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.", + npcs = { + { + x = 180, + y = 82, + name = "Trinity", + sprite_id = 2, + dialog = { + start = { + text = "Hello, Neo.", options = { - {label = "Wow.", next_node = "dialog_end"} + {label = "Who are you?", next_node = "who_are_you"}, + {label = "My name is not Neo.", next_node = "not_neo"}, + {label = "...", next_node = "silent"} } - }, - why_me = { - text = "Morpheus believes you are The One.", + }, + who_are_you = { + text = "I am Trinity. I've been looking for you.", options = { - {label = "The One?", next_node = "the_one"} + {label = "The famous hacker?", next_node = "famous_hacker"}, + {label = "Why me?", next_node = "why_me"} } - }, - the_one = { - text = "The one who will save us all.", + }, + not_neo = { + text = "I know. But you will be.", options = { - {label = "I'm just a programmer.", next_node = "dialog_end"} + {label = "What are you talking about?", next_node = "who_are_you"} } - }, - dialog_end = { - text = "We'll talk later.", - options = {} -- No options, ends conversation + }, + 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, + dialog = { + start = { + text = "I know what you're thinking. 'Am I in the right place?'", + options = { + {label = "Who are you?", next_node = "who_are_you"}, + {label = "I guess I am.", next_node = "you_are"} + } + }, + who_are_are = { + text = "I'm the Oracle. And you're right on time. Want a cookie?", + options = { + {label = "Sure.", next_node = "cookie"}, + {label = "No, thank you.", next_node = "no_cookie"} + } + }, + you_are = { + text = "Of course you are. Sooner or later, everyone comes to see me. Want a cookie?", + options = { + {label = "Yes, please.", next_node = "cookie"}, + {label = "I'm good.", next_node = "no_cookie"} + } + }, + cookie = { + text = "Here you go. Now, what's really on your mind?", + options = { + {label = "Am I The One?", next_node = "the_one"}, + {label = "What is the Matrix?", next_node = "the_matrix"} + } + }, + no_cookie = { + text = "Suit yourself. Now, what's troubling you?", + options = { + {label = "Am I The One?", next_node = "the_one"}, + {label = "What is the Matrix?", next_node = "the_matrix"} + } + }, + the_one = { + text = "Being The One is just like being in love. No one can tell you you're in love, you just know it. Through and through. Balls to bones.", + options = { + {label = "So I'm not?", next_node = "dialog_end"} + } + }, + the_matrix = { + text = "The Matrix is a system, Neo. That system is our enemy. But when you're inside, you look around, what do you see? The very minds of the people we are trying to save.", + options = { + {label = "I see.", next_node = "dialog_end"} + } + }, + dialog_end = { + text = "You have to understand, most of these people are not ready to be unplugged.", + options = {} + } } } }, - { - x = 90, - y = 102, - name = "Oracle", - sprite_id = 3, - dialog = { - start = { - text = "I know what you're thinking. 'Am I in the right place?'", - options = { - {label = "Who are you?", next_node = "who_are_you"}, - {label = "I guess I am.", next_node = "you_are"} - } - }, - who_are_you = { - text = "I'm the Oracle. And you're right on time. Want a cookie?", - options = { - {label = "Sure.", next_node = "cookie"}, - {label = "No, thank you.", next_node = "no_cookie"} - } - }, - you_are = { - text = "Of course you are. Sooner or later, everyone comes to see me. Want a cookie?", - options = { - {label = "Yes, please.", next_node = "cookie"}, - {label = "I'm good.", next_node = "no_cookie"} - } - }, - cookie = { - text = "Here you go. Now, what's really on your mind?", - options = { - {label = "Am I The One?", next_node = "the_one"}, - {label = "What is the Matrix?", next_node = "the_matrix"} - } - }, - no_cookie = { - text = "Suit yourself. Now, what's troubling you?", - options = { - {label = "Am I The One?", next_node = "the_one"}, - {label = "What is the Matrix?", next_node = "the_matrix"} - } - }, - the_one = { - text = "Being The One is just like being in love. No one can tell you you're in love, you just know it. Through and through. Balls to bones.", - options = { - {label = "So I'm not?", next_node = "dialog_end"} - } - }, - the_matrix = { - text = "The Matrix is a system, Neo. That system is our enemy. But when you're inside, you look around, what do you see? The very minds of the people we are trying to save.", - options = { - {label = "I see.", next_node = "dialog_end"} - } - }, - dialog_end = { - text = "You have to understand, most of these people are not ready to be unplugged.", - options = {} - } + items = { + { + x = 100, + y = 128, + w = 8, + h = 8, + name = "Key", + sprite_id = 4, + desc = "A rusty old key. It might open something." } } }, - items = { - { - x = 100, - y = 128, - w = 8, - h = 8, - name = "Key", - sprite_id = 4, - desc = "A rusty old key. It might open something." + { + -- Screen 2 + name = "Screen 2", + platforms = { + { + x = 30, + y = 100, + w = 50, + h = 8 + }, + { + x = 100, + y = 80, + w = 50, + h = 8 + }, + { + x = 170, + y = 60, + w = 50, + h = 8 + } + }, + npcs = { + { + x = 120, + y = 72, + name = "Morpheus", + 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 = {} -- Ends conversation + } + } + }, + { + x = 40, + y = 92, + name = "Tank", + sprite_id = 6, + dialog = { + start = { + text = "Hey, Neo! Welcome to the construct. I'm Tank.", + options = { + {label = "Good to meet you.", next_node = "good_to_meet_you"}, + {label = "This place is incredible.", next_node = "incredible"} + } + }, + good_to_meet_you = { + text = "You too! We've been waiting for you. Need anything? Training? Weapons?", + options = { + {label = "Training?", next_node = "training"}, + {label = "I'm good for now.", next_node = "dialog_end"} + } + }, + incredible = { + text = "Isn't it? The boss's design. We can load anything we need. What do you want to learn?", + options = { + {label = "Show me.", next_node = "training"} + } + }, + training = { + text = "Jujitsu? Kung Fu? How about... all of them?", + options = { + {label = "All of them.", next_node = "all_of_them"} + } + }, + all_of_them = { + text = "Operator, load the combat training program.", + options = { + {label = "...", next_node = "dialog_end"} + } + }, + dialog_end = { + text = "Just holler if you need anything. Anything at all.", + options = {} + } + } + } + }, + items = { + { + x = 180, + y = 52, + w = 8, + h = 8, + name = "Potion", + sprite_id = 7, + desc = "A glowing red potion. It looks potent." + } } + }, + { + -- Screen 3 + name = "Screen 3", + platforms = { + { + x = 50, + y = 110, + w = 30, + h = 8 + }, + { + x = 100, + y = 90, + w = 30, + h = 8 + }, + { + x = 150, + y = 70, + w = 30, + h = 8 + }, + { + x = 200, + y = 50, + w = 30, + h = 8 + } + }, + npcs = { + { + x = 210, + y = 42, + name = "Agent Smith", + sprite_id = 8, + dialog = { + start = { + text = "Mr. Anderson. We've been expecting you.", + options = { + {label = "My name is Neo.", next_node = "name_is_neo"}, + {label = "...", next_node = "silent"} + } + }, + name_is_neo = { + text = "Whatever you say. You're here for a reason.", + options = { + {label = "What reason?", next_node = "what_reason"} + } + }, + silent = { + text = "The silent type. It doesn't matter. You are an anomaly.", + options = { + {label = "What do you want?", next_node = "what_reason"} + } + }, + what_reason = { + text = "To be deleted. The system has no place for your kind.", + options = { + {label = "I won't let you.", next_node = "wont_let_you"} + } + }, + wont_let_you = { + text = "You hear that, Mr. Anderson? That is the sound of inevitability.", + options = { + {label = "...", next_node = "dialog_end"} + } + }, + dialog_end = { + text = "It is purpose that created us. Purpose that connects us. Purpose that pulls us. That guides us. That drives us. It is purpose that defines. Purpose that binds us.", + options = {} + } + } + }, + { + x = 160, + y = 62, + name = "Cypher", + sprite_id = 9, + dialog = { + start = { + text = "Well, well. The new messiah. Welcome to the real world.", + options = { + {label = "You don't seem happy.", next_node = "not_happy"}, + {label = "...", next_node = "silent"} + } + }, + not_happy = { + text = "Happy? Ignorance is bliss, Neo. We've been fighting this war for years. For what?", + options = { + {label = "For freedom.", next_node = "freedom"} + } + }, + silent = { + text = "Not a talker, huh? Smart. Less to regret later. Want a drink?", + options = { + {label = "Sure.", next_node = "drink"}, + {label = "No thanks.", next_node = "no_drink"} + } + }, + drink = { + text = "Good stuff. The little things you miss, you know? Like a good steak.", + options = { + {label = "I guess.", next_node = "dialog_end"} + } + }, + no_drink = { + text = "Your loss. More for me.", + options = { + {label = "...", next_node = "dialog_end"} + } + }, + freedom = { + text = "Freedom... right. If Morpheus told you you could fly, would you believe him?", + options = { + {label = "He's our leader.", next_node = "dialog_end"} + } + }, + dialog_end = { + text = "Just be careful who you trust.", + options = {} + } + } + } + }, + items = {} } - }, - { - -- Screen 2 - name = "Screen 2", - platforms = { - { - x = 30, - y = 100, - w = 50, - h = 8 - }, - { - x = 100, - y = 80, - w = 50, - h = 8 - }, - { - x = 170, - y = 60, - w = 50, - h = 8 - } - }, - npcs = { - { - x = 120, - y = 72, - name = "Morpheus", - 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 = {} -- Ends conversation - } - } - }, - { - x = 40, - y = 92, - name = "Tank", - sprite_id = 6, - dialog = { - start = { - text = "Hey, Neo! Welcome to the construct. I'm Tank.", - options = { - {label = "Good to meet you.", next_node = "good_to_meet_you"}, - {label = "This place is incredible.", next_node = "incredible"} - } - }, - good_to_meet_you = { - text = "You too! We've been waiting for you. Need anything? Training? Weapons?", - options = { - {label = "Training?", next_node = "training"}, - {label = "I'm good for now.", next_node = "dialog_end"} - } - }, - incredible = { - text = "Isn't it? The boss's design. We can load anything we need. What do you want to learn?", - options = { - {label = "Show me.", next_node = "training"} - } - }, - training = { - text = "Jujitsu? Kung Fu? How about... all of them?", - options = { - {label = "All of them.", next_node = "all_of_them"} - } - }, - all_of_them = { - text = "Operator, load the combat training program.", - options = { - {label = "...", next_node = "dialog_end"} - } - }, - dialog_end = { - text = "Just holler if you need anything. Anything at all.", - options = {} - } - } - } - }, - items = { - { - x = 180, - y = 52, - w = 8, - h = 8, - name = "Potion", - sprite_id = 7, - desc = "A glowing red potion. It looks potent." - } - } - }, - { - -- Screen 3 - name = "Screen 3", - platforms = { - { - x = 50, - y = 110, - w = 30, - h = 8 - }, - { - x = 100, - y = 90, - w = 30, - h = 8 - }, - { - x = 150, - y = 70, - w = 30, - h = 8 - }, - { - x = 200, - y = 50, - w = 30, - h = 8 - } - }, - npcs = { - { - x = 210, - y = 42, - name = "Agent Smith", - sprite_id = 8, - dialog = { - start = { - text = "Mr. Anderson. We've been expecting you.", - options = { - {label = "My name is Neo.", next_node = "name_is_neo"}, - {label = "...", next_node = "silent"} - } - }, - name_is_neo = { - text = "Whatever you say. You're here for a reason.", - options = { - {label = "What reason?", next_node = "what_reason"} - } - }, - silent = { - text = "The silent type. It doesn't matter. You are an anomaly.", - options = { - {label = "What do you want?", next_node = "what_reason"} - } - }, - what_reason = { - text = "To be deleted. The system has no place for your kind.", - options = { - {label = "I won't let you.", next_node = "wont_let_you"} - } - }, - wont_let_you = { - text = "You hear that, Mr. Anderson? That is the sound of inevitability.", - options = { - {label = "...", next_node = "dialog_end"} - } - }, - dialog_end = { - text = "It is purpose that created us. Purpose that connects us. Purpose that pulls us. That guides us. That drives us. It is purpose that defines. Purpose that binds us.", - options = {} - } - } - }, - { - x = 160, - y = 62, - name = "Cypher", - sprite_id = 9, - dialog = { - start = { - text = "Well, well. The new messiah. Welcome to the real world.", - options = { - {label = "You don't seem happy.", next_node = "not_happy"}, - {label = "...", next_node = "silent"} - } - }, - not_happy = { - text = "Happy? Ignorance is bliss, Neo. We've been fighting this war for years. For what?", - options = { - {label = "For freedom.", next_node = "freedom"} - } - }, - silent = { - text = "Not a talker, huh? Smart. Less to regret later. Want a drink?", - options = { - {label = "Sure.", next_node = "drink"}, - {label = "No thanks.", next_node = "no_drink"} - } - }, - drink = { - text = "Good stuff. The little things you miss, you know? Like a good steak.", - options = { - {label = "I guess.", next_node = "dialog_end"} - } - }, - no_drink = { - text = "Your loss. More for me.", - options = { - {label = "...", next_node = "dialog_end"} - } - }, - freedom = { - text = "Freedom... right. If Morpheus told you you could fly, would you believe him?", - options = { - {label = "He's our leader.", next_node = "dialog_end"} - } - }, - dialog_end = { - text = "Just be careful who you trust.", - options = {} - } - } - } - }, - items = {} - } + }) } -} +end + +Context = create_initial_context() + +local function reset_context_to_initial_state() + local initial_state = create_initial_context() + + -- Clear existing keys from Context + for k in pairs(Context) do + Context[k] = nil + end + + -- Copy all properties from initial_state to Context + for k, v in pairs(initial_state) do + Context[k] = v + end +end + +function Context.new_game() + reset_context_to_initial_state() + Context.game_in_progress = true + MenuWindow.refresh_menu_items() +end + +function Context.save_game() + if not Context.game_in_progress then return end + + mset(SAVE_GAME_MAGIC_VALUE, SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) + mset(Context.player.x * 10, SAVE_GAME_PLAYER_X_ADDRESS, SAVE_GAME_BANK) + mset(Context.player.y * 10, SAVE_GAME_PLAYER_Y_ADDRESS, SAVE_GAME_BANK) + mset( (Context.player.vx * 100) + VX_VY_OFFSET, SAVE_GAME_PLAYER_VX_ADDRESS, SAVE_GAME_BANK) + mset( (Context.player.vy * 100) + VX_VY_OFFSET, SAVE_GAME_PLAYER_VY_ADDRESS, SAVE_GAME_BANK) + mset(Context.player.jumps, SAVE_GAME_PLAYER_JUMPS_ADDRESS, SAVE_GAME_BANK) + mset(Context.current_screen, SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK) +end + +function Context.load_game() + if mget(SAVE_GAME_MAGIC_VALUE_ADDRESS, SAVE_GAME_BANK) ~= SAVE_GAME_MAGIC_VALUE then + -- No saved game found, start a new one + Context.new_game() + return + end + + reset_context_to_initial_state() + + Context.player.x = mget(SAVE_GAME_PLAYER_X_ADDRESS, SAVE_GAME_BANK) / 10 + Context.player.y = mget(SAVE_GAME_PLAYER_Y_ADDRESS, SAVE_GAME_BANK) / 10 + Context.player.vx = (mget(SAVE_GAME_PLAYER_VX_ADDRESS, SAVE_GAME_BANK) - VX_VY_OFFSET) / 100 + Context.player.vy = (mget(SAVE_GAME_PLAYER_VY_ADDRESS, SAVE_GAME_BANK) - VX_VY_OFFSET) / 100 + Context.player.jumps = mget(SAVE_GAME_PLAYER_JUMPS_ADDRESS, SAVE_GAME_BANK) + Context.current_screen = mget(SAVE_GAME_CURRENT_SCREEN_ADDRESS, SAVE_GAME_BANK) + + Context.game_in_progress = true + MenuWindow.refresh_menu_items() +end diff --git a/inc/meta/meta.header.lua b/inc/meta/meta.header.lua index ddeb0b0..85543de 100644 --- a/inc/meta/meta.header.lua +++ b/inc/meta/meta.header.lua @@ -3,5 +3,5 @@ -- desc: Life of a programmer in the Vector -- site: https://github.com/rastasi/mranderson -- license: MIT License --- version: 0.11 +-- version: 0.12 -- script: lua diff --git a/inc/system/system.input.lua b/inc/system/system.input.lua index 8aa293a..5fcc362 100644 --- a/inc/system/system.input.lua +++ b/inc/system/system.input.lua @@ -1,8 +1,8 @@ function Input.up() return btnp(0) end function Input.down() return btnp(1) end -function Input.left() return btnp(2) end -function Input.right() return btnp(3) end +function Input.left() return btn(2) end +function Input.right() return btn(3) end function Input.player_jump() return btnp(4) end function Input.menu_confirm() return btnp(4) end function Input.player_interact() return btnp(5) end -- B button -function Input.menu_back() return btnp(5) end +function Input.menu_back() return btnp(7) end diff --git a/inc/system/system.main.lua b/inc/system/system.main.lua index 809edf2..db93d45 100644 --- a/inc/system/system.main.lua +++ b/inc/system/system.main.lua @@ -35,7 +35,18 @@ local STATE_HANDLERS = { end, } +local initialized_game = false + +function init_game() + if initialized_game then return end + + MenuWindow.refresh_menu_items() + initialized_game = true +end + function TIC() + init_game() + cls(Config.colors.black) local handler = STATE_HANDLERS[Context.active_window] if handler then diff --git a/inc/system/system.ui.lua b/inc/system/system.ui.lua index 36ef965..9fb4a1b 100644 --- a/inc/system/system.ui.lua +++ b/inc/system/system.ui.lua @@ -77,3 +77,11 @@ function UI.create_numeric_stepper(label, value_getter, value_setter, min, max, type = "numeric_stepper" } end + +function UI.create_action_item(label, action) + return { + label = label, + action = action, + type = "action_item" + } +end diff --git a/inc/window/window.configuration.lua b/inc/window/window.configuration.lua index 06a2e90..216a609 100644 --- a/inc/window/window.configuration.lua +++ b/inc/window/window.configuration.lua @@ -17,6 +17,14 @@ function ConfigurationWindow.init() function(v) Config.physics.max_jumps = v end, 1, 5, 1, "%d" ), + UI.create_action_item( + "Save", + function() Config.save() end + ), + UI.create_action_item( + "Restore Defaults", + function() Config.restore_defaults() end + ), } end @@ -31,23 +39,35 @@ function ConfigurationWindow.draw() for i, control in ipairs(ConfigurationWindow.controls) do local current_y = y_start + (i - 1) * 12 local color = Config.colors.green - - local value = control.get() - local label_text = control.label - local value_text = string.format(control.format, value) - - -- Calculate x position for right-aligned value - local value_x = x_value_right_align - (#value_text * char_width) - - if i == ConfigurationWindow.selected_control then - color = Config.colors.item - print("<", x_start -8, current_y, color) - print(label_text, x_start, current_y, color) -- Shift label due to '<' - print(value_text, value_x, current_y, color) - print(">", x_value_right_align + 4, current_y, color) -- Print '>' after value - else - print(label_text, x_start, current_y, color) - print(value_text, value_x, current_y, color) + + if control.type == "numeric_stepper" then + local value = control.get() + local label_text = control.label + local value_text = string.format(control.format, value) + + -- Calculate x position for right-aligned value + local value_x = x_value_right_align - (#value_text * char_width) + + if i == ConfigurationWindow.selected_control then + color = Config.colors.item + print("<", x_start -8, current_y, color) + print(label_text, x_start, current_y, color) -- Shift label due to '<' + print(value_text, value_x, current_y, color) + print(">", x_value_right_align + 4, current_y, color) -- Print '>' after value + else + print(label_text, x_start, current_y, color) + print(value_text, value_x, current_y, color) + end + elseif control.type == "action_item" then + local label_text = control.label + if i == ConfigurationWindow.selected_control then + color = Config.colors.item + print("<", x_start -8, current_y, color) + print(label_text, x_start, current_y, color) + print(">", x_start + 8 + (#label_text * char_width) + 4, current_y, color) + else + print(label_text, x_start, current_y, color) + end end end @@ -56,13 +76,10 @@ end function ConfigurationWindow.update() if Input.menu_back() then - -- I need to find out how to switch back to the menu - -- For now, I'll assume a function GameWindow.set_state exists GameWindow.set_state(WINDOW_MENU) return end - -- Navigate between controls if Input.up() then ConfigurationWindow.selected_control = ConfigurationWindow.selected_control - 1 if ConfigurationWindow.selected_control < 1 then @@ -75,16 +92,21 @@ function ConfigurationWindow.update() end end - -- Modify control value local control = ConfigurationWindow.controls[ConfigurationWindow.selected_control] if control then - local current_value = control.get() - if Input.left() then - local new_value = math.max(control.min, current_value - control.step) - control.set(new_value) - elseif Input.right() then - local new_value = math.min(control.max, current_value + control.step) - control.set(new_value) + if control.type == "numeric_stepper" then + local current_value = control.get() + if btnp(2) then -- Left + local new_value = math.max(control.min, current_value - control.step) + control.set(new_value) + elseif btnp(3) then -- Right + local new_value = math.min(control.max, current_value + control.step) + control.set(new_value) + end + elseif control.type == "action_item" then + if Input.menu_confirm() then + control.action() + end end end end diff --git a/inc/window/window.game.lua b/inc/window/window.game.lua index 651bd05..b7c1a71 100644 --- a/inc/window/window.game.lua +++ b/inc/window/window.game.lua @@ -26,6 +26,11 @@ function GameWindow.draw() end function GameWindow.update() + if Input.menu_back() then + Context.active_window = WINDOW_MENU + MenuWindow.refresh_menu_items() + return + end Player.update() -- Call the encapsulated player update logic end diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index 07c0bd8..18e8d22 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -14,17 +14,20 @@ function MenuWindow.update() end end -function MenuWindow.play() - -- Reset player state and screen for a new 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 +function MenuWindow.new_game() + Context.new_game() -- This function will be created in Context GameWindow.set_state(WINDOW_GAME) end +function MenuWindow.load_game() + Context.load_game() -- This function will be created in Context + GameWindow.set_state(WINDOW_GAME) +end + +function MenuWindow.save_game() + Context.save_game() -- This function will be created in Context +end + function MenuWindow.exit() exit() end @@ -34,9 +37,19 @@ function MenuWindow.configuration() GameWindow.set_state(WINDOW_CONFIGURATION) end --- Initialize menu items after actions are defined -Context.menu_items = { - {label = "Play", action = MenuWindow.play}, - {label = "Configuration", action = MenuWindow.configuration}, - {label = "Exit", action = MenuWindow.exit} -} +function MenuWindow.refresh_menu_items() + Context.menu_items = { + {label = "New Game", action = MenuWindow.new_game}, + {label = "Load Game", action = MenuWindow.load_game}, + } + + if Context.game_in_progress then + table.insert(Context.menu_items, {label = "Save Game", action = MenuWindow.save_game}) + end + + table.insert(Context.menu_items, {label = "Configuration", action = MenuWindow.configuration}) + table.insert(Context.menu_items, {label = "Exit", action = MenuWindow.exit}) + Context.selected_menu_item = 1 -- Reset selection after refreshing +end + +