From fec595dc77bb1c577b88465b853b519dbaca209b Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 5 Oct 2023 11:51:10 +0300 Subject: [PATCH 01/16] show backlayer grid when peeking --- src/injected/ui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 18c1f8599..85efc59ae 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -3997,7 +3997,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, g_state->camera_layer); + auto room_temp = UI::get_room_template(x, y, peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer); if (room_temp.has_value()) { auto room_name = UI::get_room_template_name(room_temp.value()); @@ -6778,7 +6778,7 @@ void render_entity_props(int uid, bool detached = false) ImGui::InputFloat("Absolute X##EntityAbsoluteX", &entity->abs_x, 0.2f, 1.0f); ImGui::InputFloat("Absolute Y##EntityAbsoluteY", &entity->abs_y, 0.2f, 1.0f); ImGui::InputFloat("Velocity X##EntityVelocityX", &movable->velocityx, 0.2f, 1.0f); - ImGui::InputFloat("Velocity y##EntityVelocityY", &movable->velocityy, 0.2f, 1.0f); + ImGui::InputFloat("Velocity Y##EntityVelocityY", &movable->velocityy, 0.2f, 1.0f); } ImGui::InputFloat("Angle##EntityAngle", &entity->angle, 0.2f, 1.0f); if (is_movable) From 98b81d34400ddd5cf75ba8a014d50cc956653c3a Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 5 Oct 2023 12:00:31 +0300 Subject: [PATCH 02/16] add shader slider to entity props --- src/injected/ui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 85efc59ae..53b19e5d0 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -294,7 +294,7 @@ std::string fontfile = ""; std::vector fontsize = {14.0f, 32.0f, 72.0f}; [[maybe_unused]] const char s8_zero = 0, s8_one = 1, s8_min = -128, s8_max = 127; -[[maybe_unused]] const ImU8 u8_zero = 0, u8_one = 1, u8_min = 0, u8_max = 255, u8_four = 4, u8_seven = 7, u8_seventeen = 17, u8_draw_depth_max = 53; +[[maybe_unused]] const ImU8 u8_zero = 0, u8_one = 1, u8_min = 0, u8_max = 255, u8_four = 4, u8_seven = 7, u8_seventeen = 17, u8_draw_depth_max = 52, u8_shader_max = 36; [[maybe_unused]] const short s16_zero = 0, s16_one = 1, s16_min = -32768, s16_max = 32767; [[maybe_unused]] const ImU16 u16_zero = 0, u16_one = 1, u16_min = 0, u16_max = 65535; [[maybe_unused]] const ImS32 s32_zero = 0, s32_one = 1, s32_min = INT_MIN / 2, s32_max = INT_MAX / 2, s32_hi_a = INT_MAX / 2 - 100, s32_hi_b = INT_MAX / 2; @@ -7009,7 +7009,8 @@ void render_entity_props(int uid, bool detached = false) uint8_t draw_depth = entity->draw_depth; if (ImGui::DragScalar("Draw depth##EntityDrawDepth", ImGuiDataType_U8, &draw_depth, 0.2f, &u8_zero, &u8_draw_depth_max)) entity->set_draw_depth(draw_depth); - + if (entity->rendering_info) + ImGui::DragScalar("Shader##EntityShader", ImGuiDataType_U8, &entity->rendering_info->shader, 0.2f, &u8_zero, &u8_shader_max); ImGui::DragScalar("Animation frame##EntityAnimationFrame", ImGuiDataType_U16, &entity->animation_frame, 0.2f, &u16_zero, &u16_max); ImGui::InputText(fmt::format("Texture: {}##EntityTexture", textureid).c_str(), &texture, ImGuiInputTextFlags_ReadOnly); // ImGui::InputText("Texture path##EntityTexturePath", &texturepath, ImGuiInputTextFlags_ReadOnly); From 1cb1904c6f65569ad9ee42d9686f3f89490ca5bd Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 5 Oct 2023 12:10:40 +0300 Subject: [PATCH 03/16] add important comment about add_money --- src/game_api/movable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_api/movable.hpp b/src/game_api/movable.hpp index 889a6073e..45b6fc692 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -163,7 +163,7 @@ class Movable : public Entity virtual void on_picked_up_by(Entity* entity_picking_up) = 0; virtual void drop(Entity* entity_to_drop) = 0; // also used when throwing - /// Adds or subtracts the specified amount of money to the movable's (player's) inventory. Shows the calculation animation in the HUD. + /// Adds or subtracts the specified amount of money to the movable's (player's) inventory. Shows the calculation animation in the HUD. Important: Updates the collected_money_count in the inventory, but doesn't add any treasure to the following array, which will lead to crashes during the transition, if you don't deal with it. (e.g. set `collected_money_count = collected_money_count - 1` afterwards) virtual void add_money(uint32_t money) = 0; virtual void apply_movement() = 0; // disable this function and things can't move, some spin in place From e74dff355eb5603ea6cffb51f47f417dda663a81 Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 5 Oct 2023 15:00:50 +0300 Subject: [PATCH 04/16] add vsync and oldflip options --- README.md | 1 + src/game_api/window_api.cpp | 8 ++++++++ src/game_api/window_api.hpp | 1 + src/injected/ui.cpp | 10 ++++++++-- src/injector/main.cpp | 16 +++++++++++----- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c1eef5f40..1fa4253c1 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ The binaries will be in `build/bin/Release/`. You can also try the scripts in `. ``` --launch_game [path] launch ../Spel2.exe, path/Spel2.exe, or a specific exe, and load OL with Detours +--oldflip launch the game with -oldflip, may improve performance with external windows --console keep console open to debug scripts etc --inject use the old injection method instead of Detours with --launch_game --info_dump output a bunch of game data to 'Spelunky 2/game_data' diff --git a/src/game_api/window_api.cpp b/src/game_api/window_api.cpp index 43c4b4265..4ecfb1679 100644 --- a/src/game_api/window_api.cpp +++ b/src/game_api/window_api.cpp @@ -24,6 +24,7 @@ bool detect_wine() return true; } +UINT g_SyncInterval{1}; IDXGISwapChain* g_SwapChain{nullptr}; ID3D11Device* g_Device{nullptr}; ID3D11DeviceContext* g_Context{nullptr}; @@ -210,6 +211,8 @@ LRESULT CALLBACK hkKeyboard(const int code, const WPARAM wParam, const LPARAM lP static bool skip_hkPresent = false; HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) { + SyncInterval = g_SyncInterval; + if (skip_hkPresent) return g_OrigSwapChainPresent(pSwapChain, SyncInterval, Flags); @@ -468,6 +471,11 @@ void hide_cursor() } } +void imgui_vsync(bool enable) +{ + g_SyncInterval = (UINT)enable; +} + ID3D11Device* get_device() { return g_Device; diff --git a/src/game_api/window_api.hpp b/src/game_api/window_api.hpp index acb5c7884..249f2e8b7 100644 --- a/src/game_api/window_api.hpp +++ b/src/game_api/window_api.hpp @@ -33,5 +33,6 @@ HWND get_window(); void show_cursor(); void hide_cursor(); +void imgui_vsync(bool enable); struct ID3D11Device* get_device(); diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 53b19e5d0..521020880 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -348,7 +348,8 @@ std::map options = { {"hd_cursor", true}, {"inverted", false}, {"borders", false}, - {"console_alt_keys", false}}; + {"console_alt_keys", false}, + {"vsync", true}}; bool g_speedhack_hooked = false; float g_speedhack_multiplier = 1.0; @@ -1042,6 +1043,7 @@ void load_config(std::string file) } ImGui::GetIO().ConfigDockingWithShift = options["docking_with_shift"]; g_Console->set_alt_keys(options["console_alt_keys"]); + imgui_vsync(options["vsync"]); save_config(file); } @@ -5446,7 +5448,11 @@ void render_options() else ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable; } - tooltip("Allow dragging tools outside the main game window, to different monitor etc."); + tooltip("Allow dragging tools outside the main game window, to different monitor etc.\nMay work smoother with the -oldflip game command line switch."); + + if (ImGui::Checkbox("Enable vsync", &options["vsync"])) + imgui_vsync(options["vsync"]); + tooltip("Disabling game vsync may affect performance with external windows,\nfor better or worse."); if (ImGui::Checkbox("Docking only while holding Shift", &options["docking_with_shift"])) { diff --git a/src/injector/main.cpp b/src/injector/main.cpp index d65dfab0d..20bf05764 100644 --- a/src/injector/main.cpp +++ b/src/injector/main.cpp @@ -360,12 +360,16 @@ bool inject_search(fs::path overlunky_path) return false; } -bool launch(fs::path exe_path, fs::path overlunky_path, bool& do_inject) +bool launch(fs::path exe_path, fs::path overlunky_path, bool& do_inject, bool& oldflip) { auto exe_dir = fs::canonical(exe_path).parent_path(); auto cwd = fs::current_path(); g_exe = exe_path.filename().string(); + std::string cmdline{"Spel2.exe"}; + if (oldflip) + cmdline = cmdline + " -oldflip"; + char dll_path[MAX_PATH] = {}; sprintf_s(dll_path, MAX_PATH, "%s", overlunky_path.string().c_str()); @@ -400,14 +404,14 @@ bool launch(fs::path exe_path, fs::path overlunky_path, bool& do_inject) STARTUPINFOA si{}; si.cb = sizeof(STARTUPINFO); - if (!do_inject && DetourCreateProcessWithDlls(NULL, (LPSTR)exe_path.string().c_str(), NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE, (LPVOID)child_env.c_str(), exe_dir.string().c_str(), &si, &pi, 1, dll_paths, NULL)) + if (!do_inject && DetourCreateProcessWithDlls((LPSTR)exe_path.string().c_str(), (LPSTR)cmdline.c_str(), NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE, (LPVOID)child_env.c_str(), exe_dir.string().c_str(), &si, &pi, 1, dll_paths, NULL)) { INFO("Game launched with DLL"); wait(); CloseHandle(pi.hThread); return true; } - else if (CreateProcess((LPSTR)exe_path.string().c_str(), NULL, NULL, NULL, TRUE, 0, (LPVOID)child_env.c_str(), exe_dir.string().c_str(), &si, &pi)) + else if (CreateProcess((LPSTR)exe_path.string().c_str(), (LPSTR)cmdline.c_str(), NULL, NULL, TRUE, 0, (LPVOID)child_env.c_str(), exe_dir.string().c_str(), &si, &pi)) { auto proc = Process{pi.hProcess, {g_exe, pi.dwProcessId}}; INFO("Game launched, injecting DLL..."); @@ -454,6 +458,7 @@ int main(int argc, char** argv) INFO("You can press ENTER to stop searching and try to launch the game from the parent folder."); INFO("Command line switches:"); INFO(" --launch_game [path] launch ../Spel2.exe, path/Spel2.exe, or a specific exe, and load OL with Detours"); + INFO(" --oldflip launch the game with -oldflip, may improve performance with external windows"); INFO(" --console keep console open to debug scripts etc"); INFO(" --inject use the old injection method instead of Detours with --launch_game"); INFO(" --info_dump output a bunch of game data to 'Spelunky 2/game_data'"); @@ -519,6 +524,7 @@ int main(int argc, char** argv) bool do_inject = GetCmdLineParam(cmd_line_parser, "inject", false); g_console = GetCmdLineParam(cmd_line_parser, "console", false); + bool oldflip = GetCmdLineParam(cmd_line_parser, "oldflip", false); if (info_dump) { do_inject = true; @@ -537,7 +543,7 @@ int main(int argc, char** argv) if (fs::exists(exe)) { - if (launch(exe, overlunky_path, do_inject)) + if (launch(exe, overlunky_path, do_inject, oldflip)) { FreeConsole(); return 0; @@ -547,7 +553,7 @@ int main(int argc, char** argv) { if (inject_search(overlunky_path)) { - launch(fs::canonical("../Spel2.exe"), overlunky_path, do_inject); + launch(fs::canonical("../Spel2.exe"), overlunky_path, do_inject, oldflip); } } FreeConsole(); From c5e16f8648a783e3cba5e72c082463914e8e835f Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 5 Oct 2023 17:11:37 +0300 Subject: [PATCH 05/16] expose update_state --- src/game_api/rpc.cpp | 16 ++++++++++++++++ src/game_api/rpc.hpp | 1 + src/game_api/script/lua_vm.cpp | 3 +++ 3 files changed, 20 insertions(+) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 0b53eda97..5dc75b42d 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -2058,3 +2058,19 @@ void set_boss_door_control_enabled(bool enable) else recover_mem("set_boss_door_control_enabled"); } + +void update_state() +{ + static size_t offset = 0; + if (offset == 0) + { + offset = get_address("state_refresh"); + } + if (offset != 0) + { + auto state = State::get().ptr(); + typedef void refresh_func(StateMemory*); + static refresh_func* rf = (refresh_func*)(offset); + rf(state); + } +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index 2b6c155fa..e283863aa 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -143,3 +143,4 @@ void activate_tiamat_position_hack(bool activate); void activate_crush_elevator_hack(bool activate); void activate_hundun_hack(bool activate); void set_boss_door_control_enabled(bool enable); +void update_state(); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index a9f789d9b..5a1b8c716 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2039,6 +2039,9 @@ end /// This will also prevent game crashing when there is no exit door when they are in level lua["set_boss_door_control_enabled"] = set_boss_door_control_enabled; + /// Run state update manually, i.e. simulate one logic frame + lua["update_state"] = update_state; + lua.create_named_table("INPUTS", "NONE", 0, "JUMP", 1, "WHIP", 2, "BOMB", 4, "ROPE", 8, "RUN", 16, "DOOR", 32, "MENU", 64, "JOURNAL", 128, "LEFT", 256, "RIGHT", 512, "UP", 1024, "DOWN", 2048); lua.create_named_table( From 5bde3ab42fde04b28efcd7bd730382643bfb9e1c Mon Sep 17 00:00:00 2001 From: Dregu Date: Thu, 5 Oct 2023 17:46:25 +0300 Subject: [PATCH 06/16] Revert "add important comment about add_money" Was supposed to be in the other pr anyway... This reverts commit 1cb1904c6f65569ad9ee42d9686f3f89490ca5bd. --- src/game_api/movable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_api/movable.hpp b/src/game_api/movable.hpp index 45b6fc692..889a6073e 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -163,7 +163,7 @@ class Movable : public Entity virtual void on_picked_up_by(Entity* entity_picking_up) = 0; virtual void drop(Entity* entity_to_drop) = 0; // also used when throwing - /// Adds or subtracts the specified amount of money to the movable's (player's) inventory. Shows the calculation animation in the HUD. Important: Updates the collected_money_count in the inventory, but doesn't add any treasure to the following array, which will lead to crashes during the transition, if you don't deal with it. (e.g. set `collected_money_count = collected_money_count - 1` afterwards) + /// Adds or subtracts the specified amount of money to the movable's (player's) inventory. Shows the calculation animation in the HUD. virtual void add_money(uint32_t money) = 0; virtual void apply_movement() = 0; // disable this function and things can't move, some spin in place From de6f81ae616052240519ebe501f4e160a15f87f2 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 03:55:03 +0300 Subject: [PATCH 07/16] add launcher warning --- src/injected/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/injected/main.cpp b/src/injected/main.cpp index 143ee6d74..569a15f4e 100644 --- a/src/injected/main.cpp +++ b/src/injected/main.cpp @@ -99,7 +99,7 @@ void attach_stdout(DWORD pid) freopen_s(&stream, "CONOUT$", "w", stdout); freopen_s(&stream, "CONOUT$", "w", stderr); // freopen_s(&stream, "CONIN$", "r", stdin); - INFO("Press Ctrl+C to detach this window from the process."); + INFO("Do not close this window or the game will also die. Press Ctrl+C to detach this window from the game process."); } void run() From d055481397ff2b9b3c60ddd015348281d0f01115 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 04:27:16 +0300 Subject: [PATCH 08/16] spawn logical door entities on grid and uncrashing --- src/injected/ui.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 521020880..ae8b8310d 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -1306,6 +1306,11 @@ std::string spawned_type() int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) { + static const ENT_TYPE also_snap[] = { + to_id("ENT_TYPE_LOGICAL_DOOR"), + to_id("ENT_TYPE_LOGICAL_BLACKMARKET_DOOR"), + to_id("ENT_TYPE_LOGICAL_PLATFORM_SPAWNER"), + }; bool flip = g_vx < -0.04f; std::pair cpos = UI::click_position(g_x, g_y); if (to_spawn.name.find("ENT_TYPE_CHAR") != std::string::npos) @@ -1332,7 +1337,11 @@ int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) else if (to_spawn.name.find("ENT_TYPE_LIQUID") == std::string::npos) { bool snap = options["snap_to_grid"]; - if (to_spawn.name.find("ENT_TYPE_FLOOR") != std::string::npos) + if (std::find(std::begin(also_snap), std::end(also_snap), to_spawn.id) != std::end(also_snap)) + { + snap = true; + } + else if (to_spawn.name.find("ENT_TYPE_FLOOR") != std::string::npos) { snap = true; g_vx = 0; @@ -1383,12 +1392,18 @@ int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) callbacks.push_back(cb); } } - else if (flip) + if (flip) { auto ent = get_entity_ptr(spawned); if (ent) ent->flags |= (1U << 16); } + if (to_spawn.id == also_snap[0]) + { + auto ent = get_entity_ptr(spawned)->as(); + ent->door_type = to_id("ENT_TYPE_FLOOR_DOOR_LAYER"); + ent->platform_type = to_id("ENT_TYPE_FLOOR_DOOR_PLATFORM"); + } if (!lock_entity && set_last) g_last_id = spawned; return spawned; From 9a22e3e62e1daba44a4acf6b8ce8b0ec60cb5318 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 04:58:27 +0300 Subject: [PATCH 09/16] add pot to containers --- src/game_api/custom_types.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game_api/custom_types.cpp b/src/game_api/custom_types.cpp index f0459dc5e..23fb7ff45 100644 --- a/src/game_api/custom_types.cpp +++ b/src/game_api/custom_types.cpp @@ -551,7 +551,8 @@ std::span get_custom_entity_types(CUSTOM_TYPE type) "ENT_TYPE_ITEM_DMCRATE", "ENT_TYPE_ITEM_PRESENT", "ENT_TYPE_ITEM_GHIST_PRESENT", - "ENT_TYPE_ITEM_ALIVE_EMBEDDED_ON_ICE"); + "ENT_TYPE_ITEM_ALIVE_EMBEDDED_ON_ICE", + "ENT_TYPE_ITEM_POT"); case CUSTOM_TYPE::CONVEYORBELT: return make_custom_entity_type_list( "ENT_TYPE_FLOOR_CONVEYORBELT_LEFT", From c544e6c00b0aa46e04de7bf27b218ac99a367c10 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 05:17:56 +0300 Subject: [PATCH 10/16] fix some hitboxes --- src/injected/ui.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index ae8b8310d..9b4a066b4 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -4192,7 +4192,7 @@ void render_path() draw_list->PathStroke(color, 0, thick); } -void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false) +void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false, bool rounded = false) { const auto type = ent->type->id; if (!type) @@ -4208,8 +4208,8 @@ void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false) UI::screen_position(render_position.first + ent->hitboxx + ent->offsetx, render_position.second + ent->hitboxy + ent->offsety); ImVec2 sorigin = screenify({originx, originy}); ImVec2 spos = screenify({(boxa_x + boxb_x) / 2, (boxa_y + boxb_y) / 2}); - ImVec2 sboxa = screenify({boxa_x, boxa_y}); - ImVec2 sboxb = screenify({boxb_x, boxb_y}); + ImVec2 sboxa = screenify({boxa_x, boxb_y}); + ImVec2 sboxb = screenify({boxb_x, boxa_y}); auto* draw_list = ImGui::GetWindowDrawList(); if (cross) { @@ -4235,9 +4235,9 @@ void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false) else draw_list->AddCircle(fix_pos(spos), sboxb.x - spos.x, color, 0, 2.0f); else if (filled) - draw_list->AddRectFilled(fix_pos(sboxa), fix_pos(sboxb), color); + draw_list->AddRectFilled(fix_pos(sboxa), fix_pos(sboxb), color, rounded ? 5.0f : 0.0f); else - draw_list->AddRect(fix_pos(sboxa), fix_pos(sboxb), color, 0.0f, 0, 2.0f); + draw_list->AddRect(fix_pos(sboxa), fix_pos(sboxb), color, rounded ? 5.0f : 0.0f, 0, 2.0f); if ((g_hitbox_mask & 0x8000) == 0) return; @@ -4513,7 +4513,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)g_state->camera_layer)) + for (auto entity : UI::get_entities_by({}, g_hitbox_mask, (LAYER)(peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer))) { auto ent = get_entity_ptr(entity); if (!ent) @@ -4563,16 +4563,21 @@ void render_clickhandler() to_id("ENT_TYPE_FLOOR_TELEPORTINGBORDER"), to_id("ENT_TYPE_FLOOR_SPIKES"), }; - for (auto entity : UI::get_entities_by(additional_fixed_entities, 0x180, LAYER::PLAYER)) // FLOOR | ACTIVEFLOOR + 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 { 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::PLAYER)) // LOGICAL + 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 { 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 + { + auto ent = get_entity_ptr(entity); + render_hitbox(ent, false, ImColor(255, 180, 45, 150), false, true); + } // OOB kill bounds auto* draw_list = ImGui::GetBackgroundDrawList(); From 4ed220ef2f5ab226f2c544f851b8e35411244e36 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 06:03:47 +0300 Subject: [PATCH 11/16] add logical door to special entity props --- src/injected/ui.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 9b4a066b4..9ca182e13 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -6927,6 +6927,20 @@ void render_entity_props(int uid, bool detached = false) ImGui::SameLine(); ImGui::Text("%s", theme_name(target->theme)); } + else if (entity_type == to_id("ENT_TYPE_LOGICAL_DOOR")) + { + auto door = entity->as(); + ImGui::Text("Door type:"); + ImGui::InputInt("##LogicalDoorDoor", (int*)&door->door_type, 1, 10); + ImGui::SameLine(); + ImGui::Text("%s", entity_names[door->door_type].c_str()); + ImGui::Text("Platform type:"); + ImGui::InputInt("##LogicalDoorPlatform", (int*)&door->platform_type, 1, 10); + ImGui::SameLine(); + ImGui::Text("%s", entity_names[door->platform_type].c_str()); + ImGui::Checkbox("Door spawned##LogicalDoorSpawned", &door->not_hidden); + ImGui::Checkbox("Platform spawned##LogicalDoorPlatformSpawned", &door->platform_spawned); + } else if (entity->type->search_flags & 0x7) // PLYAER, MOUNT, MONSTER { auto entity_pow = entity->as(); From d46c88e9d8d89363fec227e3c37103b5ae91ab09 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 07:30:02 +0300 Subject: [PATCH 12/16] add entity info option, fix some hitbox stuff --- src/injected/ui.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 9ca182e13..f2e6399d8 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -95,6 +95,7 @@ std::map default_keys{ {"toggle_disable_pause", OL_KEY_CTRL | OL_KEY_SHIFT | 'P'}, {"toggle_grid", OL_KEY_CTRL | OL_KEY_SHIFT | 'G'}, {"toggle_hitboxes", OL_KEY_CTRL | OL_KEY_SHIFT | 'H'}, + {"toggle_entityinfo", OL_KEY_CTRL | OL_KEY_SHIFT | 'E'}, {"toggle_hud", OL_KEY_CTRL | 'H'}, {"toggle_lights", OL_KEY_CTRL | 'L'}, {"toggle_ghost", OL_KEY_CTRL | 'O'}, @@ -327,6 +328,7 @@ std::map options = { {"disable_pause", false}, {"draw_grid", false}, {"draw_hitboxes", false}, + {"draw_entityinfo", false}, {"enable_unsafe_scripts", false}, {"warp_increments_level_count", true}, {"warp_transition", false}, @@ -2812,6 +2814,10 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) { options["draw_hitboxes"] = !options["draw_hitboxes"]; } + else if (pressed("toggle_entityinfo", wParam)) + { + options["draw_entityinfo"] = !options["draw_entityinfo"]; + } else if (pressed("toggle_grid", wParam)) { options["draw_grid"] = !options["draw_grid"]; @@ -4192,6 +4198,45 @@ void render_path() draw_list->PathStroke(color, 0, thick); } +std::string entity_tooltip(Entity* hovered) +{ + std::string coords; + coords += fmt::format("{}, {} ({:.2f}, {:.2f})", hovered->uid, entity_names[hovered->type->id], hovered->abs_x == -FLT_MAX ? hovered->x : hovered->abs_x, hovered->abs_y == -FLT_MAX ? hovered->y : hovered->abs_y); + if (hovered->type->id == to_id("ENT_TYPE_ITEM_POT") && hovered->as()->inside > 0) + coords += fmt::format(" ({})", entity_names[hovered->as()->inside]); + else if (hovered->type->id == to_id("ENT_TYPE_ITEM_PRESENT") || hovered->type->id == to_id("ENT_TYPE_ITEM_GHIST_PRESENT")) + coords += fmt::format(" ({})", entity_names[hovered->as()->inside]); + else if (hovered->type->id == to_id("ENT_TYPE_ITEM_COFFIN")) + coords += fmt::format(" ({})", entity_names[hovered->as()->inside]); + else if (hovered->type->id == to_id("ENT_TYPE_ITEM_CRATE") || hovered->type->id == to_id("ENT_TYPE_ITEM_DMCRATE") || hovered->type->id == to_id("ENT_TYPE_ITEM_ALIVE_EMBEDDED_ON_ICE")) + coords += fmt::format(" ({})", entity_names[hovered->as()->inside]); + else if (hovered->type->id == to_id("ENT_TYPE_ITEM_CHEST")) + { + auto chest = hovered->as(); + if (chest->bomb) + coords += " (BOMB)"; + if (chest->leprechaun) + coords += " (LEPRECHAUN)"; + } + else if (hovered->type->search_flags & 7) + { + auto ent = hovered->as(); + coords += fmt::format(" ({} HP)", ent->health); + } + if (hovered->overlay) + { + coords += fmt::format("\nON: {}, {} ({:.2f}, {:.2f})", hovered->overlay->uid, entity_names[hovered->overlay->type->id], hovered->overlay->abs_x == -FLT_MAX ? hovered->overlay->x : hovered->overlay->abs_x, hovered->overlay->abs_y == -FLT_MAX ? hovered->overlay->y : hovered->overlay->abs_y); + } + if (hovered->type->search_flags & 15 && hovered->as()->last_owner_uid > -1) + { + auto ent = hovered->as(); + auto owner = get_entity_ptr(ent->last_owner_uid); + if (owner) + coords += fmt::format("\nOWNER: {}, {} ({:.2f}, {:.2f})", owner->uid, entity_names[owner->type->id], owner->abs_x == -FLT_MAX ? owner->x : owner->abs_x, owner->abs_y == -FLT_MAX ? owner->y : owner->abs_y); + } + return coords; +} + void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false, bool rounded = false) { const auto type = ent->type->id; @@ -4239,6 +4284,9 @@ void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false, else draw_list->AddRect(fix_pos(sboxa), fix_pos(sboxb), color, rounded ? 5.0f : 0.0f, 0, 2.0f); + if (options["draw_entityinfo"] && !cross && !filled) + draw_list->AddText(fix_pos(ImVec2(sboxa.x, sboxb.y)), ImColor(1.0f, 1.0f, 1.0f, 0.8f), entity_tooltip(ent).c_str()); + if ((g_hitbox_mask & 0x8000) == 0) return; @@ -4528,7 +4576,8 @@ void render_clickhandler() if (!UI::has_active_render(ent) && (ent->type->search_flags & 0x7000) == 0) continue; - render_hitbox(ent, false, ImColor(0, 255, 255, 150)); + if ((ent->type->search_flags & 1) == 0 || ent->as()->ai) + render_hitbox(ent, false, ImColor(0, 255, 255, 150)); } if ((g_hitbox_mask & 0x1) != 0) { @@ -4606,7 +4655,7 @@ void render_clickhandler() if (hovered != nullptr) { render_hitbox(hovered, true, ImColor(50, 50, 255, 200)); - coords += fmt::format("\n{}, {}", hovered->uid, entity_names[hovered->type->id]); + coords += "\n" + entity_tooltip(hovered); } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {4.0f, 4.0f}); tooltip(coords.c_str(), true); @@ -4625,7 +4674,7 @@ void render_clickhandler() auto ent = get_entity_ptr(entity); if (ent) { - if (ent->layer == g_state->camera_layer) + if (ent->layer == (peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer)) render_hitbox(ent, false, front_col, true); else render_hitbox(ent, false, back_col, true); @@ -5445,10 +5494,12 @@ void render_options() ImGui::Checkbox("Spawn floor decorated##Decorate", &options["spawn_floor_decorated"]); tooltip("Add decorations to spawned floor."); ImGui::Checkbox("Draw hitboxes##DrawEntityBox", &options["draw_hitboxes"]); - tooltip("Draw hitboxes for all movable and hovered entities.", "toggle_hitboxes"); + tooltip("Draw hitboxes for all movable and hovered entities. Also mouse tooltips.", "toggle_hitboxes"); ImGui::SameLine(); ImGui::Checkbox("interpolated##DrawRealBox", &options["draw_hitboxes_interpolated"]); tooltip("Use interpolated render position for smoother hitboxes on hifps.\nActual game logic is not interpolated like this though."); + ImGui::Checkbox("Draw entity info##DrawEntityInfo", &options["draw_entityinfo"]); + tooltip("Draw entity names, uids and some random stuff next to the hitboxes.", "toggle_entityinfo"); ImGui::Checkbox("Smooth camera dragging", &options["smooth_camera"]); tooltip("Smooth camera movement when dragging, unless paused."); ImGui::SliderFloat("Camera speed##DragSpeed", &g_camera_speed, 1.0f, 5.0f); From bc90171879d179c70bc45589547d0073fb46bf0d Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 11:20:44 +0300 Subject: [PATCH 13/16] update some comments --- src/game_api/entities_logical.hpp | 7 +++++-- src/game_api/script/lua_vm.cpp | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/game_api/entities_logical.hpp b/src/game_api/entities_logical.hpp index 3d5610fd4..4be0598cd 100644 --- a/src/game_api/entities_logical.hpp +++ b/src/game_api/entities_logical.hpp @@ -30,10 +30,13 @@ class ShootingStarSpawner : public Entity class LogicalDoor : public Entity { public: + /// Spawns this entity when not covered by floor. Must be initialized to valid ENT_TYPE before revealed, or crashes the game. ENT_TYPE door_type; - ENT_TYPE platform_type; // always 37? yeah, that's the floor platform... + /// Spawns this entity below when tile below is uncovered. Doesn't spawn anything if it was never covered by floor, unless platform_spawned is set to false. Must be initialized to valid ENT_TYPE before revealed, or crashes the game. + ENT_TYPE platform_type; + /// Set automatically when not covered by floor. bool not_hidden; - /// Is set true when you bomb the door, no matter what door, can't be reset + /// Set automatically when tile below is not covered by floor. Unset to force the platform to spawn if it was never covered in the first place. bool platform_spawned; bool unk5; bool unk6; diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 5a1b8c716..d5a06c22d 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2014,24 +2014,24 @@ end return AABB(ax + index * w + 0.02f * f, ay, ax + index * w + w - 0.02f * f, ay - h); }; + /// Olmec cutscene moves Olmec and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and Olmec will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with Olmec in the hole. lua["set_olmec_cutscene_enabled"] = set_olmec_cutscene_enabled; - /// Tiamat cutscene is also responsible for locking the exit door - /// So you may need to close it yourself if you still want to be required to kill Tiamat + /// Tiamat cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want Tiamat kill to be required lua["set_tiamat_cutscene_enabled"] = set_tiamat_cutscene_enabled; /// Activate custom variables for position used for detecting the player (normally hardcoded) - /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Tiamat entity, recommending `set_post_entity_spawn` + /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Tiamat entity, recommending set_post_entity_spawn /// default game values are: attack_x = 17.5 attack_y = 62.5 lua["activate_tiamat_position_hack"] = activate_tiamat_position_hack; /// Activate custom variables for speed and y coordinate limit for crushing elevator - /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each CrushElevator entity, recommending `set_post_entity_spawn` + /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each CrushElevator entity, recommending set_post_entity_spawn /// default game values are: speed = 0.0125, y_limit = 98.5 lua["activate_crush_elevator_hack"] = activate_crush_elevator_hack; /// Activate custom variables for y coordinate limit for hundun and spawn of it's heads - /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Hundun entity, recommending `set_post_entity_spawn` + /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Hundun entity, recommending set_post_entity_spawn /// default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 lua["activate_hundun_hack"] = activate_hundun_hack; @@ -2039,7 +2039,7 @@ end /// This will also prevent game crashing when there is no exit door when they are in level lua["set_boss_door_control_enabled"] = set_boss_door_control_enabled; - /// Run state update manually, i.e. simulate one logic frame + /// Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. lua["update_state"] = update_state; lua.create_named_table("INPUTS", "NONE", 0, "JUMP", 1, "WHIP", 2, "BOMB", 4, "ROPE", 8, "RUN", 16, "DOOR", 32, "MENU", 64, "JOURNAL", 128, "LEFT", 256, "RIGHT", 512, "UP", 1024, "DOWN", 2048); From cf009b6c7a9181e9fb674bd6b307364a78616904 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 17:06:56 +0300 Subject: [PATCH 14/16] pattern and functions to change target framerate --- src/game_api/rpc.cpp | 48 ++++++++++++++++++++++++++++++++++ src/game_api/rpc.hpp | 4 +++ src/game_api/script/lua_vm.cpp | 12 +++++++++ src/game_api/search.cpp | 13 +++++++++ 4 files changed, 77 insertions(+) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 5dc75b42d..4c197abe8 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -2074,3 +2074,51 @@ void update_state() rf(state); } } + +void set_frametime(std::optional frametime) +{ + static size_t offset = 0; + if (offset == 0) + offset = get_address("engine_frametime"); + if (offset != 0) + { + if (frametime.has_value()) + write_mem_recoverable("engine_frametime", offset, frametime.value(), true); + else + recover_mem("engine_frametime"); + } +} + +std::optional get_frametime() +{ + static size_t offset = 0; + if (offset == 0) + offset = get_address("engine_frametime"); + if (offset != 0) + return memory_read(offset); + return std::nullopt; +} + +void set_frametime_inactive(std::optional frametime) +{ + static size_t offset = 0; + if (offset == 0) + offset = get_address("engine_frametime") + 0x10; + if (offset != 0) + { + if (frametime.has_value()) + write_mem_recoverable("engine_frametime_inactive", offset, frametime.value(), true); + else + recover_mem("engine_frametime_inactive"); + } +} + +std::optional get_frametime_inactive() +{ + static size_t offset = 0; + if (offset == 0) + offset = get_address("engine_frametime") + 0x10; + if (offset != 0) + return memory_read(offset); + return std::nullopt; +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index e283863aa..91ac1f8e8 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -144,3 +144,7 @@ void activate_crush_elevator_hack(bool activate); void activate_hundun_hack(bool activate); void set_boss_door_control_enabled(bool enable); void update_state(); +void set_frametime(std::optional frametime); +std::optional get_frametime(); +void set_frametime_inactive(std::optional frametime); +std::optional get_frametime_inactive(); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index d5a06c22d..3227af193 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2042,6 +2042,18 @@ end /// Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. lua["update_state"] = update_state; + /// Set engine target frametime (1/framerate, default 1/60). Set to 0 to go as fast as possible. Call without arguments to reset. + lua["set_frametime"] = set_frametime; + + /// Get engine target frametime (1/framerate, default 1/60). + lua["get_frametime"] = get_frametime; + + /// Set engine target frametime when game is unfocused (1/framerate, default 1/33). Set to 0 to go as fast as possible. Call without arguments to reset. + lua["set_frametime_unfocused"] = set_frametime_inactive; + + /// Get engine target frametime when game is unfocused (1/framerate, default 1/33). + lua["get_frametime_unfocused"] = get_frametime_inactive; + lua.create_named_table("INPUTS", "NONE", 0, "JUMP", 1, "WHIP", 2, "BOMB", 4, "ROPE", 8, "RUN", 16, "DOOR", 32, "MENU", 64, "JOURNAL", 128, "LEFT", 256, "RIGHT", 512, "UP", 1024, "DOWN", 2048); lua.create_named_table( diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 1923bec9a..f27008208 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -1840,6 +1840,19 @@ std::unordered_map g_address_rules{ .at_exe() .function_start(), }, + { + /* it's a static double, just find something that reads it + this+0x08 is clearly some kind of framerate related double, cause it's 60, but don't know what it does + this+0x10 is hopefully unfocused frametime for the other function, but maybe it needs own pattern + 22d12248 00 00 00 double 0.01666666753590107 + 20 11 11 + 91 3f */ + "engine_frametime"sv, + PatternCommandBuffer{} + .find_after_inst("48 8d 04 0a 48 85 d2 48 0f 44 c2 48 85 c9 48 0f 44 c1 66 0f 28 c8"_gh) + .decode_pc(4) + .at_exe(), + }, { // Borrowed from Playlunky logger.cpp "game_log_function"sv, From 12f65daba8e4ff104042792eff5a72f508e2a3d3 Mon Sep 17 00:00:00 2001 From: Dregu Date: Fri, 6 Oct 2023 20:52:04 +0300 Subject: [PATCH 15/16] add fps controls to ui, also pre_update pause --- src/game_api/flags.hpp | 1 + src/game_api/script.cpp | 4 + src/game_api/script.hpp | 2 + src/game_api/script/lua_vm.cpp | 4 +- src/game_api/script/script_impl.cpp | 33 +++++++ src/game_api/script/script_impl.hpp | 3 + src/injected/ui.cpp | 144 ++++++++++++++++++++++------ 7 files changed, 162 insertions(+), 29 deletions(-) diff --git a/src/game_api/flags.hpp b/src/game_api/flags.hpp index 88b31f807..d75ce93d4 100644 --- a/src/game_api/flags.hpp +++ b/src/game_api/flags.hpp @@ -754,4 +754,5 @@ const char* pause_types[]{ "8: ?", "16: ?", "32: Ankh (smooth camera, janky audio)", + "Freeze on PRE_UPDATE", }; diff --git a/src/game_api/script.cpp b/src/game_api/script.cpp index 61fe81965..40790dadf 100644 --- a/src/game_api/script.cpp +++ b/src/game_api/script.cpp @@ -128,3 +128,7 @@ void SpelunkyScript::render_options() { m_Impl->Lock()->render_options(); } +std::string SpelunkyScript::execute(std::string code) +{ + return m_Impl->Lock()->execute(code); +} diff --git a/src/game_api/script.hpp b/src/game_api/script.hpp index 6a993f5be..889f7bb82 100644 --- a/src/game_api/script.hpp +++ b/src/game_api/script.hpp @@ -98,6 +98,8 @@ class SpelunkyScript void draw(ImDrawList* dl); void render_options(); + std::string execute(std::string code); + private: std::unique_ptr m_Impl; }; diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 3227af193..efedc51fd 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2042,13 +2042,13 @@ end /// Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. lua["update_state"] = update_state; - /// Set engine target frametime (1/framerate, default 1/60). Set to 0 to go as fast as possible. Call without arguments to reset. + /// Set engine target frametime (1/framerate, default 1/60). Always capped by your GPU max FPS / VSync. To run the engine faster than rendered FPS, try update_state. Set to 0 to go as fast as possible. Call without arguments to reset. lua["set_frametime"] = set_frametime; /// Get engine target frametime (1/framerate, default 1/60). lua["get_frametime"] = get_frametime; - /// Set engine target frametime when game is unfocused (1/framerate, default 1/33). Set to 0 to go as fast as possible. Call without arguments to reset. + /// Set engine target frametime when game is unfocused (1/framerate, default 1/33). Always capped by the engine frametime. Set to 0 to go as fast as possible. Call without arguments to reset. lua["set_frametime_unfocused"] = set_frametime_inactive; /// Get engine target frametime when game is unfocused (1/framerate, default 1/33). diff --git a/src/game_api/script/script_impl.cpp b/src/game_api/script/script_impl.cpp index a1663b31f..dfd058b6e 100644 --- a/src/game_api/script/script_impl.cpp +++ b/src/game_api/script/script_impl.cpp @@ -207,3 +207,36 @@ const std::filesystem::path& ScriptImpl::get_root_path() const { return script_folder; } + +std::string ScriptImpl::execute(std::string str) +{ + if (!code.starts_with("return")) + { + std::string ret = execute_raw("return " + str); + if (!ret.starts_with("sol: ")) + { + return ret; + } + } + return execute_raw(std::move(str)); +} +std::string ScriptImpl::execute_raw(std::string str) +{ + try + { + auto ret = execute_lua(lua, str); + if (ret.get_type() == sol::type::nil || ret.get_type() == sol::type::none) + { + return ""; + } + else + { + sol::function serpent = lua["serpent"]["block"]; + return serpent(ret); + } + } + catch (const sol::error& e) + { + return e.what(); + } +} diff --git a/src/game_api/script/script_impl.hpp b/src/game_api/script/script_impl.hpp index f2373960e..85a11ada4 100644 --- a/src/game_api/script/script_impl.hpp +++ b/src/game_api/script/script_impl.hpp @@ -44,4 +44,7 @@ class ScriptImpl : public LockableLuaBackend virtual const char* get_path() const override; virtual const char* get_root() const override; virtual const std::filesystem::path& get_root_path() const override; + + std::string execute(std::string str); + std::string execute_raw(std::string str); }; diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index f2e6399d8..ab3bda02f 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -266,7 +266,7 @@ std::vector g_selected_ids; bool set_focus_entity = false, set_focus_world = false, set_focus_zoom = false, set_focus_finder = false, set_focus_uid = false, scroll_to_entity = false, scroll_top = false, click_teleport = false, throw_held = false, paused = false, show_app_metrics = false, lock_entity = false, lock_player = false, freeze_last = false, freeze_level = false, freeze_total = false, hide_ui = false, - enable_noclip = false, load_script_dir = true, load_packs_dir = false, enable_camp_camera = true, enable_camera_bounds = true, freeze_quest_yang = false, freeze_quest_sisters = false, freeze_quest_horsing = false, freeze_quest_sparrow = false, freeze_quest_tusk = false, freeze_quest_beg = false, run_finder = false, in_menu = false, zooming = false, g_inv = false, edit_last_id = false, edit_achievements = false, peek_layer = false; + enable_noclip = false, load_script_dir = true, load_packs_dir = false, enable_camp_camera = true, enable_camera_bounds = true, freeze_quest_yang = false, freeze_quest_sisters = false, freeze_quest_horsing = false, freeze_quest_sparrow = false, freeze_quest_tusk = false, freeze_quest_beg = false, run_finder = false, in_menu = false, zooming = false, g_inv = false, edit_last_id = false, edit_achievements = false, peek_layer = false, pause_updates = true; std::optional quest_yang_state, quest_sisters_state, quest_horsing_state, quest_sparrow_state, quest_tusk_state, quest_beg_state; Entity* g_entity = 0; Entity* g_held_entity = 0; @@ -353,6 +353,9 @@ std::map options = { {"console_alt_keys", false}, {"vsync", true}}; +double g_engine_fps = 60.0, g_unfocused_fps = 33.0; +double fps_min = 0, fps_max = 600.0; + bool g_speedhack_hooked = false; float g_speedhack_multiplier = 1.0; float g_speedhack_old_multiplier = 1.0; @@ -2088,12 +2091,55 @@ void force_kits() } } +bool toggle_pause() +{ + g_pause_at = -1; + g_pause_time = -1; + if (g_pause_type & 0x40) + { + if (!paused) + { + g_pause_time = g_state->time_startup; + pause_updates = true; + paused = true; + } + else + { + g_pause_time = -1; + pause_updates = false; + paused = false; + } + g_ui_scripts["pause"]->execute(fmt::format("pause_at = {}", g_pause_time)); + } + else + { + if (g_state->pause == 0) + { + g_state->pause = (uint8_t)g_pause_type; + paused = true; + } + else + { + g_state->pause = 0; + paused = false; + } + } + return paused; +} + void frame_advance() { - if (g_state->pause == 0 && g_pause_at != -1 && (unsigned)g_pause_at <= UI::get_frame_count()) + if (g_pause_type & 0x40) { - g_state->pause = (uint8_t)g_pause_type; - g_pause_at = -1; + g_ui_scripts["pause"]->execute(fmt::format("pause_at = {}", g_pause_time)); + } + else + { + if (g_state->pause == 0 && g_pause_at != -1 && (unsigned)g_pause_at <= UI::get_frame_count()) + { + g_state->pause = (uint8_t)g_pause_type; + g_pause_at = -1; + } } } @@ -2836,17 +2882,7 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) } else if (pressed("toggle_pause", wParam)) { - g_pause_at = -1; - if (g_state->pause == 0) - { - g_state->pause = (uint8_t)g_pause_type; - paused = true; - } - else - { - g_state->pause = 0; - paused = false; - } + toggle_pause(); } else if (pressed("toggle_hud", wParam)) { @@ -2858,10 +2894,21 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) } else if (pressed("frame_advance", wParam) || pressed("frame_advance_alt", wParam)) { - if (g_state->pause == (uint8_t)g_pause_type) + if (g_pause_type & 0x40) { - g_pause_at = UI::get_frame_count() + 1; - g_state->pause = 0; + if (pause_updates) + { + g_pause_time = g_state->time_startup + 1; + g_ui_scripts["pause"]->execute(fmt::format("pause_at = {}", g_pause_time)); + } + } + else + { + if (g_state->pause == (uint8_t)g_pause_type) + { + g_pause_at = UI::get_frame_count() + 1; + g_state->pause = 0; + } } } else if (pressed("toggle_disable_pause", wParam)) @@ -5446,6 +5493,7 @@ void render_options() hook_savegame(); } tooltip("Enable this if you want to keep your\nsave game unaffected by tomfoolery."); + if (ImGui::SliderFloat("Speedhack##SpeedHack", &g_speedhack_multiplier, 0.1f, 5.f)) { if (should_speedhack()) @@ -5455,6 +5503,27 @@ void render_options() tooltip("Slow down or speed up everything,\nlike in Cheat Engine.", "speedhack_decrease"); ImGui::Checkbox("Fast menus and transitions##SpeedHackMenu", &options["speedhack"]); tooltip("Enable 10x speedhack automatically when not controlling a character.", "toggle_speedhack_auto"); + + if (ImGui::SliderScalar("Engine FPS##EngineFPS", ImGuiDataType_Double, &g_engine_fps, &fps_min, &fps_max, "%f")) + g_Console.get()->execute(fmt::format("set_frametime({})", g_engine_fps == 0 ? 0 : 1.0 / g_engine_fps)); + tooltip("Set target engine FPS. Always capped by max GPU FPS.\n0 = as fast as it can go."); + ImGui::SameLine(); + if (ImGui::Button("Reset##ResetFPS")) + { + g_engine_fps = 60.0; + g_Console.get()->execute("set_frametime()"); + } + + if (ImGui::SliderScalar("Unfocused FPS##UnfocusedFPS", ImGuiDataType_Double, &g_unfocused_fps, &fps_min, &fps_max, "%f")) + g_Console.get()->execute(fmt::format("return set_frametime_unfocused({})", g_unfocused_fps == 0 ? 0 : 1.0 / g_engine_fps)); + tooltip("Set target unfocused FPS. Always capped by max Engine FPS.\n0 = as fast as it can go."); + ImGui::SameLine(); + if (ImGui::Button("Reset##ResetUnfocusedFPS")) + { + g_unfocused_fps = 33.0; + g_Console.get()->execute("set_frametime_unfocused()"); + } + bool void_mode = g_ui_scripts["void"]->is_enabled(); if (ImGui::Checkbox("Void sandbox mode", &void_mode)) { @@ -5585,7 +5654,7 @@ void render_options() if (submenu("Frame advance / Engine pause type")) { ImGui::PushID("PauseType"); - for (int i = 1; i < 6; i++) + for (int i = 1; i < 7; i++) { ImGui::CheckboxFlags(pause_types[i], &g_pause_type, (int)std::pow(2, i)); } @@ -7609,12 +7678,7 @@ void render_game_props() gamestate += "Pause "; ImGui::LabelText("Game state", "%s", gamestate.c_str()); if (ImGui::Checkbox("Pause game engine##PauseSim", &paused)) - { - if (paused) - g_state->pause = (uint8_t)g_pause_type; - else - g_state->pause = 0; - } + toggle_pause(); tooltip("Pause time while still being able to teleport, spawn and move entities", "toggle_pause"); endmenu(); } @@ -8329,13 +8393,27 @@ AABB player_hud_position(int p = 0) void render_prohud() { + static float engine_fps = 0; + static std::chrono::time_point last_time; + static uint32_t last_frame; + auto this_frame = UI::get_frame_count(); + auto frame_delta = this_frame - last_frame; + auto this_time = std::chrono::system_clock::now(); + auto time_delta = std::chrono::duration(this_time - last_time); + if (time_delta.count() > 1.0f) + { + engine_fps = static_cast(frame_delta) / time_delta.count(); + last_frame = this_frame; + last_time = this_time; + } + auto io = ImGui::GetIO(); auto base = ImGui::GetMainViewport(); ImDrawList* dl = ImGui::GetBackgroundDrawList(base); auto topmargin = 0.0f; if (options["menu_ui"] && !hide_ui) topmargin = ImGui::GetTextLineHeight(); - std::string buf = fmt::format("TIMER:{}/{} FRAME:{:#06} TOTAL:{:#06} LEVEL:{:#06} COUNT:{} SCREEN:{} SIZE:{}x{} PAUSE:{} FPS:{:.0f}", format_time(g_state->time_level), format_time(g_state->time_total), UI::get_frame_count(), g_state->time_total, g_state->time_level, g_state->level_count, g_state->screen, g_state->w, g_state->h, g_state->pause, io.Framerate); + std::string buf = fmt::format("TIMER:{}/{} FRAME:{:#06} START:{:#06} TOTAL:{:#06} LEVEL:{:#06} COUNT:{} SCREEN:{} SIZE:{}x{} PAUSE:{} FPS:{:.2f} ENGINE:{:.2f} TARGET:{:.2f}", format_time(g_state->time_level), format_time(g_state->time_total), UI::get_frame_count(), g_state->time_startup, g_state->time_total, g_state->time_level, g_state->level_count, g_state->screen, g_state->w, g_state->h, g_state->pause, io.Framerate, engine_fps, g_engine_fps); ImVec2 textsize = ImGui::CalcTextSize(buf.c_str()); dl->AddText({base->Pos.x + base->Size.x / 2 - textsize.x / 2, base->Pos.y + 2 + topmargin}, ImColor(1.0f, 1.0f, 1.0f, .5f), buf.c_str()); @@ -8344,7 +8422,7 @@ void render_prohud() dl->AddText({base->Pos.x + base->Size.x / 2 - textsize.x / 2, base->Pos.y + textsize.y + 4 + topmargin}, ImColor(1.0f, 1.0f, 1.0f, .5f), buf.c_str()); auto type = spawned_type(); - buf = fmt::format("{}", (type == "" ? "" : fmt::format("SPAWN:{}", type))); + buf = fmt::format("{}", (type == "" ? "" : fmt::format("SPAWN:{}{}", type, options["snap_to_grid"] ? " SNAP" : ""))); textsize = ImGui::CalcTextSize(buf.c_str()); dl->AddText({base->Pos.x + base->Size.x / 2 - textsize.x / 2, base->Pos.y + textsize.y * 2 + 4 + topmargin}, ImColor(1.0f, 1.0f, 1.0f, .5f), buf.c_str()); @@ -8453,6 +8531,18 @@ void imgui_init(ImGuiContext*) windows["tool_texture"] = new Window({"Texture viewer", is_tab_detached("tool_texture"), is_tab_open("tool_texture")}); // windows["tool_sound"] = new Window({"Sound player", is_tab_detached("tool_sound"), is_tab_open("tool_sound")}); + if (g_ui_scripts.find("pause") == g_ui_scripts.end()) + { + SpelunkyScript* script = new SpelunkyScript( + R"( +set_callback(function() return pause_at and pause_at ~= -1 and state.time_startup >= pause_at end, ON.PRE_UPDATE) + )", + "pause", + g_SoundManager.get(), + g_Console.get(), + true); + g_ui_scripts["pause"] = std::unique_ptr(script); + } if (g_ui_scripts.find("dark") == g_ui_scripts.end()) { SpelunkyScript* script = new SpelunkyScript( From 9ef2e06e605be8abb7f7580d436e3f15e905a05f Mon Sep 17 00:00:00 2001 From: Dregu Date: Sat, 7 Oct 2023 10:31:23 +0300 Subject: [PATCH 16/16] comment --- src/game_api/flags.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_api/flags.hpp b/src/game_api/flags.hpp index d75ce93d4..79aaa2c53 100644 --- a/src/game_api/flags.hpp +++ b/src/game_api/flags.hpp @@ -754,5 +754,5 @@ const char* pause_types[]{ "8: ?", "16: ?", "32: Ankh (smooth camera, janky audio)", - "Freeze on PRE_UPDATE", + "Freeze on PRE_UPDATE", // this is not a real state.pause flag, it's only used by ui.cpp for magic };