From e2689396dad8860043f3f6def4577f1368c28f86 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Fri, 2 Aug 2024 20:31:13 -0300 Subject: [PATCH 1/2] type fields, add get_liquids_at function, add liquids.lua example, add ENTITY_MASK enum in C++ --- docs/game_data/spel2.lua | 9 +++ docs/generate_emmylua.py | 1 + docs/parse_source.py | 1 + docs/src/includes/_globals.md | 11 +++ examples/liquids.lua | 120 +++++++++++++++++++++++++++++++++ src/game_api/aliases.hpp | 22 ++++++ src/game_api/rpc.cpp | 27 ++++++++ src/game_api/rpc.hpp | 1 + src/game_api/script/lua_vm.cpp | 5 ++ src/game_api/state_structs.hpp | 24 ++++--- 10 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 examples/liquids.lua diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index a49230c85..94ec05ddd 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1176,6 +1176,15 @@ function set_adventure_seed(first, second) end ---@param layer LAYER? ---@return nil function update_liquid_collision_at(x, y, add, layer) end +---Optimized function to check for the amount of liquids at a certain position, by accessing a 2d array of liquids by third of a tile. Try the `liquids.lua` example to know better how it works. +---Water blobs increase the number by 2 on the grid, while lava blobs increase it by 3. The maximum is usually 6 +---Coarse water increase the number by 3, coarse and stagnant lava by 6. Combinations of both normal and coarse can make the number higher than 6 +---@param liquid_mask MASK +---@param x number +---@param y number +---@param layer LAYER +---@return integer +function get_liquids_at(liquid_mask, x, y, layer) end ---Disable all crust item spawns, returns whether they were already disabled before the call ---@param disable boolean ---@return boolean diff --git a/docs/generate_emmylua.py b/docs/generate_emmylua.py index 89e01ec14..ee4d7423e 100644 --- a/docs/generate_emmylua.py +++ b/docs/generate_emmylua.py @@ -49,6 +49,7 @@ "object": "any", "ImVec2": "Vec2", "BucketItem": "any", + "ENTITY_MASK": "MASK", } reFloat = re.compile(r"\bfloat\b") diff --git a/docs/parse_source.py b/docs/parse_source.py index ecd6fffa7..73920155e 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -63,6 +63,7 @@ "SoundCallbackFunction": "function", "object ": "any ", "BucketItem": "any", + "ENTITY_MASK": "MASK", } header_files = [ diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 971492bff..3361ad05c 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -1537,6 +1537,17 @@ Gets the value for the specified config Get the current layer that the liquid is spawn in. Related function [set_liquid_layer](#set_liquid_layer) +### get_liquids_at + + +> Search script examples for [get_liquids_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_liquids_at) + +#### int get_liquids_at([MASK](#MASK) liquid_mask, float x, float y, [LAYER](#LAYER) layer) + +Optimized function to check for the amount of liquids at a certain position, by accessing a 2d array of liquids by third of a tile. Try the `liquids.lua` example to know better how it works. +Water blobs increase the number by 2 on the grid, while lava blobs increase it by 3. The maximum is usually 6 +Coarse water increase the number by 3, coarse and stagnant lava by 6. Combinations of both normal and coarse can make the number higher than 6 + ### get_local_prng diff --git a/examples/liquids.lua b/examples/liquids.lua new file mode 100644 index 000000000..eb4f648e1 --- /dev/null +++ b/examples/liquids.lua @@ -0,0 +1,120 @@ +register_option_bool("only_draw_at_mouse", "Testing: Draw only liquid num at mouse position", "", false) +register_option_bool("draw_grid_lines", "Draw grid lines", "", true) +register_option_bool("draw_text_shadow", "Draw text shadow", "", true) + +local function get_camera_bounds_grid() + local left, top = game_position(-1, 1) + local right, bottom = game_position(1, -1) + left, top, right, bottom = math.floor(left - 0.5), math.ceil(top + 0.5), math.ceil(right + 0.5), math.floor(bottom - 0.5) + return left, top, right, bottom +end + +local water_color = Color:new(0, 0.1, 1.0, 1.0) +local lava_color = Color:new(1.0, 0.1, 0.0, 1.0) +local black = Color:black() + +---@type table +local num_text_renders = {} +local last_zoom = get_zoom_level() + +-- Generate a text for every possible number, to optimize when there's many numbers to render +local function reload_num_text_renders(zoom) + for i = 0, 12 do + num_text_renders[i] = TextRenderingInfo:new(tostring(i), 0.0081 / zoom, 0.0081 / zoom, VANILLA_TEXT_ALIGNMENT.CENTER, VANILLA_FONT_STYLE.NORMAL) + end +end +reload_num_text_renders(last_zoom) + +---@param ctx VanillaRenderContext +---@param text_render TextRenderingInfo +local function draw_text_shadow(ctx, text_render) + local offset = 0.027 / last_zoom + local saved_x, saved_y = text_render.x, text_render.y + text_render.x, text_render.y = text_render.x + offset, text_render.y - offset + ctx:draw_text(text_render, black) + text_render.x, text_render.y = saved_x, saved_y +end + +---@param ctx VanillaRenderContext +---@param x number +---@param y number +local function draw_liquids_at(ctx, x, y) + local water = get_liquids_at(MASK.WATER, x, y, LAYER.PLAYER) + local lava = get_liquids_at(MASK.LAVA, x, y, LAYER.PLAYER) + if water == 0 and lava == 0 then return end + + local spacing = 0.0135 / last_zoom + local text_render = num_text_renders[water] + text_render.x, text_render.y = screen_position(x, y) + text_render.x = text_render.x - (text_render.width / 2.) - spacing + if options.draw_text_shadow then + draw_text_shadow(ctx, text_render) + end + ctx:draw_text(text_render, water_color) + + text_render = num_text_renders[lava] + text_render.x, text_render.y = screen_position(x, y) + text_render.x = text_render.x + (text_render.width / 2.) + spacing + if options.draw_text_shadow then + draw_text_shadow(ctx, text_render) + end + ctx:draw_text(text_render, lava_color) +end + +---@param ctx VanillaRenderContext +---@param x number +---@param y number +local function draw_grid_liquids(ctx, x, y) + local off = 1./3. + for iy = -1, 1 do + for ix = -1, 1 do + local cx, cy = x + (ix * off), y + (iy * off) + draw_liquids_at(ctx, cx, cy) + end + end +end + +local trans_white = Color:white() +trans_white.a = 0.25 + +---@param ctx VanillaRenderContext +set_callback(function(ctx) + local left, top, right, bottom = get_camera_bounds_grid() + local zoom = get_zoom_level() + if last_zoom ~= zoom then + reload_num_text_renders(zoom) + last_zoom = zoom + end + + if not options.only_draw_at_mouse then + for y = bottom, top do + for x = left, right do + draw_grid_liquids(ctx, x, y) + end + end + else + local spacing = 0.0135 / last_zoom + local x, y = game_position(mouse_position()) + x = x + (1/6) + y = y + (1/6) + x = x - (x % (1/3)) + y = y - (y % (1/3)) + draw_liquids_at(ctx, x, y) + end + + if not options.draw_grid_lines then return end + local off = 1./3. + local line_width = 3.5 + for y = bottom, top do + for i = 0, 2 do + local line_y = y + (off * i) + 0.5 + ctx:draw_screen_line(Vec2:new(screen_position(left, line_y)), Vec2:new(screen_position(right, line_y)), line_width, trans_white) + end + end + for x = left, right do + for i = 0, 2 do + local line_x = x + (off * i) + 0.5 + ctx:draw_screen_line(Vec2:new(screen_position(line_x, top)), Vec2:new(screen_position(line_x, bottom)), line_width, trans_white) + end + end +end, ON.RENDER_PRE_HUD) diff --git a/src/game_api/aliases.hpp b/src/game_api/aliases.hpp index 0d84cd23d..4db0c5f1b 100644 --- a/src/game_api/aliases.hpp +++ b/src/game_api/aliases.hpp @@ -190,3 +190,25 @@ enum class PAUSE_SCREEN : int64_t EXIT = 1 << 31, }; ENUM_CLASS_FLAGS(PAUSE_SCREEN); + +enum class ENTITY_MASK +{ + PLAYER = 0x1, + MOUNT = 0x2, + MONSTER = 0x4, + ITEM = 0x8, + EXPLOSION = 0x10, + ROPE = 0x20, + FX = 0x40, + ACTIVEFLOOR = 0x80, + FLOOR = 0x100, + DECORATION = 0x200, + BG = 0x400, + SHADOW = 0x800, + LOGICAL = 0x1000, + WATER = 0x2000, + LAVA = 0x4000, + LIQUID = 0x6000, + NONE = 0x0, +}; +ENUM_CLASS_FLAGS(ENTITY_MASK) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 4d5dfcb8e..53b56d4d2 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1354,6 +1354,33 @@ void add_entity_to_liquid_collision(uint32_t uid, bool add) } } +uint8_t get_liquids_at(ENTITY_MASK liquid_mask, float x, float y, LAYER layer) +{ + uint8_t actual_layer = enum_to_layer(layer); + LiquidPhysics* liquid_physics = State::get().ptr()->liquid_physics; + if (actual_layer != get_liquid_layer()) + return 0; + if (y > 125.5 || y < 0 || x > 85.5 || x < 0) + return 0; + + uint32_t ix = static_cast((x + 0.5) / 0.3333333); + uint32_t iy = static_cast((y + 0.5) / 0.3333333); + auto& liquids_at = (*liquid_physics->liquids_by_third_of_tile)[iy][ix]; + if (liquid_mask == ENTITY_MASK::NONE) + { + return liquids_at.water + liquids_at.lava; + } + else + { + uint8_t liquids_num = 0; + if ((liquid_mask & ENTITY_MASK::WATER) != ENTITY_MASK::NONE) + liquids_num += liquids_at.water; + if ((liquid_mask & ENTITY_MASK::LAVA) != ENTITY_MASK::NONE) + liquids_num += liquids_at.lava; + return liquids_num; + } +} + bool disable_floor_embeds(bool disable) { static const auto address = get_address("spawn_floor_embeds"); diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index 78001ae71..2906f911c 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -103,6 +103,7 @@ 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); +uint8_t get_liquids_at(ENTITY_MASK liquid_mask, float x, float y, LAYER layer); 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 6d8afe881..9cc9e125e 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1927,6 +1927,11 @@ end /// 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; + /// Optimized function to check for the amount of liquids at a certain position, by accessing a 2d array of liquids by third of a tile. Try the `liquids.lua` example to know better how it works. + /// Water blobs increase the number by 2 on the grid, while lava blobs increase it by 3. The maximum is usually 6 + /// Coarse water increase the number by 3, coarse and stagnant lava by 6. Combinations of both normal and coarse can make the number higher than 6 + lua["get_liquids_at"] = get_liquids_at; + /// Disable all crust item spawns, returns whether they were already disabled before the call lua["disable_floor_embeds"] = disable_floor_embeds; diff --git a/src/game_api/state_structs.hpp b/src/game_api/state_structs.hpp index 90abec216..c919f59e0 100644 --- a/src/game_api/state_structs.hpp +++ b/src/game_api/state_structs.hpp @@ -842,6 +842,14 @@ struct LiquidLake Entity* impostor_lake; }; +// Water blobs increase the number by 2 on the grid, while lava blobs increase it by 3. The maximum is 6 +// Coarse water increase the number by 3, coarse and stagnant lava by 6. Combinations of both normal and coarse can make the number higher than 6 +struct LiquidAmounts +{ + uint8_t lava; + uint8_t water; +}; + struct LiquidPhysics { size_t unknown1; // MysteryLiquidPointer1 in plugin, collision with floors/activefloors related @@ -867,16 +875,16 @@ struct LiquidPhysics LiquidTileSpawnData stagnant_lava_tile_spawn_data; }; }; - 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 + 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 + LiquidAmounts (*liquids_by_third_of_tile)[126 * 3][86 * 3]; // array byte* game allocates 0x2F9E8 bytes for it ((126 * 3) * (86 * 3) * 2 : y, x, liquid_type). + // always allocates after the LiquidPhysics uint32_t total_liquid_spawned2; // Same as total_liquid_spawned? - bool unknown12; + bool unknown12; // if false, I think the game should check for liquids by looking for liquid entities rather than using the previous liquids array. Is set to true by the game actively uint8_t padding12a; uint8_t padding12b; uint8_t padding12c; From 5f3eed6ac2988a339b5fd7bf754daa56c0b9ba25 Mon Sep 17 00:00:00 2001 From: Estebanfer Date: Sat, 10 Aug 2024 18:58:57 -0300 Subject: [PATCH 2/2] Rename MASK::NONE to MASK::ANY --- src/game_api/aliases.hpp | 2 +- src/game_api/rpc.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game_api/aliases.hpp b/src/game_api/aliases.hpp index 4db0c5f1b..9ee587b79 100644 --- a/src/game_api/aliases.hpp +++ b/src/game_api/aliases.hpp @@ -209,6 +209,6 @@ enum class ENTITY_MASK WATER = 0x2000, LAVA = 0x4000, LIQUID = 0x6000, - NONE = 0x0, + ANY = 0x0, }; ENUM_CLASS_FLAGS(ENTITY_MASK) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 53b56d4d2..1d3335bf4 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1366,16 +1366,16 @@ uint8_t get_liquids_at(ENTITY_MASK liquid_mask, float x, float y, LAYER layer) uint32_t ix = static_cast((x + 0.5) / 0.3333333); uint32_t iy = static_cast((y + 0.5) / 0.3333333); auto& liquids_at = (*liquid_physics->liquids_by_third_of_tile)[iy][ix]; - if (liquid_mask == ENTITY_MASK::NONE) + if (liquid_mask == ENTITY_MASK::ANY) { return liquids_at.water + liquids_at.lava; } else { uint8_t liquids_num = 0; - if ((liquid_mask & ENTITY_MASK::WATER) != ENTITY_MASK::NONE) + if ((liquid_mask & ENTITY_MASK::WATER) == ENTITY_MASK::WATER) liquids_num += liquids_at.water; - if ((liquid_mask & ENTITY_MASK::LAVA) != ENTITY_MASK::NONE) + if ((liquid_mask & ENTITY_MASK::LAVA) == ENTITY_MASK::LAVA) liquids_num += liquids_at.lava; return liquids_num; }