From 49d2164b6a4e71b73d158d9b4433e9848a2acced Mon Sep 17 00:00:00 2001 From: Dregu Date: Wed, 7 Feb 2024 02:19:01 +0200 Subject: [PATCH 01/11] arbitrary savestates testing --- examples/savestate.lua | 50 ++++++++++++++++++++++++++++++++ src/game_api/rpc.cpp | 52 ++++++++++++++++++++++++++++++++-- src/game_api/rpc.hpp | 17 ++++++++++- src/game_api/script/lua_vm.cpp | 6 ++-- src/injected/ui_util.cpp | 2 +- 5 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 examples/savestate.lua diff --git a/examples/savestate.lua b/examples/savestate.lua new file mode 100644 index 000000000..883cbe792 --- /dev/null +++ b/examples/savestate.lua @@ -0,0 +1,50 @@ +meta.name = "SaveState example" +meta.author = "Dregu" +meta.version = "1.0" + +states = {} +do_load = nil +do_save = nil + +function clear_states() + for _, v in pairs(states) do + v:clear() + end + states = {} +end + +set_callback(function(ctx) + ctx:window("Advanced SaveStates", 0, 0, 0, 0, true, function(ctx) + if ctx:win_button("New save") then + do_save = true + end + ctx:win_inline() + if ctx:win_button("Clear saves") then + clear_states() + end + ctx:win_separator() + for i, v in pairs(states) do + if ctx:win_button(F "Load #{i}: frame {v:get().time_level}") then + do_load = v + end + end + end) +end, ON.GUIFRAME) + +set_callback(function() + if #states == 0 then + states[1] = SaveState:new() + end +end, ON.LEVEL) + +set_callback(function() + if do_load then + do_load:load() + do_load = nil + elseif do_save then + states[#states + 1] = SaveState:new() + do_save = nil + end +end, ON.POST_UPDATE) + +set_callback(clear_states, ON.PRE_LEVEL_DESTRUCTION) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index e365604a3..204ee49b9 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1905,12 +1905,17 @@ void init_seeded(std::optional seed) isf(state, seed.value_or(state->seed)); } -void copy_state(int from, int to) +void copy_save_slot(int from, int to) { size_t arr = get_address("save_states"); - size_t iterIdx = 1; size_t fromBaseState = memory_read(arr + (from - 1) * 8); size_t toBaseState = memory_read(arr + (to - 1) * 8); + copy_state(fromBaseState, toBaseState); +}; + +void copy_state(size_t fromBaseState, size_t toBaseState) +{ + size_t iterIdx = 1; do { size_t copyContent = *(size_t*)((fromBaseState - 8) + iterIdx * 8); @@ -1957,3 +1962,46 @@ void invalidate_save_states() state->screen = 0; } } + +SaveState::SaveState() +{ + size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; + addr = (size_t)malloc(8 * 0x400000); + copy_state(from, addr); +} + +SaveState::~SaveState() +{ + clear(); +} + +StateMemory* SaveState::get() +{ + if (!addr) + return nullptr; + return reinterpret_cast(addr + 0x4a0); +} + +void SaveState::load() +{ + if (!addr) + return; + size_t to = (size_t)(State::get().ptr_main()) - 0x4a0; + copy_state(addr, to); +} + +void SaveState::save() +{ + if (!addr) + return; + size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; + copy_state(from, addr); +} + +void SaveState::clear() +{ + if (!addr) + return; + free((void*)addr); + addr = 0; +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index dbb8abfd7..c2dde17ea 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -19,6 +19,20 @@ struct AABB; struct Layer; struct StateMemory; +class SaveState +{ + public: + SaveState(); + ~SaveState(); + StateMemory* get(); + void load(); + void save(); + void clear(); + + private: + size_t addr; +}; + void attach_entity(Entity* overlay, Entity* attachee); void attach_entity_by_uid(uint32_t overlay_uid, uint32_t attachee_uid); int32_t attach_ball_and_chain(uint32_t uid, float off_x, float off_y); @@ -137,6 +151,7 @@ void set_speedhack(std::optional multiplier); float get_speedhack(); void init_adventure(); void init_seeded(std::optional seed); -void copy_state(int from, int to); +void copy_save_slot(int from, int to); +void copy_state(size_t fromBaseState, size_t toBaseState); StateMemory* get_save_state(int slot); void invalidate_save_states(); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index f14ce496e..ffcac5a19 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2253,14 +2253,14 @@ end lua["save_state"] = [](int slot) { if (slot >= 1 && slot <= 4) - copy_state(5, slot); + copy_save_slot(5, slot); }; /// Load level state from slot 1..4, if a save_state was made in this level. lua["load_state"] = [](int slot) { if (slot >= 1 && slot <= 4 && get_save_state(slot)) - copy_state(slot, 5); + copy_save_slot(slot, 5); }; /// Get StateMemory from a save_state slot. @@ -2271,6 +2271,8 @@ end return nullptr; }; + lua.new_usertype("SaveState", sol::constructors(), "load", &SaveState::load, "save", &SaveState::save, "clear", &SaveState::clear, "get", &SaveState::get); + lua.create_named_table("INPUTS", "NONE", 0x0, "JUMP", 0x1, "WHIP", 0x2, "BOMB", 0x4, "ROPE", 0x8, "RUN", 0x10, "DOOR", 0x20, "MENU", 0x40, "JOURNAL", 0x80, "LEFT", 0x100, "RIGHT", 0x200, "UP", 0x400, "DOWN", 0x800); lua.create_named_table("MENU_INPUT", "NONE", 0x0, "SELECT", 0x1, "BACK", 0x2, "DELETE", 0x4, "RANDOM", 0x8, "JOURNAL", 0x10, "LEFT", 0x20, "RIGHT", 0x40, "UP", 0x80, "DOWN", 0x100); diff --git a/src/injected/ui_util.cpp b/src/injected/ui_util.cpp index c24df6699..fcab791d2 100644 --- a/src/injected/ui_util.cpp +++ b/src/injected/ui_util.cpp @@ -831,7 +831,7 @@ void UI::set_adventure_seed(int64_t first, int64_t second) void UI::copy_state(int from, int to) { - ::copy_state(from, to); + ::copy_save_slot(from, to); } StateMemory* UI::get_save_state(int slot) From 740489ce314f7fef2d3c154ffa88de4ce18e35f5 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 8 Feb 2024 19:24:49 +0200 Subject: [PATCH 02/11] docs --- docs/game_data/spel2.lua | 13 +++- docs/parse_source.py | 3 +- docs/src/includes/_globals.md | 2 +- docs/src/includes/_types.md | 11 ++++ src/game_api/rpc.cpp | 101 ------------------------------- src/game_api/rpc.hpp | 18 ------ src/game_api/savestate.cpp | 107 +++++++++++++++++++++++++++++++++ src/game_api/savestate.hpp | 31 ++++++++++ src/game_api/script/events.cpp | 3 +- src/game_api/script/lua_vm.cpp | 5 +- src/injected/ui_util.cpp | 1 + 11 files changed, 170 insertions(+), 125 deletions(-) create mode 100644 src/game_api/savestate.cpp create mode 100644 src/game_api/savestate.hpp diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 120d2f8d0..30338e50e 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1378,7 +1378,7 @@ function play_adventure() end ---@param seed integer? ---@return nil function play_seeded(seed) end ----Save current level state to slot 1..4. These save states are invalid after you exit the level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). +---Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. ---@param slot integer ---@return nil function save_state(slot) end @@ -1877,6 +1877,12 @@ do ---@class Players +---@class SaveState + ---@field load fun(self): nil @Load a SaveState + ---@field save fun(self): nil @Save over a previously allocated SaveState + ---@field clear fun(self): nil @Delete the SaveState and free the memory. The SaveState can't be used after this. + ---@field get_state fun(self): StateMemory @Access the StateMemory inside a SaveState + ---@class SaveContext ---@field save fun(self, data: string): boolean @@ -6471,6 +6477,11 @@ function Color:fuchsia() end function Color:purple() end --## Constructors + +SaveState = nil +---Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. +---@return SaveState +function SaveState:new() end ---Create a new color - defaults to black ---@return Color function Color:new() end diff --git a/docs/parse_source.py b/docs/parse_source.py index 1e08a1b3d..d99891d15 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -117,6 +117,7 @@ "../src/game_api/search.hpp", "../src/game_api/bucket.hpp", "../src/game_api/socket.hpp", + "../src/game_api/savestate.hpp", ] api_files = [ "../src/game_api/script/script_impl.cpp", @@ -685,7 +686,7 @@ def run_parse(): var_name = var[0] cpp = var[1] - + if var[1].startswith("sol::property"): param_match = re.match( rf"sol::property\(\[\]\({underlying_cpp_type['name']}&(\w+)\)", diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 272fc1266..00ad01c74 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -1787,7 +1787,7 @@ Runs the [ON](#ON).SAVE callback. Fails and returns false, if you're trying to s #### nil save_state(int slot) -Save current level state to slot 1..4. These save states are invalid after you exit the level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). +Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see [SaveState](#SaveState) if you need more. ### script_enabled diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index 6f9b0877e..f4d90eb8b 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -956,6 +956,17 @@ Type | Name | Description map<int, [ItemOwnerDetails](#ItemOwnerDetails)> | [owned_items](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=owned_items) | key/index is the uid of an item vector<[RoomOwnerDetails](#RoomOwnerDetails)> | [owned_rooms](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=owned_rooms) | +### SaveState + + +Type | Name | Description +---- | ---- | ----------- +[SaveState](#SaveState) | [new()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=SaveState) | Create a new temporary [SaveState](#SaveState)/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. +nil | [load()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=load) | Load a [SaveState](#SaveState) +nil | [save()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=save) | Save over a previously allocated [SaveState](#SaveState) +nil | [clear()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear) | Delete the [SaveState](#SaveState) and free the memory. The [SaveState](#SaveState) can't be used after this. +[StateMemory](#StateMemory) | [get_state()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_state) | Access the [StateMemory](#StateMemory) inside a [SaveState](#SaveState) + ### ShortTileCodeDef Used in [get_short_tile_code](#get_short_tile_code), [get_short_tile_code_definition](#get_short_tile_code_definition) and [PostRoomGenerationContext](#PostRoomGenerationContext) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 204ee49b9..a54751d14 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1904,104 +1904,3 @@ void init_seeded(std::optional seed) auto* state = State::get().ptr(); isf(state, seed.value_or(state->seed)); } - -void copy_save_slot(int from, int to) -{ - size_t arr = get_address("save_states"); - size_t fromBaseState = memory_read(arr + (from - 1) * 8); - size_t toBaseState = memory_read(arr + (to - 1) * 8); - copy_state(fromBaseState, toBaseState); -}; - -void copy_state(size_t fromBaseState, size_t toBaseState) -{ - size_t iterIdx = 1; - do - { - size_t copyContent = *(size_t*)((fromBaseState - 8) + iterIdx * 8); - // variable used to fix pointers that point somewhere in the same Thread - size_t diff = toBaseState - fromBaseState; - if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) - { - diff = 0; - } - *(size_t*)(toBaseState + iterIdx * 8 + -8) = diff + copyContent; - - // Almost same code as before, but on the next value, idk why - copyContent = *(size_t*)(fromBaseState + iterIdx * 8); - diff = toBaseState - fromBaseState; - if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) - { - diff = 0; - } - *(size_t*)(toBaseState + iterIdx * 8) = diff + copyContent; - - iterIdx = iterIdx + 2; - } while (iterIdx != 0x400001); -}; - -StateMemory* get_save_state(int slot) -{ - size_t arr = get_address("save_states"); - size_t base = memory_read(arr + (slot - 1) * 8); - auto state = reinterpret_cast(base + 0x4a0); - if (state->screen) - return state; - return nullptr; -} - -void invalidate_save_states() -{ - auto online = get_online(); - if (online->lobby.code != 0) - return; - for (int i = 1; i <= 4; ++i) - { - auto state = get_save_state(i); - if (state) - state->screen = 0; - } -} - -SaveState::SaveState() -{ - size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; - addr = (size_t)malloc(8 * 0x400000); - copy_state(from, addr); -} - -SaveState::~SaveState() -{ - clear(); -} - -StateMemory* SaveState::get() -{ - if (!addr) - return nullptr; - return reinterpret_cast(addr + 0x4a0); -} - -void SaveState::load() -{ - if (!addr) - return; - size_t to = (size_t)(State::get().ptr_main()) - 0x4a0; - copy_state(addr, to); -} - -void SaveState::save() -{ - if (!addr) - return; - size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; - copy_state(from, addr); -} - -void SaveState::clear() -{ - if (!addr) - return; - free((void*)addr); - addr = 0; -} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index c2dde17ea..d901f3420 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -19,20 +19,6 @@ struct AABB; struct Layer; struct StateMemory; -class SaveState -{ - public: - SaveState(); - ~SaveState(); - StateMemory* get(); - void load(); - void save(); - void clear(); - - private: - size_t addr; -}; - void attach_entity(Entity* overlay, Entity* attachee); void attach_entity_by_uid(uint32_t overlay_uid, uint32_t attachee_uid); int32_t attach_ball_and_chain(uint32_t uid, float off_x, float off_y); @@ -151,7 +137,3 @@ void set_speedhack(std::optional multiplier); float get_speedhack(); void init_adventure(); void init_seeded(std::optional seed); -void copy_save_slot(int from, int to); -void copy_state(size_t fromBaseState, size_t toBaseState); -StateMemory* get_save_state(int slot); -void invalidate_save_states(); diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp new file mode 100644 index 000000000..bd0356823 --- /dev/null +++ b/src/game_api/savestate.cpp @@ -0,0 +1,107 @@ +#include "savestate.hpp" + +#include "memory.hpp" // for write_mem_prot, write_mem_recoverable +#include "online.hpp" // for Online +#include "state.hpp" // for State, get_state_ptr, enum_to_layer + +void copy_save_slot(int from, int to) +{ + size_t arr = get_address("save_states"); + size_t fromBaseState = memory_read(arr + (from - 1) * 8); + size_t toBaseState = memory_read(arr + (to - 1) * 8); + copy_state(fromBaseState, toBaseState); +}; + +void copy_state(size_t fromBaseState, size_t toBaseState) +{ + size_t iterIdx = 1; + do + { + size_t copyContent = *(size_t*)((fromBaseState - 8) + iterIdx * 8); + // variable used to fix pointers that point somewhere in the same Thread + size_t diff = toBaseState - fromBaseState; + if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) + { + diff = 0; + } + *(size_t*)(toBaseState + iterIdx * 8 + -8) = diff + copyContent; + + // Almost same code as before, but on the next value, idk why + copyContent = *(size_t*)(fromBaseState + iterIdx * 8); + diff = toBaseState - fromBaseState; + if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) + { + diff = 0; + } + *(size_t*)(toBaseState + iterIdx * 8) = diff + copyContent; + + iterIdx = iterIdx + 2; + } while (iterIdx != 0x400001); +}; + +StateMemory* get_save_state(int slot) +{ + size_t arr = get_address("save_states"); + size_t base = memory_read(arr + (slot - 1) * 8); + auto state = reinterpret_cast(base + 0x4a0); + if (state->screen) + return state; + return nullptr; +} + +void invalidate_save_slots() +{ + auto online = get_online(); + if (online->lobby.code != 0) + return; + for (int i = 1; i <= 4; ++i) + { + auto state = get_save_state(i); + if (state) + state->screen = 0; + } +} + +SaveState::SaveState() +{ + size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; + addr = (size_t)malloc(8 * 0x400000); + if (addr) + copy_state(from, addr); +} + +SaveState::~SaveState() +{ + clear(); +} + +StateMemory* SaveState::get_state() +{ + if (!addr) + return nullptr; + return reinterpret_cast(addr + 0x4a0); +} + +void SaveState::load() +{ + if (!addr) + return; + size_t to = (size_t)(State::get().ptr_main()) - 0x4a0; + copy_state(addr, to); +} + +void SaveState::save() +{ + if (!addr) + return; + size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; + copy_state(from, addr); +} + +void SaveState::clear() +{ + if (!addr) + return; + free((void*)addr); + addr = 0; +} diff --git a/src/game_api/savestate.hpp b/src/game_api/savestate.hpp new file mode 100644 index 000000000..5a577ba29 --- /dev/null +++ b/src/game_api/savestate.hpp @@ -0,0 +1,31 @@ +#pragma once + +struct StateMemory; + +class SaveState +{ + public: + /// Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. + SaveState(); + ~SaveState(); + + /// Access the StateMemory inside a SaveState + StateMemory* get_state(); + + /// Load a SaveState + void load(); + + /// Save over a previously allocated SaveState + void save(); + + /// Delete the SaveState and free the memory. The SaveState can't be used after this. + void clear(); + + private: + size_t addr; +}; + +void copy_save_slot(int from, int to); +void copy_state(size_t fromBaseState, size_t toBaseState); +StateMemory* get_save_state(int slot); +void invalidate_save_slots(); diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index 1533a32a7..5cde76f1b 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -10,6 +10,7 @@ #include "constants.hpp" // for no_return_str #include "level_api_types.hpp" // for LevelGenRoomData #include "rpc.hpp" // for game_log, get_adventure_seed +#include "savestate.hpp" // for invalidate_save_slots #include "script/lua_backend.hpp" // for LuaBackend, ON, LuaBackend::PreHan... #include "settings_api.hpp" // for restore_original_settings #include "state.hpp" // for StateMemory, State @@ -80,7 +81,7 @@ bool pre_unload_level() if (!block) { g_level_loaded = false; - invalidate_save_states(); + invalidate_save_slots(); } return block; } diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index ffcac5a19..712ebd282 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -54,6 +54,7 @@ #include "rpc.hpp" // for get_entities_by #include "safe_cb.hpp" // for make_safe_clearable_cb #include "savedata.hpp" // IWYU pragma: keep +#include "savestate.hpp" // for SaveState #include "screen.hpp" // for get_screen_ptr #include "script.hpp" // for ScriptMessage #include "script_util.hpp" // for sanitize, get_say @@ -2249,7 +2250,7 @@ end /// Initializes some seedeed run related values and loads the character select screen, as if starting a new seeded run after entering the seed. lua["play_seeded"] = init_seeded; - /// Save current level state to slot 1..4. These save states are invalid after you exit the level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). + /// Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. lua["save_state"] = [](int slot) { if (slot >= 1 && slot <= 4) @@ -2271,7 +2272,7 @@ end return nullptr; }; - lua.new_usertype("SaveState", sol::constructors(), "load", &SaveState::load, "save", &SaveState::save, "clear", &SaveState::clear, "get", &SaveState::get); + lua.new_usertype("SaveState", sol::constructors(), "load", &SaveState::load, "save", &SaveState::save, "clear", &SaveState::clear, "get_state", &SaveState::get_state); lua.create_named_table("INPUTS", "NONE", 0x0, "JUMP", 0x1, "WHIP", 0x2, "BOMB", 0x4, "ROPE", 0x8, "RUN", 0x10, "DOOR", 0x20, "MENU", 0x40, "JOURNAL", 0x80, "LEFT", 0x100, "RIGHT", 0x200, "UP", 0x400, "DOWN", 0x800); diff --git a/src/injected/ui_util.cpp b/src/injected/ui_util.cpp index fcab791d2..4d41f66ba 100644 --- a/src/injected/ui_util.cpp +++ b/src/injected/ui_util.cpp @@ -24,6 +24,7 @@ #include "memory.hpp" // #include "render_api.hpp" // for RenderInfo #include "rpc.hpp" // for get_entities_at, entity_get_ite... +#include "savestate.hpp" // for copy_save_slot #include "search.hpp" // #include "spawn_api.hpp" // for spawn_liquid, spawn_companion #include "state.hpp" // for State, StateMemory From bad75dc6259620630cf42239a45a4d6a1c01c236 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 8 Feb 2024 19:46:17 +0200 Subject: [PATCH 03/11] update example --- examples/savestate.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/savestate.lua b/examples/savestate.lua index 883cbe792..a33cd66ce 100644 --- a/examples/savestate.lua +++ b/examples/savestate.lua @@ -24,7 +24,7 @@ set_callback(function(ctx) end ctx:win_separator() for i, v in pairs(states) do - if ctx:win_button(F "Load #{i}: frame {v:get().time_level}") then + if ctx:win_button(F "Load #{i}: frame {v:get_state().time_level}") then do_load = v end end From edc3d8f1e0f032c1154449345eb9d7ff2113cb23 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 8 Feb 2024 19:51:25 +0200 Subject: [PATCH 04/11] fix documentation --- docs/game_data/spel2.lua | 2 +- docs/src/includes/_types.md | 2 +- src/game_api/savestate.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 30338e50e..9ec8f4629 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -6479,7 +6479,7 @@ function Color:purple() end --## Constructors SaveState = nil ----Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. +---Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. The garbage collector will eventually clear the SaveStates you don't have a handle to any more though. ---@return SaveState function SaveState:new() end ---Create a new color - defaults to black diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index f4d90eb8b..f8134cf3e 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -961,7 +961,7 @@ vector<[RoomOwnerDetails](#RoomOwnerDetails)> | [owned_rooms](https://gith Type | Name | Description ---- | ---- | ----------- -[SaveState](#SaveState) | [new()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=SaveState) | Create a new temporary [SaveState](#SaveState)/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. +[SaveState](#SaveState) | [new()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=SaveState) | Create a new temporary [SaveState](#SaveState)/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. The garbage collector will eventually clear the SaveStates you don't have a handle to any more though. nil | [load()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=load) | Load a [SaveState](#SaveState) nil | [save()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=save) | Save over a previously allocated [SaveState](#SaveState) nil | [clear()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear) | Delete the [SaveState](#SaveState) and free the memory. The [SaveState](#SaveState) can't be used after this. diff --git a/src/game_api/savestate.hpp b/src/game_api/savestate.hpp index 5a577ba29..21880d3ca 100644 --- a/src/game_api/savestate.hpp +++ b/src/game_api/savestate.hpp @@ -5,7 +5,7 @@ struct StateMemory; class SaveState { public: - /// Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. + /// Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. The garbage collector will eventually clear the SaveStates you don't have a handle to any more though. SaveState(); ~SaveState(); From 17afc3501e3b6d4dbc257fb2d4ff954304e9e1a8 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 29 Feb 2024 08:13:17 +0200 Subject: [PATCH 05/11] save_state events and more examples --- examples/savestate.lua | 66 +++++++++++--- src/game_api/savestate.cpp | 26 ++++-- src/game_api/script/events.cpp | 42 +++++++++ src/game_api/script/events.hpp | 4 + src/game_api/script/lua_backend.cpp | 96 +++++++++++++++++++++ src/game_api/script/lua_backend.hpp | 8 ++ src/game_api/script/lua_vm.cpp | 33 ++----- src/game_api/script/usertypes/state_lua.cpp | 35 ++++++++ 8 files changed, 265 insertions(+), 45 deletions(-) diff --git a/examples/savestate.lua b/examples/savestate.lua index a33cd66ce..f7e7e724c 100644 --- a/examples/savestate.lua +++ b/examples/savestate.lua @@ -1,22 +1,24 @@ -meta.name = "SaveState example" +meta.name = "Teleport predictor and advanced SaveStates" meta.author = "Dregu" meta.version = "1.0" states = {} -do_load = nil -do_save = nil function clear_states() for _, v in pairs(states) do v:clear() end states = {} + if tp then + tp:clear() + tp = nil + end end set_callback(function(ctx) ctx:window("Advanced SaveStates", 0, 0, 0, 0, true, function(ctx) if ctx:win_button("New save") then - do_save = true + states[#states + 1] = SaveState:new() end ctx:win_inline() if ctx:win_button("Clear saves") then @@ -25,10 +27,36 @@ set_callback(function(ctx) ctx:win_separator() for i, v in pairs(states) do if ctx:win_button(F "Load #{i}: frame {v:get_state().time_level}") then - do_load = v + v:load() end end end) + + -- teleport predictor/simulator using save states + -- simulates accurate teleport destination when pressing Z or X while paused + if not players[1] then return end + if (get_io().keypressed(KEY.Z) or get_io().keypressed(KEY.X)) and pause:paused() then + if tp then + tp:save() + else + tp = SaveState:new() + end + box_frame = state.time_level + players[1]:set_post_update_state_machine(function(e) + clear_callback() + box = get_hitbox(e.uid) + box_color = 0xcc33ff33 + end) + set_callback(function() + clear_callback() + if not players[1] then box_color = 0xcc3333ff end + if tp then tp:load() end + end, ON.POST_UPDATE) + pause:frame_advance() + end + if box and box_frame == state.time_level then + ctx:draw_rect_filled(screen_aabb(box), 0, box_color) + end end, ON.GUIFRAME) set_callback(function() @@ -37,14 +65,24 @@ set_callback(function() end end, ON.LEVEL) -set_callback(function() - if do_load then - do_load:load() - do_load = nil - elseif do_save then - states[#states + 1] = SaveState:new() - do_save = nil +set_callback(clear_states, ON.PRE_LEVEL_DESTRUCTION) + +set_callback(function(slot, current, loading) + if slot > 0 then + print(F "Loading save slot {slot}...") + else + print("Loading custom save slot...") end -end, ON.POST_UPDATE) + rewind = current.time_level - loading.time_level + if rewind < 0 then + print(F "Not forwarding {-rewind} frames, that would break spacetime!") + return true + else + print(F "Rewinding {rewind} frames...") + end +end, ON.PRE_LOAD_STATE) -set_callback(clear_states, ON.PRE_LEVEL_DESTRUCTION) +set_callback(function(slot, current, loaded) + print(F "Rewinded {rewind} frames!") + rewind = nil +end, ON.POST_LOAD_STATE) diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index bd0356823..bdfea9651 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -1,15 +1,23 @@ #include "savestate.hpp" -#include "memory.hpp" // for write_mem_prot, write_mem_recoverable -#include "online.hpp" // for Online -#include "state.hpp" // for State, get_state_ptr, enum_to_layer +#include "memory.hpp" // for write_mem_prot, write_mem_recoverable +#include "online.hpp" // for Online +#include "script/events.hpp" // for pre_load_state +#include "state.hpp" // for State, get_state_ptr, enum_to_layer void copy_save_slot(int from, int to) { + if ((from == 5 && pre_save_state(to, get_save_state(to), get_save_state(from))) || + (to == 5 && pre_load_state(from, get_save_state(to), get_save_state(from)))) + return; size_t arr = get_address("save_states"); size_t fromBaseState = memory_read(arr + (from - 1) * 8); size_t toBaseState = memory_read(arr + (to - 1) * 8); copy_state(fromBaseState, toBaseState); + if (from == 5) + post_save_state(to, get_save_state(to), get_save_state(from)); + else if (to == 5) + post_load_state(from, get_save_state(to), get_save_state(from)); }; void copy_state(size_t fromBaseState, size_t toBaseState) @@ -64,10 +72,8 @@ void invalidate_save_slots() SaveState::SaveState() { - size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; addr = (size_t)malloc(8 * 0x400000); - if (addr) - copy_state(from, addr); + save(); } SaveState::~SaveState() @@ -87,7 +93,11 @@ void SaveState::load() if (!addr) return; size_t to = (size_t)(State::get().ptr_main()) - 0x4a0; + auto state = reinterpret_cast(addr + 0x4a0); + if (pre_load_state(-1, State::get().ptr_main(), state)) + return; copy_state(addr, to); + post_load_state(-1, State::get().ptr_main(), state); } void SaveState::save() @@ -95,7 +105,11 @@ void SaveState::save() if (!addr) return; size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; + auto state = reinterpret_cast(addr + 0x4a0); + if (pre_save_state(-1, state, State::get().ptr_main())) + return; copy_state(from, addr); + post_save_state(-1, state, State::get().ptr_main()); } void SaveState::clear() diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index 5cde76f1b..e12706738 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -121,6 +121,30 @@ bool pre_init_layer(LAYER layer) return block; } +bool pre_save_state(int slot, StateMemory* current, StateMemory* saved) +{ + bool block{false}; + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + block = backend->pre_save_state(slot, current, saved); + return !block; + }); + return block; +} + +bool pre_load_state(int slot, StateMemory* current, StateMemory* loaded) +{ + bool block{false}; + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + block = backend->pre_load_state(slot, current, loaded); + return !block; + }); + return block; +} + void post_room_generation() { LuaBackend::for_each_backend( @@ -166,6 +190,24 @@ void post_unload_layer(LAYER layer) return true; }); } +void post_save_state(int slot, StateMemory* current, StateMemory* saved) +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->post_save_state(slot, current, saved); + return true; + }); +} +void post_load_state(int slot, StateMemory* current, StateMemory* loaded) +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->post_load_state(slot, current, loaded); + return true; + }); +} void on_death_message(STRINGID stringid) { LuaBackend::for_each_backend( diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index 795a5ae25..38b4d4e55 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -24,12 +24,16 @@ bool pre_init_level(); bool pre_init_layer(LAYER layer); bool pre_unload_level(); bool pre_unload_layer(LAYER layer); +bool pre_save_state(int slot, StateMemory* current, StateMemory* saved); +bool pre_load_state(int slot, StateMemory* current, StateMemory* loaded); void post_load_screen(); void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); void post_room_generation(); void post_level_generation(); +void post_save_state(int slot, StateMemory* current, StateMemory* saved); +void post_load_state(int slot, StateMemory* current, StateMemory* loaded); void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index fd64ee738..4de752598 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -1814,3 +1814,99 @@ void LuaBackend::on_post(ON event) } } } + +bool LuaBackend::pre_save_state(int slot, StateMemory* current, StateMemory* saved) +{ + if (!get_enabled()) + return false; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::PRE_SAVE_STATE) + { + set_current_callback(-1, id, CallbackType::Normal); + auto return_value = handle_function(this, callback.func, slot, current, saved).value_or(false); + clear_current_callback(); + callback.lastRan = now; + if (return_value) + return return_value; + } + } + + return false; +} + +bool LuaBackend::pre_load_state(int slot, StateMemory* current, StateMemory* loaded) +{ + if (!get_enabled()) + return false; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::PRE_LOAD_STATE) + { + set_current_callback(-1, id, CallbackType::Normal); + auto return_value = handle_function(this, callback.func, slot, current, loaded).value_or(false); + clear_current_callback(); + callback.lastRan = now; + if (return_value) + return return_value; + } + } + + return false; +} + +void LuaBackend::post_save_state(int slot, StateMemory* current, StateMemory* saved) +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::POST_SAVE_STATE) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func, slot, current, saved); + clear_current_callback(); + callback.lastRan = now; + } + } +} + +void LuaBackend::post_load_state(int slot, StateMemory* current, StateMemory* loaded) +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::POST_LOAD_STATE) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func, slot, current, loaded); + clear_current_callback(); + callback.lastRan = now; + } + } +} diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index f3d5145e3..12aa3e1c2 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -132,6 +132,10 @@ enum class ON POST_PROCESS_INPUT, PRE_GAME_LOOP, POST_GAME_LOOP, + PRE_SAVE_STATE, + POST_SAVE_STATE, + PRE_LOAD_STATE, + POST_LOAD_STATE, BLOCKED_UPDATE, BLOCKED_GAME_LOOP, BLOCKED_PROCESS_INPUT, @@ -383,12 +387,16 @@ class LuaBackend bool pre_init_layer(LAYER layer); bool pre_unload_level(); bool pre_unload_layer(LAYER layer); + bool pre_save_state(int slot, StateMemory* current, StateMemory* saved); + bool pre_load_state(int slot, StateMemory* current, StateMemory* loaded); void post_room_generation(); void post_level_generation(); void post_load_screen(); void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); + void post_save_state(int slot, StateMemory* current, StateMemory* saved); + void post_load_state(int slot, StateMemory* current, StateMemory* loaded); void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 712ebd282..b545bb17c 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -54,7 +54,6 @@ #include "rpc.hpp" // for get_entities_by #include "safe_cb.hpp" // for make_safe_clearable_cb #include "savedata.hpp" // IWYU pragma: keep -#include "savestate.hpp" // for SaveState #include "screen.hpp" // for get_screen_ptr #include "script.hpp" // for ScriptMessage #include "script_util.hpp" // for sanitize, get_say @@ -2250,30 +2249,6 @@ end /// Initializes some seedeed run related values and loads the character select screen, as if starting a new seeded run after entering the seed. lua["play_seeded"] = init_seeded; - /// Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. - lua["save_state"] = [](int slot) - { - if (slot >= 1 && slot <= 4) - copy_save_slot(5, slot); - }; - - /// Load level state from slot 1..4, if a save_state was made in this level. - lua["load_state"] = [](int slot) - { - if (slot >= 1 && slot <= 4 && get_save_state(slot)) - copy_save_slot(slot, 5); - }; - - /// Get StateMemory from a save_state slot. - lua["get_save_state"] = [](int slot) -> StateMemory* - { - if (slot >= 1 && slot <= 5) - return get_save_state(slot); - return nullptr; - }; - - lua.new_usertype("SaveState", sol::constructors(), "load", &SaveState::load, "save", &SaveState::save, "clear", &SaveState::clear, "get_state", &SaveState::get_state); - lua.create_named_table("INPUTS", "NONE", 0x0, "JUMP", 0x1, "WHIP", 0x2, "BOMB", 0x4, "ROPE", 0x8, "RUN", 0x10, "DOOR", 0x20, "MENU", 0x40, "JOURNAL", 0x80, "LEFT", 0x100, "RIGHT", 0x200, "UP", 0x400, "DOWN", 0x800); lua.create_named_table("MENU_INPUT", "NONE", 0x0, "SELECT", 0x1, "BACK", 0x2, "DELETE", 0x4, "RANDOM", 0x8, "JOURNAL", 0x10, "LEFT", 0x20, "RIGHT", 0x40, "UP", 0x80, "DOWN", 0x100); @@ -2455,6 +2430,14 @@ end ON::PRE_GAME_LOOP, "POST_GAME_LOOP", ON::POST_GAME_LOOP, + "PRE_SAVE_STATE", + ON::PRE_SAVE_STATE, + "POST_SAVE_STATE", + ON::POST_SAVE_STATE, + "PRE_LOAD_STATE", + ON::PRE_LOAD_STATE, + "POST_LOAD_STATE", + ON::POST_LOAD_STATE, "BLOCKED_UPDATE", ON::BLOCKED_UPDATE, "BLOCKED_GAME_LOOP", diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index e6fb19d85..1b4a5615a 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -18,8 +18,10 @@ #include "items.hpp" // for Items, SelectPlayerSlot, Items::is... #include "level_api.hpp" // IWYU pragma: keep #include "online.hpp" // for OnlinePlayer, OnlineLobby, Online +#include "savestate.hpp" // for SaveState #include "screen.hpp" // IWYU pragma: keep #include "screen_arena.hpp" // IWYU pragma: keep +#include "script/events.hpp" // for pre_load_state #include "state.hpp" // for StateMemory, State, StateMemory::a... #include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems @@ -554,5 +556,38 @@ void register_usertypes(sol::state& lua) { State::get().ptr()->speechbubble_timer = 1000; }; + + /// Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. + lua["save_state"] = [](int slot) + { + if (slot >= 1 && slot <= 4) + { + copy_save_slot(5, slot); + } + }; + + /// Load level state from slot 1..4, if a save_state was made in this level. + lua["load_state"] = [](int slot) + { + if (slot >= 1 && slot <= 4 && get_save_state(slot)) + copy_save_slot(slot, 5); + }; + + /// Clear save state from slot 1..4. + lua["clear_state"] = [](int slot) + { + if (slot >= 1 && slot <= 4 && get_save_state(slot)) + get_save_state(slot)->screen = 0; + }; + + /// Get StateMemory from a save_state slot. + lua["get_save_state"] = [](int slot) -> StateMemory* + { + if (slot >= 1 && slot <= 5) + return get_save_state(slot); + return nullptr; + }; + + lua.new_usertype("SaveState", sol::constructors(), "load", &SaveState::load, "save", &SaveState::save, "clear", &SaveState::clear, "get_state", &SaveState::get_state); } }; // namespace NState From d1c10f88b18a95e173b169772f47f6b6f6d1c369 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 29 Feb 2024 08:41:51 +0200 Subject: [PATCH 06/11] change pauseapi default options --- src/injected/ui.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 99e20e146..2b5bcddbd 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -377,9 +377,8 @@ std::map options = { {"console_alt_keys", false}, {"vsync", true}, {"uncap_unfocused_fps", true}, - {"pause_loading", false}, - {"pause_update_camera", false}, - {"pause_last_instance", false}, + {"pause_update_camera", true}, + {"pause_last_instance", true}, {"update_check", true}, {"modifiers_clear_input", true}, {"load_scripts", true}, From ceee9fd07ed098153b9e240d0360bb61ca425311 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 29 Feb 2024 10:44:36 +0200 Subject: [PATCH 07/11] Actually fix remove powerup button? --- src/injected/ui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 2b5bcddbd..d91788a19 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -7077,7 +7077,7 @@ void render_powerup(PowerupCapable* ent, int uid, const char* section) ImGui::Text("%s", pname); ImGui::SameLine(); ImGui::PushID(uid); - if (ImGui::Button("Remove")) + if (ImGui::Button("Remove##RemovePowerup")) { ent->as()->remove_powerup(ptype); } From c852873a1c84c88db61c3030f9aeef3d0f26e03d Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 29 Feb 2024 11:12:17 +0200 Subject: [PATCH 08/11] fix peek_layer --- src/injected/ui.cpp | 45 +++++++++++++++++++--------------------- src/injected/ui_util.cpp | 5 +++++ src/injected/ui_util.hpp | 1 + 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index d91788a19..ab9fe3fcb 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -2840,8 +2840,11 @@ void clear_script_messages() bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) { ImGuiContext& g = *GImGui; + int repeat = (lParam >> 30) & 1U; + auto& io = ImGui::GetIO(); + ImGuiWindow* current = g.NavWindow; - if (nCode == WM_KEYUP) + if (nCode == WM_KEYUP && !io.WantCaptureKeyboard) { if (pressed("speedhack_turbo", wParam)) { @@ -2853,9 +2856,13 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) UI::speedhack(g_speedhack_old_multiplier); g_speedhack_old_multiplier = 1.0f; } - else if (pressed("peek_layer", wParam)) + else if (pressed("peek_layer", wParam) && peek_layer) { peek_layer = false; + g_state->layer_transition_timer = 15; + g_state->transition_to_layer = (g_state->camera_layer + 1) % 2; + g_state->camera_layer = g_state->transition_to_layer; + UI::set_camera_layer_control_enabled(!peek_layer); } } @@ -2864,9 +2871,6 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) return false; } - int repeat = (lParam >> 30) & 1U; - auto& io = ImGui::GetIO(); - ImGuiWindow* current = g.NavWindow; g_speedhack_ui_multiplier = UI::get_speedhack(); if (current != nullptr && current == ImGui::FindWindowByName("KeyCapture")) @@ -3542,9 +3546,13 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) } g_selected_ids.clear(); } - else if (pressed("peek_layer", wParam)) + else if (pressed("peek_layer", wParam) && !repeat) { peek_layer = true; + UI::set_camera_layer_control_enabled(!peek_layer); + g_state->layer_transition_timer = 15; + g_state->transition_to_layer = (g_state->camera_layer + 1) % 2; + g_state->camera_layer = g_state->transition_to_layer; } else if (pressed("save_state_1", wParam)) { @@ -4508,7 +4516,7 @@ void render_grid(ImColor gridcolor = ImColor(1.0f, 1.0f, 1.0f, 0.2f)) { for (unsigned int y = 0; y < g_state->h; ++y) { - auto room_temp = UI::get_room_template(x, y, peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer); + auto room_temp = UI::get_room_template(x, y, g_state->camera_layer); if (room_temp.has_value()) { auto room_name = UI::get_room_template_name(room_temp.value()); @@ -5078,7 +5086,7 @@ void render_clickhandler() if (options["draw_hitboxes"] && g_state->screen != 5) { static const auto olmec = to_id("ENT_TYPE_ACTIVEFLOOR_OLMEC"); - for (auto entity : UI::get_entities_by({}, g_hitbox_mask, (LAYER)(peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer))) + for (auto entity : UI::get_entities_by({}, g_hitbox_mask, (LAYER)g_state->camera_layer)) { auto ent = get_entity_ptr(entity); if (!ent) @@ -5132,17 +5140,17 @@ void render_clickhandler() to_id("ENT_TYPE_FLOOR_SHOPKEEPER_GENERATOR"), to_id("ENT_TYPE_FLOOR_SUNCHALLENGE_GENERATOR"), }; - for (auto entity : UI::get_entities_by(additional_fixed_entities, 0x180, (LAYER)(peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer))) // FLOOR | ACTIVEFLOOR + for (auto entity : UI::get_entities_by(additional_fixed_entities, 0x180, (LAYER)g_state->camera_layer)) // FLOOR | ACTIVEFLOOR { auto ent = get_entity_ptr(entity); render_hitbox(ent, false, ImColor(0, 255, 255, 150)); } - for (auto entity : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::TRIGGER}, 0x1000, (LAYER)(peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer))) // LOGICAL + for (auto entity : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::TRIGGER}, 0x1000, (LAYER)g_state->camera_layer)) // LOGICAL { auto ent = get_entity_ptr(entity); render_hitbox(ent, false, ImColor(255, 0, 0, 150)); } - for (auto entity : UI::get_entities_by({to_id("ENT_TYPE_LOGICAL_DOOR")}, 0x1000, (LAYER)(peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer))) // DOOR + for (auto entity : UI::get_entities_by({to_id("ENT_TYPE_LOGICAL_DOOR")}, 0x1000, (LAYER)g_state->camera_layer)) // DOOR { auto ent = get_entity_ptr(entity); render_hitbox(ent, false, ImColor(255, 180, 45, 150), false, true); @@ -5200,7 +5208,7 @@ void render_clickhandler() back_fill.Value.w = 0.25f; if (update_entity()) { - auto this_layer = (peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer) == g_entity->layer; + auto this_layer = g_state->camera_layer == g_entity->layer; render_hitbox(g_entity, true, this_layer ? front_col : back_col); } for (auto entity : g_selected_ids) @@ -5208,7 +5216,7 @@ void render_clickhandler() auto ent = get_entity_ptr(entity); if (ent) { - if (ent->layer == (peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer)) + if (ent->layer == g_state->camera_layer) render_hitbox(ent, false, front_fill, true); else render_hitbox(ent, false, back_fill, true); @@ -8160,17 +8168,6 @@ struct TextureViewer static TextureViewer texture_viewer{0, -1}; void render_vanilla_stuff() { - if (peek_layer && g_state->layer_transition_timer == 0) - { - uint8_t other_layer = g_state->camera_layer ? 0 : 1; - auto [bbox_left, bbox_top] = UI::click_position(-1.0f, 1.0f); - auto [bbox_right, bbox_bottom] = UI::click_position(1.0f, -1.0f); - for (uint8_t i = 52; i > 0; --i) - { - render_draw_depth(g_state->layers[other_layer], i, bbox_left, bbox_bottom, bbox_right, bbox_top); - } - } - if (!hide_ui && options["draw_hotbar"]) render_hotbar_textures(); diff --git a/src/injected/ui_util.cpp b/src/injected/ui_util.cpp index 4d41f66ba..33e427fe2 100644 --- a/src/injected/ui_util.cpp +++ b/src/injected/ui_util.cpp @@ -839,3 +839,8 @@ StateMemory* UI::get_save_state(int slot) { return ::get_save_state(slot); } + +void UI::set_camera_layer_control_enabled(bool enable) +{ + ::set_camera_layer_control_enabled(enable); +} diff --git a/src/injected/ui_util.hpp b/src/injected/ui_util.hpp index 991c97f4d..2964ef594 100644 --- a/src/injected/ui_util.hpp +++ b/src/injected/ui_util.hpp @@ -100,4 +100,5 @@ class UI static void set_adventure_seed(int64_t first, int64_t second); static void copy_state(int from, int to); static StateMemory* get_save_state(int slot); + static void set_camera_layer_control_enabled(bool enable); }; From 2d943a4e1caa8548f3bd03f9456bd81663cfe783 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 29 Feb 2024 14:36:56 +0200 Subject: [PATCH 09/11] update docs --- docs/game_data/lua_enums.txt | 12 ++++++--- docs/game_data/spel2.lua | 44 +++++++++++++++++++-------------- docs/game_data/vtable_sizes.csv | 6 ++--- docs/src/includes/_enums.md | 4 +++ docs/src/includes/_events.md | 28 +++++++++++++++++++++ docs/src/includes/_globals.md | 9 +++++++ src/game_api/script/lua_vm.cpp | 12 +++++++++ 7 files changed, 90 insertions(+), 25 deletions(-) diff --git a/docs/game_data/lua_enums.txt b/docs/game_data/lua_enums.txt index ac9775995..76d53d635 100644 --- a/docs/game_data/lua_enums.txt +++ b/docs/game_data/lua_enums.txt @@ -1548,6 +1548,7 @@ ENT_TYPE = { POWERUPCAPABLE = 1250, PROTOSHOPKEEPER = 1251, PUNISHBALL = 1252, + PURCHASABLE = 1334, PUSHBLOCK = 1253, QILIN = 1254, QUICKSAND = 1255, @@ -2118,10 +2119,9 @@ ON = { ARENA_SCORE = 27, ARENA_SELECT = 24, ARENA_STAGES = 22, - BLOCKED_GAME_LOOP = 159, - BLOCKED_LEVEL_GENERATION = 157, - BLOCKED_PROCESS_INPUT = 160, - BLOCKED_UPDATE = 158, + BLOCKED_GAME_LOOP = 162, + BLOCKED_PROCESS_INPUT = 163, + BLOCKED_UPDATE = 161, CAMP = 11, CHARACTER_SELECT = 9, CONSTELLATION = 19, @@ -2150,8 +2150,10 @@ ON = { POST_LEVEL_GENERATION = 112, POST_LOAD_JOURNAL_CHAPTER = 139, POST_LOAD_SCREEN = 136, + POST_LOAD_STATE = 160, POST_PROCESS_INPUT = 154, POST_ROOM_GENERATION = 111, + POST_SAVE_STATE = 158, POST_UPDATE = 143, PRE_GAME_LOOP = 155, PRE_GET_FEAT = 140, @@ -2165,7 +2167,9 @@ ON = { PRE_LOAD_JOURNAL_CHAPTER = 138, PRE_LOAD_LEVEL_FILES = 109, PRE_LOAD_SCREEN = 135, + PRE_LOAD_STATE = 159, PRE_PROCESS_INPUT = 153, + PRE_SAVE_STATE = 157, PRE_SET_FEAT = 141, PRE_UPDATE = 142, PROLOGUE = 2, diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 9ec8f4629..5d37200cc 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1378,6 +1378,14 @@ function play_adventure() end ---@param seed integer? ---@return nil function play_seeded(seed) end +---@return boolean +function toast_visible() end +---@return boolean +function speechbubble_visible() end +---@return nil +function cancel_toast() end +---@return nil +function cancel_speechbubble() end ---Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. ---@param slot integer ---@return nil @@ -1386,18 +1394,14 @@ function save_state(slot) end ---@param slot integer ---@return nil function load_state(slot) end +---Clear save state from slot 1..4. +---@param slot integer +---@return nil +function clear_state(slot) end ---Get StateMemory from a save_state slot. ---@param slot integer ---@return StateMemory function get_save_state(slot) end ----@return boolean -function toast_visible() end ----@return boolean -function speechbubble_visible() end ----@return nil -function cancel_toast() end ----@return nil -function cancel_speechbubble() end ---Returns RawInput, a game structure for raw keyboard and controller state ---@return RawInput function get_raw_input() end @@ -1877,12 +1881,6 @@ do ---@class Players ----@class SaveState - ---@field load fun(self): nil @Load a SaveState - ---@field save fun(self): nil @Save over a previously allocated SaveState - ---@field clear fun(self): nil @Delete the SaveState and free the memory. The SaveState can't be used after this. - ---@field get_state fun(self): StateMemory @Access the StateMemory inside a SaveState - ---@class SaveContext ---@field save fun(self, data: string): boolean @@ -2263,6 +2261,12 @@ do ---@field room_index integer ---@field owner_uid integer +---@class SaveState + ---@field load fun(self): nil @Load a SaveState + ---@field save fun(self): nil @Save over a previously allocated SaveState + ---@field clear fun(self): nil @Delete the SaveState and free the memory. The SaveState can't be used after this. + ---@field get_state fun(self): StateMemory @Access the StateMemory inside a SaveState + ---@class BackgroundMusic ---@field game_startup BackgroundSound ---@field main_backgroundtrack BackgroundSound @@ -8182,6 +8186,7 @@ ENT_TYPE = { POWERUPCAPABLE = 1250, PROTOSHOPKEEPER = 1251, PUNISHBALL = 1252, + PURCHASABLE = 1334, PUSHBLOCK = 1253, QILIN = 1254, QUICKSAND = 1255, @@ -8775,10 +8780,9 @@ ON = { ARENA_SCORE = 27, ARENA_SELECT = 24, ARENA_STAGES = 22, - BLOCKED_GAME_LOOP = 159, - BLOCKED_LEVEL_GENERATION = 157, - BLOCKED_PROCESS_INPUT = 160, - BLOCKED_UPDATE = 158, + BLOCKED_GAME_LOOP = 162, + BLOCKED_PROCESS_INPUT = 163, + BLOCKED_UPDATE = 161, CAMP = 11, CHARACTER_SELECT = 9, CONSTELLATION = 19, @@ -8807,8 +8811,10 @@ ON = { POST_LEVEL_GENERATION = 112, POST_LOAD_JOURNAL_CHAPTER = 139, POST_LOAD_SCREEN = 136, + POST_LOAD_STATE = 160, POST_PROCESS_INPUT = 154, POST_ROOM_GENERATION = 111, + POST_SAVE_STATE = 158, POST_UPDATE = 143, PRE_GAME_LOOP = 155, PRE_GET_FEAT = 140, @@ -8822,7 +8828,9 @@ ON = { PRE_LOAD_JOURNAL_CHAPTER = 138, PRE_LOAD_LEVEL_FILES = 109, PRE_LOAD_SCREEN = 135, + PRE_LOAD_STATE = 159, PRE_PROCESS_INPUT = 153, + PRE_SAVE_STATE = 157, PRE_SET_FEAT = 141, PRE_UPDATE = 142, PROLOGUE = 2, diff --git a/docs/game_data/vtable_sizes.csv b/docs/game_data/vtable_sizes.csv index 5e96fadbd..701f60c50 100644 --- a/docs/game_data/vtable_sizes.csv +++ b/docs/game_data/vtable_sizes.csv @@ -4,7 +4,7 @@ TypeID,Name,vtable offset,~Entity,create_rendering_info,handle_state_machine,kil 3,ENT_TYPE_FLOOR_BORDERTILE_OCTOPUS,8786,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 4,ENT_TYPE_FLOOR_GENERIC,8786,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 5,ENT_TYPE_FLOOR_SURFACE,8786,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 -6,ENT_TYPE_FLOOR_SURFACE_HIDDEN,48206,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,ret,0x2287cbd0,0x2287d100,ret 0,ret true,ret,ret,ret,ret,0x228bff80,0x2287d100,ret 0,0x228bfe80,ret,ret,0x228bfe90,0x2292e2c0,0x228bdfb0,ret true,ret 0,ret 0,ret,ret,0x228ee610,0x22997220,0x228bdfb0,0x228c4340,ret 0,ret 0,0x22999730,0x22999750,0x22999aa0,0x228c6ab0,0x22999f90,ret true,ret 0,ret 0,0x22999fc0,ret,0x2299a010,0x228c6ab0,0x228bdfb0,0x2289e280,0x2299bf40,0x2299d580,0x2299d8a0,0x229a3840,0x229a3b00,0xffff817dfdb00173,0xffff817efdb00000,0x228a9670 +6,ENT_TYPE_FLOOR_SURFACE_HIDDEN,48206,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,ret,0x2287cbd0,0x2287d100,ret 0,ret true,ret,ret,ret,ret,0x228bff80,0x2287d100,ret 0,0x228bfe80,ret,ret,0x228bfe90,0x2292e2c0,0x228bdfb0,ret true,ret 0,ret 0,ret,ret,0x228ee610,0x22997220,0x228bdfb0,0x228c4340,ret 0,ret 0,0x22999730,0x22999750,0x22999aa0,0x228c6ab0,0x22999f90,ret true,ret 0,ret 0,0x22999fc0,ret,0x2299a010,0x228c6ab0,0x228bdfb0,0x2289e280,0x2299bf40,0x2299d580,0x2299d8a0,0x229a3840,0x229a3b00,0xffff817ed3580173,0xffff817fd3580000,0x228a9670 7,ENT_TYPE_FLOOR_BASECAMP_SINGLEBED,10626,0x2287aed0,0x227fcd90,ret,0x2297d3f0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x2297d600,0x2297d680,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 8,ENT_TYPE_FLOOR_BASECAMP_DININGTABLE,10626,0x2287aed0,0x227fcd90,ret,0x2297d3f0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x2297d600,0x2297d680,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 9,ENT_TYPE_FLOOR_BASECAMP_LONGTABLE,10626,0x2287aed0,0x227fcd90,ret,0x2297d3f0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x2297d600,0x2297d680,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 @@ -91,7 +91,7 @@ TypeID,Name,vtable offset,~Entity,create_rendering_info,handle_state_machine,kil 90,ENT_TYPE_FLOOR_FORCEFIELD_TOP,8786,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 91,ENT_TYPE_FLOOR_HORIZONTAL_FORCEFIELD,8946,0x2287aed0,0x227fcd90,0x22974270,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x22974960,0x227fd790,0x227fd930,0x227fd950,0x22974760,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22974240,END OF ENTITY,0x22928d00,ret,0x2287cbd0 92,ENT_TYPE_FLOOR_HORIZONTAL_FORCEFIELD_TOP,8786,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 -93,ENT_TYPE_FLOOR_PEN,48142,0x2287aed0,0x227fcd90,ret,0x22978910,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x22978990,0x909801502b6000c,0x6801100b3030c,0x68010fdba0009,0x78010fdba0009,0x98012fdb80008,0x98012fdba000a,0xa8012fdbb000b,0xbf7f8008fdbb000b,0xffff80093d300000 +93,ENT_TYPE_FLOOR_PEN,48142,0x2287aed0,0x227fcd90,ret,0x22978910,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x22978990,0x9098015d85e000c,0x68011d65b030c,0x68011d3620009,0x78011d3620009,0x98013d3600008,0x98013d362000a,0xa8013d363000b,0xbf7f8009d363000b,0xffff800a12d80000 94,ENT_TYPE_FLOOR_TOMB,8906,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x2298cc30,END OF ENTITY,0x22928d00,0x2292c410,0x2288b9e0 95,ENT_TYPE_FLOOR_YAMA_PLATFORM,8866,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x2288d9c0,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2287cbd0 96,ENT_TYPE_FLOOR_EMPRESS_GRAVE,8826,0x2287aed0,0x227fcd90,ret,0x22926290,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x22927b90,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,0x22927f40,ret,ret,0x22928420,ret,ret,0x227fe2f0,0x22928bd0,END OF ENTITY,0x22928d00,0x2292c410,0x2288b9e0 @@ -811,7 +811,7 @@ TypeID,Name,vtable offset,~Entity,create_rendering_info,handle_state_machine,kil 841,ENT_TYPE_BG_EGGSAC_STAINS,2460,0x2287aed0,0x227fcd90,ret,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,ret,END OF ENTITY 844,ENT_TYPE_LOGICAL_CONSTELLATION,2840,0x2287aed0,0x227fcd90,ret,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,0x229f53f0,END OF ENTITY 845,ENT_TYPE_LOGICAL_SHOOTING_STARS_SPAWNER,2802,0x2287aed0,0x227fcd90,0x22a004c0,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,0x22a004b0,END OF ENTITY -846,ENT_TYPE_LOGICAL_DOOR,48710,0x2287aed0,0x227fcd90,0x229f8090,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,ret,END OF ENTITY,0xffff80093d300000,0x403f80093db00000 +846,ENT_TYPE_LOGICAL_DOOR,48710,0x2287aed0,0x227fcd90,0x229f8090,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,ret,END OF ENTITY,0xffff800a12d80000,0x403f800a13580000 847,ENT_TYPE_LOGICAL_DOOR_AMBIENT_SOUND,2764,0x2287aed0,0x227fcd90,0x229f83c0,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,0x229f8360,END OF ENTITY 848,ENT_TYPE_LOGICAL_BLACKMARKET_DOOR,2726,0x2287aed0,0x227fcd90,0x229f8090,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,ret,ret 0,ret,ret,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,0x229f3af0,END OF ENTITY 849,ENT_TYPE_LOGICAL_ARROW_TRAP_TRIGGER,48672,0x2287aed0,0x227fcd90,0x229f35f0,0x227fcfd0,ret,0x227fd000,0x227fd0b0,0x227fd110,0x227fd150,0x227fd570,0x227fd580,ret 0,0x227fd5a0,0x227fd730,0x227fd740,0x227fd750,0x227fd770,0x227fd790,0x227fd930,0x227fd950,0x227fd960,0x227fe160,ret 0,0x229f3a20,ret 0,ret,0x229f3a40,ret 0,ret,ret,ret,ret,ret,ret,ret,0x227fe2f0,ret,END OF ENTITY diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index 3215971b2..3aa51fa78 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -889,6 +889,10 @@ Name | Data | Description [POST_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_PROCESS_INPUT) | ON::POST_PROCESS_INPUT | Runs right after the game gets input from various devices and writes to a bunch of buttons-variables. Probably the first chance you have to capture or edit buttons_gameplay or buttons_menu sort of things.
[PRE_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_GAME_LOOP) | ON::PRE_GAME_LOOP | Runs right before the main engine loop. Return true to block state updates and menu updates, i.e. to pause inside menus.
[POST_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_GAME_LOOP) | ON::POST_GAME_LOOP | Runs right after the main engine loop.
+[PRE_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_SAVE_STATE) | ON::PRE_SAVE_STATE | Runs right before the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block save.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
+[POST_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_SAVE_STATE) | ON::POST_SAVE_STATE | Runs right after the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
+[PRE_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_STATE) | ON::PRE_LOAD_STATE | Runs right before the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block load.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
+[POST_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LOAD_STATE) | ON::POST_LOAD_STATE | Runs right after the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
[BLOCKED_UPDATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_UPDATE) | ON::BLOCKED_UPDATE | Runs instead of POST_UPDATE when anything blocks a PRE_UPDATE. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_UPDATE.
[BLOCKED_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_GAME_LOOP) | ON::BLOCKED_GAME_LOOP | Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_GAME_LOOP.
[BLOCKED_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_PROCESS_INPUT) | ON::BLOCKED_PROCESS_INPUT | Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_PROCESS_INPUT.
diff --git a/docs/src/includes/_events.md b/docs/src/includes/_events.md index b2565057a..ca524ddf8 100644 --- a/docs/src/includes/_events.md +++ b/docs/src/includes/_events.md @@ -634,6 +634,34 @@ Runs right before the main engine loop. Return true to block state updates and m Runs right after the main engine loop.
+## ON.PRE_SAVE_STATE + + +> Search script examples for [ON.PRE_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_SAVE_STATE) + +Runs right before the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block save.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
+ +## ON.POST_SAVE_STATE + + +> Search script examples for [ON.POST_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_SAVE_STATE) + +Runs right after the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
+ +## ON.PRE_LOAD_STATE + + +> Search script examples for [ON.PRE_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_STATE) + +Runs right before the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block load.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
+ +## ON.POST_LOAD_STATE + + +> Search script examples for [ON.POST_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LOAD_STATE) + +Runs right after the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
+ ## ON.BLOCKED_UPDATE diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 00ad01c74..820138887 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -1258,6 +1258,15 @@ Change the amount of frames after the damage from poison is applied Clear cache for a file path or the whole directory +### clear_state + + +> Search script examples for [clear_state](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear_state) + +#### nil clear_state(int slot) + +Clear save state from slot 1..4. + ### clr_mask diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index e6a03a8c4..6063573b0 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2682,6 +2682,18 @@ end // Runs right before the main engine loop. Return true to block state updates and menu updates, i.e. to pause inside menus. // POST_GAME_LOOP // Runs right after the main engine loop. + // PRE_SAVE_STATE + // Runs right before the main StateMemory is manually saved to a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. Return true to block save. + // Params: int slot, StateMemory current, StateMemory saved + // POST_SAVE_STATE + // Runs right after the main StateMemory is manually saved to a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. + // Params: int slot, StateMemory current, StateMemory saved + // PRE_LOAD_STATE + // Runs right before the main StateMemory is manually loaded from a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. Return true to block load. + // Params: int slot, StateMemory current, StateMemory loaded + // POST_LOAD_STATE + // Runs right after the main StateMemory is manually loaded from a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. + // Params: int slot, StateMemory current, StateMemory loaded // BLOCKED_UPDATE // Runs instead of POST_UPDATE when anything blocks a PRE_UPDATE. Even runs in Playlunky when Overlunky blocks a PRE_UPDATE. // BLOCKED_GAME_LOOP From 4d3802dcc999111f1e52a8e73df404f4536e965e Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 29 Feb 2024 16:49:08 +0200 Subject: [PATCH 10/11] make it simpler --- docs/src/includes/_enums.md | 8 ++++---- docs/src/includes/_events.md | 8 ++++---- examples/savestate.lua | 9 +++++---- src/game_api/savestate.cpp | 16 ++++++++-------- src/game_api/script/events.cpp | 16 ++++++++-------- src/game_api/script/events.hpp | 8 ++++---- src/game_api/script/lua_backend.cpp | 16 ++++++++-------- src/game_api/script/lua_backend.hpp | 8 ++++---- src/game_api/script/lua_vm.cpp | 8 ++++---- 9 files changed, 49 insertions(+), 48 deletions(-) diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index 3aa51fa78..254f8aed1 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -889,10 +889,10 @@ Name | Data | Description [POST_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_PROCESS_INPUT) | ON::POST_PROCESS_INPUT | Runs right after the game gets input from various devices and writes to a bunch of buttons-variables. Probably the first chance you have to capture or edit buttons_gameplay or buttons_menu sort of things.
[PRE_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_GAME_LOOP) | ON::PRE_GAME_LOOP | Runs right before the main engine loop. Return true to block state updates and menu updates, i.e. to pause inside menus.
[POST_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_GAME_LOOP) | ON::POST_GAME_LOOP | Runs right after the main engine loop.
-[PRE_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_SAVE_STATE) | ON::PRE_SAVE_STATE | Runs right before the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block save.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
-[POST_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_SAVE_STATE) | ON::POST_SAVE_STATE | Runs right after the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
-[PRE_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_STATE) | ON::PRE_LOAD_STATE | Runs right before the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block load.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
-[POST_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LOAD_STATE) | ON::POST_LOAD_STATE | Runs right after the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
+[PRE_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_SAVE_STATE) | ON::PRE_SAVE_STATE | Runs right before the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block save.
Params: int slot, [StateMemory](#StateMemory) saved
+[POST_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_SAVE_STATE) | ON::POST_SAVE_STATE | Runs right after the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) saved
+[PRE_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_STATE) | ON::PRE_LOAD_STATE | Runs right before the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block load.
Params: int slot, [StateMemory](#StateMemory) loaded
+[POST_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LOAD_STATE) | ON::POST_LOAD_STATE | Runs right after the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) loaded
[BLOCKED_UPDATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_UPDATE) | ON::BLOCKED_UPDATE | Runs instead of POST_UPDATE when anything blocks a PRE_UPDATE. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_UPDATE.
[BLOCKED_GAME_LOOP](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_GAME_LOOP) | ON::BLOCKED_GAME_LOOP | Runs instead of POST_GAME_LOOP when anything blocks a PRE_GAME_LOOP. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_GAME_LOOP.
[BLOCKED_PROCESS_INPUT](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.BLOCKED_PROCESS_INPUT) | ON::BLOCKED_PROCESS_INPUT | Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when [Overlunky](#Overlunky) blocks a PRE_PROCESS_INPUT.
diff --git a/docs/src/includes/_events.md b/docs/src/includes/_events.md index ca524ddf8..d03a84b5c 100644 --- a/docs/src/includes/_events.md +++ b/docs/src/includes/_events.md @@ -639,28 +639,28 @@ Runs right after the main engine loop.
> Search script examples for [ON.PRE_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_SAVE_STATE) -Runs right before the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block save.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
+Runs right before the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block save.
Params: int slot, [StateMemory](#StateMemory) saved
## ON.POST_SAVE_STATE > Search script examples for [ON.POST_SAVE_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_SAVE_STATE) -Runs right after the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) saved
+Runs right after the main [StateMemory](#StateMemory) is manually saved to a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) saved
## ON.PRE_LOAD_STATE > Search script examples for [ON.PRE_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_STATE) -Runs right before the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block load.
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
+Runs right before the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState). Return true to block load.
Params: int slot, [StateMemory](#StateMemory) loaded
## ON.POST_LOAD_STATE > Search script examples for [ON.POST_LOAD_STATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LOAD_STATE) -Runs right after the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) current, [StateMemory](#StateMemory) loaded
+Runs right after the main [StateMemory](#StateMemory) is manually loaded from a slot or a custom [SaveState](#SaveState). Slot is 1..4 or -1 on custom [SaveState](#SaveState).
Params: int slot, [StateMemory](#StateMemory) loaded
## ON.BLOCKED_UPDATE diff --git a/examples/savestate.lua b/examples/savestate.lua index f7e7e724c..75c243e23 100644 --- a/examples/savestate.lua +++ b/examples/savestate.lua @@ -42,7 +42,8 @@ set_callback(function(ctx) tp = SaveState:new() end box_frame = state.time_level - players[1]:set_post_update_state_machine(function(e) + + players[1]:topmost_mount():set_post_update_state_machine(function(e) clear_callback() box = get_hitbox(e.uid) box_color = 0xcc33ff33 @@ -67,13 +68,13 @@ end, ON.LEVEL) set_callback(clear_states, ON.PRE_LEVEL_DESTRUCTION) -set_callback(function(slot, current, loading) +set_callback(function(slot, loading) if slot > 0 then print(F "Loading save slot {slot}...") else print("Loading custom save slot...") end - rewind = current.time_level - loading.time_level + rewind = state.time_level - loading.time_level if rewind < 0 then print(F "Not forwarding {-rewind} frames, that would break spacetime!") return true @@ -82,7 +83,7 @@ set_callback(function(slot, current, loading) end end, ON.PRE_LOAD_STATE) -set_callback(function(slot, current, loaded) +set_callback(function(slot, loaded) print(F "Rewinded {rewind} frames!") rewind = nil end, ON.POST_LOAD_STATE) diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index bdfea9651..be3136f01 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -7,17 +7,17 @@ void copy_save_slot(int from, int to) { - if ((from == 5 && pre_save_state(to, get_save_state(to), get_save_state(from))) || - (to == 5 && pre_load_state(from, get_save_state(to), get_save_state(from)))) + if ((from == 5 && pre_save_state(to, get_save_state(to))) || + (to == 5 && pre_load_state(from, get_save_state(from)))) return; size_t arr = get_address("save_states"); size_t fromBaseState = memory_read(arr + (from - 1) * 8); size_t toBaseState = memory_read(arr + (to - 1) * 8); copy_state(fromBaseState, toBaseState); if (from == 5) - post_save_state(to, get_save_state(to), get_save_state(from)); + post_save_state(to, get_save_state(to)); else if (to == 5) - post_load_state(from, get_save_state(to), get_save_state(from)); + post_load_state(from, get_save_state(from)); }; void copy_state(size_t fromBaseState, size_t toBaseState) @@ -94,10 +94,10 @@ void SaveState::load() return; size_t to = (size_t)(State::get().ptr_main()) - 0x4a0; auto state = reinterpret_cast(addr + 0x4a0); - if (pre_load_state(-1, State::get().ptr_main(), state)) + if (pre_load_state(-1, state)) return; copy_state(addr, to); - post_load_state(-1, State::get().ptr_main(), state); + post_load_state(-1, state); } void SaveState::save() @@ -106,10 +106,10 @@ void SaveState::save() return; size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; auto state = reinterpret_cast(addr + 0x4a0); - if (pre_save_state(-1, state, State::get().ptr_main())) + if (pre_save_state(-1, state)) return; copy_state(from, addr); - post_save_state(-1, state, State::get().ptr_main()); + post_save_state(-1, state); } void SaveState::clear() diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index e12706738..7b0301757 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -121,25 +121,25 @@ bool pre_init_layer(LAYER layer) return block; } -bool pre_save_state(int slot, StateMemory* current, StateMemory* saved) +bool pre_save_state(int slot, StateMemory* saved) { bool block{false}; LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - block = backend->pre_save_state(slot, current, saved); + block = backend->pre_save_state(slot, saved); return !block; }); return block; } -bool pre_load_state(int slot, StateMemory* current, StateMemory* loaded) +bool pre_load_state(int slot, StateMemory* loaded) { bool block{false}; LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - block = backend->pre_load_state(slot, current, loaded); + block = backend->pre_load_state(slot, loaded); return !block; }); return block; @@ -190,21 +190,21 @@ void post_unload_layer(LAYER layer) return true; }); } -void post_save_state(int slot, StateMemory* current, StateMemory* saved) +void post_save_state(int slot, StateMemory* saved) { LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - backend->post_save_state(slot, current, saved); + backend->post_save_state(slot, saved); return true; }); } -void post_load_state(int slot, StateMemory* current, StateMemory* loaded) +void post_load_state(int slot, StateMemory* loaded) { LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - backend->post_load_state(slot, current, loaded); + backend->post_load_state(slot, loaded); return true; }); } diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index 38b4d4e55..bbed4af55 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -24,16 +24,16 @@ bool pre_init_level(); bool pre_init_layer(LAYER layer); bool pre_unload_level(); bool pre_unload_layer(LAYER layer); -bool pre_save_state(int slot, StateMemory* current, StateMemory* saved); -bool pre_load_state(int slot, StateMemory* current, StateMemory* loaded); +bool pre_save_state(int slot, StateMemory* saved); +bool pre_load_state(int slot, StateMemory* loaded); void post_load_screen(); void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); void post_room_generation(); void post_level_generation(); -void post_save_state(int slot, StateMemory* current, StateMemory* saved); -void post_load_state(int slot, StateMemory* current, StateMemory* loaded); +void post_save_state(int slot, StateMemory* saved); +void post_load_state(int slot, StateMemory* loaded); void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 4de752598..45896d558 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -1815,7 +1815,7 @@ void LuaBackend::on_post(ON event) } } -bool LuaBackend::pre_save_state(int slot, StateMemory* current, StateMemory* saved) +bool LuaBackend::pre_save_state(int slot, StateMemory* saved) { if (!get_enabled()) return false; @@ -1830,7 +1830,7 @@ bool LuaBackend::pre_save_state(int slot, StateMemory* current, StateMemory* sav if (callback.screen == ON::PRE_SAVE_STATE) { set_current_callback(-1, id, CallbackType::Normal); - auto return_value = handle_function(this, callback.func, slot, current, saved).value_or(false); + auto return_value = handle_function(this, callback.func, slot, saved).value_or(false); clear_current_callback(); callback.lastRan = now; if (return_value) @@ -1841,7 +1841,7 @@ bool LuaBackend::pre_save_state(int slot, StateMemory* current, StateMemory* sav return false; } -bool LuaBackend::pre_load_state(int slot, StateMemory* current, StateMemory* loaded) +bool LuaBackend::pre_load_state(int slot, StateMemory* loaded) { if (!get_enabled()) return false; @@ -1856,7 +1856,7 @@ bool LuaBackend::pre_load_state(int slot, StateMemory* current, StateMemory* loa if (callback.screen == ON::PRE_LOAD_STATE) { set_current_callback(-1, id, CallbackType::Normal); - auto return_value = handle_function(this, callback.func, slot, current, loaded).value_or(false); + auto return_value = handle_function(this, callback.func, slot, loaded).value_or(false); clear_current_callback(); callback.lastRan = now; if (return_value) @@ -1867,7 +1867,7 @@ bool LuaBackend::pre_load_state(int slot, StateMemory* current, StateMemory* loa return false; } -void LuaBackend::post_save_state(int slot, StateMemory* current, StateMemory* saved) +void LuaBackend::post_save_state(int slot, StateMemory* saved) { if (!get_enabled()) return; @@ -1882,14 +1882,14 @@ void LuaBackend::post_save_state(int slot, StateMemory* current, StateMemory* sa if (callback.screen == ON::POST_SAVE_STATE) { set_current_callback(-1, id, CallbackType::Normal); - handle_function(this, callback.func, slot, current, saved); + handle_function(this, callback.func, slot, saved); clear_current_callback(); callback.lastRan = now; } } } -void LuaBackend::post_load_state(int slot, StateMemory* current, StateMemory* loaded) +void LuaBackend::post_load_state(int slot, StateMemory* loaded) { if (!get_enabled()) return; @@ -1904,7 +1904,7 @@ void LuaBackend::post_load_state(int slot, StateMemory* current, StateMemory* lo if (callback.screen == ON::POST_LOAD_STATE) { set_current_callback(-1, id, CallbackType::Normal); - handle_function(this, callback.func, slot, current, loaded); + handle_function(this, callback.func, slot, loaded); clear_current_callback(); callback.lastRan = now; } diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 12aa3e1c2..8b511f099 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -387,16 +387,16 @@ class LuaBackend bool pre_init_layer(LAYER layer); bool pre_unload_level(); bool pre_unload_layer(LAYER layer); - bool pre_save_state(int slot, StateMemory* current, StateMemory* saved); - bool pre_load_state(int slot, StateMemory* current, StateMemory* loaded); + bool pre_save_state(int slot, StateMemory* saved); + bool pre_load_state(int slot, StateMemory* loaded); void post_room_generation(); void post_level_generation(); void post_load_screen(); void post_init_layer(LAYER layer); void post_unload_layer(LAYER layer); - void post_save_state(int slot, StateMemory* current, StateMemory* saved); - void post_load_state(int slot, StateMemory* current, StateMemory* loaded); + void post_save_state(int slot, StateMemory* saved); + void post_load_state(int slot, StateMemory* loaded); void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 6063573b0..0d47bea9b 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2684,16 +2684,16 @@ end // Runs right after the main engine loop. // PRE_SAVE_STATE // Runs right before the main StateMemory is manually saved to a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. Return true to block save. - // Params: int slot, StateMemory current, StateMemory saved + // Params: int slot, StateMemory saved // POST_SAVE_STATE // Runs right after the main StateMemory is manually saved to a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. - // Params: int slot, StateMemory current, StateMemory saved + // Params: int slot, StateMemory saved // PRE_LOAD_STATE // Runs right before the main StateMemory is manually loaded from a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. Return true to block load. - // Params: int slot, StateMemory current, StateMemory loaded + // Params: int slot, StateMemory loaded // POST_LOAD_STATE // Runs right after the main StateMemory is manually loaded from a slot or a custom SaveState. Slot is 1..4 or -1 on custom SaveState. - // Params: int slot, StateMemory current, StateMemory loaded + // Params: int slot, StateMemory loaded // BLOCKED_UPDATE // Runs instead of POST_UPDATE when anything blocks a PRE_UPDATE. Even runs in Playlunky when Overlunky blocks a PRE_UPDATE. // BLOCKED_GAME_LOOP From 842716660d2d0a4bd30c19e381aebf6e5d9205a9 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 1 Mar 2024 06:18:41 +0200 Subject: [PATCH 11/11] use state_location for offset --- examples/savestate.lua | 4 +++- src/game_api/savestate.cpp | 20 ++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/savestate.lua b/examples/savestate.lua index 75c243e23..f9f1ae8ab 100644 --- a/examples/savestate.lua +++ b/examples/savestate.lua @@ -1,4 +1,4 @@ -meta.name = "Teleport predictor and advanced SaveStates" +meta.name = "Teleport predictor and SaveStates" meta.author = "Dregu" meta.version = "1.0" @@ -64,6 +64,8 @@ set_callback(function() if #states == 0 then states[1] = SaveState:new() end + box = nil + box_frame = nil end, ON.LEVEL) set_callback(clear_states, ON.PRE_LEVEL_DESTRUCTION) diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index be3136f01..6b8823fe6 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -5,6 +5,14 @@ #include "script/events.hpp" // for pre_load_state #include "state.hpp" // for State, get_state_ptr, enum_to_layer +size_t get_state_offset() +{ + auto addr = get_address("state_location"); + if (addr) + return memory_read(addr); + return 0x4a0; +} + void copy_save_slot(int from, int to) { if ((from == 5 && pre_save_state(to, get_save_state(to))) || @@ -51,7 +59,7 @@ StateMemory* get_save_state(int slot) { size_t arr = get_address("save_states"); size_t base = memory_read(arr + (slot - 1) * 8); - auto state = reinterpret_cast(base + 0x4a0); + auto state = reinterpret_cast(base + get_state_offset()); if (state->screen) return state; return nullptr; @@ -85,15 +93,15 @@ StateMemory* SaveState::get_state() { if (!addr) return nullptr; - return reinterpret_cast(addr + 0x4a0); + return reinterpret_cast(addr + get_state_offset()); } void SaveState::load() { if (!addr) return; - size_t to = (size_t)(State::get().ptr_main()) - 0x4a0; - auto state = reinterpret_cast(addr + 0x4a0); + size_t to = (size_t)(State::get().ptr_main()) - get_state_offset(); + auto state = reinterpret_cast(addr + get_state_offset()); if (pre_load_state(-1, state)) return; copy_state(addr, to); @@ -104,8 +112,8 @@ void SaveState::save() { if (!addr) return; - size_t from = (size_t)(State::get().ptr_main()) - 0x4a0; - auto state = reinterpret_cast(addr + 0x4a0); + size_t from = (size_t)(State::get().ptr_main()) - get_state_offset(); + auto state = reinterpret_cast(addr + get_state_offset()); if (pre_save_state(-1, state)) return; copy_state(from, addr);