From 5dfd248a7e191d6fb1faf043edd8bb074f823d34 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 21 Jan 2024 18:02:17 +0100 Subject: [PATCH 01/23] button thing --- src/game_api/entities_fx.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_api/entities_fx.hpp b/src/game_api/entities_fx.hpp index 8f629b1f0..fbc6d4efa 100644 --- a/src/game_api/entities_fx.hpp +++ b/src/game_api/entities_fx.hpp @@ -76,7 +76,7 @@ class Button : public Movable int8_t seen; int8_t unknown11; int16_t padding3; - int64_t unknown12; + bool (*check_leader_autorun_status)(); // used to create correct graphics in the basecamp tutorial }; class FxTornJournalPage : public Movable From 088ef8ee16add0474d5354d04851ae13c060790a Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:02:36 +0100 Subject: [PATCH 02/23] expose `level_config` thru `LevelGenSystem` --- docs/game_data/spel2.lua | 1 + docs/src/includes/_types.md | 1 + src/game_api/level_api.hpp | 4 ++-- src/game_api/script/usertypes/level_lua.cpp | 9 ++++++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 0794e81a2..7f20fe4fe 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -4764,6 +4764,7 @@ function MovableBehavior:get_state_id() end ---@field flags integer ---@field flags2 integer ---@field flags3 integer + ---@field level_config integer[] ---@class PostRoomGenerationContext ---@field set_room_template fun(self, x: integer, y: integer, layer: LAYER, room_template: ROOM_TEMPLATE): boolean @Set the room template at the given index and layer, returns `false` if the index is outside of the level. diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index 6f9b0877e..57dbd0104 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -1345,6 +1345,7 @@ array<[ThemeInfo](#ThemeInfo), 18> | [themes](https://github.com/spelunky- int | [flags](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flags) | int | [flags2](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flags2) | int | [flags3](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flags3) | +array<int> | [level_config](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=level_config) | ## Lighting types diff --git a/src/game_api/level_api.hpp b/src/game_api/level_api.hpp index f85562239..6e3dbdb6f 100644 --- a/src/game_api/level_api.hpp +++ b/src/game_api/level_api.hpp @@ -122,7 +122,7 @@ struct LevelGenData union { - std::array level_config; + std::array level_config; struct { uint32_t back_room_chance; @@ -142,7 +142,7 @@ struct LevelGenData uint32_t machine_rewardroom_chance; uint32_t max_liquid_particles; uint32_t flagged_liquid_rooms; - uint32_t unknown_config; // + uint32_t unknown_config; // padding }; }; diff --git a/src/game_api/script/usertypes/level_lua.cpp b/src/game_api/script/usertypes/level_lua.cpp index 0799be3ba..1fe885fef 100644 --- a/src/game_api/script/usertypes/level_lua.cpp +++ b/src/game_api/script/usertypes/level_lua.cpp @@ -29,6 +29,7 @@ #include "script/handle_lua_function.hpp" // for handle_function #include "script/lua_backend.hpp" // for LuaBackend, LevelGenCal... #include "script/safe_cb.hpp" // for make_safe_cb +#include "script/sol_helper.hpp" // for ZeroIndexArray #include "state.hpp" // for State, StateMemory, enu... #include "state_structs.hpp" // for QuestsInfo, Camera, Que... @@ -1372,6 +1373,9 @@ void register_usertypes(sol::state& lua) /// kept for backward compatibility, don't use, check LevelGenSystem.exit_doors lua.new_usertype("DoorCoords", sol::no_constructor, "door1_x", &DoorCoords::door1_x, "door1_y", &DoorCoords::door1_y, "door2_x", &DoorCoords::door2_x, "door2_y", &DoorCoords::door2_y); + auto level_config = [](LevelGenSystem& level_gen) + { return ZeroIndexArray(level_gen.data->level_config); }; + /// Data relating to level generation, changing anything in here from ON.LEVEL or later will likely have no effect, used in StateMemory lua.new_usertype( "LevelGenSystem", @@ -1403,7 +1407,10 @@ void register_usertypes(sol::state& lua) "flags2", &LevelGenSystem::flags2, "flags3", - &LevelGenSystem::flags3); + &LevelGenSystem::flags3, + "level_config", + sol::property([&level_config](LevelGenSystem& lg) // -> array + { return level_config(lg) /**/; })); /// Context received in ON.POST_ROOM_GENERATION. /// Used to change the room templates in the level and other shenanigans that affect level gen. From 8c2ddc0c51a5df671728e5bafe87f38e3518a264 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Wed, 6 Mar 2024 22:01:09 +0100 Subject: [PATCH 03/23] Make sure there is only one `Memory` struct and safeguard it's members --- src/game_api/drops.cpp | 8 +++--- src/game_api/game_patches.cpp | 20 +++++++------- src/game_api/memory.cpp | 20 +++++++++++--- src/game_api/memory.hpp | 44 +++++++++++++++---------------- src/game_api/movable_behavior.cpp | 2 +- src/game_api/rpc.cpp | 18 ++++++------- src/game_api/search.cpp | 14 +++++----- src/game_api/state.cpp | 2 +- src/game_api/virtual_table.cpp | 4 +-- 9 files changed, 73 insertions(+), 59 deletions(-) diff --git a/src/game_api/drops.cpp b/src/game_api/drops.cpp index 65d99b36d..ff4735526 100644 --- a/src/game_api/drops.cpp +++ b/src/game_api/drops.cpp @@ -335,9 +335,9 @@ void set_drop_chance(int32_t dropchance_id, uint32_t new_drop_chance) auto& entry = dropchance_entries.at(dropchance_id); if (entry.offset == 0) { - auto memory = Memory::get(); + auto& memory = Memory::get(); size_t offset = memory.at_exe(find_inst(memory.exe(), entry.pattern, get_virtual_function_address(entry.vtable_offset, entry.vtable_rel_offset))); - if (offset > memory.exe_ptr) + if (offset > memory.exe_address()) { entry.offset = offset; } @@ -381,12 +381,12 @@ void replace_drop(int32_t drop_id, ENT_TYPE new_drop_entity_type) } if (entry.offsets[0] == 0) { - auto memory = Memory::get(); + auto& memory = Memory::get(); size_t offset = 0; const auto drop_name{"DROP." + entry.caption}; if (entry.vtable_offset == VTABLE_OFFSET::NONE) - offset = memory.after_bundle; + offset = memory.after_bundle_address(); else offset = get_virtual_function_address(entry.vtable_offset, entry.vtable_rel_offset); diff --git a/src/game_api/game_patches.cpp b/src/game_api/game_patches.cpp index 8ce032147..419e93669 100644 --- a/src/game_api/game_patches.cpp +++ b/src/game_api/game_patches.cpp @@ -29,7 +29,7 @@ void patch_orbs_limit() if (once) return; - auto memory = Memory::get(); + auto& memory = Memory::get(); const auto function_offset = get_virtual_function_address(VTABLE_OFFSET::ITEM_FLOATING_ORB, (uint32_t)VIRT_FUNC::ENTITY_KILL); // finding exit of the loop that counts orbs, after jump there is unused code so we have enogh space for a our patch auto instance = find_inst(memory.exe(), "\xF3\x75"sv, function_offset, function_offset + 0x622, "patch_orbs_limit"); @@ -81,11 +81,11 @@ void patch_olmec_kill_crash() const auto offset = get_address("olmec_lookup_crash"); constexpr auto code_to_move = 7; - auto memory = Memory::get(); + auto& memory = Memory::get(); size_t return_addr; { // find address to escape to - size_t rva = offset - memory.exe_ptr; + size_t rva = offset - memory.exe_address(); // below the patched code there are two jumps that performe long jump, at the end of it there is 'mov rax,qword ptr ds:[rdi]', // from this point find jump that's jumps over sond meta and fmod stuff, the jump ends up on code `mov eax,dword ptr ss:[rbp+10]`, that's our target for return_addr auto jump_out_lookup = find_inst(memory.exe(), "\x48\xC7\x40\x60\x00\x00\x00\x00"sv, rva, rva + 0x69D, "patch_olmec_kill_crash"); @@ -122,7 +122,7 @@ void patch_olmec_kill_crash() size_t addr_to_jump_to; { // find end of the function that sets the camera and stuff - size_t rva = patch_addr - memory.exe_ptr; + size_t rva = patch_addr - memory.exe_address(); size_t rva_jumpout_to = find_inst(memory.exe(), "\x48\x8B\x06"sv, rva, rva + 0xA50, "patch_olmec_kill_crash"); if (rva_jumpout_to == 0) return; @@ -165,7 +165,7 @@ void patch_tiamat_kill_crash() if (once) return; - auto memory = Memory::get(); + auto& memory = Memory::get(); const auto patch_addr = get_address("tiamat_lookup_in_theme"); if (patch_addr == 0) return; @@ -173,7 +173,7 @@ void patch_tiamat_kill_crash() size_t return_to_addr; { // find end of the function that sets the camera and stuff - auto rva = patch_addr - memory.exe_ptr; + auto rva = patch_addr - memory.exe_address(); auto rva_jumpout_to = find_inst(memory.exe(), "\x49\x89\x0C\xC6"sv, rva, rva + 0x5C7, "patch_tiamat_kill_crash"); if (rva_jumpout_to == 0) return; @@ -228,8 +228,8 @@ void patch_liquid_OOB() size_t continue_addr; { // find address to continue the loop - auto memory = Memory::get(); - auto rva = offset - memory.exe_ptr; + auto& memory = Memory::get(); + auto rva = offset - memory.exe_address(); // first `ja` loop auto jump_out_lookup = find_inst(memory.exe(), "\x0F\x87"sv, rva, rva + 0x7D, "patch_liquid_OOB"); if (jump_out_lookup == 0) @@ -315,8 +315,8 @@ void patch_entering_closed_door_crash() size_t addr = get_address("enter_closed_door_crash"); size_t return_addr; { - auto memory = Memory::get(); - auto rva = find_inst(memory.exe(), "\x49\x39\xD4", addr - memory.exe_ptr, addr - memory.exe_ptr + 0x3F5, "patch_entering_closed_door_crash"); + auto& memory = Memory::get(); + auto rva = find_inst(memory.exe(), "\x49\x39\xD4", addr - memory.exe_address(), addr - memory.exe_address() + 0x3F5, "patch_entering_closed_door_crash"); if (rva == 0) return; size_t jump_addr = memory.at_exe(rva + 3); diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index e17e88100..673c1ee2a 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -39,7 +39,21 @@ void ExecutableMemory::deleter_t::operator()(std::byte* mem) const VirtualFree(mem, 0, MEM_RELEASE); } -size_t round_up(size_t i, size_t div) +Memory& Memory::get() +{ + static Memory mem{[]() + { + auto exe = (size_t)GetModuleHandleA(NULL); + + // Skipping bundle for faster memory search + auto after_bundle_ = find_after_bundle(exe); + + return Memory{exe, after_bundle_}; + }()}; + return mem; +} + +static size_t round_up(size_t i, size_t div) { return ((i + div - 1) / div) * div; } @@ -82,13 +96,13 @@ size_t function_start(size_t off, uint8_t outside_byte) LPVOID alloc_mem_rel32(size_t addr, size_t size) { - const size_t limit_addr = Memory::get().exe_ptr; + const size_t limit_addr = Memory::get().exe_address(); LPVOID new_array = nullptr; size_t test_addr = addr + 0x10000; // dunno why, without this it can get address that is more than 32bit away if (test_addr <= INT32_MAX) // redunded check as you probably won't get address that is less than INT32_MAX from "zero" - test_addr = 8; // but i did it just in case so you can't get overflow, also can't feed 0 to the VirtualAlloc as that just means: find memory wherever + test_addr = 0x1000; // but i did it just in case so you can't get overflow else test_addr -= INT32_MAX; diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index e9ed97986..9e45fe959 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -45,40 +45,40 @@ class ExecutableMemory }; struct Memory { - size_t exe_ptr; - size_t after_bundle; + static Memory& get(); - static Memory& get() - { - static Memory mem{[]() - { - auto exe = (size_t)GetModuleHandleA(NULL); - - // Skipping bundle for faster memory search - auto after_bundle_ = find_after_bundle(exe); - - return Memory{ - exe, - after_bundle_, - }; - }()}; - return mem; - } - - size_t at_exe(size_t offset) + size_t at_exe(size_t offset) const { return exe_ptr + offset; } char* exe() { - return (char*)exe_ptr; + return reinterpret_cast(exe_ptr); + } + size_t exe_address() const + { + return exe_ptr; + } + size_t after_bundle_address() const + { + return after_bundle; } static size_t decode_call(size_t off) { - auto memory = get(); + auto& memory = get(); return off + (*(int32_t*)(&memory.exe()[off + 1])) + 5; } + + private: + size_t exe_ptr; + size_t after_bundle; + Memory(size_t ptr, size_t ab) + : exe_ptr(ptr), after_bundle(ab){}; + + Memory(const Memory&) = delete; + Memory& operator=(const Memory&) = delete; + ~Memory(){}; }; LPVOID alloc_mem_rel32(size_t addr, size_t size); diff --git a/src/game_api/movable_behavior.cpp b/src/game_api/movable_behavior.cpp index 2b4796f4e..b24cc3e9c 100644 --- a/src/game_api/movable_behavior.cpp +++ b/src/game_api/movable_behavior.cpp @@ -254,7 +254,7 @@ void init_behavior_hooks() DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); - auto memory = Memory::get(); + auto& memory = Memory::get(); g_entity_turn_trampoline = (EntityTurn*)memory.at_exe(get_virtual_function_address(VTABLE_OFFSET::MONS_SNAKE, 0x10)); DetourAttach((void**)&g_entity_turn_trampoline, &entity_turn); diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 5c54ee53b..e5b3fe37a 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -47,7 +47,7 @@ #include "thread_utils.hpp" // for OnHeapPointer #include "virtual_table.hpp" // for get_virtual_function_address, VIRT_FUNC -uint32_t setflag(uint32_t flags, int bit) // shouldn't we change those to #define ? +uint32_t setflag(uint32_t flags, int bit) { return flags | (1U << (bit - 1)); } @@ -711,7 +711,7 @@ void set_time_ghost_enabled(bool b) static size_t offset_toast_trigger = 0; if (offset_trigger == 0) { - auto memory = Memory::get(); + auto& memory = Memory::get(); offset_trigger = memory.at_exe(get_virtual_function_address(VTABLE_OFFSET::LOGIC_GHOST_TRIGGER, static_cast(VIRT_FUNC::LOGIC_PERFORM))); offset_toast_trigger = memory.at_exe(get_virtual_function_address(VTABLE_OFFSET::LOGIC_GHOST_TOAST_TRIGGER, static_cast(VIRT_FUNC::LOGIC_PERFORM))); } @@ -728,7 +728,7 @@ void set_time_ghost_enabled(bool b) void set_time_jelly_enabled(bool b) { - auto memory = Memory::get(); + auto& memory = Memory::get(); static const size_t offset = memory.at_exe(get_virtual_function_address(VTABLE_OFFSET::LOGIC_COSMIC_OCEAN, static_cast(VIRT_FUNC::LOGIC_PERFORM))); if (b) recover_mem("set_time_jelly_enabled"); @@ -1164,8 +1164,8 @@ void modify_ankh_health_gain(uint8_t health, uint8_t beat_add_health) { if (!offsets[0]) { - auto memory = Memory::get(); - size_t offset = size_minus_one - memory.exe_ptr; + auto& memory = Memory::get(); + size_t offset = size_minus_one - memory.exe_address(); const auto limit_size = offset + 0x200; offsets[0] = find_inst(memory.exe(), "\x41\x80\xBF\x17\x01\x00\x00"sv, offset, limit_size, "ankh_health_gain_1"); @@ -1458,7 +1458,7 @@ void activate_tiamat_position_hack(bool activate) void activate_crush_elevator_hack(bool activate) { - auto memory = Memory::get(); + auto& memory = Memory::get(); static size_t offsets[3]; if (offsets[0] == 0) { @@ -1502,7 +1502,7 @@ void activate_hundun_hack(bool activate) if (offsets[0] == 0) { - auto memory = Memory::get(); + auto& memory = Memory::get(); auto func_offset = get_virtual_function_address(VTABLE_OFFSET::MONS_HUNDUN, 78); offsets[0] = find_inst(memory.exe(), "\x41\xF6\x85\x61\x01\x00\x00\x08"sv, func_offset, func_offset + 0x1420, "activate_hundun_hack"); if (offsets[0] == 0) @@ -1602,12 +1602,12 @@ void set_boss_door_control_enabled(bool enable) static size_t offsets[2]; if (offsets[0] == 0) { - auto memory = Memory::get(); + auto& memory = Memory::get(); offsets[0] = get_address("hundun_door_control"); if (offsets[0] == 0) return; // find tiamat door control (the same pattern) - offsets[1] = find_inst(memory.exe(), "\x4A\x8B\xB4\xC8\x80\xF4\x00\x00"sv, offsets[0] - memory.exe_ptr + 0x777, std::nullopt, "set_boss_door_control_enabled"); + offsets[1] = find_inst(memory.exe(), "\x4A\x8B\xB4\xC8\x80\xF4\x00\x00"sv, offsets[0] - memory.exe_address() + 0x777, std::nullopt, "set_boss_door_control_enabled"); if (offsets[1] == 0) { offsets[0] = 0; diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 89338b408..cbd4c1012 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -83,7 +83,7 @@ const char* current_spelunky_version() if (!version_searched) { version_searched = true; - auto memory = Memory::get(); + auto& memory = Memory::get(); PIMAGE_NT_HEADERS nt_header = RtlImageNtHeader((PVOID)memory.exe()); size_t rdata_start = 0; size_t rdata_size = 0; @@ -316,9 +316,9 @@ class PatternCommandBuffer return *this; } - std::optional operator()(Memory mem, const char* exe, std::string_view address_name) const + std::optional operator()(Memory& mem, const char* exe, std::string_view address_name) const { - size_t offset = mem.after_bundle; + size_t offset = mem.after_bundle_address(); bool optional{false}; #ifdef DEBUG @@ -390,7 +390,7 @@ class PatternCommandBuffer offset = mem.at_exe(offset); break; case CommandType::FromExe: - offset = offset - mem.exe_ptr; + offset = offset - mem.exe_address(); break; case CommandType::FunctionStart: offset = ::function_start(offset, data.outside_byte); @@ -475,7 +475,7 @@ class PatternCommandBuffer std::vector commands; }; -using AddressRule = std::function(Memory mem, const char* exe, std::string_view address_name)>; +using AddressRule = std::function(Memory& mem, const char* exe, std::string_view address_name)>; std::unordered_map g_address_rules{ { "game_malloc"sv, @@ -2115,7 +2115,7 @@ std::unordered_map g_cached_addresses; void preload_addresses() { - Memory mem = Memory::get(); + Memory& mem = Memory::get(); const char* exe = mem.exe(); for (auto [address_name, rule] : g_address_rules) { @@ -2137,7 +2137,7 @@ size_t load_address(std::string_view address_name) auto it = g_address_rules.find(address_name); if (it != g_address_rules.end()) { - Memory mem = Memory::get(); + Memory& mem = Memory::get(); if (auto address = it->second(mem, mem.exe(), address_name)) { g_cached_addresses[address_name] = address.value(); diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index a3ad961d0..aaeb2c513 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -193,7 +193,7 @@ void hook_godmode_functions() static bool functions_hooked = false; if (!functions_hooked) { - auto memory = Memory::get(); + auto& memory = Memory::get(); auto addr_damage = memory.at_exe(get_virtual_function_address(VTABLE_OFFSET::CHAR_ANA_SPELUNKY, 48)); auto addr_insta = get_address("insta_gib"); diff --git a/src/game_api/virtual_table.cpp b/src/game_api/virtual_table.cpp index 3d1fd0ea3..df1fa06cc 100644 --- a/src/game_api/virtual_table.cpp +++ b/src/game_api/virtual_table.cpp @@ -7,13 +7,13 @@ size_t get_virtual_function_address(VTABLE_OFFSET table_entry, uint32_t function { static auto first_table_entry = get_address("virtual_functions_table"); - auto mem = Memory::get(); + auto& mem = Memory::get(); if (first_table_entry == 0) { return 0; } size_t* func_address = reinterpret_cast(first_table_entry + ((static_cast(table_entry) + function_index) * sizeof(size_t))); - return *func_address - mem.exe_ptr; + return *func_address - mem.exe_address(); } size_t get_virtual_function_address(void* object, uint32_t function_index) From 1693445b87cbd319701fbe3aa853125b4a018c8f Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:16:06 +0100 Subject: [PATCH 04/23] change small things for nicer formatting, optimise small functions by moving them to the header --- src/game_api/bucket.cpp | 38 ----- src/game_api/bucket.hpp | 38 ++++- src/game_api/color.hpp | 60 ++++--- src/game_api/containers/custom_allocator.hpp | 2 +- src/game_api/containers/game_allocator.hpp | 2 +- src/game_api/entities_chars.cpp | 5 - src/game_api/entities_chars.hpp | 5 +- src/game_api/entities_mounts.cpp | 6 - src/game_api/entities_mounts.hpp | 6 +- src/game_api/entity.cpp | 45 +----- src/game_api/entity.hpp | 28 ++-- src/game_api/entity_db.cpp | 11 +- src/game_api/entity_db.hpp | 2 +- src/game_api/entity_lookup.cpp | 69 +------- src/game_api/entity_lookup.hpp | 72 +++++++-- src/game_api/game_api.cpp | 8 +- src/game_api/game_api.hpp | 9 +- src/game_api/game_patches.cpp | 2 +- src/game_api/illumination.hpp | 6 +- src/game_api/items.cpp | 8 - src/game_api/items.hpp | 5 +- src/game_api/layer.cpp | 2 +- src/game_api/layer.hpp | 2 +- src/game_api/level_api.cpp | 94 ++--------- src/game_api/level_api.hpp | 90 ++++++++--- src/game_api/math.cpp | 10 +- src/game_api/math.hpp | 160 +++++++++---------- src/game_api/memory.cpp | 14 +- src/game_api/memory.hpp | 2 +- src/game_api/movable.hpp | 25 ++- src/game_api/movable_behavior.cpp | 12 -- src/game_api/movable_behavior.hpp | 19 ++- src/game_api/particles.cpp | 74 +-------- src/game_api/particles.hpp | 92 ++++++++--- src/game_api/prng.cpp | 16 -- src/game_api/prng.hpp | 18 ++- src/game_api/render_api.hpp | 2 +- src/game_api/savestate.cpp | 9 +- src/game_api/savestate.hpp | 7 +- src/game_api/screen.cpp | 4 - src/game_api/screen.hpp | 5 +- src/game_api/script/lua_backend.cpp | 8 +- src/game_api/script/lua_backend.hpp | 8 +- src/game_api/script/lua_console.cpp | 43 ----- src/game_api/script/lua_console.hpp | 53 ++++-- src/game_api/script/lua_vm.cpp | 2 +- src/game_api/script/script_impl.cpp | 46 ------ src/game_api/script/script_impl.hpp | 55 +++++-- src/game_api/script/usertypes/entity_lua.cpp | 6 +- src/game_api/script/usertypes/gui_lua.cpp | 2 +- src/game_api/search.cpp | 2 +- src/game_api/spawn_api.cpp | 11 +- src/game_api/spawn_api.hpp | 10 +- src/game_api/state.cpp | 19 +-- src/game_api/state.hpp | 18 ++- src/game_api/state_structs.hpp | 5 +- src/game_api/virtual_table.cpp | 6 - src/game_api/virtual_table.hpp | 6 +- src/injector/cmd_line.cpp | 4 +- 59 files changed, 620 insertions(+), 768 deletions(-) delete mode 100644 src/game_api/items.cpp diff --git a/src/game_api/bucket.cpp b/src/game_api/bucket.cpp index b258d3baf..85dc2c585 100644 --- a/src/game_api/bucket.cpp +++ b/src/game_api/bucket.cpp @@ -34,39 +34,6 @@ void PauseAPI::set_pause(PAUSE_TYPE flags) state->pause = (uint8_t)(((uint32_t)flags) & 0x3f); } -bool PauseAPI::paused() -{ - return get_pause() != PAUSE_TYPE::NONE && (get_pause() & pause_type) != PAUSE_TYPE::NONE; -} - -bool PauseAPI::set_paused(bool enable) -{ - if (enable) - set_pause(get_pause() | pause_type); - else - set_pause(get_pause() & (~pause_type)); - return paused(); -} - -bool PauseAPI::toggle() -{ - if (paused()) - set_paused(false); - else - set_paused(true); - return paused(); -} - -void PauseAPI::frame_advance() -{ - skip = true; -} - -void PauseAPI::apply() -{ - set_pause(pause); -} - bool PauseAPI::check_trigger(PAUSE_TRIGGER& trigger, PAUSE_SCREEN& screen) { bool match = false; @@ -173,11 +140,6 @@ bool PauseAPI::event(PAUSE_TYPE pause_event) return block; } -void PauseAPI::pre_loop() -{ - blocked = false; -} - void PauseAPI::post_loop() { auto state = State::get().ptr(); diff --git a/src/game_api/bucket.hpp b/src/game_api/bucket.hpp index db1de4167..0331448db 100644 --- a/src/game_api/bucket.hpp +++ b/src/game_api/bucket.hpp @@ -103,20 +103,46 @@ struct PauseAPI /// Set the current pause flags void set_pause(PAUSE_TYPE flags); /// Enable/disable the current pause_type flags in pause state - bool set_paused(bool enable = true); + bool set_paused(bool enable = true) + { + if (enable) + set_pause(get_pause() | pause_type); + else + set_pause(get_pause() & (~pause_type)); + return paused(); + } /// Is the game currently paused and that pause state matches any of the current the pause_type - bool paused(); + bool paused() + { + return get_pause() != PAUSE_TYPE::NONE && (get_pause() & pause_type) != PAUSE_TYPE::NONE; + } /// Toggles pause state - bool toggle(); + bool toggle() + { + if (paused()) + set_paused(false); + else + set_paused(true); + return paused(); + } /// Sets skip - void frame_advance(); + void frame_advance() + { + skip = true; + } /// Is the game currently loading and PAUSE_SCREEN.LOADING would be triggered, based on state.loading and some arbitrary checks. bool loading(); - void apply(); + void apply() + { + set_pause(pause); + } bool event(PAUSE_TYPE event); bool check_trigger(PAUSE_TRIGGER& trigger, PAUSE_SCREEN& screen); - void pre_loop(); + void pre_loop() + { + blocked = false; + } void post_loop(); bool pre_input(); void post_input(); diff --git a/src/game_api/color.hpp b/src/game_api/color.hpp index fe9288e97..e957e6dbc 100644 --- a/src/game_api/color.hpp +++ b/src/game_api/color.hpp @@ -15,7 +15,7 @@ struct Color constexpr Color& operator=(Color&&) = default; /// Comparison using RGB to avoid non-precise float value - bool operator==(const Color& col) const + bool operator==(const Color& col) const noexcept { const auto current = get_rgba(); const auto compare = col.get_rgba(); @@ -26,18 +26,14 @@ struct Color } /// Create a new color by specifying its values - constexpr Color(float r_, float g_, float b_, float a_) - : r(r_), g(g_), b(b_), a(a_) - { - } + constexpr Color(float r_, float g_, float b_, float a_) noexcept + : r(r_), g(g_), b(b_), a(a_){}; /// Create a color from an array of 4 floats - constexpr Color(const float (&c)[4]) - : r(c[0]), g(c[1]), b(c[2]), a(c[3]) - { - } + constexpr Color(const float (&c)[4]) noexcept + : r(c[0]), g(c[1]), b(c[2]), a(c[3]){}; - constexpr void to_float(float (&c)[4]) const + constexpr void to_float(float (&c)[4]) const noexcept { c[0] = r; c[1] = g; @@ -45,93 +41,93 @@ struct Color c[3] = a; } - static constexpr Color white() + static constexpr Color white() noexcept { return Color(1.0f, 1.0f, 1.0f, 1.0f); } - static constexpr Color silver() + static constexpr Color silver() noexcept { return Color(0.75f, 0.75f, 0.75f, 1.0f); } - static constexpr Color gray() + static constexpr Color gray() noexcept { return Color(0.5f, 0.5f, 0.5f, 1.0f); } - static constexpr Color black() + static constexpr Color black() noexcept { return Color(); } - static constexpr Color red() + static constexpr Color red() noexcept { return Color(1.0f, 0.0f, 0.0f, 1.0f); } - static constexpr Color maroon() + static constexpr Color maroon() noexcept { return Color(0.5f, 0.0f, 0.0f, 1.0f); } - static constexpr Color yellow() + static constexpr Color yellow() noexcept { return Color(1.0f, 1.0f, 0.0f, 1.0f); } - static constexpr Color olive() + static constexpr Color olive() noexcept { return Color(0.5f, 0.5f, 0.0f, 1.0f); } - static constexpr Color lime() + static constexpr Color lime() noexcept { return Color(0.0f, 1.0f, 0.0f, 1.0f); } - static constexpr Color green() + static constexpr Color green() noexcept { return Color(0.0f, 0.5f, 0.0f, 1.0f); } - static constexpr Color aqua() + static constexpr Color aqua() noexcept { return Color(0.0f, 1.0f, 1.0f, 1.0f); } - static constexpr Color teal() + static constexpr Color teal() noexcept { return Color(0.0f, 0.5f, 0.5f, 1.0f); } - static constexpr Color blue() + static constexpr Color blue() noexcept { return Color(0.0f, 0.0f, 1.0f, 1.0f); } - static constexpr Color navy() + static constexpr Color navy() noexcept { return Color(0.0f, 0.0f, 0.5f, 1.0f); } - static constexpr Color fuchsia() + static constexpr Color fuchsia() noexcept { return Color(1.0f, 0.0f, 1.0f, 1.0f); } - static constexpr Color purple() + static constexpr Color purple() noexcept { return Color(0.5f, 0.0f, 0.5f, 1.0f); } /// Returns RGBA colors in 0..255 range - std::tuple get_rgba() const + std::tuple get_rgba() const noexcept { return {toRGB(r), toRGB(g), toRGB(b), toRGB(a)}; } /// Changes color based on given RGBA colors in 0..255 range - Color& set_rgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + Color& set_rgba(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) noexcept { r = red / 255.0f; g = green / 255.0f; @@ -140,12 +136,12 @@ struct Color return *this; } /// Returns the `uColor` used in `GuiDrawContext` drawing functions - uColor get_ucolor() const + uColor get_ucolor() const noexcept { return (toRGB(a) << 24) + (toRGB(b) << 16) + (toRGB(g) << 8) + (toRGB(r)); } /// Changes color based on given uColor - Color& set_ucolor(const uColor color) + Color& set_ucolor(const uColor color) noexcept { uint8_t red = color & 0xFF; uint8_t green = (color >> 8U) & 0xFF; @@ -154,7 +150,7 @@ struct Color return set_rgba(red, green, blue, alpha); } /// Copies the values of different Color to this one - Color& set(Color& other) + Color& set(Color& other) noexcept { *this = other; return *this; @@ -166,7 +162,7 @@ struct Color float a{1.0f}; private: - uint8_t toRGB(const float c) const + uint8_t toRGB(const float c) const noexcept { return static_cast(std::round(255 * std::min(std::max(c, 0.0f), 1.0f))); } diff --git a/src/game_api/containers/custom_allocator.hpp b/src/game_api/containers/custom_allocator.hpp index 7f4d058aa..817730375 100644 --- a/src/game_api/containers/custom_allocator.hpp +++ b/src/game_api/containers/custom_allocator.hpp @@ -3,7 +3,7 @@ #include // for size_t, ptrdiff_t #include // for operator new -void* custom_malloc(std::size_t size); +[[nodiscard]] void* custom_malloc(std::size_t size); void custom_free(void* mem); // This is an allocator that always uses the MemHeap implementations that the game provides diff --git a/src/game_api/containers/game_allocator.hpp b/src/game_api/containers/game_allocator.hpp index 91f870b38..a8fb1ccbf 100644 --- a/src/game_api/containers/game_allocator.hpp +++ b/src/game_api/containers/game_allocator.hpp @@ -3,7 +3,7 @@ #include // for size_t, ptrdiff_t #include // for operator new -void* game_malloc(std::size_t size); +[[nodiscard]] void* game_malloc(std::size_t size); void game_free(void* mem); // This is an allocator that always uses the malloc/free implementations that the game provides diff --git a/src/game_api/entities_chars.cpp b/src/game_api/entities_chars.cpp index 02b651b70..12e27214d 100644 --- a/src/game_api/entities_chars.cpp +++ b/src/game_api/entities_chars.cpp @@ -36,11 +36,6 @@ void PowerupCapable::give_powerup(ENT_TYPE powerup_type) } } -bool PowerupCapable::has_powerup(ENT_TYPE powerup_type) -{ - return powerups.find(powerup_type) != powerups.end(); -} - std::vector PowerupCapable::get_powerups() { std::vector return_powerups; diff --git a/src/game_api/entities_chars.hpp b/src/game_api/entities_chars.hpp index ef4f182e4..6ec0918ef 100644 --- a/src/game_api/entities_chars.hpp +++ b/src/game_api/entities_chars.hpp @@ -70,7 +70,10 @@ class PowerupCapable : public Movable void give_powerup(ENT_TYPE powerup_type); /// Checks whether the player/monster has a certain powerup - bool has_powerup(ENT_TYPE powerup_type); + bool has_powerup(ENT_TYPE powerup_type) + { + return powerups.find(powerup_type) != powerups.end(); + } /// Return all powerups that the entity has std::vector get_powerups(); diff --git a/src/game_api/entities_mounts.cpp b/src/game_api/entities_mounts.cpp index a6f253b07..e4f6454e7 100644 --- a/src/game_api/entities_mounts.cpp +++ b/src/game_api/entities_mounts.cpp @@ -12,9 +12,3 @@ void Mount::carry(Movable* rider) rider->move_state = 0x11; return carry(this, rider); } - -void Mount::tame(bool value) -{ - tamed = value; - flags = flags | 0x20000; -} diff --git a/src/game_api/entities_mounts.hpp b/src/game_api/entities_mounts.hpp index 49f316254..767085683 100644 --- a/src/game_api/entities_mounts.hpp +++ b/src/game_api/entities_mounts.hpp @@ -26,7 +26,11 @@ class Mount : public PowerupCapable void carry(Movable* rider); - void tame(bool value); + void tame(bool value) + { + tamed = value; + flags = flags | 0x20000; + } virtual std::pair& get_special_offset(std::pair& offset) = 0; // gets special offset for the raider when jumping on mount virtual std::pair& v96(std::pair& value) = 0; // gets something for when crouching on mount diff --git a/src/game_api/entity.cpp b/src/game_api/entity.cpp index 4d0f09f38..bb0f86a6c 100644 --- a/src/game_api/entity.cpp +++ b/src/game_api/entity.cpp @@ -156,11 +156,6 @@ void Entity::remove() } } -void Entity::respawn(LAYER layer_to) -{ - set_layer(layer_to); -} - void Entity::perform_teleport(uint8_t delta_x, uint8_t delta_y) { using TeleportFun = void(Entity*, uint8_t, uint8_t); @@ -168,7 +163,7 @@ void Entity::perform_teleport(uint8_t delta_x, uint8_t delta_y) tp(this, delta_x, delta_y); } -std::pair Entity::position() +std::pair Entity::position() const { auto [x_pos, y_pos] = position_self(); @@ -183,11 +178,6 @@ std::pair Entity::position() return {x_pos, y_pos}; } -std::pair Entity::position_self() const -{ - return std::pair(x, y); -} - void Entity::remove_item(uint32_t item_uid) { auto entity = get_entity_ptr(item_uid); @@ -209,16 +199,6 @@ void Movable::poison(int16_t frames) write_mem_prot(offset_subsequent, frames, true); } -bool Movable::is_poisoned() -{ - return (poison_tick_timer != -1); -} - -bool Movable::broken_damage(uint32_t damage_dealer_uid, int8_t damage_amount, uint16_t stun_time, float velocity_x, float velocity_y) -{ - return damage(damage_dealer_uid, damage_amount, stun_time, velocity_x, velocity_y, 80); -} - bool Movable::damage(uint32_t damage_dealer_uid, int8_t damage_amount, uint16_t stun_time, float velocity_x, float velocity_y, uint8_t iframes) { /* why? @@ -240,19 +220,6 @@ bool Movable::damage(uint32_t damage_dealer_uid, int8_t damage_amount, uint16_t return on_damage(dealer, damage_amount, 0x1, &velocity, unknown1, stun_time, iframes, unknown2); } -bool Movable::is_button_pressed(BUTTON button) -{ - return (buttons & button) == button && (buttons_previous & button) == 0; -} -bool Movable::is_button_held(BUTTON button) -{ - return (buttons & button) == button && (buttons_previous & button) == button; -} -bool Movable::is_button_released(BUTTON button) -{ - return (buttons & button) == 0 && (buttons_previous & button) == button; -} - std::tuple get_position(uint32_t uid) { Entity* ent = get_entity_ptr(uid); @@ -319,7 +286,7 @@ AABB get_hitbox(uint32_t uid, bool use_render_pos) return AABB{0.0f, 0.0f, 0.0f, 0.0f}; } -TEXTURE Entity::get_texture() +TEXTURE Entity::get_texture() const { if (texture) return texture->id; @@ -336,17 +303,17 @@ bool Entity::set_texture(TEXTURE texture_id) return false; } -bool Entity::is_player() +bool Entity::is_player() const { if (type->search_flags & 1) { - Player* pl = this->as(); + auto pl = static_cast(this); return pl->ai == nullptr; } return false; } -bool Entity::is_movable() +bool Entity::is_movable() const { static const ENT_TYPE first_logical = to_id("ENT_TYPE_LOGICAL_CONSTELLATION"); if (type->search_flags & 0b11111111) // PLAYER | MOUNT | MONSTER | ITEM | ROPE | EXPLOSION | FX | ACTIVEFLOOR @@ -358,7 +325,7 @@ bool Entity::is_movable() return false; } -bool Entity::is_liquid() +bool Entity::is_liquid() const { static const ENT_TYPE liquid_water = to_id("ENT_TYPE_LIQUID_WATER"); static const ENT_TYPE liquid_coarse_water = to_id("ENT_TYPE_LIQUID_COARSE_WATER"); diff --git a/src/game_api/entity.hpp b/src/game_api/entity.hpp index 5a90e0137..4624a0750 100644 --- a/src/game_api/entity.hpp +++ b/src/game_api/entity.hpp @@ -115,7 +115,7 @@ class Entity return (size_t)this; } - std::pair position(); + std::pair position() const; void teleport(float dx, float dy, bool s, float vx, float vy, bool snap); void teleport_abs(float dx, float dy, float vx, float vy); @@ -127,7 +127,10 @@ class Entity /// Moves the entity to the limbo-layer where it can later be retrieved from again via `respawn` void remove(); /// Moves the entity from the limbo-layer (where it was previously put by `remove`) to `layer` - void respawn(LAYER layer); + void respawn(LAYER layer_to) + { + set_layer(layer_to); + } /// Performs a teleport as if the entity had a teleporter and used it. The delta coordinates are where you want the entity to teleport to relative to its current position, in tiles (so integers, not floats). Positive numbers = to the right and up, negative left and down. void perform_teleport(uint8_t delta_x, uint8_t delta_y); @@ -156,14 +159,14 @@ class Entity return topmost; } - bool overlaps_with(AABB hitbox) + bool overlaps_with(AABB hitbox) const { return overlaps_with(hitbox.left, hitbox.bottom, hitbox.right, hitbox.top); } /// Deprecated /// Use `overlaps_with(AABB hitbox)` instead - bool overlaps_with(float rect_left, float rect_bottom, float rect_right, float rect_top) + bool overlaps_with(float rect_left, float rect_bottom, float rect_right, float rect_top) const { const auto [posx, posy] = position(); const float left = posx - hitboxx + offsetx; @@ -174,7 +177,7 @@ class Entity return left < rect_right && rect_left < right && bottom < rect_top && rect_bottom < top; } - bool overlaps_with(Entity* other) + bool overlaps_with(Entity* other) const { const auto [other_posx, other_posy] = other->position(); const float other_left = other_posx - other->hitboxx + other->offsetx; @@ -185,17 +188,20 @@ class Entity return overlaps_with(other_left, other_bottom, other_right, other_top); } - std::pair position_self() const; + std::pair position_self() const + { + return std::pair(x, y); + } void remove_item(uint32_t item_uid); - TEXTURE get_texture(); + TEXTURE get_texture() const; /// Changes the entity texture, check the [textures.txt](game_data/textures.txt) for available vanilla textures or use [define_texture](#define_texture) to make custom one bool set_texture(TEXTURE texture_id); - bool is_player(); - bool is_movable(); - bool is_liquid(); - bool is_cursed() + bool is_player() const; + bool is_movable() const; + bool is_liquid() const; + bool is_cursed() const { return more_flags & 0x4000; }; diff --git a/src/game_api/entity_db.cpp b/src/game_api/entity_db.cpp index ff2148386..df8c803b6 100644 --- a/src/game_api/entity_db.cpp +++ b/src/game_api/entity_db.cpp @@ -27,19 +27,12 @@ #include "texture.hpp" // for get_texture, Texture #include "vtable_hook.hpp" // for hook_vtable, hook_dtor, unregis... -template -class OnHeapPointer; - -EntityDB::EntityDB(const EntityDB& other) = default; EntityDB::EntityDB(const ENT_TYPE other) - : EntityDB(*get_type(other)) -{ -} - -using namespace std::chrono_literals; + : EntityDB(*get_type(other)){}; EntityFactory* entity_factory() { + using namespace std::chrono_literals; static EntityFactory* cache_entity_factory = *(EntityFactory**)get_address("entity_factory"sv); while (cache_entity_factory == 0) { diff --git a/src/game_api/entity_db.hpp b/src/game_api/entity_db.hpp index 6cabb4fcd..5a5c267c3 100644 --- a/src/game_api/entity_db.hpp +++ b/src/game_api/entity_db.hpp @@ -106,7 +106,7 @@ struct EntityDB float default_special_offsety; uint8_t init; - EntityDB(const EntityDB& other); + EntityDB(const EntityDB& other) = default; EntityDB(const ENT_TYPE other); }; diff --git a/src/game_api/entity_lookup.cpp b/src/game_api/entity_lookup.cpp index b64572a9f..ec452fd22 100644 --- a/src/game_api/entity_lookup.cpp +++ b/src/game_api/entity_lookup.cpp @@ -12,10 +12,7 @@ bool entity_type_check(const std::vector& types_array, const ENT_TYPE find) { - if (types_array.empty() || types_array[0] == 0 || std::find(types_array.begin(), types_array.end(), find) != types_array.end()) - return true; - - return false; + return (types_array.empty() || types_array[0] == 0 || std::find(types_array.begin(), types_array.end(), find) != types_array.end()); } std::vector get_proper_types(std::vector ent_types) @@ -52,30 +49,6 @@ int32_t get_grid_entity_at(float x, float y, LAYER layer) return -1; } -std::vector get_entities() -{ - return get_entities_by({}, 0, LAYER::BOTH); -} - -std::vector get_entities_by_layer(LAYER layer) -{ - return get_entities_by({}, 0, layer); -} - -std::vector get_entities_by_type(std::vector entity_types) -{ - return get_entities_by(std::move(entity_types), 0, LAYER::BOTH); -} -std::vector get_entities_by_type(ENT_TYPE entity_type) -{ - return get_entities_by(std::vector{entity_type}, 0, LAYER::BOTH); -} - -std::vector get_entities_by_mask(uint32_t mask) -{ - return get_entities_by({}, mask, LAYER::BOTH); -} - template requires std::is_invocable_v void foreach_mask(uint32_t mask, Layer* l, FunT&& fun) @@ -162,11 +135,6 @@ std::vector get_entities_by(std::vector entity_types, uint32 return found; } -std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer) -{ - return get_entities_by(std::vector{entity_type}, mask, layer); -} - std::vector get_entities_at(std::vector entity_types, uint32_t mask, float x, float y, LAYER layer, float radius) { // TODO: use entity regions? @@ -197,11 +165,6 @@ std::vector get_entities_at(std::vector entity_types, uint32 return found; } -std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius) -{ - return get_entities_at(std::vector{entity_type}, mask, x, y, layer, radius); -} - std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer) { // TODO: use entity regions? @@ -222,19 +185,6 @@ std::vector get_entities_overlapping_hitbox(std::vector enti } return result; } -std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer) -{ - return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, hitbox, layer); -} - -std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) -{ - return get_entities_overlapping_hitbox(std::move(entity_types), mask, {sx, sy2, sx2, sy}, layer); -} -std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) -{ - return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, {sx, sy2, sx2, sy}, layer); -} std::vector get_entities_overlapping_by_pointer(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) { @@ -251,10 +201,6 @@ std::vector get_entities_overlapping_by_pointer(std::vector return found; } -std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) -{ - return get_entities_overlapping_by_pointer(std::vector{entity_type}, mask, sx, sy, sx2, sy2, layer); -} bool entity_has_item_uid(uint32_t uid, uint32_t item_uid) { @@ -281,10 +227,6 @@ bool entity_has_item_type(uint32_t uid, std::vector entity_types) } return false; } -bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type) -{ - return entity_has_item_type(uid, std::vector{entity_type}); -} std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, uint32_t mask) { @@ -313,15 +255,6 @@ std::vector entity_get_items_by(uint32_t uid, std::vector en } return found; } -std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask) -{ - return entity_get_items_by(uid, std::vector{entity_type}, mask); -} - -std::vector get_entities_by_draw_depth(uint8_t draw_depth, LAYER l) -{ - return get_entities_by_draw_depth(std::vector{draw_depth}, l); -} std::vector get_entities_by_draw_depth(std::vector draw_depths, LAYER l) { diff --git a/src/game_api/entity_lookup.hpp b/src/game_api/entity_lookup.hpp index c774cbb0a..fca752170 100644 --- a/src/game_api/entity_lookup.hpp +++ b/src/game_api/entity_lookup.hpp @@ -9,26 +9,70 @@ struct Layer; int32_t get_grid_entity_at(float x, float y, LAYER layer); -std::vector get_entities(); + std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer); -std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer); -std::vector get_entities_by_type(std::vector entity_types); -std::vector get_entities_by_type(ENT_TYPE entity_type); -std::vector get_entities_by_mask(uint32_t mask); -std::vector get_entities_by_layer(LAYER layer); + +inline std::vector get_entities() +{ + return get_entities_by({}, 0, LAYER::BOTH); +} +inline std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer) +{ + return get_entities_by(std::vector{entity_type}, mask, layer); +} +inline std::vector get_entities_by_type(std::vector entity_types) +{ + return get_entities_by(std::move(entity_types), 0, LAYER::BOTH); +} +inline std::vector get_entities_by_type(ENT_TYPE entity_type) +{ + return get_entities_by(std::vector{entity_type}, 0, LAYER::BOTH); +} +inline std::vector get_entities_by_mask(uint32_t mask) +{ + return get_entities_by({}, mask, LAYER::BOTH); +} +inline std::vector get_entities_by_layer(LAYER layer) +{ + return get_entities_by({}, 0, layer); +} std::vector get_entities_at(std::vector entity_types, uint32_t mask, float x, float y, LAYER layer, float radius); -std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius); +inline std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius) +{ + return get_entities_at(std::vector{entity_type}, mask, x, y, layer, radius); +} std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer); -std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer); -std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer); -std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer); +inline std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer) +{ + return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, hitbox, layer); +} +inline std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) +{ + return get_entities_overlapping_hitbox(std::move(entity_types), mask, {sx, sy2, sx2, sy}, layer); +} +inline std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) +{ + return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, {sx, sy2, sx2, sy}, layer); +} std::vector get_entities_overlapping_by_pointer(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer); -std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer); +inline std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) +{ + return get_entities_overlapping_by_pointer(std::vector{entity_type}, mask, sx, sy, sx2, sy2, layer); +} bool entity_has_item_uid(uint32_t uid, uint32_t item_uid); bool entity_has_item_type(uint32_t uid, std::vector entity_types); -bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type); +inline bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type) +{ + return entity_has_item_type(uid, std::vector{entity_type}); +} std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, uint32_t mask); -std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask); -std::vector get_entities_by_draw_depth(uint8_t draw_depth, LAYER l); +inline std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask) +{ + return entity_get_items_by(uid, std::vector{entity_type}, mask); +} std::vector get_entities_by_draw_depth(std::vector draw_depths, LAYER l); +inline std::vector get_entities_by_draw_depth(uint8_t draw_depth, LAYER l) +{ + return get_entities_by_draw_depth(std::vector{draw_depth}, l); +} std::vector get_proper_types(std::vector ent_types); diff --git a/src/game_api/game_api.cpp b/src/game_api/game_api.cpp index b4521058a..5ccdefcab 100644 --- a/src/game_api/game_api.cpp +++ b/src/game_api/game_api.cpp @@ -6,22 +6,18 @@ GameAPI* GameAPI::get() { + static_assert(sizeof(GameAPI) == 0x60); using GetGameAPI = GameAPI*(); static auto addr = (GetGameAPI*)get_address("get_game_api"); return addr(); } -float GameAPI::get_current_zoom() +float GameAPI::get_current_zoom() const { auto state = State::get().ptr(); return renderer->current_zoom + get_layer_transition_zoom_offset(state->camera_layer); } -float GameAPI::get_target_zoom() -{ - return renderer->target_zoom + renderer->target_zoom_offset; -} - void GameAPI::set_zoom(std::optional current, std::optional target) { if (current.has_value()) diff --git a/src/game_api/game_api.hpp b/src/game_api/game_api.hpp index 025a018e3..6a894e649 100644 --- a/src/game_api/game_api.hpp +++ b/src/game_api/game_api.hpp @@ -110,12 +110,15 @@ struct UnknownAPIStuff uint32_t unknown7; // padding? }; -struct GameAPI // size 0x60 +struct GameAPI { static GameAPI* get(); - float get_current_zoom(); - float get_target_zoom(); + float get_current_zoom() const; + float get_target_zoom() const + { + return renderer->target_zoom + renderer->target_zoom_offset; + } void set_zoom(std::optional current, std::optional target); diff --git a/src/game_api/game_patches.cpp b/src/game_api/game_patches.cpp index 419e93669..ffd6763a4 100644 --- a/src/game_api/game_patches.cpp +++ b/src/game_api/game_patches.cpp @@ -320,7 +320,7 @@ void patch_entering_closed_door_crash() if (rva == 0) return; size_t jump_addr = memory.at_exe(rva + 3); - size_t offset = memory_read(jump_addr + 2); + int32_t offset = memory_read(jump_addr + 2); return_addr = jump_addr + 6 + offset; } std::string_view new_code{ diff --git a/src/game_api/illumination.hpp b/src/game_api/illumination.hpp index 7376ce473..aa4389b0f 100644 --- a/src/game_api/illumination.hpp +++ b/src/game_api/illumination.hpp @@ -69,7 +69,7 @@ struct Illumination }; }; -Illumination* create_illumination(Vec2 pos, Color color, LIGHT_TYPE type, float size, uint8_t flags, int32_t uid, LAYER layer); -Illumination* create_illumination(Color color, float size, float x, float y); -Illumination* create_illumination(Color color, float size, int32_t uid); +[[nodiscard]] Illumination* create_illumination(Vec2 pos, Color color, LIGHT_TYPE type, float size, uint8_t flags, int32_t uid, LAYER layer); +[[nodiscard]] Illumination* create_illumination(Color color, float size, float x, float y); +[[nodiscard]] Illumination* create_illumination(Color color, float size, int32_t uid); void refresh_illumination(Illumination* illumination); diff --git a/src/game_api/items.cpp b/src/game_api/items.cpp deleted file mode 100644 index 35e6de696..000000000 --- a/src/game_api/items.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "items.hpp" - -#include // for uint8_t - -Player* Items::player(uint8_t index) -{ - return players[index]; -} diff --git a/src/game_api/items.hpp b/src/game_api/items.hpp index e8e4c88d4..b53f30c5b 100644 --- a/src/game_api/items.hpp +++ b/src/game_api/items.hpp @@ -125,5 +125,8 @@ struct Items uint8_t player_count; - Player* player(uint8_t index); + Player* player(uint8_t index) const + { + return players[index]; + } }; diff --git a/src/game_api/layer.cpp b/src/game_api/layer.cpp index 0a2bd6f9f..20fa461aa 100644 --- a/src/game_api/layer.cpp +++ b/src/game_api/layer.cpp @@ -100,7 +100,7 @@ Entity* Layer::spawn_entity_over(ENT_TYPE id, Entity* overlay, float x, float y) return ent; } -Entity* Layer::get_grid_entity_at(float x, float y) +Entity* Layer::get_grid_entity_at(float x, float y) const { const uint32_t ix = static_cast(std::round(x)); const uint32_t iy = static_cast(std::round(y)); diff --git a/src/game_api/layer.hpp b/src/game_api/layer.hpp index e77c86e0d..752eb2238 100644 --- a/src/game_api/layer.hpp +++ b/src/game_api/layer.hpp @@ -205,7 +205,7 @@ struct Layer Entity* spawn_apep(float x, float y, bool right); - Entity* get_grid_entity_at(float x, float y); + Entity* get_grid_entity_at(float x, float y) const; Entity* get_entity_at(float x, float y, uint32_t search_flags, uint32_t include_flags, uint32_t exclude_flags, uint32_t one_of_flags); diff --git a/src/game_api/level_api.cpp b/src/game_api/level_api.cpp index 2b700a90b..d4c459179 100644 --- a/src/game_api/level_api.cpp +++ b/src/game_api/level_api.cpp @@ -85,7 +85,7 @@ bool is_room_flipped(float x, float y) { thread_local StateMemory* state_ptr = State::get().ptr_local(); auto [ix, iy] = state_ptr->level_gen->get_room_index(x, y); - return state_ptr->level_gen->flipped_rooms->rooms[ix + iy * 8]; + return state_ptr->level_gen->flipped_rooms->rooms[ix + iy * 8ull]; } struct CommunityTileCode @@ -1124,7 +1124,7 @@ void do_extra_spawns(ThemeInfo* theme, std::uint32_t border_size, std::uint32_t { const auto random_idx = static_cast(prng.internal_random_index(valid_pos.size(), PRNG::EXTRA_SPAWNS)); const auto idx = random_idx < valid_pos.size() ? random_idx : 0; - const auto [x, y] = valid_pos[idx]; + const auto& [x, y] = valid_pos[idx]; provider.provider.do_spawn(x, y, layer); valid_pos.erase(valid_pos.begin() + idx); @@ -1244,11 +1244,11 @@ void spawn_room_from_tile_codes(LevelGenData* level_gen_data, int room_idx_x, in } LevelGenRoomData room_data{}; - std::memcpy(room_data.front_layer.data(), front_room_data, 10 * 8); + std::memcpy(room_data.front_layer.data(), front_room_data, 10 * 8ull); if (dual_room) { room_data.back_layer.emplace(); - std::memcpy(room_data.back_layer.value().data(), front_room_data, 10 * 8); + std::memcpy(room_data.back_layer.value().data(), front_room_data, 10 * 8ull); } std::optional changed_data = pre_handle_room_tiles(room_data, room_idx_x, room_idx_y, room_template); if (changed_data) @@ -1529,15 +1529,6 @@ void LevelGenData::init() g_test_chance = (TestChance*)get_address("level_gen_test_spawn_chance"); } -std::optional LevelGenData::get_tile_code(const std::string& tile_code) -{ - auto it = tile_codes.find((game_string&)tile_code); - if (it != tile_codes.end()) - { - return it->second.id; - } - return {}; -} std::uint32_t LevelGenData::define_tile_code(std::string tile_code) { if (auto existing = get_tile_code(tile_code)) @@ -1553,30 +1544,6 @@ std::uint32_t LevelGenData::define_tile_code(std::string tile_code) return it->second.id; } -std::optional LevelGenData::get_short_tile_code(ShortTileCodeDef short_tile_code_def) -{ - for (auto& [i, def] : short_tile_codes) - { - if (def == short_tile_code_def) - { - return i; - } - } - return std::nullopt; -} -std::optional LevelGenData::get_short_tile_code_def(uint8_t short_tile_code) -{ - auto it = short_tile_codes.find(short_tile_code); - if (it != short_tile_codes.end()) - { - return it->second; - } - return {}; -} -void LevelGenData::change_short_tile_code(uint8_t short_tile_code, ShortTileCodeDef short_tile_code_def) -{ - short_tile_codes[short_tile_code] = short_tile_code_def; -} std::optional LevelGenData::define_short_tile_code(ShortTileCodeDef short_tile_code_def) { // Try all printable chars, note that all chars are allowed since we won't need to parse this anymore @@ -1624,24 +1591,6 @@ std::pair& get_or_emplace_chance(game_unordered_ma return node.first->value; } -std::optional LevelGenData::get_chance(const std::string& chance) -{ - { - auto it = monster_chances.find((game_string&)chance); - if (it != monster_chances.end()) - { - return it->second.id; - } - } - { - auto it = trap_chances.find((game_string&)chance); - if (it != trap_chances.end()) - { - return it->second.id; - } - } - return {}; -} std::uint32_t LevelGenData::define_chance(std::string chance) { if (auto existing = get_chance(chance)) @@ -1731,15 +1680,6 @@ void LevelGenData::undefine_extra_spawn(std::uint32_t extra_spawn_id) { return provider.extra_spawn_id == extra_spawn_id; }); } -std::optional LevelGenData::get_room_template(const std::string& room_template) -{ - auto it = room_templates.find((game_string&)room_template); - if (it != room_templates.end()) - { - return it->second.id; - } - return {}; -} std::uint16_t LevelGenData::define_room_template(std::string room_template, RoomTemplateType type) { if (auto existing = get_room_template(room_template)) @@ -1770,7 +1710,7 @@ bool LevelGenData::set_room_template_size(std::uint16_t room_template, uint16_t } return false; } -RoomTemplateType LevelGenData::get_room_template_type(std::uint16_t room_template) +RoomTemplateType LevelGenData::get_room_template_type(std::uint16_t room_template) const { auto it = std::find_if(g_room_template_types.begin(), g_room_template_types.end(), [room_template](auto& t) { return t.first == room_template; }); @@ -1780,7 +1720,7 @@ RoomTemplateType LevelGenData::get_room_template_type(std::uint16_t room_templat } return RoomTemplateType::None; } -uint16_t LevelGenData::get_pretend_room_template(std::uint16_t room_template) +uint16_t LevelGenData::get_pretend_room_template(std::uint16_t room_template) const { switch (get_room_template_type(room_template)) { @@ -1796,7 +1736,7 @@ uint16_t LevelGenData::get_pretend_room_template(std::uint16_t room_template) } } -uint32_t ThemeInfo::get_aux_id() +uint32_t ThemeInfo::get_aux_id() const { thread_local const LevelGenSystem* level_gen_system = State::get().ptr_local()->level_gen; for (size_t i = 0; i < std::size(level_gen_system->themes); i++) @@ -1884,7 +1824,7 @@ std::pair LevelGenSystem::get_room_pos(uint32_t x, uint32_t y) static_cast(x * 10) + 2.5f, 122.5f - static_cast(y * 8)}; } -std::optional LevelGenSystem::get_room_template(uint32_t x, uint32_t y, uint8_t l) +std::optional LevelGenSystem::get_room_template(uint32_t x, uint32_t y, uint8_t l) const { auto* state_ptr = State::get().ptr_local(); @@ -1920,7 +1860,7 @@ bool LevelGenSystem::set_room_template(uint32_t x, uint32_t y, int l, uint16_t r return true; } -bool LevelGenSystem::is_room_flipped(uint32_t x, uint32_t y) +bool LevelGenSystem::is_room_flipped(uint32_t x, uint32_t y) const { auto* state_ptr = State::get().ptr_local(); @@ -1929,7 +1869,7 @@ bool LevelGenSystem::is_room_flipped(uint32_t x, uint32_t y) return flipped_rooms->rooms[x + y * 8]; } -bool LevelGenSystem::is_machine_room_origin(uint32_t x, uint32_t y) +bool LevelGenSystem::is_machine_room_origin(uint32_t x, uint32_t y) const { auto* state_ptr = State::get().ptr_local(); @@ -1979,7 +1919,7 @@ bool LevelGenSystem::set_shop_type(uint32_t x, uint32_t y, uint8_t l, SHOP_TYPE return true; } -std::string_view LevelGenSystem::get_room_template_name(uint16_t room_template) +std::string_view LevelGenSystem::get_room_template_name(uint16_t room_template) const { for (const auto& [name, room_tpl] : data->room_templates) { @@ -2017,7 +1957,7 @@ std::optional LevelGenSystem::get_procedural_spawn_chance_name return std::nullopt; } -uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) +uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) const { if (g_monster_chance_id_to_name.contains(chance_id)) { @@ -2181,11 +2121,6 @@ void force_co_subtheme(COSUBTHEME subtheme) } } -void grow_vines(LAYER l, uint32_t max_lengh) -{ - grow_vines(l, max_lengh, {0, 0, 0, 0}, false); -} - void grow_vines(LAYER l, uint32_t max_lengh, AABB area, bool destroy_broken) { area.abs(); @@ -2264,11 +2199,6 @@ void grow_vines(LAYER l, uint32_t max_lengh, AABB area, bool destroy_broken) } } -void grow_poles(LAYER l, uint32_t max_lengh) -{ - grow_poles(l, max_lengh, {0, 0, 0, 0}, false); -} - void grow_poles(LAYER l, uint32_t max_lengh, AABB area, bool destroy_broken) { area.abs(); diff --git a/src/game_api/level_api.hpp b/src/game_api/level_api.hpp index 6e3dbdb6f..b3b727649 100644 --- a/src/game_api/level_api.hpp +++ b/src/game_api/level_api.hpp @@ -95,15 +95,55 @@ struct LevelGenData { void init(); - std::optional get_tile_code(const std::string& tile_code); + std::optional get_tile_code(const std::string& tile_code) const + { + auto it = tile_codes.find((game_string&)tile_code); + if (it != tile_codes.end()) + { + return it->second.id; + } + return {}; + } std::uint32_t define_tile_code(std::string tile_code); - std::optional get_short_tile_code(ShortTileCodeDef short_tile_code_def); - std::optional get_short_tile_code_def(uint8_t short_tile_code); - void change_short_tile_code(uint8_t short_tile_code, ShortTileCodeDef short_tile_code_def); + std::optional get_short_tile_code(ShortTileCodeDef short_tile_code_def) const + { + for (auto& [i, def] : short_tile_codes) + { + if (def == short_tile_code_def) + { + return i; + } + } + return {}; + } + std::optional get_short_tile_code_def(uint8_t short_tile_code) const + { + auto it = short_tile_codes.find(short_tile_code); + if (it != short_tile_codes.end()) + { + return it->second; + } + return {}; + } + void change_short_tile_code(uint8_t short_tile_code, ShortTileCodeDef short_tile_code_def) + { + short_tile_codes[short_tile_code] = short_tile_code_def; + } std::optional define_short_tile_code(ShortTileCodeDef short_tile_code_def); - std::optional get_chance(const std::string& chance); + std::optional get_chance(const std::string& chance) const + { + if (auto it = monster_chances.find((game_string&)chance); it != monster_chances.end()) + { + return it->second.id; + } + if (auto it = trap_chances.find((game_string&)chance); it != trap_chances.end()) + { + return it->second.id; + } + return {}; + } std::uint32_t define_chance(std::string chance); std::uint32_t register_chance_logic_provider(std::uint32_t chance_id, SpawnLogicProvider provider); @@ -114,11 +154,19 @@ struct LevelGenData std::pair get_missing_extra_spawns(std::uint32_t extra_spawn_id); void undefine_extra_spawn(std::uint32_t extra_spawn_id); - std::optional get_room_template(const std::string& room_template); + std::optional get_room_template(const std::string& room_template) const + { + auto it = room_templates.find((game_string&)room_template); + if (it != room_templates.end()) + { + return it->second.id; + } + return {}; + } std::uint16_t define_room_template(std::string room_template, RoomTemplateType type); bool set_room_template_size(std::uint16_t room_template, uint16_t width, uint16_t height); - RoomTemplateType get_room_template_type(std::uint16_t room_template); - uint16_t get_pretend_room_template(std::uint16_t room_template); + RoomTemplateType get_room_template_type(std::uint16_t room_template) const; + uint16_t get_pretend_room_template(std::uint16_t room_template) const; union { @@ -368,7 +416,7 @@ class ThemeInfo /// Spawns a single procedural entity, used in spawn_procedural (mostly monsters, scarb in dark levels etc.) virtual void do_procedural_spawn(SpawnInfo* info) = 0; - uint32_t get_aux_id(); + uint32_t get_aux_id() const; }; static_assert(sizeof(ThemeInfo) == 0x20); @@ -531,19 +579,19 @@ struct LevelGenSystem static std::pair get_room_index(float x, float y); static std::pair get_room_pos(uint32_t x, uint32_t y); - std::optional get_room_template(uint32_t x, uint32_t y, uint8_t l); + std::optional get_room_template(uint32_t x, uint32_t y, uint8_t l) const; bool set_room_template(uint32_t x, uint32_t y, int l, uint16_t room_template); - bool is_room_flipped(uint32_t x, uint32_t y); - bool is_machine_room_origin(uint32_t x, uint32_t y); + bool is_room_flipped(uint32_t x, uint32_t y) const; + bool is_machine_room_origin(uint32_t x, uint32_t y) const; bool mark_as_machine_room_origin(uint32_t x, uint32_t y, uint8_t l); bool mark_as_set_room(uint32_t x, uint32_t y, uint8_t l, bool is_set_room); - bool set_shop_type(uint32_t x, uint32_t y, uint8_t l, SHOP_TYPE shop_type); + static bool set_shop_type(uint32_t x, uint32_t y, uint8_t l, SHOP_TYPE shop_type); - std::string_view get_room_template_name(uint16_t room_template); - std::optional get_procedural_spawn_chance_name(uint32_t chance_id); - uint32_t get_procedural_spawn_chance(uint32_t chance_id); + std::string_view get_room_template_name(uint16_t room_template) const; + static std::optional get_procedural_spawn_chance_name(uint32_t chance_id); + uint32_t get_procedural_spawn_chance(uint32_t chance_id) const; bool set_procedural_spawn_chance(uint32_t chance_id, uint32_t inverse_chance); ~LevelGenSystem() = delete; // cuz it was complaining @@ -559,11 +607,17 @@ using COSUBTHEME = int8_t; // NoAlias COSUBTHEME get_co_subtheme(); void force_co_subtheme(COSUBTHEME subtheme); -void grow_vines(LAYER l, uint32_t max_lengh); void grow_vines(LAYER l, uint32_t max_lengh, AABB area, bool destroy_broken); +inline void grow_vines(LAYER l, uint32_t max_lengh) +{ + grow_vines(l, max_lengh, {0, 0, 0, 0}, false); +} -void grow_poles(LAYER l, uint32_t max_lengh); void grow_poles(LAYER l, uint32_t max_lengh, AABB area, bool destroy_broken); +inline void grow_poles(LAYER l, uint32_t max_lengh) +{ + grow_poles(l, max_lengh, {0, 0, 0, 0}, false); +} bool grow_chain_and_blocks(); bool grow_chain_and_blocks(uint32_t x, uint32_t y); diff --git a/src/game_api/math.cpp b/src/game_api/math.cpp index e09db53b9..ff430de1f 100644 --- a/src/game_api/math.cpp +++ b/src/game_api/math.cpp @@ -1,6 +1,6 @@ #include "math.hpp" -bool Triangle::is_point_inside(const Vec2 p, float epsilon) const +bool Triangle::is_point_inside(const Vec2 p, float epsilon) const noexcept { // you can compare it eather by area or by angle // not sure if one if faster thne the order, so i left code for both @@ -17,7 +17,7 @@ bool Triangle::is_point_inside(const Vec2 p, float epsilon) const return std::abs(pi - (angle1 + angle2 + angle3)) < epsilon; } -Vec2 intersection(const Vec2 A, const Vec2 B, const Vec2 C, const Vec2 D) +Vec2 intersection(const Vec2 A, const Vec2 B, const Vec2 C, const Vec2 D) noexcept { float a = B.y - A.y; float b = A.x - B.x; @@ -35,19 +35,19 @@ Vec2 intersection(const Vec2 A, const Vec2 B, const Vec2 C, const Vec2 D) return Vec2{(b1 * c - b * c1) / det, (a * c1 - a1 * c) / det}; } -float two_lines_angle(const Vec2 A, const Vec2 common, const Vec2 B) +float two_lines_angle(const Vec2 A, const Vec2 common, const Vec2 B) noexcept { Vec2 ab = common - B; Vec2 bc = A - common; return std::atan2((bc.y * ab.x - bc.x * ab.y), (bc.x * ab.x + bc.y * ab.y)); }; -float two_lines_angle(const Vec2 line1_A, const Vec2 line1_B, const Vec2 line2_A, const Vec2 line2_B) +float two_lines_angle(const Vec2 line1_A, const Vec2 line1_B, const Vec2 line2_A, const Vec2 line2_B) noexcept { return two_lines_angle(line1_A, intersection(line1_A, line1_B, line2_A, line2_B), line2_B); }; -bool Quad::is_point_inside(const Vec2 p, float epsilon) const +bool Quad::is_point_inside(const Vec2 p, float epsilon) const noexcept { std::tuple points = *this; diff --git a/src/game_api/math.hpp b/src/game_api/math.hpp index 7cf3593ec..189a8792d 100644 --- a/src/game_api/math.hpp +++ b/src/game_api/math.hpp @@ -11,17 +11,17 @@ struct Vec2 Vec2(const Vec2& other) = default; - Vec2(float x_, float y_) + Vec2(float x_, float y_) noexcept : x(x_), y(y_){}; /// NoDoc - Vec2(std::pair p) + Vec2(std::pair p) noexcept : x(p.first), y(p.second){}; /// NoDoc - Vec2(const ImVec2&); + Vec2(const ImVec2&) noexcept; - Vec2& rotate(float angle, float px, float py) + Vec2& rotate(float angle, float px, float py) noexcept { const float sin_a{std::sin(angle)}; const float cos_a{std::cos(angle)}; @@ -35,90 +35,90 @@ struct Vec2 return *this; } /// Just simple pythagoras theorem - float distance_to(const Vec2 other) const + float distance_to(const Vec2 other) const noexcept { auto diff{*this - other}; diff *= diff; // pow return (float)std::sqrt(diff.x + diff.y); } - Vec2& set(const Vec2& other) + Vec2& set(const Vec2& other) noexcept { *this = other; return *this; } - Vec2 operator+(const Vec2& a) const + Vec2 operator+(const Vec2& a) const noexcept { return Vec2{x + a.x, y + a.y}; } - Vec2 operator-(const Vec2& a) const + Vec2 operator-(const Vec2& a) const noexcept { return Vec2{x - a.x, y - a.y}; } - Vec2 operator-() const + Vec2 operator-() const noexcept { return {-x, -y}; } - Vec2 operator*(const Vec2& a) const + Vec2 operator*(const Vec2& a) const noexcept { return Vec2{x * a.x, y * a.y}; } - Vec2 operator*(float a) const + Vec2 operator*(float a) const noexcept { return Vec2{x * a, y * a}; } - Vec2 operator/(const Vec2& a) const + Vec2 operator/(const Vec2& a) const noexcept { return Vec2{x / a.x, y / a.y}; } - Vec2 operator/(const float& a) const + Vec2 operator/(const float& a) const noexcept { return Vec2{x / a, y / a}; } - Vec2& operator+=(const Vec2& a) + Vec2& operator+=(const Vec2& a) noexcept { x += a.x; y += a.y; return *this; } - Vec2& operator-=(const Vec2& a) + Vec2& operator-=(const Vec2& a) noexcept { x -= a.x; y -= a.y; return *this; } - Vec2& operator*=(const Vec2& a) + Vec2& operator*=(const Vec2& a) noexcept { x *= a.x; y *= a.y; return *this; } - Vec2& operator++() + Vec2& operator++() noexcept { x++; y++; return *this; } - Vec2 operator++(int) + Vec2 operator++(int) noexcept { Vec2 old = *this; operator++(); return old; } - Vec2& operator--() + Vec2& operator--() noexcept { x--; y--; return *this; } - Vec2 operator--(int) + Vec2 operator--(int) noexcept { Vec2 old = *this; operator--(); return old; } Vec2& operator=(const Vec2& a) = default; - bool operator==(const Vec2& a) const + bool operator==(const Vec2& a) const noexcept { return x == a.x && y == a.y; } @@ -126,15 +126,15 @@ struct Vec2 std::tuple split() {} // just for the autodoc */ - operator std::pair() const + operator std::pair() const noexcept { return {x, y}; } - operator std::tuple() const + operator std::tuple() const noexcept { return {x, y}; } - operator std::tuple() + operator std::tuple() noexcept { return {x, y}; } @@ -151,7 +151,7 @@ struct AABB /// Copy an axis aligned bounding box AABB(const AABB& other) = default; /// NoDoc - AABB(const std::tuple tuple) + AABB(const std::tuple tuple) noexcept { left = std::get<0>(tuple); top = std::get<1>(tuple); @@ -159,25 +159,25 @@ struct AABB bottom = std::get<3>(tuple); }; - AABB(const Vec2& top_left, const Vec2& bottom_right) + AABB(const Vec2& top_left, const Vec2& bottom_right) noexcept : left(top_left.x), top(top_left.y), right(bottom_right.x), bottom(bottom_right.y){}; /// Create a new axis aligned bounding box by specifying its values - AABB(float left_, float top_, float right_, float bottom_) + AABB(float left_, float top_, float right_, float bottom_) noexcept : left(left_), top(top_), right(right_), bottom(bottom_){}; - bool overlaps_with(const AABB& other) const + bool overlaps_with(const AABB& other) const noexcept { return left < other.right && other.left < right && bottom < other.top && other.bottom < top; } - bool is_valid() const + bool is_valid() const noexcept { return !(left == 0.0f && right == 0.0f && top == 0.0f && bottom == 0.0f); } /// Fixes the AABB if any of the sides have negative length - AABB& abs() + AABB& abs() noexcept { if (left > right) std::swap(left, right); @@ -188,13 +188,13 @@ struct AABB /// Grows or shrinks the AABB by the given amount in all directions. /// If `amount < 0` and `abs(amount) > right/top - left/bottom` the respective dimension of the AABB will become `0`. - AABB& extrude(float amount) + AABB& extrude(float amount) noexcept { return extrude(amount, amount); } /// Grows or shrinks the AABB by the given amount in each direction. /// If `amount_x/y < 0` and `abs(amount_x/y) > right/top - left/bottom` the respective dimension of the AABB will become `0`. - AABB& extrude(float amount_x, float amount_y) + AABB& extrude(float amount_x, float amount_y) noexcept { left -= amount_x; right += amount_x; @@ -214,7 +214,7 @@ struct AABB return *this; } /// Offsets the AABB by the given offset. - AABB& offset(float off_x, float off_y) + AABB& offset(float off_x, float off_y) noexcept { left += off_x; bottom += off_y; @@ -223,14 +223,14 @@ struct AABB return *this; } /// Same as offset - AABB operator+(const Vec2& a) const + AABB operator+(const Vec2& a) const noexcept { AABB new_aabb{*this}; new_aabb.offset(a.x, a.y); return new_aabb; } /// Same as offset - AABB operator-(const Vec2& a) const + AABB operator-(const Vec2& a) const noexcept { AABB new_aabb{*this}; new_aabb.offset(-a.x, -a.y); @@ -238,27 +238,27 @@ struct AABB } AABB& operator=(const AABB& a) = default; /// Compute area of the AABB, can be zero if one dimension is zero or negative if one dimension is inverted. - float area() const + float area() const noexcept { return width() * height(); } /// Short for `(aabb.left + aabb.right) / 2.0f, (aabb.top + aabb.bottom) / 2.0f`. - std::pair center() const + std::pair center() const noexcept { return {(left + right) / 2.0f, (top + bottom) / 2.0f}; } /// Short for `aabb.right - aabb.left`. - float width() const + float width() const noexcept { return (right - left); } /// Short for `aabb.top - aabb.bottom`. - float height() const + float height() const noexcept { return (top - bottom); } /// Checks if point lies between left/right and top/bottom - bool is_point_inside(const Vec2 p) const + bool is_point_inside(const Vec2 p) const noexcept { AABB copy{*this}; copy.abs(); @@ -267,7 +267,7 @@ struct AABB return false; } - bool is_point_inside(float x, float y) const + bool is_point_inside(float x, float y) const noexcept { AABB copy{*this}; copy.abs(); @@ -276,7 +276,7 @@ struct AABB return false; } - AABB& set(const AABB& other) + AABB& set(const AABB& other) noexcept { *this = other; return *this; @@ -285,7 +285,7 @@ struct AABB std::tuple split() {} // just for the autodoc */ - operator std::tuple() const + operator std::tuple() const noexcept { return {left, top, right, bottom}; } @@ -300,29 +300,29 @@ struct Triangle { Triangle() = default; Triangle(const Triangle& other) = default; - Triangle(const Vec2& _a, const Vec2& _b, const Vec2& _c) + Triangle(const Vec2& _a, const Vec2& _b, const Vec2& _c) noexcept : A(_a), B(_b), C(_c){}; - Triangle(float ax, float ay, float bx, float by, float cx, float cy) + Triangle(float ax, float ay, float bx, float by, float cx, float cy) noexcept : A(ax, ay), B(bx, by), C(cx, cy){}; - Triangle& offset(const Vec2& off) + Triangle& offset(const Vec2& off) noexcept { A += off; B += off; C += off; return *this; } - Triangle& offset(float x, float y) + Triangle& offset(float x, float y) noexcept { return offset({x, y}); } - Triangle operator+(const Vec2& a) const + Triangle operator+(const Vec2& a) const noexcept { Triangle new_triangle{*this}; new_triangle.offset(a); return new_triangle; } - Triangle operator-(const Vec2& a) const + Triangle operator-(const Vec2& a) const noexcept { Triangle new_triangle{*this}; new_triangle.offset(-a); @@ -330,7 +330,7 @@ struct Triangle } Triangle& operator=(const Triangle& a) = default; /// Rotate triangle by an angle, the px/py are just coordinates, not offset from the center - Triangle& rotate(float angle, float px, float py) + Triangle& rotate(float angle, float px, float py) noexcept { const float sin_a{std::sin(angle)}; const float cos_a{std::cos(angle)}; @@ -352,12 +352,12 @@ struct Triangle return *this; } /// Also known as centroid - Vec2 center() const + Vec2 center() const noexcept { return {(A.x + B.x + C.x) / 3, (A.y + B.y + C.y) / 3}; } /// Returns ABC, BCA, CAB angles in radians - std::tuple get_angles() const + std::tuple get_angles() const noexcept { Vec2 ab = B - A; Vec2 ac = C - A; @@ -367,7 +367,7 @@ struct Triangle float a_cab = std::abs(std::atan2(ab.y * ac.x - ab.x * ac.y, ab.x * ac.x + ab.y * ac.y)); return {a_abc, a_cab, a_bca}; } - Triangle& scale(float scale) + Triangle& scale(float scale) noexcept { Vec2 centroid = center(); A = (A - centroid) * scale + centroid; @@ -375,24 +375,24 @@ struct Triangle C = (C - centroid) * scale + centroid; return *this; } - float area() const + float area() const noexcept { return std::abs((A.x * (B.y - C.y) + B.x * (C.y - A.y) + C.x * (A.y - B.y)) / 2.0f); } - bool is_point_inside(const Vec2 p) const + bool is_point_inside(const Vec2 p) const noexcept { return is_point_inside(p, 0.0001f); } - bool is_point_inside(const Vec2 p, float epsilon) const; - bool is_point_inside(float x, float y) const + bool is_point_inside(const Vec2 p, float epsilon) const noexcept; + bool is_point_inside(float x, float y) const noexcept { return is_point_inside(Vec2{x, y}, 0.0001f); } - bool is_point_inside(float x, float y, float epsilon) const + bool is_point_inside(float x, float y, float epsilon) const noexcept { return is_point_inside(Vec2{x, y}, epsilon); } - Triangle& set(const Triangle& other) + Triangle& set(const Triangle& other) noexcept { *this = other; return *this; @@ -407,7 +407,7 @@ struct Triangle */ /// Returns the corners - operator std::tuple() const + operator std::tuple() const noexcept { return {A, B, C}; } @@ -423,17 +423,17 @@ struct Quad Quad(const Quad& other) = default; - Quad(const Vec2& bottom_left_, const Vec2& bottom_right_, const Vec2& top_right_, const Vec2& top_left_) + Quad(const Vec2& bottom_left_, const Vec2& bottom_right_, const Vec2& top_right_, const Vec2& top_left_) noexcept : bottom_left_x(bottom_left_.x), bottom_left_y(bottom_left_.y), bottom_right_x(bottom_right_.x), bottom_right_y(bottom_right_.y), top_right_x(top_right_.x), top_right_y(top_right_.y), top_left_x(top_left_.x), top_left_y(top_left_.y){}; - Quad(float _bottom_left_x, float _bottom_left_y, float _bottom_right_x, float _bottom_right_y, float _top_right_x, float _top_right_y, float _top_left_x, float _top_left_y) + Quad(float _bottom_left_x, float _bottom_left_y, float _bottom_right_x, float _bottom_right_y, float _top_right_x, float _top_right_y, float _top_left_x, float _top_left_y) noexcept : bottom_left_x(_bottom_left_x), bottom_left_y(_bottom_left_y), bottom_right_x(_bottom_right_x), bottom_right_y(_bottom_right_y), top_right_x(_top_right_x), top_right_y(_top_right_y), top_left_x(_top_left_x), top_left_y(_top_left_y){}; - Quad(const AABB& aabb) + Quad(const AABB& aabb) noexcept : bottom_left_x(aabb.left), bottom_left_y(aabb.bottom), bottom_right_x(aabb.right), bottom_right_y(aabb.bottom), top_right_x(aabb.right), top_right_y(aabb.top), top_left_x(aabb.left), top_left_y(aabb.top){}; /// Returns the max/min values of the Quad - AABB get_AABB() const + AABB get_AABB() const noexcept { AABB result; result.right = std::max({bottom_left_x, bottom_right_x, top_right_x, top_left_x}); @@ -443,7 +443,7 @@ struct Quad return result; } - Quad& offset(float off_x, float off_y) + Quad& offset(float off_x, float off_y) noexcept { bottom_left_x += off_x; bottom_right_x += off_x; @@ -457,28 +457,28 @@ struct Quad return *this; } /// Same as offset - Quad operator+(const Vec2& a) const + Quad operator+(const Vec2& a) const noexcept { Quad new_quad{*this}; new_quad.offset(a.x, a.y); return new_quad; } /// Same as offset - Quad operator-(const Vec2& a) const + Quad operator-(const Vec2& a) const noexcept { Quad new_quad{*this}; new_quad.offset(-a.x, -a.y); return new_quad; } Quad& operator=(const Quad& a) = default; - bool is_null() const + bool is_null() const noexcept { return bottom_left_x == 0 && bottom_left_y == 0 && bottom_right_x == 0 && bottom_right_y == 0 /**/ && top_left_x == 0 && top_left_y == 0 && top_right_x == 0 && top_right_y == 0; } /// Rotates a Quad by an angle, px/py are not offsets, use `:get_AABB():center()` to get approximated center for simetrical quadrangle - Quad& rotate(float angle, float px, float py) + Quad& rotate(float angle, float px, float py) noexcept { const float sin_a{std::sin(angle)}; const float cos_a{std::cos(angle)}; @@ -502,7 +502,7 @@ struct Quad return *this; } - Quad& flip_horizontally() + Quad& flip_horizontally() noexcept { std::swap(top_left_x, top_right_x); std::swap(top_left_y, top_right_y); @@ -512,7 +512,7 @@ struct Quad return *this; } - Quad& flip_vertically() + Quad& flip_vertically() noexcept { std::swap(top_left_x, bottom_left_x); std::swap(top_left_y, bottom_left_y); @@ -522,20 +522,20 @@ struct Quad return *this; } - bool is_point_inside(const Vec2 p) const + bool is_point_inside(const Vec2 p) const noexcept { return is_point_inside(p, 0.00001f); } - bool is_point_inside(const Vec2 p, float epsilon) const; - bool is_point_inside(float x, float y) const + bool is_point_inside(const Vec2 p, float epsilon) const noexcept; + bool is_point_inside(float x, float y) const noexcept { return is_point_inside(Vec2{x, y}, 0.00001f); } - bool is_point_inside(float x, float y, float epsilon) const + bool is_point_inside(float x, float y, float epsilon) const noexcept { return is_point_inside(Vec2{x, y}, epsilon); } - Quad& set(const Quad& other) + Quad& set(const Quad& other) noexcept { *this = other; return *this; @@ -551,7 +551,7 @@ struct Quad */ /// Returns the corners in order: bottom_left, bottom_right, top_right, top_left - operator std::tuple() const + operator std::tuple() const noexcept { return {{bottom_left_x, bottom_left_y}, {bottom_right_x, bottom_right_y}, {top_right_x, top_right_y}, {top_left_x, top_left_y}}; } @@ -567,10 +567,10 @@ struct Quad }; /// Find intersection point of two lines [A, B] and [C, D], returns INFINITY if the lines don't intersect each other [parallel] -Vec2 intersection(const Vec2 A, const Vec2 B, const Vec2 C, const Vec2 D); +Vec2 intersection(const Vec2 A, const Vec2 B, const Vec2 C, const Vec2 D) noexcept; /// Mesures angle between two lines with one common point -float two_lines_angle(const Vec2 A, const Vec2 common, const Vec2 B); +float two_lines_angle(const Vec2 A, const Vec2 common, const Vec2 B) noexcept; /// Gets line1_A, intersection point and line2_B and calls the 3 parameter version of this function -float two_lines_angle(const Vec2 line1_A, const Vec2 line1_B, const Vec2 line2_A, const Vec2 line2_B); +float two_lines_angle(const Vec2 line1_A, const Vec2 line1_B, const Vec2 line2_A, const Vec2 line2_B) noexcept; diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index 673c1ee2a..510709093 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -41,15 +41,15 @@ void ExecutableMemory::deleter_t::operator()(std::byte* mem) const Memory& Memory::get() { - static Memory mem{[]() - { - auto exe = (size_t)GetModuleHandleA(NULL); + static Memory mem = []() + { + auto exe = (size_t)GetModuleHandleA(NULL); - // Skipping bundle for faster memory search - auto after_bundle_ = find_after_bundle(exe); + // Skipping bundle for faster memory search + auto after_bundle_ = find_after_bundle(exe); - return Memory{exe, after_bundle_}; - }()}; + return Memory{exe, after_bundle_}; + }(); return mem; } diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index 9e45fe959..2d4055a8e 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -81,7 +81,7 @@ struct Memory ~Memory(){}; }; -LPVOID alloc_mem_rel32(size_t addr, size_t size); +[[nodiscard]] LPVOID alloc_mem_rel32(size_t addr, size_t size); void write_mem_prot(size_t addr, std::string_view payload, bool prot); void write_mem_prot(size_t addr, std::string payload, bool prot); void write_mem(size_t addr, std::string payload); diff --git a/src/game_api/movable.hpp b/src/game_api/movable.hpp index 8badf0250..198080099 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -90,17 +90,32 @@ class Movable : public Entity /// NoDoc void poison(int16_t frames); // 1 - 32767 frames ; -1 = no poison // Changes default poison_tick_timer - bool is_poisoned(); + bool is_poisoned() const + { + return (poison_tick_timer != -1); + } /// Damage the movable by the specified amount, stuns and gives it invincibility for the specified amount of frames and applies the velocities /// Returns: true if entity was affected, damage_dealer should break etc. false if the event should be ignored by damage_dealer? bool damage(uint32_t damage_dealer_uid, int8_t damage_amount, uint16_t stun_time, float velocity_x, float velocity_y, uint8_t iframes); // the original damage function was added to the API without the iframes param, but for backwards compatibility we preserve the broken one - bool broken_damage(uint32_t damage_dealer_uid, int8_t damage_amount, uint16_t stun_time, float velocity_x, float velocity_y); + bool broken_damage(uint32_t damage_dealer_uid, int8_t damage_amount, uint16_t stun_time, float velocity_x, float velocity_y) + { + return damage(damage_dealer_uid, damage_amount, stun_time, velocity_x, velocity_y, 80); + } - bool is_button_pressed(BUTTON button); - bool is_button_held(BUTTON button); - bool is_button_released(BUTTON button); + bool is_button_pressed(BUTTON button) const + { + return (buttons & button) == button && (buttons_previous & button) == 0; + } + bool is_button_held(BUTTON button) const + { + return (buttons & button) == button && (buttons_previous & button) == button; + } + bool is_button_released(BUTTON button) const + { + return (buttons & button) == 0 && (buttons_previous & button) == button; + } void set_pre_statemachine(std::uint32_t reserved_callback_id, std::function pre_state_machine); void set_post_statemachine(std::uint32_t reserved_callback_id, std::function post_state_machine); diff --git a/src/game_api/movable_behavior.cpp b/src/game_api/movable_behavior.cpp index 02732ed99..2ceb81d28 100644 --- a/src/game_api/movable_behavior.cpp +++ b/src/game_api/movable_behavior.cpp @@ -82,14 +82,6 @@ T call_custom_or_original(const auto& custom, VanillaMovableBehavior* base, T fa return fallback_return; } -uint8_t CustomMovableBehavior::get_state_id() const -{ - return state_id; -} -uint8_t CustomMovableBehavior::secondary_sort_id() const -{ - return 255; -} bool CustomMovableBehavior::force_state(Movable* movable) { return call_custom_or_original<&VanillaMovableBehavior::force_state>(custom_force_state, base_behavior, false, movable); @@ -183,10 +175,6 @@ void clear_behaviors(Movable* movable) using UpdateMovable = void(Movable&, const Vec2&, float, bool, bool, bool, bool); -void update_movable(Movable* movable) -{ - update_movable(movable, false); -} void update_movable(Movable* movable, bool disable_gravity) { Vec2 null{}; diff --git a/src/game_api/movable_behavior.hpp b/src/game_api/movable_behavior.hpp index fcc7ea397..573d33a25 100644 --- a/src/game_api/movable_behavior.hpp +++ b/src/game_api/movable_behavior.hpp @@ -51,8 +51,14 @@ struct CustomMovableBehavior final : MovableBehavior ~CustomMovableBehavior(); - virtual uint8_t get_state_id() const override; - virtual uint8_t secondary_sort_id() const override; + virtual uint8_t get_state_id() const override + { + return state_id; + } + virtual uint8_t secondary_sort_id() const override + { + return 255; + } virtual bool force_state(Movable* movable) override; virtual void on_enter(Movable* movable) override; virtual void on_exit(Movable* movable) override; @@ -76,12 +82,15 @@ void clear_behavior(Movable* movable, MovableBehavior* behavior); /// Clears all behaviors of this movable, need to call `add_behavior` to avoid crashing void clear_behaviors(Movable* movable); -/// Move a movable according to its velocity, update physics, gravity, etc. -/// Will also update `movable.animation_frame` and various timers and counters -void update_movable(Movable* movable); /// Move a movable according to its velocity, can disable gravity /// Will also update `movable.animation_frame` and various timers and counters void update_movable(Movable* movable, bool disable_gravity); +/// Move a movable according to its velocity, update physics, gravity, etc. +/// Will also update `movable.animation_frame` and various timers and counters +inline void update_movable(Movable* movable) +{ + update_movable(movable, false); +} /// Move a movable according to its velocity and `move`, if the movables `BUTTON.RUN` is /// held apply `sprint_factor` on `move.x`, can disable gravity or lock its horizontal /// movement via `on_rope`. Use this for example to update a custom enemy type. diff --git a/src/game_api/particles.cpp b/src/game_api/particles.cpp index c38ea89dc..5b967c71d 100644 --- a/src/game_api/particles.cpp +++ b/src/game_api/particles.cpp @@ -19,7 +19,7 @@ ParticleDB* particle_db_ptr() return addr; } -std::uint64_t ParticleDB::get_texture() +std::uint64_t ParticleDB::get_texture() const { return texture->id; } @@ -33,78 +33,6 @@ bool ParticleDB::set_texture(std::uint32_t texture_id) return false; } -EmittedParticlesInfo::Iterator EmittedParticlesInfo::begin() -{ - return Iterator{this, 0}; -} -EmittedParticlesInfo::Iterator EmittedParticlesInfo::end() -{ - return Iterator{this, particle_count}; -} -EmittedParticlesInfo::ConstIterator EmittedParticlesInfo::begin() const -{ - return cbegin(); -} -EmittedParticlesInfo::ConstIterator EmittedParticlesInfo::end() const -{ - return cend(); -} -EmittedParticlesInfo::ConstIterator EmittedParticlesInfo::cbegin() const -{ - return ConstIterator{this, 0}; -} -EmittedParticlesInfo::ConstIterator EmittedParticlesInfo::cend() const -{ - return ConstIterator{this, particle_count}; -} - -Particle EmittedParticlesInfo::front() -{ - return (*this)[0]; -} -Particle EmittedParticlesInfo::back() -{ - return (*this)[particle_count - 1]; -} -const Particle EmittedParticlesInfo::front() const -{ - return (*this)[0]; -} -const Particle EmittedParticlesInfo::back() const -{ - return (*this)[particle_count - 1]; -} - -bool EmittedParticlesInfo::empty() -{ - return particle_count == 0; -} -EmittedParticlesInfo::size_type EmittedParticlesInfo::size() -{ - return particle_count; -} - -Particle EmittedParticlesInfo::operator[](const size_type idx) -{ - return static_cast(*this)[idx]; -} -const Particle EmittedParticlesInfo::operator[](const size_type idx) const -{ - return { - max_lifetimes + idx, - lifetimes + idx, - x_positions + idx, - y_positions + idx, - unknown_x_positions + idx, - unknown_y_positions + idx, - colors + idx, - widths + idx, - heights + idx, - x_velocities + idx, - y_velocities + idx, - }; -} - ParticleDB* get_particle_type(PARTICLEEMITTER id) { static std::unordered_map mapping = {}; diff --git a/src/game_api/particles.hpp b/src/game_api/particles.hpp index 281531293..e12ec6ec6 100644 --- a/src/game_api/particles.hpp +++ b/src/game_api/particles.hpp @@ -71,7 +71,7 @@ struct ParticleDB ParticleDB(const ParticleDB& other) = default; ParticleDB(const PARTICLEEMITTER particle_id); - std::uint64_t get_texture(); + std::uint64_t get_texture() const; bool set_texture(std::uint32_t texture_id); }; @@ -81,9 +81,7 @@ struct ParticleEmitter uint32_t id; ParticleEmitter(const std::string& name_, uint32_t id_) - : name(name_), id(id_) - { - } + : name(name_), id(id_){}; }; struct Particle @@ -132,9 +130,7 @@ struct EmittedParticlesInfo { public: IteratorImpl(T* const src, uint32_t i) - : source{src}, index{i} - { - } + : source{src}, index{i} {}; Particle dereference() const noexcept { @@ -167,23 +163,77 @@ struct EmittedParticlesInfo using iterator = Iterator; using const_iterator = ConstIterator; - Iterator begin(); - Iterator end(); - ConstIterator begin() const; - ConstIterator end() const; - ConstIterator cbegin() const; - ConstIterator cend() const; + Iterator begin() + { + return Iterator{this, 0}; + } + Iterator end() + { + return Iterator{this, particle_count}; + } + ConstIterator begin() const + { + return cbegin(); + } + ConstIterator end() const + { + return cend(); + } + ConstIterator cbegin() const + { + return ConstIterator{this, 0}; + } + ConstIterator cend() const + { + return ConstIterator{this, particle_count}; + } - Particle front(); - Particle back(); - const Particle front() const; - const Particle back() const; + Particle front() + { + return (*this)[0]; + } + Particle back() + { + return (*this)[particle_count - 1]; + } + const Particle front() const + { + return (*this)[0]; + } + const Particle back() const + { + return (*this)[particle_count - 1]; + } - bool empty(); - size_type size(); + bool empty() const noexcept + { + return particle_count == 0; + } + size_type size() const noexcept + { + return particle_count; + } - Particle operator[](const size_type idx); - const Particle operator[](const size_type idx) const; + Particle operator[](const size_type idx) + { + return static_cast(*this)[idx]; + } + const Particle operator[](const size_type idx) const + { + return { + max_lifetimes + idx, + lifetimes + idx, + x_positions + idx, + y_positions + idx, + unknown_x_positions + idx, + unknown_y_positions + idx, + colors + idx, + widths + idx, + heights + idx, + x_velocities + idx, + y_velocities + idx, + }; + } }; struct ParticleEmitterInfo diff --git a/src/game_api/prng.cpp b/src/game_api/prng.cpp index 4cef622c9..4f406c9bb 100644 --- a/src/game_api/prng.cpp +++ b/src/game_api/prng.cpp @@ -112,19 +112,3 @@ std::optional PRNG::random_int(std::int64_t min, std::int64_t max, } return std::nullopt; } -std::pair PRNG::get_pair(PRNG_CLASS type) -{ - if (type >= 0 && type <= 9) - { - return pairs[type]; - } - return {0, 0}; -} -void PRNG::set_pair(PRNG_CLASS type, int64_t first, int64_t second) -{ - if (type >= 0 && type <= 9) - { - pairs[type].first = first; - pairs[type].second = second; - } -} diff --git a/src/game_api/prng.hpp b/src/game_api/prng.hpp index 3ac2c75d6..2a7872ff1 100644 --- a/src/game_api/prng.hpp +++ b/src/game_api/prng.hpp @@ -34,8 +34,22 @@ struct PRNG using prng_pair = std::pair; prng_pair get_and_advance(PRNG_CLASS type); - std::pair get_pair(PRNG_CLASS type); - void set_pair(PRNG_CLASS type, int64_t first, int64_t second); + std::pair get_pair(PRNG_CLASS type) const + { + if (type >= 0 && type <= 9) + { + return pairs[type]; + } + return {0, 0}; + } + void set_pair(PRNG_CLASS type, int64_t first, int64_t second) + { + if (type >= 0 && type <= 9) + { + pairs[type].first = first; + pairs[type].second = second; + } + } /// Generate a random integer in the range `[0, size)` std::int64_t internal_random_index(std::int64_t size, PRNG_CLASS type); diff --git a/src/game_api/render_api.hpp b/src/game_api/render_api.hpp index d0c90d1f7..bfc71dd79 100644 --- a/src/game_api/render_api.hpp +++ b/src/game_api/render_api.hpp @@ -174,7 +174,7 @@ struct TextRenderingInfo Letter* dest{nullptr}; Letter* source{nullptr}; - // 6 * wcslen(input_text), just numbers in order 0, 1, 2 ... have some strage effect if you change them + // 6 * text_length, just numbers in order 0, 1, 2 ... have some strage effect if you change them uint16_t* unknown6{nullptr}; uint16_t nof_special_character; // number of special characters, still not sure how the game knows which ones are the special ones? diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index 6b8823fe6..758806111 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -80,16 +80,11 @@ void invalidate_save_slots() SaveState::SaveState() { - addr = (size_t)malloc(8 * 0x400000); + addr = (size_t)malloc(8ull * 0x400000); save(); } -SaveState::~SaveState() -{ - clear(); -} - -StateMemory* SaveState::get_state() +StateMemory* SaveState::get_state() const { if (!addr) return nullptr; diff --git a/src/game_api/savestate.hpp b/src/game_api/savestate.hpp index 21880d3ca..f74a6590a 100644 --- a/src/game_api/savestate.hpp +++ b/src/game_api/savestate.hpp @@ -7,10 +7,13 @@ 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. The garbage collector will eventually clear the SaveStates you don't have a handle to any more though. SaveState(); - ~SaveState(); + ~SaveState() + { + clear(); + } /// Access the StateMemory inside a SaveState - StateMemory* get_state(); + StateMemory* get_state() const; /// Load a SaveState void load(); diff --git a/src/game_api/screen.cpp b/src/game_api/screen.cpp index 1050d7a66..8dbb02ece 100644 --- a/src/game_api/screen.cpp +++ b/src/game_api/screen.cpp @@ -262,10 +262,6 @@ JournalPageStory* JournalPageStory::construct(bool right_side, uint32_t pn) return page; } -bool JournalPage::is_right_side_page() -{ - return (this->background.x < 0); -} void JournalPage::set_page_background_side(bool right) { if (right) diff --git a/src/game_api/screen.hpp b/src/game_api/screen.hpp index 2200623c2..3422f2b81 100644 --- a/src/game_api/screen.hpp +++ b/src/game_api/screen.hpp @@ -892,7 +892,10 @@ class JournalPage } /// background.x < 0 - bool is_right_side_page(); + bool is_right_side_page() const + { + return (this->background.x < 0); + } void set_page_background_side(bool right); JOURNAL_PAGE_TYPE get_type(); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 5d426a7e9..35c2c4e4d 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -169,12 +169,6 @@ void LuaBackend::clear_all_callbacks() lua["on_screen"] = sol::lua_nil; } -bool LuaBackend::reset() -{ - clear(); - return true; -} - CustomMovableBehavior* LuaBackend::get_custom_movable_behavior(std::string_view name) { auto it = std::find_if(custom_movable_behaviors.begin(), custom_movable_behaviors.end(), [name](const CustomMovableBehaviorStorage& beh) @@ -1654,7 +1648,7 @@ bool LuaBackend::pre_set_feat(FEAT feat) return false; } -CurrentCallback LuaBackend::get_current_callback() +CurrentCallback LuaBackend::get_current_callback() const { return current_cb; } diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 8553b5518..54989b654 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -353,7 +353,11 @@ class LuaBackend void clear_all_callbacks(); bool update(); - virtual bool reset(); + virtual bool reset() + { + clear(); + return true; + } virtual bool pre_draw() { return true; @@ -438,7 +442,7 @@ class LuaBackend bool pre_load_journal_chapter(uint8_t chapter); std::vector post_load_journal_chapter(uint8_t chapter, const std::vector& pages); - CurrentCallback get_current_callback(); + CurrentCallback get_current_callback() const; void set_current_callback(int32_t aux_id, int32_t id, CallbackType type); void clear_current_callback(); diff --git a/src/game_api/script/lua_console.cpp b/src/game_api/script/lua_console.cpp index a75a19079..ef692d0ec 100644 --- a/src/game_api/script/lua_console.cpp +++ b/src/game_api/script/lua_console.cpp @@ -972,38 +972,6 @@ bool LuaConsole::pre_draw() return true; } -void LuaConsole::set_enabled(bool enable) -{ - enabled = enable; -} -bool LuaConsole::get_enabled() const -{ - return enabled; -} -bool LuaConsole::get_unsafe() const -{ - return unsafe; -} -const char* LuaConsole::get_name() const -{ - return "lua_console"; -} -const char* LuaConsole::get_id() const -{ - return "dev/lua_console"; -} -const char* LuaConsole::get_version() const -{ - return "1.337"; -} -const char* LuaConsole::get_path() const -{ - return "console_proxy.lua"; -} -const char* LuaConsole::get_root() const -{ - return "."; -} const std::filesystem::path& LuaConsole::get_root_path() const { static std::filesystem::path root_path{"."}; @@ -1167,14 +1135,3 @@ unsigned int LuaConsole::get_input_lines() num += *str == '\n'; return num; } - -void LuaConsole::set_geometry(float x, float y, float w, float h) -{ - pos = ImVec2(x, y); - size = ImVec2(w, h); -} - -void LuaConsole::set_alt_keys(bool enable) -{ - alt_keys = enable; -} diff --git a/src/game_api/script/lua_console.hpp b/src/game_api/script/lua_console.hpp index 717e51dd9..3c268c4dc 100644 --- a/src/game_api/script/lua_console.hpp +++ b/src/game_api/script/lua_console.hpp @@ -71,15 +71,39 @@ class LuaConsole : public LockableLuaBackend using LuaBackend::reset; virtual bool pre_draw() override; - virtual void set_enabled(bool enabled) override; - virtual bool get_enabled() const override; - - virtual bool get_unsafe() const override; - virtual const char* get_name() const override; - virtual const char* get_id() const override; - virtual const char* get_version() const override; - virtual const char* get_path() const override; - virtual const char* get_root() const override; + virtual void set_enabled(bool enable) override + { + enabled = enable; + } + virtual bool get_enabled() const override + { + return enabled; + } + + virtual bool get_unsafe() const override + { + return unsafe; + } + virtual const char* get_name() const override + { + return "lua_console"; + } + virtual const char* get_id() const override + { + return "dev/lua_console"; + } + virtual const char* get_version() const override + { + return "1.337"; + } + virtual const char* get_path() const override + { + return "console_proxy.lua"; + } + virtual const char* get_root() const override + { + return "."; + } virtual const std::filesystem::path& get_root_path() const override; void register_command(LuaBackend* provider, std::string command_name, sol::function cmd); @@ -94,6 +118,13 @@ class LuaConsole : public LockableLuaBackend std::string dump_api(); unsigned int get_input_lines(); - void set_geometry(float x, float y, float w, float h); - void set_alt_keys(bool enable); + void set_geometry(float x, float y, float w, float h) + { + pos = ImVec2(x, y); + size = ImVec2(w, h); + } + void set_alt_keys(bool enable) + { + alt_keys = enable; + } }; diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index f6845ecc9..87293226b 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2000,7 +2000,7 @@ end { std::vector files; auto backend = LuaBackend::get_calling_backend(); - auto base = backend->get_root_path(); + const auto& base = backend->get_root_path(); auto path = base / std::filesystem::path(dir.value_or(".")); if (!std::filesystem::exists(path) || !std::filesystem::is_directory(path)) return sol::make_object(lua, sol::lua_nil); diff --git a/src/game_api/script/script_impl.cpp b/src/game_api/script/script_impl.cpp index d0b4111b8..7318c38f4 100644 --- a/src/game_api/script/script_impl.cpp +++ b/src/game_api/script/script_impl.cpp @@ -143,19 +143,6 @@ bool ScriptImpl::reset() return false; } } -bool ScriptImpl::pre_update() -{ - if (changed) - { - result = ""; - changed = false; - if (!reset()) - { - return false; - } - } - return true; -} void ScriptImpl::set_enabled(bool enbl) { @@ -174,39 +161,6 @@ void ScriptImpl::set_enabled(bool enbl) } enabled = enbl; } -bool ScriptImpl::get_enabled() const -{ - return enabled; -} - -bool ScriptImpl::get_unsafe() const -{ - return meta.unsafe; -} -const char* ScriptImpl::get_name() const -{ - return meta.stem.c_str(); -} -const char* ScriptImpl::get_path() const -{ - return meta.file.c_str(); -} -const char* ScriptImpl::get_id() const -{ - return meta.id.c_str(); -} -const char* ScriptImpl::get_version() const -{ - return meta.version.c_str(); -} -const char* ScriptImpl::get_root() const -{ - return meta.path.c_str(); -} -const std::filesystem::path& ScriptImpl::get_root_path() const -{ - return script_folder; -} std::string ScriptImpl::execute(std::string str, bool raw) { diff --git a/src/game_api/script/script_impl.hpp b/src/game_api/script/script_impl.hpp index b21272a63..bd42fd99c 100644 --- a/src/game_api/script/script_impl.hpp +++ b/src/game_api/script/script_impl.hpp @@ -32,18 +32,53 @@ class ScriptImpl : public LockableLuaBackend std::string script_id(); virtual bool reset() override; - virtual bool pre_update() override; + virtual bool pre_update() override + { + if (changed) + { + result = ""; + changed = false; + if (!reset()) + { + return false; + } + } + return true; + } virtual void set_enabled(bool enabled) override; - virtual bool get_enabled() const override; - - virtual bool get_unsafe() const override; - virtual const char* get_name() const override; - virtual const char* get_id() const override; - virtual const char* get_version() const override; - virtual const char* get_path() const override; - virtual const char* get_root() const override; - virtual const std::filesystem::path& get_root_path() const override; + virtual bool get_enabled() const override + { + return enabled; + } + virtual bool get_unsafe() const override + { + return meta.unsafe; + } + virtual const char* get_name() const override + { + return meta.stem.c_str(); + } + virtual const char* get_id() const override + { + return meta.id.c_str(); + } + virtual const char* get_version() const override + { + return meta.version.c_str(); + } + virtual const char* get_path() const override + { + return meta.file.c_str(); + } + virtual const char* get_root() const override + { + return meta.path.c_str(); + } + virtual const std::filesystem::path& get_root_path() const override + { + return script_folder; + } std::string execute(std::string str, bool raw = false); sol::protected_function_result execute_raw(std::string str); diff --git a/src/game_api/script/usertypes/entity_lua.cpp b/src/game_api/script/usertypes/entity_lua.cpp index 7e4e0ec5e..00a14e91a 100644 --- a/src/game_api/script/usertypes/entity_lua.cpp +++ b/src/game_api/script/usertypes/entity_lua.cpp @@ -208,9 +208,9 @@ void register_usertypes(sol::state& lua) auto user_data = sol::property(get_user_data, set_user_data); auto overlaps_with = sol::overload( - static_cast(&Entity::overlaps_with), - static_cast(&Entity::overlaps_with), - static_cast(&Entity::overlaps_with)); + static_cast(&Entity::overlaps_with), + static_cast(&Entity::overlaps_with), + static_cast(&Entity::overlaps_with)); auto kill_recursive = sol::overload( static_cast(&Entity::kill_recursive), diff --git a/src/game_api/script/usertypes/gui_lua.cpp b/src/game_api/script/usertypes/gui_lua.cpp index 5ca776824..7c27a147e 100644 --- a/src/game_api/script/usertypes/gui_lua.cpp +++ b/src/game_api/script/usertypes/gui_lua.cpp @@ -46,7 +46,7 @@ const int OL_MOUSE_WHEEL = 0x10; const int OL_WHEEL_DOWN = 0x11; const int OL_WHEEL_UP = 0x12; -Vec2::Vec2(const ImVec2& p) +Vec2::Vec2(const ImVec2& p) noexcept : x(p.x), y(p.y){}; struct Gamepad : XINPUT_GAMEPAD diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 9184099fd..91be6aba3 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -2122,7 +2122,7 @@ void preload_addresses() { Memory& mem = Memory::get(); const char* exe = mem.exe(); - for (auto [address_name, rule] : g_address_rules) + for (auto& [address_name, rule] : g_address_rules) { if (auto address = rule(mem, exe, address_name)) { diff --git a/src/game_api/spawn_api.cpp b/src/game_api/spawn_api.cpp index 28013a6dd..e377ab773 100644 --- a/src/game_api/spawn_api.cpp +++ b/src/game_api/spawn_api.cpp @@ -144,7 +144,8 @@ void spawn_liquid(ENT_TYPE entity_type, float x, float y, float velocityx, float liquid_spawn_info->spawn_velocity_x = velocityx; liquid_spawn_info->spawn_velocity_y = velocityy; liquid_spawn_info->liquidtile_liquid_amount = amount; - if (blobs_separation != INFINITY) + + if (!std::isinf(blobs_separation)) liquid_spawn_info->blobs_separation = blobs_separation; spawn_liquid(entity_type, x, y); @@ -266,10 +267,6 @@ int32_t spawn_apep(float x, float y, LAYER layer, bool right) return State::get().layer(actual_layer)->spawn_apep(x + offset_position.first, y + offset_position.second, right)->uid; } -int32_t spawn_tree(float x, float y, LAYER layer) -{ - return spawn_tree(x, y, layer, 0); -} int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) { push_spawn_type_flags(SPAWN_TYPE_SCRIPT); @@ -354,10 +351,6 @@ int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) return base_piece->uid; } -int32_t spawn_mushroom(float x, float y, LAYER l) -{ - return spawn_mushroom(x, y, l, 0); -} int32_t spawn_mushroom(float x, float y, LAYER l, uint16_t height) // height relates to trunk { push_spawn_type_flags(SPAWN_TYPE_SCRIPT); diff --git a/src/game_api/spawn_api.hpp b/src/game_api/spawn_api.hpp index b618399b0..ab4690737 100644 --- a/src/game_api/spawn_api.hpp +++ b/src/game_api/spawn_api.hpp @@ -33,10 +33,16 @@ void spawn_backdoor_abs(float x, float y); int32_t spawn_apep(float x, float y, LAYER layer, bool right); -int32_t spawn_tree(float x, float y, LAYER layer); int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height); -int32_t spawn_mushroom(float x, float y, LAYER l); +inline int32_t spawn_tree(float x, float y, LAYER layer) +{ + return spawn_tree(x, y, layer, 0); +} int32_t spawn_mushroom(float x, float y, LAYER l, uint16_t height); +inline int32_t spawn_mushroom(float x, float y, LAYER l) +{ + return spawn_mushroom(x, y, l, 0); +} int32_t spawn_unrolled_player_rope(float x, float y, LAYER layer, TEXTURE texture); int32_t spawn_unrolled_player_rope(float x, float y, LAYER layer, TEXTURE texture, uint16_t max_length); diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index beea20dcb..ba39f8c91 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -47,14 +47,6 @@ bool get_forward_events() return g_forward_blocked_events; } -uint16_t StateMemory::get_correct_ushabti() // returns animation_frame of ushabti -{ - return (correct_ushabti + (correct_ushabti / 10) * 2); -} -void StateMemory::set_correct_ushabti(uint16_t animation_frame) -{ - correct_ushabti = static_cast(animation_frame - (animation_frame / 12) * 2); -} StateMemory* get_state_ptr() { return State::get().ptr(); @@ -374,7 +366,7 @@ std::pair State::screen_position(float x, float y) return {rx, ry}; } -void State::zoom(float level) +void State::zoom(float level) const { auto roomx = ptr()->w; if (level == 0.0) @@ -469,7 +461,7 @@ std::pair State::get_camera_position() void State::set_camera_position(float cx, float cy) { static const auto addr = (float*)get_address("camera_position"); - auto camera = ptr()->camera; + auto* camera = ptr()->camera; camera->focus_x = cx; camera->focus_y = cy; camera->adjusted_focus_x = cx; @@ -594,7 +586,7 @@ Entity* State::find(StateMemory* state, uint32_t uid) } } -LiquidPhysicsEngine* State::get_correct_liquid_engine(ENT_TYPE liquid_type) +LiquidPhysicsEngine* State::get_correct_liquid_engine(ENT_TYPE liquid_type) const { const auto state = ptr(); static const ENT_TYPE LIQUID_WATER = to_id("ENT_TYPE_LIQUID_WATER"sv); @@ -1088,11 +1080,6 @@ void LogicList::stop_logic(Logic* log) logic_indexed[(uint32_t)idx] = nullptr; } -void LogicMagmamanSpawn::add_spawn(uint32_t x, uint32_t y) -{ - magmaman_positions.emplace_back(x, y); -} - void LogicMagmamanSpawn::remove_spawn(uint32_t x, uint32_t y) { for (auto it = magmaman_positions.begin(); it < magmaman_positions.end(); ++it) diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 68bd709d2..c90d369c5 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -314,8 +314,14 @@ struct StateMemory void force_current_theme(THEME t); /// Returns animation_frame of the correct ushabti - uint16_t get_correct_ushabti(); - void set_correct_ushabti(uint16_t animation_frame); + uint16_t get_correct_ushabti() const + { + return (correct_ushabti + (correct_ushabti / 10) * 2); + } + void set_correct_ushabti(uint16_t animation_frame) + { + correct_ushabti = static_cast(animation_frame - (animation_frame / 12) * 2); + } }; #pragma pack(pop) @@ -350,10 +356,10 @@ struct State void godmode(bool g); void godmode_companions(bool g); - void darkmode(bool g); + static void darkmode(bool g); - void zoom(float level); - void zoom_reset(); + void zoom(float level) const; + static void zoom_reset(); static std::pair click_position(float x, float y); static std::pair screen_position(float x, float y); @@ -385,7 +391,7 @@ struct State void warp(uint8_t w, uint8_t l, uint8_t t); void set_seed(uint32_t seed); SaveData* savedata(); - LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type); + LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE liquid_type) const; private: State(size_t addr) diff --git a/src/game_api/state_structs.hpp b/src/game_api/state_structs.hpp index a34d6a108..c976d3e97 100644 --- a/src/game_api/state_structs.hpp +++ b/src/game_api/state_structs.hpp @@ -511,7 +511,10 @@ class LogicMagmamanSpawn : public Logic public: custom_vector magmaman_positions; - void add_spawn(uint32_t x, uint32_t y); + void add_spawn(uint32_t x, uint32_t y) + { + magmaman_positions.emplace_back(x, y); + } void add_spawn(MagmamanSpawnPosition ms) { add_spawn(ms.x, ms.y); diff --git a/src/game_api/virtual_table.cpp b/src/game_api/virtual_table.cpp index df1fa06cc..df396b2ed 100644 --- a/src/game_api/virtual_table.cpp +++ b/src/game_api/virtual_table.cpp @@ -15,9 +15,3 @@ size_t get_virtual_function_address(VTABLE_OFFSET table_entry, uint32_t function size_t* func_address = reinterpret_cast(first_table_entry + ((static_cast(table_entry) + function_index) * sizeof(size_t))); return *func_address - mem.exe_address(); } - -size_t get_virtual_function_address(void* object, uint32_t function_index) -{ - auto v_table = *static_cast(object); - return *(v_table + function_index); -} diff --git a/src/game_api/virtual_table.hpp b/src/game_api/virtual_table.hpp index ed368b9ce..a7654bead 100644 --- a/src/game_api/virtual_table.hpp +++ b/src/game_api/virtual_table.hpp @@ -990,4 +990,8 @@ enum class VTABLE_OFFSET }; size_t get_virtual_function_address(VTABLE_OFFSET table_entry, uint32_t function_index); -size_t get_virtual_function_address(void* object, uint32_t function_index); +inline size_t get_virtual_function_address(void* object, uint32_t function_index) +{ + auto v_table = *static_cast(object); + return *(v_table + function_index); +} diff --git a/src/injector/cmd_line.cpp b/src/injector/cmd_line.cpp index 31bfb352e..2cafdf63e 100644 --- a/src/injector/cmd_line.cpp +++ b/src/injector/cmd_line.cpp @@ -6,9 +6,7 @@ #include // for min, find_if CmdLineParser::CmdLineParser(int argc, char** argv) - : m_CmdLine(argv, argv + argc) -{ -} + : m_CmdLine(argv, argv + argc){}; std::vector CmdLineParser::Get(std::string_view arg, has_args_tag) const { From 8e5322c7d72cc5b9b9c072e2e7b9edfebb4798c3 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:30:39 +0100 Subject: [PATCH 05/23] update doc, add `inline` and `[[nodiscard]]` to the filter, move `get_address` to debug functions --- docs/game_data/spel2.lua | 44 +++++++++++++++++------------------ docs/generate.py | 1 + docs/generate_emmylua.py | 2 ++ docs/parse_source.py | 2 ++ docs/src/includes/_globals.md | 38 +++++++++++++++--------------- docs/src/includes/_types.md | 4 ++-- 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index a656f9ec4..e4aa56df4 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -369,32 +369,32 @@ function spawn_apep(x, y, layer, right) end ---@param x number ---@param y number ---@param layer LAYER +---@param height integer ---@return integer -function spawn_tree(x, y, layer) end +function spawn_tree(x, y, layer, height) end ---Spawns and grows a tree ---@param x number ---@param y number ---@param layer LAYER ----@param height integer ---@return integer -function spawn_tree(x, y, layer, height) end +function spawn_tree(x, y, layer) end ---Spawns and grows mushroom, height relates to the trunk, without it, it will roll the game default 3-5 height ---Regardless, if there is not enough space, it will spawn shorter one or if there is no space even for the smallest one, it will just not spawn at all ---Returns uid of the base or -1 if it wasn't able to spawn ---@param x number ---@param y number ---@param l LAYER +---@param height integer ---@return integer -function spawn_mushroom(x, y, l) end +function spawn_mushroom(x, y, l, height) end ---Spawns and grows mushroom, height relates to the trunk, without it, it will roll the game default 3-5 height ---Regardless, if there is not enough space, it will spawn shorter one or if there is no space even for the smallest one, it will just not spawn at all ---Returns uid of the base or -1 if it wasn't able to spawn ---@param x number ---@param y number ---@param l LAYER ----@param height integer ---@return integer -function spawn_mushroom(x, y, l, height) end +function spawn_mushroom(x, y, l) end ---Spawns an already unrolled rope as if created by player ---@param x number ---@param y number @@ -1286,16 +1286,16 @@ function add_custom_type(types) end function add_custom_type() end ---Get uids of entities by draw_depth. Can also use table of draw_depths. ---You can later use [filter_entities](https://spelunky-fyi.github.io/overlunky/#filter_entities) if you want specific entity ----@param draw_depth integer +---@param draw_depths integer[] ---@param l LAYER ---@return integer[] -function get_entities_by_draw_depth(draw_depth, l) end +function get_entities_by_draw_depth(draw_depths, l) end ---Get uids of entities by draw_depth. Can also use table of draw_depths. ---You can later use [filter_entities](https://spelunky-fyi.github.io/overlunky/#filter_entities) if you want specific entity ----@param draw_depths integer[] +---@param draw_depth integer ---@param l LAYER ---@return integer[] -function get_entities_by_draw_depth(draw_depths, l) end +function get_entities_by_draw_depth(draw_depth, l) end ---Just convenient way of getting the current amount of money ---short for state->money_shop_total + loop[inventory.money + inventory.collected_money_total] ---@return integer @@ -1604,20 +1604,15 @@ function set_level_config(config, value) end ---Grow vines from `GROWABLE_VINE` and `VINE_TREE_TOP` entities in a level, `area` default is whole level, `destroy_broken` default is false ---@param l LAYER ---@param max_lengh integer ----@return nil -function grow_vines(l, max_lengh) end ----Grow vines from `GROWABLE_VINE` and `VINE_TREE_TOP` entities in a level, `area` default is whole level, `destroy_broken` default is false ----@param l LAYER ----@param max_lengh integer ---@param area AABB ---@param destroy_broken boolean ---@return nil function grow_vines(l, max_lengh, area, destroy_broken) end ----Grow pole from `GROWABLE_CLIMBING_POLE` entities in a level, `area` default is whole level, `destroy_broken` default is false +---Grow vines from `GROWABLE_VINE` and `VINE_TREE_TOP` entities in a level, `area` default is whole level, `destroy_broken` default is false ---@param l LAYER ---@param max_lengh integer ---@return nil -function grow_poles(l, max_lengh) end +function grow_vines(l, max_lengh) end ---Grow pole from `GROWABLE_CLIMBING_POLE` entities in a level, `area` default is whole level, `destroy_broken` default is false ---@param l LAYER ---@param max_lengh integer @@ -1625,6 +1620,11 @@ function grow_poles(l, max_lengh) end ---@param destroy_broken boolean ---@return nil function grow_poles(l, max_lengh, area, destroy_broken) end +---Grow pole from `GROWABLE_CLIMBING_POLE` entities in a level, `area` default is whole level, `destroy_broken` default is false +---@param l LAYER +---@param max_lengh integer +---@return nil +function grow_poles(l, max_lengh) end ---Grow chains from `ENT_TYPE_FLOOR_CHAIN_CEILING` and chain with blocks on it from `ENT_TYPE_FLOOR_CHAINANDBLOCKS_CEILING`, it starts looking for the ceilings from the top left corner of a level. ---To limit it use the parameters, so x = 10 will only grow chains from ceilings with x < 10, with y = 10 it's ceilings that have y > (level bound top - 10) ---@return boolean @@ -2499,7 +2499,7 @@ function PRNG:random(min, max) end ---@field set_layer fun(self, layer: LAYER): nil @Moves the entity to specified layer, nothing else happens, so this does not emulate a door transition ---@field apply_layer fun(self): nil @Adds the entity to its own layer, to add it to entity lookup tables without waiting for a state update ---@field remove fun(self): nil @Moves the entity to the limbo-layer where it can later be retrieved from again via `respawn` - ---@field respawn fun(self, layer: LAYER): nil @Moves the entity from the limbo-layer (where it was previously put by `remove`) to `layer` + ---@field respawn fun(self, layer_to: LAYER): nil @Moves the entity from the limbo-layer (where it was previously put by `remove`) to `layer` ---@field kill fun(self, destroy_corpse: boolean, responsible: Entity): nil @Kills the entity, you can set responsible to `nil` to ignore it ---@field destroy fun(self): nil @Completely removes the entity from existence ---@field activate fun(self, activator: Entity): nil @Activates a button prompt (with the Use door/Buy button), e.g. buy shop item, activate drill, read sign, interact in camp, ... `get_entity():activate(players[1])` (make sure player 1 has the udjat eye though) @@ -2708,15 +2708,15 @@ function Entity:destroy_recursive() end ---@field set_pre_crush fun(self, fun: fun(self: Movable, Entity: ): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool crush(Movable self, Entity)` ---@field set_post_crush fun(self, fun: fun(self: Movable, Entity: ): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil crush(Movable self, Entity)` local Movable = nil ----Move a movable according to its velocity, update physics, gravity, etc. ----Will also update `movable.animation_frame` and various timers and counters ----@return nil -function Movable:generic_update_world() end ---Move a movable according to its velocity, can disable gravity ---Will also update `movable.animation_frame` and various timers and counters ---@param disable_gravity boolean ---@return nil function Movable:generic_update_world(disable_gravity) end +---Move a movable according to its velocity, update physics, gravity, etc. +---Will also update `movable.animation_frame` and various timers and counters +---@return nil +function Movable:generic_update_world() end ---Move a movable according to its velocity and `move`, if the movables `BUTTON.RUN` is ---held apply `sprint_factor` on `move.x`, can disable gravity or lock its horizontal ---movement via `on_rope`. Use this for example to update a custom enemy type. diff --git a/docs/generate.py b/docs/generate.py index 74f6e5e07..3e4e673df 100644 --- a/docs/generate.py +++ b/docs/generate.py @@ -412,6 +412,7 @@ def print_lf(lf): "dump_network", "dump", "dump_string", + "get_address", ] ): cat = "Debug functions" diff --git a/docs/generate_emmylua.py b/docs/generate_emmylua.py index 665ea88df..387d5524a 100644 --- a/docs/generate_emmylua.py +++ b/docs/generate_emmylua.py @@ -32,6 +32,8 @@ " = nullopt": "", "void": "", "constexpr": "", + "inline": "", + "[[nodiscard]]": "", "...va:": "...ent_type:", "set<": "Array<", "span<": "Array<", diff --git a/docs/parse_source.py b/docs/parse_source.py index a1e399cb8..9cf9bba07 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -60,6 +60,8 @@ "SoundCallbackFunction": "function", "object ": "any ", "BucketItem": "any", + "[[nodiscard]]": "", + "inline": "", } header_files = [ diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 9d3a08b74..ee9517d5d 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -389,6 +389,15 @@ Dump the object (table, container, class) as a recursive table, for pretty print Hook the sendto and recvfrom functions and start dumping network data to terminal +### get_address + + +> Search script examples for [get_address](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_address) + +#### nil get_address([[maybe_unused]] any o) + +Get memory address from a lua object + ### get_rva @@ -631,10 +640,10 @@ Recommended to always set the mask, even if you look for one entity type > Search script examples for [get_entities_by_draw_depth](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_by_draw_depth) -#### vector<int> get_entities_by_draw_depth(int draw_depth, [LAYER](#LAYER) l) - #### vector<int> get_entities_by_draw_depth(array draw_depths, [LAYER](#LAYER) l) +#### vector<int> get_entities_by_draw_depth(int draw_depth, [LAYER](#LAYER) l) + Get uids of entities by draw_depth. Can also use table of draw_depths. You can later use [filter_entities](#filter_entities) if you want specific entity @@ -1371,15 +1380,6 @@ Flips the nth bit in a number. This doesn't actually change the variable you pas Force the journal to open on a chapter and entry# when pressing the journal button. Only use even entry numbers. Set chapter to `JOURNALUI_PAGE_SHOWN.JOURNAL` to reset. (This forces the journal toggle to always read from `game_manager.save_related.journal_popup_ui.entry_to_show` etc.) -### get_address - - -> Search script examples for [get_address](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_address) - -#### nil get_address([[maybe_unused]] any o) - -Get memory address from a lua object - ### get_adventure_seed @@ -1595,10 +1595,10 @@ To limit it use the parameters, so x = 10 will only grow chains from ceilings wi > Search script examples for [grow_poles](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=grow_poles) -#### nil grow_poles([LAYER](#LAYER) l, int max_lengh) - #### nil grow_poles([LAYER](#LAYER) l, int max_lengh, [AABB](#AABB) area, bool destroy_broken) +#### nil grow_poles([LAYER](#LAYER) l, int max_lengh) + Grow pole from `GROWABLE_CLIMBING_POLE` entities in a level, `area` default is whole level, `destroy_broken` default is false ### grow_vines @@ -1606,10 +1606,10 @@ Grow pole from `GROWABLE_CLIMBING_POLE` entities in a level, `area` default is w > Search script examples for [grow_vines](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=grow_vines) -#### nil grow_vines([LAYER](#LAYER) l, int max_lengh) - #### nil grow_vines([LAYER](#LAYER) l, int max_lengh, [AABB](#AABB) area, bool destroy_broken) +#### nil grow_vines([LAYER](#LAYER) l, int max_lengh) + Grow vines from `GROWABLE_VINE` and `VINE_TREE_TOP` entities in a level, `area` default is whole level, `destroy_broken` default is false ### import @@ -3325,10 +3325,10 @@ Don't overuse this, you are still restricted by the liquid pool sizes and thus m > Search script examples for [spawn_mushroom](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawn_mushroom) -#### int spawn_mushroom(float x, float y, [LAYER](#LAYER) l) - #### int spawn_mushroom(float x, float y, [LAYER](#LAYER) l, int height) +#### int spawn_mushroom(float x, float y, [LAYER](#LAYER) l) + Spawns and grows mushroom, height relates to the trunk, without it, it will roll the game default 3-5 height Regardless, if there is not enough space, it will spawn shorter one or if there is no space even for the smallest one, it will just not spawn at all Returns uid of the base or -1 if it wasn't able to spawn @@ -3376,10 +3376,10 @@ or change it's `player_inputs` to the `input` of real player so he can control i > Search script examples for [spawn_tree](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawn_tree) -#### int spawn_tree(float x, float y, [LAYER](#LAYER) layer) - #### int spawn_tree(float x, float y, [LAYER](#LAYER) layer, int height) +#### int spawn_tree(float x, float y, [LAYER](#LAYER) layer) + Spawns and grows a tree ### spawn_unrolled_player_rope diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index 377064f54..992d59f47 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -4188,7 +4188,7 @@ nil | [liberate_from_shop()](https://github.com/spelunky-fyi/overlunky/search?l= nil | [set_layer(LAYER layer)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_layer) | Moves the entity to specified layer, nothing else happens, so this does not emulate a door transition nil | [apply_layer()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=apply_layer) | Adds the entity to its own layer, to add it to entity lookup tables without waiting for a state update nil | [remove()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=remove) | Moves the entity to the limbo-layer where it can later be retrieved from again via `respawn` -nil | [respawn(LAYER layer)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=respawn) | Moves the entity from the limbo-layer (where it was previously put by `remove`) to `layer` +nil | [respawn(LAYER layer_to)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=respawn) | Moves the entity from the limbo-layer (where it was previously put by `remove`) to `layer` nil | [kill(bool destroy_corpse, Entity responsible)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=kill) | Kills the entity, you can set responsible to `nil` to ignore it nil | [destroy()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy) | Completely removes the entity from existence nil | [activate(Entity activator)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=activate) | Activates a button prompt (with the Use door/Buy button), e.g. buy shop item, activate drill, read sign, interact in camp, ... `get_entity():activate(players[1])` (make sure player 1 has the udjat eye though) @@ -6867,8 +6867,8 @@ nil | [process_input()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q nil | [add_behavior(MovableBehavior behavior)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_behavior) | Add a behavior to this movable, can be either a `VanillaMovableBehavior` or a
`CustomMovableBehavior` nil | [clear_behavior(MovableBehavior behavior)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear_behavior) | Clear a specific behavior of this movable, can be either a `VanillaMovableBehavior` or a
`CustomMovableBehavior`, a behavior with this behaviors `state_id` may be required to
run this movables statemachine without crashing, so add a new one if you are not sure nil | [clear_behaviors()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear_behaviors) | Clears all behaviors of this movable, need to call `add_behavior` to avoid crashing -nil | [generic_update_world()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=generic_update_world) | Move a movable according to its velocity, update physics, gravity, etc.
Will also update `movable.animation_frame` and various timers and counters nil | [generic_update_world(bool disable_gravity)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=generic_update_world) | Move a movable according to its velocity, can disable gravity
Will also update `movable.animation_frame` and various timers and counters +nil | [generic_update_world()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=generic_update_world) | Move a movable according to its velocity, update physics, gravity, etc.
Will also update `movable.animation_frame` and various timers and counters nil | [generic_update_world(Vec2 move, float sprint_factor, bool disable_gravity, bool on_rope)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=generic_update_world) | Move a movable according to its velocity and `move`, if the movables `BUTTON.RUN` is
held apply `sprint_factor` on `move.x`, can disable gravity or lock its horizontal
movement via `on_rope`. Use this for example to update a custom enemy type.
Will also update `movable.animation_frame` and various timers and counters [CallbackId](#Aliases) | [set_pre_virtual(ENTITY_OVERRIDE entry, function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_virtual) | Hooks before the virtual function at index `entry`. [CallbackId](#Aliases) | [set_post_virtual(ENTITY_OVERRIDE entry, function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_virtual) | Hooks after the virtual function at index `entry`. From ee02ec6545601d8eab587d81ec42859db42413be Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:42:31 +0100 Subject: [PATCH 06/23] also filter out `[[maybe_unused]]`, move mask functions to `Flag functions` category --- docs/generate.py | 2 +- docs/generate_emmylua.py | 1 + docs/parse_source.py | 5 ++- docs/src/includes/_globals.md | 74 +++++++++++++++++------------------ 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/generate.py b/docs/generate.py index 3e4e673df..1a7072f26 100644 --- a/docs/generate.py +++ b/docs/generate.py @@ -427,7 +427,7 @@ def print_lf(lf): for subs in ["interval", "timeout", "callback", "set_on", "set_pre", "set_post"] ): cat = "Callback functions" - elif any(subs in func["name"] for subs in ["flag"]): + elif any(subs in func["name"] for subs in ["flag", "clr_mask", "flip_mask", "set_mask", "test_mask"]): cat = "Flag functions" elif any(subs in func["name"] for subs in ["shop"]): cat = "Shop functions" diff --git a/docs/generate_emmylua.py b/docs/generate_emmylua.py index 387d5524a..89e01ec14 100644 --- a/docs/generate_emmylua.py +++ b/docs/generate_emmylua.py @@ -34,6 +34,7 @@ "constexpr": "", "inline": "", "[[nodiscard]]": "", + "[[maybe_unused]]": "", "...va:": "...ent_type:", "set<": "Array<", "span<": "Array<", diff --git a/docs/parse_source.py b/docs/parse_source.py index 9cf9bba07..547e0ccef 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -53,6 +53,9 @@ "constexpr": "", "const": "", "static": "", + "[[nodiscard]]": "", + "[[maybe_unused]]": "", + "inline": "", # special "variadic_args va": "ENT_TYPE, ENT_TYPE...", "EmittedParticlesInfo": "array", @@ -60,8 +63,6 @@ "SoundCallbackFunction": "function", "object ": "any ", "BucketItem": "any", - "[[nodiscard]]": "", - "inline": "", } header_files = [ diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index ee9517d5d..eaa8fd9d3 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -394,7 +394,7 @@ Hook the sendto and recvfrom functions and start dumping network data to termina > Search script examples for [get_address](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_address) -#### nil get_address([[maybe_unused]] any o) +#### nil get_address(any o) Get memory address from a lua object @@ -1111,6 +1111,15 @@ Set the visibility of a feat Clears the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. +### clr_mask + + +> Search script examples for [clr_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clr_mask) + +#### [Flags](#Aliases) clr_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) + +Clears a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. + ### flip_flag @@ -1120,6 +1129,15 @@ Clears the nth bit in a number. This doesn't actually change the variable you pa Flips the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. +### flip_mask + + +> Search script examples for [flip_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flip_mask) + +#### [Flags](#Aliases) flip_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) + +Flips the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. + ### get_entity_flags @@ -1183,6 +1201,15 @@ Set the nth bit in a number. This doesn't actually change the variable you pass, Set `state.level_flags` +### set_mask + + +> Search script examples for [set_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_mask) + +#### [Flags](#Aliases) set_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) + +Set a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. + ### test_flag @@ -1192,6 +1219,15 @@ Set `state.level_flags` Returns true if the nth bit is set in the number. +### test_mask + + +> Search script examples for [test_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=test_mask) + +#### bool test_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) + +Returns true if a bitmask is set in the number. + ## Generic functions @@ -1276,15 +1312,6 @@ Clear cache for a file path or the whole directory Clear save state from slot 1..4. -### clr_mask - - -> Search script examples for [clr_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clr_mask) - -#### [Flags](#Aliases) clr_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) - -Clears a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. - ### create_image @@ -1362,15 +1389,6 @@ Destroys all layers and all entities in the level. Usually a bad idea, unless yo Disable all crust item spawns, returns whether they were already disabled before the call -### flip_mask - - -> Search script examples for [flip_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flip_mask) - -#### [Flags](#Aliases) flip_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) - -Flips the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. - ### force_journal @@ -1964,15 +1982,6 @@ Set the value for the specified config Setting to false disables all player logic in [SCREEN](#SCREEN).LEVEL, mainly the death screen from popping up if all players are dead or missing, but also shop camera zoom and some other small things. -### set_mask - - -> Search script examples for [set_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_mask) - -#### [Flags](#Aliases) set_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) - -Set a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. - ### set_seed @@ -2096,15 +2105,6 @@ Set layer to search for storage items on Open the journal on a chapter and page. The main Journal spread is pages 0..1, so most chapters start at 2. Use even page numbers only. -### test_mask - - -> Search script examples for [test_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=test_mask) - -#### bool test_mask([Flags](#Aliases) flags, [Flags](#Aliases) mask) - -Returns true if a bitmask is set in the number. - ### toggle_journal From 11307bd455bcd8ea28054e067b9c89fa7c42ad79 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:50:22 +0100 Subject: [PATCH 07/23] improve `fix_liquid_out_of_bounds` description --- src/game_api/script/lua_vm.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 87293226b..e66773bbc 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1822,7 +1822,9 @@ end /// Refreshes an Illumination, keeps it from fading out (updates the timer, keeping it in sync with the game render) lua["refresh_illumination"] = refresh_illumination; - /// Removes all liquid that is about to go out of bounds, which crashes the game. + /// Removes all liquid that is about to go out of bounds, this would normally crash the game, but playlunky/overlunky patch this bug. + /// The patch however does not destroy the liquids that fall pass the level bounds, + /// so you may still want to use this function if you spawn a lot of liquid that may fall out of the level lua["fix_liquid_out_of_bounds"] = fix_liquid_out_of_bounds; /// Return the name of the first matching number in an enum table From e48587f9a656802c29f657d9256ad9ef32ecc44b Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 30 Mar 2024 14:58:13 +0100 Subject: [PATCH 08/23] add clonegun, elixir and player bag, (challenge rewards) to `replace_drop` --- src/game_api/drops.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/game_api/drops.cpp b/src/game_api/drops.cpp index ff4735526..838265ac0 100644 --- a/src/game_api/drops.cpp +++ b/src/game_api/drops.cpp @@ -209,6 +209,12 @@ std::vector drop_entries{ /// Game does this (this value | 0x1) to get BOMBBAG (so depending on the chosen ENT_TYPE it can be + 1 or + 0) {"OLMEC_SISTERS_ROPEPILE", "\x0D\x00\x02\x00\x00\x83\xFB\x03"sv, VTABLE_OFFSET::NONE, 0, 1}, {"OLMEC_SISTERS_BOMBBOX", "\xBA\x02\x02\x00\x00\x0F\x45\xD0"sv, VTABLE_OFFSET::NONE, 0, 1}, + /// Challenge rewards: + {"CHALLENGESTAR_CLONEGUN", "\xB8\x4D\x02\x00\x00"sv, VTABLE_OFFSET::LOGIC_TUN_STAR_CHALLENGE, 1, 1}, + {"CHALLENGESTAR_ELIXIR", "\xBD\x08\x02\x00\x00"sv, VTABLE_OFFSET::LOGIC_TUN_STAR_CHALLENGE, 1, 1}, + /// Game will change texture based on the active player unless the item MASK is: FLOOR, DECORATION, BG, SHADOW, LOGICAL, WATER, LAVA, or the 16th bit that is normally unused + {"CHALLENGESUN_PLAYERBAG", "\xBA\x1F\x02\x00\x00"sv, VTABLE_OFFSET::LOGIC_TUN_SUN_CHALLENGE, 1, 1}, + /// Mattock and Arrow of Light are build into the level, use level editor or `set_pre_entity_spawn` to replace them /// /// Attacks: From 98b02ef92cf2a9ee7a26d74d18c6252db1053293 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 31 Mar 2024 18:41:50 +0200 Subject: [PATCH 09/23] remove limitation in `change_sunchallenge_spawns` by using `patch_and_redirect`, update doc, clarify type casting in `memory` a bit --- docs/game_data/spel2.lua | 5 +- docs/src/includes/_globals.md | 5 +- src/game_api/drops.cpp | 6 +-- src/game_api/memory.cpp | 60 +++++++++++++----------- src/game_api/memory.hpp | 2 + src/game_api/rpc.cpp | 86 ++++++++++++++++------------------ src/game_api/script/lua_vm.cpp | 1 - src/game_api/search.cpp | 5 +- 8 files changed, 86 insertions(+), 84 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index e4aa56df4..f3b2bb1d0 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1030,7 +1030,6 @@ function clear_custom_name(uid) end function enter_door(player_uid, door_uid) end ---Change ENT_TYPE's spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4: ---{MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER} ----Because of the game logic number of entity types has to be a power of 2: (1, 2, 4, 8, 16, 32), if you want say 30 types, you need to write two entities two times (they will have higher "spawn chance"). ---Use empty table as argument to reset to the game default ---@param ent_types ENT_TYPE[] ---@return nil @@ -1107,7 +1106,9 @@ function create_illumination(color, size, uid) end ---@param illumination Illumination ---@return nil function refresh_illumination(illumination) end ----Removes all liquid that is about to go out of bounds, which crashes the game. +---Removes all liquid that is about to go out of bounds, this would normally crash the game, but playlunky/overlunky patch this bug. +---The patch however does not destroy the liquids that fall pass the level bounds, +---so you may still want to use this function if you spawn a lot of liquid that may fall out of the level ---@return nil function fix_liquid_out_of_bounds() end ---Return the name of the first matching number in an enum table diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index eaa8fd9d3..a84f494ae 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -2645,7 +2645,9 @@ set_callback(fix_liquid_out_of_bounds, ON.FRAME) #### nil fix_liquid_out_of_bounds() -Removes all liquid that is about to go out of bounds, which crashes the game. +Removes all liquid that is about to go out of bounds, this would normally crash the game, but playlunky/overlunky patch this bug. +The patch however does not destroy the liquids that fall pass the level bounds, +so you may still want to use this function if you spawn a lot of liquid that may fall out of the level ### game_position @@ -3094,7 +3096,6 @@ Use empty table as argument to reset to the game default Change [ENT_TYPE](#ENT_TYPE)'s spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4:
{MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER}
-Because of the game logic number of entity types has to be a power of 2: (1, 2, 4, 8, 16, 32), if you want say 30 types, you need to write two entities two times (they will have higher "spawn chance"). Use empty table as argument to reset to the game default ### default_spawn_is_valid diff --git a/src/game_api/drops.cpp b/src/game_api/drops.cpp index 838265ac0..f5bfb94b9 100644 --- a/src/game_api/drops.cpp +++ b/src/game_api/drops.cpp @@ -212,7 +212,7 @@ std::vector drop_entries{ /// Challenge rewards: {"CHALLENGESTAR_CLONEGUN", "\xB8\x4D\x02\x00\x00"sv, VTABLE_OFFSET::LOGIC_TUN_STAR_CHALLENGE, 1, 1}, {"CHALLENGESTAR_ELIXIR", "\xBD\x08\x02\x00\x00"sv, VTABLE_OFFSET::LOGIC_TUN_STAR_CHALLENGE, 1, 1}, - /// Game will change texture based on the active player unless the item MASK is: FLOOR, DECORATION, BG, SHADOW, LOGICAL, WATER, LAVA, or the 16th bit that is normally unused + /// Game will change texture based on the active player unless the item search_flags/MASK is: FLOOR, DECORATION, BG, SHADOW, LOGICAL, WATER, LAVA, or the 16th bit that is normally unused {"CHALLENGESUN_PLAYERBAG", "\xBA\x1F\x02\x00\x00"sv, VTABLE_OFFSET::LOGIC_TUN_SUN_CHALLENGE, 1, 1}, /// Mattock and Arrow of Light are build into the level, use level editor or `set_pre_entity_spawn` to replace them @@ -267,7 +267,7 @@ std::vector drop_entries{ /// Spawn: /// - /// It set's move_state for them for sleep and wake_up_timer, so i has to be movable and entity + 0x150 can't be something important + /// It set's move_state for them for sleep and wake_up_timer, so i has to be movable and entity offset 0x150 can't be something important {"COOKFIRE_CAVEMAN_1", "\xBA\xE1\x00\x00\x00\x0F\x28\xD7\xE8****\x48\x89\xC3"sv, VTABLE_OFFSET::ITEM_COOKFIRE, 75, 1}, {"COOKFIRE_CAVEMAN_2", "\xBA\xE1\x00\x00\x00\x0F\x28\xD7\xE8****\x48\x89\xC6"sv, VTABLE_OFFSET::ITEM_COOKFIRE, 75, 1}, {"BONEBLOCK_SKELETON", "\xBA\xE3\x00\x00\x00"sv, VTABLE_OFFSET::ACTIVEFLOOR_BONEBLOCK, 75, 1}, @@ -306,7 +306,7 @@ std::vector drop_entries{ {"KAPALA_HEALTH", "\xBA\x01\x00\x00\x00\xB8\x01"sv, VTABLE_OFFSET::NONE, 0, 1}, /* can't do elixir as there are some calculations for cursed, poisoned etc. can't do pet, it has some complex calculation for some reason - can't do ankh made a separete function for that (see modify_ankh_health_gain) + can't do ankh, made a separete function for that (see modify_ankh_health_gain) can't do initial health (camp, level, duat, coffin) as it's a word/byte can't do drops for: humphead, yetiking, yetiqueen, alien queen, pangxie (gems) those are stored in array, need special funciton for that */ diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index 510709093..abaf9b0eb 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -53,7 +53,7 @@ Memory& Memory::get() return mem; } -static size_t round_up(size_t i, size_t div) +inline size_t round_up(size_t i, size_t div) { return ((i + div - 1) / div) * div; } @@ -65,12 +65,12 @@ void write_mem_prot(size_t addr, std::string_view payload, bool prot) auto size = round_up((addr + payload.size() - page), 0x1000); if (prot) { - VirtualProtect((void*)page, size, PAGE_EXECUTE_READWRITE, &old_protect); + VirtualProtect(reinterpret_cast(page), size, PAGE_EXECUTE_READWRITE, &old_protect); } memcpy((void*)addr, payload.data(), payload.size()); if (prot) { - VirtualProtect((LPVOID)page, size, old_protect, &old_protect); + VirtualProtect(reinterpret_cast(page), size, old_protect, &old_protect); } } void write_mem_prot(size_t addr, std::string payload, bool prot) @@ -111,24 +111,24 @@ LPVOID alloc_mem_rel32(size_t addr, size_t size) for (; test_addr < limit_addr; test_addr += 0x1000) // add 4KB memory page size { - new_array = VirtualAlloc((LPVOID)test_addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + new_array = VirtualAlloc(reinterpret_cast(test_addr), size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (new_array) break; } return new_array; } -void write_mem_recoverable(std::string name, size_t addr, std::string_view payload, bool prot) +void save_mem_recoverable(std::string name, size_t addr, size_t size, bool prot) { static const auto bucket = Bucket::get(); auto map_it = bucket->original_memory.find(name); if (map_it == bucket->original_memory.end()) { - char* old_data = (char*)VirtualAlloc(0, payload.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + char* old_data = static_cast(VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); if (old_data) { - memcpy(old_data, (char*)addr, payload.size()); - bucket->original_memory.emplace(name, EditedMemory{{{addr, old_data, payload.size(), prot}}, true}); + std::memcpy(old_data, reinterpret_cast(addr), size); + bucket->original_memory.emplace(std::move(name), EditedMemory{{{addr, old_data, size, prot}}, true}); } } else @@ -144,14 +144,20 @@ void write_mem_recoverable(std::string name, size_t addr, std::string_view paylo } if (new_addr) { - char* old_data = (char*)VirtualAlloc(0, payload.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + char* old_data = static_cast(VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); if (old_data) { - memcpy(old_data, (char*)addr, payload.size()); - map_it->second.mem.emplace_back(addr, old_data, payload.size(), prot); + std::memcpy(old_data, reinterpret_cast(addr), size); + map_it->second.mem.emplace_back(addr, old_data, size, prot); } } } +} + +void write_mem_recoverable(std::string name, size_t addr, std::string_view payload, bool prot) +{ + static const auto bucket = Bucket::get(); + save_mem_recoverable(name, addr, payload.size(), prot); bucket->original_memory[name].dirty = true; write_mem_prot(addr, payload, prot); } @@ -159,14 +165,15 @@ void write_mem_recoverable(std::string name, size_t addr, std::string_view paylo void recover_mem(std::string name, size_t addr) { static const auto bucket = Bucket::get(); - if (bucket->original_memory.contains(name)) + auto it = bucket->original_memory.find(name); + if (it != bucket->original_memory.end()) { size_t fixed = 0; - for (auto& it : bucket->original_memory[name].mem) + for (auto& mem : it->second.mem) { - if (!addr || addr == it.address) + if (!addr || addr == mem.address) { - write_mem_prot(it.address, std::string_view{it.old_data, it.size}, it.prot_used); + write_mem_prot(mem.address, std::string_view{mem.old_data, mem.size}, mem.prot_used); if (++fixed == bucket->original_memory[name].mem.size()) bucket->original_memory[name].dirty = false; } @@ -177,7 +184,8 @@ void recover_mem(std::string name, size_t addr) bool mem_written(std::string name) { static const auto bucket = Bucket::get(); - return bucket->original_memory.contains(name) && bucket->original_memory[name].dirty; + auto it = bucket->original_memory.find(name); + return it != bucket->original_memory.end() && it->second.dirty; } size_t patch_and_redirect(size_t addr, size_t replace_size, const std::string_view payload, bool just_nop, size_t return_to_addr, bool game_code_first) @@ -195,39 +203,35 @@ size_t patch_and_redirect(size_t addr, size_t replace_size, const std::string_vi const size_t target = std::max(return_to_addr, addr); const auto new_memory_size = payload.size() + data_size_to_move + jump_size; - auto new_code = (char*)alloc_mem_rel32(target, new_memory_size); + auto new_code = static_cast(alloc_mem_rel32(target, new_memory_size)); if (new_code == nullptr) return 0; if (game_code_first && !just_nop) { - std::memcpy(new_code, (void*)addr, data_size_to_move); + std::memcpy(new_code, reinterpret_cast(addr), data_size_to_move); std::memcpy(new_code + data_size_to_move, payload.data(), payload.size()); } else { - std::memcpy(new_code + data_size_to_move, payload.data(), payload.size()); + std::memcpy(new_code, payload.data(), payload.size()); if (!just_nop) - std::memcpy(new_code, (void*)addr, data_size_to_move); + std::memcpy(new_code + payload.size(), reinterpret_cast(addr), data_size_to_move); } - size_t return_addr = addr + replace_size; - if (return_to_addr != 0) - { - return_addr = return_to_addr; - } - int32_t rel_back = static_cast(return_addr - (size_t)(new_code + new_memory_size)); + size_t return_addr = return_to_addr == 0 ? addr + replace_size : return_to_addr; + int32_t rel_back = static_cast(return_addr - reinterpret_cast(new_code + new_memory_size)); const std::string jump_back = fmt::format("\xE9{}"sv, to_le_bytes(rel_back)); std::memcpy(new_code + payload.size() + data_size_to_move, jump_back.data(), jump_size); DWORD dummy; VirtualProtect(new_code, new_memory_size, PAGE_EXECUTE_READ, &dummy); - int32_t rel = static_cast((size_t)new_code - (addr + jump_size)); + int32_t rel = static_cast(reinterpret_cast(new_code) - (addr + jump_size)); const std::string redirect_code = fmt::format("\xE9{}{}"sv, to_le_bytes(rel), get_nop(replace_size - jump_size)); write_mem_prot(addr, redirect_code, true); - return (size_t)new_code; + return reinterpret_cast(new_code); } std::string get_nop(size_t size, bool true_nop) diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index 2d4055a8e..55fcb5892 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -86,6 +86,8 @@ void write_mem_prot(size_t addr, std::string_view payload, bool prot); void write_mem_prot(size_t addr, std::string payload, bool prot); void write_mem(size_t addr, std::string payload); size_t function_start(size_t off, uint8_t outside_byte = '\xcc'); +// save copy of the oryginal memory so it can be later recovered via recover_mem +void save_mem_recoverable(std::string name, size_t addr, size_t size, bool prot); void write_mem_recoverable(std::string name, size_t addr, std::string_view payload, bool prot); void recover_mem(std::string name, size_t addr = NULL); bool mem_written(std::string name); diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 246d96f6a..36144da93 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -920,63 +920,57 @@ void enter_door(int32_t player_uid, int32_t door_uid) void change_sunchallenge_spawns(std::vector ent_types) { - static bool modified = false; - - uint32_t ent_types_size = static_cast(ent_types.size()); - static const auto offset = get_address("sun_chalenge_generator_ent_types"); - ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(offset) + offset + 4); - - if (ent_types_size == 0) + // [Known_Issue]: as all the functions that base some functionality on static, this can break if used in PL and OV simultaneously + static uintptr_t offset; + static uintptr_t new_code_address; + if (offset == 0) { - if (modified) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - recover_mem("sunchallenge_spawn"); - modified = false; - return; - } + offset = get_address("sun_chalenge_generator_ent_types"); - const uint8_t old_size = ((memory_read(offset - 4)) >> 2) + 1; - - if (ent_types_size >= 32) - ent_types_size = 32; - else if ((ent_types_size & (ent_types_size - 1))) // if the size is not power of 2 - { - auto get_previous_power_of_two = [](uint32_t x) - { - x = x | (x >> 1); - x = x | (x >> 2); - x = x | (x >> 4); - x = x | (x >> 8); - x = x | (x >> 16); - return x ^ (x >> 1); - }; - ent_types_size = get_previous_power_of_two(ent_types_size); + // just so we can recover the oryginal later + save_mem_recoverable("sunchallenge_spawn", offset, 14, true); } - - if (old_size == ent_types_size) + const size_t table_offset = offset + 10; // offset to the offset of ent_type table + ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(table_offset) + table_offset + 4); + bool was_edited_before = mem_written("sunchallenge_spawn"); + if (ent_types.size() == 0) { - for (uint32_t i = 0; i < ent_types_size; ++i) - write_mem_recoverable("sunchallenge_spawn", (size_t)&old_types_array[i], ent_types[i], true); + recover_mem("sunchallenge_spawn"); + if (was_edited_before) + VirtualFree(old_types_array, 0, MEM_RELEASE); + // just free it since it's just easier to put the code again + if (new_code_address != 0) + { + VirtualFree(reinterpret_cast(new_code_address), 0, MEM_RELEASE); + new_code_address = 0; + } return; } - - const auto data_size = ent_types_size * sizeof(ENT_TYPE); - ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(offset + 4, data_size); + const auto data_size = ent_types.size() * sizeof(ENT_TYPE); + ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(table_offset + 4, data_size); if (new_array) { - if (modified) - VirtualFree(old_types_array, 0, MEM_RELEASE); + std::memcpy(new_array, ent_types.data(), data_size); + int32_t rel = static_cast((size_t)new_array - (table_offset + 4)); + write_mem_prot(table_offset, rel, true); - memcpy(new_array, ent_types.data(), data_size); - int32_t rel = static_cast((size_t)new_array - (offset + 4)); - write_mem_recoverable("sunchallenge_spawn", offset, rel, true); + if (new_code_address == 0) + { + std::string new_code = fmt::format("\x31\xD2\xB9{}\xF7\xF1\x67\x8D\x04\x95\x00\x00\x00\x00"sv, to_le_bytes(static_cast(ent_types.size()))); + // xor edx, edx ; dividend high half = 0. + // mov ecx, ent_types.size() ; dividend low half + // div ecx ; division, (divisor already in rax) + // ; edx - remainder + // lea eax,[edx * 4 + 0] ; multiply by 4 (sizeof ENT_TYPE) and put result in rax + + new_code_address = patch_and_redirect(offset, 7, new_code, true); + } + else // update the size since the code is in place + write_mem_prot(new_code_address + 3, to_le_bytes(static_cast(ent_types.size())), true); - // the game does bitwise "and" with value 12 (0xC), so it would get 0, 4, 8 or 12 (4 positions in table) - int8_t new_value = static_cast((ent_types_size - 1) << 2); - write_mem_recoverable("sunchallenge_spawn", offset - 4, new_value, true); - modified = true; + if (was_edited_before) + VirtualFree(old_types_array, 0, MEM_RELEASE); } } diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index e66773bbc..08f1a8b36 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1773,7 +1773,6 @@ end /// Change ENT_TYPE's spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4:
/// {MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER}
- /// Because of the game logic number of entity types has to be a power of 2: (1, 2, 4, 8, 16, 32), if you want say 30 types, you need to write two entities two times (they will have higher "spawn chance"). /// Use empty table as argument to reset to the game default lua["change_sunchallenge_spawns"] = change_sunchallenge_spawns; diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 91be6aba3..157db56ca 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -1641,11 +1641,12 @@ std::unordered_map g_address_rules{ }, { // Set condition bp on spawn_entity (not load_item) for one of the entities spawned by this generator - // execute to the return two times, you should see this array right above + // execute to the return two times, you should see this array right above the call // It's pointer to array[4]: 0x000000F5 0x000000EB 0x000000FC 0x000000FA + // we want the address to the `shift right` instruction since we gonna replace it all, but not mess with PRNG stuff "sun_chalenge_generator_ent_types"sv, PatternCommandBuffer{} - .find_after_inst("\x48\x89\x4A\x38\x48\xC1\xE8\x1C\x83\xE0"sv) + .find_inst("\x48\x89\x4A\x38\x48\xC1\xE8\x1C\x83\xE0"sv) .offset(0x4) .at_exe(), }, From 455d2ee7fed695f32e20720aa93cf3447efda31a Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 31 Mar 2024 21:03:08 +0200 Subject: [PATCH 10/23] make `prng:random_int` always return a value, some small optimisations as well, update doc --- docs/game_data/spel2.lua | 4 +- docs/src/includes/_types.md | 4 +- src/game_api/prng.cpp | 40 ++--------------- src/game_api/prng.hpp | 52 +++++++++++++++++----- src/game_api/script/usertypes/prng_lua.cpp | 2 +- src/game_api/spawn_api.cpp | 6 +-- 6 files changed, 53 insertions(+), 55 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index f3b2bb1d0..8a9dbd066 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -2360,7 +2360,7 @@ do ---@field random_float fun(self, type: PRNG_CLASS): number @Generate a random floating point number in the range `[0, 1)` ---@field random_chance fun(self, inverse_chance: integer, type: PRNG_CLASS): boolean @Returns true with a chance of `1/inverse_chance` ---@field random_index fun(self, i: integer, type: PRNG_CLASS): integer? @Generate a integer number in the range `[1, i]` or `nil` if `i < 1` - ---@field random_int fun(self, min: integer, max: integer, type: PRNG_CLASS): integer? @Generate a integer number in the range `[min, max]` or `nil` if `max < min` + ---@field random_int fun(self, min: integer, max: integer, type: PRNG_CLASS): integer @Generate a integer number in the range `[min, max]` ---@field get_pair fun(self, type: PRNG_CLASS): integer, integer ---@field set_pair fun(self, type: PRNG_CLASS, first: integer, second: integer): nil local PRNG = nil @@ -2374,7 +2374,7 @@ function PRNG:random(i) end ---Drop-in replacement for `math.random(min, max)` ---@param min integer ---@param max integer ----@return integer? +---@return integer function PRNG:random(min, max) end ---@class Color diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index 992d59f47..d3631c89e 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -827,10 +827,10 @@ nil | [seed(int seed)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q= float | [random_float(PRNG_CLASS type)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random_float) | Generate a random floating point number in the range `[0, 1)` bool | [random_chance(int inverse_chance, PRNG_CLASS type)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random_chance) | Returns true with a chance of `1/inverse_chance` optional<int> | [random_index(int i, PRNG_CLASS type)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random_index) | Generate a integer number in the range `[1, i]` or `nil` if `i < 1` -optional<int> | [random_int(int min, int max, PRNG_CLASS type)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random_int) | Generate a integer number in the range `[min, max]` or `nil` if `max < min` +int | [random_int(int min, int max, PRNG_CLASS type)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random_int) | Generate a integer number in the range `[min, max]` float | [random()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random) | Drop-in replacement for `math.random()` optional<int> | [random(int i)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random) | Drop-in replacement for `math.random(i)` -optional<int> | [random(int min, int max)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random) | Drop-in replacement for `math.random(min, max)` +int | [random(int min, int max)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=random) | Drop-in replacement for `math.random(min, max)` tuple<int, int> | [get_pair(PRNG_CLASS type)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_pair) | nil | [set_pair(PRNG_CLASS type, int first, int second)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pair) | diff --git a/src/game_api/prng.cpp b/src/game_api/prng.cpp index 4f406c9bb..8a933a103 100644 --- a/src/game_api/prng.cpp +++ b/src/game_api/prng.cpp @@ -1,7 +1,5 @@ #include "prng.hpp" -#include // for numeric_limits - #include "state.hpp" // for State PRNG& PRNG::get_main() @@ -57,14 +55,9 @@ PRNG::prng_pair PRNG::get_and_advance(PRNG_CLASS type) return copy; } -std::int64_t PRNG::internal_random_index(std::int64_t size, PRNG_CLASS type) +std::optional PRNG::internal_random_int(std::int64_t min, std::int64_t size, PRNG_CLASS type) { - prng_pair pair = get_and_advance(type); - return static_cast((pair.first & 0xffffffff) * size >> 0x20); -} -std::optional PRNG::internal_random_int(std::int64_t min, std::int64_t max, PRNG_CLASS type) -{ - if (max <= min) + if (size <= min) { return std::nullopt; } @@ -83,32 +76,5 @@ std::optional PRNG::internal_random_int(std::int64_t min, std::int // Technically not a uniform distribution, but we have 64bit to map to a range that is many orders of magnitude smaller // So in the grand scheme this is close enough to a uniform distribution - return wrap(static_cast(pair.first), min, max); -} - -bool PRNG::random_chance(std::int64_t inverse_chance, PRNG_CLASS type) -{ - if (inverse_chance <= 0ll) - return false; - if (inverse_chance == 1) - return true; - return random_int(0, inverse_chance - 1, type) == 0; -} - -float PRNG::random_float(PRNG_CLASS type) -{ - prng_pair pair = get_and_advance(type); - return static_cast(pair.first) / static_cast(std::numeric_limits::max()); -} -std::optional PRNG::random_index(std::int64_t i, PRNG_CLASS type) -{ - return random_int(1, i, type); -} -std::optional PRNG::random_int(std::int64_t min, std::int64_t max, PRNG_CLASS type) -{ - if (min <= max) - { - return internal_random_int(min, max + 1, type); - } - return std::nullopt; + return wrap(static_cast(pair.first), min, size); } diff --git a/src/game_api/prng.hpp b/src/game_api/prng.hpp index 2a7872ff1..403e3b17f 100644 --- a/src/game_api/prng.hpp +++ b/src/game_api/prng.hpp @@ -2,6 +2,7 @@ #include // for size_t #include // for int64_t, uint64_t +#include // for numeric_limits #include // for optional #include // for pair @@ -52,33 +53,64 @@ struct PRNG } /// Generate a random integer in the range `[0, size)` - std::int64_t internal_random_index(std::int64_t size, PRNG_CLASS type); - /// Generate a random integer in the range `[min, size)`, returns `nil` if `min == max` - std::optional internal_random_int(std::int64_t min, std::int64_t max, PRNG_CLASS type); - + std::int64_t internal_random_index(std::int64_t size, PRNG_CLASS type) + { + prng_pair pair = get_and_advance(type); + return static_cast((pair.first & 0xffffffff) * size >> 0x20); + } /// Returns true with a chance of `1/inverse_chance` - bool random_chance(std::int64_t inverse_chance, PRNG_CLASS type); + bool random_chance(std::int64_t inverse_chance, PRNG_CLASS type) + { + // TODO: even thou those first checks just return, shouldn't it still advance the given pair? + if (inverse_chance <= 0ll) + return false; + if (inverse_chance == 1) + return true; + return internal_random_int(0, inverse_chance, type) == 0; + } /// Generate a random floating point number in the range `[0, 1)` - float random_float(PRNG_CLASS type); + float random_float(PRNG_CLASS type) + { + prng_pair pair = get_and_advance(type); + // TODO: shouldn't this cast to double and then cast result to float? + return static_cast(pair.first) / static_cast(std::numeric_limits::max()); + } /// Drop-in replacement for `math.random()` float random() { return random_float(static_cast(7)); } /// Generate a integer number in the range `[1, i]` or `nil` if `i < 1` - std::optional random_index(std::int64_t i, PRNG_CLASS type); + std::optional random_index(std::int64_t i, PRNG_CLASS type) + { + if (i < 1) + return std::nullopt; + + return internal_random_int(1, i + 1, type).value(); + } /// Drop-in replacement for `math.random(i)` std::optional random(std::int64_t i) { return random_index(i, static_cast(7)); } - /// Generate a integer number in the range `[min, max]` or `nil` if `max < min` - std::optional random_int(std::int64_t min, std::int64_t max, PRNG_CLASS type); + /// Generate a integer number in the range `[min, max]` + std::int64_t random_int(std::int64_t min, std::int64_t max, PRNG_CLASS type) + { + if (max < min) + std::swap(min, max); + + return internal_random_int(min, max + 1, type).value(); + } /// Drop-in replacement for `math.random(min, max)` - std::optional random(std::int64_t min, std::int64_t max) + std::int64_t random(std::int64_t min, std::int64_t max) { return random_int(min, max, static_cast(7)); } + + private: + /// Generate a random integer in the range `[min, size]`, returns `nil` if `min <= size` + std::optional internal_random_int(std::int64_t min, std::int64_t size, PRNG_CLASS type); + std::array pairs; }; diff --git a/src/game_api/script/usertypes/prng_lua.cpp b/src/game_api/script/usertypes/prng_lua.cpp index 3e1955380..246212a54 100644 --- a/src/game_api/script/usertypes/prng_lua.cpp +++ b/src/game_api/script/usertypes/prng_lua.cpp @@ -16,7 +16,7 @@ namespace NPRNG { void register_usertypes(sol::state& lua) { - auto random = sol::overload(static_cast(&PRNG::random), static_cast (PRNG::*)(std::int64_t)>(&PRNG::random), static_cast (PRNG::*)(std::int64_t, std::int64_t)>(&PRNG::random)); + auto random = sol::overload(static_cast(&PRNG::random), static_cast (PRNG::*)(std::int64_t)>(&PRNG::random), static_cast(&PRNG::random)); /// Seed the game prng. lua["seed_prng"] = [](int64_t seed) diff --git a/src/game_api/spawn_api.cpp b/src/game_api/spawn_api.cpp index e377ab773..e746b9a24 100644 --- a/src/game_api/spawn_api.cpp +++ b/src/game_api/spawn_api.cpp @@ -326,7 +326,7 @@ int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) auto spawn_deco = [&](Entity* branch, bool left) { Entity* deco = layer_ptr->spawn_entity_over(tree_deco, branch, 0.0f, 0.49f); - deco->animation_frame = 7 * 12 + 3 + static_cast(prng.random_int(0, 2, PRNG::PRNG_CLASS::ENTITY_VARIATION).value_or(0)) * 12; + deco->animation_frame = 7 * 12 + 3 + static_cast(prng.random_int(0, 2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) * 12; if (left) deco->flags |= 1U << 16; // flag 17: facing left }; @@ -385,7 +385,7 @@ int32_t spawn_mushroom(float x, float y, LAYER l, uint16_t height) // height rel else { auto& prng = PRNG::get_local(); - height = static_cast(prng.random_int(1, 3, PRNG::PRNG_CLASS::PROCEDURAL_SPAWNS).value_or(0)); + height = static_cast(prng.random_int(1, 3, PRNG::PRNG_CLASS::PROCEDURAL_SPAWNS)); } i_y += 3; @@ -816,5 +816,5 @@ MagmamanSpawnPosition::MagmamanSpawnPosition(uint32_t x_, uint32_t y_) { x = x_; y = y_; - timer = static_cast(PRNG::get_local().random_int(2700, 27000, PRNG::PRNG_CLASS::PROCEDURAL_SPAWNS).value_or(10000)); + timer = static_cast(PRNG::get_local().random_int(2700, 27000, PRNG::PRNG_CLASS::PROCEDURAL_SPAWNS)); } From d642e159376b613d80ecb5bafc38d613181c57c9 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:54:10 +0200 Subject: [PATCH 11/23] add `get_entities_overlaping_grid`, fix `door:unlock` not updating the BG of the hundun door when unlocking --- docs/examples/Door.lua | 13 +++++++++++++ docs/game_data/spel2.lua | 6 ++++++ docs/src/includes/_globals.md | 9 +++++++++ docs/src/includes/_types.md | 18 ++++++++++++++++++ src/game_api/entities_floors.cpp | 14 ++++++++++++-- src/game_api/entity_lookup.cpp | 18 ++++++++++++++++++ src/game_api/entity_lookup.hpp | 2 ++ src/game_api/layer.cpp | 11 +++++++++++ src/game_api/layer.hpp | 4 +++- src/game_api/memory.cpp | 4 ++++ src/game_api/script/lua_vm.cpp | 2 ++ 11 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 docs/examples/Door.lua diff --git a/docs/examples/Door.lua b/docs/examples/Door.lua new file mode 100644 index 000000000..9d19f8720 --- /dev/null +++ b/docs/examples/Door.lua @@ -0,0 +1,13 @@ +--If you want the locked door to look like the closed exit door at Hundun + +function close_hundun_door(door) + door:unlock(false) + for _, uid in pairs(get_entities_overlapping_grid(door.x, door.y, door.layer)) do + ent = get_entity(uid) + if ent.type.id == ENT_TYPE.BG_DOOR then + ent:set_texture(TEXTURE.DATA_TEXTURES_DECO_EGGPLANT_0) + return + end + end +end + diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 8a9dbd066..1dead0de5 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -544,6 +544,12 @@ function get_type(id) end ---@param layer LAYER ---@return integer function get_grid_entity_at(x, y, layer) end +---Get uids of static entities overlaping this grid position (decorations, backgrounds etc.) +---@param x number +---@param y number +---@param layer LAYER +---@return integer[] +function get_entities_overlapping_grid(x, y, layer) end ---Returns a list of all uids in `entities` for which `predicate(get_entity(uid))` returns true ---@param entities integer[] ---@param predicate function diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index a84f494ae..aacf579b2 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -670,6 +670,15 @@ Get uids of entities matching id. This function is variadic, meaning it accepts You can even pass a table! This function can be slower than the [get_entities_by](#get_entities_by) with the mask parameter filled +### get_entities_overlapping_grid + + +> Search script examples for [get_entities_overlapping_grid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_overlapping_grid) + +#### vector<int> get_entities_overlapping_grid(float x, float y, [LAYER](#LAYER) layer) + +Get uids of static entities overlaping this grid position (decorations, backgrounds etc.) + ### get_entities_overlapping_hitbox diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index d3631c89e..f9371bede 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -3732,6 +3732,24 @@ Type | Name | Description ### Door + +```lua +--If you want the locked door to look like the closed exit door at Hundun + +function close_hundun_door(door) + door:unlock(false) + for _, uid in pairs(get_entities_overlapping_grid(door.x, door.y, door.layer)) do + ent = get_entity(uid) + if ent.type.id == ENT_TYPE.BG_DOOR then + ent:set_texture(TEXTURE.DATA_TEXTURES_DECO_EGGPLANT_0) + return + end + end +end + + +``` + Derived from [Entity](#Entity) [Floor](#Floor) diff --git a/src/game_api/entities_floors.cpp b/src/game_api/entities_floors.cpp index d34874589..4f36ea021 100644 --- a/src/game_api/entities_floors.cpp +++ b/src/game_api/entities_floors.cpp @@ -772,12 +772,22 @@ void Door::unlock(bool unlock) if (ent_type == entrence_door || ent_type == entrence_door + 1 || ent_type == entrence_door + 3) { static const ENT_TYPE door_bg = to_id("ENT_TYPE_BG_DOOR"); - const auto state_layer = state.layer(this->layer); - for (const auto& item : state_layer->entities_overlaping_grid[static_cast(y)][static_cast(x)].entities()) + const auto entities = state.layer(this->layer)->get_entities_overlapping_grid_at(x, y); + if (entities == nullptr) + return; + for (const auto& item : entities->entities()) { + // technically for exit door the bg is uid + 3, but it feels wrong to do it that way if (item->type->id == door_bg) { item->animation_frame = unlock ? 1 : 0; + // for Hundun door + // there is locked door sprite in both textures, so we don't mess with it and just use animation_frame when locking back up + // added example in the API doc on how to do the texture correctly for the other variant + if (unlock && item->get_texture() == 202) // TEXTURE.DATA_TEXTURES_DECO_EGGPLANT_0 + { + item->set_texture(200); // TEXTURE.DATA_TEXTURES_FLOOR_SUNKEN_3 + } break; } } diff --git a/src/game_api/entity_lookup.cpp b/src/game_api/entity_lookup.cpp index ec452fd22..776067628 100644 --- a/src/game_api/entity_lookup.cpp +++ b/src/game_api/entity_lookup.cpp @@ -49,6 +49,24 @@ int32_t get_grid_entity_at(float x, float y, LAYER layer) return -1; } +std::vector get_entities_overlapping_grid(float x, float y, LAYER layer) +{ + auto& state = State::get(); + uint8_t actual_layer = enum_to_layer(layer); + std::vector uids; + auto entities = state.layer(actual_layer)->get_entities_overlapping_grid_at(x, y); + if (entities) + uids.insert(uids.end(), entities->uids().begin(), entities->uids().end()); + if (layer == LAYER::BOTH) + { + // enum_to_layer returns 0 for LAYER::BOTH, so we only need to add entities from second layer + auto entities2 = state.layer(1)->get_entities_overlapping_grid_at(x, y); + if (entities2) + uids.insert(uids.end(), entities2->uids().begin(), entities2->uids().end()); + } + return uids; +} + template requires std::is_invocable_v void foreach_mask(uint32_t mask, Layer* l, FunT&& fun) diff --git a/src/game_api/entity_lookup.hpp b/src/game_api/entity_lookup.hpp index fca752170..3c9157b2f 100644 --- a/src/game_api/entity_lookup.hpp +++ b/src/game_api/entity_lookup.hpp @@ -10,6 +10,8 @@ struct Layer; int32_t get_grid_entity_at(float x, float y, LAYER layer); +std::vector get_entities_overlapping_grid(float x, float y, LAYER layer); + std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer); inline std::vector get_entities() diff --git a/src/game_api/layer.cpp b/src/game_api/layer.cpp index 20fa461aa..07288e349 100644 --- a/src/game_api/layer.cpp +++ b/src/game_api/layer.cpp @@ -100,6 +100,17 @@ Entity* Layer::spawn_entity_over(ENT_TYPE id, Entity* overlay, float x, float y) return ent; } +EntityList* Layer::get_entities_overlapping_grid_at(float x, float y) const +{ + const uint32_t ix = static_cast(std::round(x)); + const uint32_t iy = static_cast(std::round(y)); + if (ix < g_level_max_x && iy < g_level_max_y) + { + return const_cast(&entities_overlapping_grid[iy][ix]); + } + return nullptr; +} + Entity* Layer::get_grid_entity_at(float x, float y) const { const uint32_t ix = static_cast(std::round(x)); diff --git a/src/game_api/layer.hpp b/src/game_api/layer.hpp index 752eb2238..1c89a74b2 100644 --- a/src/game_api/layer.hpp +++ b/src/game_api/layer.hpp @@ -149,7 +149,7 @@ struct Layer std::map entity_regions; // key is uid, all entities except FX, FLOOR, DECORATION, BG, SHADOW and LOGICAL Entity* grid_entities[g_level_max_y][g_level_max_x]; - EntityList entities_overlaping_grid[g_level_max_y][g_level_max_x]; // static entities (like midbg, decorations) that overlap this grid position + EntityList entities_overlapping_grid[g_level_max_y][g_level_max_x]; // static entities (like midbg, decorations) that overlap this grid position EntityList unknown_entities2; std::array entities_by_draw_depth; @@ -207,6 +207,8 @@ struct Layer Entity* get_grid_entity_at(float x, float y) const; + EntityList* get_entities_overlapping_grid_at(float x, float y) const; + Entity* get_entity_at(float x, float y, uint32_t search_flags, uint32_t include_flags, uint32_t exclude_flags, uint32_t one_of_flags); void move_grid_entity(Entity* ent, float x, float y, Layer* dest_layer); diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index abaf9b0eb..2f6a3cf39 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -179,6 +179,10 @@ void recover_mem(std::string name, size_t addr) } } } + else + { + DEBUG("Warning: (recover_mem) tried to recover non existing memeory named: {}", name); + } } bool mem_written(std::string name) diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 08f1a8b36..e58acfcca 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1040,6 +1040,8 @@ end lua["get_type"] = get_type; /// Gets a grid entity, such as floor or spikes, at the given position and layer. lua["get_grid_entity_at"] = get_grid_entity_at; + /// Get uids of static entities overlaping this grid position (decorations, backgrounds etc.) + lua["get_entities_overlapping_grid"] = get_entities_overlapping_grid; /// Deprecated /// Use `get_entities_by(0, MASK.ANY, LAYER.BOTH)` instead lua["get_entities"] = get_entities; From 9616bc04084259db77b2216dbae73fc0ebd0e616 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:56:33 +0200 Subject: [PATCH 12/23] hide debug message --- src/game_api/memory.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index 2f6a3cf39..b610bd4c0 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -179,10 +179,8 @@ void recover_mem(std::string name, size_t addr) } } } - else - { - DEBUG("Warning: (recover_mem) tried to recover non existing memeory named: {}", name); - } + //else + // DEBUG("Warning: (recover_mem) tried to recover non existing memeory named: {}", name); } bool mem_written(std::string name) From ecdf6cac7bff9b96d2cedfc2d481bb2a5cdc3444 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:02:11 +0200 Subject: [PATCH 13/23] fix `Movable:set_cursed` missing parameter --- src/game_api/memory.cpp | 4 ++-- src/game_api/movable.hpp | 8 +++++++- src/game_api/script/usertypes/entity_lua.cpp | 2 +- src/injected/ui.cpp | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index b610bd4c0..9ac738c5e 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -179,8 +179,8 @@ void recover_mem(std::string name, size_t addr) } } } - //else - // DEBUG("Warning: (recover_mem) tried to recover non existing memeory named: {}", name); + // else + // DEBUG("Warning: (recover_mem) tried to recover non existing memeory named: {}", name); } bool mem_written(std::string name) diff --git a/src/game_api/movable.hpp b/src/game_api/movable.hpp index 198080099..232b87b97 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -150,6 +150,12 @@ class Movable : public Entity this->collect_treasure(amount, coin); } + /// effect = true - plays the sound and spawn particle above entity + void set_cursed_fix(bool b, std::optional effect) + { + set_cursed(b, effect.value_or(true)); + } + /// Return true if the entity is allowed to jump, even midair. Return false and can't jump, except from ladders apparently. virtual bool can_jump() = 0; // 37 virtual void get_collision_info(CollisionInfo* dest) = 0; // 38 @@ -174,7 +180,7 @@ class Movable : public Entity /// Does not damage entity virtual void light_on_fire(uint8_t time) = 0; // 53 - virtual void set_cursed(bool b) = 0; // 54 + virtual void set_cursed(bool b, bool other) = 0; // 54 virtual void on_spiderweb_collision() = 0; // 55 virtual void set_last_owner_uid_b127(Entity* owner) = 0; // 56, assigns player as last_owner_uid and also manipulates movable.b127 virtual uint32_t get_last_owner_uid() = 0; // 57, for players, it checks !stunned && !frozen && !cursed && !has_overlay; for others: just returns last_owner_uid diff --git a/src/game_api/script/usertypes/entity_lua.cpp b/src/game_api/script/usertypes/entity_lua.cpp index 00a14e91a..a07cbdb52 100644 --- a/src/game_api/script/usertypes/entity_lua.cpp +++ b/src/game_api/script/usertypes/entity_lua.cpp @@ -345,7 +345,7 @@ void register_usertypes(sol::state& lua) movable_type["stun"] = &Movable::stun; movable_type["freeze"] = &Movable::freeze; movable_type["light_on_fire"] = light_on_fire; - movable_type["set_cursed"] = &Movable::set_cursed; + movable_type["set_cursed"] = &Movable::set_cursed_fix; movable_type["drop"] = &Movable::drop; movable_type["pick_up"] = &Movable::pick_up; movable_type["can_jump"] = &Movable::can_jump; diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index affa782d5..b65b94cdd 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -1947,7 +1947,7 @@ void force_cheats() ent->onfire_effect_timer = 0; ent->wet_effect_timer = 0; ent->lock_input_timer = 0; - ent->set_cursed(false); + ent->set_cursed(false, false); ent->more_flags &= ~(1U << 16); UI::destroy_entity_item_type(ent, ink); } From 021fd6655f0453cb60e52dc1a17b5a22c993571c Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 7 Apr 2024 16:03:45 +0200 Subject: [PATCH 14/23] add function to add movable entity to the liquid collision (the game tracks the entity), add function to switch liquids to back layer --- src/game_api/rpc.cpp | 130 +++++++++++++++++++++++- src/game_api/rpc.hpp | 4 +- src/game_api/script/lua_vm.cpp | 10 ++ src/game_api/search.cpp | 175 ++++++++++++++++++++++++++++++++- src/game_api/state_structs.hpp | 14 +-- 5 files changed, 316 insertions(+), 17 deletions(-) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 36144da93..a570744e6 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1311,17 +1311,48 @@ std::pair get_adventure_seed(std::optional run_start) } } -void update_liquid_collision_at(float x, float y, bool add) +void update_liquid_collision_at(float x, float y, bool add, std::optional layer) { - using UpdateLiquidCollision = void(LiquidPhysics*, int32_t, int32_t, bool); // setting last parameter to true just skips the whole function + using UpdateLiquidCollision = void(LiquidPhysics*, int32_t, int32_t, uint8_t); static UpdateLiquidCollision* RemoveLiquidCollision_fun = (UpdateLiquidCollision*)get_address("remove_from_liquid_collision_map"); - static UpdateLiquidCollision* AddLiquidCollision_fun = (UpdateLiquidCollision*)get_address("add_from_liquid_collision_map"); + static UpdateLiquidCollision* AddLiquidCollision_fun = (UpdateLiquidCollision*)get_address("add_to_liquid_collision_map"); auto state = get_state_ptr(); + uint8_t actual_layer = enum_to_layer(layer.value_or(LAYER::FRONT)); if (add) - AddLiquidCollision_fun(state->liquid_physics, static_cast(std::round(x)), static_cast(std::round(y)), false); + AddLiquidCollision_fun(state->liquid_physics, static_cast(std::round(x)), static_cast(std::round(y)), actual_layer); else - RemoveLiquidCollision_fun(state->liquid_physics, static_cast(std::round(x)), static_cast(std::round(y)), false); + RemoveLiquidCollision_fun(state->liquid_physics, static_cast(std::round(x)), static_cast(std::round(y)), actual_layer); +} + +void add_entity_to_liquid_collision(uint32_t uid, bool add, std::optional layer) +{ + using AddEntityLiquidCollision = void(LiquidPhysics*, Entity*, uint8_t); + static AddEntityLiquidCollision* add_entity_liquid_collision = (AddEntityLiquidCollision*)get_address("add_movable_to_liquid_collision_map"); + auto state = get_state_ptr(); + uint8_t actual_layer = enum_to_layer(layer.value_or(LAYER::FRONT)); + auto entity = get_entity_ptr(uid); + if (!entity) + return; + + auto map = state->liquid_physics->push_blocks; + if (!map) + return; + + auto it = map->find(uid); + + // if it already exists we can't add it again, since it will create the collision struct anyway and just overwrite the pointer to it in the map + // the actual collision struct is held somewhere else, unrelated to this map + if (add && it == map->end()) + add_entity_liquid_collision(state->liquid_physics, entity, actual_layer); + else if (!add && it != map->end()) + { + // very illegal, don't do this, we can because we're professionals xd + // game loops thru the map and checks if uid still exists, if not, it removes the collision + // which is some bigger struct held in some weird container, and the function is doing other stuff, so this is the easiest way besides killing the entity + auto test = const_cast(&it->first); + *test = ~0u; + } } bool disable_floor_embeds(bool disable) @@ -1898,3 +1929,92 @@ void init_seeded(std::optional seed) auto* state = State::get().ptr(); isf(state, seed.value_or(state->seed)); } + +void set_liquid_layer(LAYER l) +{ + static std::array jumps; // jne (0F85) -> je (0F84) + static std::array layer_offsets; // 0x1300 -> 0x1308 + static std::array layer_byte; + static uintptr_t jump2; + if (jumps[0] == 0) + { + layer_byte[0] = get_address("check_if_collides_with_liquid_layer"); + layer_byte[1] = get_address("check_if_collides_with_liquid_layer2"); + layer_byte[2] = get_address("lavamander_spewing_lava"); + layer_byte[3] = get_address("movement_calculations_layer_check"); + layer_byte[4] = get_address("jump_calculations_layer_check"); + // i don't actually know what this bit does, probably bool param, it's not just liquid relates as it's for all the entities with collision mask + // and it's not layer as well since other collision occur in back layer even with this set to 0 and vice versa + layer_byte[5] = get_address("collision_mask_check_param"); + + for (auto addr : layer_byte) + if (addr == 0) + return; + + layer_offsets[0] = get_address("spawn_liquid_layer"); + auto sound_stuff = get_address("liquid_stream_spawner"); + { + if (sound_stuff == 0) + return; + + auto& mem = Memory::get(); + auto last_offset = sound_stuff - mem.exe_address(); + bool skip = true; + for (uint8_t idx = 0; idx < 6; ++idx) + { + last_offset = find_inst(mem.exe(), "\x48\x8B\x8A"sv, last_offset, last_offset + 0x170, "set_liquid_layer"); + if (idx == 5 && skip) // skip one, same instruction but not layer related + { + idx = 4; + skip = false; + last_offset += 7; + continue; + } + layer_offsets[idx + 1] = mem.at_exe(last_offset); + last_offset += 7; + } + } + layer_offsets[7] = get_address("tidepool_impostor_spawn"); + layer_offsets[8] = get_address("tiamat_impostor_spawn"); + layer_offsets[9] = get_address("olmec_impostor_spawn"); + layer_offsets[10] = get_address("abzu_impostor_spawn"); + + for (auto addr : layer_offsets) + if (addr == 0) + return; + + jump2 = get_address("robot_layer_check"); + if (jump2 == 0) + return; + + jumps[0] = get_address("layer_check_in_add_liquid_collision"); + jumps[1] = get_address("layer_check_in_remove_liquid_collision"); + jumps[2] = get_address("is_entity_in_liquid_check"); // TODO there is also layer offset nerby, test if it's related + jumps[3] = get_address("liquid_render_layer"); + jumps[4] = get_address("entity_in_liquid_detection1"); + jumps[5] = get_address("entity_in_liquid_detection2"); + jumps[6] = get_address("layer_check_in_add_movable_liquid_collision"); + + for (auto addr : jumps) + if (addr == 0) + { + jumps[0] = 0; + return; + } + } + auto actual_layer = enum_to_layer(l); + uint8_t offset_ending = actual_layer == 0 ? 0 : 8; + uint8_t jump_oppcode = actual_layer == 0 ? 0x85 : 0x84; + uint8_t jump_oppcode2 = actual_layer == 0 ? 0x75 : 0x74; + + for (auto addr : jumps) + write_mem_prot(addr + 1, jump_oppcode, true); + + for (auto addr : layer_offsets) + write_mem_prot(addr + 3, offset_ending, true); + + for (auto addr : layer_byte) + write_mem_prot(addr, actual_layer, true); + + write_mem_prot(jump2, jump_oppcode2, true); +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index d901f3420..d8ffbd673 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -101,7 +101,8 @@ void add_item_to_shop(int32_t item_uid, int32_t shop_owner_uid); void change_poison_timer(int16_t frames); void set_adventure_seed(int64_t first, int64_t second); std::pair get_adventure_seed(std::optional run_start); -void update_liquid_collision_at(float x, float y, bool add); +void update_liquid_collision_at(float x, float y, bool add, std::optional layer = std::nullopt); +void add_entity_to_liquid_collision(uint32_t uid, bool add, std::optional layer); bool disable_floor_embeds(bool disable); void set_cursepot_ghost_enabled(bool enable); void game_log(std::string message); @@ -137,3 +138,4 @@ void set_speedhack(std::optional multiplier); float get_speedhack(); void init_adventure(); void init_seeded(std::optional seed); +void set_liquid_layer(LAYER l); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index e58acfcca..669e0bbce 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1922,6 +1922,7 @@ end /// Set the current adventure seed pair. Use just before resetting a run to recreate an adventure run. lua["set_adventure_seed"] = set_adventure_seed; /// Updates the floor collisions used by the liquids, set add to false to remove tile of collision, set to true to add one + /// optional `layer` parameter to be used when liquid was moved to back layer using [set_liquid_layer](#set_liquid_layer) lua["update_liquid_collision_at"] = update_liquid_collision_at; /// Disable all crust item spawns, returns whether they were already disabled before the call @@ -2263,6 +2264,15 @@ 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; + /// Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid + lua["set_liquid_layer"] = set_liquid_layer; + + /// Attach liquid collision to entity by uid (this is what the push blocks use) + /// Collision is based on the entity's hitbox, collision in removed when the entity is destroyed (bodies of killed entities will still have the collision) + /// Use only for entities that can move around, (for static entities use [update_liquid_collision_at](#update_liquid_collision_at) ) + /// optional `layer` parameter to be used when liquid was moved to back layer using [set_liquid_layer](#set_liquid_layer) + lua["add_entity_to_liquid_collision"] = add_entity_to_liquid_collision; + 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/game_api/search.cpp b/src/game_api/search.cpp index 157db56ca..c08d8a356 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -1797,9 +1797,9 @@ std::unordered_map g_address_rules{ { // Go to the spawn function when spawning floor, there should be something like call r8, go into that function, inside there would be couple calls to virtuals (not entity virtuals) // one of them is the one that calls this function (can be recognize by the getting address for the LiquidPhysics) - "add_from_liquid_collision_map"sv, + "add_to_liquid_collision_map"sv, PatternCommandBuffer{} - .find_inst("\x31\xF6\x39\x6B\x20\x40\x0F\x92\xC6"sv) + .find_inst("\x31\xF6\x39\x6B\x20\x40\x0F\x92\xC6"sv) // other pattern: 48 81 EC 10 01 00 00 45 84 C9 .at_exe() .function_start(), }, @@ -2009,7 +2009,7 @@ std::unordered_map g_address_rules{ // kill exit door, crash the game by killing hundun. It crashes in the function that we need // this pattern is also using in set_boss_door_control_enabled function PatternCommandBuffer{} - .find_inst("\x4A\x8B\xB4\xC8\x80\xF4\x00\x00") + .find_inst("\x4A\x8B\xB4\xC8\x80\xF4\x00\x00"sv) .at_exe() .function_start(), }, @@ -2074,7 +2074,7 @@ std::unordered_map g_address_rules{ }, { "get_game_api"sv, - // can be found together with get_feat function + // can be found together with get_feat function, or rendering stuff PatternCommandBuffer{} .find_after_inst("49 89 CE 4C 8B 79 08"_gh) .find_inst("\xE8"sv) @@ -2116,6 +2116,173 @@ std::unordered_map g_address_rules{ PatternCommandBuffer{} .from_exe_base(0x22e0d1d0) // TODO }, + { + // look into spawn entity function when spawninig activefloor + // or set break point on write to the activefloors map in state.liquid_physics.activefloors + "add_movable_to_liquid_collision_map"sv, // jump + PatternCommandBuffer{} + .find_after_inst("4C 8B BF 80 03 00 00"_gh) + .at_exe() + .function_start(), + }, + { + "spawn_liquid_layer"sv, // layer offset + PatternCommandBuffer{} + .get_address("spawn_liquid") + .find_after_inst("44 0F 28 C2 44 0F 28 D1"_gh) + .at_exe(), + }, + { + "is_entity_in_liquid_check"sv, // jump + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::CHAR_AU, (VIRT_FUNC)12) // could probably be any entity + .find_inst("CC CC CC CC"_gh) // end of the function + .offset(-5) // there is jump to the function that we need at the end + .decode_pc(1) + .find_after_inst("45 84 C9"_gh) // find the actual layer check + .at_exe(), + }, + { + "liquid_render_layer"sv, // jump + PatternCommandBuffer{} + .find_after_inst("48 0F 44 D0 83 7A 14 01"_gh) + .offset(0xE) + .at_exe(), + }, + { + // look for function that spawns ENT_TYPE_LOGICAL_STREAMWATER_SOUND_SOURCE, ENT_TYPE_LOGICAL_STREAMLAVA_SOUND_SOURCE and ENT_TYPE_LOGICAL_STATICLAVA_SOUND_SOURCE + "liquid_stream_spawner"sv, // multiple layer offset - will get specific address in the function itself + PatternCommandBuffer{} + .find_after_inst("FF 90 C0 00 00 00 48 8B 86 20 01 00 00"_gh) + .at_exe(), + }, + { + // found this by looking what sets the swimming flag and then what part of the code runs when the entity fall into water and finally this stupid is layer 0 check + "entity_in_liquid_detection1"sv, // jump + PatternCommandBuffer{} + .find_after_inst("\x80\xB9\xB4\x03\x00\x00\x00\x0F\x84****\x31\xC0"sv) + .find_after_inst("45 84 C0"_gh) + .at_exe(), + }, + { + // same pattern as above + "entity_in_liquid_detection2"sv, // jump + PatternCommandBuffer{} + .from_exe_base(0x22B6A118), + }, + { + "layer_check_in_add_liquid_collision"sv, // jump + PatternCommandBuffer{} + .get_address("add_to_liquid_collision_map") + .find_after_inst("45 84 C9"_gh) + .at_exe(), + }, + { + "layer_check_in_remove_liquid_collision"sv, // jump + PatternCommandBuffer{} + .get_address("remove_from_liquid_collision_map") + .find_after_inst("45 84 C9"_gh) + .at_exe(), + }, + { + "layer_check_in_add_movable_liquid_collision"sv, // jump + PatternCommandBuffer{} + .get_address("add_movable_to_liquid_collision_map") + .find_after_inst("45 84 C0"_gh) + .at_exe(), + }, + { + // set bp on write of 'is_lit' variable for Torch, jump into the water, execute til return, then go back into the call + // towards the end of the function is call to this function, look to a lot of stuff written to stack before a call, on of those is byte - layer + "check_if_collides_with_liquid_layer"sv, // layer byte or bool // unsure, seam to be only used for fire, even thou it has mask parameter + PatternCommandBuffer{} + .find_after_inst("4C 89 E0 F3 0F 58 60 44"_gh) + .find_after_inst("C6 44 24"_gh) + .offset(1) + .at_exe(), + }, + { + // almost identical function + "check_if_collides_with_liquid_layer2"sv, // layer byte or bool + PatternCommandBuffer{} + .find_after_inst("44 0F 28 C3 44 0F 28 F2 44 0F 28 F9"_gh) + .find_after_inst("C6 44 24"_gh) + .offset(1) + .at_exe(), + }, + { + // when he spews lava, go to hes current behavior, and to function `get_next_state_id` + // then find a few writes to stack and then a function call + // one of those writes is byte [+0x50] with value 0 (presumbly layer? or bool that means check both layers?) + "lavamander_spewing_lava"sv, // layer byte or bool + PatternCommandBuffer{} + .from_exe_base(0x22A45F94), // code too generic to find anything unique + }, + { + // go into virtual Movable:sprint_factor for player, set bp, execute til return + // you will end up towards the end of a function, there is another call, go into it a look for comparison with offset +0xA0 (entity.layer) + "movement_calculations_layer_check"sv, // layer byte or bool + PatternCommandBuffer{} + .find_after_inst("F3 0F 58 4A 40 0F 2E 0D"_gh) + .find_after_inst("41 80 BC 24 A0 00 00 00"_gh) + .at_exe(), + }, + { + // go into Movable:calculate_jump_height for player + // find the same check as above + "jump_calculations_layer_check"sv, // layer byte or bool + PatternCommandBuffer{} + .find_after_inst("\x77*\x80\xB9\xA0\x00\x00\x00"sv) + .at_exe(), + }, + { + "tidepool_impostor_spawn"sv, // layer offset + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_TIDEPOOL, (VIRT_FUNC)15) + .find_inst("4C 8B AA"_gh) + .at_exe(), + }, + { + "tiamat_impostor_spawn"sv, // layer offset + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_TIAMAT, (VIRT_FUNC)15) + .find_inst("48 8B 9A"_gh) + .at_exe(), + }, + { + "olmec_impostor_spawn"sv, // layer offset + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_OLMEC, (VIRT_FUNC)15) + .find_inst("48 8B 8A"_gh) + .at_exe(), + }, + { + "abzu_impostor_spawn"sv, // layer offset + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_ABZU, (VIRT_FUNC)15) + .find_inst("48 8B 9A"_gh) + .at_exe(), + }, + { + // set bp on on_collision2 for plasma cannon (probably any entity works) + // execute till return, when in state update function, above the call to the collision virtual should entity lookup + // with the useal stack set, one of the params is byte 0 which we want to edit + "collision_mask_check_param"sv, // layer byte or bool + PatternCommandBuffer{} + .get_address("state_refresh") + .find_after_inst("48 8B 46 08 8B 40 3C"_gh) + .find_after_inst("C6 44 24"_gh) + .offset(1) + .at_exe(), + }, + { + "robot_layer_check"sv, // different type of jump instruction + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::MONS_ROBOT, (VIRT_FUNC)78) // process input + .find_after_inst("84 C9"_gh) + .at_exe(), + + }, }; std::unordered_map g_cached_addresses; diff --git a/src/game_api/state_structs.hpp b/src/game_api/state_structs.hpp index c976d3e97..5db7df841 100644 --- a/src/game_api/state_structs.hpp +++ b/src/game_api/state_structs.hpp @@ -862,13 +862,13 @@ struct LiquidPhysics LiquidTileSpawnData stagnant_lava_tile_spawn_data; }; }; - std::map, size_t*>* floors; // key is a grid position, the struct seams to be the same as in push_blocks - std::map* push_blocks; // key is uid, not sure about the struct it points to (it's also possible that the value is 2 pointers) - custom_vector impostor_lakes; // - uint32_t total_liquid_spawned; // Total number of spawned liquid entities, all types. - uint32_t unknown8; // padding probably - uint8_t* unknown9; // array byte* ? game allocates 0x2F9E8 bytes for it, (0x2F9E8 / g_level_max_x * g_level_max_y = 18) which is weird, but i still think it's position based index, maybe it's 16 and accounts for more rows (grater level height) - // always allocates after the LiquidPhysics + custom_map, size_t*>* floors; // key is a grid position, the struct seams to be the same as in push_blocks + custom_map* push_blocks; // key is uid, not sure about the struct it points to (it's also possible that the value is 2 pointers) + custom_vector impostor_lakes; // + uint32_t total_liquid_spawned; // Total number of spawned liquid entities, all types. + uint32_t unknown8; // padding probably + uint8_t* unknown9; // array byte* ? game allocates 0x2F9E8 bytes for it, (0x2F9E8 / g_level_max_x * g_level_max_y = 18) which is weird, but i still think it's position based index, maybe it's 16 and accounts for more rows (grater level height) + // always allocates after the LiquidPhysics uint32_t total_liquid_spawned2; // Same as total_liquid_spawned? bool unknown12; From cd7c1e948a1e20519880380756906de7fb8f2c4c Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:47:17 +0200 Subject: [PATCH 15/23] fix lavaman's spawn logic in back layer --- src/game_api/rpc.cpp | 38 ++++++++++++++++++++++++++++++++++---- src/game_api/search.cpp | 23 ++++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index a570744e6..c4c5c9711 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1933,7 +1933,7 @@ void init_seeded(std::optional seed) void set_liquid_layer(LAYER l) { static std::array jumps; // jne (0F85) -> je (0F84) - static std::array layer_offsets; // 0x1300 -> 0x1308 + static std::array layer_offsets; // 0x1300 -> 0x1308 static std::array layer_byte; static uintptr_t jump2; if (jumps[0] == 0) @@ -1951,18 +1951,19 @@ void set_liquid_layer(LAYER l) if (addr == 0) return; + auto& mem = Memory::get(); layer_offsets[0] = get_address("spawn_liquid_layer"); - auto sound_stuff = get_address("liquid_stream_spawner"); + { + auto sound_stuff = get_address("liquid_stream_spawner"); if (sound_stuff == 0) return; - auto& mem = Memory::get(); auto last_offset = sound_stuff - mem.exe_address(); bool skip = true; for (uint8_t idx = 0; idx < 6; ++idx) { - last_offset = find_inst(mem.exe(), "\x48\x8B\x8A"sv, last_offset, last_offset + 0x170, "set_liquid_layer"); + last_offset = find_inst(mem.exe(), "\x48\x8B\x8A"sv, last_offset, last_offset + 0x170, "set_liquid_layer-sound stuff"); if (idx == 5 && skip) // skip one, same instruction but not layer related { idx = 4; @@ -1979,6 +1980,35 @@ void set_liquid_layer(LAYER l) layer_offsets[9] = get_address("olmec_impostor_spawn"); layer_offsets[10] = get_address("abzu_impostor_spawn"); + { + auto logic_magman_spawn = get_virtual_function_address(VTABLE_OFFSET::LOGIC_VOLCANA_RELATED, 1); + if (logic_magman_spawn == 0) + return; + + auto lookup_patterns = { + // in order + "\x48\x8B\x8D*\x13\x00\x00"sv, + "\x48\x03\xB7*\x13\x00\x00"sv, + "\x48\x8B\x89*\x13\x00\x00"sv, + "\x48\x03\x95*\x13\x00\x00"sv, + "\x48\x03\x95*\x13\x00\x00"sv, + "\x48\x03\x8D*\x13\x00\x00"sv, + "\x48\x8B\x8A*\x13\x00\x00"sv, + }; + auto current_offset = logic_magman_spawn; + uint8_t idx = 11; // next free index + for (auto& pattern : lookup_patterns) + { + current_offset = find_inst(mem.exe(), pattern, current_offset + 7, logic_magman_spawn + 0x764, "set_liquid_layer-volcana"); + if (current_offset == 0) + return; + + layer_offsets[idx++] = mem.at_exe(current_offset); + } + } + layer_offsets[18] = get_address("logic_volcana_gather_magman_spawn_locations"); + layer_offsets[19] = get_address("logic_volcana_gather_magman_spawn_locations2"); + for (auto addr : layer_offsets) if (addr == 0) return; diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index c08d8a356..0f10c8d57 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -2116,6 +2116,9 @@ std::unordered_map g_address_rules{ PatternCommandBuffer{} .from_exe_base(0x22e0d1d0) // TODO }, + // + // liquid layer stuff begin + // { // look into spawn entity function when spawninig activefloor // or set break point on write to the activefloors map in state.liquid_physics.activefloors @@ -2281,8 +2284,26 @@ std::unordered_map g_address_rules{ .get_virtual_function_address(VTABLE_OFFSET::MONS_ROBOT, (VIRT_FUNC)78) // process input .find_after_inst("84 C9"_gh) .at_exe(), - }, + { + // set bp on write to the pointer to the logic + // above you should see call to custom malloc and then this function in which we looking for layer offsets + "logic_volcana_gather_magman_spawn_locations"sv, + PatternCommandBuffer{} + .find_after_inst("89 D2 48 69 FE B0 02 00 00"_gh) + .at_exe(), + }, + { + "logic_volcana_gather_magman_spawn_locations2"sv, + PatternCommandBuffer{} + .get_address("logic_volcana_gather_magman_spawn_locations") + .find_inst("0F 28 D9"_gh) + .offset(-7) + .at_exe(), + }, + // + // liquid layer stuff end + // }; std::unordered_map g_cached_addresses; From 88dfdeb6210b1dde0968886554203d77b60ab1de Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 13 Apr 2024 20:40:48 +0200 Subject: [PATCH 16/23] fix underwater bubbles logic, fix documentation, update docs, expose variables in the `LogicUnderwaterBubbles` logic --- docs/game_data/spel2.lua | 29 +++++++++++++++++---- docs/src/includes/_globals.md | 26 +++++++++++++++++- docs/src/includes/_types.md | 11 +++++--- src/game_api/movable.hpp | 2 +- src/game_api/rpc.cpp | 11 +++++--- src/game_api/rpc.hpp | 2 +- src/game_api/script/lua_vm.cpp | 8 +++--- src/game_api/script/usertypes/logic_lua.cpp | 6 +++++ src/game_api/search.cpp | 13 +++++++-- src/game_api/state.cpp | 6 ++--- src/game_api/state_structs.hpp | 17 ++++++------ 11 files changed, 99 insertions(+), 32 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 1dead0de5..8672deebb 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1169,11 +1169,13 @@ function get_adventure_seed(run_start) end ---@return nil function set_adventure_seed(first, second) end ---Updates the floor collisions used by the liquids, set add to false to remove tile of collision, set to true to add one +---optional `layer` parameter to be used when liquid was moved to back layer using [set_liquid_layer](https://spelunky-fyi.github.io/overlunky/#set_liquid_layer) ---@param x number ---@param y number ---@param add boolean +---@param layer LAYER? ---@return nil -function update_liquid_collision_at(x, y, add) end +function update_liquid_collision_at(x, y, add, layer) end ---Disable all crust item spawns, returns whether they were already disabled before the call ---@param disable boolean ---@return boolean @@ -1385,6 +1387,20 @@ function play_adventure() end ---@param seed integer? ---@return nil function play_seeded(seed) end +---Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid +---This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or placing them directly in level files) +---Everything should be working more or less correctly (report on community discord if you find something unusual) +---@param l LAYER +---@return nil +function set_liquid_layer(l) end +---Attach liquid collision to entity by uid (this is what the push blocks use) +---Collision is based on the entity's hitbox, collision is removed when the entity is destroyed (bodies of killed entities will still have the collision) +---Use only for entities that can move around, (for static prefer [update_liquid_collision_at](https://spelunky-fyi.github.io/overlunky/#update_liquid_collision_at) ) +---If entity is in back layer and liquid in the front, there will be no collision created, also collision is not destroyed when entity changes layers, so you have to handle that yourself +---@param uid integer +---@param add boolean +---@return nil +function add_entity_to_liquid_collision(uid, add) end ---@return boolean function toast_visible() end ---@return boolean @@ -2640,7 +2656,7 @@ function Entity:destroy_recursive() end ---@field stun fun(self, framecount: integer): nil ---@field freeze fun(self, framecount: integer): nil ---@field light_on_fire fun(self, time: integer): nil @Does not damage entity - ---@field set_cursed fun(self, b: boolean): nil + ---@field set_cursed fun(self, b: boolean, effect: boolean?): nil @effect = true - plays the sound and spawn particle above entity ---@field drop fun(self, entity_to_drop: Entity): nil @Called when dropping or throwing ---@field pick_up fun(self, entity_to_pick_up: Entity): nil ---@field can_jump fun(self): boolean @Return true if the entity is allowed to jump, even midair. Return false and can't jump, except from ladders apparently. @@ -2680,8 +2696,8 @@ function Entity:destroy_recursive() end ---@field set_post_freeze fun(self, fun: fun(self: Movable, framecount: integer): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil freeze(Movable self, integer framecount)` ---@field set_pre_light_on_fire fun(self, fun: fun(self: Movable, time: integer): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool light_on_fire(Movable self, integer time)`
Virtual function docs:
Does not damage entity ---@field set_post_light_on_fire fun(self, fun: fun(self: Movable, time: integer): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil light_on_fire(Movable self, integer time)`
Virtual function docs:
Does not damage entity - ---@field set_pre_set_cursed fun(self, fun: fun(self: Movable, b: boolean): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool set_cursed(Movable self, boolean b)` - ---@field set_post_set_cursed fun(self, fun: fun(self: Movable, b: boolean): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil set_cursed(Movable self, boolean b)` + ---@field set_pre_set_cursed fun(self, fun: fun(self: Movable, b: boolean, effect: boolean): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool set_cursed(Movable self, boolean b, boolean effect)` + ---@field set_post_set_cursed fun(self, fun: fun(self: Movable, b: boolean, effect: boolean): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil set_cursed(Movable self, boolean b, boolean effect)` ---@field set_pre_web_collision fun(self, fun: fun(self: Movable): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool web_collision(Movable self)` ---@field set_post_web_collision fun(self, fun: fun(self: Movable): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil web_collision(Movable self)` ---@field set_pre_check_out_of_bounds fun(self, fun: fun(self: Movable): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool check_out_of_bounds(Movable self)` @@ -6275,7 +6291,7 @@ function Quad:is_point_inside(x, y, epsilon) end ---@field tun_star_challenge LogicStarChallenge ---@field tun_sun_challenge LogicSunChallenge ---@field magmaman_spawn LogicMagmamanSpawn - ---@field water_bubbles LogicUnderwaterBubbles @Only the bubbles that spawn from the floor
Even without it, entities moving in water still spawn bubbles + ---@field water_bubbles LogicUnderwaterBubbles @Only the bubbles that spawn from the floor (no border tiles, checks decoration flag), also spawn droplets falling from ceiling
Even without it, entities moving in water still spawn bubbles ---@field olmec_cutscene LogicOlmecCutscene ---@field tiamat_cutscene LogicTiamatCutscene ---@field apep_spawner LogicApepTrigger @Triggers and spawns Apep only in rooms set as ROOM_TEMPLATE.APEP @@ -6373,6 +6389,9 @@ function LogicMagmamanSpawn:remove_spawn(x, y) end function LogicMagmamanSpawn:remove_spawn(ms) end ---@class LogicUnderwaterBubbles : Logic + ---@field gravity_direction number @1.0 = normal, -1.0 = inversed, other values have undefined behavior
this value basically have to be the same as return from `ThemeInfo:get_liquid_gravity()` + ---@field droplets_spawn_chance integer @It's inverse chance, so the lower the number the higher the chance, values below 10 may crash the game + ---@field droplets_enabled boolean @Enable/disable spawn of ENT_TYPE.FX_WATER_DROP from ceiling (or ground if liquid gravity is inverse) ---@class LogicOlmecCutscene : Logic ---@field fx_olmecpart_large Entity diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index aacf579b2..3275ae68a 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -457,6 +457,18 @@ Activate custom variables for speed and distance in the `ITEM_SPARK` note: because those the variables are custom and game does not initiate them, you need to do it yourself for each spark, recommending `set_post_entity_spawn` default game values are: speed = -0.015, distance = 3.0 +### add_entity_to_liquid_collision + + +> Search script examples for [add_entity_to_liquid_collision](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_entity_to_liquid_collision) + +#### nil add_entity_to_liquid_collision(int uid, bool add) + +Attach liquid collision to entity by uid (this is what the push blocks use) +Collision is based on the entity's hitbox, collision is removed when the entity is destroyed (bodies of killed entities will still have the collision) +Use only for entities that can move around, (for static prefer [update_liquid_collision_at](#update_liquid_collision_at) ) +If entity is in back layer and liquid in the front, there will be no collision created, also collision is not destroyed when entity changes layers, so you have to handle that yourself + ### apply_entity_db @@ -1991,6 +2003,17 @@ Set the value for the specified config Setting to false disables all player logic in [SCREEN](#SCREEN).LEVEL, mainly the death screen from popping up if all players are dead or missing, but also shop camera zoom and some other small things. +### set_liquid_layer + + +> Search script examples for [set_liquid_layer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_liquid_layer) + +#### nil set_liquid_layer([LAYER](#LAYER) l) + +Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid +This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or placing them directly in level files) +Everything should be working more or less correctly (report on community discord if you find something unusual) + ### set_seed @@ -2142,9 +2165,10 @@ Gets line1_A, intersection point and line2_B and calls the 3 parameter version o > Search script examples for [update_liquid_collision_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=update_liquid_collision_at) -#### nil update_liquid_collision_at(float x, float y, bool add) +#### nil update_liquid_collision_at(float x, float y, bool add, optional<[LAYER](#LAYER)> layer = nullopt) Updates the floor collisions used by the liquids, set add to false to remove tile of collision, set to true to add one +optional `layer` parameter to be used when liquid was moved to back layer using [set_liquid_layer](#set_liquid_layer) ### update_state diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index f9371bede..c72ff9ef3 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -1576,7 +1576,7 @@ Type | Name | Description [LogicStarChallenge](#LogicStarChallenge) | [tun_star_challenge](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=tun_star_challenge) | [LogicSunChallenge](#LogicSunChallenge) | [tun_sun_challenge](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=tun_sun_challenge) | [LogicMagmamanSpawn](#LogicMagmamanSpawn) | [magmaman_spawn](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=magmaman_spawn) | -[LogicUnderwaterBubbles](#LogicUnderwaterBubbles) | [water_bubbles](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=water_bubbles) | Only the bubbles that spawn from the floor
Even without it, entities moving in water still spawn bubbles +[LogicUnderwaterBubbles](#LogicUnderwaterBubbles) | [water_bubbles](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=water_bubbles) | Only the bubbles that spawn from the floor (no border tiles, checks decoration flag), also spawn droplets falling from ceiling
Even without it, entities moving in water still spawn bubbles [LogicOlmecCutscene](#LogicOlmecCutscene) | [olmec_cutscene](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=olmec_cutscene) | [LogicTiamatCutscene](#LogicTiamatCutscene) | [tiamat_cutscene](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=tiamat_cutscene) | [LogicApepTrigger](#LogicApepTrigger) | [apep_spawner](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=apep_spawner) | Triggers and spawns Apep only in rooms set as [ROOM_TEMPLATE](#ROOM_TEMPLATE).APEP @@ -1718,6 +1718,9 @@ Derived from [Logic](#Logic) Type | Name | Description ---- | ---- | ----------- +float | [gravity_direction](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=gravity_direction) | 1.0 = normal, -1.0 = inversed, other values have undefined behavior
this value basically have to be the same as return from `ThemeInfo:get_liquid_gravity()` +int | [droplets_spawn_chance](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=droplets_spawn_chance) | It's inverse chance, so the lower the number the higher the chance, values below 10 may crash the game +bool | [droplets_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=droplets_enabled) | Enable/disable spawn of [ENT_TYPE](#ENT_TYPE).FX_WATER_DROP from ceiling (or ground if liquid gravity is inverse) ## Online types @@ -6864,7 +6867,7 @@ int | [price](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=price) | nil | [stun(int framecount)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=stun) | nil | [freeze(int framecount)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=freeze) | nil | [light_on_fire(int time)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=light_on_fire) | Does not damage entity -nil | [set_cursed(bool b)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_cursed) | +nil | [set_cursed(bool b, optional effect)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_cursed) | effect = true - plays the sound and spawn particle above entity nil | [drop(Entity entity_to_drop)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=drop) | Called when dropping or throwing nil | [pick_up(Entity entity_to_pick_up)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=pick_up) | bool | [can_jump()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=can_jump) | Return true if the entity is allowed to jump, even midair. Return false and can't jump, except from ladders apparently. @@ -6907,8 +6910,8 @@ nil | [clear_virtual(CallbackId callback_id)](https://github.com/spelunky-fyi/ov [CallbackId](#Aliases) | [set_post_freeze(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_freeze) | Hooks after the virtual function.
The callback signature is `nil freeze(Movable self, int framecount)` [CallbackId](#Aliases) | [set_pre_light_on_fire(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_light_on_fire) | Hooks before the virtual function.
The callback signature is `bool light_on_fire(Movable self, int time)`
Virtual function docs:
Does not damage entity [CallbackId](#Aliases) | [set_post_light_on_fire(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_light_on_fire) | Hooks after the virtual function.
The callback signature is `nil light_on_fire(Movable self, int time)`
Virtual function docs:
Does not damage entity -[CallbackId](#Aliases) | [set_pre_set_cursed(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_set_cursed) | Hooks before the virtual function.
The callback signature is `bool set_cursed(Movable self, bool b)` -[CallbackId](#Aliases) | [set_post_set_cursed(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_set_cursed) | Hooks after the virtual function.
The callback signature is `nil set_cursed(Movable self, bool b)` +[CallbackId](#Aliases) | [set_pre_set_cursed(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_set_cursed) | Hooks before the virtual function.
The callback signature is `bool set_cursed(Movable self, bool b, bool effect)` +[CallbackId](#Aliases) | [set_post_set_cursed(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_set_cursed) | Hooks after the virtual function.
The callback signature is `nil set_cursed(Movable self, bool b, bool effect)` [CallbackId](#Aliases) | [set_pre_web_collision(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_web_collision) | Hooks before the virtual function.
The callback signature is `bool web_collision(Movable self)` [CallbackId](#Aliases) | [set_post_web_collision(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_web_collision) | Hooks after the virtual function.
The callback signature is `nil web_collision(Movable self)` [CallbackId](#Aliases) | [set_pre_check_out_of_bounds(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_check_out_of_bounds) | Hooks before the virtual function.
The callback signature is `bool check_out_of_bounds(Movable self)` diff --git a/src/game_api/movable.hpp b/src/game_api/movable.hpp index 232b87b97..bf9a49d2c 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -180,7 +180,7 @@ class Movable : public Entity /// Does not damage entity virtual void light_on_fire(uint8_t time) = 0; // 53 - virtual void set_cursed(bool b, bool other) = 0; // 54 + virtual void set_cursed(bool b, bool effect) = 0; // 54 virtual void on_spiderweb_collision() = 0; // 55 virtual void set_last_owner_uid_b127(Entity* owner) = 0; // 56, assigns player as last_owner_uid and also manipulates movable.b127 virtual uint32_t get_last_owner_uid() = 0; // 57, for players, it checks !stunned && !frozen && !cursed && !has_overlay; for others: just returns last_owner_uid diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index c4c5c9711..55966fdd5 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1325,12 +1325,11 @@ void update_liquid_collision_at(float x, float y, bool add, std::optional RemoveLiquidCollision_fun(state->liquid_physics, static_cast(std::round(x)), static_cast(std::round(y)), actual_layer); } -void add_entity_to_liquid_collision(uint32_t uid, bool add, std::optional layer) +void add_entity_to_liquid_collision(uint32_t uid, bool add) { using AddEntityLiquidCollision = void(LiquidPhysics*, Entity*, uint8_t); static AddEntityLiquidCollision* add_entity_liquid_collision = (AddEntityLiquidCollision*)get_address("add_movable_to_liquid_collision_map"); auto state = get_state_ptr(); - uint8_t actual_layer = enum_to_layer(layer.value_or(LAYER::FRONT)); auto entity = get_entity_ptr(uid); if (!entity) return; @@ -1344,7 +1343,7 @@ void add_entity_to_liquid_collision(uint32_t uid, bool add, std::optional // if it already exists we can't add it again, since it will create the collision struct anyway and just overwrite the pointer to it in the map // the actual collision struct is held somewhere else, unrelated to this map if (add && it == map->end()) - add_entity_liquid_collision(state->liquid_physics, entity, actual_layer); + add_entity_liquid_collision(state->liquid_physics, entity, entity->layer); else if (!add && it != map->end()) { // very illegal, don't do this, we can because we're professionals xd @@ -1936,6 +1935,7 @@ void set_liquid_layer(LAYER l) static std::array layer_offsets; // 0x1300 -> 0x1308 static std::array layer_byte; static uintptr_t jump2; + static uintptr_t jump3; if (jumps[0] == 0) { layer_byte[0] = get_address("check_if_collides_with_liquid_layer"); @@ -2014,7 +2014,8 @@ void set_liquid_layer(LAYER l) return; jump2 = get_address("robot_layer_check"); - if (jump2 == 0) + jump3 = get_address("logic_underwater_bubbles_loop_check"); + if (jump2 == 0 || jump3 == 0) return; jumps[0] = get_address("layer_check_in_add_liquid_collision"); @@ -2036,6 +2037,7 @@ void set_liquid_layer(LAYER l) uint8_t offset_ending = actual_layer == 0 ? 0 : 8; uint8_t jump_oppcode = actual_layer == 0 ? 0x85 : 0x84; uint8_t jump_oppcode2 = actual_layer == 0 ? 0x75 : 0x74; + uint8_t jump_oppcode2_inverse = actual_layer == 0 ? 0x74 : 0x75; for (auto addr : jumps) write_mem_prot(addr + 1, jump_oppcode, true); @@ -2047,4 +2049,5 @@ void set_liquid_layer(LAYER l) write_mem_prot(addr, actual_layer, true); write_mem_prot(jump2, jump_oppcode2, true); + write_mem_prot(jump3, jump_oppcode2_inverse, true); } diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index d8ffbd673..07ca33820 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -102,7 +102,7 @@ void change_poison_timer(int16_t frames); void set_adventure_seed(int64_t first, int64_t second); std::pair get_adventure_seed(std::optional run_start); void update_liquid_collision_at(float x, float y, bool add, std::optional layer = std::nullopt); -void add_entity_to_liquid_collision(uint32_t uid, bool add, std::optional layer); +void add_entity_to_liquid_collision(uint32_t uid, bool add); bool disable_floor_embeds(bool disable); void set_cursepot_ghost_enabled(bool enable); void game_log(std::string message); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 669e0bbce..8c9fc9e43 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2265,12 +2265,14 @@ end lua["play_seeded"] = init_seeded; /// Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid + /// This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or placing them directly in level files) + /// Everything should be working more or less correctly (report on community discord if you find something unusual) lua["set_liquid_layer"] = set_liquid_layer; /// Attach liquid collision to entity by uid (this is what the push blocks use) - /// Collision is based on the entity's hitbox, collision in removed when the entity is destroyed (bodies of killed entities will still have the collision) - /// Use only for entities that can move around, (for static entities use [update_liquid_collision_at](#update_liquid_collision_at) ) - /// optional `layer` parameter to be used when liquid was moved to back layer using [set_liquid_layer](#set_liquid_layer) + /// Collision is based on the entity's hitbox, collision is removed when the entity is destroyed (bodies of killed entities will still have the collision) + /// Use only for entities that can move around, (for static prefer [update_liquid_collision_at](#update_liquid_collision_at) ) + /// If entity is in back layer and liquid in the front, there will be no collision created, also collision is not destroyed when entity changes layers, so you have to handle that yourself lua["add_entity_to_liquid_collision"] = add_entity_to_liquid_collision; 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/game_api/script/usertypes/logic_lua.cpp b/src/game_api/script/usertypes/logic_lua.cpp index d5fd293ef..673ff655c 100644 --- a/src/game_api/script/usertypes/logic_lua.cpp +++ b/src/game_api/script/usertypes/logic_lua.cpp @@ -312,6 +312,12 @@ void register_usertypes(sol::state& lua) /// Used in LogicList lua.new_usertype( "LogicUnderwaterBubbles", + "gravity_direction", + &LogicUnderwaterBubbles::gravity_direction, + "droplets_spawn_chance", + &LogicUnderwaterBubbles::droplets_spawn_chance, + "droplets_enabled", + &LogicUnderwaterBubbles::droplets_enabled, sol::base_classes, sol::bases()); /// Used in LogicList diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 0f10c8d57..886397579 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -2288,19 +2288,28 @@ std::unordered_map g_address_rules{ { // set bp on write to the pointer to the logic // above you should see call to custom malloc and then this function in which we looking for layer offsets - "logic_volcana_gather_magman_spawn_locations"sv, + "logic_volcana_gather_magman_spawn_locations"sv, // layer offset PatternCommandBuffer{} .find_after_inst("89 D2 48 69 FE B0 02 00 00"_gh) .at_exe(), }, { - "logic_volcana_gather_magman_spawn_locations2"sv, + "logic_volcana_gather_magman_spawn_locations2"sv, // layer offset PatternCommandBuffer{} .get_address("logic_volcana_gather_magman_spawn_locations") .find_inst("0F 28 D9"_gh) .offset(-7) .at_exe(), }, + { + // the whole function runs twice (for each layer), there is variable on stack that is set at the end of looking thru layer 0 + // this check skips spawn of the bubbles in the back layer, but still rolls for the droplets + "logic_underwater_bubbles_loop_check"sv, // different type of jump, also inversed compared to robot_layer_check + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::LOGIC_WATER_RELATED, (VIRT_FUNC)1) + .find_after_inst("F6 45 F4 01"_gh) + .at_exe(), + }, // // liquid layer stuff end // diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index ba39f8c91..6b4df32a1 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -1040,9 +1040,9 @@ Logic* LogicList::start_logic(LOGIC idx) if (idx == LOGIC::WATER_BUBBLES) { auto proper_type = (LogicUnderwaterBubbles*)new_logic; - proper_type->unknown1 = 1.0f; - proper_type->unknown2 = 1000; - proper_type->unknown3 = true; + proper_type->gravity_direction = 1.0f; + proper_type->droplets_spawn_chance = 1000; + proper_type->droplets_enabled = true; } else if (idx == LOGIC::OUROBOROS) { diff --git a/src/game_api/state_structs.hpp b/src/game_api/state_structs.hpp index 5db7df841..0f938d247 100644 --- a/src/game_api/state_structs.hpp +++ b/src/game_api/state_structs.hpp @@ -604,13 +604,14 @@ class LogicArenaLooseBombs : public Logic class LogicUnderwaterBubbles : public Logic { public: - // no idea what does are, messing with them can crash - float unknown1; // default: 1.0, excludes liquid from spawning the bubbles by y level from the top to bottom - // is treated like number (calculations to get the right grid entity level) - // it's more like a value in rooms than y coordinates - - int16_t unknown2; // default: 1000 - bool unknown3; // default: 1 or 0 + /// 1.0 = normal, -1.0 = inversed, other values have undefined behavior + /// this value basically have to be the same as return from `ThemeInfo:get_liquid_gravity()` + float gravity_direction; + + /// It's inverse chance, so the lower the number the higher the chance, values below 10 may crash the game + int16_t droplets_spawn_chance; + /// Enable/disable spawn of ENT_TYPE.FX_WATER_DROP from ceiling (or ground if liquid gravity is inverse) + bool droplets_enabled; }; class LogicTunPreChallenge : public Logic @@ -660,7 +661,7 @@ struct LogicList LogicStarChallenge* tun_star_challenge; LogicSunChallenge* tun_sun_challenge; LogicMagmamanSpawn* magmaman_spawn; - /// Only the bubbles that spawn from the floor + /// Only the bubbles that spawn from the floor (no border tiles, checks decoration flag), also spawn droplets falling from ceiling /// Even without it, entities moving in water still spawn bubbles LogicUnderwaterBubbles* water_bubbles; LogicOlmecCutscene* olmec_cutscene; From adfde2098cf1512b1598734b10beb12337572dc6 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 13 Apr 2024 20:43:07 +0200 Subject: [PATCH 17/23] clang format changes --- 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 bf9a49d2c..e69c27775 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -180,7 +180,7 @@ class Movable : public Entity /// Does not damage entity virtual void light_on_fire(uint8_t time) = 0; // 53 - virtual void set_cursed(bool b, bool effect) = 0; // 54 + virtual void set_cursed(bool b, bool effect) = 0; // 54 virtual void on_spiderweb_collision() = 0; // 55 virtual void set_last_owner_uid_b127(Entity* owner) = 0; // 56, assigns player as last_owner_uid and also manipulates movable.b127 virtual uint32_t get_last_owner_uid() = 0; // 57, for players, it checks !stunned && !frozen && !cursed && !has_overlay; for others: just returns last_owner_uid From 0a00b657701bfb97030f0118758a0d1d86101e03 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 21 Apr 2024 14:55:14 +0200 Subject: [PATCH 18/23] force patterns to be writen as string_view to avoid null termination --- src/game_api/search.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 886397579..8d6d14c48 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -246,29 +246,41 @@ class PatternCommandBuffer commands.push_back({CommandType::GetVirtualFunctionAddress, {.get_vfunc_addr_args = {.table_offset = table_offset, .function_index = function_index}}}); return *this; } - PatternCommandBuffer& find_inst(std::string_view pattern) + template + requires std::is_same_v + PatternCommandBuffer& find_inst(T pattern) { commands.push_back({CommandType::FindInst, {.find_inst_args = {pattern}}}); return *this; } - PatternCommandBuffer& find_after_inst(std::string_view pattern) + template + requires std::is_same_v + PatternCommandBuffer& find_after_inst(T pattern) { return find_inst(pattern).offset(pattern.size()); } - PatternCommandBuffer& find_next_inst(std::string_view pattern) + template + requires std::is_same_v + PatternCommandBuffer& find_next_inst(T pattern) { return offset(0x1).find_inst(pattern); } - PatternCommandBuffer& find_inst_in_range(std::string_view pattern, size_t range) + template + requires std::is_same_v + PatternCommandBuffer& find_inst_in_range(T pattern, size_t range) { commands.push_back({CommandType::FindInst, {.find_inst_args = {.pattern = pattern, .range = range}}}); return *this; } - PatternCommandBuffer& find_after_inst_in_range(std::string_view pattern, size_t range) + template + requires std::is_same_v + PatternCommandBuffer& find_after_inst_in_range(T pattern, size_t range) { return find_inst_in_range(pattern, range).offset(pattern.size()); } - PatternCommandBuffer& find_next_inst_in_range(std::string_view pattern, size_t range) + template + requires std::is_same_v + PatternCommandBuffer& find_next_inst_in_range(T pattern, size_t range) { return offset(0x1).find_inst_in_range(pattern, range); } From 9d0c0f7b7653cee41d85b012fffd7dcda3fa1b6f Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 21 Apr 2024 15:55:17 +0200 Subject: [PATCH 19/23] some self review stuff --- docs/game_data/spel2.lua | 2 +- docs/src/includes/_globals.md | 2 +- src/game_api/rpc.cpp | 4 ++-- src/game_api/script/lua_vm.cpp | 2 +- src/game_api/search.cpp | 14 ++++++++++---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 8672deebb..efc1db87e 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1388,7 +1388,7 @@ function play_adventure() end ---@return nil function play_seeded(seed) end ---Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid ----This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or placing them directly in level files) +---This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or place them directly in level files) ---Everything should be working more or less correctly (report on community discord if you find something unusual) ---@param l LAYER ---@return nil diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 3275ae68a..f18f7fa93 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -2011,7 +2011,7 @@ Setting to false disables all player logic in [SCREEN](#SCREEN).LEVEL, mainly th #### nil set_liquid_layer([LAYER](#LAYER) l) Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid -This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or placing them directly in level files) +This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or place them directly in level files) Everything should be working more or less correctly (report on community discord if you find something unusual) ### set_seed diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 55966fdd5..6b6f8faa0 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1349,8 +1349,8 @@ void add_entity_to_liquid_collision(uint32_t uid, bool add) // very illegal, don't do this, we can because we're professionals xd // game loops thru the map and checks if uid still exists, if not, it removes the collision // which is some bigger struct held in some weird container, and the function is doing other stuff, so this is the easiest way besides killing the entity - auto test = const_cast(&it->first); - *test = ~0u; + auto key = const_cast(&it->first); + *key = ~0u; } } diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 8c9fc9e43..3c88c0e74 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2265,7 +2265,7 @@ end lua["play_seeded"] = init_seeded; /// Change layer at which the liquid spawns in, THIS FUNCTION NEEDS TO BE CALLED BEFORE THE LEVEL IS BUILD, otherwise collisions and other stuff will be wrong for the newly spawned liquid - /// This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or placing them directly in level files) + /// This sadly also makes lavamanders extinct, since the logic for their spawn is harcoded to front layer with bunch of other unrelated stuff (you can still spawn them with script or place them directly in level files) /// Everything should be working more or less correctly (report on community discord if you find something unusual) lua["set_liquid_layer"] = set_liquid_layer; diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 8d6d14c48..d9aca8745 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -2128,9 +2128,6 @@ std::unordered_map g_address_rules{ PatternCommandBuffer{} .from_exe_base(0x22e0d1d0) // TODO }, - // - // liquid layer stuff begin - // { // look into spawn entity function when spawninig activefloor // or set break point on write to the activefloors map in state.liquid_physics.activefloors @@ -2140,6 +2137,9 @@ std::unordered_map g_address_rules{ .at_exe() .function_start(), }, + // + // liquid layer stuff begin + // { "spawn_liquid_layer"sv, // layer offset PatternCommandBuffer{} @@ -2235,7 +2235,7 @@ std::unordered_map g_address_rules{ }, { // go into virtual Movable:sprint_factor for player, set bp, execute til return - // you will end up towards the end of a function, there is another call, go into it a look for comparison with offset +0xA0 (entity.layer) + // you will end up towards the end of a function, there is another call, go into it and look for comparison with offset +0xA0 (entity.layer) "movement_calculations_layer_check"sv, // layer byte or bool PatternCommandBuffer{} .find_after_inst("F3 0F 58 4A 40 0F 2E 0D"_gh) @@ -2321,7 +2321,13 @@ std::unordered_map g_address_rules{ .get_virtual_function_address(VTABLE_OFFSET::LOGIC_WATER_RELATED, (VIRT_FUNC)1) .find_after_inst("F6 45 F4 01"_gh) .at_exe(), + // there is also layer offset at 22B74A1F, no idea how to trigger that part of the code }, + /* Other potential liquid lookups: + * 228BC562 - lookup with unknown mask + * 228BCB42 - same as above, runs all the time, so potentially unrelated + * 228BD4A5 - same as above + */ // // liquid layer stuff end // From 5e9488d6ced7ebfce015606d4044d84106fba245 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 5 May 2024 16:33:50 +0200 Subject: [PATCH 20/23] correct the layer size (the extra stuff was just end buckets for the maps) --- src/game_api/layer.hpp | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/src/game_api/layer.hpp b/src/game_api/layer.hpp index 1c89a74b2..b79101f64 100644 --- a/src/game_api/layer.hpp +++ b/src/game_api/layer.hpp @@ -162,38 +162,11 @@ struct Layer EntityList expired_entities; bool is_layer_loading; bool unknown14; - uint8_t unknown15; - uint8_t unknown16; - uint32_t unknown17; - uint32_t unknown18; - uint32_t unknown19; - size_t entity_items_begin; // begin of the memory that holds the items of entities, maybe vector? - size_t unknown21; - size_t unknown22; - bool unknown23; - bool layer_freeze; // locking mechanism? - uint8_t unknown25; - uint8_t unknown26; - uint32_t unknown27; - uint64_t unknown28; - uint64_t unknown29; - uint64_t unknown30; - uint64_t unknown31; - uint64_t unknown32; - uint32_t unknown33; - uint32_t unknown34; - size_t unknown35; // maybe vector? - size_t unknown36; - size_t unknown37; - bool unknown38; - bool unknown39; - uint8_t unknown40; - uint8_t unknown41; - uint32_t unknown42; - uint64_t unknown43; - uint64_t unknown44; - uint64_t unknown45; - uint64_t unknown46; // next layer below + + // probably just padding + // uint8_t unknown15; + // uint8_t unknown16; + // uint32_t unknown17; Entity* spawn_entity(ENT_TYPE id, float x, float y, bool screen, float vx, float vy, bool snap); From 5e8b796f80a37b2581deaf707413e6ee1fe5688f Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Tue, 7 May 2024 23:49:46 +0200 Subject: [PATCH 21/23] rephrase the description in `Generator` class --- src/game_api/entities_floors.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game_api/entities_floors.hpp b/src/game_api/entities_floors.hpp index 01ce8b3d9..89a25dcbc 100644 --- a/src/game_api/entities_floors.hpp +++ b/src/game_api/entities_floors.hpp @@ -285,9 +285,9 @@ class Generator : public Floor int32_t spawned_uid; uint16_t set_timer; uint16_t timer; - /// works only for star challenge + /// Applicable only for ENT_TYPE`.FLOOR_SUNCHALLENGE_GENERATOR` uint8_t start_counter; - /// works only for star challenge + /// Applicable only for ENT_TYPE`.FLOOR_SUNCHALLENGE_GENERATOR` bool on_off; virtual void randomize_timer() = 0; // called after it spawns entity and it's "ready" (have proper flags set etc.) From 040a187fad7e787247e754eae8d17f7781dfe5c9 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Wed, 8 May 2024 00:01:21 +0200 Subject: [PATCH 22/23] add `get_liquid_layer`, update docs --- docs/game_data/spel2.lua | 7 +++++-- docs/src/includes/_globals.md | 9 +++++++++ docs/src/includes/_types.md | 4 ++-- src/game_api/rpc.cpp | 6 ++++++ src/game_api/rpc.hpp | 1 + src/game_api/script/lua_vm.cpp | 3 +++ 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index efc1db87e..bff6c70fb 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1393,6 +1393,9 @@ function play_seeded(seed) end ---@param l LAYER ---@return nil function set_liquid_layer(l) end +---Get the current layer that the liquid is spawn in. Related function [set_liquid_layer](https://spelunky-fyi.github.io/overlunky/#set_liquid_layer) +---@return integer +function get_liquid_layer() end ---Attach liquid collision to entity by uid (this is what the push blocks use) ---Collision is based on the entity's hitbox, collision is removed when the entity is destroyed (bodies of killed entities will still have the collision) ---Use only for entities that can move around, (for static prefer [update_liquid_collision_at](https://spelunky-fyi.github.io/overlunky/#update_liquid_collision_at) ) @@ -2936,8 +2939,8 @@ function Movable:generic_update_world(move, sprint_factor, disable_gravity, on_r ---@field spawned_uid integer ---@field set_timer integer ---@field timer integer - ---@field start_counter integer @works only for star challenge - ---@field on_off boolean @works only for star challenge + ---@field start_counter integer @Applicable only for ENT_TYPE`.FLOOR_SUNCHALLENGE_GENERATOR` + ---@field on_off boolean @Applicable only for ENT_TYPE`.FLOOR_SUNCHALLENGE_GENERATOR` ---@class SlidingWallCeiling : Floor ---@field attached_piece Entity diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index f18f7fa93..7a7463b8f 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -1518,6 +1518,15 @@ Get your sanitized script id to be used in import. Gets the value for the specified config +### get_liquid_layer + + +> Search script examples for [get_liquid_layer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_liquid_layer) + +#### int get_liquid_layer() + +Get the current layer that the liquid is spawn in. Related function [set_liquid_layer](#set_liquid_layer) + ### get_local_prng diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index c72ff9ef3..cffe47d80 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -3860,8 +3860,8 @@ Type | Name | Description int | [spawned_uid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawned_uid) | int | [set_timer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_timer) | int | [timer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=timer) | -int | [start_counter](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=start_counter) | works only for star challenge -bool | [on_off](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=on_off) | works only for star challenge +int | [start_counter](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=start_counter) | Applicable only for [ENT_TYPE](#ENT_TYPE)`.FLOOR_SUNCHALLENGE_GENERATOR` +bool | [on_off](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=on_off) | Applicable only for [ENT_TYPE](#ENT_TYPE)`.FLOOR_SUNCHALLENGE_GENERATOR` ### HorizontalForceField diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 6b6f8faa0..4d5dfcb8e 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -2051,3 +2051,9 @@ void set_liquid_layer(LAYER l) write_mem_prot(jump2, jump_oppcode2, true); write_mem_prot(jump3, jump_oppcode2_inverse, true); } + +uint8_t get_liquid_layer() +{ + static auto addr = get_address("check_if_collides_with_liquid_layer"); + return memory_read(addr); +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index 07ca33820..78001ae71 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -139,3 +139,4 @@ float get_speedhack(); void init_adventure(); void init_seeded(std::optional seed); void set_liquid_layer(LAYER l); +uint8_t get_liquid_layer(); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 3c88c0e74..738d3e414 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -2269,6 +2269,9 @@ end /// Everything should be working more or less correctly (report on community discord if you find something unusual) lua["set_liquid_layer"] = set_liquid_layer; + /// Get the current layer that the liquid is spawn in. Related function [set_liquid_layer](#set_liquid_layer) + lua["get_liquid_layer"] = get_liquid_layer; + /// Attach liquid collision to entity by uid (this is what the push blocks use) /// Collision is based on the entity's hitbox, collision is removed when the entity is destroyed (bodies of killed entities will still have the collision) /// Use only for entities that can move around, (for static prefer [update_liquid_collision_at](#update_liquid_collision_at) ) From cdad11be835eb0dfbd1fe1f8df510ef314e9268b Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Thu, 16 May 2024 18:44:16 +0200 Subject: [PATCH 23/23] add variable to screen menu --- docs/game_data/spel2.lua | 1 + docs/src/includes/_types.md | 1 + src/game_api/screen.hpp | 3 ++- src/game_api/script/usertypes/screen_lua.cpp | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index bff6c70fb..1c9c8f07c 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -5714,6 +5714,7 @@ function Quad:is_point_inside(x, y, epsilon) end ---@field scroll_text STRINGID ---@field shake_offset_x number ---@field shake_offset_y number + ---@field loaded_once boolean @Set to true when going from title to menu screen for the first time, makes sure the animation play once ---@class ScreenOptions : Screen ---@field down boolean diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index cffe47d80..614eac03c 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -2496,6 +2496,7 @@ float | [play_scroll_descend_timer](https://github.com/spelunky-fyi/overlunky/se [STRINGID](#Aliases) | [scroll_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=scroll_text) | float | [shake_offset_x](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=shake_offset_x) | float | [shake_offset_y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=shake_offset_y) | +bool | [loaded_once](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=loaded_once) | Set to true when going from title to menu screen for the first time, makes sure the animation play once ### ScreenOnlineLoading diff --git a/src/game_api/screen.hpp b/src/game_api/screen.hpp index 3422f2b81..61f2abf34 100644 --- a/src/game_api/screen.hpp +++ b/src/game_api/screen.hpp @@ -249,7 +249,8 @@ class ScreenMenu : public Screen // ID: 4 STRINGID scroll_text; float shake_offset_x; float shake_offset_y; - bool unknown30; + /// Set to true when going from title to menu screen for the first time, makes sure the animation play once + bool loaded_once; // maybe two more 32bit values? hard to tell }; diff --git a/src/game_api/script/usertypes/screen_lua.cpp b/src/game_api/script/usertypes/screen_lua.cpp index f6ed822b2..2861f241f 100644 --- a/src/game_api/script/usertypes/screen_lua.cpp +++ b/src/game_api/script/usertypes/screen_lua.cpp @@ -227,6 +227,7 @@ void register_usertypes(sol::state& lua) screenmenu_type["scroll_text"] = &ScreenMenu::scroll_text; screenmenu_type["shake_offset_x"] = &ScreenMenu::shake_offset_x; screenmenu_type["shake_offset_y"] = &ScreenMenu::shake_offset_y; + screenmenu_type["loaded_once"] = &ScreenMenu::loaded_once; auto screenoptions_type = lua.new_usertype("ScreenOptions", sol::base_classes, sol::bases()); screenoptions_type["down"] = &ScreenOptions::down;