diff --git a/saver/properties_panel/property_saver_slots.gui b/saver/properties_panel/property_saver_slots.gui new file mode 100644 index 0000000..f8316dc --- /dev/null +++ b/saver/properties_panel/property_saver_slots.gui @@ -0,0 +1,313 @@ +fonts { + name: "druid_text_bold" + font: "/druid/fonts/druid_text_bold.font" +} +textures { + name: "druid" + texture: "/druid/druid.atlas" +} +nodes { + size { + x: 400.0 + y: 40.0 + } + type: TYPE_BOX + texture: "druid/empty" + id: "root" + adjust_mode: ADJUST_MODE_STRETCH + inherit_alpha: true + visible: false +} +nodes { + position { + x: -200.0 + } + scale { + x: 0.5 + y: 0.5 + } + size { + x: 350.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_TEXT + text: "Slots" + font: "druid_text_bold" + id: "text_name" + pivot: PIVOT_W + outline { + x: 1.0 + y: 1.0 + z: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + } + adjust_mode: ADJUST_MODE_STRETCH + parent: "root" + inherit_alpha: true + outline_alpha: 0.0 + shadow_alpha: 0.0 +} +nodes { + position { + x: 200.0 + } + size { + x: 200.0 + y: 40.0 + } + type: TYPE_BOX + id: "E_Anchor" + pivot: PIVOT_E + parent: "root" + inherit_alpha: true + size_mode: SIZE_MODE_AUTO + visible: false +} +nodes { + position { + x: -170.0 + } + size { + x: 60.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_BOX + texture: "druid/rect_round2_width2" + id: "button_1" + parent: "E_Anchor" + inherit_alpha: true + slice9 { + x: 5.0 + y: 5.0 + z: 5.0 + w: 5.0 + } +} +nodes { + position { + y: -20.0 + } + size { + x: 60.0 + y: 4.0 + } + color { + x: 0.894 + y: 0.506 + z: 0.333 + } + type: TYPE_BOX + texture: "druid/pixel" + id: "selected_1" + pivot: PIVOT_S + adjust_mode: ADJUST_MODE_STRETCH + parent: "button_1" + inherit_alpha: true +} +nodes { + scale { + x: 0.5 + y: 0.5 + } + size { + x: 90.0 + y: 50.0 + } + color { + x: 0.722 + y: 0.741 + z: 0.761 + } + type: TYPE_TEXT + text: "1" + font: "druid_text_bold" + id: "text_button_1" + outline { + x: 1.0 + y: 1.0 + z: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + } + parent: "button_1" + inherit_alpha: true + outline_alpha: 0.0 + shadow_alpha: 0.0 +} +nodes { + position { + x: -100.0 + } + size { + x: 60.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_BOX + texture: "druid/rect_round2_width2" + id: "button_2" + parent: "E_Anchor" + inherit_alpha: true + slice9 { + x: 5.0 + y: 5.0 + z: 5.0 + w: 5.0 + } +} +nodes { + position { + y: -20.0 + } + size { + x: 60.0 + y: 4.0 + } + color { + x: 0.894 + y: 0.506 + z: 0.333 + } + type: TYPE_BOX + texture: "druid/pixel" + id: "selected_2" + pivot: PIVOT_S + adjust_mode: ADJUST_MODE_STRETCH + parent: "button_2" + inherit_alpha: true +} +nodes { + scale { + x: 0.5 + y: 0.5 + } + size { + x: 90.0 + y: 50.0 + } + color { + x: 0.722 + y: 0.741 + z: 0.761 + } + type: TYPE_TEXT + text: "2" + font: "druid_text_bold" + id: "text_button_2" + outline { + x: 1.0 + y: 1.0 + z: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + } + parent: "button_2" + inherit_alpha: true + outline_alpha: 0.0 + shadow_alpha: 0.0 +} +nodes { + position { + x: -30.0 + } + size { + x: 60.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_BOX + texture: "druid/rect_round2_width2" + id: "button_3" + parent: "E_Anchor" + inherit_alpha: true + slice9 { + x: 5.0 + y: 5.0 + z: 5.0 + w: 5.0 + } +} +nodes { + position { + y: -20.0 + } + size { + x: 60.0 + y: 4.0 + } + color { + x: 0.894 + y: 0.506 + z: 0.333 + } + type: TYPE_BOX + texture: "druid/pixel" + id: "selected_3" + pivot: PIVOT_S + adjust_mode: ADJUST_MODE_STRETCH + parent: "button_3" + inherit_alpha: true +} +nodes { + scale { + x: 0.5 + y: 0.5 + } + size { + x: 90.0 + y: 50.0 + } + color { + x: 0.722 + y: 0.741 + z: 0.761 + } + type: TYPE_TEXT + text: "3" + font: "druid_text_bold" + id: "text_button_3" + outline { + x: 1.0 + y: 1.0 + z: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + } + parent: "button_3" + inherit_alpha: true + outline_alpha: 0.0 + shadow_alpha: 0.0 +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/saver/properties_panel/property_saver_slots.lua b/saver/properties_panel/property_saver_slots.lua new file mode 100644 index 0000000..92783a8 --- /dev/null +++ b/saver/properties_panel/property_saver_slots.lua @@ -0,0 +1,59 @@ +---@class druid.widget.property_three_buttons: druid.widget +---@field root node +---@field container druid.container +---@field text_name druid.text +---@field button_1 druid.button +---@field text_button_1 druid.text +---@field button_2 druid.button +---@field text_button_2 druid.text +---@field button_3 druid.button +---@field text_button_3 druid.text +local M = {} + + +function M:init() + self.root = self:get_node("root") + self.text_name = self.druid:new_text("text_name") + :set_text_adjust("scale_then_trim", 0.3) + + self.selected_nodes = { + self:get_node("selected_1"), + self:get_node("selected_2"), + self:get_node("selected_3"), + } + gui.set_alpha(self.selected_nodes[1], 0) + gui.set_alpha(self.selected_nodes[2], 0) + gui.set_alpha(self.selected_nodes[3], 0) + + self.button_1 = self.druid:new_button("button_1", self.on_click, 1) + self.text_button_1 = self.druid:new_text("text_button_1") + + self.button_2 = self.druid:new_button("button_2", self.on_click, 2) + self.text_button_2 = self.druid:new_text("text_button_2") + + self.button_3 = self.druid:new_button("button_3", self.on_click, 3) + self.text_button_3 = self.druid:new_text("text_button_3") + + self.container = self.druid:new_container(self.root) + self.container:add_container("text_name", nil, function(_, size) + self.text_name:set_size(size) + end) + self.container:add_container("E_Anchor") +end + + +function M:on_click(index) + gui.set_alpha(self.selected_nodes[index], 1) + gui.animate(self.selected_nodes[index], "color.w", 0, gui.EASING_INSINE, 0.16) +end + + +---@param text string +---@return druid.widget.property_three_buttons +function M:set_text_property(text) + self.text_name:set_text(text) + return self +end + + +return M diff --git a/saver/saver.lua b/saver/saver.lua index b1694ad..9a50b89 100644 --- a/saver/saver.lua +++ b/saver/saver.lua @@ -100,7 +100,7 @@ function M.init(config) M.check_game_version() M.set_autosave_timer(DEFAULT_AUTOSAVE_TIMER) - saver_internal.logger:info("Save loaded", M.get_game_state()[SAVER_KEY]) + saver_internal.logger:debug("Saver initialized") end @@ -369,7 +369,8 @@ function M.save_file_by_name(data, filename, format) return false end - return M.save_file_by_path(data, M.get_save_path(filename), format) + local path = M.get_save_path(filename) + return M.save_file_by_path(data, path, format) end @@ -421,25 +422,26 @@ end ---Returns the absolute path to the game save folder. If a file name is provided, the path to the file in the game save folder is returned. Filename supports subfolders. +--- local folder_path = saver.get_save_path() +--- print(folder_path) -- "/Users/user/Library/Application Support/Defold Saver/" +--- --- local file_path = saver.get_save_path("data.json") --- print(file_path) -- "/Users/user/Library/Application Support/Defold Saver/data.json" --- --- local file_path_2 = saver.get_save_path("profiles/profile1.json") --- print(file_path_2) -- "/Users/user/Library/Application Support/Defold Saver/profiles/profile1.json" ----@param filename string The name of the file to get the path for. Can contain subfolders. +---@param filename string|nil The name of the file to get the path for. Can contain subfolders. If nil, returns the folder path. ---@return string path The absolute path to the game save folder, or the path to the file in the game save folder if a file name is provided. function M.get_save_path(filename) - assert(filename, "Can't get save path without filename") - - -- If filename contains "/" extract subfolder to the dir_name - local directory_path = DIRECTORY_PATH - if filename:find("/") then - local splitted = saver_internal.split(filename, "/") - filename = splitted[#splitted] - directory_path = directory_path .. "/" .. table.concat(splitted, "/", 1, #splitted - 1) + -- If no filename provided, return just the folder path + if not filename then + local directory_path = DIRECTORY_PATH + return sys.get_save_file(directory_path, "") end - return sys.get_save_file(directory_path, filename) + filename = filename:gsub("/", "_") + + return sys.get_save_file(DIRECTORY_PATH, filename) end @@ -463,6 +465,13 @@ function M.set_autosave_timer(timer) end +---Returns the current autosave timer. +---@return number timer The current autosave timer. +function M.get_autosave_timer() + return AUTOSAVE_TIMER +end + + ---@private ---Autosave timer callback function M.on_autosave_timer() diff --git a/saver/saver_debug_page.lua b/saver/saver_debug_page.lua new file mode 100644 index 0000000..72c8c3e --- /dev/null +++ b/saver/saver_debug_page.lua @@ -0,0 +1,149 @@ +local saver = require("saver.saver") +local properties_saver_slots = require("saver.properties_panel.property_saver_slots") + +local M = {} + + +---@param druid druid.instance +---@param properties_panel druid.widget.properties_panel +function M.render_properties_panel(druid, properties_panel) + properties_panel:next_scene() + properties_panel:set_header("Saver Panel") + + properties_panel:add_button(function(button) + button:set_text_property("Save Game") + button:set_text_button("Save") + button.button.on_click:subscribe(function() + saver.save_game_state() + end) + end) + + properties_panel:add_button(function(button) + button:set_text_property("Save Folder") + button:set_text_button("Open") + button:set_color("#6FA4DC") + button.button.on_click:subscribe(function() + M.open_at_desktop(saver.get_save_path()) + end) + end) + + properties_panel:add_button(function(button) + button:set_text_property("Inspect State") + button:set_text_button("Inspect") + button.button.on_click:subscribe(function() + properties_panel:next_scene() + properties_panel:set_header("Game State") + properties_panel:render_lua_table(saver.get_game_state()) + end) + end) + + properties_panel:add_widget(function() + local three_buttons = druid:new_widget(properties_saver_slots, "property_saver_slots", "root") + three_buttons:set_text_property("Save Slot") + + three_buttons.button_1.on_click:subscribe(function() + saver.save_game_state("saver_slot_1") + end) + three_buttons.button_2.on_click:subscribe(function() + saver.save_game_state("saver_slot_2") + end) + three_buttons.button_3.on_click:subscribe(function() + saver.save_game_state("saver_slot_3") + end) + + return three_buttons + end) + + properties_panel:add_widget(function() + local three_buttons = druid:new_widget(properties_saver_slots, "property_saver_slots", "root") + three_buttons:set_text_property("Load Slot") + + three_buttons.button_1.on_click:subscribe(function() + saver.load_game_state("saver_slot_1") + sys.reboot("--config=saver.save_name=saver_slot_1", "--config=saver.autosave_timer=0") + end) + three_buttons.button_2.on_click:subscribe(function() + saver.load_game_state("saver_slot_2") + sys.reboot("--config=saver.save_name=saver_slot_2", "--config=saver.autosave_timer=0") + end) + three_buttons.button_3.on_click:subscribe(function() + saver.load_game_state("saver_slot_3") + sys.reboot("--config=saver.save_name=saver_slot_3", "--config=saver.autosave_timer=0") + end) + + return three_buttons + end) + + properties_panel:add_widget(function() + local three_buttons = druid:new_widget(properties_saver_slots, "property_saver_slots", "root") + three_buttons:set_text_property("Delete Slot") + + three_buttons.button_1.on_click:subscribe(function() + print("You pressed delete button. Hold to confirm.") + end) + three_buttons.button_1.on_long_click:subscribe(function() + saver.delete_game_state("saver_slot_1") + end) + + three_buttons.button_2.on_click:subscribe(function() + print("You pressed delete button. Hold to confirm.") + end) + three_buttons.button_2.on_long_click:subscribe(function() + saver.delete_game_state("saver_slot_2") + end) + + three_buttons.button_3.on_click:subscribe(function() + print("You pressed delete button. Hold to confirm.") + end) + three_buttons.button_3.on_long_click:subscribe(function() + saver.delete_game_state("saver_slot_3") + end) + + return three_buttons + end) + + properties_panel:add_button(function(button) + button:set_text_property("pprint") + button:set_text_button("Game State") + button.button.on_click:subscribe(function() + pprint(saver.get_game_state()) + end) + end) + + properties_panel:add_input(function(input) + input:set_text_property("Autosave") + input.on_change_value:subscribe(function(value) + value = tonumber(value) or 0 + print("Autosave timer: " .. value) + saver.set_autosave_timer(value) + end) + input:set_text_value(saver.get_autosave_timer()) + end) + + properties_panel:add_text(function(text) + text:set_text_property("Version") + text:set_text_value(tostring(saver.get_save_version())) + end) +end + + +---@param path string +---@return boolean +function M.open_at_desktop(path) + local system = sys.get_sys_info().system_name + if system == "Windows" then + os.execute(string.format('explorer /select,"%s"', path)) + return true + elseif system == "Linux" then + os.execute(string.format("xdg-open %q", path)) + return true + elseif system == "Darwin" then + os.execute(string.format("open -R %q", path)) + return true + end + + return false +end + + +return M diff --git a/saver/saver_internal.lua b/saver/saver_internal.lua index bccfb88..2d45727 100644 --- a/saver/saver_internal.lua +++ b/saver/saver_internal.lua @@ -112,7 +112,7 @@ end ---@param filepath string The file path ---@return boolean success Whether the save was successful function M.save_json(data, filepath) - local file = io.open(filepath, "w+") + local file = io.open(filepath, "wb") if not file then M.logger:error("Can't open file for writing", filepath) return false @@ -157,7 +157,7 @@ end ---@param filepath string The file path ---@return boolean success Whether the save was successful function M.save_lua(data, filepath) - local file = io.open(filepath, "w+") + local file = io.open(filepath, "wb") if not file then M.logger:error("Can't open file for writing", filepath) return false @@ -188,7 +188,7 @@ function M.load_lua(filepath) return nil end - if LUA_REQUIRE_AS_STRING then + if M.LUA_REQUIRE_AS_STRING then -- Replace all require("some.path") to "/some/path.lua" file_data = file_data:gsub('require%("([^"]+)"%)', function(path) return string.format('"/%s"', path:gsub("%.", "/") .. ".lua") @@ -446,7 +446,7 @@ function M.table_to_lua_string(tbl, indent, is_array) local v_is_array = #v > 0 result = result .. M.table_to_lua_string(v, indent .. " ", v_is_array) elseif type(v) == "string" then - local is_require = LUA_REQUIRE_AS_STRING and v:sub(-1) == ')' and v:sub(1, 8) == 'require(' + local is_require = M.LUA_REQUIRE_AS_STRING and v:sub(-1) == ')' and v:sub(1, 8) == 'require(' if is_require then result = result .. v else