diff --git a/.github/workflows/continous_integration.yml b/.github/workflows/continous_integration.yml index d296a2b54..9e7d69445 100644 --- a/.github/workflows/continous_integration.yml +++ b/.github/workflows/continous_integration.yml @@ -37,7 +37,7 @@ jobs: run: | mkdir build cd build - cmake .. -A x64 -T ${{ matrix.toolset }} ${{ matrix.additional_opts }} + cmake .. -Wno-dev -A x64 -T ${{ matrix.toolset }} ${{ matrix.additional_opts }} - name: Build run: | diff --git a/.gitmodules b/.gitmodules index 56a582c76..9b7a0bd8e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "docs/slate"] path = docs/slate url = https://github.com/spelunky-fyi/slate +[submodule "src/neo"] + path = src/neo + url = https://github.com/vector-of-bool/neo-fun.git diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index db97e9298..d2ef48ea0 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1416,10 +1416,13 @@ function mouse_position() end ---- Note: `gamepad` is basically [XINPUT_GAMEPAD](https://docs.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_gamepad) but variables are renamed and values are normalized to -1.0..1.0 range. ---@return ImGuiIO function get_io() end +---Force the LUT texture for the given layer (or both) until it is reset +---Pass `nil` in the first parameter to reset ---@param texture_id TEXTURE? ---@param layer LAYER ---@return nil function set_lut(texture_id, layer) end +---Same as `set_lut(nil, layer)` ---@param layer LAYER ---@return nil function reset_lut(layer) end @@ -3735,6 +3738,18 @@ local function MovableBehavior_get_state_id(self) end ---@field y number ---@field offset_x number ---@field offset_y number + ---@field emitted_particles Particle[] + +---@class Particle + ---@field x number + ---@field y number + ---@field velocityx number + ---@field velocityy number + ---@field color uColor + ---@field width number + ---@field height number + ---@field lifetime integer + ---@field max_lifetime integer ---@class ThemeInfo ---@field sub_theme ThemeInfo @@ -4230,13 +4245,19 @@ local function VanillaRenderContext_draw_world_texture(self, texture_id, source, ---@field top number ---@field overlaps_with fun(self, other: AABB): boolean ---@field abs fun(self): AABB - ---@field extrude fun(self, amount: number): AABB + ---@field extrude AABB_extrude ---@field offset fun(self, off_x: number, off_y: number): AABB ---@field area fun(self): number ---@field center fun(self): number, number ---@field width fun(self): number ---@field height fun(self): number +---@class AABB_extrude +---@param amount_x number +---@param amount_y number +---@overload fun(self, amount: number): AABB +local function AABB_extrude(self, amount_x, amount_y) end + ---@class Quad ---@field bottom_left_x number ---@field bottom_left_y number diff --git a/docs/generate.py b/docs/generate.py index f714595cb..9a9be5190 100644 --- a/docs/generate.py +++ b/docs/generate.py @@ -34,6 +34,7 @@ "constexpr": "", "static": "", "variadic_args va": "int, int...", + "EmittedParticlesInfo": "array", } @@ -133,7 +134,7 @@ def print_af(lf, af): def print_lf(lf): comments = lf["comment"] - if comments and comments[0] == "NoDoc": + if comments and "NoDoc" in comments[0]: return name = lf["name"] if name in printed_funcs: @@ -441,7 +442,7 @@ def print_lf(lf): if len(ps.rpcfunc(lf["cpp"])): print_lf(lf) elif not (lf["name"].startswith("on_") or lf["name"] in ps.not_functions): - if lf["comment"] and lf["comment"][0] == "NoDoc": + if lf["comment"] and "NoDoc" in lf["comment"][0]: continue m = re.search(r"\(([^\{]*)\)\s*->\s*([^\{]*)", lf["cpp"]) m2 = re.search(r"\(([^\{]*)\)", lf["cpp"]) @@ -486,7 +487,7 @@ def print_lf(lf): if len(ps.rpcfunc(lf["cpp"])): print_lf(lf) elif not (lf["name"].startswith("on_") or lf["name"] in ps.not_functions): - if lf["comment"] and lf["comment"][0] == "NoDoc": + if lf["comment"] and "NoDoc" in lf["comment"][0]: continue m = re.search(r"\(([^\{]*)\)\s*->\s*([^\{]*)", lf["cpp"]) m2 = re.search(r"\(([^\{]*)\)", lf["cpp"]) diff --git a/docs/generate_emmylua.py b/docs/generate_emmylua.py index b4902a8f8..ca99f33b2 100644 --- a/docs/generate_emmylua.py +++ b/docs/generate_emmylua.py @@ -36,6 +36,7 @@ "&": "", "const ": "", "ShopType": "SHOP_TYPE", + "EmittedParticlesInfo": "Array", } reFloat = re.compile(r"\bfloat\b") @@ -150,7 +151,7 @@ def print_comment(lf): def print_af(lf, af): - if lf["comment"] and lf["comment"][0] == "NoDoc": + if lf["comment"] and "NoDoc" in lf["comment"][0]: return ret = replace_all(af["return"]) or "nil" name = lf["name"] @@ -197,7 +198,7 @@ def print_af(lf, af): for af in ps.rpcfunc(lf["cpp"]): print_af(lf, af) elif not (lf["name"].startswith("on_") or lf["name"] in ps.not_functions): - if lf["comment"] and lf["comment"][0] == "NoDoc": + if lf["comment"] and "NoDoc" in lf["comment"][0]: continue m = re.search(r"\(([^\{]*)\)\s*->\s*([^\{]*)", lf["cpp"]) m2 = re.search(r"\(([^\{]*)\)", lf["cpp"]) diff --git a/docs/parse_source.py b/docs/parse_source.py index 74968e13a..57ee5a2da 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -230,7 +230,7 @@ def run_parse(): line = line.replace("*", "") if not class_name and ("struct" in line or "class" in line): m = re.match(r"(struct|class)\s+(\S+)", line) - if m: + if m and not line.endswith(";"): class_name = m[2] elif class_name: prev_brackets_depth = brackets_depth @@ -423,10 +423,11 @@ def run_parse(): "cpp": m.group(2), "comment": comment, } - if comment and comment[0] == "Deprecated": - deprecated_funcs.append(func) - else: - funcs.append(func) + if not comment or "NoDoc" not in comment[0]: + if comment and comment[0] == "Deprecated": + deprecated_funcs.append(func) + else: + funcs.append(func) comment = [] c = re.search(r"/// ?(.*)$", line) @@ -438,6 +439,8 @@ def run_parse(): data = open(file, "r").read() data = data.replace("\n", "") data = re.sub(r" ", "", data) + reParticleHelper = re.compile(r"MakeParticleMemberAccess<(.+?)>\(\)") + data = reParticleHelper.sub(r"\1", data) m = re.findall(r'new_usertype\<([^\>]*?)\>\s*\(\s*"([^"]*)",(.*?)\);', data) for type in m: cpp_type = type[0] diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index 5c016c869..3ec363446 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -545,6 +545,7 @@ Name | Data | Description [RENDER_POST_JOURNAL_PAGE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.RENDER_POST_JOURNAL_PAGE) | ON::RENDER_POST_JOURNAL_PAGE | Params: `VanillaRenderContext render_ctx, JOURNAL_PAGE_TYPE page_type, JournalPage page`
Runs after the journal page is drawn on screen. In this event, you can draw textures with the `draw_screen_texture` function of the render_ctx
The page_type parameter values can be found in the JOURNAL_PAGE_TYPE ENUM
The JournalPage parameter gives you access to the specific fields of the page. Be sure to cast it to the correct type, the following functions are available to do that:
`page:as_journal_page_progress()`
`page:as_journal_page_journalmenu()`
`page:as_journal_page_places()`
`page:as_journal_page_people()`
`page:as_journal_page_bestiary()`
`page:as_journal_page_items()`
`page:as_journal_page_traps()`
`page:as_journal_page_story()`
`page:as_journal_page_feats()`
`page:as_journal_page_deathcause()`
`page:as_journal_page_deathmenu()`
`page:as_journal_page_recap()`
`page:as_journal_page_playerprofile()`
`page:as_journal_page_lastgameplayed()`
[SPEECH_BUBBLE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.SPEECH_BUBBLE) | ON::SPEECH_BUBBLE | Params: `Entity speaking_entity, string text`
Runs before any speech bubble is created, even the one using `say` function
Return behavior: if you don't return anything it will execute the speech bubble function normally with default message
if you return empty string, it will not create the speech bubble at all, if you return string, it will use that instead of the original
The first script to return string (empty or not) will take priority, the rest will receive callback call but the return behavior won't matter
[TOAST](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.TOAST) | ON::TOAST | Params: `string text`
Runs before any toast is created, even the one using `toast` function
Return behavior: if you don't return anything it will execute the toast function normally with default message
if you return empty string, it will not create the toast at all, if you return string, it will use that instead of the original message
The first script to return string (empty or not) will take priority, the rest will receive callback call but the return behavior won't matter
+[DEATH_MESSAGE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.DEATH_MESSAGE) | ON::DEATH_MESSAGE | Params: `STRINGID id`
Runs once after death when the death message journal page is shown. The parameter is the STRINGID of the title, like 1221 for BLOWN UP.
## PARTICLEEMITTER diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index de6d235c6..1c2fa209e 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -2744,6 +2744,7 @@ Note that `define_texture` will also reload the texture if it already exists #### nil reset_lut([LAYER](#LAYER) layer) +Same as `set_lut(nil, layer)` ### set_lut @@ -2752,6 +2753,8 @@ Note that `define_texture` will also reload the texture if it already exists #### nil set_lut(optional<[TEXTURE](#TEXTURE)> texture_id, [LAYER](#LAYER) layer) +Force the LUT texture for the given layer (or both) until it is reset +Pass `nil` in the first parameter to reset ## Theme functions diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index cb1711816..01aa413bd 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -394,6 +394,7 @@ float | [top](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=top) | bool | [overlaps_with(const AABB& other)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=overlaps_with) | [AABB](#AABB)& | [abs()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=abs) | Fixes the [AABB](#AABB) if any of the sides have negative length [AABB](#AABB)& | [extrude(float amount)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=extrude) | Grows or shrinks the [AABB](#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](#AABB)& | [extrude(float amount_x, float amount_y)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=extrude) | Grows or shrinks the [AABB](#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](#AABB)& | [offset(float off_x, float off_y)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=offset) | Offsets the [AABB](#AABB) by the given offset. float | [area()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=area) | Compute area of the [AABB](#AABB), can be zero if one dimension is zero or negative if one dimension is inverted. tuple<float, float> | [center()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=center) | Short for `(aabb.left + aabb.right) / 2.0f, (aabb.top + aabb.bottom) / 2.0f`. @@ -1012,6 +1013,21 @@ string | [player_name](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q= ## Particle types +### Particle + + +Type | Name | Description +---- | ---- | ----------- +float | [x](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=x) | +float | [y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=y) | +float | [velocityx](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=velocityx) | +float | [velocityy](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=velocityy) | +[uColor](#Aliases) | [color](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=color) | +float | [width](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=width) | +float | [height](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=height) | +int | [lifetime](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=lifetime) | +int | [max_lifetime](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=max_lifetime) | + ### ParticleDB @@ -1060,6 +1076,7 @@ float | [x](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=x) | float | [y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=y) | float | [offset_x](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=offset_x) | float | [offset_y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=offset_y) | +array<[Particle](#Particle)> | [emitted_particles](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=emitted_particles) | ## Savegame types diff --git a/src/game_api/CMakeLists.txt b/src/game_api/CMakeLists.txt index be04584ba..cb822e05c 100644 --- a/src/game_api/CMakeLists.txt +++ b/src/game_api/CMakeLists.txt @@ -25,4 +25,5 @@ if(MSVC) target_compile_options(spel2_api PRIVATE /Zm80 /bigobj) + target_compile_definitions(spel2_api PRIVATE _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) endif() diff --git a/src/game_api/console.cpp b/src/game_api/console.cpp index 14fe313d6..bf78659e6 100644 --- a/src/game_api/console.cpp +++ b/src/game_api/console.cpp @@ -70,7 +70,7 @@ void SpelunkyConsole::set_max_history_size(size_t max_history) } void SpelunkyConsole::save_history(std::string_view path) { - if (std::ofstream history_file = std::ofstream(path)) + if (std::ofstream history_file = std::ofstream(std::string{path})) { std::string line; for (const auto& history_item : m_Impl->history) @@ -82,7 +82,7 @@ void SpelunkyConsole::save_history(std::string_view path) } void SpelunkyConsole::load_history(std::string_view path) { - if (std::ifstream history_file = std::ifstream(path)) + if (std::ifstream history_file = std::ifstream(std::string{path})) { std::string line; std::string history_item; diff --git a/src/game_api/math.hpp b/src/game_api/math.hpp index a40f3d6f3..628d834af 100644 --- a/src/game_api/math.hpp +++ b/src/game_api/math.hpp @@ -138,16 +138,22 @@ struct AABB /// If `amount < 0` and `abs(amount) > right/top - left/bottom` the respective dimension of the AABB will become `0`. AABB& extrude(float amount) { - left -= amount; - right += amount; + 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) + { + left -= amount_x; + right += amount_x; if (left > right) { left = (left + right) / 2.0f; right = left; } - bottom -= amount; - top += amount; + bottom -= amount_y; + top += amount_y; if (bottom > top) { bottom = (bottom + top) / 2.0f; diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index 82ba9720c..849185258 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -66,10 +66,12 @@ void write_mem(size_t addr, std::string payload) write_mem_prot(addr, payload, false); } -size_t function_start(size_t off) +// Looks for padding between functions, which sometimes does not exist, in that case +// you might be able to specify a different distinct byte +size_t function_start(size_t off, uint8_t outside_byte) { off &= ~0xf; - while (read_u8(off - 1) != 0xcc) + while (read_u8(off - 1) != outside_byte) { off -= 0x10; } diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index e3067c30f..e2a5b855a 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -96,7 +96,7 @@ 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); -size_t function_start(size_t off); +size_t function_start(size_t off, uint8_t outside_byte = '\xcc'); 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); diff --git a/src/game_api/particles.cpp b/src/game_api/particles.cpp index a9c2b262e..528eed69f 100644 --- a/src/game_api/particles.cpp +++ b/src/game_api/particles.cpp @@ -31,6 +31,78 @@ 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_emitter->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_emitter->particle_count}; +} + +Particle EmittedParticlesInfo::front() +{ + return (*this)[0]; +} +Particle EmittedParticlesInfo::back() +{ + return (*this)[particle_emitter->particle_count - 1]; +} +const Particle EmittedParticlesInfo::front() const +{ + return (*this)[0]; +} +const Particle EmittedParticlesInfo::back() const +{ + return (*this)[particle_emitter->particle_count - 1]; +} + +bool EmittedParticlesInfo::empty() +{ + return particle_emitter->particle_count == 0; +} +EmittedParticlesInfo::size_type EmittedParticlesInfo::size() +{ + return particle_emitter->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(uint32_t id) { static std::unordered_map mapping = {}; diff --git a/src/game_api/particles.hpp b/src/game_api/particles.hpp index 8291eb2e5..ad8fd70d8 100644 --- a/src/game_api/particles.hpp +++ b/src/game_api/particles.hpp @@ -1,10 +1,17 @@ #pragma once -#include // for size_t +#include // for size_t, ptrdiff_t #include // for uint32_t, uint8_t, int32_t, uint64_t #include // for string, basic_string #include // for vector +#pragma warning(push) +#pragma warning(disable : 4003) +#include // for neo::iterator_facade +#pragma warning(pop) + +#include "aliases.hpp" // for uColor + struct Texture; struct ParticleDB @@ -74,55 +81,113 @@ struct ParticleEmitter } }; -struct ParticleEmitterInfo +struct Particle { - ParticleDB* particle_type; - ParticleDB* particle_type2; - uint32_t particle_count; - uint32_t unknown2; - size_t unknown3; - size_t unknown4; - size_t unknown5; - size_t unknown6; + uint16_t* max_lifetime; + uint16_t* lifetime; + float* x; + float* y; + float* unknown_x_related; + float* unknown_y_related; + uColor* color; + float* width; + float* height; + float* velocityx; + float* velocityy; +}; + +struct EmittedParticlesInfo +{ + struct ParticleEmitterInfo* particle_emitter; + void* memory; + uint16_t* max_lifetimes; + uint16_t* lifetimes; size_t unknown7; size_t unknown8; - size_t unknown9; - size_t unknown10; - size_t unknown11; - size_t unknown12; - size_t unknown13; - size_t unknown14; - size_t unknown15; - size_t unknown16; - size_t unknown17; + float* x_positions; + float* y_positions; + float* unknown_x_positions; + float* unknown_y_positions; + uColor* colors; + float* widths; + float* heights; + float* x_velocities; + float* y_velocities; size_t unknown18; size_t unknown19; size_t unknown20; size_t unknown21; size_t unknown22; size_t unknown23; + + template + class IteratorImpl : public neo::iterator_facade> + { + public: + IteratorImpl(T* const src, uint32_t i) + : source{src}, index{i} + { + } + + Particle dereference() const noexcept + { + return (*source)[static_cast(index)]; + } + + void advance(ptrdiff_t off) noexcept + { + index = static_cast(index + off); + } + ptrdiff_t distance_to(IteratorImpl rhs) const noexcept + { + return static_cast(rhs.index) - static_cast(index); + } + bool operator==(IteratorImpl rhs) const noexcept + { + return rhs.source == source && rhs.index == index; + } + + private: + T* const source; + uint32_t index; + }; + using Iterator = IteratorImpl; + using ConstIterator = IteratorImpl; + + using size_type = size_t; + using value_type = Particle; + using difference_type = ptrdiff_t; + using iterator = Iterator; + using const_iterator = ConstIterator; + + Iterator begin(); + Iterator end(); + ConstIterator begin() const; + ConstIterator end() const; + ConstIterator cbegin() const; + ConstIterator cend() const; + + Particle front(); + Particle back(); + const Particle front() const; + const Particle back() const; + + bool empty(); + size_type size(); + + Particle operator[](const size_type idx); + const Particle operator[](const size_type idx) const; +}; + +struct ParticleEmitterInfo +{ + ParticleDB* particle_type; + ParticleDB* particle_type2; + uint32_t particle_count; + uint32_t unknown2; + EmittedParticlesInfo emitted_particles; size_t unknown24; - size_t unknown25; - size_t unknown26; - size_t unknown27; - size_t unknown28; - size_t unknown29; - size_t unknown30; - size_t unknown31; - size_t unknown32; - size_t unknown33; - size_t unknown34; - size_t unknown35; - size_t unknown36; - size_t unknown37; - size_t unknown38; - size_t unknown39; - size_t unknown40; - size_t unknown41; - size_t unknown42; - size_t unknown43; - size_t unknown44; - size_t unknown45; + EmittedParticlesInfo emitted_particles2; int32_t entity_uid; // set to -1 to decouple emitter position from entity position (and move it around freely) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 979a1f194..3cb32527d 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1118,28 +1118,14 @@ void extinguish_particles(ParticleEmitterInfo* particle_emitter) auto state = get_state_ptr(); std::erase(*state->particle_emitters, particle_emitter); - static size_t offset = 0; - if (offset == 0) - { - offset = get_address("free_particleemitterinfo"); - } + using generic_free_func = void(void*); + static generic_free_func* generic_free = (generic_free_func*)get_address("generic_free"); - if (offset != 0) + if (particle_emitter != nullptr) { - typedef void free_particleemitter_func(ParticleEmitterInfo*); - static free_particleemitter_func* fpf = (free_particleemitter_func*)(offset); - if (particle_emitter != nullptr) - { - if (particle_emitter->unknown26 != 0) - { - fpf((ParticleEmitterInfo*)particle_emitter->unknown26); - } - if (particle_emitter->unknown4 != 0) - { - fpf((ParticleEmitterInfo*)particle_emitter->unknown4); - } - fpf(particle_emitter); - } + generic_free(particle_emitter->emitted_particles.memory); + generic_free(particle_emitter->emitted_particles2.memory); + generic_free(particle_emitter); } } diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 8c1720fc0..7ed2ecfb7 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1568,6 +1568,13 @@ end /// Returns STRINGID of the new string lua["add_string"] = add_string; + /// Get localized name of an entity, pass `fallback_strategy` as `true` to fall back to the `ENT_TYPE.*` enum name + /// if the entity has no localized name + lua["get_entity_name"] = [](ENT_TYPE type, sol::optional fallback_strategy) + { + return get_entity_name(type, fallback_strategy.value_or(false)); + }; + /// Adds custom name to the item by uid used in the shops /// This is better alternative to `add_string` but instead of changing the name for entity type, it changes it for this particular entity lua["add_custom_name"] = add_custom_name; diff --git a/src/game_api/script/usertypes/hitbox_lua.cpp b/src/game_api/script/usertypes/hitbox_lua.cpp index 26c208c30..9fd7f616d 100644 --- a/src/game_api/script/usertypes/hitbox_lua.cpp +++ b/src/game_api/script/usertypes/hitbox_lua.cpp @@ -80,6 +80,10 @@ void register_usertypes(sol::state& lua) "split", &Vec2::operator std::pair); + const auto extrude = sol::overload( + static_cast(&AABB::extrude), + static_cast(&AABB::extrude)); + /// Axis-Aligned-Bounding-Box, represents for example a hitbox of an entity or the size of a gui element lua.new_usertype( "AABB", @@ -97,7 +101,7 @@ void register_usertypes(sol::state& lua) "abs", &AABB::abs, "extrude", - &AABB::extrude, + extrude, "offset", &AABB::offset, "area", diff --git a/src/game_api/script/usertypes/particles_lua.cpp b/src/game_api/script/usertypes/particles_lua.cpp index c2a9ee3e4..367ac0098 100644 --- a/src/game_api/script/usertypes/particles_lua.cpp +++ b/src/game_api/script/usertypes/particles_lua.cpp @@ -12,6 +12,16 @@ #include "particles.hpp" // for ParticleDB, ParticleEmitterInfo, ParticleEm... #include "rpc.hpp" // for generate_world_particles, advance_screen_pa... +template +auto MakeParticleMemberAccess() +{ + return sol::property( + [](const Particle& p) + { return *(p.*MemberT); }, + [](Particle& p, std::remove_pointer_t().*MemberT)>> v) + { *(p.*MemberT) = v; }); +}; + namespace NParticles { void register_usertypes(sol::state& lua) @@ -111,7 +121,30 @@ void register_usertypes(sol::state& lua) "offset_x", &ParticleEmitterInfo::offset_x, "offset_y", - &ParticleEmitterInfo::offset_y); + &ParticleEmitterInfo::offset_y, + "emitted_particles", + &ParticleEmitterInfo::emitted_particles); + + lua.new_usertype( + "Particle", + "x", + MakeParticleMemberAccess<&Particle::x>(), + "y", + MakeParticleMemberAccess<&Particle::y>(), + "velocityx", + MakeParticleMemberAccess<&Particle::velocityx>(), + "velocityy", + MakeParticleMemberAccess<&Particle::velocityy>(), + "color", + MakeParticleMemberAccess<&Particle::color>(), + "width", + MakeParticleMemberAccess<&Particle::width>(), + "height", + MakeParticleMemberAccess<&Particle::height>(), + "lifetime", + MakeParticleMemberAccess<&Particle::lifetime>(), + "max_lifetime", + MakeParticleMemberAccess<&Particle::max_lifetime>()); lua.create_named_table("PARTICLEEMITTER" //, "TITLE_TORCHFLAME_SMOKE", 1 diff --git a/src/game_api/script/usertypes/vanilla_render_lua.cpp b/src/game_api/script/usertypes/vanilla_render_lua.cpp index 9c2000e4c..6cd41c0cd 100644 --- a/src/game_api/script/usertypes/vanilla_render_lua.cpp +++ b/src/game_api/script/usertypes/vanilla_render_lua.cpp @@ -214,19 +214,19 @@ void register_usertypes(sol::state& lua) } }; - // Force the LUT texture for the given layer (or both) until it is reset - // Pass `nil` in the first parameter to reset + /// Force the LUT texture for the given layer (or both) until it is reset + /// Pass `nil` in the first parameter to reset lua["set_lut"] = [](sol::optional texture_id, LAYER layer) { set_lut(texture_id, layer); }; - // Same as `set_lut(nil, layer)` + /// Same as `set_lut(nil, layer)` lua["reset_lut"] = [](LAYER layer) { set_lut(sol::nullopt, layer); }; - // NoDoc: Dev-tool only + /// NoDoc: Dev-tool only lua["reload_shaders"] = []() { RenderAPI::get().reload_shaders(); diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 8c46a965e..90bd09802 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -282,9 +282,9 @@ class PatternCommandBuffer commands.push_back({CommandType::AtExe}); return *this; } - PatternCommandBuffer& function_start() + PatternCommandBuffer& function_start(uint8_t outside_byte = 0xcc) { - commands.push_back({CommandType::FunctionStart}); + commands.push_back({CommandType::FunctionStart, {.outside_byte = outside_byte}}); return *this; } @@ -368,7 +368,7 @@ class PatternCommandBuffer offset = mem.at_exe(offset); break; case CommandType::FunctionStart: - offset = ::function_start(offset); + offset = ::function_start(offset, data.outside_byte); break; case CommandType::FromExeBase: offset = data.base_offset; @@ -429,6 +429,7 @@ class PatternCommandBuffer uint8_t decode_imm_prefix; GetVirtualFunctionAddressArgs get_vfunc_addr_args; uint64_t base_offset; + uint8_t outside_byte; }; struct Command { @@ -1204,7 +1205,7 @@ std::unordered_map g_address_rules{ .at_exe(), }, { - "free_particleemitterinfo"sv, + "generic_free"sv, // See `generate_screen_particles`, above that, the pointers to the particleemitters are checked, as well as fields inside // the particleemitter, and the same function is called if they are not null PatternCommandBuffer{} @@ -1462,6 +1463,13 @@ std::unordered_map g_address_rules{ .decode_pc() .at_exe(), }, + { + "get_entity_name"sv, + PatternCommandBuffer{} + .find_inst("\x44\x21\xe2\x4c\x8b\x91\x78\x02\x00\x00"sv) + .at_exe() + .function_start(0xff), + }, { "construct_soundposition_ptr"sv, // Put a write bp on ACTIVEFLOOR_DRILL sound_pos1 and release the drill diff --git a/src/game_api/strings.cpp b/src/game_api/strings.cpp index d3ca26507..116f87671 100644 --- a/src/game_api/strings.cpp +++ b/src/game_api/strings.cpp @@ -1,6 +1,7 @@ #include "strings.hpp" #include // for GetCurrentThread, LONG, NO_... +#include // for codecvt_utf16 #include // for swprintf_s, NULL, size_t #include // for memcpy #include // for equal_to @@ -234,3 +235,30 @@ void clear_custom_shopitem_names() g_custom_shopitem_names.clear(); } + +std::u16string get_entity_name(ENT_TYPE id, bool fallback_strategy) +{ + using get_entity_name_func = void(ENT_TYPE, char16_t*, size_t, bool); + static get_entity_name_func* get_entity_name_impl = (get_entity_name_func*)get_address("get_entity_name"); + + std::array out_buffer{}; + get_entity_name_impl(id, out_buffer.data(), out_buffer.size(), true); + std::u16string return_string{out_buffer.data()}; + if (fallback_strategy && return_string.empty()) + { + std::string enum_name{to_name(id).substr(sizeof("ENT_TYPE_") - 1)}; + std::replace(enum_name.begin(), enum_name.end(), '_', ' '); + for (size_t i = 1; i < enum_name.size(); i++) + { + if (enum_name[i - 1] != ' ' && enum_name[i] != ' ') + { + enum_name[i] = (char)std::tolower((int)enum_name[i]); + } + } + + using cvt_type = std::codecvt_utf8_utf16; + std::wstring_convert cvt; + return_string = cvt.from_bytes(enum_name); + } + return return_string; +} diff --git a/src/game_api/strings.hpp b/src/game_api/strings.hpp index 5bba1d073..a405f4b72 100644 --- a/src/game_api/strings.hpp +++ b/src/game_api/strings.hpp @@ -19,3 +19,4 @@ void clear_custom_shopitem_names(); void add_custom_name(uint32_t uid, std::u16string name); void clear_custom_name(uint32_t uid); STRINGID pointer_to_stringid(size_t ptr); +std::u16string get_entity_name(ENT_TYPE id, bool fallback_strategy); diff --git a/src/info_dump/CMakeLists.txt b/src/info_dump/CMakeLists.txt index b587bfc8f..69044c347 100644 --- a/src/info_dump/CMakeLists.txt +++ b/src/info_dump/CMakeLists.txt @@ -2,10 +2,10 @@ add_library(info_dump SHARED main.cpp) target_link_libraries(info_dump PRIVATE shared - spel2_api) -target_link_libraries_system(info_dump PRIVATE - nlohmann_json::nlohmann_json + spel2_api overlunky_warnings) +target_link_libraries_system(info_dump PRIVATE + nlohmann_json::nlohmann_json) if(MSVC) target_compile_options(info_dump PRIVATE /bigobj) diff --git a/src/neo b/src/neo new file mode 160000 index 000000000..ea44f956e --- /dev/null +++ b/src/neo @@ -0,0 +1 @@ +Subproject commit ea44f956e3eaf1c96197c8fc2530c994b1268a38 diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt index 5aa93b5ff..d06f802a5 100644 --- a/src/shared/CMakeLists.txt +++ b/src/shared/CMakeLists.txt @@ -1,5 +1,5 @@ add_library(shared INTERFACE) -target_include_directories(shared INTERFACE .) +target_include_directories(shared INTERFACE . ../neo/src) target_link_libraries(shared INTERFACE overlunky_version) target_link_libraries_system(shared INTERFACE