diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 7db38b8c4..dbaf66edd 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -185,482 +185,679 @@ function toast(message) end ---@param top boolean ---@return nil function say(entity_uid, message, sound_type, top) end ----Add an integer option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft ----limits, you can override them in the UI with double click. ----@param name string ----@param desc string ----@param long_desc string ----@param value integer ----@param min integer ----@param max integer ----@return nil -function register_option_int(name, desc, long_desc, value, min, max) end ----Add a float option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft ----limits, you can override them in the UI with double click. ----@param name string ----@param desc string ----@param long_desc string ----@param value number ----@param min number ----@param max number ----@return nil -function register_option_float(name, desc, long_desc, value, min, max) end ----Add a boolean option that the user can change in the UI. Read with `options.name`, `value` is the default. ----@param name string ----@param desc string ----@param long_desc string ----@param value boolean ----@return nil -function register_option_bool(name, desc, long_desc, value) end ----Add a string option that the user can change in the UI. Read with `options.name`, `value` is the default. ----@param name string ----@param desc string ----@param long_desc string ----@param value string ----@return nil -function register_option_string(name, desc, long_desc, value) end ----Add a combobox option that the user can change in the UI. Read the int index of the selection with `options.name`. Separate `opts` with `\0`, ----with a double `\0\0` at the end. `value` is the default index 1..n. ----@param name string ----@param desc string ----@param long_desc string ----@param opts string ----@param value integer ----@return nil -function register_option_combo(name, desc, long_desc, opts, value) end ----Add a button that the user can click in the UI. Sets the timestamp of last click on value and runs the callback function. ----@param name string ----@param desc string ----@param long_desc string ----@param on_click function +---Enable/disable godmode for players. +---@param g boolean ---@return nil -function register_option_button(name, desc, long_desc, on_click) end ----Add custom options using the window drawing functions. Everything drawn in the callback will be rendered in the options window and the return value saved to `options[name]` or overwriting the whole `options` table if using and empty name. ----`value` is the default value, and pretty important because anything defined in the callback function will only be defined after the options are rendered. See the example for details. ----The callback signature is optional on_render(GuiDrawContext draw_ctx) ----@param name string ----@param value any ----@param on_render fun(draw_ctx: GuiDrawContext): any? +function god(g) end +---Enable/disable godmode for companions. +---@param g boolean ---@return nil -function register_option_callback(name, value, on_render) end ----Removes an option by name. To make complicated conditionally visible options you should probably just use register_option_callback though. ----@param name string +function god_companions(g) end +---Set the zoom level used in levels and shops. 13.5 is the default, or 12.5 for shops. See zoom_reset. +---@param level number ---@return nil -function unregister_option(name) end ----Spawn liquids, always spawns in the front layer, will have fun effects if `entity_type` is not a liquid (only the short version, without velocity etc.). ----Don't overuse this, you are still restricted by the liquid pool sizes and thus might crash the game. ----`liquid_flags` - not much known about, 2 - will probably crash the game, 3 - pause_physics, 6-12 is probably agitation, surface_tension etc. set to 0 to ignore ----`amount` - it will spawn amount x amount (so 1 = 1, 2 = 4, 3 = 6 etc.), `blobs_separation` is optional ----@param entity_type ENT_TYPE ----@param x number ----@param y number +function zoom(level) end +---Reset the default zoom levels for all areas and sets current zoom level to 13.5. ---@return nil -function spawn_liquid(entity_type, x, y) end ----Spawn liquids, always spawns in the front layer, will have fun effects if `entity_type` is not a liquid (only the short version, without velocity etc.). ----Don't overuse this, you are still restricted by the liquid pool sizes and thus might crash the game. ----`liquid_flags` - not much known about, 2 - will probably crash the game, 3 - pause_physics, 6-12 is probably agitation, surface_tension etc. set to 0 to ignore ----`amount` - it will spawn amount x amount (so 1 = 1, 2 = 4, 3 = 6 etc.), `blobs_separation` is optional ----@param entity_type ENT_TYPE ----@param x number ----@param y number ----@param velocityx number ----@param velocityy number ----@param liquid_flags integer ----@param amount integer ----@param blobs_separation number +function zoom_reset() end +---Set the contents of [Coffin](https://spelunky-fyi.github.io/overlunky/#Coffin), [Present](https://spelunky-fyi.github.io/overlunky/#Present), [Pot](https://spelunky-fyi.github.io/overlunky/#Pot), [Container](https://spelunky-fyi.github.io/overlunky/#Container) +---Check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) for what the exact ENT_TYPE's can this function affect +---@param uid integer +---@param item_entity_type ENT_TYPE ---@return nil -function spawn_liquid(entity_type, x, y, velocityx, velocityy, liquid_flags, amount, blobs_separation) end ----Spawn an entity in position with some velocity and return the uid of spawned entity. ----Uses level coordinates with [LAYER.FRONT](#LAYER) and LAYER.BACK, but player-relative coordinates with LAYER.PLAYER(n), where (n) is a player number (1-4). ----@param entity_type ENT_TYPE +function set_contents(uid, item_entity_type) end +---Gets a grid entity, such as floor or spikes, at the given position and layer. ---@param x number ---@param y number ---@param layer LAYER ----@param vx number ----@param vy number ---@return integer -function spawn_entity(entity_type, x, y, layer, vx, vy) end ----Short for [spawn_entity](https://spelunky-fyi.github.io/overlunky/#spawn_entity). ----@param entity_type ENT_TYPE +function get_grid_entity_at(x, y, layer) end +---Get uids of static entities overlapping this grid position (decorations, backgrounds etc.) ---@param x number ---@param y number ---@param layer LAYER ----@param vx number ----@param vy number ----@return integer -function spawn(entity_type, x, y, layer, vx, vy) end ----Spawns an entity directly on the floor below the tile at the given position. ----Use this to avoid the little fall that some entities do when spawned during level gen callbacks. ----@param entity_type ENT_TYPE ----@param x number ----@param y number +---@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 +---@return integer[] +function filter_entities(entities, predicate) end +---Get uids of entities by some conditions ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. +---Recommended to always set the mask, even if you look for one entity type +---@param entity_types ENT_TYPE[] +---@param mask MASK ---@param layer LAYER ----@return integer -function spawn_entity_snapped_to_floor(entity_type, x, y, layer) end ----Short for [spawn_entity_snapped_to_floor](https://spelunky-fyi.github.io/overlunky/#spawn_entity_snapped_to_floor). +---@return integer[] +function get_entities_by(entity_types, mask, layer) end +---Get uids of entities by some conditions ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. +---Recommended to always set the mask, even if you look for one entity type ---@param entity_type ENT_TYPE ----@param x number ----@param y number +---@param mask MASK ---@param layer LAYER ----@return integer -function spawn_on_floor(entity_type, x, y, layer) end ----Spawn a grid entity, such as floor or traps, that snaps to the grid. ----@param entity_type ENT_TYPE +---@return integer[] +function get_entities_by(entity_type, mask, layer) end +---Get uids of entities matching id. This function is variadic, meaning it accepts any number of id's. +---You can even pass a table! +---This function can be slower than the [get_entities_by](https://spelunky-fyi.github.io/overlunky/#get_entities_by) with the mask parameter filled +---@vararg any +---@return integer[] +function get_entities_by_type(...) end +---Get uids of matching entities inside some radius ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types +---Recommended to always set the mask, even if you look for one entity type +---@param entity_types ENT_TYPE[] +---@param mask MASK ---@param x number ---@param y number ---@param layer LAYER ----@return integer -function spawn_grid_entity(entity_type, x, y, layer) end ----Same as `spawn_entity` but does not trigger any pre-entity-spawn callbacks, so it will not be replaced by another script +---@param radius number +---@return integer[] +function get_entities_at(entity_types, mask, x, y, layer, radius) end +---Get uids of matching entities inside some radius ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types +---Recommended to always set the mask, even if you look for one entity type ---@param entity_type ENT_TYPE +---@param mask MASK ---@param x number ---@param y number ---@param layer LAYER ----@param vx number ----@param vy number ----@return integer -function spawn_entity_nonreplaceable(entity_type, x, y, layer, vx, vy) end ----Short for [spawn_entity_nonreplaceable](https://spelunky-fyi.github.io/overlunky/#spawn_entity_nonreplaceable). +---@param radius number +---@return integer[] +function get_entities_at(entity_type, mask, x, y, layer, radius) end +---Get uids of matching entities overlapping with the given hitbox. Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types +---@param entity_types ENT_TYPE[] +---@param mask MASK +---@param hitbox AABB +---@param layer LAYER +---@return integer[] +function get_entities_overlapping_hitbox(entity_types, mask, hitbox, layer) end +---Get uids of matching entities overlapping with the given hitbox. Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types ---@param entity_type ENT_TYPE ----@param x number ----@param y number +---@param mask MASK +---@param hitbox AABB ---@param layer LAYER ----@param vx number ----@param vy number ----@return integer -function spawn_critical(entity_type, x, y, layer, vx, vy) end ----Spawn a door to another world, level and theme and return the uid of spawned entity. ----Uses level coordinates with LAYER.FRONT and LAYER.BACK, but player-relative coordinates with LAYER.PLAYERn +---@return integer[] +function get_entities_overlapping_hitbox(entity_type, mask, hitbox, layer) end +---Get the current set zoom level +---@return number +function get_zoom_level() end +---Get the game coordinates at the screen position (`x`, `y`) ---@param x number ---@param y number ----@param layer LAYER ----@param w integer ----@param l integer ----@param t integer ----@return integer -function spawn_door(x, y, layer, w, l, t) end ----Short for [spawn_door](https://spelunky-fyi.github.io/overlunky/#spawn_door). +---@return number, number +function game_position(x, y) end +---Translate an entity position to screen position to be used in drawing functions ---@param x number ---@param y number ----@param layer LAYER ----@param w integer ----@param l integer ----@param t integer ----@return integer -function door(x, y, layer, w, l, t) end ----Spawn a door to backlayer. +---@return number, number +function screen_position(x, y) end +---Translate a distance of `x` tiles to screen distance to be be used in drawing functions ---@param x number ----@param y number +---@return number +function screen_distance(x) end +---Get the current frame count since the game was started*. You can use this to make some timers yourself, the engine runs at 60fps. This counter is paused if the pause is set with flags PAUSE.FADE or PAUSE.ANKH. +---@return integer +function get_frame() end +---Get the current global frame count since the game was started. You can use this to make some timers yourself, the engine runs at 60fps. This counter keeps incrementing when state is updated, even during loading screens. +---@return integer +function get_global_frame() end +---Get the current timestamp in milliseconds since the Unix Epoch. ---@return nil -function spawn_layer_door(x, y) end ----Short for [spawn_layer_door](https://spelunky-fyi.github.io/overlunky/#spawn_layer_door). ----@param x number ----@param y number +function get_ms() end +---Enables or disables the journal +---@param b boolean ---@return nil -function layer_door(x, y) end ----Spawns apep with the choice if it going left or right, if you want the game to choose use regular spawn functions with `ENT_TYPE.MONS_APEP_HEAD` ----@param x number ----@param y number ----@param layer LAYER ----@param right boolean ----@return integer -function spawn_apep(x, y, layer, right) end ----Spawns and grows a tree +function set_journal_enabled(b) end +---Checks whether a coordinate is inside a room containing an active shop. This function checks whether the shopkeeper is still alive. ---@param x number ---@param y number ---@param layer LAYER ----@param height integer ----@return integer -function spawn_tree(x, y, layer, height) end ----Spawns and grows a tree +---@return boolean +function is_inside_active_shop_room(x, y, layer) end +---Checks whether a coordinate is inside a shop zone, the rectangle where the camera zooms in a bit. Does not check if the shop is still active! ---@param x number ---@param y number ---@param layer LAYER ----@return integer -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, 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 ----@return integer -function spawn_mushroom(x, y, l) end ----Spawns an already unrolled rope as if created by player ----@param x number ----@param y number ----@param layer LAYER ----@param texture TEXTURE ----@return integer -function spawn_unrolled_player_rope(x, y, layer, texture) end ----Spawns an already unrolled rope as if created by player ----@param x number ----@param y number ----@param layer LAYER ----@param texture TEXTURE ----@param max_length integer ----@return integer -function spawn_unrolled_player_rope(x, y, layer, texture, max_length) end ----Spawn a player in given location, if player of that slot already exist it will spawn clone, the game may crash as this is very unexpected situation ----If you want to respawn a player that is a ghost, set in his Inventory `health` to above 0, and `time_of_death` to 0 and call this function, the ghost entity will be removed automatically ----@param player_slot integer ----@param x number? ----@param y number? ----@param layer LAYER? ----@return integer -function spawn_player(player_slot, x, y, layer) end ----Spawn the PlayerGhost entity, it will not move and not be connected to any player, you can then use [steal_input](https://spelunky-fyi.github.io/overlunky/#steal_input) and send_input to control it ----or change it's `player_inputs` to the `input` of real player so he can control it directly ----@param char_type ENT_TYPE ----@param x number ----@param y number ----@param layer LAYER ----@return integer -function spawn_playerghost(char_type, x, y, layer) end ----Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. ----This is run before the entity is spawned, spawn your own entity and return its uid to replace the intended spawn. ----In many cases replacing the intended entity won't have the intended effect or will even break the game, so use only if you really know what you're doing. ----The callback signature is optional pre_entity_spawn(ENT_TYPE entity_type, float x, float y, int layer, Entity overlay_entity, SPAWN_TYPE spawn_flags) ----@param cb fun(entity_type: ENT_TYPE, x: number, y: number, layer: integer, overlay_entity: Entity, spawn_flags: SPAWN_TYPE): integer? ----@param flags SPAWN_TYPE ----@param mask integer ----@vararg any ----@return CallbackId -function set_pre_entity_spawn(cb, flags, mask, ...) end ----Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. ----This is run right after the entity is spawned but before and particular properties are changed, e.g. owner or velocity. ----The callback signature is nil post_entity_spawn(Entity ent, SPAWN_TYPE spawn_flags) ----@param cb fun(ent: Entity, spawn_flags: SPAWN_TYPE): nil ----@param flags SPAWN_TYPE ----@param mask integer ----@vararg any ----@return CallbackId -function set_post_entity_spawn(cb, flags, mask, ...) end ----Warp to a level immediately. ----@param w integer ----@param l integer ----@param t integer ----@return nil -function warp(w, l, t) end ----Set seed and reset run. ----@param seed integer +---@return boolean +function is_inside_shop_zone(x, y, layer) end +---Basically gets the absolute coordinates of the area inside the unbreakable bedrock walls, from wall to wall. Every solid entity should be +---inside these boundaries. The order is: left x, top y, right x, bottom y +---@return number, number, number, number +function get_bounds() end +---Same as [get_bounds](https://spelunky-fyi.github.io/overlunky/#get_bounds) but returns AABB struct instead of loose floats +---@return AABB +function get_aabb_bounds() end +---Gets the current camera position in the level +---@return number, number +function get_camera_position() end +---Sets the absolute current camera position without rubberbanding animation. Ignores camera bounds or currently focused uid, but doesn't clear them. Best used in ON.RENDER_PRE_GAME or similar. See Camera for proper camera handling with bounds and rubberbanding. +---@param cx number +---@param cy number ---@return nil -function set_seed(seed) end ----Enable/disable godmode for players. ----@param g boolean +function set_camera_position(cx, cy) end +---Updates the camera focus according to the params set in Camera, i.e. to apply normal camera movement when paused etc. ---@return nil -function god(g) end ----Enable/disable godmode for companions. ----@param g boolean +function update_camera_position() end +---Set the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. +---@param flags Flags +---@param bit integer +---@return Flags +function set_flag(flags, bit) end +---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. +---@param flags Flags +---@param bit integer +---@return Flags +function clr_flag(flags, bit) end +---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. +---@param flags Flags +---@param bit integer +---@return Flags +function flip_flag(flags, bit) end +---Returns true if the nth bit is set in the number. +---@param flags Flags +---@param bit integer +---@return boolean +function test_flag(flags, bit) end +---Set a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. +---@param flags Flags +---@param mask Flags +---@return Flags +function set_mask(flags, mask) end +---Clears a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. +---@param flags Flags +---@param mask Flags +---@return Flags +function clr_mask(flags, mask) end +---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. +---@param flags Flags +---@param mask Flags +---@return Flags +function flip_mask(flags, mask) end +---Returns true if a bitmask is set in the number. +---@param flags Flags +---@param mask Flags +---@return boolean +function test_mask(flags, mask) end +---Gets the resolution (width and height) of the screen +---@return integer, integer +function get_window_size() end +---Clears a callback that is specific to a screen. +---@param screen_id integer +---@param cb_id CallbackId ---@return nil -function god_companions(g) end ----Set the zoom level used in levels and shops. 13.5 is the default, or 12.5 for shops. See zoom_reset. ----@param level number +function clear_screen_callback(screen_id, cb_id) end +---Returns unique id for the callback to be used in [clear_screen_callback](https://spelunky-fyi.github.io/overlunky/#clear_screen_callback) or `nil` if screen_id is not valid. +---Sets a callback that is called right before the screen is drawn, return `true` to skip the default rendering. +---The callback signature is bool render_screen(Screen self, VanillaRenderContext render_ctx) +---@param screen_id integer +---@param fun fun(self: Screen, render_ctx: VanillaRenderContext): boolean +---@return CallbackId? +function set_pre_render_screen(screen_id, fun) end +---Returns unique id for the callback to be used in [clear_screen_callback](https://spelunky-fyi.github.io/overlunky/#clear_screen_callback) or `nil` if screen_id is not valid. +---Sets a callback that is called right after the screen is drawn. +---The callback signature is nil render_screen(Screen self, VanillaRenderContext render_ctx) +---@param screen_id integer +---@param fun fun(self: Screen, render_ctx: VanillaRenderContext): nil +---@return CallbackId? +function set_post_render_screen(screen_id, fun) end +---Returns unique id for the callback to be used in [clear_callback](https://spelunky-fyi.github.io/overlunky/#clear_callback) or `nil` if uid is not valid. +---Sets a callback that is called right when an player/hired hand is crushed/insta-gibbed, return `true` to skip the game's crush handling. +---The game's instagib function will be forcibly executed (regardless of whatever you return in the callback) when the entity's health is zero. +---This is so that when the entity dies (from other causes), the death screen still gets shown. +---Use this only when no other approach works, this call can be expensive if overused. +---The callback signature is bool on_player_instagib(Entity self) +---@param uid integer +---@param fun fun(self: Entity): boolean +---@return CallbackId? +function set_on_player_instagib(uid, fun) end +---Raise a signal and probably crash the game ---@return nil -function zoom(level) end ----Reset the default zoom levels for all areas and sets current zoom level to 13.5. +function raise() end +---Convert the hash to stringid +---Check [strings00_hashed.str](https://github.com/spelunky-fyi/overlunky/blob/main/docs/game_data/strings00_hashed.str) for the hash values, or extract assets with modlunky and check those. +---@param hash integer +---@return STRINGID +function hash_to_stringid(hash) end +---Get string behind STRINGID, **don't use stringid directly for vanilla string**, use [hash_to_stringid](https://spelunky-fyi.github.io/overlunky/#hash_to_stringid) first +---Will return the string of currently choosen language +---@param string_id STRINGID +---@return string +function get_string(string_id) end +---Change string at the given id (**don't use stringid directly for vanilla string**, use [hash_to_stringid](https://spelunky-fyi.github.io/overlunky/#hash_to_stringid) first) +---This edits custom string and in game strings but changing the language in settings will reset game strings +---@param id STRINGID +---@param str string ---@return nil -function zoom_reset() end ----Teleport entity to coordinates with optional velocity +function change_string(id, str) end +---Add custom string, currently can only be used for names of shop items (EntityDB->description) +---Returns STRINGID of the new string +---@param str string +---@return STRINGID +function add_string(str) end +---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 ---@param uid integer ----@param x number ----@param y number ----@param vx number ----@param vy number +---@param name string ---@return nil -function move_entity(uid, x, y, vx, vy) end ----Teleport entity to coordinates with optional velocity +function add_custom_name(uid, name) end +---Clears the name set with [add_custom_name](https://spelunky-fyi.github.io/overlunky/#add_custom_name) ---@param uid integer ----@param x number ----@param y number ----@param vx number ----@param vy number ----@param layer LAYER ---@return nil -function move_entity(uid, x, y, vx, vy, layer) end ----Teleport grid entity, the destination should be whole number, this ensures that the collisions will work properly ----@param uid integer ----@param x number ----@param y number ----@param layer LAYER +function clear_custom_name(uid) end +---Adds entity as shop item, has to be of [Purchasable](https://spelunky-fyi.github.io/overlunky/#Purchasable) type, check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) to find all the Purchasable entity types. +---Adding other entities will result in not obtainable items or game crash +---@param item_uid integer +---@param shop_owner_uid integer ---@return nil -function move_grid_entity(uid, x, y, layer) end ----Destroy the grid entity (by uid or position), and its item entities, removing them from the grid without dropping particles or gold. ----Will also destroy monsters or items that are standing on a linked activefloor or chain, though excludes MASK.PLAYER to prevent crashes +function add_item_to_shop(item_uid, shop_owner_uid) end +---Creates a new Illumination. Don't forget to continuously call [refresh_illumination](https://spelunky-fyi.github.io/overlunky/#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. +---@param pos Vec2 +---@param color Color +---@param type LIGHT_TYPE +---@param size number +---@param flags integer ---@param uid integer ----@return nil -function destroy_grid(uid) end ----Destroy the grid entity (by uid or position), and its item entities, removing them from the grid without dropping particles or gold. ----Will also destroy monsters or items that are standing on a linked activefloor or chain, though excludes MASK.PLAYER to prevent crashes +---@param layer LAYER +---@return Illumination +function create_illumination(pos, color, type, size, flags, uid, layer) end +---Creates a new Illumination. Don't forget to continuously call [refresh_illumination](https://spelunky-fyi.github.io/overlunky/#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. +---@param color Color +---@param size number ---@param x number ---@param y number ----@param layer LAYER ----@return nil -function destroy_grid(x, y, layer) end ----Make an ENT_TYPE.FLOOR_DOOR_EXIT go to world `w`, level `l`, theme `t` ----@param uid integer ----@param w integer ----@param l integer ----@param t integer ----@return nil -function set_door_target(uid, w, l, t) end ----Short for [set_door_target](https://spelunky-fyi.github.io/overlunky/#set_door_target). +---@return Illumination +function create_illumination(color, size, x, y) end +---Creates a new Illumination. Don't forget to continuously call [refresh_illumination](https://spelunky-fyi.github.io/overlunky/#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. +---@param color Color +---@param size number ---@param uid integer ----@param w integer ----@param l integer ----@param t integer +---@return Illumination +function create_illumination(color, size, uid) end +---Refreshes an Illumination, keeps it from fading out, short for `illumination.timer = get_frame()` +---@param illumination Illumination ---@return nil -function set_door(uid, w, l, t) end ----Get door target `world`, `level`, `theme` ----@param uid integer ----@return integer, integer, integer -function get_door_target(uid) end ----Set the contents of [Coffin](https://spelunky-fyi.github.io/overlunky/#Coffin), [Present](https://spelunky-fyi.github.io/overlunky/#Present), [Pot](https://spelunky-fyi.github.io/overlunky/#Pot), [Container](https://spelunky-fyi.github.io/overlunky/#Container) ----Check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) for what the exact ENT_TYPE's can this function affect ----@param uid integer ----@param item_entity_type ENT_TYPE +function refresh_illumination(illumination) end +---Return the name of the first matching number in an enum table +---@param enum table +---@param value integer +---@return string +function enum_get_name(enum, value) end +---Return all the names of a number in an enum table +---@param enum table +---@param value integer +---@return table +function enum_get_names(enum, value) end +---Return the matching names for a bitmask in an enum table of masks +---@param enum table +---@param value integer +---@return table +function enum_get_mask_names(enum, value) end +---Gets the specified setting, values might need to be interpreted differently per setting +---@param setting GAME_SETTING +---@return integer? +function get_setting(setting) end +---Sets the specified setting temporarily. These values are not saved and might reset to the users real settings if they visit the options menu. (Check example.) All settings are available in unsafe mode and only a smaller subset SAFE_SETTING by default for Hud and other visuals. Returns false, if setting failed. +---@param setting GAME_SETTING +---@param value integer +---@return boolean +function set_setting(setting, value) end +---Short for print(string.format(...)) ---@return nil -function set_contents(uid, item_entity_type) end ----Get the Entity behind an uid, converted to the correct type. To see what type you will get, consult the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) ----@param uid integer ----@return Entity -function get_entity(uid) end ----Get the [EntityDB](https://spelunky-fyi.github.io/overlunky/#EntityDB) behind an ENT_TYPE... ----@param id ENT_TYPE ----@return EntityDB -function get_type(id) end ----Gets a grid entity, such as floor or spikes, at the given position and layer. ----@param x number ----@param y number ----@param layer LAYER ----@return integer -function get_grid_entity_at(x, y, layer) end ----Get uids of static entities overlapping 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 ----@return integer[] -function filter_entities(entities, predicate) end ----Get uids of entities by some conditions ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. ----Recommended to always set the mask, even if you look for one entity type ----@param entity_types ENT_TYPE[] ----@param mask integer ----@param layer LAYER ----@return integer[] -function get_entities_by(entity_types, mask, layer) end ----Get uids of entities by some conditions ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. ----Recommended to always set the mask, even if you look for one entity type ----@param entity_type ENT_TYPE ----@param mask integer ----@param layer LAYER ----@return integer[] -function get_entities_by(entity_type, mask, layer) end ----Get uids of entities matching id. This function is variadic, meaning it accepts any number of id's. ----You can even pass a table! ----This function can be slower than the [get_entities_by](https://spelunky-fyi.github.io/overlunky/#get_entities_by) with the mask parameter filled ----@vararg any ----@return integer[] -function get_entities_by_type(...) end ----Get uids of matching entities inside some radius ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types ----Recommended to always set the mask, even if you look for one entity type ----@param entity_types ENT_TYPE[] ----@param mask integer +function printf() end +---Get the current adventure seed pair, or optionally what it was at the start of this run, because it changes every level. +---@param run_start boolean? +---@return integer, integer +function get_adventure_seed(run_start) end +---Set the current adventure seed pair. Use just before resetting a run to recreate an adventure run. +---@param first integer +---@param second integer +---@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 layer LAYER ----@param radius number ----@return integer[] -function get_entities_at(entity_types, mask, x, y, layer, radius) end ----Get uids of matching entities inside some radius ([ENT_TYPE](https://spelunky-fyi.github.io/overlunky/#ENT_TYPE), [MASK](https://spelunky-fyi.github.io/overlunky/#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types ----Recommended to always set the mask, even if you look for one entity type ----@param entity_type ENT_TYPE ----@param mask integer +---@param add boolean +---@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. +---Returns a pair of water and lava, in that order. +---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 x number ---@param y number ---@param layer LAYER ----@param radius number ----@return integer[] -function get_entities_at(entity_type, mask, x, y, layer, radius) end ----Get uids of matching entities overlapping with the given hitbox. Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types ----@param entity_types ENT_TYPE[] ----@param mask integer ----@param hitbox AABB ----@param layer LAYER +---@return integer, integer +function get_liquids_at(x, y, layer) end +---Get the rva for a pattern name, used for debugging. +---@param address_name string +---@return string +function get_rva(address_name) end +---Get the rva for a vtable offset and index, used for debugging. +---@param offset VTABLE_OFFSET +---@param index integer +---@return string +function get_virtual_rva(offset, index) end +---Get memory address from a lua object +---@param o any +---@return nil +function get_address(o) end +---Log to spelunky.log +---@param message string +---@return nil +function log_print(message) end +---Immediately ends the run with the death screen, also calls the [save_progress](https://spelunky-fyi.github.io/overlunky/#save_progress) +---@return nil +function load_death_screen() end +---Saves the game to savegame.sav, unless game saves are blocked in the settings. Also runs the ON.SAVE callback. Fails and returns false, if you're trying to save too often (2s). +---@return boolean +function save_progress() end +---Runs the ON.SAVE callback. Fails and returns false, if you're trying to save too often (2s). +---@return boolean +function save_script() end +---Set the level number shown in the hud and journal to any string. This is reset to the default "%d-%d" automatically just before PRE_LOAD_SCREEN to a level or main menu, so use in PRE_LOAD_SCREEN, POST_LEVEL_GENERATION or similar for each level. +---Use "%d-%d" to reset to default manually. Does not affect the "...COMPLETED!" message in transitions or lines in "Dear Journal", you need to edit them separately with [change_string](https://spelunky-fyi.github.io/overlunky/#change_string). +---@param str string +---@return nil +function set_level_string(str) end +---List files in directory relative to the script root. Returns table of file/directory names or nil if not found. +---@param dir string? +---@return nil +function list_dir(dir) end +---List files in directory relative to the mods data directory (Mods/Data/...). Returns table of file/directory names or nil if not found. +---@param dir string? +---@return nil +function list_data_dir(dir) end +---List all char_*.png files recursively from Mods/Packs. Returns table of file paths. +---@return nil +function list_char_mods() end +---Approximate bounding box of the player hud element for player index 1..4 based on user settings and player count +---@param index integer +---@return AABB +function get_hud_position(index) end +---Set engine target frametime (1/framerate, default 1/60). Always capped by your GPU max FPS / VSync. To run the engine faster than rendered FPS, try update_state. Set to 0 to go as fast as possible. Call without arguments to reset. Also see set_speedhack +---@param frametime double? +---@return nil +function set_frametime(frametime) end +---Get engine target frametime (1/framerate, default 1/60). +---@return double +function get_frametime() end +---Set engine target frametime when game is unfocused (1/framerate, default 1/33). Always capped by the engine frametime. Set to 0 to go as fast as possible. Call without arguments to reset. +---@param frametime double? +---@return nil +function set_frametime_unfocused(frametime) end +---Get engine target frametime when game is unfocused (1/framerate, default 1/33). +---@return double +function get_frametime_unfocused() end +---Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn +---Use empty array or no parameter to get new unique ENT_TYPE that can be used for custom EntityDB +---@param types ENT_TYPE[] +---@return ENT_TYPE +function add_custom_type(types) end +---Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn +---Use empty array or no parameter to get new unique ENT_TYPE that can be used for custom EntityDB +---@return ENT_TYPE +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_depths integer[] +---@param l LAYER ---@return integer[] -function get_entities_overlapping_hitbox(entity_types, mask, hitbox, layer) end ----Get uids of matching entities overlapping with the given hitbox. Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types ----@param entity_type ENT_TYPE ----@param mask integer ----@param hitbox AABB ----@param layer LAYER +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_depth integer +---@param l LAYER ---@return integer[] -function get_entities_overlapping_hitbox(entity_type, mask, hitbox, layer) end ----Attaches `attachee` to `overlay`, similar to setting `get_entity(attachee).overlay = get_entity(overlay)`. ----However this function offsets `attachee` (so you don't have to) and inserts it into `overlay`'s inventory. ----@param overlay_uid integer ----@param attachee_uid integer ----@return nil -function attach_entity(overlay_uid, attachee_uid) end ----Get the `flags` field from entity by uid ----@param uid integer +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 -function get_entity_flags(uid) end ----Set the `flags` field from entity by uid ----@param uid integer ----@param flags integer ----@return nil -function set_entity_flags(uid, flags) end ----Get the `more_flags` field from entity by uid ----@param uid integer +function get_current_money() end +---Adds money to the state.money_shop_total and displays the effect on the HUD for money change +---Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction +---@param amount integer +---@param display_time integer? ---@return integer -function get_entity_flags2(uid) end ----Set the `more_flags` field from entity by uid ----@param uid integer ----@param flags integer ----@return nil -function set_entity_flags2(uid, flags) end ----Get `state.level_flags` +function add_money(amount, display_time) end +---Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change +---Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction +---@param amount integer +---@param player_slot integer +---@param display_time integer? ---@return integer -function get_level_flags() end ----Set `state.level_flags` ----@param flags integer +function add_money_slot(amount, player_slot, display_time) end +---Destroys all layers and all entities in the level. Usually a bad idea, unless you also call create_level and spawn the player back in. ---@return nil -function set_level_flags(flags) end ----Get the ENT_TYPE... of the entity by uid ----@param uid integer ----@return ENT_TYPE -function get_entity_type(uid) end ----Get the current set zoom level ----@return number -function get_zoom_level() end ----Get the game coordinates at the screen position (`x`, `y`) +function destroy_level() end +---Destroys a layer and all entities in it. +---@param layer integer +---@return nil +function destroy_layer(layer) end +---Initializes an empty front and back layer that don't currently exist. Does nothing(?) if layers already exist. +---@return nil +function create_level() end +---Initializes an empty layer that doesn't currently exist. +---@param layer integer +---@return nil +function create_layer(layer) end +---Returns true if the level pause hack is enabled +---@return boolean +function get_start_level_paused() end +---Converts INPUTS to (x, y, BUTTON) +---@param inputs INPUTS +---@return number, number, BUTTON +function inputs_to_buttons(inputs) end +---Converts (x, y, BUTTON) to INPUTS ---@param x number ---@param y number ----@return number, number -function game_position(x, y) end ----Translate an entity position to screen position to be used in drawing functions +---@param buttons BUTTON +---@return INPUTS +function buttons_to_inputs(x, y, buttons) end +---Disable the Infinite Loop Detection of 420 million instructions per frame, if you know what you're doing and need to perform some serious calculations that hang the game updates for several seconds. +---@param enable boolean +---@return nil +function set_infinite_loop_detection_enabled(enable) end +---Set multiplier (default 1.0) for a QueryPerformanceCounter hook based speedhack, similar to the one in Cheat Engine. Call without arguments to reset. Also see [set_frametime](https://spelunky-fyi.github.io/overlunky/#set_frametime) +---@param multiplier number? +---@return nil +function set_speedhack(multiplier) end +---Get the current speedhack multiplier +---@return number +function get_speedhack() end +---Retrieves the current value of the performance counter, which is a high resolution (<1us) time stamp that can be used for time-interval measurements. +---@return integer +function get_performance_counter() end +---Retrieves the frequency of the performance counter. The frequency of the performance counter is fixed at system boot and is consistent across all processors. Therefore, the frequency need only be queried upon application initialization, and the result can be cached. +---@return integer +function get_performance_frequency() end +---Initializes some adventure run related values and loads the character select screen, as if starting a new adventure run from the Play menu. Character select can be skipped by changing `state.screen_next` right after calling this function, maybe with `warp()`. If player isn't already selected, make sure to set `state.items.player_select` and `state.items.player_count` appropriately too. +---@return nil +function play_adventure() end +---Initializes some seeded run related values and loads the character select screen, as if starting a new seeded run after entering the seed. +---@param seed integer? +---@return nil +function play_seeded(seed) 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) ) +---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 +function speechbubble_visible() end +---@return nil +function cancel_toast() end +---@return nil +function cancel_speechbubble() end +---Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. +---@param slot integer +---@return nil +function save_state(slot) end +---Load level state from slot 1..4, if a save_state was made in this level. +---@param slot integer +---@return nil +function load_state(slot) end +---Clear save state from slot 1..4. +---@param slot integer +---@return nil +function clear_state(slot) end +---Get StateMemory from a save_state slot. +---@param slot integer +---@return StateMemory +function get_save_state(slot) end +---Get the thread-local version of state +---@return StateMemory +function get_local_state() end +---Get the thread-local version of players +---@return Player[] +function get_local_players() end +---Warp to a level immediately. +---@param world integer +---@param level integer +---@param theme integer +---@return nil +function warp(world, level, theme) end +---Set seed and reset run. +---@param seed integer +---@return nil +function set_seed(seed) end +---Get `state.level_flags` +---@return integer +function get_level_flags() end +---Set `state.level_flags` +---@param flags integer +---@return nil +function set_level_flags(flags) end +---Returns how many of a specific entity type Waddler has stored +---@param entity_type ENT_TYPE +---@return integer +function waddler_count_entity(entity_type) end +---Store an entity type in Waddler's storage. Returns the slot number the item was stored in or -1 when storage is full and the item couldn't be stored. +---@param entity_type ENT_TYPE +---@return integer +function waddler_store_entity(entity_type) end +---Removes an entity type from Waddler's storage. Second param determines how many of the item to remove (default = remove all) +---@param entity_type ENT_TYPE +---@param amount_to_remove integer +---@return nil +function waddler_remove_entity(entity_type, amount_to_remove) end +---Gets the 16-bit meta-value associated with the entity type in the associated slot +---@param slot integer +---@return integer +function waddler_get_entity_meta(slot) end +---Sets the 16-bit meta-value associated with the entity type in the associated slot +---@param slot integer +---@param meta integer +---@return nil +function waddler_set_entity_meta(slot, meta) end +---Gets the entity type of the item in the provided slot +---@param slot integer +---@return integer +function waddler_entity_type_in_slot(slot) end +---Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. +---@return nil +function update_state() end +---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 +---Returns RawInput, a game structure for raw keyboard and controller state +---@return RawInput +function get_raw_input() end +---Seed the game prng. +---@param seed integer +---@return nil +function seed_prng(seed) end +---Get the thread-local version of prng +---@return PRNG +function get_local_prng() end +---Get the Entity behind an uid, converted to the correct type. To see what type you will get, consult the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) +---@param uid integer +---@return Entity +function get_entity(uid) end +---Get the [EntityDB](https://spelunky-fyi.github.io/overlunky/#EntityDB) behind an ENT_TYPE... +---@param id ENT_TYPE +---@return EntityDB +function get_type(id) end +---Get the ENT_TYPE... of the entity by uid +---@param uid integer +---@return ENT_TYPE +function get_entity_type(uid) end +---Get localized name of an entity from the journal, pass `fallback_strategy` as `true` to fall back to the `ENT_TYPE.*` enum name +---if the entity has no localized name +---@param type ENT_TYPE +---@param fallback_strategy boolean? +---@return string +function get_entity_name(type, fallback_strategy) end +---Teleport entity to coordinates with optional velocity +---@param uid integer ---@param x number ---@param y number ----@return number, number -function screen_position(x, y) end ----Translate a distance of `x` tiles to screen distance to be be used in drawing functions +---@param vx number +---@param vy number +---@return nil +function move_entity(uid, x, y, vx, vy) end +---Teleport entity to coordinates with optional velocity +---@param uid integer ---@param x number ----@return number -function screen_distance(x) end +---@param y number +---@param vx number +---@param vy number +---@param layer LAYER +---@return nil +function move_entity(uid, x, y, vx, vy, layer) end +---Teleport grid entity, the destination should be whole number, this ensures that the collisions will work properly +---@param uid integer +---@param x number +---@param y number +---@param layer LAYER +---@return nil +function move_grid_entity(uid, x, y, layer) end +---Destroy the grid entity (by uid or position), and its item entities, removing them from the grid without dropping particles or gold. +---Will also destroy monsters or items that are standing on a linked activefloor or chain, though excludes MASK.PLAYER to prevent crashes +---@param uid integer +---@return nil +function destroy_grid(uid) end +---Destroy the grid entity (by uid or position), and its item entities, removing them from the grid without dropping particles or gold. +---Will also destroy monsters or items that are standing on a linked activefloor or chain, though excludes MASK.PLAYER to prevent crashes +---@param x number +---@param y number +---@param layer LAYER +---@return nil +function destroy_grid(x, y, layer) end +---Attaches `attachee` to `overlay`, similar to setting `get_entity(attachee).overlay = get_entity(overlay)`. +---However this function offsets `attachee` (so you don't have to) and inserts it into `overlay`'s inventory. +---@param overlay_uid integer +---@param attachee_uid integer +---@return nil +function attach_entity(overlay_uid, attachee_uid) end +---Get the `flags` field from entity by uid +---@param uid integer +---@return ENT_FLAG +function get_entity_flags(uid) end +---Set the `flags` field from entity by uid +---@param uid integer +---@param flags ENT_FLAG +---@return nil +function set_entity_flags(uid, flags) end +---Get the `more_flags` field from entity by uid +---@param uid integer +---@return ENT_MORE_FLAG +function get_entity_flags2(uid) end +---Set the `more_flags` field from entity by uid +---@param uid integer +---@param flags ENT_MORE_FLAG +---@return nil +function set_entity_flags2(uid, flags) end ---Get position `x, y, layer` of entity by uid. Use this, don't use `Entity.x/y` because those are sometimes just the offset to the entity ---you're standing on, not real level coordinates. ---@param uid integer @@ -686,20 +883,6 @@ function entity_remove_item(uid, item_uid, check_autokill) end ---@param off_y number ---@return integer function attach_ball_and_chain(uid, off_x, off_y) end ----Spawn an entity of `entity_type` attached to some other entity `over_uid`, in offset `x`, `y` ----@param entity_type ENT_TYPE ----@param over_uid integer ----@param x number ----@param y number ----@return integer -function spawn_entity_over(entity_type, over_uid, x, y) end ----Short for [spawn_entity_over](https://spelunky-fyi.github.io/overlunky/#spawn_entity_over) ----@param entity_type ENT_TYPE ----@param over_uid integer ----@param x number ----@param y number ----@return integer -function spawn_over(entity_type, over_uid, x, y) end ---Check if the entity `uid` has some specific `item_uid` by uid in their inventory ---@param uid integer ---@param item_uid integer @@ -718,13 +901,13 @@ function entity_has_item_type(uid, entity_type) end ---Gets uids of entities attached to given entity uid. Use `entity_type` and `mask` ([MASK](https://spelunky-fyi.github.io/overlunky/#MASK)) to filter, set them to 0 to return all attached entities. ---@param uid integer ---@param entity_types ENT_TYPE[] ----@param mask integer +---@param mask MASK ---@return integer[] function entity_get_items_by(uid, entity_types, mask) end ---Gets uids of entities attached to given entity uid. Use `entity_type` and `mask` ([MASK](https://spelunky-fyi.github.io/overlunky/#MASK)) to filter, set them to 0 to return all attached entities. ---@param uid integer ---@param entity_type ENT_TYPE ----@param mask integer +---@param mask MASK ---@return integer[] function entity_get_items_by(uid, entity_type, mask) end ---Kills an entity by uid. `destroy_corpse` defaults to `true`, if you are killing for example a caveman and want the corpse to stay make sure to pass `false`. @@ -754,1198 +937,999 @@ function worn_backitem(who_uid) end ---@param uid integer ---@return nil function apply_entity_db(uid) end ----Try to lock the exit at coordinates ----@param x number ----@param y number ----@return nil -function lock_door_at(x, y) end ----Try to unlock the exit at coordinates ----@param x number ----@param y number +---Calculate the tile distance of two entities by uid +---@param uid_a integer +---@param uid_b integer +---@return number +function distance(uid_a, uid_b) end +---Poisons entity, to cure poison set [Movable](https://spelunky-fyi.github.io/overlunky/#Movable).`poison_tick_timer` to -1 +---@param entity_uid integer ---@return nil -function unlock_door_at(x, y) end ----Get the current frame count since the game was started. You can use this to make some timers yourself, the engine runs at 60fps. This counter is paused if you block PRE_UPDATE from running, and also doesn't increment during some loading screens, even though state update still runs. ----@return integer -function get_frame() end ----Get the current global frame count since the game was started. You can use this to make some timers yourself, the engine runs at 60fps. This counter keeps incrementing when state is updated, even during loading screens. ----@return integer -function get_global_frame() end ----Get the current timestamp in milliseconds since the Unix Epoch. +function poison_entity(entity_uid) end +---Same as `Player.get_name` +---@param type_id ENT_TYPE +---@return string +function get_character_name(type_id) end +---Same as `Player.get_short_name` +---@param type_id ENT_TYPE +---@return string +function get_character_short_name(type_id) end +---Same as `Player.get_heart_color` +---@param type_id ENT_TYPE +---@return Color +function get_character_heart_color(type_id) end +---Same as `Player.is_female` +---@param type_id ENT_TYPE +---@return boolean +function is_character_female(type_id) end +---Same as `Player.set_heart_color` +---@param type_id ENT_TYPE +---@param color Color ---@return nil -function get_ms() end +function set_character_heart_color(type_id, color) end +---Make an ENT_TYPE.FLOOR_DOOR_EXIT go to world `w`, level `l`, theme `t` +---@param uid integer +---@param w integer +---@param l integer +---@param t integer +---@return nil +function set_door_target(uid, w, l, t) end +---Short for [set_door_target](https://spelunky-fyi.github.io/overlunky/#set_door_target). +---@param uid integer +---@param w integer +---@param l integer +---@param t integer +---@return nil +function set_door(uid, w, l, t) end +---Get door target `world`, `level`, `theme` +---@param uid integer +---@return integer, integer, integer +function get_door_target(uid) end +---Calls the enter door function, position doesn't matter, can also enter closed doors (like COG, EW) without unlocking them +---@param player_uid integer +---@param door_uid integer +---@return nil +function enter_door(player_uid, door_uid) end ---Make `mount_uid` carry `rider_uid` on their back. Only use this with actual mounts and living things. ---@param mount_uid integer ---@param rider_uid integer ---@return nil function carry(mount_uid, rider_uid) end ----Sets the amount of blood drops in the Kapala needed to trigger a health increase (default = 7). ----@param threshold integer ----@return nil -function set_kapala_blood_threshold(threshold) end ----Sets the hud icon for the Kapala (0-6 ; -1 for default behaviour). ----If you set a Kapala treshold greater than 7, make sure to set the hud icon in the range 0-6, or other icons will appear in the hud! ----@param icon_index integer ----@return nil -function set_kapala_hud_icon(icon_index) end ----Changes characteristics of (all) sparktraps: speed, rotation direction and distance from center ----Speed: expressed as the amount that should be added to the angle every frame (use a negative number to go in the other direction) ----Distance from center: if you go above 3.0 the game might crash because a spark may go out of bounds! ----@param angle_increment number ----@param distance number ----@return nil -function modify_sparktraps(angle_increment, distance) end ----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 ----@param activate boolean ----@return nil -function activate_sparktraps_hack(activate) end ----Set layer to search for storage items on ----@param layer LAYER ----@return nil -function set_storage_layer(layer) end ----Flip entity around by uid. All new entities face right by default. +---Make a `CustomMovableBehavior`, if `base_behavior` is `nil` you will have to set all of the +---behavior functions. If a behavior with `behavior_name` already exists for your script it will +---be returned instead. +---@param behavior_name string +---@param state_id integer +---@param base_behavior VanillaMovableBehavior +---@return CustomMovableBehavior +function make_custom_behavior(behavior_name, state_id, base_behavior) end +---Get the [ParticleDB](https://spelunky-fyi.github.io/overlunky/#ParticleDB) details of the specified ID +---@param id PARTICLEEMITTER +---@return ParticleDB +function get_particle_type(id) end +---Generate particles of the specified type around the specified entity uid (use e.g. `local emitter = generate_world_particles(PARTICLEEMITTER.PETTING_PET, players[1].uid)`). You can then decouple the emitter from the entity with `emitter.entity_uid = -1` and freely move it around. See the `particles.lua` example script for more details. +---@param particle_emitter_id PARTICLEEMITTER ---@param uid integer ----@return nil -function flip_entity(uid) end ----Sets the Y-level at which Olmec changes phases ----@param phase integer +---@return ParticleEmitterInfo +function generate_world_particles(particle_emitter_id, uid) end +---Generate particles of the specified type at a certain screen coordinate (use e.g. `local emitter = generate_screen_particles(PARTICLEEMITTER.CHARSELECTOR_TORCHFLAME_FLAMES, 0.0, 0.0)`). See the `particles.lua` example script for more details. +---@param particle_emitter_id PARTICLEEMITTER +---@param x number ---@param y number +---@return ParticleEmitterInfo +function generate_screen_particles(particle_emitter_id, x, y) end +---Advances the state of the screen particle emitter (simulates the next positions, ... of all the particles in the emitter). Only used with screen particle emitters. See the `particles.lua` example script for more details. +---@param particle_emitter ParticleEmitterInfo ---@return nil -function set_olmec_phase_y_level(phase, y) end ----Forces Olmec to stay on phase 0 (stomping) ----@param b boolean ----@return nil -function force_olmec_phase_0(b) end ----Determines when the ghost appears, either when the player is cursed or not ----@param normal integer ----@param cursed integer ----@return nil -function set_ghost_spawn_times(normal, cursed) end ----Determines whether the ghost appears when breaking the ghost pot ----@param enable boolean ----@return nil -function set_cursepot_ghost_enabled(enable) end ----Determines whether the time ghost appears, including the showing of the ghost toast ----@param b boolean ----@return nil -function set_time_ghost_enabled(b) end ----Determines whether the time jelly appears in cosmic ocean ----@param b boolean ----@return nil -function set_time_jelly_enabled(b) end ----Enables or disables the journal ----@param b boolean ----@return nil -function set_journal_enabled(b) end ----Enables or disables the default position based camp camera bounds, to set them manually yourself ----@param b boolean ----@return nil -function set_camp_camera_bounds_enabled(b) end ----Sets which entities are affected by a bomb explosion. Default = MASK.PLAYER | MASK.MOUNT | MASK.MONSTER | MASK.ITEM | MASK.ACTIVEFLOOR | MASK.FLOOR ----@param mask integer +function advance_screen_particles(particle_emitter) end +---Renders the particles to the screen. Only used with screen particle emitters. See the `particles.lua` example script for more details. +---@param particle_emitter ParticleEmitterInfo ---@return nil -function set_explosion_mask(mask) end ----Sets the maximum length of a thrown rope (anchor segment not included). Unfortunately, setting this higher than default (6) creates visual glitches in the rope, even though it is fully functional. ----@param length integer +function render_screen_particles(particle_emitter) end +---Extinguish a particle emitter (use the return value of `generate_world_particles` or `generate_screen_particles` as the parameter in this function) +---@param particle_emitter ParticleEmitterInfo ---@return nil -function set_max_rope_length(length) end ----Checks whether a coordinate is inside a room containing an active shop. This function checks whether the shopkeeper is still alive. +function extinguish_particles(particle_emitter) end +---Default function in spawn definitions to check whether a spawn is valid or not ---@param x number ---@param y number ---@param layer LAYER ---@return boolean -function is_inside_active_shop_room(x, y, layer) end ----Checks whether a coordinate is inside a shop zone, the rectangle where the camera zooms in a bit. Does not check if the shop is still active! +function default_spawn_is_valid(x, y, layer) end +---Check if position satisfies the given POS_TYPE flags, to be used in a custom is_valid function procedural for spawns. ---@param x number ---@param y number ---@param layer LAYER +---@param flags POS_TYPE ---@return boolean -function is_inside_shop_zone(x, y, layer) end ----Returns how many of a specific entity type Waddler has stored ----@param entity_type ENT_TYPE ----@return integer -function waddler_count_entity(entity_type) end ----Store an entity type in Waddler's storage. Returns the slot number the item was stored in or -1 when storage is full and the item couldn't be stored. ----@param entity_type ENT_TYPE ----@return integer -function waddler_store_entity(entity_type) end ----Removes an entity type from Waddler's storage. Second param determines how many of the item to remove (default = remove all) ----@param entity_type ENT_TYPE ----@param amount_to_remove integer ----@return nil -function waddler_remove_entity(entity_type, amount_to_remove) end ----Gets the 16-bit meta-value associated with the entity type in the associated slot ----@param slot integer ----@return integer -function waddler_get_entity_meta(slot) end ----Sets the 16-bit meta-value associated with the entity type in the associated slot ----@param slot integer ----@param meta integer ----@return nil -function waddler_set_entity_meta(slot, meta) end ----Gets the entity type of the item in the provided slot ----@param slot integer +function position_is_valid(x, y, layer, flags) end +---Add a callback for a specific tile code that is called before the game handles the tile code. +---Return true in order to stop the game or scripts loaded after this script from handling this tile code. +---For example, when returning true in this callback set for `"floor"` then no floor will spawn in the game (unless you spawn it yourself) +---The callback signature is bool pre_tile_code(float x, float y, int layer, ROOM_TEMPLATE room_template) +---@param cb fun(x: number, y: number, layer: integer, room_template: ROOM_TEMPLATE): boolean +---@param tile_code string +---@return CallbackId +function set_pre_tile_code_callback(cb, tile_code) end +---Add a callback for a specific tile code that is called after the game handles the tile code. +---Use this to affect what the game or other scripts spawned in this position. +---This is received even if a previous pre-tile-code-callback has returned true +---The callback signature is nil post_tile_code(float x, float y, int layer, ROOM_TEMPLATE room_template) +---@param cb fun(x: number, y: number, layer: integer, room_template: ROOM_TEMPLATE): nil +---@param tile_code string +---@return CallbackId +function set_post_tile_code_callback(cb, tile_code) end +---Define a new tile code, to make this tile code do anything you have to use either [set_pre_tile_code_callback](https://spelunky-fyi.github.io/overlunky/#set_pre_tile_code_callback) or [set_post_tile_code_callback](https://spelunky-fyi.github.io/overlunky/#set_post_tile_code_callback). +---If a user disables your script but still uses your level mod nothing will be spawned in place of your tile code. +---@param tile_code string +---@return TILE_CODE +function define_tile_code(tile_code) end +---Gets a short tile code based on definition, returns `nil` if it can't be found +---@param short_tile_code_def ShortTileCodeDef +---@return integer? +function get_short_tile_code(short_tile_code_def) end +---Gets the definition of a short tile code (if available), will vary depending on which file is loaded +---@param short_tile_code SHORT_TILE_CODE +---@return ShortTileCodeDef? +function get_short_tile_code_definition(short_tile_code) end +---Define a new procedural spawn, the function `nil do_spawn(float x, float y, LAYER layer)` contains your code to spawn the thing, whatever it is. +---The function `bool is_valid(float x, float y, LAYER layer)` determines whether the spawn is legal in the given position and layer. +---Use for example when you can spawn only on the ceiling, under water or inside a shop. +---Set `is_valid` to `nil` in order to use the default rule (aka. on top of floor and not obstructed). +---If a user disables your script but still uses your level mod nothing will be spawned in place of your procedural spawn. +---@param procedural_spawn string +---@param do_spawn fun(x: number, y: number, layer: LAYER): nil +---@param is_valid fun(x: number, y: number, layer: LAYER): boolean +---@return PROCEDURAL_CHANCE +function define_procedural_spawn(procedural_spawn, do_spawn, is_valid) end +---Define a new extra spawn, these are semi-guaranteed level gen spawns with a fixed upper bound. +---The function `nil do_spawn(float x, float y, LAYER layer)` contains your code to spawn the thing, whatever it is. +---The function `bool is_valid(float x, float y, LAYER layer)` determines whether the spawn is legal in the given position and layer. +---Use for example when you can spawn only on the ceiling, under water or inside a shop. +---Set `is_valid` to `nil` in order to use the default rule (aka. on top of floor and not obstructed). +---To change the number of spawns use `PostRoomGenerationContext:set_num_extra_spawns` during `ON.POST_ROOM_GENERATION` +---No name is attached to the extra spawn since it is not modified from level files, instead every call to this function will return a new unique id. +---@param do_spawn fun(x: number, y: number, layer: LAYER): nil +---@param is_valid fun(x: number, y: number, layer: LAYER): boolean +---@param num_spawns_frontlayer integer +---@param num_spawns_backlayer integer ---@return integer -function waddler_entity_type_in_slot(slot) end ----Spawn a companion (hired hand, player character, eggplant child) ----@param companion_type ENT_TYPE +function define_extra_spawn(do_spawn, is_valid, num_spawns_frontlayer, num_spawns_backlayer) end +---Use to query whether any of the requested spawns could not be made, usually because there were not enough valid spaces in the level. +---Returns missing spawns in the front layer and missing spawns in the back layer in that order. +---The value only makes sense after level generation is complete, aka after `ON.POST_LEVEL_GENERATION` has run. +---@param extra_spawn_chance_id integer +---@return integer, integer +function get_missing_extra_spawns(extra_spawn_chance_id) end +---Transform a position to a room index to be used in `get_room_template` and `PostRoomGenerationContext.set_room_template` ---@param x number ---@param y number +---@return integer, integer +function get_room_index(x, y) end +---Transform a room index into the top left corner position in the room +---@param x integer +---@param y integer +---@return number, number +function get_room_pos(x, y) end +---Get the room template given a certain index, returns `nil` if coordinates are out of bounds +---@param x integer +---@param y integer ---@param layer LAYER ----@return integer -function spawn_companion(companion_type, x, y, layer) end ----Calculate the tile distance of two entities by uid ----@param uid_a integer ----@param uid_b integer ----@return number -function distance(uid_a, uid_b) end ----Basically gets the absolute coordinates of the area inside the unbreakable bedrock walls, from wall to wall. Every solid entity should be ----inside these boundaries. The order is: left x, top y, right x, bottom y ----@return number, number, number, number -function get_bounds() end ----Same as [get_bounds](https://spelunky-fyi.github.io/overlunky/#get_bounds) but returns AABB struct instead of loose floats ----@return AABB -function get_aabb_bounds() end ----Gets the current camera position in the level ----@return number, number -function get_camera_position() end ----Sets the absolute current camera position without rubberbanding animation. Ignores camera bounds or currently focused uid, but doesn't clear them. Best used in ON.RENDER_PRE_GAME or similar. See Camera for proper camera handling with bounds and rubberbanding. ----@param cx number ----@param cy number ----@return nil -function set_camera_position(cx, cy) end ----Updates the camera focus according to the params set in Camera, i.e. to apply normal camera movement when paused etc. ----@return nil -function update_camera_position() end ----Set the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. ----@param flags Flags ----@param bit integer ----@return Flags -function set_flag(flags, bit) end ----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. ----@param flags Flags ----@param bit integer ----@return Flags -function clr_flag(flags, bit) end ----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. ----@param flags Flags ----@param bit integer ----@return Flags -function flip_flag(flags, bit) end ----Returns true if the nth bit is set in the number. ----@param flags Flags ----@param bit integer +---@return integer? +function get_room_template(x, y, layer) end +---Get whether a room is flipped at the given index, returns `false` if coordinates are out of bounds +---@param x integer +---@param y integer ---@return boolean -function test_flag(flags, bit) end ----Set a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. ----@param flags Flags ----@param mask Flags ----@return Flags -function set_mask(flags, mask) end ----Clears a bitmask in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. ----@param flags Flags ----@param mask Flags ----@return Flags -function clr_mask(flags, mask) end ----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. ----@param flags Flags ----@param mask Flags ----@return Flags -function flip_mask(flags, mask) end ----Returns true if a bitmask is set in the number. ----@param flags Flags ----@param mask Flags +function is_room_flipped(x, y) end +---Get whether a room is the origin of a machine room +---@param x integer +---@param y integer ---@return boolean -function test_mask(flags, mask) end ----Gets the resolution (width and height) of the screen ----@return integer, integer -function get_window_size() end ----Clears a callback that is specific to a screen. ----@param screen_id integer ----@param cb_id CallbackId ----@return nil -function clear_screen_callback(screen_id, cb_id) end ----Returns unique id for the callback to be used in [clear_screen_callback](https://spelunky-fyi.github.io/overlunky/#clear_screen_callback) or `nil` if screen_id is not valid. ----Sets a callback that is called right before the screen is drawn, return `true` to skip the default rendering. ----The callback signature is bool render_screen(Screen self, VanillaRenderContext render_ctx) ----@param screen_id integer ----@param fun fun(self: Screen, render_ctx: VanillaRenderContext): boolean ----@return CallbackId? -function set_pre_render_screen(screen_id, fun) end ----Returns unique id for the callback to be used in [clear_screen_callback](https://spelunky-fyi.github.io/overlunky/#clear_screen_callback) or `nil` if screen_id is not valid. ----Sets a callback that is called right after the screen is drawn. ----The callback signature is nil render_screen(Screen self, VanillaRenderContext render_ctx) ----@param screen_id integer ----@param fun fun(self: Screen, render_ctx: VanillaRenderContext): nil ----@return CallbackId? -function set_post_render_screen(screen_id, fun) end ----Returns unique id for the callback to be used in [clear_callback](https://spelunky-fyi.github.io/overlunky/#clear_callback) or `nil` if uid is not valid. ----Sets a callback that is called right when an player/hired hand is crushed/insta-gibbed, return `true` to skip the game's crush handling. ----The game's instagib function will be forcibly executed (regardless of whatever you return in the callback) when the entity's health is zero. ----This is so that when the entity dies (from other causes), the death screen still gets shown. ----Use this only when no other approach works, this call can be expensive if overused. ----The callback signature is bool on_player_instagib(Entity self) ----@param uid integer ----@param fun fun(self: Entity): boolean ----@return CallbackId? -function set_on_player_instagib(uid, fun) end ----Raise a signal and probably crash the game ----@return nil -function raise() end ----Convert the hash to stringid ----Check [strings00_hashed.str](https://github.com/spelunky-fyi/overlunky/blob/main/docs/game_data/strings00_hashed.str) for the hash values, or extract assets with modlunky and check those. ----@param hash integer ----@return STRINGID -function hash_to_stringid(hash) end ----Get string behind STRINGID, **don't use stringid directly for vanilla string**, use [hash_to_stringid](https://spelunky-fyi.github.io/overlunky/#hash_to_stringid) first ----Will return the string of currently choosen language ----@param string_id STRINGID ----@return string -function get_string(string_id) end ----Change string at the given id (**don't use stringid directly for vanilla string**, use [hash_to_stringid](https://spelunky-fyi.github.io/overlunky/#hash_to_stringid) first) ----This edits custom string and in game strings but changing the language in settings will reset game strings ----@param id STRINGID ----@param str string ----@return nil -function change_string(id, str) end ----Add custom string, currently can only be used for names of shop items (EntityDB->description) ----Returns STRINGID of the new string ----@param str string ----@return STRINGID -function add_string(str) end ----Get localized name of an entity from the journal, pass `fallback_strategy` as `true` to fall back to the `ENT_TYPE.*` enum name ----if the entity has no localized name ----@param type ENT_TYPE ----@param fallback_strategy boolean? +function is_machine_room_origin(x, y) end +---For debugging only, get the name of a room template, returns `'invalid'` if room template is not defined +---@param room_template integer ---@return string -function get_entity_name(type, fallback_strategy) end ----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 ----@param uid integer ----@param name string ----@return nil -function add_custom_name(uid, name) end ----Clears the name set with [add_custom_name](https://spelunky-fyi.github.io/overlunky/#add_custom_name) ----@param uid integer ----@return nil -function clear_custom_name(uid) end ----Calls the enter door function, position doesn't matter, can also enter closed doors (like COG, EW) without unlocking them ----@param player_uid integer ----@param door_uid integer +function get_room_template_name(room_template) end +---Define a new room template to use with `set_room_template` +---@param room_template string +---@param type ROOM_TEMPLATE_TYPE +---@return integer +function define_room_template(room_template, type) end +---Set the size of room template in tiles, the template must be of type `ROOM_TEMPLATE_TYPE.MACHINE_ROOM`. +---@param room_template integer +---@param width integer +---@param height integer +---@return boolean +function set_room_template_size(room_template, width, height) end +---Get the inverse chance of a procedural spawn for the current level. +---A return value of 0 does not mean the chance is infinite, it means the chance is zero. +---@param chance_id PROCEDURAL_CHANCE +---@return integer +function get_procedural_spawn_chance(chance_id) end +---Gets the sub theme of the current cosmic ocean level, returns COSUBTHEME.NONE if the current level is not a CO level. +---@return COSUBTHEME +function get_co_subtheme() end +---Forces the theme of the next cosmic ocean level(s) (use e.g. `force_co_subtheme(COSUBTHEME.JUNGLE)`. Use COSUBTHEME.RESET to reset to default random behaviour) +---@param subtheme COSUBTHEME ---@return nil -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} ----Use empty table as argument to reset to the game default ----@param ent_types ENT_TYPE[] +function force_co_subtheme(subtheme) end +---Gets the value for the specified config +---@param config LEVEL_CONFIG +---@return integer +function get_level_config(config) end +---Set the value for the specified config +---@param config LEVEL_CONFIG +---@param value integer ---@return nil -function change_sunchallenge_spawns(ent_types) end ----Change ENT_TYPE's spawned in dice shops (Madame Tusk as well), by default there are 25: ----{ITEM_PICKUP_BOMBBAG, ITEM_PICKUP_BOMBBOX, ITEM_PICKUP_ROPEPILE, ITEM_PICKUP_COMPASS, ITEM_PICKUP_PASTE, ITEM_PICKUP_PARACHUTE, ITEM_PURCHASABLE_CAPE, ITEM_PICKUP_SPECTACLES, ITEM_PICKUP_CLIMBINGGLOVES, ITEM_PICKUP_PITCHERSMITT, ----ENT_TYPE_ITEM_PICKUP_SPIKESHOES, ENT_TYPE_ITEM_PICKUP_SPRINGSHOES, ITEM_MACHETE, ITEM_BOOMERANG, ITEM_CROSSBOW, ITEM_SHOTGUN, ITEM_FREEZERAY, ITEM_WEBGUN, ITEM_CAMERA, ITEM_MATTOCK, ITEM_PURCHASABLE_JETPACK, ITEM_PURCHASABLE_HOVERPACK, ----ITEM_TELEPORTER, ITEM_PURCHASABLE_TELEPORTER_BACKPACK, ITEM_PURCHASABLE_POWERPACK} ----Min 6, Max 255, if you want less then 6 you need to write some of them more then once (they will have higher "spawn chance"). ----If you use this function in the level with dice shop in it, you have to update `item_ids` in the [ITEM_DICE_PRIZE_DISPENSER](https://spelunky-fyi.github.io/overlunky/#PrizeDispenser). ----Use empty table as argument to reset to the game default ----@param ent_types ENT_TYPE[] +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_length integer +---@param area AABB +---@param destroy_broken boolean ---@return nil -function change_diceshop_prizes(ent_types) end ----Change ENT_TYPE's spawned when you damage the altar, by default there are 6: ----{MONS_BAT, MONS_BEE, MONS_SPIDER, MONS_JIANGSHI, MONS_FEMALE_JIANGSHI, MONS_VAMPIRE} ----Max 255 types. ----Use empty table as argument to reset to the game default ----@param ent_types ENT_TYPE[] +function grow_vines(l, max_length, area, destroy_broken) 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_length integer ---@return nil -function change_altar_damage_spawns(ent_types) end ----Change ENT_TYPE's spawned when Waddler dies, by default there are 3: ----{ITEM_PICKUP_COMPASS, ITEM_CHEST, ITEM_KEY} ----Max 255 types. ----Use empty table as argument to reset to the game default ----@param ent_types ENT_TYPE[] +function grow_vines(l, max_length) 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_length integer +---@param area AABB +---@param destroy_broken boolean ---@return nil -function change_waddler_drop(ent_types) end ----Poisons entity, to cure poison set [Movable](https://spelunky-fyi.github.io/overlunky/#Movable).`poison_tick_timer` to -1 ----@param entity_uid integer +function grow_poles(l, max_length, 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_length integer ---@return nil -function poison_entity(entity_uid) end ----Change how much health the ankh gives you after death, with every beat (the heart beat effect) it will add `beat_add_health` to your health, ----`beat_add_health` has to be divisor of `health` and can't be 0, otherwise the function does nothing. Set `health` to 0 to return to the game defaults ----If you set `health` above the game max health it will be forced down to the game max ----@param max_health integer ----@param beat_add_health integer +function grow_poles(l, max_length) 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 +function grow_chainandblocks() 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) +---@param x integer +---@param y integer +---@return boolean +function grow_chainandblocks(x, y) end +---Immediately load a screen based on [state](https://spelunky-fyi.github.io/overlunky/#state).screen_next and stuff ---@return nil -function modify_ankh_health_gain(max_health, beat_add_health) end ----Adds entity as shop item, has to be of [Purchasable](https://spelunky-fyi.github.io/overlunky/#Purchasable) type, check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) to find all the Purchasable entity types. ----Adding other entities will result in not obtainable items or game crash ----@param item_uid integer ----@param shop_owner_uid integer +function load_screen() end +---Force a theme in PRE_LOAD_LEVEL_FILES, POST_ROOM_GENERATION or PRE_LEVEL_GENERATION to change different aspects of the levelgen. You can pass a CustomTheme, ThemeInfo or THEME. +---@param customtheme CustomTheme|ThemeInfo|THEME ---@return nil -function add_item_to_shop(item_uid, shop_owner_uid) end ----Change the amount of frames after the damage from poison is applied ----@param frames integer +function force_custom_theme(customtheme) end +---Force current subtheme used in the CO theme. You can pass a CustomTheme, ThemeInfo or THEME. Not to be confused with force_co_subtheme. +---@param customtheme CustomTheme|ThemeInfo|THEME ---@return nil -function change_poison_timer(frames) end ----Creates a new Illumination. Don't forget to continuously call [refresh_illumination](https://spelunky-fyi.github.io/overlunky/#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. ----@param pos Vec2 ----@param color Color ----@param type LIGHT_TYPE ----@param size number ----@param flags integer ----@param uid integer ----@param layer LAYER ----@return Illumination -function create_illumination(pos, color, type, size, flags, uid, layer) end ----Creates a new Illumination. Don't forget to continuously call [refresh_illumination](https://spelunky-fyi.github.io/overlunky/#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. ----@param color Color ----@param size number ----@param x number ----@param y number ----@return Illumination -function create_illumination(color, size, x, y) end ----Creates a new Illumination. Don't forget to continuously call [refresh_illumination](https://spelunky-fyi.github.io/overlunky/#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. ----@param color Color ----@param size number ----@param uid integer ----@return Illumination -function create_illumination(color, size, uid) end ----Refreshes an Illumination, keeps it from fading out (updates the timer, keeping it in sync with the game render) ----@param illumination Illumination ----@return nil -function refresh_illumination(illumination) end ----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 +function force_custom_subtheme(customtheme) end +---Loads a sound from disk relative to this script, ownership might be shared with other code that loads the same file. Returns nil if file can't be found +---@param path string +---@return CustomSound? +function create_sound(path) end +---Gets an existing sound, either if a file at the same path was already loaded or if it is already loaded by the game +---@param path_or_vanilla_sound string +---@return CustomSound? +function get_sound(path_or_vanilla_sound) end +---Returns unique id for the callback to be used in [clear_vanilla_sound_callback](https://spelunky-fyi.github.io/overlunky/#clear_vanilla_sound_callback). +---Sets a callback for a vanilla sound which lets you hook creation or playing events of that sound +---Callbacks are executed on another thread, so avoid touching any global state, only the local Lua state is protected +---If you set such a callback and then play the same sound yourself you have to wait until receiving the STARTED event before changing any properties on the sound. Otherwise you may cause a deadlock. +---The callback signature is nil on_vanilla_sound(PlayingSound sound) +---@param name VANILLA_SOUND +---@param types VANILLA_SOUND_CALLBACK_TYPE +---@param cb fun(sound: PlayingSound): nil +---@return CallbackId +function set_vanilla_sound_callback(name, types, cb) end +---Clears a previously set callback +---@param id CallbackId ---@return nil -function fix_liquid_out_of_bounds() end ----Return the name of the first matching number in an enum table ----@param enum table ----@param value integer +function clear_vanilla_sound_callback(id) end +---Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" +---Returns SoundMeta, beware that the sound can't be stopped (`start_over` and `playing` are unavailable). Should only be used for sfx. +---@param sound VANILLA_SOUND +---@param source_uid integer +---@return SoundMeta +function play_sound(sound, source_uid) end +---Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" +---Returns SoundMeta, beware that the sound can't be stopped (`start_over` and `playing` are unavailable). Should only be used for sfx. +---@param sound_id SOUNDID +---@param source_uid integer +---@return SoundMeta +function play_sound(sound_id, source_uid) end +---@param id SOUNDID +---@return VANILLA_SOUND +function convert_sound_id(id) end +---Convert SOUNDID to VANILLA_SOUND and vice versa +---@param sound VANILLA_SOUND +---@return SOUNDID +function convert_sound_id(sound) end +---Calculate the bounding box of text, so you can center it etc. Returns `width`, `height` in screen distance. +---@param size number +---@param text string +---@return number, number +function draw_text_size(size, text) end +---Create image from file. Returns a tuple containing id, width and height. +---Depending on the image size, this can take a moment, preferably don't create them dynamically, rather create all you need in global scope so it will load them as soon as the game starts +---@param path string +---@return IMAGE, integer, integer +function create_image(path) end +---Create image from file, cropped to the geometry provided. Returns a tuple containing id, width and height. +---Depending on the image size, this can take a moment, preferably don't create them dynamically, rather create all you need in global scope so it will load them as soon as the game starts +---@param path string +---@param x integer +---@param y integer +---@param w integer +---@param h integer +---@return IMAGE, integer, integer +function create_image_crop(path, x, y, w, h) end +---Get image size from file. Returns a tuple containing width and height. +---@param path string +---@return integer, integer +function get_image_size(path) end +---Current mouse cursor position in screen coordinates. +---@return number, number +function mouse_position() end +---Returns human readable string from KEY chord (e.g. "Ctrl+X", "Unknown" or "None") ---@return string -function enum_get_name(enum, value) end ----Return all the names of a number in an enum table ----@param enum table ----@param value integer ----@return table -function enum_get_names(enum, value) end ----Return the matching names for a bitmask in an enum table of masks ----@param enum table ----@param value integer ----@return table -function enum_get_mask_names(enum, value) end ----Gets the specified setting, values might need to be interpreted differently per setting ----@param setting GAME_SETTING ----@return integer? -function get_setting(setting) end ----Sets the specified setting temporarily. These values are not saved and might reset to the users real settings if they visit the options menu. (Check example.) All settings are available in unsafe mode and only a smaller subset SAFE_SETTING by default for Hud and other visuals. Returns false, if setting failed. ----@param setting GAME_SETTING ----@param value integer ----@return boolean -function set_setting(setting, value) end ----Short for print(string.format(...)) ----@return nil -function printf() end ----Spawn a Shopkeeper in the coordinates and make the room their shop. Returns the Shopkeeper uid. Also see [spawn_roomowner](https://spelunky-fyi.github.io/overlunky/#spawn_roomowner). ----@param x number ----@param y number ----@param layer LAYER ----@param room_template ROOM_TEMPLATE ----@return integer -function spawn_shopkeeper(x, y, layer, room_template) end ----Spawn a RoomOwner (or a few other like [CavemanShopkeeper](https://spelunky-fyi.github.io/overlunky/#CavemanShopkeeper)) in the coordinates and make them own the room, optionally changing the room template. Returns the RoomOwner uid. ----@param owner_type ENT_TYPE ----@param x number ----@param y number +function key_name() end +---Returns: [ImGuiIO](https://spelunky-fyi.github.io/overlunky/#ImGuiIO) for raw keyboard, mouse and xinput gamepad stuff. +---@return ImGuiIO +function get_io() end +---Returns unique id >= 0 for the callback to be used in [clear_callback](https://spelunky-fyi.github.io/overlunky/#clear_callback) or -1 if the key could not be registered. +---Add callback function to be called on a hotkey, using Windows hotkey api. These hotkeys will override all game and UI input and can work even when the game is unfocused. They are by design very intrusive and won't let anything else use the same key combo. Can't detect if input is active in another instance, use ImGuiIO if you need Playlunky hotkeys to react to Overlunky input state. Key is a KEY combo (e.g. `KEY.OL_MOD_CTRL | KEY.X`), possibly returned by GuiDrawContext:key_picker. Doesn't work with mouse buttons. +---The callback signature is nil on_hotkey(KEY key) +---@param cb fun(key: KEY): nil +---@param key KEY +---@param flags HOTKEY_TYPE +---@return CallbackId +function set_hotkey(cb, key, flags) 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 ----@param room_template ROOM_TEMPLATE ----@return integer -function spawn_roomowner(owner_type, x, y, layer, room_template) end ----Get the current adventure seed pair, or optionally what it was at the start of this run, because it changes every level. ----@param run_start boolean? ----@return integer, integer -function get_adventure_seed(run_start) end ----Set the current adventure seed pair. Use just before resetting a run to recreate an adventure run. ----@param first integer ----@param second integer ---@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, 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. ----Returns a pair of water and lava, in that order. ----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 x number ----@param y number +function set_lut(texture_id, layer) end +---Same as `set_lut(nil, layer)` ---@param layer LAYER ----@return integer, integer -function get_liquids_at(x, y, layer) end ----Disable all crust item spawns, returns whether they were already disabled before the call ----@param disable boolean ----@return boolean -function disable_floor_embeds(disable) end ----Get the rva for a pattern name, used for debugging. ----@param address_name string ----@return string -function get_rva(address_name) end ----Get the rva for a vtable offset and index, used for debugging. ----@param offset VTABLE_OFFSET ----@param index integer ----@return string -function get_virtual_rva(offset, index) end ----Get memory address from a lua object ----@param o any ----@return nil -function get_address(o) end ----Log to spelunky.log ----@param message string ----@return nil -function log_print(message) end ----Immediately ends the run with the death screen, also calls the [save_progress](https://spelunky-fyi.github.io/overlunky/#save_progress) ---@return nil -function load_death_screen() end ----Saves the game to savegame.sav, unless game saves are blocked in the settings. Also runs the ON.SAVE callback. Fails and returns false, if you're trying to save too often (2s). ----@return boolean -function save_progress() end ----Runs the ON.SAVE callback. Fails and returns false, if you're trying to save too often (2s). ----@return boolean -function save_script() end ----Set the level number shown in the hud and journal to any string. This is reset to the default "%d-%d" automatically just before PRE_LOAD_SCREEN to a level or main menu, so use in PRE_LOAD_SCREEN, POST_LEVEL_GENERATION or similar for each level. ----Use "%d-%d" to reset to default manually. Does not affect the "...COMPLETED!" message in transitions or lines in "Dear Journal", you need to edit them separately with [change_string](https://spelunky-fyi.github.io/overlunky/#change_string). ----@param str string ----@return nil -function set_level_string(str) end ----Force the character unlocked in either ending to ENT_TYPE. Set to 0 to reset to the default guys. Does not affect the texture of the actual savior. (See example) ----@param type ENT_TYPE ----@return nil -function set_ending_unlock(type) end ----Get the thread-local version of state ----@return StateMemory -function get_local_state() end ----Get the thread-local version of players ----@return Player[] -function get_local_players() end ----List files in directory relative to the script root. Returns table of file/directory names or nil if not found. ----@param dir string? ----@return nil -function list_dir(dir) end ----List files in directory relative to the mods data directory (Mods/Data/...). Returns table of file/directory names or nil if not found. ----@param dir string? ----@return nil -function list_data_dir(dir) end ----List all char_*.png files recursively from Mods/Packs. Returns table of file paths. ----@return nil -function list_char_mods() end ----Approximate bounding box of the player hud element for player index 1..4 based on user settings and player count ----@param index integer ----@return AABB -function get_hud_position(index) end ----Olmec cutscene moves Olmec and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and Olmec will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with Olmec in the hole. ----@param enable boolean ----@return nil -function set_olmec_cutscene_enabled(enable) end ----Tiamat cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want Tiamat kill to be required ----@param enable boolean ----@return nil -function set_tiamat_cutscene_enabled(enable) end ----Activate custom variables for position used for detecting the player (normally hardcoded) ----note: because those variables are custom and game does not initiate them, you need to do it yourself for each Tiamat entity, recommending set_post_entity_spawn ----default game values are: attack_x = 17.5 attack_y = 62.5 ----@param activate boolean ----@return nil -function activate_tiamat_position_hack(activate) end ----Activate custom variables for speed and y coordinate limit for crushing elevator ----note: because those variables are custom and game does not initiate them, you need to do it yourself for each CrushElevator entity, recommending set_post_entity_spawn ----default game values are: speed = 0.0125, y_limit = 98.5 ----@param activate boolean ----@return nil -function activate_crush_elevator_hack(activate) end ----Activate custom variables for y coordinate limit for hundun and spawn of it's heads ----note: because those variables are custom and game does not initiate them, you need to do it yourself for each Hundun entity, recommending set_post_entity_spawn ----default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 ----@param activate boolean ----@return nil -function activate_hundun_hack(activate) end ----Allows you to disable the control over the door for Hundun and Tiamat ----This will also prevent game crashing when there is no exit door when they are in level ----@param enable boolean ----@return nil -function set_boss_door_control_enabled(enable) end ----Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. ----@return nil -function update_state() end ----Set engine target frametime (1/framerate, default 1/60). Always capped by your GPU max FPS / VSync. To run the engine faster than rendered FPS, try update_state. Set to 0 to go as fast as possible. Call without arguments to reset. Also see set_speedhack ----@param frametime double? ----@return nil -function set_frametime(frametime) end ----Get engine target frametime (1/framerate, default 1/60). ----@return double -function get_frametime() end ----Set engine target frametime when game is unfocused (1/framerate, default 1/33). Always capped by the engine frametime. Set to 0 to go as fast as possible. Call without arguments to reset. ----@param frametime double? ----@return nil -function set_frametime_unfocused(frametime) end ----Get engine target frametime when game is unfocused (1/framerate, default 1/33). ----@return double -function get_frametime_unfocused() end ----Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn ----Use empty array or no parameter to get new unique ENT_TYPE that can be used for custom EntityDB ----@param types ENT_TYPE[] ----@return ENT_TYPE -function add_custom_type(types) end ----Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn ----Use empty array or no parameter to get new unique ENT_TYPE that can be used for custom EntityDB ----@return ENT_TYPE -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_depths integer[] ----@param l LAYER ----@return integer[] -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_depth integer ----@param l LAYER ----@return integer[] -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 -function get_current_money() end ----Adds money to the state.money_shop_total and displays the effect on the HUD for money change ----Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction ----@param amount integer ----@param display_time integer? ----@return integer -function add_money(amount, display_time) end ----Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change ----Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction ----@param amount integer ----@param player_slot integer ----@param display_time integer? ----@return integer -function add_money_slot(amount, player_slot, display_time) end ----Destroys all layers and all entities in the level. Usually a bad idea, unless you also call create_level and spawn the player back in. ----@return nil -function destroy_level() end ----Destroys a layer and all entities in it. ----@param layer integer ----@return nil -function destroy_layer(layer) end ----Initializes an empty front and back layer that don't currently exist. Does nothing(?) if layers already exist. +function reset_lut(layer) end +---@return HudData +function get_hud() end +---Alters the drop chance for the provided monster-item combination (use e.g. set_drop_chance(DROPCHANCE.MOLE_MATTOCK, 10) for a 1 in 10 chance) +---Use `-1` as dropchance_id to reset all to default +---@param dropchance_id DROPCHANCE +---@param new_drop_chance integer ---@return nil -function create_level() end ----Initializes an empty layer that doesn't currently exist. ----@param layer integer +function set_drop_chance(dropchance_id, new_drop_chance) end +---Changes a particular drop, e.g. what Van Horsing throws at you (use e.g. replace_drop(DROP.VAN_HORSING_DIAMOND, ENT_TYPE.ITEM_PLASMACANNON)) +---Use `0` as type to reset this drop to default, use `-1` as drop_id to reset all to default +---@param drop_id DROP +---@param new_drop_entity_type ENT_TYPE ---@return nil -function create_layer(layer) end ----Setting to false disables all player logic in 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. ----@param enable boolean +function replace_drop(drop_id, new_drop_entity_type) end +---Gets a `TextureDefinition` for equivalent to the one used to define the texture with `id` +---@param texture_id TEXTURE +---@return TextureDefinition +function get_texture_definition(texture_id) end +---Defines a new texture that can be used in Entity::set_texture +---If a texture with the same definition already exists the texture will be reloaded from disk. +---@param texture_data TextureDefinition +---@return TEXTURE +function define_texture(texture_data) end +---Gets a texture with the same definition as the given, if none exists returns `nil` +---@param texture_data TextureDefinition +---@return TEXTURE? +function get_texture(texture_data) end +---Reloads a texture from disk, use this only as a development tool for example in the console +---Note that [define_texture](https://spelunky-fyi.github.io/overlunky/#define_texture) will also reload the texture if it already exists +---@param texture_path string ---@return nil -function set_level_logic_enabled(enable) end ----Setting to true will stop the state update from unpausing after a screen load, leaving you with state.pause == PAUSE.FADE on the first frame to do what you want. ----@param enable boolean +function reload_texture(texture_path) end +---Replace a vanilla texture definition with a custom texture definition and reload the texture. +---@param vanilla_id TEXTURE +---@param custom_id TEXTURE +---@return boolean +function replace_texture(vanilla_id, custom_id) end +---Reset a replaced vanilla texture to the original and reload the texture. +---@param vanilla_id TEXTURE ---@return nil -function set_start_level_paused(enable) end ----Returns true if the level pause hack is enabled +function reset_texture(vanilla_id) end +---Replace a vanilla texture definition with a custom texture definition and reload the texture. Set corresponding character heart color to the pixel in the center of the player indicator arrow in that texture. (448,1472) +---@param vanilla_id TEXTURE +---@param custom_id TEXTURE ---@return boolean -function get_start_level_paused() end ----Converts INPUTS to (x, y, BUTTON) ----@param inputs INPUTS ----@return number, number, BUTTON -function inputs_to_buttons(inputs) end ----Converts (x, y, BUTTON) to INPUTS ----@param x number ----@param y number ----@param buttons BUTTON ----@return INPUTS -function buttons_to_inputs(x, y, buttons) end ----Disable the Infinite Loop Detection of 420 million instructions per frame, if you know what you're doing and need to perform some serious calculations that hang the game updates for several seconds. ----@param enable boolean +function replace_texture_and_heart_color(vanilla_id, custom_id) end +---Clear cache for a file path or the whole directory ---@return nil -function set_infinite_loop_detection_enabled(enable) end ----This disables the `state.camera_layer` to be forced to the `(leader player).layer` and setting of the `state.layer_transition_timer` & `state.transition_to_layer` when player enters layer door. ----Letting you control those manually. ----Look at the example on how to mimic game layer switching behavior ----@param enable boolean +function clear_cache() end +---Gets the hitbox of an entity, use `extrude` to make the hitbox bigger/smaller in all directions and `offset` to offset the hitbox in a given direction +---@param uid integer +---@param extrude number? +---@param offsetx number? +---@param offsety number? +---@return AABB +function get_hitbox(uid, extrude, offsetx, offsety) end +---Same as `get_hitbox` but based on `get_render_position` +---@param uid integer +---@param extrude number? +---@param offsetx number? +---@param offsety number? +---@return AABB +function get_render_hitbox(uid, extrude, offsetx, offsety) end +---Convert an `AABB` to a screen `AABB` that can be directly passed to draw functions +---@param box AABB +---@return AABB +function screen_aabb(box) end +---Find intersection point of two lines [A, B] and [C, D], returns INFINITY if the lines don't intersect each other [parallel] +---@param A Vec2 +---@param B Vec2 +---@param C Vec2 +---@param D Vec2 +---@return Vec2 +function intersection(A, B, C, D) end +---Measures angle between two lines with one common point +---@param A Vec2 +---@param common Vec2 +---@param B Vec2 +---@return number +function two_lines_angle(A, common, B) end +---Gets line1_A, intersection point and line2_B and calls the 3 parameter version of this function +---@param line1_A Vec2 +---@param line1_B Vec2 +---@param line2_A Vec2 +---@param line2_B Vec2 +---@return number +function two_lines_angle(line1_A, line1_B, line2_A, line2_B) end +---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.) +---@param chapter integer +---@param entry integer ---@return nil -function set_camera_layer_control_enabled(enable) end ----Set multiplier (default 1.0) for a QueryPerformanceCounter hook based speedhack, similar to the one in Cheat Engine. Call without arguments to reset. Also see [set_frametime](https://spelunky-fyi.github.io/overlunky/#set_frametime) ----@param multiplier number? +function force_journal(chapter, entry) end +---Open or close the journal as if pressing the journal button. Will respect visible journal popups and [force_journal](https://spelunky-fyi.github.io/overlunky/#force_journal). ---@return nil -function set_speedhack(multiplier) end ----Get the current speedhack multiplier ----@return number -function get_speedhack() end ----Retrieves the current value of the performance counter, which is a high resolution (<1us) time stamp that can be used for time-interval measurements. ----@return integer -function get_performance_counter() end ----Retrieves the frequency of the performance counter. The frequency of the performance counter is fixed at system boot and is consistent across all processors. Therefore, the frequency need only be queried upon application initialization, and the result can be cached. ----@return integer -function get_performance_frequency() end ----Initializes some adventure run related values and loads the character select screen, as if starting a new adventure run from the Play menu. Character select can be skipped by changing `state.screen_next` right after calling this function, maybe with `warp()`. If player isn't already selected, make sure to set `state.items.player_select` and `state.items.player_count` appropriately too. +function toggle_journal() end +---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. +---@param chapter JOURNALUI_PAGE_SHOWN +---@param page integer ---@return nil -function play_adventure() end ----Initializes some seeded run related values and loads the character select screen, as if starting a new seeded run after entering the seed. ----@param seed integer? +function show_journal(chapter, page) end +---Start an UDP server on specified address and run callback when data arrives. Return a string from the callback to reply. Requires unsafe mode. +---The server will be closed once the handle is released. +---@param host string +---@param port integer +---@param cb function +---@return UdpServer +function udp_listen(host, port, cb) end +---Send data to specified UDP address. Requires unsafe mode. +---@param host string +---@param port integer +---@param msg string ---@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 hardcoded 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 +function udp_send(host, port, msg) end +---Hook the sendto and recvfrom functions and start dumping network data to terminal ---@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) ) ----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 +function dump_network() end +---Send a synchronous HTTP GET request and return response as a string or nil on an error +---@param url string +---@return string? +function http_get(url) end +---Send an asynchronous HTTP GET request and run the callback when done. If there is an error, response will be nil and vice versa. +---The callback signature is nil on_data(string response, string error) +---@param url string +---@param on_data fun(response: string, error: string): nil ---@return nil -function add_entity_to_liquid_collision(uid, add) end ----@return boolean -function toast_visible() end +function http_get_async(url, on_data) end +---Check if the user has performed a feat (Real Steam achievement or a hooked one). Returns: `bool unlocked, bool hidden, string name, string description` +---@param feat FEAT +---@return boolean, boolean, string, string +function get_feat(feat) end +---Get the visibility of a feat +---@param feat FEAT ---@return boolean -function speechbubble_visible() end +function get_feat_hidden(feat) end +---Set the visibility of a feat +---@param feat FEAT +---@param hidden boolean ---@return nil -function cancel_toast() end +function set_feat_hidden(feat, hidden) end +---Helper function to set the title and description strings for a FEAT with change_string, as well as the hidden state. +---@param feat FEAT +---@param hidden boolean +---@param name string +---@param description string ---@return nil -function cancel_speechbubble() end ----Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. ----@param slot integer +function change_feat(feat, hidden, name, description) end +---Access the PauseAPI, or directly call `pause(true)` to enable current `pause.pause_type` ---@return nil -function save_state(slot) end ----Load level state from slot 1..4, if a save_state was made in this level. ----@param slot integer +function pause() end +---Returns the Bucket of data stored in shared memory between Overlunky and Playlunky +---@return Bucket +function get_bucket() end +---Converts a color to int to be used in drawing functions. Use values from `0..255`. +---@param r integer +---@param g integer +---@param b integer +---@param a integer +---@return uColor +function rgba(r, g, b, a) end +---Convert a string to a color, you can use the HTML color names, or even HTML color codes, just prefix them with '#' symbol +---You can also convert hex string into a color, prefix it with '0x', but use it only if you need to since lua allows for hex values directly too. +---Default alpha value will be 0xFF, unless it's specified +---Format: [name], #RRGGBB, #RRGGBBAA, 0xBBGGRR, 0xAABBGGRR +---@param color_name string +---@param alpha integer? +---@return uColor +function get_color(color_name, alpha) end +---Spawn liquids, always spawns in the front layer, will have fun effects if `entity_type` is not a liquid (only the short version, without velocity etc.). +---Don't overuse this, you are still restricted by the liquid pool sizes and thus might crash the game. +---`liquid_flags` - not much known about, 2 - will probably crash the game, 3 - pause_physics, 6-12 is probably agitation, surface_tension etc. set to 0 to ignore +---`amount` - it will spawn amount x amount (so 1 = 1, 2 = 4, 3 = 6 etc.), `blobs_separation` is optional +---@param entity_type ENT_TYPE +---@param x number +---@param y number ---@return nil -function load_state(slot) end ----Clear save state from slot 1..4. ----@param slot integer +function spawn_liquid(entity_type, x, y) end +---Spawn liquids, always spawns in the front layer, will have fun effects if `entity_type` is not a liquid (only the short version, without velocity etc.). +---Don't overuse this, you are still restricted by the liquid pool sizes and thus might crash the game. +---`liquid_flags` - not much known about, 2 - will probably crash the game, 3 - pause_physics, 6-12 is probably agitation, surface_tension etc. set to 0 to ignore +---`amount` - it will spawn amount x amount (so 1 = 1, 2 = 4, 3 = 6 etc.), `blobs_separation` is optional +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param velocityx number +---@param velocityy number +---@param liquid_flags integer +---@param amount integer +---@param blobs_separation number ---@return nil -function clear_state(slot) end ----Get StateMemory from a save_state slot. ----@param slot integer ----@return StateMemory -function get_save_state(slot) end ----Returns RawInput, a game structure for raw keyboard and controller state ----@return RawInput -function get_raw_input() end ----Seed the game prng. ----@param seed integer +function spawn_liquid(entity_type, x, y, velocityx, velocityy, liquid_flags, amount, blobs_separation) end +---Spawn an entity in position with some velocity and return the uid of spawned entity. +---Uses level coordinates with [LAYER.FRONT](#LAYER) and LAYER.BACK, but player-relative coordinates with LAYER.PLAYER(n), where (n) is a player number (1-4). +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@param vx number +---@param vy number +---@return integer +function spawn_entity(entity_type, x, y, layer, vx, vy) end +---Short for [spawn_entity](https://spelunky-fyi.github.io/overlunky/#spawn_entity). +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@param vx number +---@param vy number +---@return integer +function spawn(entity_type, x, y, layer, vx, vy) end +---Spawns an entity directly on the floor below the tile at the given position. +---Use this to avoid the little fall that some entities do when spawned during level gen callbacks. +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@return integer +function spawn_entity_snapped_to_floor(entity_type, x, y, layer) end +---Short for [spawn_entity_snapped_to_floor](https://spelunky-fyi.github.io/overlunky/#spawn_entity_snapped_to_floor). +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@return integer +function spawn_on_floor(entity_type, x, y, layer) end +---Spawn a grid entity, such as floor or traps, that snaps to the grid. +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@return integer +function spawn_grid_entity(entity_type, x, y, layer) end +---Same as `spawn_entity` but does not trigger any pre-entity-spawn callbacks, so it will not be replaced by another script +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@param vx number +---@param vy number +---@return integer +function spawn_entity_nonreplaceable(entity_type, x, y, layer, vx, vy) end +---Short for [spawn_entity_nonreplaceable](https://spelunky-fyi.github.io/overlunky/#spawn_entity_nonreplaceable). +---@param entity_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@param vx number +---@param vy number +---@return integer +function spawn_critical(entity_type, x, y, layer, vx, vy) end +---Spawn a door to another world, level and theme and return the uid of spawned entity. +---Uses level coordinates with LAYER.FRONT and LAYER.BACK, but player-relative coordinates with LAYER.PLAYERn +---@param x number +---@param y number +---@param layer LAYER +---@param w integer +---@param l integer +---@param t integer +---@return integer +function spawn_door(x, y, layer, w, l, t) end +---Short for [spawn_door](https://spelunky-fyi.github.io/overlunky/#spawn_door). +---@param x number +---@param y number +---@param layer LAYER +---@param w integer +---@param l integer +---@param t integer +---@return integer +function door(x, y, layer, w, l, t) end +---Spawn a door to backlayer. +---@param x number +---@param y number ---@return nil -function seed_prng(seed) end ----Get the thread-local version of prng ----@return PRNG -function get_local_prng() end ----Same as `Player.get_name` ----@param type_id ENT_TYPE ----@return string -function get_character_name(type_id) end ----Same as `Player.get_short_name` ----@param type_id ENT_TYPE ----@return string -function get_character_short_name(type_id) end ----Same as `Player.get_heart_color` ----@param type_id ENT_TYPE ----@return Color -function get_character_heart_color(type_id) end ----Same as `Player.is_female` ----@param type_id ENT_TYPE ----@return boolean -function is_character_female(type_id) end ----Same as `Player.set_heart_color` ----@param type_id ENT_TYPE ----@param color Color +function spawn_layer_door(x, y) end +---Short for [spawn_layer_door](https://spelunky-fyi.github.io/overlunky/#spawn_layer_door). +---@param x number +---@param y number ---@return nil -function set_character_heart_color(type_id, color) end ----Make a `CustomMovableBehavior`, if `base_behavior` is `nil` you will have to set all of the ----behavior functions. If a behavior with `behavior_name` already exists for your script it will ----be returned instead. ----@param behavior_name string ----@param state_id integer ----@param base_behavior VanillaMovableBehavior ----@return CustomMovableBehavior -function make_custom_behavior(behavior_name, state_id, base_behavior) end ----Get the [ParticleDB](https://spelunky-fyi.github.io/overlunky/#ParticleDB) details of the specified ID ----@param id PARTICLEEMITTER ----@return ParticleDB -function get_particle_type(id) end ----Generate particles of the specified type around the specified entity uid (use e.g. `local emitter = generate_world_particles(PARTICLEEMITTER.PETTING_PET, players[1].uid)`). You can then decouple the emitter from the entity with `emitter.entity_uid = -1` and freely move it around. See the `particles.lua` example script for more details. ----@param particle_emitter_id PARTICLEEMITTER ----@param uid integer ----@return ParticleEmitterInfo -function generate_world_particles(particle_emitter_id, uid) end ----Generate particles of the specified type at a certain screen coordinate (use e.g. `local emitter = generate_screen_particles(PARTICLEEMITTER.CHARSELECTOR_TORCHFLAME_FLAMES, 0.0, 0.0)`). See the `particles.lua` example script for more details. ----@param particle_emitter_id PARTICLEEMITTER +function layer_door(x, y) end +---Spawns apep with the choice if it going left or right, if you want the game to choose use regular spawn functions with `ENT_TYPE.MONS_APEP_HEAD` +---@param x number +---@param y number +---@param layer LAYER +---@param right boolean +---@return integer +function spawn_apep(x, y, layer, right) 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 +---Spawns and grows a tree ---@param x number ---@param y number ----@return ParticleEmitterInfo -function generate_screen_particles(particle_emitter_id, x, y) end ----Advances the state of the screen particle emitter (simulates the next positions, ... of all the particles in the emitter). Only used with screen particle emitters. See the `particles.lua` example script for more details. ----@param particle_emitter ParticleEmitterInfo ----@return nil -function advance_screen_particles(particle_emitter) end ----Renders the particles to the screen. Only used with screen particle emitters. See the `particles.lua` example script for more details. ----@param particle_emitter ParticleEmitterInfo ----@return nil -function render_screen_particles(particle_emitter) end ----Extinguish a particle emitter (use the return value of `generate_world_particles` or `generate_screen_particles` as the parameter in this function) ----@param particle_emitter ParticleEmitterInfo ----@return nil -function extinguish_particles(particle_emitter) end ----Default function in spawn definitions to check whether a spawn is valid or not +---@param layer LAYER +---@return integer +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, 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 +---@return integer +function spawn_mushroom(x, y, l) end +---Spawns an already unrolled rope as if created by player ---@param x number ---@param y number ---@param layer LAYER ----@return boolean -function default_spawn_is_valid(x, y, layer) end ----Check if position satisfies the given POS_TYPE flags, to be used in a custom is_valid function procedural for spawns. +---@param texture TEXTURE +---@return integer +function spawn_unrolled_player_rope(x, y, layer, texture) end +---Spawns an already unrolled rope as if created by player ---@param x number ---@param y number ---@param layer LAYER ----@param flags POS_TYPE ----@return boolean -function position_is_valid(x, y, layer, flags) end ----Add a callback for a specific tile code that is called before the game handles the tile code. ----Return true in order to stop the game or scripts loaded after this script from handling this tile code. ----For example, when returning true in this callback set for `"floor"` then no floor will spawn in the game (unless you spawn it yourself) ----The callback signature is bool pre_tile_code(float x, float y, int layer, ROOM_TEMPLATE room_template) ----@param cb fun(x: number, y: number, layer: integer, room_template: ROOM_TEMPLATE): boolean ----@param tile_code string +---@param texture TEXTURE +---@param max_length integer +---@return integer +function spawn_unrolled_player_rope(x, y, layer, texture, max_length) end +---Spawn a player in given location, if player of that slot already exist it will spawn clone, the game may crash as this is very unexpected situation +---If you want to respawn a player that is a ghost, set in his Inventory `health` to above 0, and `time_of_death` to 0 and call this function, the ghost entity will be removed automatically +---@param player_slot integer +---@param x number? +---@param y number? +---@param layer LAYER? +---@return integer +function spawn_player(player_slot, x, y, layer) end +---Spawn the PlayerGhost entity, it will not move and not be connected to any player, you can then use [steal_input](https://spelunky-fyi.github.io/overlunky/#steal_input) and send_input to control it +---or change it's `player_inputs` to the `input` of real player so he can control it directly +---@param char_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@return integer +function spawn_playerghost(char_type, x, y, layer) end +---Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. +---This is run before the entity is spawned, spawn your own entity and return its uid to replace the intended spawn. +---In many cases replacing the intended entity won't have the intended effect or will even break the game, so use only if you really know what you're doing. +---The callback signature is optional pre_entity_spawn(ENT_TYPE entity_type, float x, float y, int layer, Entity overlay_entity, SPAWN_TYPE spawn_flags) +---@param cb fun(entity_type: ENT_TYPE, x: number, y: number, layer: integer, overlay_entity: Entity, spawn_flags: SPAWN_TYPE): integer? +---@param flags SPAWN_TYPE +---@param mask MASK +---@vararg any ---@return CallbackId -function set_pre_tile_code_callback(cb, tile_code) end ----Add a callback for a specific tile code that is called after the game handles the tile code. ----Use this to affect what the game or other scripts spawned in this position. ----This is received even if a previous pre-tile-code-callback has returned true ----The callback signature is nil post_tile_code(float x, float y, int layer, ROOM_TEMPLATE room_template) ----@param cb fun(x: number, y: number, layer: integer, room_template: ROOM_TEMPLATE): nil ----@param tile_code string +function set_pre_entity_spawn(cb, flags, mask, ...) end +---Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. +---This is run right after the entity is spawned but before and particular properties are changed, e.g. owner or velocity. +---The callback signature is nil post_entity_spawn(Entity ent, SPAWN_TYPE spawn_flags) +---@param cb fun(ent: Entity, spawn_flags: SPAWN_TYPE): nil +---@param flags SPAWN_TYPE +---@param mask MASK +---@vararg any ---@return CallbackId -function set_post_tile_code_callback(cb, tile_code) end ----Define a new tile code, to make this tile code do anything you have to use either [set_pre_tile_code_callback](https://spelunky-fyi.github.io/overlunky/#set_pre_tile_code_callback) or [set_post_tile_code_callback](https://spelunky-fyi.github.io/overlunky/#set_post_tile_code_callback). ----If a user disables your script but still uses your level mod nothing will be spawned in place of your tile code. ----@param tile_code string ----@return TILE_CODE -function define_tile_code(tile_code) end ----Gets a short tile code based on definition, returns `nil` if it can't be found ----@param short_tile_code_def ShortTileCodeDef ----@return integer? -function get_short_tile_code(short_tile_code_def) end ----Gets the definition of a short tile code (if available), will vary depending on which file is loaded ----@param short_tile_code SHORT_TILE_CODE ----@return ShortTileCodeDef? -function get_short_tile_code_definition(short_tile_code) end ----Define a new procedural spawn, the function `nil do_spawn(float x, float y, LAYER layer)` contains your code to spawn the thing, whatever it is. ----The function `bool is_valid(float x, float y, LAYER layer)` determines whether the spawn is legal in the given position and layer. ----Use for example when you can spawn only on the ceiling, under water or inside a shop. ----Set `is_valid` to `nil` in order to use the default rule (aka. on top of floor and not obstructed). ----If a user disables your script but still uses your level mod nothing will be spawned in place of your procedural spawn. ----@param procedural_spawn string ----@param do_spawn fun(x: number, y: number, layer: LAYER): nil ----@param is_valid fun(x: number, y: number, layer: LAYER): boolean ----@return PROCEDURAL_CHANCE -function define_procedural_spawn(procedural_spawn, do_spawn, is_valid) end ----Define a new extra spawn, these are semi-guaranteed level gen spawns with a fixed upper bound. ----The function `nil do_spawn(float x, float y, LAYER layer)` contains your code to spawn the thing, whatever it is. ----The function `bool is_valid(float x, float y, LAYER layer)` determines whether the spawn is legal in the given position and layer. ----Use for example when you can spawn only on the ceiling, under water or inside a shop. ----Set `is_valid` to `nil` in order to use the default rule (aka. on top of floor and not obstructed). ----To change the number of spawns use `PostRoomGenerationContext:set_num_extra_spawns` during `ON.POST_ROOM_GENERATION` ----No name is attached to the extra spawn since it is not modified from level files, instead every call to this function will return a new unique id. ----@param do_spawn fun(x: number, y: number, layer: LAYER): nil ----@param is_valid fun(x: number, y: number, layer: LAYER): boolean ----@param num_spawns_frontlayer integer ----@param num_spawns_backlayer integer +function set_post_entity_spawn(cb, flags, mask, ...) end +---Spawn a Shopkeeper in the coordinates and make the room their shop. Returns the Shopkeeper uid. Also see [spawn_roomowner](https://spelunky-fyi.github.io/overlunky/#spawn_roomowner). +---@param x number +---@param y number +---@param layer LAYER +---@param room_template ROOM_TEMPLATE ---@return integer -function define_extra_spawn(do_spawn, is_valid, num_spawns_frontlayer, num_spawns_backlayer) end ----Use to query whether any of the requested spawns could not be made, usually because there were not enough valid spaces in the level. ----Returns missing spawns in the front layer and missing spawns in the back layer in that order. ----The value only makes sense after level generation is complete, aka after `ON.POST_LEVEL_GENERATION` has run. ----@param extra_spawn_chance_id integer ----@return integer, integer -function get_missing_extra_spawns(extra_spawn_chance_id) end ----Transform a position to a room index to be used in `get_room_template` and `PostRoomGenerationContext.set_room_template` +function spawn_shopkeeper(x, y, layer, room_template) end +---Spawn a RoomOwner (or a few other like [CavemanShopkeeper](https://spelunky-fyi.github.io/overlunky/#CavemanShopkeeper)) in the coordinates and make them own the room, optionally changing the room template. Returns the RoomOwner uid. +---@param owner_type ENT_TYPE ---@param x number ---@param y number ----@return integer, integer -function get_room_index(x, y) end ----Transform a room index into the top left corner position in the room ----@param x integer ----@param y integer ----@return number, number -function get_room_pos(x, y) end ----Get the room template given a certain index, returns `nil` if coordinates are out of bounds ----@param x integer ----@param y integer ---@param layer LAYER ----@return integer? -function get_room_template(x, y, layer) end ----Get whether a room is flipped at the given index, returns `false` if coordinates are out of bounds ----@param x integer ----@param y integer ----@return boolean -function is_room_flipped(x, y) end ----Get whether a room is the origin of a machine room ----@param x integer ----@param y integer ----@return boolean -function is_machine_room_origin(x, y) end ----For debugging only, get the name of a room template, returns `'invalid'` if room template is not defined ----@param room_template integer ----@return string -function get_room_template_name(room_template) end ----Define a new room template to use with `set_room_template` ----@param room_template string ----@param type ROOM_TEMPLATE_TYPE +---@param room_template ROOM_TEMPLATE ---@return integer -function define_room_template(room_template, type) end ----Set the size of room template in tiles, the template must be of type `ROOM_TEMPLATE_TYPE.MACHINE_ROOM`. ----@param room_template integer ----@param width integer ----@param height integer ----@return boolean -function set_room_template_size(room_template, width, height) end ----Get the inverse chance of a procedural spawn for the current level. ----A return value of 0 does not mean the chance is infinite, it means the chance is zero. ----@param chance_id PROCEDURAL_CHANCE +function spawn_roomowner(owner_type, x, y, layer, room_template) end +---Spawn an entity of `entity_type` attached to some other entity `over_uid`, in offset `x`, `y` +---@param entity_type ENT_TYPE +---@param over_uid integer +---@param x number +---@param y number ---@return integer -function get_procedural_spawn_chance(chance_id) end ----Gets the sub theme of the current cosmic ocean level, returns COSUBTHEME.NONE if the current level is not a CO level. ----@return COSUBTHEME -function get_co_subtheme() end ----Forces the theme of the next cosmic ocean level(s) (use e.g. `force_co_subtheme(COSUBTHEME.JUNGLE)`. Use COSUBTHEME.RESET to reset to default random behaviour) ----@param subtheme COSUBTHEME ----@return nil -function force_co_subtheme(subtheme) end ----Gets the value for the specified config ----@param config LEVEL_CONFIG +function spawn_entity_over(entity_type, over_uid, x, y) end +---Short for [spawn_entity_over](https://spelunky-fyi.github.io/overlunky/#spawn_entity_over) +---@param entity_type ENT_TYPE +---@param over_uid integer +---@param x number +---@param y number ---@return integer -function get_level_config(config) end ----Set the value for the specified config ----@param config LEVEL_CONFIG +function spawn_over(entity_type, over_uid, x, y) end +---Spawn a companion (hired hand, player character, eggplant child) +---@param companion_type ENT_TYPE +---@param x number +---@param y number +---@param layer LAYER +---@return integer +function spawn_companion(companion_type, x, y, layer) end +---Add an integer option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft +---limits, you can override them in the UI with double click. +---@param name string +---@param desc string +---@param long_desc string +---@param value integer +---@param min integer +---@param max integer +---@return nil +function register_option_int(name, desc, long_desc, value, min, max) end +---Add a float option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft +---limits, you can override them in the UI with double click. +---@param name string +---@param desc string +---@param long_desc string +---@param value number +---@param min number +---@param max number +---@return nil +function register_option_float(name, desc, long_desc, value, min, max) end +---Add a boolean option that the user can change in the UI. Read with `options.name`, `value` is the default. +---@param name string +---@param desc string +---@param long_desc string +---@param value boolean +---@return nil +function register_option_bool(name, desc, long_desc, value) end +---Add a string option that the user can change in the UI. Read with `options.name`, `value` is the default. +---@param name string +---@param desc string +---@param long_desc string +---@param value string +---@return nil +function register_option_string(name, desc, long_desc, value) end +---Add a combobox option that the user can change in the UI. Read the int index of the selection with `options.name`. Separate `opts` with `\0`, +---with a double `\0\0` at the end. `value` is the default index 1..n. +---@param name string +---@param desc string +---@param long_desc string +---@param opts string ---@param value integer ---@return nil -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_length integer ----@param area AABB ----@param destroy_broken boolean ----@return nil -function grow_vines(l, max_length, area, destroy_broken) 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_length integer +function register_option_combo(name, desc, long_desc, opts, value) end +---Add a button that the user can click in the UI. Sets the timestamp of last click on value and runs the callback function. +---@param name string +---@param desc string +---@param long_desc string +---@param on_click function ---@return nil -function grow_vines(l, max_length) 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_length integer ----@param area AABB ----@param destroy_broken boolean +function register_option_button(name, desc, long_desc, on_click) end +---Add custom options using the window drawing functions. Everything drawn in the callback will be rendered in the options window and the return value saved to `options[name]` or overwriting the whole `options` table if using and empty name. +---`value` is the default value, and pretty important because anything defined in the callback function will only be defined after the options are rendered. See the example for details. +---The callback signature is optional on_render(GuiDrawContext draw_ctx) +---@param name string +---@param value any +---@param on_render fun(draw_ctx: GuiDrawContext): any? ---@return nil -function grow_poles(l, max_length, 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_length integer +function register_option_callback(name, value, on_render) end +---Removes an option by name. To make complicated conditionally visible options you should probably just use register_option_callback though. +---@param name string ---@return nil -function grow_poles(l, max_length) 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 -function grow_chainandblocks() 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) ----@param x integer ----@param y integer ----@return boolean -function grow_chainandblocks(x, y) end ----Immediately load a screen based on [state](https://spelunky-fyi.github.io/overlunky/#state).screen_next and stuff +function unregister_option(name) end +---Sets the amount of blood drops in the Kapala needed to trigger a health increase (default = 7). +---@param threshold integer ---@return nil -function load_screen() end ----Force a theme in PRE_LOAD_LEVEL_FILES, POST_ROOM_GENERATION or PRE_LEVEL_GENERATION to change different aspects of the levelgen. You can pass a CustomTheme, ThemeInfo or THEME. ----@param customtheme CustomTheme|ThemeInfo|THEME +function set_kapala_blood_threshold(threshold) end +---Sets the hud icon for the Kapala (0-6 ; -1 for default behaviour). +---If you set a Kapala treshold greater than 7, make sure to set the hud icon in the range 0-6, or other icons will appear in the hud! +---@param icon_index integer ---@return nil -function force_custom_theme(customtheme) end ----Force current subtheme used in the CO theme. You can pass a CustomTheme, ThemeInfo or THEME. Not to be confused with force_co_subtheme. ----@param customtheme CustomTheme|ThemeInfo|THEME +function set_kapala_hud_icon(icon_index) end +---Changes characteristics of (all) sparktraps: speed, rotation direction and distance from center +---Speed: expressed as the amount that should be added to the angle every frame (use a negative number to go in the other direction) +---Distance from center: if you go above 3.0 the game might crash because a spark may go out of bounds! +---@param angle_increment number +---@param distance number ---@return nil -function force_custom_subtheme(customtheme) end ----Loads a sound from disk relative to this script, ownership might be shared with other code that loads the same file. Returns nil if file can't be found ----@param path string ----@return CustomSound? -function create_sound(path) end ----Gets an existing sound, either if a file at the same path was already loaded or if it is already loaded by the game ----@param path_or_vanilla_sound string ----@return CustomSound? -function get_sound(path_or_vanilla_sound) end ----Returns unique id for the callback to be used in [clear_vanilla_sound_callback](https://spelunky-fyi.github.io/overlunky/#clear_vanilla_sound_callback). ----Sets a callback for a vanilla sound which lets you hook creation or playing events of that sound ----Callbacks are executed on another thread, so avoid touching any global state, only the local Lua state is protected ----If you set such a callback and then play the same sound yourself you have to wait until receiving the STARTED event before changing any properties on the sound. Otherwise you may cause a deadlock. ----The callback signature is nil on_vanilla_sound(PlayingSound sound) ----@param name VANILLA_SOUND ----@param types VANILLA_SOUND_CALLBACK_TYPE ----@param cb fun(sound: PlayingSound): nil ----@return CallbackId -function set_vanilla_sound_callback(name, types, cb) end ----Clears a previously set callback ----@param id CallbackId +function modify_sparktraps(angle_increment, distance) end +---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 +---@param activate boolean ---@return nil -function clear_vanilla_sound_callback(id) end ----Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" ----Returns SoundMeta, beware that the sound can't be stopped (`start_over` and `playing` are unavailable). Should only be used for sfx. ----@param sound VANILLA_SOUND ----@param source_uid integer ----@return SoundMeta -function play_sound(sound, source_uid) end ----Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" ----Returns SoundMeta, beware that the sound can't be stopped (`start_over` and `playing` are unavailable). Should only be used for sfx. ----@param sound_id SOUNDID ----@param source_uid integer ----@return SoundMeta -function play_sound(sound_id, source_uid) end ----@param id SOUNDID ----@return VANILLA_SOUND -function convert_sound_id(id) end ----Convert SOUNDID to VANILLA_SOUND and vice versa ----@param sound VANILLA_SOUND ----@return SOUNDID -function convert_sound_id(sound) end ----Calculate the bounding box of text, so you can center it etc. Returns `width`, `height` in screen distance. ----@param size number ----@param text string ----@return number, number -function draw_text_size(size, text) end ----Create image from file. Returns a tuple containing id, width and height. ----Depending on the image size, this can take a moment, preferably don't create them dynamically, rather create all you need in global scope so it will load them as soon as the game starts ----@param path string ----@return IMAGE, integer, integer -function create_image(path) end ----Create image from file, cropped to the geometry provided. Returns a tuple containing id, width and height. ----Depending on the image size, this can take a moment, preferably don't create them dynamically, rather create all you need in global scope so it will load them as soon as the game starts ----@param path string ----@param x integer ----@param y integer ----@param w integer ----@param h integer ----@return IMAGE, integer, integer -function create_image_crop(path, x, y, w, h) end ----Get image size from file. Returns a tuple containing width and height. ----@param path string ----@return integer, integer -function get_image_size(path) end ----Current mouse cursor position in screen coordinates. ----@return number, number -function mouse_position() end ----Returns human readable string from KEY chord (e.g. "Ctrl+X", "Unknown" or "None") ----@return string -function key_name() end ----Returns: [ImGuiIO](https://spelunky-fyi.github.io/overlunky/#ImGuiIO) for raw keyboard, mouse and xinput gamepad stuff. ----@return ImGuiIO -function get_io() end ----Returns unique id >= 0 for the callback to be used in [clear_callback](https://spelunky-fyi.github.io/overlunky/#clear_callback) or -1 if the key could not be registered. ----Add callback function to be called on a hotkey, using Windows hotkey api. These hotkeys will override all game and UI input and can work even when the game is unfocused. They are by design very intrusive and won't let anything else use the same key combo. Can't detect if input is active in another instance, use ImGuiIO if you need Playlunky hotkeys to react to Overlunky input state. Key is a KEY combo (e.g. `KEY.OL_MOD_CTRL | KEY.X`), possibly returned by GuiDrawContext:key_picker. Doesn't work with mouse buttons. ----The callback signature is nil on_hotkey(KEY key) ----@param cb fun(key: KEY): nil ----@param key KEY ----@param flags HOTKEY_TYPE ----@return CallbackId -function set_hotkey(cb, key, flags) 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? +function activate_sparktraps_hack(activate) end +---Set layer to search for storage items on ---@param layer LAYER ---@return nil -function set_lut(texture_id, layer) end ----Same as `set_lut(nil, layer)` ----@param layer LAYER +function set_storage_layer(layer) end +---Sets the Y-level at which Olmec changes phases +---@param phase integer +---@param y number ---@return nil -function reset_lut(layer) end ----@return HudData -function get_hud() end ----Alters the drop chance for the provided monster-item combination (use e.g. set_drop_chance(DROPCHANCE.MOLE_MATTOCK, 10) for a 1 in 10 chance) ----Use `-1` as dropchance_id to reset all to default ----@param dropchance_id integer ----@param new_drop_chance integer +function set_olmec_phase_y_level(phase, y) end +---Forces Olmec to stay on phase 0 (stomping) +---@param b boolean ---@return nil -function set_drop_chance(dropchance_id, new_drop_chance) end ----Changes a particular drop, e.g. what Van Horsing throws at you (use e.g. replace_drop(DROP.VAN_HORSING_DIAMOND, ENT_TYPE.ITEM_PLASMACANNON)) ----Use `0` as type to reset this drop to default, use `-1` as drop_id to reset all to default ----@param drop_id integer ----@param new_drop_entity_type ENT_TYPE +function force_olmec_phase_0(b) end +---Determines when the ghost appears, either when the player is cursed or not +---@param normal integer +---@param cursed integer ---@return nil -function replace_drop(drop_id, new_drop_entity_type) end ----Gets a `TextureDefinition` for equivalent to the one used to define the texture with `id` ----@param texture_id TEXTURE ----@return TextureDefinition -function get_texture_definition(texture_id) end ----Defines a new texture that can be used in Entity::set_texture ----If a texture with the same definition already exists the texture will be reloaded from disk. ----@param texture_data TextureDefinition ----@return TEXTURE -function define_texture(texture_data) end ----Gets a texture with the same definition as the given, if none exists returns `nil` ----@param texture_data TextureDefinition ----@return TEXTURE? -function get_texture(texture_data) end ----Reloads a texture from disk, use this only as a development tool for example in the console ----Note that [define_texture](https://spelunky-fyi.github.io/overlunky/#define_texture) will also reload the texture if it already exists ----@param texture_path string +function set_ghost_spawn_times(normal, cursed) end +---Determines whether the ghost appears when breaking the ghost pot +---@param enable boolean +---@return nil +function set_cursepot_ghost_enabled(enable) end +---Determines whether the time ghost appears, including the showing of the ghost toast +---@param b boolean +---@return nil +function set_time_ghost_enabled(b) end +---Determines whether the time jelly appears in cosmic ocean +---@param b boolean +---@return nil +function set_time_jelly_enabled(b) end +---Enables or disables the default position based camp camera bounds, to set them manually yourself +---@param b boolean +---@return nil +function set_camp_camera_bounds_enabled(b) end +---Sets which entities are affected by a bomb explosion. Default = MASK.PLAYER | MASK.MOUNT | MASK.MONSTER | MASK.ITEM | MASK.ACTIVEFLOOR | MASK.FLOOR +---@param mask integer +---@return nil +function set_explosion_mask(mask) end +---Sets the maximum length of a thrown rope (anchor segment not included). Unfortunately, setting this higher than default (6) creates visual glitches in the rope, even though it is fully functional. +---@param length integer +---@return nil +function set_max_rope_length(length) end +---Change ENT_TYPE's spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4: +---{MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER} +---Use empty table as argument to reset to the game default +---@param ent_types ENT_TYPE[] +---@return nil +function change_sunchallenge_spawns(ent_types) end +---Change ENT_TYPE's spawned in dice shops (Madame Tusk as well), by default there are 25: +---{ITEM_PICKUP_BOMBBAG, ITEM_PICKUP_BOMBBOX, ITEM_PICKUP_ROPEPILE, ITEM_PICKUP_COMPASS, ITEM_PICKUP_PASTE, ITEM_PICKUP_PARACHUTE, ITEM_PURCHASABLE_CAPE, ITEM_PICKUP_SPECTACLES, ITEM_PICKUP_CLIMBINGGLOVES, ITEM_PICKUP_PITCHERSMITT, +---ENT_TYPE_ITEM_PICKUP_SPIKESHOES, ENT_TYPE_ITEM_PICKUP_SPRINGSHOES, ITEM_MACHETE, ITEM_BOOMERANG, ITEM_CROSSBOW, ITEM_SHOTGUN, ITEM_FREEZERAY, ITEM_WEBGUN, ITEM_CAMERA, ITEM_MATTOCK, ITEM_PURCHASABLE_JETPACK, ITEM_PURCHASABLE_HOVERPACK, +---ITEM_TELEPORTER, ITEM_PURCHASABLE_TELEPORTER_BACKPACK, ITEM_PURCHASABLE_POWERPACK} +---Min 6, Max 255, if you want less then 6 you need to write some of them more then once (they will have higher "spawn chance"). +---If you use this function in the level with dice shop in it, you have to update `item_ids` in the [ITEM_DICE_PRIZE_DISPENSER](https://spelunky-fyi.github.io/overlunky/#PrizeDispenser). +---Use empty table as argument to reset to the game default +---@param ent_types ENT_TYPE[] +---@return nil +function change_diceshop_prizes(ent_types) end +---Change ENT_TYPE's spawned when you damage the altar, by default there are 6: +---{MONS_BAT, MONS_BEE, MONS_SPIDER, MONS_JIANGSHI, MONS_FEMALE_JIANGSHI, MONS_VAMPIRE} +---Max 255 types. +---Use empty table as argument to reset to the game default +---@param ent_types ENT_TYPE[] +---@return nil +function change_altar_damage_spawns(ent_types) end +---Change ENT_TYPE's spawned when Waddler dies, by default there are 3: +---{ITEM_PICKUP_COMPASS, ITEM_CHEST, ITEM_KEY} +---Max 255 types. +---Use empty table as argument to reset to the game default +---@param ent_types ENT_TYPE[] ---@return nil -function reload_texture(texture_path) end ----Replace a vanilla texture definition with a custom texture definition and reload the texture. ----@param vanilla_id TEXTURE ----@param custom_id TEXTURE ----@return boolean -function replace_texture(vanilla_id, custom_id) end ----Reset a replaced vanilla texture to the original and reload the texture. ----@param vanilla_id TEXTURE +function change_waddler_drop(ent_types) end +---Change how much health the ankh gives you after death, with every beat (the heart beat effect) it will add `beat_add_health` to your health, +---`beat_add_health` has to be divisor of `health` and can't be 0, otherwise the function does nothing. Set `health` to 0 to return to the game defaults +---If you set `health` above the game max health it will be forced down to the game max +---@param max_health integer +---@param beat_add_health integer ---@return nil -function reset_texture(vanilla_id) end ----Replace a vanilla texture definition with a custom texture definition and reload the texture. Set corresponding character heart color to the pixel in the center of the player indicator arrow in that texture. (448,1472) ----@param vanilla_id TEXTURE ----@param custom_id TEXTURE +function modify_ankh_health_gain(max_health, beat_add_health) end +---Change the amount of frames after the damage from poison is applied +---@param frames integer +---@return nil +function change_poison_timer(frames) end +---Disable all crust item spawns, returns whether they were already disabled before the call +---@param disable boolean ---@return boolean -function replace_texture_and_heart_color(vanilla_id, custom_id) end ----Clear cache for a file path or the whole directory +function disable_floor_embeds(disable) end +---Force the character unlocked in either ending to ENT_TYPE. Set to 0 to reset to the default guys. Does not affect the texture of the actual savior. (See example) +---@param type ENT_TYPE ---@return nil -function clear_cache() end ----Gets the hitbox of an entity, use `extrude` to make the hitbox bigger/smaller in all directions and `offset` to offset the hitbox in a given direction ----@param uid integer ----@param extrude number? ----@param offsetx number? ----@param offsety number? ----@return AABB -function get_hitbox(uid, extrude, offsetx, offsety) end ----Same as `get_hitbox` but based on `get_render_position` ----@param uid integer ----@param extrude number? ----@param offsetx number? ----@param offsety number? ----@return AABB -function get_render_hitbox(uid, extrude, offsetx, offsety) end ----Convert an `AABB` to a screen `AABB` that can be directly passed to draw functions ----@param box AABB ----@return AABB -function screen_aabb(box) end ----Find intersection point of two lines [A, B] and [C, D], returns INFINITY if the lines don't intersect each other [parallel] ----@param A Vec2 ----@param B Vec2 ----@param C Vec2 ----@param D Vec2 ----@return Vec2 -function intersection(A, B, C, D) end ----Measures angle between two lines with one common point ----@param A Vec2 ----@param common Vec2 ----@param B Vec2 ----@return number -function two_lines_angle(A, common, B) end ----Gets line1_A, intersection point and line2_B and calls the 3 parameter version of this function ----@param line1_A Vec2 ----@param line1_B Vec2 ----@param line2_A Vec2 ----@param line2_B Vec2 ----@return number -function two_lines_angle(line1_A, line1_B, line2_A, line2_B) end ----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.) ----@param chapter integer ----@param entry integer +function set_ending_unlock(type) end +---Olmec cutscene moves Olmec and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and Olmec will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with Olmec in the hole. +---@param enable boolean ---@return nil -function force_journal(chapter, entry) end ----Open or close the journal as if pressing the journal button. Will respect visible journal popups and [force_journal](https://spelunky-fyi.github.io/overlunky/#force_journal). +function set_olmec_cutscene_enabled(enable) end +---Tiamat cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want Tiamat kill to be required +---@param enable boolean ---@return nil -function toggle_journal() end ----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. ----@param chapter JOURNALUI_PAGE_SHOWN ----@param page integer +function set_tiamat_cutscene_enabled(enable) end +---Activate custom variables for position used for detecting the player (normally hardcoded) +---note: because those variables are custom and game does not initiate them, you need to do it yourself for each Tiamat entity, recommending set_post_entity_spawn +---default game values are: attack_x = 17.5 attack_y = 62.5 +---@param activate boolean ---@return nil -function show_journal(chapter, page) end ----Start an UDP server on specified address and run callback when data arrives. Return a string from the callback to reply. Requires unsafe mode. ----The server will be closed once the handle is released. ----@param host string ----@param port integer ----@param cb function ----@return UdpServer -function udp_listen(host, port, cb) end ----Send data to specified UDP address. Requires unsafe mode. ----@param host string ----@param port integer ----@param msg string +function activate_tiamat_position_hack(activate) end +---Activate custom variables for speed and y coordinate limit for crushing elevator +---note: because those variables are custom and game does not initiate them, you need to do it yourself for each CrushElevator entity, recommending set_post_entity_spawn +---default game values are: speed = 0.0125, y_limit = 98.5 +---@param activate boolean ---@return nil -function udp_send(host, port, msg) end ----Hook the sendto and recvfrom functions and start dumping network data to terminal +function activate_crush_elevator_hack(activate) end +---Activate custom variables for y coordinate limit for hundun and spawn of it's heads +---note: because those variables are custom and game does not initiate them, you need to do it yourself for each Hundun entity, recommending set_post_entity_spawn +---default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 +---@param activate boolean ---@return nil -function dump_network() end ----Send a synchronous HTTP GET request and return response as a string or nil on an error ----@param url string ----@return string? -function http_get(url) end ----Send an asynchronous HTTP GET request and run the callback when done. If there is an error, response will be nil and vice versa. ----The callback signature is nil on_data(string response, string error) ----@param url string ----@param on_data fun(response: string, error: string): nil +function activate_hundun_hack(activate) end +---Allows you to disable the control over the door for Hundun and Tiamat +---This will also prevent game crashing when there is no exit door when they are in level +---@param enable boolean ---@return nil -function http_get_async(url, on_data) end ----Check if the user has performed a feat (Real Steam achievement or a hooked one). Returns: `bool unlocked, bool hidden, string name, string description` ----@param feat FEAT ----@return boolean, boolean, string, string -function get_feat(feat) end ----Get the visibility of a feat ----@param feat FEAT ----@return boolean -function get_feat_hidden(feat) end ----Set the visibility of a feat ----@param feat FEAT ----@param hidden boolean +function set_boss_door_control_enabled(enable) end +---Setting to false disables all player logic in 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. +---@param enable boolean ---@return nil -function set_feat_hidden(feat, hidden) end ----Helper function to set the title and description strings for a FEAT with change_string, as well as the hidden state. ----@param feat FEAT ----@param hidden boolean ----@param name string ----@param description string +function set_level_logic_enabled(enable) end +---Setting to true will stop the state update from unpausing after a screen load, leaving you with state.pause == PAUSE.FADE on the first frame to do what you want. +---@param enable boolean ---@return nil -function change_feat(feat, hidden, name, description) end ----Access the PauseAPI, or directly call `pause(true)` to enable current `pause.pause_type` +function set_start_level_paused(enable) end +---This disables the `state.camera_layer` to be forced to the `(leader player).layer` and setting of the `state.layer_transition_timer` & `state.transition_to_layer` when player enters layer door. +---Letting you control those manually. +---Look at the example on how to mimic game layer switching behavior +---@param enable boolean ---@return nil -function pause() end ----Returns the Bucket of data stored in shared memory between Overlunky and Playlunky ----@return Bucket -function get_bucket() end ----Converts a color to int to be used in drawing functions. Use values from `0..255`. ----@param r integer ----@param g integer ----@param b integer ----@param a integer ----@return uColor -function rgba(r, g, b, a) end ----Convert a string to a color, you can use the HTML color names, or even HTML color codes, just prefix them with '#' symbol ----You can also convert hex string into a color, prefix it with '0x', but use it only if you need to since lua allows for hex values directly too. ----Default alpha value will be 0xFF, unless it's specified ----Format: [name], #RRGGBB, #RRGGBBAA, 0xBBGGRR, 0xAABBGGRR ----@param color_name string ----@param alpha integer? ----@return uColor -function get_color(color_name, alpha) end +function set_camera_layer_control_enabled(enable) 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 hardcoded 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 +function set_liquid_layer(l) end --## Types do ----@class Players - ---@class SaveContext ---@field save fun(self, data: string): boolean @@ -2307,6 +2291,7 @@ do ---@field online_players OnlinePlayer[] @size: 4 ---@field local_player OnlinePlayer ---@field lobby OnlineLobby + ---@field is_active fun(self): boolean ---@class OnlinePlayer ---@field game_mode GAME_MODE @@ -2338,6 +2323,8 @@ do ---@field save fun(self): nil @Save over a previously allocated SaveState ---@field clear fun(self): nil @Delete the SaveState and free the memory. The SaveState can't be used after this. ---@field get_state fun(self): StateMemory @Access the StateMemory inside a SaveState + ---@field get_frame fun(self): integer @Get the current frame from the SaveState, equivelent to the [get_frame](#Get_frame) global function that returns the frame from the "loaded in state" + ---@field get_prng fun(self): PRNG @Access the PRNG inside a SaveState ---@class BackgroundMusic ---@field game_startup BackgroundSound @@ -2490,7 +2477,7 @@ function PRNG:random(min, max) end ---@class EntityDB ---@field id ENT_TYPE - ---@field search_flags integer @MASK + ---@field search_flags MASK @MASK ---@field width number ---@field height number ---@field draw_depth integer @@ -2528,39 +2515,6 @@ function PRNG:random(min, max) end ---@field default_special_offsetx number ---@field default_special_offsety number ----@class RenderInfo - ---@field x number - ---@field y number - ---@field offset_x number - ---@field offset_y number - ---@field shader WORLD_SHADER - ---@field source Quad - ---@field destination Quad - ---@field tilew number - ---@field tileh number - ---@field facing_left boolean - ---@field angle number - ---@field animation_frame integer - ---@field render_inactive boolean - ---@field brightness number - ---@field texture_num integer - ---@field get_entity fun(self): Entity - ---@field set_normal_map_texture fun(self, texture_id: TEXTURE): boolean @Sets second_texture to the texture specified, then sets third_texture to SHINE_0 and texture_num to 3. You still have to change shader to 30 to render with normal map (same as COG normal maps) - ---@field get_second_texture fun(self): TEXTURE? - ---@field get_third_texture fun(self): TEXTURE? - ---@field set_second_texture fun(self, texture_id: TEXTURE): boolean - ---@field set_third_texture fun(self, texture_id: TEXTURE): boolean - ---@field set_texture_num fun(self, num: integer): boolean @Set the number of textures that may be used, need to have them set before for it to work - ---@field set_pre_virtual fun(self, entry: RENDER_INFO_OVERRIDE, fun: function): CallbackId @Hooks before the virtual function at index `entry`. - ---@field set_post_virtual fun(self, entry: RENDER_INFO_OVERRIDE, fun: function): CallbackId @Hooks after the virtual function at index `entry`. - ---@field clear_virtual fun(self, callback_id: CallbackId): nil @Clears the hook given by `callback_id`, alternatively use `clear_callback()` inside the hook. - ---@field set_pre_dtor fun(self, fun: fun(self: RenderInfo): nil): CallbackId @Hooks before the virtual function.
The callback signature is `nil dtor(RenderInfo self)` - ---@field set_post_dtor fun(self, fun: fun(self: RenderInfo): nil): CallbackId @Hooks after the virtual function.
The callback signature is `nil dtor(RenderInfo self)` - ---@field set_pre_draw fun(self, fun: fun(self: RenderInfo): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool draw(RenderInfo self)`
Virtual function docs:
Called when the entity enters the camera view, using its hitbox with an extra threshold. Handles low-level graphics tasks related to the GPU - ---@field set_post_draw fun(self, fun: fun(self: RenderInfo): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil draw(RenderInfo self)`
Virtual function docs:
Called when the entity enters the camera view, using its hitbox with an extra threshold. Handles low-level graphics tasks related to the GPU - ---@field set_pre_render fun(self, fun: fun(self: RenderInfo, offset: Vec2, vanilla_render_context: VanillaRenderContext): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool render(RenderInfo self, Vec2 offset, VanillaRenderContext vanilla_render_context)`
Virtual function docs:
Offset used in CO to draw the fake image of the entity on the other side of a level - ---@field set_post_render fun(self, fun: fun(self: RenderInfo, offset: Vec2, vanilla_render_context: VanillaRenderContext): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil render(RenderInfo self, Vec2 offset, VanillaRenderContext vanilla_render_context)`
Virtual function docs:
Offset used in CO to draw the fake image of the entity on the other side of a level - ---@class Entity ---@field type EntityDB @Type of the entity, contains special properties etc. If you want to edit them just for this entity look at the EntityDB ---@field overlay Entity @@ -2702,7 +2656,7 @@ function Entity:overlaps_with(other) end ---destroy_corpse and responsible are the standard parameters for the kill function ---@param destroy_corpse boolean ---@param responsible Entity ----@param mask integer? +---@param mask MASK? ---@param ent_types ENT_TYPE[] ---@param rec_mode RECURSIVE_MODE ---@return nil @@ -2714,7 +2668,7 @@ function Entity:kill_recursive(destroy_corpse, responsible, mask, ent_types, rec function Entity:kill_recursive(destroy_corpse, responsible) end ---Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc. ---To avoid that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check the mask, if the entity doesn't match, it will look in the provided ENT_TYPE's ----@param mask integer? +---@param mask MASK? ---@param ent_types ENT_TYPE[] ---@param rec_mode RECURSIVE_MODE ---@return nil @@ -3024,6 +2978,8 @@ function Movable:generic_update_world(move, sprint_factor, disable_gravity, on_r ---@field timer integer ---@field world integer ---@field theme integer + ---@field set_target fun(self, ww: integer, l: integer, t: integer): nil + ---@field get_target fun(self): integer, integer, integer @Get target world, level, theme of this door. If the `special_door` is false, it returns the StateMemory world_next, level_next, theme_next ---@class DecoratedDoor : ExitDoor ---@field special_bg Entity @@ -5668,6 +5624,39 @@ function VanillaRenderContext:draw_world_poly_filled(points, color) end ---@field get_font fun(self): TEXTURE ---@field set_font fun(self, id: TEXTURE): boolean +---@class RenderInfo + ---@field x number + ---@field y number + ---@field offset_x number + ---@field offset_y number + ---@field shader WORLD_SHADER + ---@field source Quad + ---@field destination Quad + ---@field tilew number + ---@field tileh number + ---@field facing_left boolean + ---@field angle number + ---@field animation_frame integer + ---@field render_inactive boolean + ---@field brightness number + ---@field texture_num integer + ---@field get_entity fun(self): Entity + ---@field set_normal_map_texture fun(self, texture_id: TEXTURE): boolean @Sets second_texture to the texture specified, then sets third_texture to SHINE_0 and texture_num to 3. You still have to change shader to 30 to render with normal map (same as COG normal maps) + ---@field get_second_texture fun(self): TEXTURE? + ---@field get_third_texture fun(self): TEXTURE? + ---@field set_second_texture fun(self, texture_id: TEXTURE): boolean + ---@field set_third_texture fun(self, texture_id: TEXTURE): boolean + ---@field set_texture_num fun(self, num: integer): boolean @Set the number of textures that may be used, need to have them set before for it to work + ---@field set_pre_virtual fun(self, entry: RENDER_INFO_OVERRIDE, fun: function): CallbackId @Hooks before the virtual function at index `entry`. + ---@field set_post_virtual fun(self, entry: RENDER_INFO_OVERRIDE, fun: function): CallbackId @Hooks after the virtual function at index `entry`. + ---@field clear_virtual fun(self, callback_id: CallbackId): nil @Clears the hook given by `callback_id`, alternatively use `clear_callback()` inside the hook. + ---@field set_pre_dtor fun(self, fun: fun(self: RenderInfo): nil): CallbackId @Hooks before the virtual function.
The callback signature is `nil dtor(RenderInfo self)` + ---@field set_post_dtor fun(self, fun: fun(self: RenderInfo): nil): CallbackId @Hooks after the virtual function.
The callback signature is `nil dtor(RenderInfo self)` + ---@field set_pre_draw fun(self, fun: fun(self: RenderInfo): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool draw(RenderInfo self)`
Virtual function docs:
Called when the entity enters the camera view, using its hitbox with an extra threshold. Handles low-level graphics tasks related to the GPU + ---@field set_post_draw fun(self, fun: fun(self: RenderInfo): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil draw(RenderInfo self)`
Virtual function docs:
Called when the entity enters the camera view, using its hitbox with an extra threshold. Handles low-level graphics tasks related to the GPU + ---@field set_pre_render fun(self, fun: fun(self: RenderInfo, offset: Vec2, vanilla_render_context: VanillaRenderContext): boolean): CallbackId @Hooks before the virtual function.
The callback signature is `bool render(RenderInfo self, Vec2 offset, VanillaRenderContext vanilla_render_context)`
Virtual function docs:
Offset used in CO to draw the fake image of the entity on the other side of a level + ---@field set_post_render fun(self, fun: fun(self: RenderInfo, offset: Vec2, vanilla_render_context: VanillaRenderContext): boolean): CallbackId @Hooks after the virtual function.
The callback signature is `nil render(RenderInfo self, Vec2 offset, VanillaRenderContext vanilla_render_context)`
Virtual function docs:
Offset used in CO to draw the fake image of the entity on the other side of a level + ---@class HudInventory ---@field enabled boolean ---@field health integer @@ -6775,6 +6764,12 @@ function LogicMagmamanSpawn:remove_spawn(ms) end end --## Static class functions +SaveState = nil +---Get the pre-allocated by the game save slot 1-4 +---@param save_slot integer +---@return SaveState +function SaveState:get(save_slot) end + Color = nil ---@return Color function Color:white() end @@ -6810,8 +6805,6 @@ function Color:fuchsia() end function Color:purple() end --## Constructors - -SaveState = nil ---Create a new temporary SaveState/clone of the main level state. Unlike save_state slots that are preallocated by the game anyway, these will use 32MiB a pop and aren't freed automatically, so make sure to clear them or reuse the same one to save memory. The garbage collector will eventually clear the SaveStates you don't have a handle to any more though. ---@return SaveState function SaveState:new() end diff --git a/docs/generate.py b/docs/generate.py index 9a89e6b1a..eb7b13c04 100644 --- a/docs/generate.py +++ b/docs/generate.py @@ -429,6 +429,9 @@ def print_lf(lf): cat = "Callback functions" 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["file"] for subs in ["game_patches_lua.cpp"]) or any( + subs in func["name"] for subs in ["replace_drop", "set_drop_chance"]): + cat = "Game patching functions" elif any(subs in func["name"] for subs in ["shop"]): cat = "Shop functions" elif any(subs in func["name"] for subs in ["_room"]): @@ -506,6 +509,10 @@ def print_lf(lf): for cat in sorted(func_cats): print("\n## " + cat + "\n") + if cat.startswith("Game patch"): + print( + "" +) for lf in sorted(func_cats[cat], key=lambda x: x["name"]): if len(ps.rpcfunc(lf["cpp"])): print_lf(lf) diff --git a/docs/parse_source.py b/docs/parse_source.py index 2147bcb45..a72935799 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -126,6 +126,8 @@ "../src/game_api/bucket.hpp", "../src/game_api/socket.hpp", "../src/game_api/savestate.hpp", + "../src/game_api/game_patches.hpp", + "../src/game_api/liquid_engine.hpp", ] api_files = [ "../src/game_api/script/script_impl.cpp", @@ -171,6 +173,10 @@ "../src/game_api/script/usertypes/logic_lua.cpp", "../src/game_api/script/usertypes/bucket_lua.cpp", "../src/game_api/script/usertypes/color_lua.cpp", + "../src/game_api/script/usertypes/deprecated_func.cpp", + "../src/game_api/script/usertypes/spawn_lua.cpp", + "../src/game_api/script/usertypes/options_lua.cpp", + "../src/game_api/script/usertypes/game_patches_lua.cpp", ] vtable_api_files = [ "../src/game_api/script/usertypes/vtables_lua.cpp", @@ -561,6 +567,7 @@ def run_parse(): "cpp": replace_fun(m.group(2)), "comment": comment, "cb_signature": cb_signature, + "file": file, } if not comment or "NoDoc" not in comment[0]: if comment and comment[0] == "Deprecated": diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 8fe898cea..060f0f904 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -305,7 +305,7 @@ Use this only when no other approach works, this call can be expensive if overus > Search script examples for [set_post_entity_spawn](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_entity_spawn) -#### [CallbackId](#Aliases) set_post_entity_spawn(function cb, [SPAWN_TYPE](#SPAWN_TYPE) flags, int mask, variadic_args entity_types) +#### [CallbackId](#Aliases) set_post_entity_spawn(function cb, [SPAWN_TYPE](#SPAWN_TYPE) flags, [MASK](#MASK) mask, variadic_args entity_types) Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. This is run right after the entity is spawned but before and particular properties are changed, e.g. owner or velocity. @@ -327,7 +327,7 @@ Sets a callback that is called right after the screen is drawn. > Search script examples for [set_pre_entity_spawn](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_entity_spawn) -#### [CallbackId](#Aliases) set_pre_entity_spawn(function cb, [SPAWN_TYPE](#SPAWN_TYPE) flags, int mask, variadic_args entity_types) +#### [CallbackId](#Aliases) set_pre_entity_spawn(function cb, [SPAWN_TYPE](#SPAWN_TYPE) flags, [MASK](#MASK) mask, variadic_args entity_types) Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. This is run before the entity is spawned, spawn your own entity and return its uid to replace the intended spawn. @@ -428,35 +428,6 @@ Raise a signal and probably crash the game ## Entity functions -### activate_sparktraps_hack - - -```lua -activate_sparktraps_hack(true); - --- set random speed, direction and distance for the spark -set_post_entity_spawn(function(ent) - - direction = 1 - if prng:random_chance(2, PRNG_CLASS.ENTITY_VARIATION) then - direction = -1 - end - - ent.speed = prng:random_float(PRNG_CLASS.ENTITY_VARIATION) * 0.1 * direction - ent.distance = prng:random_float(PRNG_CLASS.ENTITY_VARIATION) * 10 - -end, SPAWN_TYPE.ANY, 0, ENT_TYPE.ITEM_SPARK) -``` - - -> Search script examples for [activate_sparktraps_hack](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=activate_sparktraps_hack) - -#### nil activate_sparktraps_hack(bool activate) - -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 @@ -506,18 +477,6 @@ However this function offsets `attachee` (so you don't have to) and inserts it i Make `mount_uid` carry `rider_uid` on their back. Only use this with actual mounts and living things. -### change_waddler_drop - - -> Search script examples for [change_waddler_drop](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_waddler_drop) - -#### nil change_waddler_drop(array<[ENT_TYPE](#ENT_TYPE)> ent_types) - -Change [ENT_TYPE](#ENT_TYPE)'s spawned when [Waddler](#Waddler) dies, by default there are 3:
-{ITEM_PICKUP_COMPASS, ITEM_CHEST, ITEM_KEY}
-Max 255 types. -Use empty table as argument to reset to the game default - ### drop @@ -541,9 +500,9 @@ Calls the enter door function, position doesn't matter, can also enter closed do > Search script examples for [entity_get_items_by](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=entity_get_items_by) -#### vector<int> entity_get_items_by(int uid, array<[ENT_TYPE](#ENT_TYPE)> entity_types, int mask) +#### vector<int> entity_get_items_by(int uid, array<[ENT_TYPE](#ENT_TYPE)> entity_types, [MASK](#MASK) mask) -#### vector<int> entity_get_items_by(int uid, [ENT_TYPE](#ENT_TYPE) entity_type, int mask) +#### vector<int> entity_get_items_by(int uid, [ENT_TYPE](#ENT_TYPE) entity_type, [MASK](#MASK) mask) Gets uids of entities attached to given entity uid. Use `entity_type` and `mask` ([MASK](#MASK)) to filter, set them to 0 to return all attached entities. @@ -585,24 +544,6 @@ Remove item by uid from entity. `check_autokill` defaults to true, checks if ent Returns a list of all uids in `entities` for which `predicate(get_entity(uid))` returns true -### flip_entity - - -> Search script examples for [flip_entity](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flip_entity) - -#### nil flip_entity(int uid) - -Flip entity around by uid. All new entities face right by default. - -### force_olmec_phase_0 - - -> Search script examples for [force_olmec_phase_0](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=force_olmec_phase_0) - -#### nil force_olmec_phase_0(bool b) - -Forces [Olmec](#Olmec) to stay on phase 0 (stomping) - ### get_door_target @@ -617,9 +558,9 @@ Get door target `world`, `level`, `theme` > Search script examples for [get_entities_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_at) -#### vector<int> get_entities_at(array<[ENT_TYPE](#ENT_TYPE)> entity_types, int mask, float x, float y, [LAYER](#LAYER) layer, float radius) +#### vector<int> get_entities_at(array<[ENT_TYPE](#ENT_TYPE)> entity_types, [MASK](#MASK) mask, float x, float y, [LAYER](#LAYER) layer, float radius) -#### vector<int> get_entities_at([ENT_TYPE](#ENT_TYPE) entity_type, int mask, float x, float y, [LAYER](#LAYER) layer, float radius) +#### vector<int> get_entities_at([ENT_TYPE](#ENT_TYPE) entity_type, [MASK](#MASK) mask, float x, float y, [LAYER](#LAYER) layer, float radius) Get uids of matching entities inside some radius ([ENT_TYPE](#ENT_TYPE), [MASK](#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types Recommended to always set the mask, even if you look for one entity type @@ -640,9 +581,9 @@ end > Search script examples for [get_entities_by](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_by) -#### vector<int> get_entities_by(array<[ENT_TYPE](#ENT_TYPE)> entity_types, int mask, [LAYER](#LAYER) layer) +#### vector<int> get_entities_by(array<[ENT_TYPE](#ENT_TYPE)> entity_types, [MASK](#MASK) mask, [LAYER](#LAYER) layer) -#### vector<int> get_entities_by([ENT_TYPE](#ENT_TYPE) entity_type, int mask, [LAYER](#LAYER) layer) +#### vector<int> get_entities_by([ENT_TYPE](#ENT_TYPE) entity_type, [MASK](#MASK) mask, [LAYER](#LAYER) layer) Get uids of entities by some conditions ([ENT_TYPE](#ENT_TYPE), [MASK](#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. Recommended to always set the mask, even if you look for one entity type @@ -696,9 +637,9 @@ Get uids of static entities overlapping this grid position (decorations, backgro > Search script examples for [get_entities_overlapping_hitbox](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_overlapping_hitbox) -#### vector<int> get_entities_overlapping_hitbox(array<[ENT_TYPE](#ENT_TYPE)> entity_types, int mask, [AABB](#AABB) hitbox, [LAYER](#LAYER) layer) +#### vector<int> get_entities_overlapping_hitbox(array<[ENT_TYPE](#ENT_TYPE)> entity_types, [MASK](#MASK) mask, [AABB](#AABB) hitbox, [LAYER](#LAYER) layer) -#### vector<int> get_entities_overlapping_hitbox([ENT_TYPE](#ENT_TYPE) entity_type, int mask, [AABB](#AABB) hitbox, [LAYER](#LAYER) layer) +#### vector<int> get_entities_overlapping_hitbox([ENT_TYPE](#ENT_TYPE) entity_type, [MASK](#MASK) mask, [AABB](#AABB) hitbox, [LAYER](#LAYER) layer) Get uids of matching entities overlapping with the given hitbox. Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types @@ -784,37 +725,6 @@ Get the [EntityDB](#EntityDB) behind an [ENT_TYPE](#ENT_TYPE)... Kills an entity by uid. `destroy_corpse` defaults to `true`, if you are killing for example a caveman and want the corpse to stay make sure to pass `false`. -### lock_door_at - - -> Search script examples for [lock_door_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=lock_door_at) - -#### nil lock_door_at(float x, float y) - -Try to lock the exit at coordinates - -### modify_ankh_health_gain - - -> Search script examples for [modify_ankh_health_gain](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=modify_ankh_health_gain) - -#### nil modify_ankh_health_gain(int max_health, int beat_add_health) - -Change how much health the ankh gives you after death, with every beat (the heart beat effect) it will add `beat_add_health` to your health, -`beat_add_health` has to be divisor of `health` and can't be 0, otherwise the function does nothing. Set `health` to 0 to return to the game defaults -If you set `health` above the game max health it will be forced down to the game max - -### modify_sparktraps - - -> Search script examples for [modify_sparktraps](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=modify_sparktraps) - -#### nil modify_sparktraps(float angle_increment = 0.015, float distance = 3.0) - -Changes characteristics of (all) sparktraps: speed, rotation direction and distance from center -Speed: expressed as the amount that should be added to the angle every frame (use a negative number to go in the other direction) -Distance from center: if you go above 3.0 the game might crash because a spark may go out of bounds! - ### move_entity @@ -860,26 +770,6 @@ Pick up another entity by uid. Make sure you're not already holding something, o Poisons entity, to cure poison set [Movable](#Movable).`poison_tick_timer` to -1 -### replace_drop - - -> Search script examples for [replace_drop](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=replace_drop) - -#### nil replace_drop(int drop_id, [ENT_TYPE](#ENT_TYPE) new_drop_entity_type) - -Changes a particular drop, e.g. what Van Horsing throws at you (use e.g. replace_drop([DROP](#DROP).VAN_HORSING_DIAMOND, [ENT_TYPE](#ENT_TYPE).ITEM_PLASMACANNON)) -Use `0` as type to reset this drop to default, use `-1` as drop_id to reset all to default - -### set_boss_door_control_enabled - - -> Search script examples for [set_boss_door_control_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_boss_door_control_enabled) - -#### nil set_boss_door_control_enabled(bool enable) - -Allows you to disable the control over the door for [Hundun](#Hundun) and [Tiamat](#Tiamat) -This will also prevent game crashing when there is no exit door when they are in level - ### set_contents @@ -890,15 +780,6 @@ This will also prevent game crashing when there is no exit door when they are in Set the contents of [Coffin](#Coffin), [Present](#Present), [Pot](#Pot), [Container](#Container) Check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) for what the exact [ENT_TYPE](#ENT_TYPE)'s can this function affect -### set_cursepot_ghost_enabled - - -> Search script examples for [set_cursepot_ghost_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_cursepot_ghost_enabled) - -#### nil set_cursepot_ghost_enabled(bool enable) - -Determines whether the ghost appears when breaking the ghost pot - ### set_door @@ -917,89 +798,6 @@ Short for [set_door_target](#set_door_target). Make an [ENT_TYPE](#ENT_TYPE).FLOOR_DOOR_EXIT go to world `w`, level `l`, theme `t` -### set_drop_chance - - -> Search script examples for [set_drop_chance](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_drop_chance) - -#### nil set_drop_chance(int dropchance_id, int new_drop_chance) - -Alters the drop chance for the provided monster-item combination (use e.g. set_drop_chance([DROPCHANCE](#DROPCHANCE).MOLE_MATTOCK, 10) for a 1 in 10 chance) -Use `-1` as dropchance_id to reset all to default - -### set_explosion_mask - - -> Search script examples for [set_explosion_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_explosion_mask) - -#### nil set_explosion_mask(int mask) - -Sets which entities are affected by a bomb explosion. Default = [MASK](#MASK).PLAYER | [MASK](#MASK).MOUNT | [MASK](#MASK).MONSTER | [MASK](#MASK).ITEM | [MASK](#MASK).ACTIVEFLOOR | [MASK](#MASK).FLOOR - -### set_kapala_blood_threshold - - -> Search script examples for [set_kapala_blood_threshold](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_kapala_blood_threshold) - -#### nil set_kapala_blood_threshold(int threshold) - -Sets the amount of blood drops in the Kapala needed to trigger a health increase (default = 7). - -### set_kapala_hud_icon - - -> Search script examples for [set_kapala_hud_icon](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_kapala_hud_icon) - -#### nil set_kapala_hud_icon(int icon_index) - -Sets the hud icon for the Kapala (0-6 ; -1 for default behaviour). -If you set a Kapala treshold greater than 7, make sure to set the hud icon in the range 0-6, or other icons will appear in the hud! - -### set_max_rope_length - - -> Search script examples for [set_max_rope_length](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_max_rope_length) - -#### nil set_max_rope_length(int length) - -Sets the maximum length of a thrown rope (anchor segment not included). Unfortunately, setting this higher than default (6) creates visual glitches in the rope, even though it is fully functional. - -### set_olmec_cutscene_enabled - - -> Search script examples for [set_olmec_cutscene_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_olmec_cutscene_enabled) - -#### nil set_olmec_cutscene_enabled(bool enable) - -[Olmec](#Olmec) cutscene moves [Olmec](#Olmec) and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and [Olmec](#Olmec) will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with [Olmec](#Olmec) in the hole. - -### set_olmec_phase_y_level - - -> Search script examples for [set_olmec_phase_y_level](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_olmec_phase_y_level) - -#### nil set_olmec_phase_y_level(int phase, float y) - -Sets the Y-level at which [Olmec](#Olmec) changes phases - -### set_time_ghost_enabled - - -> Search script examples for [set_time_ghost_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_time_ghost_enabled) - -#### nil set_time_ghost_enabled(bool b) - -Determines whether the time ghost appears, including the showing of the ghost toast - -### set_time_jelly_enabled - - -> Search script examples for [set_time_jelly_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_time_jelly_enabled) - -#### nil set_time_jelly_enabled(bool b) - -Determines whether the time jelly appears in cosmic ocean - ### unequip_backitem @@ -1009,15 +807,6 @@ Determines whether the time jelly appears in cosmic ocean Unequips the currently worn backitem -### unlock_door_at - - -> Search script examples for [unlock_door_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=unlock_door_at) - -#### nil unlock_door_at(float x, float y) - -Try to unlock the exit at coordinates - ### waddler_count_entity @@ -1164,7 +953,7 @@ Flips the nth bit in a number. This doesn't actually change the variable you pas > Search script examples for [get_entity_flags](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entity_flags) -#### int get_entity_flags(int uid) +#### [ENT_FLAG](#ENT_FLAG) get_entity_flags(int uid) Get the `flags` field from entity by uid @@ -1173,7 +962,7 @@ Get the `flags` field from entity by uid > Search script examples for [get_entity_flags2](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entity_flags2) -#### int get_entity_flags2(int uid) +#### [ENT_MORE_FLAG](#ENT_MORE_FLAG) get_entity_flags2(int uid) Get the `more_flags` field from entity by uid @@ -1191,7 +980,7 @@ Get `state.level_flags` > Search script examples for [set_entity_flags](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_entity_flags) -#### nil set_entity_flags(int uid, int flags) +#### nil set_entity_flags(int uid, [ENT_FLAG](#ENT_FLAG) flags) Set the `flags` field from entity by uid @@ -1200,7 +989,7 @@ Set the `flags` field from entity by uid > Search script examples for [set_entity_flags2](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_entity_flags2) -#### nil set_entity_flags2(int uid, int flags) +#### nil set_entity_flags2(int uid, [ENT_MORE_FLAG](#ENT_MORE_FLAG) flags) Set the `more_flags` field from entity by uid @@ -1249,8 +1038,9 @@ Returns true if the nth bit is set in the number. Returns true if a bitmask is set in the number. -## Generic functions +## Game patching functions + ### activate_crush_elevator_hack @@ -1274,46 +1064,478 @@ Activate custom variables for y coordinate limit for hundun and spawn of it's he note: because those variables are custom and game does not initiate them, you need to do it yourself for each [Hundun](#Hundun) entity, recommending set_post_entity_spawn default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 -### add_custom_type +### activate_sparktraps_hack -> Search script examples for [add_custom_type](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_custom_type) +```lua +activate_sparktraps_hack(true); -#### [ENT_TYPE](#ENT_TYPE) add_custom_type(array<[ENT_TYPE](#ENT_TYPE)> types) +-- set random speed, direction and distance for the spark +set_post_entity_spawn(function(ent) -#### [ENT_TYPE](#ENT_TYPE) add_custom_type() + direction = 1 + if prng:random_chance(2, PRNG_CLASS.ENTITY_VARIATION) then + direction = -1 + end -Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn -Use empty array or no parameter to get new unique [ENT_TYPE](#ENT_TYPE) that can be used for custom [EntityDB](#EntityDB) + ent.speed = prng:random_float(PRNG_CLASS.ENTITY_VARIATION) * 0.1 * direction + ent.distance = prng:random_float(PRNG_CLASS.ENTITY_VARIATION) * 10 -### add_money +end, SPAWN_TYPE.ANY, 0, ENT_TYPE.ITEM_SPARK) +``` -> Search script examples for [add_money](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_money) +> Search script examples for [activate_sparktraps_hack](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=activate_sparktraps_hack) -#### int add_money(int amount, optional display_time) +#### nil activate_sparktraps_hack(bool activate) -Adds money to the state.money_shop_total and displays the effect on the HUD for money change -Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction +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_money_slot +### activate_tiamat_position_hack -> Search script examples for [add_money_slot](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_money_slot) +```lua +activate_tiamat_position_hack(true); -#### int add_money_slot(int amount, int player_slot, optional display_time) +set_post_entity_spawn(function(ent) -Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change -Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction + -- make them same as in the game, but relative to the tiamat entity + ent.attack_x = ent.x - 1 + ent.attack_y = ent.y + 2 -### change_poison_timer +end, SPAWN_TYPE.ANY, 0, ENT_TYPE.MONS_TIAMAT) +``` -> Search script examples for [change_poison_timer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_poison_timer) +> Search script examples for [activate_tiamat_position_hack](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=activate_tiamat_position_hack) -#### nil change_poison_timer(int frames) +#### nil activate_tiamat_position_hack(bool activate) -Change the amount of frames after the damage from poison is applied +Activate custom variables for position used for detecting the player (normally hardcoded) +note: because those variables are custom and game does not initiate them, you need to do it yourself for each [Tiamat](#Tiamat) entity, recommending set_post_entity_spawn +default game values are: attack_x = 17.5 attack_y = 62.5 + +### change_altar_damage_spawns + + +> Search script examples for [change_altar_damage_spawns](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_altar_damage_spawns) + +#### nil change_altar_damage_spawns(array<[ENT_TYPE](#ENT_TYPE)> ent_types) + +Change [ENT_TYPE](#ENT_TYPE)'s spawned when you damage the altar, by default there are 6:
+{MONS_BAT, MONS_BEE, MONS_SPIDER, MONS_JIANGSHI, MONS_FEMALE_JIANGSHI, MONS_VAMPIRE}
+Max 255 types. +Use empty table as argument to reset to the game default + +### change_diceshop_prizes + + +> Search script examples for [change_diceshop_prizes](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_diceshop_prizes) + +#### nil change_diceshop_prizes(array<[ENT_TYPE](#ENT_TYPE)> ent_types) + +Change [ENT_TYPE](#ENT_TYPE)'s spawned in dice shops (Madame Tusk as well), by default there are 25:
+{ITEM_PICKUP_BOMBBAG, ITEM_PICKUP_BOMBBOX, ITEM_PICKUP_ROPEPILE, ITEM_PICKUP_COMPASS, ITEM_PICKUP_PASTE, ITEM_PICKUP_PARACHUTE, ITEM_PURCHASABLE_CAPE, ITEM_PICKUP_SPECTACLES, ITEM_PICKUP_CLIMBINGGLOVES, ITEM_PICKUP_PITCHERSMITT, +ENT_TYPE_ITEM_PICKUP_SPIKESHOES, ENT_TYPE_ITEM_PICKUP_SPRINGSHOES, ITEM_MACHETE, ITEM_BOOMERANG, ITEM_CROSSBOW, ITEM_SHOTGUN, ITEM_FREEZERAY, ITEM_WEBGUN, ITEM_CAMERA, ITEM_MATTOCK, ITEM_PURCHASABLE_JETPACK, ITEM_PURCHASABLE_HOVERPACK, +ITEM_TELEPORTER, ITEM_PURCHASABLE_TELEPORTER_BACKPACK, ITEM_PURCHASABLE_POWERPACK}
+Min 6, Max 255, if you want less then 6 you need to write some of them more then once (they will have higher "spawn chance"). +If you use this function in the level with dice shop in it, you have to update `item_ids` in the [ITEM_DICE_PRIZE_DISPENSER](#PrizeDispenser). +Use empty table as argument to reset to the game default + +### change_poison_timer + + +> Search script examples for [change_poison_timer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_poison_timer) + +#### nil change_poison_timer(int frames) + +Change the amount of frames after the damage from poison is applied + +### change_sunchallenge_spawns + + +> Search script examples for [change_sunchallenge_spawns](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_sunchallenge_spawns) + +#### nil change_sunchallenge_spawns(array<[ENT_TYPE](#ENT_TYPE)> ent_types) + +Change [ENT_TYPE](#ENT_TYPE)'s spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4:
+{MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER}
+Use empty table as argument to reset to the game default + +### change_waddler_drop + + +> Search script examples for [change_waddler_drop](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_waddler_drop) + +#### nil change_waddler_drop(array<[ENT_TYPE](#ENT_TYPE)> ent_types) + +Change [ENT_TYPE](#ENT_TYPE)'s spawned when [Waddler](#Waddler) dies, by default there are 3:
+{ITEM_PICKUP_COMPASS, ITEM_CHEST, ITEM_KEY}
+Max 255 types. +Use empty table as argument to reset to the game default + +### disable_floor_embeds + + +> Search script examples for [disable_floor_embeds](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=disable_floor_embeds) + +#### bool disable_floor_embeds(bool disable) + +Disable all crust item spawns, returns whether they were already disabled before the call + +### force_olmec_phase_0 + + +> Search script examples for [force_olmec_phase_0](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=force_olmec_phase_0) + +#### nil force_olmec_phase_0(bool b) + +Forces [Olmec](#Olmec) to stay on phase 0 (stomping) + +### modify_ankh_health_gain + + +> Search script examples for [modify_ankh_health_gain](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=modify_ankh_health_gain) + +#### nil modify_ankh_health_gain(int max_health, int beat_add_health) + +Change how much health the ankh gives you after death, with every beat (the heart beat effect) it will add `beat_add_health` to your health, +`beat_add_health` has to be divisor of `health` and can't be 0, otherwise the function does nothing. Set `health` to 0 to return to the game defaults +If you set `health` above the game max health it will be forced down to the game max + +### modify_sparktraps + + +> Search script examples for [modify_sparktraps](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=modify_sparktraps) + +#### nil modify_sparktraps(float angle_increment = 0.015, float distance = 3.0) + +Changes characteristics of (all) sparktraps: speed, rotation direction and distance from center +Speed: expressed as the amount that should be added to the angle every frame (use a negative number to go in the other direction) +Distance from center: if you go above 3.0 the game might crash because a spark may go out of bounds! + +### replace_drop + + +> Search script examples for [replace_drop](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=replace_drop) + +#### nil replace_drop([DROP](#DROP) drop_id, [ENT_TYPE](#ENT_TYPE) new_drop_entity_type) + +Changes a particular drop, e.g. what Van Horsing throws at you (use e.g. replace_drop([DROP](#DROP).VAN_HORSING_DIAMOND, [ENT_TYPE](#ENT_TYPE).ITEM_PLASMACANNON)) +Use `0` as type to reset this drop to default, use `-1` as drop_id to reset all to default + +### set_boss_door_control_enabled + + +> Search script examples for [set_boss_door_control_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_boss_door_control_enabled) + +#### nil set_boss_door_control_enabled(bool enable) + +Allows you to disable the control over the door for [Hundun](#Hundun) and [Tiamat](#Tiamat) +This will also prevent game crashing when there is no exit door when they are in level + +### set_camera_layer_control_enabled + + +```lua +set_camera_layer_control_enabled(false) + +g_current_timer = nil +-- default load_time 36 +function change_layer(layer_to, load_time) + + if state.camera_layer == layer_to then + return + end + if g_current_timer ~= nil then + clear_callback(g_current_timer) + g_current_timer = nil + end + -- if we don't want the load time, we can just change the actual layer + if load_time == nil or load_time == 0 then + state.camera_layer = layer_to + return + end + + state.layer_transition_timer = load_time + state.transition_to_layer = layer_to + state.camera_layer = layer_to +end + +``` + + +> Search script examples for [set_camera_layer_control_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_camera_layer_control_enabled) + +#### nil set_camera_layer_control_enabled(bool enable) + +This disables the `state.camera_layer` to be forced to the `(leader player).layer` and setting of the `state.layer_transition_timer` & `state.transition_to_layer` when player enters layer door. +Letting you control those manually. +Look at the example on how to mimic game layer switching behavior + +### set_camp_camera_bounds_enabled + + +> Search script examples for [set_camp_camera_bounds_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_camp_camera_bounds_enabled) + +#### nil set_camp_camera_bounds_enabled(bool b) + +Enables or disables the default position based camp camera bounds, to set them manually yourself + +### set_cursepot_ghost_enabled + + +> Search script examples for [set_cursepot_ghost_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_cursepot_ghost_enabled) + +#### nil set_cursepot_ghost_enabled(bool enable) + +Determines whether the ghost appears when breaking the ghost pot + +### set_drop_chance + + +> Search script examples for [set_drop_chance](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_drop_chance) + +#### nil set_drop_chance([DROPCHANCE](#DROPCHANCE) dropchance_id, int new_drop_chance) + +Alters the drop chance for the provided monster-item combination (use e.g. set_drop_chance([DROPCHANCE](#DROPCHANCE).MOLE_MATTOCK, 10) for a 1 in 10 chance) +Use `-1` as dropchance_id to reset all to default + +### set_ending_unlock + + +```lua +-- change character unlocked by endings to pilot +set_ending_unlock(ENT_TYPE.CHAR_PILOT) + +-- change texture of the actual savior in endings to pilot +set_callback(function() + set_post_entity_spawn(function(ent) + if state.screen == SCREEN.WIN then + ent:set_texture(TEXTURE.DATA_TEXTURES_CHAR_PINK_0) + end + clear_callback() + end, SPAWN_TYPE.SYSTEMIC, MASK.PLAYER) +end, ON.WIN) + +``` + + +> Search script examples for [set_ending_unlock](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_ending_unlock) + +#### nil set_ending_unlock([ENT_TYPE](#ENT_TYPE) type) + +Force the character unlocked in either ending to [ENT_TYPE](#ENT_TYPE). Set to 0 to reset to the default guys. Does not affect the texture of the actual savior. (See example) + +### set_explosion_mask + + +> Search script examples for [set_explosion_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_explosion_mask) + +#### nil set_explosion_mask(int mask) + +Sets which entities are affected by a bomb explosion. Default = [MASK](#MASK).PLAYER | [MASK](#MASK).MOUNT | [MASK](#MASK).MONSTER | [MASK](#MASK).ITEM | [MASK](#MASK).ACTIVEFLOOR | [MASK](#MASK).FLOOR + +### set_ghost_spawn_times + + +> Search script examples for [set_ghost_spawn_times](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_ghost_spawn_times) + +#### nil set_ghost_spawn_times(int normal = 10800, int cursed = 9000) + +Determines when the ghost appears, either when the player is cursed or not + +### set_kapala_blood_threshold + + +> Search script examples for [set_kapala_blood_threshold](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_kapala_blood_threshold) + +#### nil set_kapala_blood_threshold(int threshold) + +Sets the amount of blood drops in the Kapala needed to trigger a health increase (default = 7). + +### set_kapala_hud_icon + + +> Search script examples for [set_kapala_hud_icon](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_kapala_hud_icon) + +#### nil set_kapala_hud_icon(int icon_index) + +Sets the hud icon for the Kapala (0-6 ; -1 for default behaviour). +If you set a Kapala treshold greater than 7, make sure to set the hud icon in the range 0-6, or other icons will appear in the hud! + +### set_level_logic_enabled + + +> Search script examples for [set_level_logic_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_level_logic_enabled) + +#### nil set_level_logic_enabled(bool enable) + +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 hardcoded 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_max_rope_length + + +> Search script examples for [set_max_rope_length](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_max_rope_length) + +#### nil set_max_rope_length(int length) + +Sets the maximum length of a thrown rope (anchor segment not included). Unfortunately, setting this higher than default (6) creates visual glitches in the rope, even though it is fully functional. + +### set_olmec_cutscene_enabled + + +> Search script examples for [set_olmec_cutscene_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_olmec_cutscene_enabled) + +#### nil set_olmec_cutscene_enabled(bool enable) + +[Olmec](#Olmec) cutscene moves [Olmec](#Olmec) and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and [Olmec](#Olmec) will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with [Olmec](#Olmec) in the hole. + +### set_olmec_phase_y_level + + +> Search script examples for [set_olmec_phase_y_level](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_olmec_phase_y_level) + +#### nil set_olmec_phase_y_level(int phase, float y) + +Sets the Y-level at which [Olmec](#Olmec) changes phases + +### set_start_level_paused + + +> Search script examples for [set_start_level_paused](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_start_level_paused) + +#### nil set_start_level_paused(bool enable) + +Setting to true will stop the state update from unpausing after a screen load, leaving you with state.pause == [PAUSE](#PAUSE).[FADE](#FADE) on the first frame to do what you want. + +### set_storage_layer + + +```lua +-- Sets the right layer when using the vanilla tile code if waddler is still happy, +-- otherwise spawns the floor to the left of this tile. +-- Manually spawning FLOOR_STORAGE pre-tilecode doesn't seem to work as expected, +-- so we destroy it post-tilecode. +set_post_tile_code_callback(function(x, y, layer) + if not test_flag(state.quest_flags, 10) then + -- Just set the layer and let the vanilla tilecode handle the floor + set_storage_layer(layer) + else + local floor = get_entity(get_grid_entity_at(x, y, layer)) + if floor then + floor:destroy() + end + if get_grid_entity_at(x - 1, y, layer) ~= -1 then + local left = get_entity(get_grid_entity_at(x - 1, y, layer)) + spawn_grid_entity(left.type.id, x, y, layer) + end + end +end, "storage_floor") + +-- This fixes a bug in the game that breaks storage on transition. +-- The old storage_uid is not cleared after every level for some reason. +set_callback(function() + state.storage_uid = -1 +end, ON.TRANSITION) + +-- Having a waddler is completely optional for storage, +-- but this makes a nice waddler room if he still likes you. +define_tile_code("waddler") +set_pre_tile_code_callback(function(x, y, layer) + if not test_flag(state.quest_flags, 10) then + local uid = spawn_roomowner(ENT_TYPE.MONS_STORAGEGUY, x + 0.5, y, layer, ROOM_TEMPLATE.WADDLER) + set_on_kill(uid, function() + -- Disable current level storage if you kill waddler + state.storage_uid = -1 + end) + end + return true +end, "waddler") + +``` + + +> Search script examples for [set_storage_layer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_storage_layer) + +#### nil set_storage_layer([LAYER](#LAYER) layer) + +Set layer to search for storage items on + +### set_tiamat_cutscene_enabled + + +> Search script examples for [set_tiamat_cutscene_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_tiamat_cutscene_enabled) + +#### nil set_tiamat_cutscene_enabled(bool enable) + +[Tiamat](#Tiamat) cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want [Tiamat](#Tiamat) kill to be required + +### set_time_ghost_enabled + + +> Search script examples for [set_time_ghost_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_time_ghost_enabled) + +#### nil set_time_ghost_enabled(bool b) + +Determines whether the time ghost appears, including the showing of the ghost toast + +### set_time_jelly_enabled + + +> Search script examples for [set_time_jelly_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_time_jelly_enabled) + +#### nil set_time_jelly_enabled(bool b) + +Determines whether the time jelly appears in cosmic ocean + +## Generic functions + + +### add_custom_type + + +> Search script examples for [add_custom_type](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_custom_type) + +#### [ENT_TYPE](#ENT_TYPE) add_custom_type(array<[ENT_TYPE](#ENT_TYPE)> types) + +#### [ENT_TYPE](#ENT_TYPE) add_custom_type() + +Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn +Use empty array or no parameter to get new unique [ENT_TYPE](#ENT_TYPE) that can be used for custom [EntityDB](#EntityDB) + +### add_money + + +> Search script examples for [add_money](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_money) + +#### int add_money(int amount, optional display_time) + +Adds money to the state.money_shop_total and displays the effect on the HUD for money change +Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction + +### add_money_slot + + +> Search script examples for [add_money_slot](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_money_slot) + +#### int add_money_slot(int amount, int player_slot, optional display_time) + +Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change +Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction ### clear_cache @@ -1401,15 +1623,6 @@ Destroys a layer and all entities in it. Destroys all layers and all entities in the level. Usually a bad idea, unless you also call create_level and spawn the player back in. -### disable_floor_embeds - - -> Search script examples for [disable_floor_embeds](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=disable_floor_embeds) - -#### bool disable_floor_embeds(bool disable) - -Disable all crust item spawns, returns whether they were already disabled before the call - ### force_journal @@ -1473,7 +1686,7 @@ short for state->money_shop_total + loop[inventory.money + inventory.collected_m #### int get_frame() -Get the current frame count since the game was started. You can use this to make some timers yourself, the engine runs at 60fps. This counter is paused if you block PRE_UPDATE from running, and also doesn't increment during some loading screens, even though state update still runs. +Get the current frame count since the game was started*. You can use this to make some timers yourself, the engine runs at 60fps. This counter is paused if the pause is set with flags [PAUSE](#PAUSE).[FADE](#FADE) or [PAUSE](#PAUSE).ANKH. ### get_frametime @@ -1895,45 +2108,6 @@ Seed the game prng. Set the current adventure seed pair. Use just before resetting a run to recreate an adventure run. -### set_camera_layer_control_enabled - - -```lua -set_camera_layer_control_enabled(false) - -g_current_timer = nil --- default load_time 36 -function change_layer(layer_to, load_time) - - if state.camera_layer == layer_to then - return - end - if g_current_timer ~= nil then - clear_callback(g_current_timer) - g_current_timer = nil - end - -- if we don't want the load time, we can just change the actual layer - if load_time == nil or load_time == 0 then - state.camera_layer = layer_to - return - end - - state.layer_transition_timer = load_time - state.transition_to_layer = layer_to - state.camera_layer = layer_to -end - -``` - - -> Search script examples for [set_camera_layer_control_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_camera_layer_control_enabled) - -#### nil set_camera_layer_control_enabled(bool enable) - -This disables the `state.camera_layer` to be forced to the `(leader player).layer` and setting of the `state.layer_transition_timer` & `state.transition_to_layer` when player enters layer door. -Letting you control those manually. -Look at the example on how to mimic game layer switching behavior - ### set_character_heart_color @@ -1943,32 +2117,6 @@ Look at the example on how to mimic game layer switching behavior Same as `Player.set_heart_color` -### set_ending_unlock - - -```lua --- change character unlocked by endings to pilot -set_ending_unlock(ENT_TYPE.CHAR_PILOT) - --- change texture of the actual savior in endings to pilot -set_callback(function() - set_post_entity_spawn(function(ent) - if state.screen == SCREEN.WIN then - ent:set_texture(TEXTURE.DATA_TEXTURES_CHAR_PINK_0) - end - clear_callback() - end, SPAWN_TYPE.SYSTEMIC, MASK.PLAYER) -end, ON.WIN) - -``` - - -> Search script examples for [set_ending_unlock](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_ending_unlock) - -#### nil set_ending_unlock([ENT_TYPE](#ENT_TYPE) type) - -Force the character unlocked in either ending to [ENT_TYPE](#ENT_TYPE). Set to 0 to reset to the default guys. Does not affect the texture of the actual savior. (See example) - ### set_frametime @@ -2025,26 +2173,6 @@ Enables or disables the journal Set the value for the specified config -### set_level_logic_enabled - - -> Search script examples for [set_level_logic_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_level_logic_enabled) - -#### nil set_level_logic_enabled(bool enable) - -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 hardcoded 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 @@ -2088,77 +2216,6 @@ Sets the specified setting temporarily. These values are not saved and might res Set multiplier (default 1.0) for a QueryPerformanceCounter hook based speedhack, similar to the one in Cheat Engine. Call without arguments to reset. Also see [set_frametime](#set_frametime) -### set_start_level_paused - - -> Search script examples for [set_start_level_paused](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_start_level_paused) - -#### nil set_start_level_paused(bool enable) - -Setting to true will stop the state update from unpausing after a screen load, leaving you with state.pause == [PAUSE](#PAUSE).[FADE](#FADE) on the first frame to do what you want. - -### set_storage_layer - - -```lua --- Sets the right layer when using the vanilla tile code if waddler is still happy, --- otherwise spawns the floor to the left of this tile. --- Manually spawning FLOOR_STORAGE pre-tilecode doesn't seem to work as expected, --- so we destroy it post-tilecode. -set_post_tile_code_callback(function(x, y, layer) - if not test_flag(state.quest_flags, 10) then - -- Just set the layer and let the vanilla tilecode handle the floor - set_storage_layer(layer) - else - local floor = get_entity(get_grid_entity_at(x, y, layer)) - if floor then - floor:destroy() - end - if get_grid_entity_at(x - 1, y, layer) ~= -1 then - local left = get_entity(get_grid_entity_at(x - 1, y, layer)) - spawn_grid_entity(left.type.id, x, y, layer) - end - end -end, "storage_floor") - --- This fixes a bug in the game that breaks storage on transition. --- The old storage_uid is not cleared after every level for some reason. -set_callback(function() - state.storage_uid = -1 -end, ON.TRANSITION) - --- Having a waddler is completely optional for storage, --- but this makes a nice waddler room if he still likes you. -define_tile_code("waddler") -set_pre_tile_code_callback(function(x, y, layer) - if not test_flag(state.quest_flags, 10) then - local uid = spawn_roomowner(ENT_TYPE.MONS_STORAGEGUY, x + 0.5, y, layer, ROOM_TEMPLATE.WADDLER) - set_on_kill(uid, function() - -- Disable current level storage if you kill waddler - state.storage_uid = -1 - end) - end - return true -end, "waddler") - -``` - - -> Search script examples for [set_storage_layer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_storage_layer) - -#### nil set_storage_layer([LAYER](#LAYER) layer) - -Set layer to search for storage items on - -### set_tiamat_cutscene_enabled - - -> Search script examples for [set_tiamat_cutscene_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_tiamat_cutscene_enabled) - -#### nil set_tiamat_cutscene_enabled(bool enable) - -[Tiamat](#Tiamat) cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want [Tiamat](#Tiamat) kill to be required - ### show_journal @@ -2215,7 +2272,7 @@ Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDAT > Search script examples for [warp](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=warp) -#### nil warp(int w, int l, int t) +#### nil warp(int world, int level, int theme) Warp to a level immediately. @@ -2281,7 +2338,7 @@ Creates a new [Illumination](#Illumination). Don't forget to continuously call [ #### nil refresh_illumination([Illumination](#Illumination) illumination) -Refreshes an [Illumination](#Illumination), keeps it from fading out (updates the timer, keeping it in sync with the game render) +Refreshes an [Illumination](#Illumination), keeps it from fading out, short for `illumination.timer = get_frame()` ## Message functions @@ -2636,30 +2693,6 @@ Renders the particles to the screen. Only used with screen particle emitters. Se ## Position functions -### activate_tiamat_position_hack - - -```lua -activate_tiamat_position_hack(true); - -set_post_entity_spawn(function(ent) - - -- make them same as in the game, but relative to the tiamat entity - ent.attack_x = ent.x - 1 - ent.attack_y = ent.y + 2 - -end, SPAWN_TYPE.ANY, 0, ENT_TYPE.MONS_TIAMAT) -``` - - -> Search script examples for [activate_tiamat_position_hack](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=activate_tiamat_position_hack) - -#### nil activate_tiamat_position_hack(bool activate) - -Activate custom variables for position used for detecting the player (normally hardcoded) -note: because those variables are custom and game does not initiate them, you need to do it yourself for each [Tiamat](#Tiamat) entity, recommending set_post_entity_spawn -default game values are: attack_x = 17.5 attack_y = 62.5 - ### distance @@ -2889,15 +2922,6 @@ Translate an entity position to screen position to be used in drawing functions Sets the absolute current camera position without rubberbanding animation. Ignores camera bounds or currently focused uid, but doesn't clear them. Best used in [ON](#ON).RENDER_PRE_GAME or similar. See [Camera](#Camera) for proper camera handling with bounds and rubberbanding. -### set_camp_camera_bounds_enabled - - -> Search script examples for [set_camp_camera_bounds_enabled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_camp_camera_bounds_enabled) - -#### nil set_camp_camera_bounds_enabled(bool b) - -Enables or disables the default position based camp camera bounds, to set them manually yourself - ### update_camera_position @@ -3045,21 +3069,6 @@ Spawn a [RoomOwner](#RoomOwner) (or a few other like [CavemanShopkeeper](#Cavema Adds entity as shop item, has to be of [Purchasable](#Purchasable) type, check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) to find all the [Purchasable](#Purchasable) entity types. Adding other entities will result in not obtainable items or game crash -### change_diceshop_prizes - - -> Search script examples for [change_diceshop_prizes](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_diceshop_prizes) - -#### nil change_diceshop_prizes(array<[ENT_TYPE](#ENT_TYPE)> ent_types) - -Change [ENT_TYPE](#ENT_TYPE)'s spawned in dice shops (Madame Tusk as well), by default there are 25:
-{ITEM_PICKUP_BOMBBAG, ITEM_PICKUP_BOMBBOX, ITEM_PICKUP_ROPEPILE, ITEM_PICKUP_COMPASS, ITEM_PICKUP_PASTE, ITEM_PICKUP_PARACHUTE, ITEM_PURCHASABLE_CAPE, ITEM_PICKUP_SPECTACLES, ITEM_PICKUP_CLIMBINGGLOVES, ITEM_PICKUP_PITCHERSMITT, -ENT_TYPE_ITEM_PICKUP_SPIKESHOES, ENT_TYPE_ITEM_PICKUP_SPRINGSHOES, ITEM_MACHETE, ITEM_BOOMERANG, ITEM_CROSSBOW, ITEM_SHOTGUN, ITEM_FREEZERAY, ITEM_WEBGUN, ITEM_CAMERA, ITEM_MATTOCK, ITEM_PURCHASABLE_JETPACK, ITEM_PURCHASABLE_HOVERPACK, -ITEM_TELEPORTER, ITEM_PURCHASABLE_TELEPORTER_BACKPACK, ITEM_PURCHASABLE_POWERPACK}
-Min 6, Max 255, if you want less then 6 you need to write some of them more then once (they will have higher "spawn chance"). -If you use this function in the level with dice shop in it, you have to update `item_ids` in the [ITEM_DICE_PRIZE_DISPENSER](#PrizeDispenser). -Use empty table as argument to reset to the game default - ### is_inside_active_shop_room @@ -3145,38 +3154,15 @@ Gets an existing sound, either if a file at the same path was already loaded or > Search script examples for [play_sound](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=play_sound) -#### [SoundMeta](#SoundMeta) play_sound([VANILLA_SOUND](#VANILLA_SOUND) sound, int source_uid) - -#### [SoundMeta](#SoundMeta) play_sound([SOUNDID](#Aliases) sound_id, int source_uid) - -Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" -Returns [SoundMeta](#SoundMeta), beware that the sound can't be stopped (`start_over` and `playing` are unavailable). Should only be used for sfx. - -## Spawn functions - - -### change_altar_damage_spawns - - -> Search script examples for [change_altar_damage_spawns](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_altar_damage_spawns) - -#### nil change_altar_damage_spawns(array<[ENT_TYPE](#ENT_TYPE)> ent_types) - -Change [ENT_TYPE](#ENT_TYPE)'s spawned when you damage the altar, by default there are 6:
-{MONS_BAT, MONS_BEE, MONS_SPIDER, MONS_JIANGSHI, MONS_FEMALE_JIANGSHI, MONS_VAMPIRE}
-Max 255 types. -Use empty table as argument to reset to the game default - -### change_sunchallenge_spawns +#### [SoundMeta](#SoundMeta) play_sound([VANILLA_SOUND](#VANILLA_SOUND) sound, int source_uid) +#### [SoundMeta](#SoundMeta) play_sound([SOUNDID](#Aliases) sound_id, int source_uid) -> Search script examples for [change_sunchallenge_spawns](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=change_sunchallenge_spawns) +Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" +Returns [SoundMeta](#SoundMeta), beware that the sound can't be stopped (`start_over` and `playing` are unavailable). Should only be used for sfx. -#### nil change_sunchallenge_spawns(array<[ENT_TYPE](#ENT_TYPE)> ent_types) +## Spawn functions -Change [ENT_TYPE](#ENT_TYPE)'s spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4:
-{MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER}
-Use empty table as argument to reset to the game default ### default_spawn_is_valid @@ -3254,15 +3240,6 @@ A return value of 0 does not mean the chance is infinite, it means the chance is Short for [spawn_layer_door](#spawn_layer_door). -### set_ghost_spawn_times - - -> Search script examples for [set_ghost_spawn_times](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_ghost_spawn_times) - -#### nil set_ghost_spawn_times(int normal = 10800, int cursed = 9000) - -Determines when the ghost appears, either when the player is cursed or not - ### spawn @@ -3823,25 +3800,263 @@ Use `set_callback(function, ON.TRANSITION)` instead Use `set_callback(function, ON.DEATH)` instead -### on_win +### on_win + +Use `set_callback(function, ON.WIN)` instead + +### on_screen + +Use `set_callback(function, ON.SCREEN)` instead + +### on_guiframe + +Use `set_callback(function, ON.GUIFRAME)` instead + +### load_script + + +> Search script examples for [load_script](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=load_script) + +`nil load_script()`
+Same as import(). + +### setflag + + +> Search script examples for [setflag](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=setflag) + +`nil setflag()`
+ +### clrflag + + +> Search script examples for [clrflag](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clrflag) + +`nil clrflag()`
+ +### testflag + + +> Search script examples for [testflag](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=testflag) + +`nil testflag()`
+ +### generate_particles + + +> Search script examples for [generate_particles](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=generate_particles) + +#### [ParticleEmitterInfo](#ParticleEmitterInfo) generate_particles([PARTICLEEMITTER](#PARTICLEEMITTER) particle_emitter_id, int uid) + +Use `generate_world_particles` + +### draw_line + + +> Search script examples for [draw_line](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_line) + +`nil draw_line(float x1, float y1, float x2, float y2, float thickness, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_line` instead + +### draw_rect + + +> Search script examples for [draw_rect](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_rect) + +`nil draw_rect(float x1, float y1, float x2, float y2, float thickness, float rounding, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_rect` instead + +### draw_rect_filled + + +> Search script examples for [draw_rect_filled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_rect_filled) + +`nil draw_rect_filled(float x1, float y1, float x2, float y2, float rounding, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_rect_filled` instead + +### draw_circle + + +> Search script examples for [draw_circle](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_circle) + +`nil draw_circle(float x, float y, float radius, float thickness, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_circle` instead + +### draw_circle_filled + + +> Search script examples for [draw_circle_filled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_circle_filled) + +`nil draw_circle_filled(float x, float y, float radius, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_circle_filled` instead + +### draw_text + + +> Search script examples for [draw_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_text) + +`nil draw_text(float x, float y, float size, string text, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_text` instead + +### draw_image + + +> Search script examples for [draw_image](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_image) + +`nil draw_image(IMAGE image, float x1, float y1, float x2, float y2, float uvx1, float uvy1, float uvx2, float uvy2, uColor color)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_image` instead + +### draw_image_rotated + + +> Search script examples for [draw_image_rotated](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_image_rotated) + +`nil draw_image_rotated(IMAGE image, float x1, float y1, float x2, float y2, float uvx1, float uvy1, float uvx2, float uvy2, uColor color, float angle, float px, float py)`
+Use [GuiDrawContext](#GuiDrawContext)`.draw_image_rotated` instead + +### window + + +> Search script examples for [window](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=window) + +`nil window(string title, float x, float y, float w, float h, bool movable, function callback)`
+Use [GuiDrawContext](#GuiDrawContext)`.window` instead + +### win_text + + +> Search script examples for [win_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_text) + +`nil win_text(string text)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_text` instead + +### win_separator + + +> Search script examples for [win_separator](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_separator) + +`nil win_separator()`
+Use [GuiDrawContext](#GuiDrawContext)`.win_separator` instead + +### win_inline + + +> Search script examples for [win_inline](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_inline) + +`nil win_inline()`
+Use [GuiDrawContext](#GuiDrawContext)`.win_inline` instead + +### win_sameline + + +> Search script examples for [win_sameline](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_sameline) + +`nil win_sameline(float offset, float spacing)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_sameline` instead + +### win_button + + +> Search script examples for [win_button](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_button) + +`bool win_button(string text)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_button` instead + +### win_input_text + + +> Search script examples for [win_input_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_input_text) + +`string win_input_text(string label, string value)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_input_text` instead + +### win_input_int + + +> Search script examples for [win_input_int](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_input_int) + +`int win_input_int(string label, int value)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_input_int` instead + +### win_input_float + + +> Search script examples for [win_input_float](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_input_float) + +`float win_input_float(string label, float value)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_input_float` instead + +### win_slider_int + + +> Search script examples for [win_slider_int](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_slider_int) + +`int win_slider_int(string label, int value, int min, int max)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_slider_int` instead + +### win_drag_int + + +> Search script examples for [win_drag_int](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_drag_int) + +`int win_drag_int(string label, int value, int min, int max)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_drag_int` instead + +### win_slider_float + + +> Search script examples for [win_slider_float](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_slider_float) + +`float win_slider_float(string label, float value, float min, float max)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_slider_float` instead + +### win_drag_float + + +> Search script examples for [win_drag_float](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_drag_float) + +`float win_drag_float(string label, float value, float min, float max)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_drag_float` instead + +### win_check + + +> Search script examples for [win_check](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_check) + +`bool win_check(string label, bool value)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_check` instead + +### win_combo + + +> Search script examples for [win_combo](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_combo) + +`int win_combo(string label, int selected, string opts)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_combo` instead + +### win_pushid + + +> Search script examples for [win_pushid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_pushid) -Use `set_callback(function, ON.WIN)` instead +`nil win_pushid(int id)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_pushid` instead -### on_screen +### win_popid -Use `set_callback(function, ON.SCREEN)` instead -### on_guiframe +> Search script examples for [win_popid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_popid) -Use `set_callback(function, ON.GUIFRAME)` instead +`nil win_popid()`
+Use [GuiDrawContext](#GuiDrawContext)`.win_popid` instead -### load_script +### win_image -> Search script examples for [load_script](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=load_script) +> Search script examples for [win_image](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_image) -`nil load_script()`
-Same as import(). +`nil win_image(IMAGE image, float width, float height)`
+Use [GuiDrawContext](#GuiDrawContext)`.win_image` instead ### read_prng @@ -3882,7 +4097,7 @@ Use `get_entities_by(0, MASK.ANY, LAYER.BOTH)` instead > Search script examples for [get_entities_by_mask](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_by_mask) -#### vector<int> get_entities_by_mask(int mask) +#### vector<int> get_entities_by_mask([MASK](#MASK) mask) Use `get_entities_by(0, mask, LAYER.BOTH)` instead @@ -3900,9 +4115,9 @@ Use `get_entities_by(0, MASK.ANY, layer)` instead > Search script examples for [get_entities_overlapping](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_overlapping) -#### vector<int> get_entities_overlapping(array<[ENT_TYPE](#ENT_TYPE)> entity_types, int mask, float sx, float sy, float sx2, float sy2, [LAYER](#LAYER) layer) +#### vector<int> get_entities_overlapping(array<[ENT_TYPE](#ENT_TYPE)> entity_types, [MASK](#MASK) mask, float sx, float sy, float sx2, float sy2, [LAYER](#LAYER) layer) -#### vector<int> get_entities_overlapping([ENT_TYPE](#ENT_TYPE) entity_type, int mask, float sx, float sy, float sx2, float sy2, [LAYER](#LAYER) layer) +#### vector<int> get_entities_overlapping([ENT_TYPE](#ENT_TYPE) entity_type, [MASK](#MASK) mask, float sx, float sy, float sx2, float sy2, [LAYER](#LAYER) layer) Use `get_entities_overlapping_hitbox` instead @@ -3911,8 +4126,7 @@ Use `get_entities_overlapping_hitbox` instead > Search script examples for [get_entity_ai_state](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entity_ai_state) -#### int get_entity_ai_state(int uid) - +`int get_entity_ai_state(int uid)`
As the name is misleading. use [Movable](#Movable).`move_state` field instead ### set_arrowtrap_projectile @@ -3920,8 +4134,7 @@ As the name is misleading. use [Movable](#Movable).`move_state` field instead > Search script examples for [set_arrowtrap_projectile](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_arrowtrap_projectile) -#### nil set_arrowtrap_projectile([ENT_TYPE](#ENT_TYPE) regular_entity_type, [ENT_TYPE](#ENT_TYPE) poison_entity_type) - +`nil set_arrowtrap_projectile(ENT_TYPE regular_entity_type, ENT_TYPE poison_entity_type)`
Use [replace_drop](#replace_drop)([DROP](#DROP).ARROWTRAP_WOODENARROW, new_arrow_type) and [replace_drop](#replace_drop)([DROP](#DROP).POISONEDARROWTRAP_WOODENARROW, new_arrow_type) instead ### set_blood_multiplication @@ -3929,32 +4142,10 @@ Use [replace_drop](#replace_drop)([DROP](#DROP).ARROWTRAP_WOODENARROW, new_arrow > Search script examples for [set_blood_multiplication](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_blood_multiplication) -#### nil set_blood_multiplication(int default_multiplier, int vladscape_multiplier) - +`nil set_blood_multiplication(int /default_multiplier/, int vladscape_multiplier)`
This function never worked properly as too many places in the game individually check for vlads cape and calculate the blood multiplication `default_multiplier` doesn't do anything due to some changes in last game updates, `vladscape_multiplier` only changes the multiplier to some entities death's blood spit -### setflag - - -> Search script examples for [setflag](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=setflag) - -`nil setflag()`
- -### clrflag - - -> Search script examples for [clrflag](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clrflag) - -`nil clrflag()`
- -### testflag - - -> Search script examples for [testflag](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=testflag) - -`nil testflag()`
- ### steal_input @@ -4194,219 +4385,26 @@ Sets a callback that is called right after the entity is rendered. Use this only when no other approach works, this call can be expensive if overused.
The callback signature is nil post_render([VanillaRenderContext](#VanillaRenderContext) render_ctx, [Entity](#Entity) self) -### generate_particles - - -> Search script examples for [generate_particles](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=generate_particles) - -#### [ParticleEmitterInfo](#ParticleEmitterInfo) generate_particles([PARTICLEEMITTER](#PARTICLEEMITTER) particle_emitter_id, int uid) - -Use `generate_world_particles` - -### draw_line - - -> Search script examples for [draw_line](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_line) - -`nil draw_line(float x1, float y1, float x2, float y2, float thickness, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_line` instead - -### draw_rect - - -> Search script examples for [draw_rect](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_rect) - -`nil draw_rect(float x1, float y1, float x2, float y2, float thickness, float rounding, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_rect` instead - -### draw_rect_filled - - -> Search script examples for [draw_rect_filled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_rect_filled) - -`nil draw_rect_filled(float x1, float y1, float x2, float y2, float rounding, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_rect_filled` instead - -### draw_circle - - -> Search script examples for [draw_circle](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_circle) - -`nil draw_circle(float x, float y, float radius, float thickness, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_circle` instead - -### draw_circle_filled - - -> Search script examples for [draw_circle_filled](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_circle_filled) - -`nil draw_circle_filled(float x, float y, float radius, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_circle_filled` instead - -### draw_text - - -> Search script examples for [draw_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_text) - -`nil draw_text(float x, float y, float size, string text, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_text` instead - -### draw_image - - -> Search script examples for [draw_image](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_image) - -`nil draw_image(IMAGE image, float x1, float y1, float x2, float y2, float uvx1, float uvy1, float uvx2, float uvy2, uColor color)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_image` instead - -### draw_image_rotated - - -> Search script examples for [draw_image_rotated](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_image_rotated) - -`nil draw_image_rotated(IMAGE image, float x1, float y1, float x2, float y2, float uvx1, float uvy1, float uvx2, float uvy2, uColor color, float angle, float px, float py)`
-Use [GuiDrawContext](#GuiDrawContext)`.draw_image_rotated` instead - -### window - - -> Search script examples for [window](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=window) - -`nil window(string title, float x, float y, float w, float h, bool movable, function callback)`
-Use [GuiDrawContext](#GuiDrawContext)`.window` instead - -### win_text - - -> Search script examples for [win_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_text) - -`nil win_text(string text)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_text` instead - -### win_separator - - -> Search script examples for [win_separator](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_separator) - -`nil win_separator()`
-Use [GuiDrawContext](#GuiDrawContext)`.win_separator` instead - -### win_inline - - -> Search script examples for [win_inline](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_inline) - -`nil win_inline()`
-Use [GuiDrawContext](#GuiDrawContext)`.win_inline` instead - -### win_sameline - - -> Search script examples for [win_sameline](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_sameline) - -`nil win_sameline(float offset, float spacing)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_sameline` instead - -### win_button - - -> Search script examples for [win_button](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_button) - -`bool win_button(string text)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_button` instead - -### win_input_text - - -> Search script examples for [win_input_text](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_input_text) - -`string win_input_text(string label, string value)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_input_text` instead - -### win_input_int - - -> Search script examples for [win_input_int](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_input_int) - -`int win_input_int(string label, int value)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_input_int` instead - -### win_input_float - - -> Search script examples for [win_input_float](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_input_float) - -`float win_input_float(string label, float value)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_input_float` instead - -### win_slider_int - - -> Search script examples for [win_slider_int](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_slider_int) - -`int win_slider_int(string label, int value, int min, int max)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_slider_int` instead - -### win_drag_int - - -> Search script examples for [win_drag_int](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_drag_int) - -`int win_drag_int(string label, int value, int min, int max)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_drag_int` instead - -### win_slider_float - - -> Search script examples for [win_slider_float](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_slider_float) - -`float win_slider_float(string label, float value, float min, float max)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_slider_float` instead - -### win_drag_float - - -> Search script examples for [win_drag_float](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_drag_float) - -`float win_drag_float(string label, float value, float min, float max)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_drag_float` instead - -### win_check - - -> Search script examples for [win_check](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_check) - -`bool win_check(string label, bool value)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_check` instead - -### win_combo - - -> Search script examples for [win_combo](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_combo) - -`int win_combo(string label, int selected, string opts)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_combo` instead - -### win_pushid +### flip_entity -> Search script examples for [win_pushid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_pushid) +> Search script examples for [flip_entity](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flip_entity) -`nil win_pushid(int id)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_pushid` instead +`nil flip_entity(int uid)`
+Use `Entity:flip` instead -### win_popid +### lock_door_at -> Search script examples for [win_popid](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_popid) +> Search script examples for [lock_door_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=lock_door_at) -`nil win_popid()`
-Use [GuiDrawContext](#GuiDrawContext)`.win_popid` instead +`nil lock_door_at(float x, float y)`
+use `Door:unlock` instead -### win_image +### unlock_door_at -> Search script examples for [win_image](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=win_image) +> Search script examples for [unlock_door_at](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=unlock_door_at) -`nil win_image(IMAGE image, float width, float height)`
-Use [GuiDrawContext](#GuiDrawContext)`.win_image` instead +`nil unlock_door_at(float x, float y)`
+use `Door:unlock` instead diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index 846137ad7..8f477c262 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -453,7 +453,7 @@ Type | Name | Description [EntityDB](#EntityDB) | [new(EntityDB other)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=EntityDB) | [EntityDB](#EntityDB) | [new(ENT_TYPE other)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=EntityDB) | [ENT_TYPE](#ENT_TYPE) | [id](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=id) | -int | [search_flags](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=search_flags) | [MASK](#MASK) +[MASK](#MASK) | [search_flags](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=search_flags) | [MASK](#MASK) 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 | [draw_depth](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=draw_depth) | @@ -1017,6 +1017,9 @@ nil | [load()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=load) | nil | [save()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=save) | Save over a previously allocated [SaveState](#SaveState) nil | [clear()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear) | Delete the [SaveState](#SaveState) and free the memory. The [SaveState](#SaveState) can't be used after this. [StateMemory](#StateMemory) | [get_state()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_state) | Access the [StateMemory](#StateMemory) inside a [SaveState](#SaveState) +int | [get_frame()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_frame) | Get the current frame from the [SaveState](#SaveState), equivelent to the [get_frame](#Get_frame) global function that returns the frame from the "loaded in state" +[PRNG](#PRNG) | [get_prng()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_prng) | Access the [PRNG](#PRNG) inside a [SaveState](#SaveState) +[SaveState](#SaveState) | [get(int save_slot)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get) | Get the pre-allocated by the game save slot 1-4 ### SharedIO @@ -1830,6 +1833,7 @@ Type | Name | Description array<[OnlinePlayer](#OnlinePlayer), 4> | [online_players](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=online_players) | [OnlinePlayer](#OnlinePlayer) | [local_player](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=local_player) | [OnlineLobby](#OnlineLobby) | [lobby](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=lobby) | +bool | [is_active()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_active) | ### OnlineLobby @@ -3943,6 +3947,8 @@ int | [level](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=level) | int | [timer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=timer) | int | [world](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=world) | int | [theme](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=theme) | +nil | [set_target(int ww, int l, int t)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_target) | +tuple<int, int, int> | [get_target()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_target) | Get target world, level, theme of this door. If the `special_door` is false, it returns the StateMemory world_next, level_next, theme_next ### Floor @@ -4354,9 +4360,9 @@ bool | [is_in_liquid()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q bool | [is_cursed()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_cursed) | bool | [is_movable()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_movable) | bool | [can_be_pushed()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=can_be_pushed) | -nil | [kill_recursive(bool destroy_corpse, Entity responsible, optional mask, array ent_types, RECURSIVE_MODE rec_mode)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=kill_recursive) | Kill entity along with all entities attached to it. Be aware that for example killing push block with this function will also kill anything on top of it, any items, players, monsters etc.
To avoid that, you can inclusively or exclusively limit certain [MASK](#MASK) and [ENT_TYPE](#ENT_TYPE). Note: the function will first check mask, if the entity doesn't match, it will look in the provided [ENT_TYPE](#ENT_TYPE)'s
destroy_corpse and responsible are the standard parameters for the kill function +nil | [kill_recursive(bool destroy_corpse, Entity responsible, optional mask, array ent_types, RECURSIVE_MODE rec_mode)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=kill_recursive) | Kill entity along with all entities attached to it. Be aware that for example killing push block with this function will also kill anything on top of it, any items, players, monsters etc.
To avoid that, you can inclusively or exclusively limit certain [MASK](#MASK) and [ENT_TYPE](#ENT_TYPE). Note: the function will first check mask, if the entity doesn't match, it will look in the provided [ENT_TYPE](#ENT_TYPE)'s
destroy_corpse and responsible are the standard parameters for the kill function nil | [kill_recursive(bool destroy_corpse, Entity responsible)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=kill_recursive) | Short for using [RECURSIVE_MODE](#RECURSIVE_MODE).NONE -nil | [destroy_recursive(optional mask, array ent_types, RECURSIVE_MODE rec_mode)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_recursive) | Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc.
To avoid that, you can inclusively or exclusively limit certain [MASK](#MASK) and [ENT_TYPE](#ENT_TYPE). Note: the function will first check the mask, if the entity doesn't match, it will look in the provided [ENT_TYPE](#ENT_TYPE)'s +nil | [destroy_recursive(optional mask, array ent_types, RECURSIVE_MODE rec_mode)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_recursive) | Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc.
To avoid that, you can inclusively or exclusively limit certain [MASK](#MASK) and [ENT_TYPE](#ENT_TYPE). Note: the function will first check the mask, if the entity doesn't match, it will look in the provided [ENT_TYPE](#ENT_TYPE)'s nil | [destroy_recursive()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_recursive) | Short for using [RECURSIVE_MODE](#RECURSIVE_MODE).NONE nil | [update()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=update) | nil | [flip(bool left)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flip) | diff --git a/src/game_api/aliases.hpp b/src/game_api/aliases.hpp index 3a32f7a2c..6578e8ad8 100644 --- a/src/game_api/aliases.hpp +++ b/src/game_api/aliases.hpp @@ -214,6 +214,11 @@ enum class ENTITY_MASK : uint32_t }; ENUM_CLASS_FLAGS(ENTITY_MASK) +inline bool operator!(ENTITY_MASK v) +{ + return v == static_cast(0); +} + // Returns true if any of the set bits in `mask` are in `flags` template requires std::is_enum_v @@ -221,3 +226,8 @@ bool test_mask(T flags, T mask) { return static_cast>(flags & mask) != 0; } + +// safe function, returns only 0 or 1. returns 0 for LAYER::BOTH +uint8_t enum_to_layer(const LAYER layer, struct Vec2& player_position); +// safe function, returns only 0 or 1. returns 0 for LAYER::BOTH +uint8_t enum_to_layer(const LAYER layer); diff --git a/src/game_api/bucket.cpp b/src/game_api/bucket.cpp index 6fb10e4f6..8da089291 100644 --- a/src/game_api/bucket.cpp +++ b/src/game_api/bucket.cpp @@ -1,12 +1,11 @@ #include "bucket.hpp" -#include "containers/game_allocator.hpp" -#include "entities_chars.hpp" -#include "game_manager.hpp" -#include "items.hpp" -#include "memory.hpp" -#include "screen.hpp" -#include "state.hpp" +#include "entities_chars.hpp" // for Player +#include "game_manager.hpp" // for GameManager, get_game_manager +#include "items.hpp" // for Items +#include "screen.hpp" // for ScreenCharacterSelect +#include "search.hpp" // for get_address +#include "state.hpp" // for StateMemory, get_state_ptr Bucket* Bucket::get() { @@ -23,13 +22,13 @@ Bucket* Bucket::get() PAUSE_TYPE PauseAPI::get_pause() { - pause = (PAUSE_TYPE)(State::get().ptr()->pause | ((uint32_t)pause & ~0x3f)); + pause = (PAUSE_TYPE)(get_state_ptr()->pause | ((uint32_t)pause & ~0x3f)); return pause; } void PauseAPI::set_pause(PAUSE_TYPE flags) { - auto state = State::get().ptr(); + auto state = get_state_ptr(); pause = flags; state->pause = (uint8_t)(((uint32_t)flags) & 0x3f); } @@ -37,7 +36,7 @@ void PauseAPI::set_pause(PAUSE_TYPE flags) bool PauseAPI::check_trigger(PAUSE_TRIGGER& trigger, PAUSE_SCREEN& screen) const { bool match = false; - auto state = State::get().ptr(); + auto state = get_state_ptr(); if (state->loading == 2 && (trigger & PAUSE_TRIGGER::SCREEN) != PAUSE_TRIGGER::NONE && (screen == PAUSE_SCREEN::NONE || (screen & (PAUSE_SCREEN)(1 << state->screen_next)) != PAUSE_SCREEN::NONE)) match = true; @@ -54,7 +53,7 @@ bool PauseAPI::check_trigger(PAUSE_TRIGGER& trigger, PAUSE_SCREEN& screen) const if (match && (trigger & PAUSE_TRIGGER::ONCE) != PAUSE_TRIGGER::NONE) trigger = PAUSE_TRIGGER::NONE; - if (match && last_trigger_frame == get_global_update_count()) + if (match && (uint64_t)last_trigger_frame == API::get_global_update_count()) match = false; return match; @@ -62,7 +61,7 @@ bool PauseAPI::check_trigger(PAUSE_TRIGGER& trigger, PAUSE_SCREEN& screen) const bool PauseAPI::loading() { - auto state = State::get().ptr(); + auto state = get_state_ptr(); auto gm = get_game_manager(); bool loading = state->loading > 0 || state->fade_timer > 0 || (state->screen == 4 && gm->screen_menu->menu_text_opacity < 1) || (state->screen == 9 && (state->screen_character_select->topleft_woodpanel_esc_slidein == 0 || state->screen_character_select->start_pressed)) || state->logic->ouroboros; if ((state->loading == 3 && (state->fade_timer <= 1 || state->fade_length == 0)) || (state->loading == 1 && state->fade_timer == state->fade_length)) @@ -74,7 +73,7 @@ bool PauseAPI::event(PAUSE_TYPE pause_event) { bool block = false; std::optional force; - auto state = State::get().ptr(); + auto state = get_state_ptr(); if (skip_fade) { @@ -95,13 +94,13 @@ bool PauseAPI::event(PAUSE_TYPE pause_event) { set_paused(true); force = true; - last_trigger_frame = get_global_update_count(); + last_trigger_frame = API::get_global_update_count(); } if (check_trigger(unpause_trigger, unpause_screen)) { set_paused(false); force = false; - last_trigger_frame = get_global_update_count(); + last_trigger_frame = API::get_global_update_count(); } last_fade_timer = state->fade_timer; last_level_flags = state->level_flags; @@ -132,7 +131,7 @@ bool PauseAPI::event(PAUSE_TYPE pause_event) set_paused(true); if (update_camera && ((block && (pause_event == PAUSE_TYPE::PRE_UPDATE || pause_event == PAUSE_TYPE::PRE_GAME_LOOP)) || ((pause_event == PAUSE_TYPE::PRE_UPDATE && (uint8_t)pause_type & 0x3f) && state->pause > 0)) && ((state->pause & 1) == 0 || (uint8_t)pause_type & 1)) - update_camera_position(); + state->camera->update_position(); if ((pause_type & pause_event) != PAUSE_TYPE::NONE) blocked = block; @@ -142,7 +141,7 @@ bool PauseAPI::event(PAUSE_TYPE pause_event) void PauseAPI::post_loop() { - auto state = State::get().ptr(); + auto state = get_state_ptr(); if (skip) state->pause |= (uint8_t)pause_type & 0x3f; skip = false; @@ -156,7 +155,7 @@ bool PauseAPI::pre_input() return false; auto gm = get_game_manager(); - auto state = State::get().ptr(); + auto state = get_state_ptr(); if (bucket->pause_api->modifiers_block & bucket->pause_api->modifiers_down) { diff --git a/src/game_api/color.cpp b/src/game_api/color.cpp index 5d812046e..b7e591a43 100644 --- a/src/game_api/color.cpp +++ b/src/game_api/color.cpp @@ -8,7 +8,7 @@ uColor get_color(const std::string& color_name, std::optional alpha) { // #RRGGBBAA // 0xAABBGGRR - // treating not even number of hex characters as user error = behavior is undefined + // treating non even number of hex characters as user error = behavior is undefined if (color_name.size() == 0) return 0; diff --git a/src/game_api/containers/custom_allocator.cpp b/src/game_api/containers/custom_allocator.cpp index beb631870..132447622 100644 --- a/src/game_api/containers/custom_allocator.cpp +++ b/src/game_api/containers/custom_allocator.cpp @@ -2,9 +2,9 @@ #include // for operator""sv -#include "memory.hpp" // for memory_read, Memory -#include "search.hpp" // for get_address -#include "thread_utils.hpp" // for OnHeapPointer +#include "heap_base.hpp" // for OnHeapPointer +#include "memory.hpp" // for memory_read, Memory +#include "search.hpp" // for get_address using CustomMallocFun = void*(void*, std::size_t); using CustomFreeFun = void*(void*, void*); diff --git a/src/game_api/custom_types.cpp b/src/game_api/custom_types.cpp index 0e428e0db..1960aafc6 100644 --- a/src/game_api/custom_types.cpp +++ b/src/game_api/custom_types.cpp @@ -7,7 +7,7 @@ #include // for min, find #include // for vector, allocator, vector<>::iterator -#include "entity.hpp" // for to_id +#include "entity_db.hpp" // for to_id const std::vector> custom_type_names = { {CUSTOM_TYPE::ACIDBUBBLE, "ACIDBUBBLE"}, diff --git a/src/game_api/drops.cpp b/src/game_api/drops.cpp index b7ebed55e..dc890b966 100644 --- a/src/game_api/drops.cpp +++ b/src/game_api/drops.cpp @@ -5,10 +5,13 @@ #include // #include // for min, max -#include "entity.hpp" // for to_id -#include "memory.hpp" // for Memory, recover_mem, write_mem_recoverable -#include "search.hpp" // for find_inst +#include "entity_db.hpp" // for to_id +#include "memory.hpp" // for Memory, recover_mem, write_mem_recoverable +#include "search.hpp" // for find_inst +/// +/// DROP +/// std::vector drop_entries{ {"ALTAR_DICE_CLIMBINGGLOVES", "\xBA\x0D\x02\x00\x00\xEB\x05"sv, VTABLE_OFFSET::NONE, 0, 1}, // VTABLE_OFFSET::FLOOR_ALTAR, 26 {"ALTAR_DICE_COOKEDTURKEY", "\xBA\x06\x02\x00\x00\xEB\x0C"sv, VTABLE_OFFSET::NONE, 0, 1}, @@ -316,6 +319,9 @@ std::vector drop_entries{ /// maybe TODO: if someone wants all the explosions (from damage/death/crush), could also be added }; +/// +/// DROPCHANCE +/// std::vector dropchance_entries{ {"BONEBLOCK_SKELETONKEY", "\xE8\x03\x00\x00"sv, VTABLE_OFFSET::ACTIVEFLOOR_BONEBLOCK, 3}, {"CROCMAN_TELEPACK", "\x64"sv, VTABLE_OFFSET::MONS_CROCMAN, 3, 1}, @@ -330,7 +336,7 @@ std::vector dropchance_entries{ {"YETI_PITCHERSMITT", "\xE8\x03\x00\x00"sv, VTABLE_OFFSET::MONS_YETI, 3}, }; -void set_drop_chance(int32_t dropchance_id, uint32_t new_drop_chance) +void set_drop_chance(DROPCHANCE dropchance_id, uint32_t new_drop_chance) { if (dropchance_id < (int32_t)dropchance_entries.size()) { @@ -366,7 +372,7 @@ void set_drop_chance(int32_t dropchance_id, uint32_t new_drop_chance) } } -void replace_drop(int32_t drop_id, ENT_TYPE new_drop_entity_type) +void replace_drop(DROP drop_id, ENT_TYPE new_drop_entity_type) { const static auto nof_ent_types = to_id("ENT_TYPE_LIQUID_COARSE_LAVA") + 1; if (drop_id < (int32_t)drop_entries.size() && new_drop_entity_type < nof_ent_types) diff --git a/src/game_api/drops.hpp b/src/game_api/drops.hpp index 826dbe63d..e0812fe9a 100644 --- a/src/game_api/drops.hpp +++ b/src/game_api/drops.hpp @@ -28,9 +28,10 @@ struct DropChanceEntry uint8_t chance_sizeof = 4; size_t offset = 0; }; - -void set_drop_chance(int32_t dropchance_id, uint32_t new_drop_chance); -void replace_drop(int32_t drop_id, ENT_TYPE new_drop_entity_type); +using DROPCHANCE = int32_t; +void set_drop_chance(DROPCHANCE dropchance_id, uint32_t new_drop_chance); +using DROP = int32_t; +void replace_drop(DROP drop_id, ENT_TYPE new_drop_entity_type); extern std::vector drop_entries; diff --git a/src/game_api/entities_activefloors.cpp b/src/game_api/entities_activefloors.cpp index b66b62948..3e70b01c1 100644 --- a/src/game_api/entities_activefloors.cpp +++ b/src/game_api/entities_activefloors.cpp @@ -3,8 +3,7 @@ #include "entity.hpp" // for Entity, to_id, EntityDB #include "items.hpp" // IWYU pragma: keep #include "layer.hpp" // for EntityList::Range, EntityList, EntityList::Ent... -#include "search.hpp" // for get_address -#include "sound_manager.hpp" // +#include "sound_manager.hpp" // for construct_soundmeta uint8_t Olmec::broken_floaters() { diff --git a/src/game_api/entities_floors.cpp b/src/game_api/entities_floors.cpp index ee7c0d8c9..726aded61 100644 --- a/src/game_api/entities_floors.cpp +++ b/src/game_api/entities_floors.cpp @@ -7,7 +7,7 @@ #include "layer.hpp" // for EntityList, EntityList::Range, Layer, Entit... #include "movable.hpp" // for Movable #include "spawn_api.hpp" // for spawn_entity_over -#include "state.hpp" // for State +#include "state.hpp" // for StateMemory #include "texture.hpp" // for Texture void Floor::fix_border_tile_animation() @@ -88,8 +88,7 @@ void Floor::fix_decorations(bool fix_also_neighbors, bool fix_styled_floor) Floor* neighbours[4]{}; bool neighbours_same[4]{}; - auto& state = State::get(); - auto layer_ptr = state.layer(layer); + auto layer_ptr = get_state_ptr()->layer(layer); for (size_t i = 0; i < 4; i++) { @@ -188,8 +187,7 @@ void Floor::add_decoration(FLOOR_SIDE side) return; } - auto& state = State::get(); - auto layer_ptr = state.layer(layer); + auto layer_ptr = get_state_ptr()->layer(layer); add_decoration_opt(side, decoration_entity_type, layer_ptr); } void Floor::remove_decoration(FLOOR_SIDE side) @@ -741,7 +739,7 @@ void Door::unlock(bool unlock) static const ENT_TYPE eggchild_room_door = to_id("ENT_TYPE_FLOOR_DOOR_MOAI_STATUE"); static const ENT_TYPE EW_door = to_id("ENT_TYPE_FLOOR_DOOR_EGGPLANT_WORLD"); const auto ent_type = this->type->id; - auto& state = State::get(); + auto state = get_state_ptr(); if (ent_type == locked_door || ent_type == locked_door + 1) // plus one for DOOR_LOCKED_PEN { @@ -772,7 +770,7 @@ void Door::unlock(bool unlock) if (ent_type == entrance_door || ent_type == entrance_door + 1 || ent_type == entrance_door + 3) { static const ENT_TYPE door_bg = to_id("ENT_TYPE_BG_DOOR"); - const auto entities = state.layer(this->layer)->get_entities_overlapping_grid_at(x, y); + const auto entities = state->layer(this->layer)->get_entities_overlapping_grid_at(x, y); if (entities == nullptr) return; for (const auto& item : entities->entities()) @@ -817,7 +815,7 @@ void Door::unlock(bool unlock) { if (!main_door->door_blocker) { - main_door->door_blocker = state.layer(layer)->spawn_entity_over(door_bg_large, this, 0, 2.0); + main_door->door_blocker = state->layer(layer)->spawn_entity_over(door_bg_large, this, 0, 2.0); main_door->door_blocker->animation_frame = 1; } } @@ -842,3 +840,17 @@ void Door::unlock(bool unlock) } } } + +std::tuple ExitDoor::get_target() const +{ + if (special_door) + { + return {world, level, theme}; + } + + auto state = get_state_ptr(); + if (state->screen == 11) // camp + return {}; + else + return {state->world_next, state->level_next, state->theme_next}; +} diff --git a/src/game_api/entities_floors.hpp b/src/game_api/entities_floors.hpp index 9f3db7b93..a2b67e50c 100644 --- a/src/game_api/entities_floors.hpp +++ b/src/game_api/entities_floors.hpp @@ -88,7 +88,7 @@ class Door : public Floor // can't be bother to look into the functions virtual void on_enter_attempt(Entity* who) = 0; - // check if it's CHAR_*, then sets State.level_flags -> 21 (Hide hud, transition) + // check if it's CHAR_*, then sets state.level_flags -> 21 (Hide hud, transition) virtual void hide_ui(Entity* who) = 0; /// Returns the entity state / behavior id to set the entity to after the entering animation. @@ -119,6 +119,16 @@ class ExitDoor : public Door uint8_t world; uint8_t theme; uint16_t padding; + + void set_target(uint8_t ww, uint8_t l, uint8_t t) + { + world = ww; + level = l; + theme = t; + special_door = true; + } + /// Get target world, level, theme of this door. If the `special_door` is false, it returns the StateMemory world_next, level_next, theme_next + std::tuple get_target() const; }; class DecoratedDoor : public ExitDoor diff --git a/src/game_api/entities_items.cpp b/src/game_api/entities_items.cpp index b4c915f0e..f8be28f21 100644 --- a/src/game_api/entities_items.cpp +++ b/src/game_api/entities_items.cpp @@ -4,10 +4,9 @@ #include // for move #include // for vector, _Vector_iterator, _Vector_cons... -#include "entities_chars.hpp" // for PowerupCapable -#include "entity.hpp" // for to_id, SHAPE -#include "entity_hooks_info.hpp" // for HookWithId, EntityHooksInfo -#include "vtable_hook.hpp" // for hook_vtable +#include "entities_chars.hpp" // for PowerupCapable +#include "entity.hpp" // SHAPE +#include "entity_db.hpp" // to_id void ParachutePowerup::deploy() { diff --git a/src/game_api/entities_liquids.cpp b/src/game_api/entities_liquids.cpp index 471371f3b..7ba65d47b 100644 --- a/src/game_api/entities_liquids.cpp +++ b/src/game_api/entities_liquids.cpp @@ -1,16 +1,16 @@ #include "entities_liquids.hpp" -#include "state.hpp" // for State -#include "state_structs.hpp" // for LiquidPhysicsEngine +#include "heap_base.hpp" // for HeapBase +#include "liquid_engine.hpp" // for LiquidPhysicsEngine uint32_t Liquid::get_liquid_flags() { - auto liquid_engine = State::get().get_correct_liquid_engine(type->id); + auto liquid_engine = HeapBase::get().liquid_physics()->get_correct_liquid_engine(type->id); return liquid_engine->liquid_flags[*liquid_id]; } void Liquid::set_liquid_flags(uint32_t liquid_flags) { - auto liquid_engine = State::get().get_correct_liquid_engine(type->id); + auto liquid_engine = HeapBase::get().liquid_physics()->get_correct_liquid_engine(type->id); liquid_engine->liquid_flags[*liquid_id] = liquid_flags; } diff --git a/src/game_api/entities_monsters.hpp b/src/game_api/entities_monsters.hpp index edb6a9d76..b9db8912d 100644 --- a/src/game_api/entities_monsters.hpp +++ b/src/game_api/entities_monsters.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "containers/custom_set.hpp" #include "containers/custom_vector.hpp" #include "entities_chars.hpp" @@ -7,8 +9,6 @@ #include "particles.hpp" #include "sound_manager.hpp" -#include - class Monster : public PowerupCapable { public: diff --git a/src/game_api/entities_mounts.hpp b/src/game_api/entities_mounts.hpp index 3bf06631c..76b0f325b 100644 --- a/src/game_api/entities_mounts.hpp +++ b/src/game_api/entities_mounts.hpp @@ -4,7 +4,7 @@ #include // for pair #include "entities_chars.hpp" // for PowerupCapable -#include "math.h" // for Vec2 +#include "math.hpp" // for Vec2 class Movable; struct SoundMeta; diff --git a/src/game_api/entity.cpp b/src/game_api/entity.cpp index 0c3d895da..3926a182e 100644 --- a/src/game_api/entity.cpp +++ b/src/game_api/entity.cpp @@ -1,30 +1,30 @@ #include "entity.hpp" -#include // for IsBadWritePtr -#include // for operator<=>, operator-, operator+ -#include // for round -#include // for operator<, operator<=, operator> -#include // for uint32_t, uint16_t, uint8_t -#include // for abs, NULL, size_t -#include // for _List_const_iterator -#include // for _Tree_iterator, map, _Tree_cons... -#include // for operator new -#include // for allocator, string, operator""sv -#include // for sleep_for -#include // for vector, _Vector_iterator, erase_if +#include // for operator<=>, operator-, operator+ +#include // for round +#include // for operator<, operator<=, operator> +#include // for uint32_t, uint16_t, uint8_t +#include // for abs, NULL, size_t +#include // for _List_const_iterator +#include // for _Tree_iterator, map, _Tree_cons... +#include // for operator new +#include // for allocator, string, operator""sv +#include // for sleep_for +#include // for vector, _Vector_iterator, erase_if #include "containers/custom_map.hpp" // for custom_map #include "entities_chars.hpp" // for Player -#include "entities_monsters.hpp" // +#include "entities_monsters.hpp" // for MegaJellyfish #include "entity_hooks_info.hpp" // for EntityHooksInfo -#include "entity_lookup.hpp" // +#include "entity_lookup.hpp" // for get_proper_types +#include "heap_base.hpp" // for HeapBase +#include "liquid_engine.hpp" // for LiquidPhysicsEngine #include "memory.hpp" // for write_mem_prot #include "movable.hpp" // for Movable #include "movable_behavior.hpp" // for MovableBehavior #include "render_api.hpp" // for RenderInfo #include "search.hpp" // for get_address -#include "state.hpp" // for State, StateMemory, enum_to_layer -#include "state_structs.hpp" // for LiquidPhysicsEngine +#include "state.hpp" // for StateMemory #include "texture.hpp" // for get_texture, Texture #include "vtable_hook.hpp" // for hook_vtable, hook_dtor, unregis... @@ -36,20 +36,20 @@ void Entity::set_layer(LAYER layer_to) if (layer == dest_layer) return; - auto& state = State::get(); + auto state = HeapBase::get().state(); if (this != this->topmost_mount()) this->topmost_mount()->set_layer(layer_to); if (layer == 0 || layer == 1) { - auto ptr_from = state.ptr()->layers[layer]; + auto ptr_from = state->layers[layer]; using RemoveFromLayer = void(Layer*, Entity*); static RemoveFromLayer* remove_from_layer = (RemoveFromLayer*)get_address("remove_from_layer"); remove_from_layer(ptr_from, this); } - auto ptr_to = state.ptr()->layers[dest_layer]; + auto ptr_to = state->layers[dest_layer]; using AddToLayer = void(Layer*, Entity*); static AddToLayer* add_to_layer = (AddToLayer*)get_address("add_to_layer"); @@ -63,7 +63,7 @@ void Entity::set_layer(LAYER layer_to) void Entity::apply_layer() { - auto ptr_to = State::get().ptr()->layers[layer]; + auto ptr_to = HeapBase::get().state()->layer(layer); using AddToLayer = void(Layer*, Entity*); static AddToLayer* add_to_layer = (AddToLayer*)get_address("add_to_layer"); @@ -79,9 +79,9 @@ void Entity::remove() { if (layer != 2) { - auto& state = State::get(); - auto ptr_from = state.ptr()->layers[layer]; - if ((this->type->search_flags & 1) == 0 || ((Player*)this)->ai != 0) + auto state = HeapBase::get().state(); + auto ptr_from = state->layers[layer]; + if (!(this->type->search_flags & ENTITY_MASK::PLAYER) || this->as()->ai != nullptr) { using RemoveFromLayer = void(Layer*, Entity*); static RemoveFromLayer* remove_from_layer = (RemoveFromLayer*)get_address("remove_from_layer"); @@ -146,7 +146,7 @@ Vec2 Entity::get_absolute_velocity() const } else if (is_liquid()) { - auto liquid_engine = State::get().get_correct_liquid_engine(type->id); + auto liquid_engine = HeapBase::get().liquid_physics()->get_correct_liquid_engine(type->id); velocity.x = liquid_engine->entity_velocities->x; velocity.y = liquid_engine->entity_velocities->y; } @@ -195,24 +195,27 @@ bool Entity::set_texture(TEXTURE texture_id) bool Entity::is_player() const { - if (type->search_flags & 1) - { - auto pl = static_cast(this); - return pl->ai == nullptr; - } - return false; + if (!(type->search_flags & ENTITY_MASK::PLAYER)) + return false; + + auto pl = static_cast(this); + return pl->ai == nullptr; } 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 - return true; - else if (type->search_flags & 0x1000) // LOGICAL - as it has some movable entities - if (type->id < first_logical) // actually check if it's not logical + constexpr auto test_mask = ENTITY_MASK::PLAYER | ENTITY_MASK::MOUNT | ENTITY_MASK::MONSTER | ENTITY_MASK::ITEM | ENTITY_MASK::ROPE | ENTITY_MASK::EXPLOSION | ENTITY_MASK::FX | ENTITY_MASK::ACTIVEFLOOR; + if (!(type->search_flags & test_mask)) + { + if (!(type->search_flags & ENTITY_MASK::LOGICAL)) // LOGICAL - as it has some movable entities + return false; + else if (type->id < first_logical) // actually check if it's not logical return true; - return false; + return false; + } + return true; } bool Entity::is_liquid() const @@ -236,10 +239,7 @@ void Entity::set_enable_turning(bool enabled) Entity* get_entity_ptr(uint32_t uid) { - auto p = State::find(State::get().ptr(), uid); - // if (IsBadWritePtr(p, 0x178)) - // return nullptr; - return p; + return HeapBase::get().state()->get_entity(uid); } std::vector Movable::get_all_behaviors() @@ -321,19 +321,20 @@ void Movable::set_position(float to_x, float to_y) rendering_info->x_dupe4 += dx; rendering_info->y_dupe4 += dy; } - if (State::get().ptr()->camera->focused_entity_uid == uid) - State::get().set_camera_position(dx, dy); + auto camera = HeapBase::get().state()->camera; + if (camera->focused_entity_uid == uid) + camera->set_position(dx, dy); } template -bool recursive(Entity* ent, std::optional mask, std::vector ent_types, RECURSIVE_MODE rec_mode, F func) +bool recursive(Entity* ent, std::optional mask, std::vector ent_types, RECURSIVE_MODE rec_mode, F func) { - auto acutal_mask = [](uint32_t m) -> uint32_t // for the MASK.ANY - { return m == 0 ? 0xFFFF : m; }; + auto actual_mask = [](ENTITY_MASK m) -> ENTITY_MASK // for the MASK.ANY + { return m == ENTITY_MASK::ANY ? (ENTITY_MASK)0xFFFF : m; }; if (rec_mode == RECURSIVE_MODE::EXCLUSIVE) { - if (mask.has_value() && (acutal_mask(mask.value()) & ent->type->search_flags) != 0) + if (mask.has_value() && !!(actual_mask(mask.value()) & ent->type->search_flags)) return false; if (std::find(ent_types.begin(), ent_types.end(), ent->type->id) != ent_types.end()) @@ -341,7 +342,7 @@ bool recursive(Entity* ent, std::optional mask, std::vector } else if (rec_mode == RECURSIVE_MODE::INCLUSIVE) { - if (mask.has_value() && (acutal_mask(mask.value()) & ent->type->search_flags) == 0) + if (mask.has_value() && !(actual_mask(mask.value()) & ent->type->search_flags)) { if (std::find(ent_types.begin(), ent_types.end(), ent->type->id) == ent_types.end()) return false; @@ -383,7 +384,7 @@ bool recursive(Entity* ent, std::optional mask, std::vector return true; } -void Entity::kill_recursive(bool destroy_corpse, Entity* responsible, std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode) +void Entity::kill_recursive(bool destroy_corpse, Entity* responsible, std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode) { auto kill_func = [destroy_corpse, &responsible](Entity* ent) -> void { @@ -393,7 +394,7 @@ void Entity::kill_recursive(bool destroy_corpse, Entity* responsible, std::optio kill(destroy_corpse, responsible); } -void Entity::destroy_recursive(std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode) +void Entity::destroy_recursive(std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode) { auto destroy_func = [](Entity* ent) -> void { diff --git a/src/game_api/entity.hpp b/src/game_api/entity.hpp index f39d45d38..369d2f997 100644 --- a/src/game_api/entity.hpp +++ b/src/game_api/entity.hpp @@ -150,12 +150,10 @@ class Entity auto topmost = this; while (auto cur = topmost->overlay) { - if (cur->type->search_flags <= 2) - { - topmost = cur; - } - else + if (!(cur->type->search_flags & (ENTITY_MASK::MOUNT | ENTITY_MASK::PLAYER))) break; + + topmost = cur; } return topmost; } @@ -209,7 +207,7 @@ class Entity /// Kill entity along with all entities attached to it. Be aware that for example killing push block with this function will also kill anything on top of it, any items, players, monsters etc. /// To avoid that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check mask, if the entity doesn't match, it will look in the provided ENT_TYPE's /// destroy_corpse and responsible are the standard parameters for the kill function - void kill_recursive(bool destroy_corpse, Entity* responsible, std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode); + void kill_recursive(bool destroy_corpse, Entity* responsible, std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode); /// Short for using RECURSIVE_MODE.NONE void kill_recursive(bool destroy_corpse, Entity* responsible) { @@ -217,7 +215,7 @@ class Entity }; /// Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc. /// To avoid that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check the mask, if the entity doesn't match, it will look in the provided ENT_TYPE's - void destroy_recursive(std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode); + void destroy_recursive(std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode); /// Short for using RECURSIVE_MODE.NONE void destroy_recursive() { @@ -288,7 +286,7 @@ class Entity virtual void generate_damage_particles(Entity* victim, DAMAGE_TYPE damage, bool killing) = 0; // 8, contact dmg virtual float get_type_field_a8() = 0; // 9 virtual bool can_be_pushed() = 0; // 10, (runs only for activefloors?) checks if entity type is pushblock, for chained push block checks ChainedPushBlock.is_chained, is only a check that allows for the pushing animation - virtual bool v11() = 0; // 11, is in motion? (only projectiles and some weapons) + virtual bool v11() = 0; // 11, is in motion? (only projectiles and some weapons), theme procedural spawn uses this /// Returns true if entity is in water/lava virtual bool is_in_liquid() = 0; // 12, drill always returns false virtual bool check_type_properties_flags_19() = 0; // 13, checks (properties_flags >> 0x12) & 1; for hermitcrab checks if he's invisible; can't get it to trigger diff --git a/src/game_api/entity_db.cpp b/src/game_api/entity_db.cpp index 2237a4ebc..2302d6016 100644 --- a/src/game_api/entity_db.cpp +++ b/src/game_api/entity_db.cpp @@ -1,31 +1,29 @@ #include "entity_db.hpp" #include "entity.hpp" -#include // for IsBadWritePtr -#include // for operator<=>, operator-, operator+ -#include // for round -#include // for operator<, operator<=, operator> -#include // for uint32_t, uint16_t, uint8_t -#include // for abs, NULL, size_t -#include // for _List_const_iterator -#include // for _Tree_iterator, map, _Tree_cons... -#include // for operator new -#include // for allocator, string, operator""sv -#include // for sleep_for -#include // for vector, _Vector_iterator, erase_if +#include // for operator<=>, operator-, operator+ +#include // for round +#include // for operator<, operator<=, operator> +#include // for uint32_t, uint16_t, uint8_t +#include // for abs, NULL, size_t +#include // for _List_const_iterator +#include // for _Tree_iterator, map, _Tree_cons... +#include // for operator new +#include // for allocator, string, operator""sv +#include // for sleep_for +#include // for vector, _Vector_iterator, erase_if -#include "containers/custom_map.hpp" // for custom_map -#include "entities_chars.hpp" // for Player -#include "entity_hooks_info.hpp" // for EntityHooksInfo -#include "memory.hpp" // for write_mem_prot -#include "movable.hpp" // for Movable -#include "movable_behavior.hpp" // for MovableBehavior -#include "render_api.hpp" // for RenderInfo -#include "search.hpp" // for get_address -#include "state.hpp" // for State, StateMemory, enum_to_layer -#include "state_structs.hpp" // for LiquidPhysicsEngine -#include "texture.hpp" // for get_texture, Texture -#include "vtable_hook.hpp" // for hook_vtable, hook_dtor, unregis... +#include "entities_chars.hpp" // for Player +#include "entity_hooks_info.hpp" // for EntityHooksInfo +#include "memory.hpp" // for write_mem_prot +#include "movable.hpp" // for Movable +#include "movable_behavior.hpp" // for MovableBehavior +#include "render_api.hpp" // for RenderInfo +#include "search.hpp" // for get_address +#include "state.hpp" // for StateMemory, enum_to_layer +#include "state_structs.hpp" // for LiquidPhysicsEngine +#include "texture.hpp" // for get_texture, Texture +#include "vtable_hook.hpp" // for hook_vtable, hook_dtor, unregis... EntityDB::EntityDB(const ENT_TYPE other) : EntityDB(*get_type(other)){}; diff --git a/src/game_api/entity_db.hpp b/src/game_api/entity_db.hpp index 5a82eec41..fb50e9f67 100644 --- a/src/game_api/entity_db.hpp +++ b/src/game_api/entity_db.hpp @@ -18,9 +18,9 @@ #include "containers/game_unordered_map.hpp" // for game_unordered_map #include "containers/identity_hasher.hpp" // for identity_hasher #include "entity_structs.hpp" // for CollisionInfo +#include "heap_base.hpp" // for OnHeapPointer #include "layer.hpp" // for EntityList #include "math.hpp" // for AABB -#include "thread_utils.hpp" // for OnHeapPointer struct RenderInfo; struct Texture; @@ -36,7 +36,7 @@ struct EntityDB int32_t field_10; ENT_TYPE id; /// MASK - uint32_t search_flags; + ENTITY_MASK search_flags; float width; float height; uint8_t draw_depth; diff --git a/src/game_api/entity_lookup.cpp b/src/game_api/entity_lookup.cpp index ef2d41f75..2ca4517bf 100644 --- a/src/game_api/entity_lookup.cpp +++ b/src/game_api/entity_lookup.cpp @@ -3,11 +3,9 @@ #include #include -#include "aliases.hpp" #include "custom_types.hpp" #include "entity.hpp" #include "layer.hpp" -#include "math.hpp" #include "state.hpp" bool entity_type_check(const std::vector& types_array, const ENT_TYPE find) @@ -40,10 +38,7 @@ std::vector get_proper_types(std::vector ent_types) int32_t get_grid_entity_at(float x, float y, LAYER layer) { - auto& state = State::get(); - uint8_t actual_layer = enum_to_layer(layer); - - if (Entity* ent = state.layer(actual_layer)->get_grid_entity_at(x, y)) + if (Entity* ent = get_state_ptr()->layer(layer)->get_grid_entity_at(x, y)) return ent->uid; return -1; @@ -51,16 +46,15 @@ int32_t get_grid_entity_at(float x, float y, LAYER layer) std::vector get_entities_overlapping_grid(float x, float y, LAYER layer) { - auto& state = State::get(); - uint8_t actual_layer = enum_to_layer(layer); + auto state = get_state_ptr(); std::vector uids; - auto entities = state.layer(actual_layer)->get_entities_overlapping_grid_at(x, y); + auto entities = state->layer(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); + auto entities2 = state->layers[1]->get_entities_overlapping_grid_at(x, y); if (entities2) uids.insert(uids.end(), entities2->uids().begin(), entities2->uids().end()); } @@ -69,9 +63,9 @@ std::vector get_entities_overlapping_grid(float x, float y, LAYER laye template requires std::is_invocable_v -void foreach_mask(uint32_t mask, Layer* l, FunT&& fun) +void foreach_mask(ENTITY_MASK mask, Layer* l, FunT&& fun) { - if (mask == 0) + if (mask == ENTITY_MASK::ANY) { fun(l->all_entities); } @@ -79,21 +73,21 @@ void foreach_mask(uint32_t mask, Layer* l, FunT&& fun) { for (uint32_t test_flag = 1U; test_flag < 0x8000; test_flag <<= 1U) { - if (mask & test_flag) + if (!(mask & static_cast(test_flag))) + continue; + + const auto& it = l->entities_by_mask.find(test_flag); + if (it != l->entities_by_mask.end()) { - const auto& it = l->entities_by_mask.find(test_flag); - if (it != l->entities_by_mask.end()) - { - fun(it->second); - } + fun(it->second); } } } } -std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer) +std::vector get_entities_by(std::vector entity_types, ENTITY_MASK mask, LAYER layer) { - auto state = State::get().ptr(); + auto state = get_state_ptr(); std::vector found; const std::vector proper_types = get_proper_types(std::move(entity_types)); @@ -119,7 +113,7 @@ std::vector get_entities_by(std::vector entity_types, uint32 auto layer_back = state->layers[1]; if (proper_types.empty() || proper_types[0] == 0) { - if (mask == 0) // all entities + if (mask == ENTITY_MASK::ANY) // all entities { // this exception for small improvements with calling reserve once found.reserve(found.size() + (size_t)layer_front->all_entities.size + (size_t)layer_back->all_entities.size); @@ -140,23 +134,22 @@ std::vector get_entities_by(std::vector entity_types, uint32 } else { - uint8_t correct_layer = enum_to_layer(layer); if (proper_types.empty() || proper_types[0] == 0) // all types { - foreach_mask(mask, state->layers[correct_layer], insert_all_uids); + foreach_mask(mask, state->layer(layer), insert_all_uids); } else { - foreach_mask(mask, state->layers[correct_layer], push_matching_types); + foreach_mask(mask, state->layer(layer), push_matching_types); } } return found; } -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(std::vector entity_types, ENTITY_MASK mask, float x, float y, LAYER layer, float radius) { // TODO: use entity regions? - auto& state = State::get(); + auto state = get_state_ptr(); std::vector found; const std::vector proper_types = get_proper_types(std::move(entity_types)); auto push_entities_at = [&x, &y, &radius, &proper_types, &found](const EntityList& entities) @@ -171,40 +164,34 @@ std::vector get_entities_at(std::vector entity_types, uint32 } } }; + foreach_mask(mask, state->layer(layer), push_entities_at); if (layer == LAYER::BOTH) { - foreach_mask(mask, state.layer(0), push_entities_at); - foreach_mask(mask, state.layer(1), push_entities_at); - } - else - { - foreach_mask(mask, state.layer(enum_to_layer(layer)), push_entities_at); + // if it's both, then the actual_layer is 0 + foreach_mask(mask, state->layers[1], push_entities_at); } return found; } -std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer) +std::vector get_entities_overlapping_hitbox(std::vector entity_types, ENTITY_MASK mask, AABB hitbox, LAYER layer) { // TODO: use entity regions? - auto& state = State::get(); + auto state = get_state_ptr(); std::vector result; const std::vector proper_types = get_proper_types(std::move(entity_types)); + + result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state->layer(layer)); + if (layer == LAYER::BOTH) { - std::vector result2; - result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(0)); - result2 = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(1)); + // if it's both, then the actual_layer is 0 + auto result2 = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state->layers[1]); result.insert(result.end(), result2.begin(), result2.end()); } - else - { - uint8_t actual_layer = enum_to_layer(layer); - result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(actual_layer)); - } return result; } -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(std::vector entity_types, ENTITY_MASK mask, float sx, float sy, float sx2, float sy2, Layer* layer) { std::vector found; foreach_mask(mask, layer, [&entity_types, &found, &sx, &sy, &sx2, &sy2](const EntityList& entities) @@ -246,7 +233,7 @@ bool entity_has_item_type(uint32_t uid, std::vector entity_types) return false; } -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, std::vector entity_types, ENTITY_MASK mask) { std::vector found; Entity* entity = get_entity_ptr(uid); @@ -255,7 +242,7 @@ std::vector entity_get_items_by(uint32_t uid, std::vector en if (entity->items.size > 0) { const std::vector proper_types = get_proper_types(std::move(entity_types)); - if ((!proper_types.size() || !proper_types[0]) && !mask) // all items + if ((!proper_types.size() || !proper_types[0]) && mask == ENTITY_MASK::ANY) // all items { const auto uids = entity->items.uids(); found.insert(found.end(), uids.begin(), uids.end()); @@ -264,7 +251,7 @@ std::vector entity_get_items_by(uint32_t uid, std::vector en { for (auto item : entity->items.entities()) { - if ((mask == 0 || (item->type->search_flags & mask)) && entity_type_check(proper_types, item->type->id)) + if ((mask == ENTITY_MASK::ANY || !!(item->type->search_flags & mask)) && entity_type_check(proper_types, item->type->id)) { found.push_back(item->uid); } @@ -276,19 +263,19 @@ std::vector entity_get_items_by(uint32_t uid, std::vector en std::vector get_entities_by_draw_depth(std::vector draw_depths, LAYER l) { - auto state = State::get().ptr_local(); + auto state = get_state_ptr(); std::vector found; - auto actual_layer = enum_to_layer(l); for (auto draw_depth : draw_depths) { if (draw_depth > 52) continue; - auto uids_layer1 = state->layers[actual_layer]->entities_by_draw_depth[draw_depth].uids(); + auto uids_layer1 = state->layer(l)->entities_by_draw_depth[draw_depth].uids(); found.insert(found.end(), uids_layer1.begin(), uids_layer1.end()); - if (l == LAYER::BOTH) // if it's both, then the actual_layer is 0 + if (l == LAYER::BOTH) { + // if it's both, then the actual_layer is 0 auto uids_layer2 = state->layers[1]->entities_by_draw_depth[draw_depth].uids(); found.insert(found.end(), uids_layer2.begin(), uids_layer2.end()); } diff --git a/src/game_api/entity_lookup.hpp b/src/game_api/entity_lookup.hpp index 3c9157b2f..1e6e4331f 100644 --- a/src/game_api/entity_lookup.hpp +++ b/src/game_api/entity_lookup.hpp @@ -12,52 +12,52 @@ 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); +std::vector get_entities_by(std::vector entity_types, ENTITY_MASK mask, LAYER layer); inline std::vector get_entities() { - return get_entities_by({}, 0, LAYER::BOTH); + return get_entities_by({}, ENTITY_MASK::ANY, LAYER::BOTH); } -inline std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer) +inline std::vector get_entities_by(ENT_TYPE entity_type, ENTITY_MASK 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); + return get_entities_by(std::move(entity_types), ENTITY_MASK::ANY, LAYER::BOTH); } inline std::vector get_entities_by_type(ENT_TYPE entity_type) { - return get_entities_by(std::vector{entity_type}, 0, LAYER::BOTH); + return get_entities_by(std::vector{entity_type}, ENTITY_MASK::ANY, LAYER::BOTH); } -inline std::vector get_entities_by_mask(uint32_t mask) +inline std::vector get_entities_by_mask(ENTITY_MASK mask) { return get_entities_by({}, mask, LAYER::BOTH); } inline std::vector get_entities_by_layer(LAYER layer) { - return get_entities_by({}, 0, layer); + return get_entities_by({}, ENTITY_MASK::ANY, layer); } -std::vector get_entities_at(std::vector entity_types, 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) +std::vector get_entities_at(std::vector entity_types, ENTITY_MASK mask, float x, float y, LAYER layer, float radius); +inline std::vector get_entities_at(ENT_TYPE entity_type, ENTITY_MASK 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); -inline std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer) +std::vector get_entities_overlapping_hitbox(std::vector entity_types, ENTITY_MASK mask, AABB hitbox, LAYER layer); +inline std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, ENTITY_MASK 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) +inline std::vector get_entities_overlapping(std::vector entity_types, ENTITY_MASK 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) +inline std::vector get_entities_overlapping(ENT_TYPE entity_type, ENTITY_MASK 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); -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) +std::vector get_entities_overlapping_by_pointer(std::vector entity_types, ENTITY_MASK mask, float sx, float sy, float sx2, float sy2, Layer* layer); +inline std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, ENTITY_MASK 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); } @@ -67,8 +67,8 @@ 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); -inline std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask) +std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, ENTITY_MASK mask); +inline std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, ENTITY_MASK mask) { return entity_get_items_by(uid, std::vector{entity_type}, mask); } diff --git a/src/game_api/entity_structs.hpp b/src/game_api/entity_structs.hpp index 1495ab502..4bc6e18f8 100644 --- a/src/game_api/entity_structs.hpp +++ b/src/game_api/entity_structs.hpp @@ -1,16 +1,6 @@ #pragma once -#include // for size_t -#include // for uint8_t, uint32_t, int32_t, uint16_t, int64_t -#include // for function, equal_to -#include // for span -#include // for allocator, string -#include // for string_view -#include // for tuple -#include // for move -#include // for _Umap_traits<>::allocator_type, unordered_map -#include // for pair -#include // for vector +#include // for uint8_t, int32_t enum class REPEAT_TYPE : uint8_t { @@ -28,7 +18,8 @@ enum class SHAPE : uint8_t struct Animation { int32_t first_tile; - int32_t count; // num_tiles + // num_tiles + int32_t count; int32_t interval; uint8_t id; REPEAT_TYPE repeat; diff --git a/src/game_api/file_api.cpp b/src/game_api/file_api.cpp index e7c8bccfc..097dade91 100644 --- a/src/game_api/file_api.cpp +++ b/src/game_api/file_api.cpp @@ -2,16 +2,17 @@ #include #include +#include +#include -#include #include -#include "containers/game_allocator.hpp" - -#include "memory.hpp" -#include "render_api.hpp" -#include "util.hpp" -#include "window_api.hpp" +#include "color.hpp" // for Color +#include "containers/game_allocator.hpp" // game_malloc +#include "render_api.hpp" // for RenderAPI +#include "search.hpp" // for get_address +#include "util.hpp" // for OnScopeExit +#include "window_api.hpp" // for get_device #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" @@ -56,7 +57,7 @@ MakeSavePathCallback g_MakeSavePathCallback{nullptr}; std::string hash_path(std::string_view path) { - auto abs_path = std::filesystem::absolute(path).make_preferred(); + auto& abs_path = std::filesystem::absolute(path).make_preferred(); auto abs_path_str = abs_path.string(); uint64_t res = 10000019; int i = 0; diff --git a/src/game_api/file_api.hpp b/src/game_api/file_api.hpp index e02764d18..5d5247249 100644 --- a/src/game_api/file_api.hpp +++ b/src/game_api/file_api.hpp @@ -1,11 +1,10 @@ #pragma once -#include -#include -#include -#include - -#include +#include // for size_t +#include // for malloc +#include // for ID3D11ShaderResourceView +#include // for string +#include // string_view using AllocFun = decltype(malloc); diff --git a/src/game_api/game_api.cpp b/src/game_api/game_api.cpp index 5ccdefcab..7754676c2 100644 --- a/src/game_api/game_api.cpp +++ b/src/game_api/game_api.cpp @@ -1,8 +1,7 @@ #include "game_api.hpp" -#include "render_api.hpp" -#include "search.hpp" -#include "state.hpp" +#include "search.hpp" // for get_address +#include "state.hpp" // for StateMemory, get_state_ptr GameAPI* GameAPI::get() { @@ -14,7 +13,7 @@ GameAPI* GameAPI::get() float GameAPI::get_current_zoom() const { - auto state = State::get().ptr(); + auto state = get_state_ptr(); return renderer->current_zoom + get_layer_transition_zoom_offset(state->camera_layer); } diff --git a/src/game_api/game_api.hpp b/src/game_api/game_api.hpp index 9f1f6da3f..960c89de0 100644 --- a/src/game_api/game_api.hpp +++ b/src/game_api/game_api.hpp @@ -6,6 +6,15 @@ #include #include +#include "heap_base.hpp" // for OnHeapPointer + +struct UnknownRenderStuff +{ + size_t* unknown1; + size_t* unknown2; + size_t unknown3; +}; + struct Renderer { // check x64dbg plugin for up to date structure @@ -56,11 +65,11 @@ struct Renderer uint16_t unknown60a; // 512 uint16_t unknown60b[2]; // padding? size_t* unknown61[4]; - size_t unknown62; // bool? - std::unordered_map unknown63; // not sure about the key/value + size_t unknown62; // bool? + std::unordered_set textures; // all game textures including placeholder - // bounch of vectors that probably used to load textures or something, they all seam to contain names of the .dds files - // when i checked all seam to be already cleared and just have the data leftover, the "const char**" pointers identical as in texturedb + // bunch of vectors that probably used to load textures or something, they all seam to contain names of the .dds files + // when i checked all seam to be already cleared and just have the data leftover, the "const char**" pointers identical as in textureDB size_t unknown64[6]; // possibly two more vectors? std::vector unknown65; // splash 0,1,2 @@ -86,13 +95,16 @@ struct Renderer uint8_t unknown86[6]; // padding probably size_t* unknown87; // some vtables + bool unknown87a[110]; + UnknownRenderStuff unknown87b[110]; - uint8_t skip3[0xAD8]; // probably some static arrays of ... stuff - - size_t swap_chain; // unsure? + OnHeapPointer camera; + size_t unknown87d; // bool? + size_t* unknown88; + size_t swap_chain; // unsure? offset 0x80FD0 // a lot of stuff more, total size is 0x81138 bytes - // somewhere there should be shaders stored + // somewhere there should be shaders stored? // added just to have the vtable virtual ~Renderer() = 0; diff --git a/src/game_api/game_manager.cpp b/src/game_api/game_manager.cpp index 746a5a618..19981eae3 100644 --- a/src/game_api/game_manager.cpp +++ b/src/game_api/game_manager.cpp @@ -1,7 +1,5 @@ #include "game_manager.hpp" -#include // for operator""sv - #include "search.hpp" // for get_address GameManager* get_game_manager() diff --git a/src/game_api/game_manager.hpp b/src/game_api/game_manager.hpp index 373747dc2..c9b1e2238 100644 --- a/src/game_api/game_manager.hpp +++ b/src/game_api/game_manager.hpp @@ -6,9 +6,9 @@ #include "aliases.hpp" // for MAX_PLAYERS #include "containers/game_unordered_map.hpp" // for game_unordered_map #include "containers/identity_hasher.hpp" // for identity_hasher +#include "heap_base.hpp" // for OnHeapPointer #include "render_api.hpp" // for TextureRenderingInfo #include "sound_manager.hpp" // for BackgroundSound -#include "thread_utils.hpp" // for OnHeapPointer struct SaveData; class ScreenCamp; diff --git a/src/game_api/game_patches.cpp b/src/game_api/game_patches.cpp index 3d666c0d2..e46ddb0ce 100644 --- a/src/game_api/game_patches.cpp +++ b/src/game_api/game_patches.cpp @@ -8,14 +8,13 @@ #include #include -#include "entity.hpp" -#include "layer.hpp" -#include "memory.hpp" -#include "movable.hpp" -#include "search.hpp" -#include "state.hpp" -#include "state_structs.hpp" -#include "virtual_table.hpp" +#include "entity.hpp" // for Entity +#include "entity_db.hpp" // for to_id +#include "layer.hpp" // for Layer, EntityList +#include "memory.hpp" // for Memory, get_address, memory_read ... +#include "search.hpp" // for get_address +#include "state.hpp" // for StateMemory +#include "virtual_table.hpp" // for get_virtual_function_address void patch_orbs_limit() { @@ -52,9 +51,7 @@ void patch_orbs_limit() bool check_if_ent_type_exists(ENT_TYPE type, int mask) { - StateMemory* state = State::get().ptr_local(); - if (state == nullptr) - state = State::get().ptr_main(); + StateMemory* state = get_state_ptr(); const auto entities_map = &state->layers[0]->entities_by_mask; // game code only cares about the front layer, so we do the same auto it = entities_map->find(mask); @@ -275,7 +272,7 @@ void set_skip_olmec_cutscene(bool skip) { patch_olmec_kill_crash(); // just in case - // simple jump over the tiamat check, nop here just so there is no funny business + // simple jump over the olmec check, nop here just so there is no funny business static const std::string code = fmt::format("\xEB{}\x90"sv, to_le_bytes(static_cast(g_olmec_patch_size - 2))); if (skip) write_mem_recoverable("set_skip_olmec_cutscene", g_olmec_patch_addr, code, true); @@ -339,3 +336,899 @@ void patch_entering_closed_door_crash() write_mem_prot(new_code_addr + 14, rel, true); once = true; } + +float* g_sparktrap_parameters{nullptr}; +void modify_sparktraps(float angle_increment, float distance) +{ + if (g_sparktrap_parameters == nullptr) + { + static const auto offset = get_address("sparktrap_angle_increment") + 4; + + if (memory_read(offset - 1) == 0x89) // check if sparktraps_hack is active + return; + + const int32_t distance_offset = 0xF1; + g_sparktrap_parameters = (float*)alloc_mem_rel32(offset + 4, sizeof(float) * 2); + if (!g_sparktrap_parameters) + return; + + int32_t rel = static_cast((size_t)g_sparktrap_parameters - (offset + 4)); + write_mem_prot(offset, rel, true); + write_mem_prot(offset + distance_offset, (int32_t)(rel - distance_offset + sizeof(float)), true); + } + *g_sparktrap_parameters = angle_increment; + *(g_sparktrap_parameters + 1) = distance; +} + +float* get_sparktraps_parameters_ptr() // only for the UI +{ + return g_sparktrap_parameters; +} + +void activate_sparktraps_hack(bool activate) +{ + if (activate) + { + static const auto offset = get_address("sparktrap_angle_increment"); + const int32_t distance_offset = 0xF1; + + write_mem_recoverable("sparktraps_hack", offset, "\xF3\x0F\x58\x89\x6C\x01\x00\x00"sv, true); + write_mem_recoverable("sparktraps_hack", offset + distance_offset, "\xF3\x0F\x10\xB9\x70\x01\x00\x00"sv, true); + } + else + { + recover_mem("sparktraps_hack"); + } +} + +void set_storage_layer(LAYER layer) +{ + static const auto storage_layer = get_address("storage_layer"); + if (layer == LAYER::FRONT || layer == LAYER::BACK) + write_mem_prot(storage_layer, 0x1300 + 8 * (uint8_t)layer, true); +} + +void set_kapala_blood_threshold(uint8_t threshold) +{ + static const auto kapala_blood_threshold = get_address("kapala_blood_threshold"); + write_mem_prot(kapala_blood_threshold, threshold, true); +} + +void set_kapala_hud_icon(int8_t icon_index) +{ + static const size_t instruction_offset = get_address("kapala_hud_icon"); + static const size_t icon_index_offset = instruction_offset + 0x12; + static const uint32_t distance = static_cast(icon_index_offset - (instruction_offset + 7)); + + if (icon_index < 0) // reset to original + { + write_mem_prot(instruction_offset + 2, 0x00013089, true); + } + else + { + // Instead of loading the value from KapalaPowerup:amount_of_blood (the instruction pointed at by instruction_offset) + // we overwrite this with an instruction that loads a byte located a bit after the current function. + // So you need to assemble `movzx ,BYTE PTR [rip+]` + write_mem_prot(instruction_offset + 2, {0x0d}, true); + write_mem_prot(instruction_offset + 3, distance, true); + if (icon_index > 6) + { + icon_index = 6; + } + write_mem_prot(icon_index_offset, icon_index, true); + } +} + +void set_olmec_phase_y_level(uint8_t phase, float y) +{ + // Sets the Y-level Olmec changes phases. The defaults are : + // - phase 1 (bombs) = 100 + // - phase 2 (ufos) = 83 + // Olmecs checks phases in order! The means if you want ufo's from the start + // you have to put both phase 1 and 2 at e.g. level 199 + // If you want to make Olmec stay in phase 0 (stomping) all the time, you can just set + // the phase 1 y level to 70. Don't set it too low, from 1.25.0 onwards, Olmec's stomp + // activation distance seems to be related to the y-level trigger point. + static size_t phase1_offset; + if (phase1_offset == 0) + { + // from 1.23.x onwards, there are now two instructions per phase that reference the y-level float + const size_t phase_1_instruction_a = get_address("olmec_transition_phase_1_y_level"); + const size_t phase_1_instruction_b = phase_1_instruction_a + 0xd; + + const size_t phase_2_instruction_a = get_address("olmec_transition_phase_2_y_level"); + const size_t phase_2_instruction_b = phase_2_instruction_a + 0x11; + phase1_offset = (size_t)alloc_mem_rel32(phase_2_instruction_b + 4, sizeof(float) * 2); + if (!phase1_offset) + return; + + auto phase2_offset = phase1_offset + 0x4; + + // write the default values to our new floats + write_mem_prot(phase1_offset, 100.0f, true); + write_mem_prot(phase2_offset, 83.0f, true); + + // calculate the distances between our floats and the movss instructions + auto distance_1_a = static_cast(phase1_offset - phase_1_instruction_a); + auto distance_1_b = static_cast(phase1_offset - phase_1_instruction_b); + auto distance_2_a = static_cast(phase2_offset - phase_2_instruction_a); + auto distance_2_b = static_cast(phase2_offset - phase_2_instruction_b); + + // overwrite the movss instructions to load our floats + write_mem_prot(phase_1_instruction_a - 4, distance_1_a, true); + write_mem_prot(phase_1_instruction_b - 4, distance_1_b, true); + write_mem_prot(phase_2_instruction_a - 4, distance_2_a, true); + write_mem_prot(phase_2_instruction_b - 4, distance_2_b, true); + } + + if (phase == 1) + { + *(float*)phase1_offset = y; + } + else if (phase == 2) + { + *(float*)(phase1_offset + sizeof(float)) = y; + } +} + +void force_olmec_phase_0(bool b) +{ + static const size_t offset = get_address("olmec_transition_phase_1"); + + if (b) + write_mem_recoverable("force_olmec_phase_0", offset, "\xEB\x2E"sv, true); // jbe -> jmp + else + recover_mem("force_olmec_phase_0"); +} + +void set_ghost_spawn_times(uint32_t normal, uint32_t cursed) +{ + static const auto ghost_spawn_time = get_address("ghost_spawn_time"); + static const auto ghost_spawn_time_cursed_p1 = get_address("ghost_spawn_time_cursed_player1"); + static const auto ghost_spawn_time_cursed_p2 = get_address("ghost_spawn_time_cursed_player2"); + static const auto ghost_spawn_time_cursed_p3 = get_address("ghost_spawn_time_cursed_player3"); + static const auto ghost_spawn_time_cursed_p4 = get_address("ghost_spawn_time_cursed_player4"); + + write_mem_prot(ghost_spawn_time, normal, true); + write_mem_prot(ghost_spawn_time_cursed_p1, cursed, true); + write_mem_prot(ghost_spawn_time_cursed_p2, cursed, true); + write_mem_prot(ghost_spawn_time_cursed_p3, cursed, true); + write_mem_prot(ghost_spawn_time_cursed_p4, cursed, true); +} + +void set_time_ghost_enabled(bool b) +{ + static size_t offset_trigger = 0; + static size_t offset_toast_trigger = 0; + if (offset_trigger == 0) + { + 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))); + } + if (b) + { + recover_mem("set_time_ghost_enabled"); + } + else + { + write_mem_recoverable("set_time_ghost_enabled", offset_trigger, "\xC3\x90\x90\x90"sv, true); + write_mem_recoverable("set_time_ghost_enabled", offset_toast_trigger, "\xC3\x90\x90\x90"sv, true); + } +} + +void set_time_jelly_enabled(bool b) +{ + 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"); + else + write_mem_recoverable("set_time_jelly_enabled", offset, "\xC3\x90\x90\x90"sv, true); +} + +void set_camp_camera_bounds_enabled(bool b) +{ + static const size_t offset = get_address("enforce_camp_camera_bounds"); + if (b) + recover_mem("camp_camera_bounds"); + else + write_mem_recoverable("camp_camera_bounds", offset, "\xC3\x90\x90"sv, true); +} + +void set_explosion_mask(int32_t mask) +{ + static const size_t addr = get_address("explosion_mask"); + if (mask == -1) + recover_mem("explosion_mask"); + else + write_mem_recoverable("explosion_mask", addr, mask, true); +} + +void set_max_rope_length(uint8_t length) +{ + uint32_t length_32 = length; + static const auto attach_thrown_rope = get_address("attach_thrown_rope_to_background"); + static const auto process_ropes_one = get_address("process_ropes_one"); + static const auto process_ropes_two = get_address("process_ropes_two"); + static const auto process_ropes_three = get_address("process_ropes_three"); + + // there's four instances where the max (default=6) is used + + // 1) When throwing a rope and it attaches to the background, the initial entity is + // given a start value in its segment_nr_inverse variable + write_mem_prot(attach_thrown_rope, length_32, true); + + // 2) and 3) at the top of the rope processing function are two comparisons to the max + write_mem_prot(process_ropes_one, length, true); + write_mem_prot(process_ropes_two, length, true); + + // 4) in the same function at the end of the little loop of process_ropes_two is a comparison to n-1 + uint8_t length_minus_one_8 = length - 1; + write_mem_prot(process_ropes_three, length_minus_one_8, true); +} + +void change_sunchallenge_spawns(std::vector ent_types) +{ + // [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) + { + offset = get_address("sun_challenge_generator_ent_types"); + + // just so we can recover the oryginal later + save_mem_recoverable("sunchallenge_spawn", offset, 14, true); + } + 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) + { + 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(table_offset + 4, data_size); + if (new_array) + { + 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); + + 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); + + if (was_edited_before) + VirtualFree(old_types_array, 0, MEM_RELEASE); + } +} + +void change_diceshop_prizes(std::vector ent_types) +{ + static const auto offset = get_address("dice_shop_prizes_id_roll"); + static const auto array_offset = get_address("dice_shop_prizes"); + ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(array_offset) + array_offset + 4); + bool original_instr = (memory_read(offset) == 0x89); + + if (ent_types.size() > 255 || ent_types.size() < 6) // has to be min 6 as the game needs 6 uniqe item ids for prize_dispenser + { + if (!ent_types.size()) + { + if (!original_instr) + VirtualFree(old_types_array, 0, MEM_RELEASE); + + recover_mem("diceshop_prizes"); + } + return; + } + + if ((original_instr && ent_types.size() == 25) || // if it's the unchanged instruction and we set the same number of ent_type's + (!original_instr && memory_read(offset + 5) == ent_types.size())) // or new instruction but the same size + { + for (unsigned int i = 0; i < ent_types.size(); ++i) + write_mem_recoverable("diceshop_prizes", (size_t)&old_types_array[i], ent_types[i], true); + + return; + } + + const auto data_size = ent_types.size() * sizeof(ENT_TYPE); + ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(array_offset + 4, data_size); + + if (new_array) + { + if (!original_instr) + VirtualFree(old_types_array, 0, MEM_RELEASE); + + memcpy(new_array, ent_types.data(), data_size); + int32_t rel = static_cast((size_t)new_array - (array_offset + 4)); + write_mem_recoverable("diceshop_prizes", array_offset, rel, true); + + if (original_instr) + { + std::string new_code = fmt::format("\x50\x31\xC0\x41\xB3{}\x88\xD0\x41\xF6\xF3\x88\xE2\x58"sv, to_le_bytes((uint8_t)ent_types.size())); + // push rax + // xor eax, eax + // mov r11b, (size) + // mov al, dl + // divb r11b + // mov dl, ah + // pop rax + write_mem_recoverable("diceshop_prizes", offset, new_code, true); + } + else + { + write_mem_recoverable("diceshop_prizes", offset + 5, (uint8_t)ent_types.size(), true); + } + } +} + +void change_altar_damage_spawns(std::vector ent_types) +{ + if (ent_types.size() > 255) + return; + + static const auto array_offset = get_address("altar_break_ent_types"); + ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(array_offset) + array_offset + 4); + const auto code_offset = array_offset + 0xDD; + const auto instruction_shr = array_offset + 0x13D; + const auto instruction_to_modifiy = array_offset + 0x204; + const auto original_instr = (memory_read(instruction_shr) == 0x41); + if (ent_types.empty()) + { + if (!original_instr) + VirtualFree(old_types_array, 0, MEM_RELEASE); + + recover_mem("altar_damage_spawn"); + return; + } + if (!original_instr && memory_read(code_offset + 2) == ent_types.size()) + { + // original array is used for something else as well, so i never edit that content + for (uint32_t i = 0; i < ent_types.size(); ++i) + write_mem_recoverable("altar_damage_spawn", (size_t)&old_types_array[i], ent_types[i], true); + + return; + } + const auto data_size = ent_types.size() * sizeof(ENT_TYPE); + ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(array_offset + 4, data_size); + if (new_array) + { + if (!original_instr) + VirtualFree(old_types_array, 0, MEM_RELEASE); + + memcpy(new_array, ent_types.data(), data_size); + int32_t rel = static_cast((size_t)new_array - (array_offset + 4)); + write_mem_recoverable("altar_damage_spawn", array_offset, rel, true); + + if (original_instr) + { + std::string new_code = fmt::format("\x41\xB1{}\x48\xC1\xE8\x38\x41\xF6\xF1\x49\x89\xC1"sv, to_le_bytes((uint8_t)ent_types.size())); + // mov R9b, (size) + // shr RAX, 0x38 + // divb R9b + // mov R9, RAX + write_mem_recoverable("altar_damage_spawn", code_offset, new_code, true); + write_mem_recoverable("altar_damage_spawn", instruction_shr, "\x49\xC1\xE9\x08"sv, true); // shr r9,0x8 + write_mem_recoverable("altar_damage_spawn", instruction_to_modifiy, (uint8_t)0x8C, true); // r9+r12 => r12+r9*4 + } + else + { + write_mem_recoverable("altar_damage_spawn", code_offset + 2, (uint8_t)ent_types.size(), true); + } + } +} + +void change_waddler_drop(std::vector ent_types) +{ + static bool modified = false; + + static const auto offset = get_address("waddler_drop_size"); + static const auto array_offset = get_address("waddler_drop_array"); + ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(array_offset) + array_offset + 4); + + if (ent_types.size() > 255 || ent_types.size() < 1) + { + if (!ent_types.size()) + { + if (modified) + VirtualFree(old_types_array, 0, MEM_RELEASE); + + recover_mem("waddler_drop"); + modified = false; + } + return; + } + + if ((!modified && ent_types.size() == 3) || // if it's the unchanged instruction and we set the same number of ent_type's + (modified && memory_read(offset) == ent_types.size())) // or new instruction but the same size + { + for (unsigned int i = 0; i < ent_types.size(); ++i) + write_mem_recoverable("waddler_drop", (size_t)&old_types_array[i], ent_types[i], true); + + return; + } + + const auto data_size = ent_types.size() * sizeof(ENT_TYPE); + ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(array_offset + 4, data_size); + + if (new_array) + { + if (modified) + VirtualFree(old_types_array, 0, MEM_RELEASE); + + memcpy(new_array, ent_types.data(), data_size); + int32_t rel = static_cast((size_t)new_array - (array_offset + 4)); + write_mem_recoverable("waddler_drop", array_offset, rel, true); + write_mem_recoverable("waddler_drop", offset, (uint8_t)ent_types.size(), true); + modified = true; + } +} + +void modify_ankh_health_gain(uint8_t health, uint8_t beat_add_health) +{ + static size_t offsets[4]; + static const auto size_minus_one = get_address("ankh_health"); + if (!health) + { + recover_mem("ankh_health"); + return; + } + if (size_minus_one && beat_add_health) + { + if (!offsets[0]) + { + 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"); + offsets[1] = find_inst(memory.exe(), "\x41\x80\xBF\x17\x01\x00\x00"sv, offsets[0] + 7, limit_size, "ankh_health_gain_2"); + offsets[2] = find_inst(memory.exe(), "\x0F\x42\xCA\x83\xC0"sv, offset, limit_size, "ankh_health_gain_3"); + offsets[3] = find_inst(memory.exe(), "\x8A\x83\x17\x01\x00\x00\x3C"sv, offset, std::nullopt, "ankh_health_gain_4"); // this is some bs + if (!offsets[0] || !offsets[1] || !offsets[2] || !offsets[3]) + { + offsets[0] = 0; + return; + } + offsets[0] = memory.at_exe(offsets[0] + 7); // add pattern size + offsets[1] = memory.at_exe(offsets[1] + 7); + offsets[2] = memory.at_exe(offsets[2] + 5); + offsets[3] = memory.at_exe(offsets[3] + 7); + } + const uint8_t game_maxhp = memory_read(offsets[2] - 14); + if (health > game_maxhp) + health = game_maxhp; + + if (health % beat_add_health == 0) + { + write_mem_recoverable("ankh_health", size_minus_one, (uint8_t)(health - 1), true); + write_mem_recoverable("ankh_health", offsets[0], health, true); + write_mem_recoverable("ankh_health", offsets[1], health, true); + write_mem_recoverable("ankh_health", offsets[2], beat_add_health, true); + if (health < 4) + { + write_mem_recoverable("ankh_health", offsets[3], (uint8_t)0, true); + } + else + { + if (memory_read(offsets[3]) != 3) + recover_mem("ankh_health", offsets[3]); + } + } + } +} + +void change_poison_timer(int16_t frames) +{ + static const size_t offset_first = get_address("first_poison_tick_timer_default"); + static const size_t offset_subsequent = get_address("subsequent_poison_tick_timer_default"); + + if (frames == -1) + { + recover_mem("change_poison_timer"); + } + else + { + write_mem_recoverable("change_poison_timer", offset_first, frames, true); + write_mem_recoverable("change_poison_timer", offset_subsequent, frames, true); + } +} + +bool disable_floor_embeds(bool disable) +{ + static const auto address = get_address("spawn_floor_embeds"); + const bool current_value = memory_read(address) == 0xc3; + if (disable) + write_mem_recoverable("disable_floor_embeds", address, "\xC3"sv, true); + else + recover_mem("disable_floor_embeds"); + return current_value; +} + +void set_cursepot_ghost_enabled(bool enable) +{ + static const auto address = get_address("ghost_jar_ghost_spawn"); + if (!enable) + write_mem_recoverable("ghost_jar_ghost_spawn", address, "\x90\x90\x90\x90\x90"sv, true); + else + recover_mem("ghost_jar_ghost_spawn"); +} + +void set_ending_unlock(ENT_TYPE type) +{ + static const ENT_TYPE first = to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); + static const ENT_TYPE last = to_id("ENT_TYPE_CHAR_CLASSIC_GUY"); + if (type >= first && type <= last) + { + static const auto offset = get_address("ending_unlock"); + const int32_t char_offset = 10; + + write_mem_recoverable("ending_unlock", offset, "\x90\x90\x90\x90\x90\x90\x90\x90"sv, true); + write_mem_recoverable("ending_unlock", offset + char_offset, type, true); + } + else + { + recover_mem("ending_unlock"); + } +} + +void activate_tiamat_position_hack(bool activate) +{ + static const auto code_addr = get_address("tiamat_attack_position"); + + static const std::string_view code{"\xF3\x0F\x5C\xBE\x78\x01\x00\x00"sv // subss xmm7,DWORD PTR [rsi+0x178] + "\xF3\x0F\x5C\xB6\x7C\x01\x00\x00"sv}; // subss xmm6,DWORD PTR [rsi+0x17C] + + if (activate) + write_mem_recoverable("activate_tiamat_position_hack", code_addr, code, true); + else + recover_mem("activate_tiamat_position_hack"); +} + +void activate_crush_elevator_hack(bool activate) +{ + auto& memory = Memory::get(); + static size_t offsets[3]; + if (offsets[0] == 0) + { + auto func_offset = get_virtual_function_address(VTABLE_OFFSET::ACTIVEFLOOR_CRUSHING_ELEVATOR, 78); + + offsets[0] = find_inst(memory.exe(), "\xF3\x0F\x58\xD0"sv, func_offset, func_offset + 0x80, "activate_crush_elevator_hack"); + if (offsets[0] == 0) + return; + + offsets[0] += 4; // pattern size + offsets[1] = find_inst(memory.exe(), "\xEB*\x0F\x57\xD2"sv, offsets[0], offsets[0] + 0xF0, "activate_crush_elevator_hack"); + if (offsets[1] == 0) + return; + + offsets[1] += 5; // pattern size + offsets[2] = find_inst(memory.exe(), "\xF3\x0F\x58\xC1"sv, offsets[1], offsets[1] + 0x40, "activate_crush_elevator_hack"); + if (offsets[2] == 0) + return; + + offsets[2] += 4; // pattern size + } + + if (activate) + { + write_mem_recoverable("activate_crush_elevator_hack", memory.at_exe(offsets[0]), "\x0f\x2e\x90\x30\x01\x00\x00"sv, true); // ucomiss xmm2,DWORD PTR [rax+0x130] // limit + write_mem_recoverable("activate_crush_elevator_hack", memory.at_exe(offsets[1]), "\xf3\x0f\x10\x9b\x30\x01\x00"sv, true); // movss xmm3,DWORD PTR [rbx+0x130] // limit + write_mem_recoverable("activate_crush_elevator_hack", memory.at_exe(offsets[2]), "\xf3\x0f\x58\x83\x34\x01\x00"sv, true); // addss xmm0,DWORD PTR [rbx+0x134] // speed + } + else + recover_mem("activate_crush_elevator_hack"); +} + +void activate_hundun_hack(bool activate) +{ + /* + * Pointer to Hundun entity is stored in r13 register. which means we need 8 bytes for ucomiss instruction + * but we have 7 available, that's why we jump out to new code with the instruction and back + */ + static size_t offsets[6]; // y_limit, y_limit, bird_head, sneak_head, speed, speed + static char new_code[3][8]; + + if (offsets[0] == 0) + { + 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) + return; + + offsets[0] -= 13; // offset, no good pattern above + offsets[1] = find_inst(memory.exe(), "\x41\x80\x8D\x61\x01\x00\x00\x04"sv, offsets[0], offsets[0] + 0xF40, "activate_hundun_hack"); + if (offsets[1] == 0) + { + offsets[0] = 0; + return; + } + offsets[1] += 8; // pattern size + + offsets[2] = find_inst(memory.exe(), "\xF3\x41\x0F\x58\x45\x7C"sv, offsets[0], offsets[1], "activate_hundun_hack"); + if (offsets[2] == 0) + { + offsets[0] = 0; + return; + } + offsets[2] += 6; // pattern size + + offsets[3] = find_inst(memory.exe(), "\xF3\x41\x0F\x58\x45\x7C"sv, offsets[2], offsets[1], "activate_hundun_hack"); + if (offsets[3] == 0) + { + offsets[0] = 0; + return; + } + offsets[3] += 6; // pattern size + + offsets[4] = find_inst(memory.exe(), "\x83\x7A\x0C\x0E"sv, offsets[1], offsets[1] + 0xC0, "activate_hundun_hack"); + if (offsets[4] == 0) + { + offsets[0] = 0; + return; + } + offsets[4] += 6; // pattern size plus jump + + offsets[5] = find_inst(memory.exe(), "\xF3\x41\x0F"sv, offsets[4], offsets[4] + 0x58, "activate_hundun_hack"); + if (offsets[5] == 0) + { + offsets[0] = 0; + return; + } + offsets[5] += 9; // instruction size (didn't include the whole thing in pattern, very short distance from previous pattern) + + offsets[0] = memory.at_exe(offsets[0]); + offsets[1] = memory.at_exe(offsets[1]); + offsets[2] = memory.at_exe(offsets[2]); + offsets[3] = memory.at_exe(offsets[3]); + offsets[4] = memory.at_exe(offsets[4]); + offsets[5] = memory.at_exe(offsets[5]); + + char old_code[3][8]; + + std::memcpy(old_code[0], (void*)offsets[0], 7); + std::memcpy(old_code[1], (void*)offsets[1], 7); + std::memcpy(old_code[2], (void*)offsets[5], 8); + + const std::string_view patch_code{"\x41\x0F\x2E\xBD\x64\x01\x00\x00"sv}; // ucomiss xmm7,DWORD PTR [r13+0x164] + const std::string_view speed_patch{"\xF3\x41\x0F\x58\x85\x6C\x01\x00\x00"sv}; // addss xmm0,DWORD PTR [r13+0x16C] + + patch_and_redirect(offsets[0], 7, patch_code, true); + patch_and_redirect(offsets[1], 7, patch_code, true); + patch_and_redirect(offsets[5], 8, speed_patch, true); + + std::memcpy(new_code[0], (void*)offsets[0], 7); + std::memcpy(new_code[1], (void*)offsets[1], 7); + std::memcpy(new_code[2], (void*)offsets[5], 8); + + // writing back the old code so we can just use write_mem_recoverable for going from vanilla to the patch + write_mem_prot(offsets[0], std::string_view{&old_code[0][0], &old_code[0][7]}, true); + write_mem_prot(offsets[1], std::string_view{&old_code[1][0], &old_code[1][7]}, true); + write_mem_prot(offsets[5], std::string_view{&old_code[2][0], &old_code[2][8]}, true); + } + + if (activate) + { + static const std::string_view speed_code{"\x49\x8D\x95\x68\x01\x00\x00"sv // lea rdx,[r13+0x168] + "\x66\x2E\x0F\x1F\x84\x00\x00\x00\x00\x00\x90\x90\x90"sv}; // spoiled with space, all nop + + write_mem_recoverable("activate_hundun_hack", offsets[0], std::string_view{&new_code[0][0], &new_code[0][7]}, true); // limit + write_mem_recoverable("activate_hundun_hack", offsets[1], std::string_view{&new_code[1][0], &new_code[1][7]}, true); // limit + write_mem_recoverable("activate_hundun_hack", offsets[5], std::string_view{&new_code[2][0], &new_code[2][8]}, true); // speed for adding to the y_limit + + write_mem_recoverable("activate_hundun_hack", offsets[4], speed_code, true); // speed (for adding to the x position) + + write_mem_recoverable("activate_hundun_hack", offsets[2], "\x0F\x2E\xB8\x70\x01\x00\x00"sv, true); // ucomiss xmm7,DWORD PTR [rax+0x170] // bird_head + write_mem_recoverable("activate_hundun_hack", offsets[3], "\x0F\x2E\xB8\x74\x01\x00\x00"sv, true); // ucomiss xmm7,DWORD PTR [rax+0x174] // snake head + } + else + recover_mem("activate_hundun_hack"); +} + +void set_boss_door_control_enabled(bool enable) +{ + static size_t offsets[2]; + if (offsets[0] == 0) + { + 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_address() + 0x777, std::nullopt, "set_boss_door_control_enabled"); + if (offsets[1] == 0) + { + offsets[0] = 0; + return; + } + offsets[1] = function_start(memory.at_exe(offsets[1])); + } + if (!enable) + { + write_mem_recoverable("set_boss_door_control_enabled", offsets[0], "\xC3\x90"sv, true); + write_mem_recoverable("set_boss_door_control_enabled", offsets[1], "\xC3\x90"sv, true); + } + else + recover_mem("set_boss_door_control_enabled"); +} + +void set_level_logic_enabled(bool enable) +{ + auto state = HeapBase::get().state(); + static const size_t offset = get_virtual_function_address(state->screen_level, 1); + + if (!enable) + write_mem_recoverable("set_level_logic_enabled", offset, "\xC3\x90"sv, true); + else + recover_mem("set_level_logic_enabled"); +} + +void set_camera_layer_control_enabled(bool enable) +{ + static const size_t offset = get_address("camera_layer_control"); + static const size_t offset2 = get_address("player_behavior_layer_switch"); + + if (enable) + { + recover_mem("set_camera_layer_control"); + } + else + { + write_mem_recoverable("set_camera_layer_control", offset, get_nop(7), true); + write_mem_recoverable("set_camera_layer_control", offset2, get_nop(18), true); + } +} + +void set_start_level_paused(bool enable) +{ + static const size_t offset = get_address("unpause_level"); + if (enable) + write_mem_recoverable("start_level_paused", offset, get_nop(3), true); + else + recover_mem("start_level_paused"); +} + +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; + static uintptr_t jump3; + 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; + + auto& mem = Memory::get(); + layer_offsets[0] = get_address("spawn_liquid_layer"); + + { + auto sound_stuff = get_address("liquid_stream_spawner"); + if (sound_stuff == 0) + return; + + 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-sound stuff"); + 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"); + + { + 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; + + jump2 = get_address("robot_layer_check"); + jump3 = get_address("logic_underwater_bubbles_loop_check"); + if (jump2 == 0 || jump3 == 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 nearby, 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; + uint8_t jump_oppcode2_inverse = actual_layer == 0 ? 0x74 : 0x75; + + 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); + write_mem_prot(jump3, jump_oppcode2_inverse, true); +} diff --git a/src/game_api/game_patches.hpp b/src/game_api/game_patches.hpp index a0aeaa2c5..d0c62db5f 100644 --- a/src/game_api/game_patches.hpp +++ b/src/game_api/game_patches.hpp @@ -1,5 +1,10 @@ #pragma once +#include +#include + +#include "aliases.hpp" + void patch_orbs_limit(); void patch_olmec_kill_crash(); void patch_liquid_OOB(); @@ -8,3 +13,35 @@ void patch_tiamat_kill_crash(); void set_skip_tiamat_cutscene(bool skip); void patch_ushabti_error(); void patch_entering_closed_door_crash(); + +void modify_sparktraps(float angle_increment = 0.015, float distance = 3.0); +float* get_sparktraps_parameters_ptr(); // for UI +void activate_sparktraps_hack(bool activate); +void set_storage_layer(LAYER layer); +void set_kapala_blood_threshold(uint8_t threshold); +void set_kapala_hud_icon(int8_t icon_index); +void set_olmec_phase_y_level(uint8_t phase, float y); +void force_olmec_phase_0(bool b); +void set_ghost_spawn_times(uint32_t normal = 10800, uint32_t cursed = 9000); +void set_time_ghost_enabled(bool b); +void set_time_jelly_enabled(bool b); +void set_camp_camera_bounds_enabled(bool b); +void set_explosion_mask(int32_t mask); +void set_max_rope_length(uint8_t length); +void change_sunchallenge_spawns(std::vector ent_types); +void change_diceshop_prizes(std::vector ent_types); +void change_altar_damage_spawns(std::vector ent_types); +void change_waddler_drop(std::vector ent_types); +void modify_ankh_health_gain(uint8_t max_health, uint8_t beat_add_health); +void change_poison_timer(int16_t frames); +bool disable_floor_embeds(bool disable); +void set_cursepot_ghost_enabled(bool enable); +void set_ending_unlock(ENT_TYPE type); +void activate_tiamat_position_hack(bool activate); +void activate_crush_elevator_hack(bool activate); +void activate_hundun_hack(bool activate); +void set_boss_door_control_enabled(bool enable); +void set_level_logic_enabled(bool enable); +void set_camera_layer_control_enabled(bool enable); +void set_start_level_paused(bool enable); +void set_liquid_layer(LAYER l); diff --git a/src/game_api/heap_base.cpp b/src/game_api/heap_base.cpp new file mode 100644 index 000000000..a7881afe9 --- /dev/null +++ b/src/game_api/heap_base.cpp @@ -0,0 +1,159 @@ +#include "heap_base.hpp" + +#include // for HANDLE, GetCurrentProcessId, GetCurrentThread + +#include // for CreateToolhelp32Snapshot, THREADENTRY32, Thr... +#include // for KPRIORITY, NTSTATUS, CLIENT_ID, THREADINFOCLASS +#include // for ULONG + +#include "logger.h" // for DEBUG +#include "memory.hpp" // for memory_read +#include "script/events.hpp" // for pre_copy_state_event +#include "search.hpp" // for get_address + +constexpr size_t TEB_OFFSET = 0x120; + +HANDLE get_main_thread() +{ + static const auto main_thread = [] + { + HANDLE main_thread_handle = NULL; + + DWORD pid = GetCurrentProcessId(); + auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + auto entry = THREADENTRY32{ + sizeof(THREADENTRY32), + }; + auto keep = Thread32First(snapshot, &entry); + while (keep) + { + if (entry.th32OwnerProcessID == pid) + { + main_thread_handle = OpenThread(THREAD_ALL_ACCESS, 0, entry.th32ThreadID); + break; + } + keep = Thread32Next(snapshot, &entry); + } + + if (main_thread_handle == NULL) + { + DEBUG("Didn't not get the thread. Process id: {}", pid); + } + + return main_thread_handle; + }(); + return main_thread; +} + +typedef struct _THREAD_BASIC_INFORMATION +{ + NTSTATUS ExitStatus; + PVOID TebBaseAddress; + CLIENT_ID ClientId; + KAFFINITY AffinityMask; + KPRIORITY Priority; + KPRIORITY BasePriority; +} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; +size_t* get_thread_heap_base(HANDLE thread) +{ + THREAD_BASIC_INFORMATION tib{}; + using FuncPtr = NTSTATUS(NTAPI*)( + IN HANDLE ThreadHandle, + IN THREADINFOCLASS ThreadInformationClass, + OUT PVOID ThreadInformation, + IN ULONG ThreadInformationLength, + OUT PULONG ReturnLength OPTIONAL); + static const auto NtQueryInformationThread_ptr = reinterpret_cast(GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationThread")); + NtQueryInformationThread_ptr(thread, (_THREADINFOCLASS)0, (&tib), sizeof(THREAD_BASIC_INFORMATION), nullptr); + return (size_t*)(memory_read(((uint64_t*)tib.TebBaseAddress)[11]) + TEB_OFFSET); +} + +HeapBase HeapBase::get_main() +{ + static const auto main_thread = get_main_thread(); + static const uintptr_t* this_thread_heap_base_addr = get_thread_heap_base(main_thread); + return *this_thread_heap_base_addr; +} + +HeapBase HeapBase::get() +{ + thread_local const uintptr_t* this_thread_heap_base_addr = get_thread_heap_base(GetCurrentThread()); + if (this_thread_heap_base_addr == nullptr || *this_thread_heap_base_addr == NULL) // keeping for now just to be sure + return get_main(); + return *this_thread_heap_base_addr; +} + +HeapBase HeapBase::get(uint8_t slot) +{ + if (slot >= MAX_SAVE_SLOTS) + return NULL; + static HeapBase* save_slots = reinterpret_cast(get_address("save_states")); + return *(save_slots + slot); +} + +void HeapClone(HeapBase heap_to, uint64_t heap_container_from) +{ + auto heap_from = memory_read(heap_container_from + 0x88); + HeapBase heap_base_from = reinterpret_cast(heap_from); + pre_copy_state_event(heap_base_from, heap_to); +} + +// Original function params: clone_heap(ThreadStorageContainer to, ThreadStorageContainer from) +// HeapContainer has heap1 and heap2 variables, and some sort of timer, that just increases constantly, I guess to handle the rollback and multi-threaded stuff +// The rest of what HeapContainer has is unknown for now +// After writing to a chosen storage from the content of `from->heap1`, sets `to->heap2` to the newly copied thread storage +void init_heap_clone_hook() +{ + auto heap_clone = get_address("heap_clone"); + // Hook the function after it has chosen a thread storage to write to, and pass it to the hook + size_t heap_clone_redirect_from_addr = heap_clone + 0x65; + const std::string redirect_code = fmt::format( + "\x51" // PUSH RCX + "\x52" // PUSH RDX + "\x41\x50" // PUSH R8 + "\x41\x51" // PUSH R9 + "\x48\x83\xEC\x28" // SUB RSP, 28 // Shadow space + Stack alignment + "\x4C\x89\xC9" // MOV RCX, R9 == heap_to + "\x48\xb8{}" // MOV RAX, &HeapClone + "\xff\xd0" // CALL RAX + "\x48\x83\xC4\x28" // ADD RSP, 28 + "\x41\x59" // POP R9 + "\x41\x58" // POP R8 + "\x5A" // POP RDX + "\x59"sv, // POP RCX + to_le_bytes(&HeapClone)); + + patch_and_redirect(heap_clone_redirect_from_addr, 7, redirect_code, false, 0, false); +} + +void HeapBase::copy_to(HeapBase other) const +{ + if (is_null() || other.is_null()) + return; + + auto fromBaseState = address(); + auto toBaseState = other.address(); + size_t iterIdx = 1; + do + { + size_t copyContent = *(size_t*)((fromBaseState - 8) + iterIdx * 8); + // variable used to fix pointers that point somewhere in the same Thread + size_t diff = toBaseState - fromBaseState; + if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) + { + diff = 0; + } + *(size_t*)(toBaseState + iterIdx * 8 + -8) = diff + copyContent; + + // Almost same code as before, but on the next value, idk why + copyContent = *(size_t*)(fromBaseState + iterIdx * 8); + diff = toBaseState - fromBaseState; + if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) + { + diff = 0; + } + *(size_t*)(toBaseState + iterIdx * 8) = diff + copyContent; + + iterIdx = iterIdx + 2; + } while (iterIdx != 0x400001); +}; diff --git a/src/game_api/heap_base.hpp b/src/game_api/heap_base.hpp new file mode 100644 index 000000000..7d77fa13c --- /dev/null +++ b/src/game_api/heap_base.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include // for size_t +#include // for uint32_t +#include // for free + +struct PRNG; +struct StateMemory; +struct LevelGenSystem; +struct LiquidPhysics; + +struct HeapBase +{ + // get HeapBase from save slots + static HeapBase get(uint8_t slot); + // get local, fallback to main if can't get local + static HeapBase get(); + // use only if you know what you're doing + static HeapBase get_main(); + + bool is_null() const noexcept + { + return ptr == NULL; + } + uintptr_t address() const noexcept + { + return ptr; + } + StateMemory* state() const noexcept + { + if (is_null()) + return nullptr; + + return reinterpret_cast(ptr + GAME_OFFSET::STATE); + } + uint32_t frame_count() const noexcept + { + if (is_null()) + return NULL; + + return *reinterpret_cast(ptr + GAME_OFFSET::FRAME_COUNTER); + } + PRNG* prng() const noexcept + { + if (is_null()) + return nullptr; + + return reinterpret_cast(ptr + GAME_OFFSET::_PRNG); + } + LevelGenSystem* level_gen() const noexcept + { + if (is_null()) + return nullptr; + + return reinterpret_cast(ptr + GAME_OFFSET::LEVEL_GEN); + } + LiquidPhysics* liquid_physics() const noexcept + { + if (is_null()) + return nullptr; + + return reinterpret_cast(ptr + GAME_OFFSET::LIQUID_ENGINE); + } + + void copy_to(HeapBase other) const; + + protected: + HeapBase(uintptr_t addr) noexcept + : ptr(addr){}; + + void free() + { + if (ptr != NULL) + ::free(reinterpret_cast(ptr)); + + ptr = NULL; + } + + private: + uintptr_t ptr{NULL}; + + enum GAME_OFFSET : size_t + { + UNKNOWN1 = 0x8, // - ? + MALLOC = 0x20, // - custom malloc base + FRAME_COUNTER = 0x3D0, // - FRAME_COUNTER + _PRNG = 0x3F0, // - PRNG + STATE = 0x4A0, // - State Memory + LEVEL_GEN = 0xD7B30, // - level gen + LIQUID_ENGINE = 0xD8650, // - liquid physics + UNKNOWN3 = 0x108420, // - some vector? + }; + static const uint8_t MAX_SAVE_SLOTS = 5; + friend class SaveState; +}; + +// Used for objects that are allocated with the game's custom allocator +template +class OnHeapPointer +{ + public: + explicit OnHeapPointer(size_t ptr) + : ptr_(ptr){}; + + T* decode() const // TODO: change to decode_main and decode + { + return reinterpret_cast(ptr_ + HeapBase::get_main().address()); + } + + T* decode_local() const + { + auto lhb = HeapBase::get(); + return reinterpret_cast(ptr_ + lhb.address()); + } + + private: + size_t ptr_; +}; + +void init_heap_clone_hook(); diff --git a/src/game_api/illumination.cpp b/src/game_api/illumination.cpp index 491a1f31d..ae0a64f81 100644 --- a/src/game_api/illumination.cpp +++ b/src/game_api/illumination.cpp @@ -1,13 +1,13 @@ #include "illumination.hpp" -#include "color.hpp" -#include "entity.hpp" -#include "math.hpp" -#include "search.hpp" -#include "state.hpp" -#include "thread_utils.hpp" #include +#include "color.hpp" // for Color +#include "entity.hpp" // for Entity +#include "math.hpp" // for Vec2 +#include "search.hpp" // for get_address +#include "state.hpp" // for get_state_ptr + Illumination* create_illumination(Vec2 pos, Color col, LIGHT_TYPE type, float size, uint8_t light_flags, int32_t uid, LAYER layer) { static size_t offset = get_address("generate_illumination"); @@ -18,6 +18,7 @@ Illumination* create_illumination(Vec2 pos, Color col, LIGHT_TYPE type, float si typedef Illumination* create_illumination_func(custom_vector*, Vec2*, Color, LIGHT_TYPE, float, uint8_t light_flags, int32_t uid, uint8_t layer); static create_illumination_func* cif = (create_illumination_func*)(offset); + // enum_to_layer here does not use offset which you could argue should be used, since this function is comparable with spawn type function auto emitted_light = cif(state->lightsources, &pos, std::move(col), type, size, light_flags, uid, enum_to_layer(layer)); return emitted_light; } @@ -41,14 +42,5 @@ Illumination* create_illumination(Color color, float size, int32_t uid) void refresh_illumination(Illumination* illumination) { - static size_t** heap_offset = (size_t**)get_address("refresh_illumination_heap_offset"); - if (heap_offset == nullptr) - return; - - auto illumination_counter = OnHeapPointer(**heap_offset); - uint32_t* offset = illumination_counter.decode_local(); - if (offset == nullptr) - offset = illumination_counter.decode(); - - illumination->timer = *offset; + illumination->timer = HeapBase::get().frame_count(); } diff --git a/src/game_api/items.hpp b/src/game_api/items.hpp index 421466dce..3bf511541 100644 --- a/src/game_api/items.hpp +++ b/src/game_api/items.hpp @@ -128,6 +128,6 @@ struct Items Player* player(uint8_t index) const { - return players[index]; + return index >= players.size() ? nullptr : players[index]; } }; diff --git a/src/game_api/layer.cpp b/src/game_api/layer.cpp index eae4b6419..e7a999ed6 100644 --- a/src/game_api/layer.cpp +++ b/src/game_api/layer.cpp @@ -9,9 +9,9 @@ #include "entity.hpp" // for Entity, to_id, EntityDB, entity_factory #include "logger.h" // for DEBUG #include "movable.hpp" // for Movable -#include "rpc.hpp" // +#include "rpc.hpp" // for update_liquid_collision_at #include "search.hpp" // for get_address -#include "state.hpp" // for State, StateMemory +#include "state.hpp" // for StateMemory, API struct EntityFactory; @@ -31,8 +31,7 @@ Entity* Layer::spawn_entity(ENT_TYPE id, float x, float y, bool screen, float vx } else if (screen) { - auto& state = State::get(); - std::tie(x, y) = state.click_position(x, y); + std::tie(x, y) = API::click_position(x, y); min_speed_check = 0.04f; if (snap && abs(vx) + abs(vy) <= min_speed_check) { @@ -70,7 +69,8 @@ Entity* Layer::spawn_entity_snap_to_floor(ENT_TYPE id, float x, float y) const float y_center = roundf(y) - 0.5f; const float snapped_y = y_center + type->default_collision_info.rect.hitboxy - type->default_collision_info.rect.offsety; Entity* ent = spawn_entity(id, x, snapped_y, false, 0.0f, 0.0f, false); - if ((type->search_flags & 0x700) == 0) + constexpr auto test_mask = ENTITY_MASK::FLOOR | ENTITY_MASK::BG | ENTITY_MASK::DECORATION; + if (!(type->search_flags & test_mask)) { snap_to_floor(ent, y_center); } @@ -128,7 +128,7 @@ Entity* Layer::get_entity_at(float x, float y, uint32_t search_flags, uint32_t i Entity* Layer::spawn_door(float x, float y, uint8_t w, uint8_t l, uint8_t t) { - auto screen = State::get().ptr()->screen_next; + auto screen = get_state_ptr()->screen_next; Entity* door; switch (screen) { diff --git a/src/game_api/level_api.cpp b/src/game_api/level_api.cpp index eb5fc5ddc..cbba4e492 100644 --- a/src/game_api/level_api.cpp +++ b/src/game_api/level_api.cpp @@ -22,8 +22,9 @@ #include "entities_activefloors.hpp" // #include "entities_items.hpp" // #include "entities_monsters.hpp" // for GHOST_BEHAVIOR, GHOST_BEHAVIOR::MED... -#include "entity.hpp" // for to_id, Entity, get_entity_ptr, Enti... -#include "entity_lookup.hpp" // +#include "entity.hpp" // for Entity, get_entity_ptr, Enti... +#include "entity_db.hpp" // for to_id +#include "entity_lookup.hpp" // for get_entities_overlapping_by_pointer ... #include "layer.hpp" // for Layer, g_level_max_y, g_level_max_x #include "logger.h" // for DEBUG #include "memory.hpp" // for to_le_bytes, write_mem_prot, Execut... @@ -33,7 +34,7 @@ #include "script/events.hpp" // for post_load_screen, pre_load_screen #include "search.hpp" // for get_address #include "spawn_api.hpp" // for pop_spawn_type_flags, push_spawn_ty... -#include "state.hpp" // for StateMemory, State, enum_to_layer +#include "state.hpp" // for StateMemory #include "util.hpp" // for OnScopeExit, trim #include "vtable_hook.hpp" // for hook_vtable @@ -83,7 +84,7 @@ using TileCodeFunc = void(const CommunityTileCode& self, float x, float y, Layer bool is_room_flipped(float x, float y) { - thread_local StateMemory* state_ptr = State::get().ptr_local(); + thread_local StateMemory* state_ptr = HeapBase::get().state(); auto [ix, iy] = state_ptr->level_gen->get_room_index(x, y); return state_ptr->level_gen->flipped_rooms->rooms[ix + iy * 8ull]; } @@ -174,7 +175,7 @@ void g_spawn_punishball_attach(const CommunityTileCode& self, float x, float y, y += static_cast(offset_y); auto do_spawn = [=]() { - std::vector entities_neighbour = get_entities_overlapping_by_pointer({}, 0, x - 0.5f, y - 0.5f, x + 0.5f, y + 0.5f, layer); + std::vector entities_neighbour = get_entities_overlapping_by_pointer({}, ENTITY_MASK::ANY, x - 0.5f, y - 0.5f, x + 0.5f, y + 0.5f, layer); if (!entities_neighbour.empty()) { get_entity_ptr(attach_ball_and_chain(entities_neighbour.front(), -static_cast(offset_x), -static_cast(offset_y))); @@ -459,7 +460,7 @@ std::array g_community_tile_codes{ Olmite* olmite = layer->spawn_entity_snap_to_floor(self.entity_id, x, y)->as(); - std::vector entities_above = get_entities_overlapping_by_pointer({}, 0x4, x - 0.1f, y + 0.9f, x + 0.1f, y + 1.1f, layer); + std::vector entities_above = get_entities_overlapping_by_pointer({}, ENTITY_MASK::MONSTER, x - 0.1f, y + 0.9f, x + 0.1f, y + 1.1f, layer); for (uint32_t uid : entities_above) { if (Entity* ent = get_entity_ptr(uid)) @@ -504,7 +505,7 @@ std::array g_community_tile_codes{ { auto do_spawn = [=]() { - std::vector entities_neighbour = get_entities_overlapping_by_pointer({}, 0, x - 0.5f, y - 1.5f, x + 0.5f, y - 0.5f, layer); + std::vector entities_neighbour = get_entities_overlapping_by_pointer({}, ENTITY_MASK::ANY, x - 0.5f, y - 1.5f, x + 0.5f, y - 0.5f, layer); if (!entities_neighbour.empty()) { layer->spawn_entity_over(self.entity_id, get_entity_ptr(entities_neighbour.front()), 0.0f, 1.0f); @@ -565,7 +566,8 @@ auto g_SafeTestFunc = [](float x, float y, Layer* layer) auto g_PositionTestFunc = [](float x, float y, Layer* layer, uint32_t flags) { uint32_t default_mask = 0x6180; - uint32_t empty_mask = 0x61bf; + ENTITY_MASK empty_mask = ENTITY_MASK::LIQUID | ENTITY_MASK::FLOOR | ENTITY_MASK::PLAYER | ENTITY_MASK::MOUNT | ENTITY_MASK::MONSTER | + ENTITY_MASK::ITEM | ENTITY_MASK::ACTIVEFLOOR | ENTITY_MASK::ROPE | ENTITY_MASK::EXPLOSION; if (flags & (uint32_t)POS_TYPE::DEFAULT) flags = (uint32_t)POS_TYPE::FLOOR | (uint32_t)POS_TYPE::SAFE | (uint32_t)POS_TYPE::EMPTY; @@ -573,7 +575,7 @@ auto g_PositionTestFunc = [](float x, float y, Layer* layer, uint32_t flags) if (flags & (uint32_t)POS_TYPE::WATER || flags & (uint32_t)POS_TYPE::LAVA) { default_mask -= 0x6000; - empty_mask -= 0x6000; + empty_mask = empty_mask ^ ENTITY_MASK::LIQUID; } if (flags & (uint32_t)POS_TYPE::FLOOR) @@ -912,7 +914,7 @@ void handle_tile_code(LevelGenSystem* self, std::uint32_t tile_code, std::uint16 if (tile_code > g_last_tile_code_id && tile_code < g_last_community_tile_code_id) { - auto* layer_ptr = State::get().ptr_local()->layers[layer]; + auto* layer_ptr = HeapBase::get().state()->layers[layer]; const CommunityTileCode& community_tile_code = g_community_tile_codes[tile_code - g_last_tile_code_id - 1]; community_tile_code.func(community_tile_code, x, y, layer_ptr); } @@ -940,6 +942,7 @@ void handle_tile_code(LevelGenSystem* self, std::uint32_t tile_code, std::uint16 if (!g_floor_requiring_entities.empty()) { Entity* floor{nullptr}; + auto state = HeapBase::get().state(); for (auto& pending_entity : g_floor_requiring_entities) { for (const auto& pos : pending_entity.pos) @@ -950,8 +953,7 @@ void handle_tile_code(LevelGenSystem* self, std::uint32_t tile_code, std::uint16 { if (floor == nullptr) { - auto* layer_ptr = State::get().ptr_local()->layers[layer]; - floor = layer_ptr->get_grid_entity_at(x, y); + floor = state->layers[layer]->get_grid_entity_at(x, y); } if (floor != nullptr) @@ -1052,7 +1054,7 @@ void get_room_size(uint16_t room_template, uint32_t& room_width, uint32_t& room_ } void get_room_size(const char* room_template_name, uint32_t& room_width, uint32_t& room_height) { - const auto room_template = State::get().ptr_local()->level_gen->data->get_room_template(room_template_name); + const auto room_template = HeapBase::get().level_gen()->data->get_room_template(room_template_name); if (!room_template) { DEBUG("Unkown room_template name {}", room_template_name); @@ -1088,8 +1090,6 @@ void do_extra_spawns(ThemeInfo* theme, std::uint32_t border_size, std::uint32_t { g_do_extra_spawns_trampoline(theme, border_size, level_width, level_height, layer); - PRNG& prng = PRNG::get_local(); - std::lock_guard lock{g_extra_spawn_logic_providers_lock}; if (!g_extra_spawn_logic_providers.empty()) { @@ -1116,13 +1116,13 @@ void do_extra_spawns(ThemeInfo* theme, std::uint32_t border_size, std::uint32_t } } } - + PRNG* prng = HeapBase::get().prng(); for (ExtraSpawnLogicProviderImpl& provider : g_extra_spawn_logic_providers) { auto& valid_pos = provider.transient_valid_positions; while (!valid_pos.empty() && provider.transient_num_remaining_spawns[layer] > 0) { - const auto random_idx = static_cast(prng.internal_random_index(valid_pos.size(), PRNG::EXTRA_SPAWNS)); + 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]; provider.provider.do_spawn(x, y, layer); @@ -1184,7 +1184,7 @@ using GatherRoomData = void(LevelGenData*, byte, int room_x, int, bool, uint8_t* GatherRoomData* g_gather_room_data_trampoline{nullptr}; void gather_room_data(LevelGenData* tile_storage, byte param_2, int room_idx_x, int room_idx_y, bool hard_level, uint8_t* param_6, uint8_t* param_7, size_t param_8, uint8_t* param_9, uint8_t* param_10, uint8_t* out_room_width, uint8_t* out_room_height) { - const auto* level_gen = State::get().ptr()->level_gen; + const auto* level_gen = HeapBase::get().level_gen(); for (size_t j = 0; j < 2; j++) { if (g_overridden_room_templates[j].has_value()) @@ -1230,7 +1230,7 @@ using SpawnRoomFromTileCodes = void(LevelGenData*, int, int, SingleRoomData*, Si SpawnRoomFromTileCodes* g_spawn_room_from_tile_codes_trampoline{nullptr}; void spawn_room_from_tile_codes(LevelGenData* level_gen_data, int room_idx_x, int room_idx_y, SingleRoomData* front_room_data, SingleRoomData* back_room_data, uint16_t param_6, bool dual_room, uint16_t room_template) { - auto level_gen = State::get().ptr()->level_gen; + auto level_gen = HeapBase::get().level_gen(); std::optional before[2]; for (size_t i = 0; i < 2; i++) @@ -1275,11 +1275,11 @@ TestChance* g_test_chance{nullptr}; bool handle_chance(SpawnInfo* spawn_info) { - auto level_gen_data = State::get().ptr()->level_gen->data; - uint8_t layer = 0; - auto* layer_ptr = State::get().layer(layer); - LevelGenSystem* level_gen = State::get().ptr()->level_gen; + const uint8_t layer = 0; // only handles the front layer, backlayer is hardcoded + auto* layer_ptr = HeapBase::get().state()->layer(layer); + LevelGenSystem* level_gen = HeapBase::get().level_gen(); + auto level_gen_data = level_gen->data; for (const CommunityChance& community_chance : g_community_chances) { if (level_gen->get_procedural_spawn_chance(community_chance.chance_id) != 0 && community_chance.test_func(community_chance, spawn_info->x, spawn_info->y, layer_ptr)) @@ -1614,7 +1614,7 @@ std::uint32_t LevelGenData::register_chance_logic_provider(std::uint32_t chance_ { provider.is_valid = [](float x, float y, uint8_t layer) { - return g_DefaultTestFunc(x, y, State::get().layer(layer)); + return g_DefaultTestFunc(x, y, HeapBase::get().state()->layers[layer]); }; } @@ -1638,7 +1638,7 @@ std::uint32_t LevelGenData::define_extra_spawn(std::uint32_t num_spawns_front_la { provider.is_valid = [](float x, float y, uint8_t layer) { - return g_DefaultTestFunc(x, y, State::get().layer(layer)); + return g_DefaultTestFunc(x, y, HeapBase::get().state()->layers[layer]); }; } @@ -1739,7 +1739,7 @@ uint16_t LevelGenData::get_pretend_room_template(std::uint16_t room_template) co uint32_t ThemeInfo::get_aux_id() const { - thread_local const LevelGenSystem* level_gen_system = State::get().ptr_local()->level_gen; + thread_local const LevelGenSystem* level_gen_system = HeapBase::get().level_gen(); for (size_t i = 0; i < std::size(level_gen_system->themes); i++) { if (level_gen_system->themes[i] == this) @@ -1823,7 +1823,7 @@ Vec2 LevelGenSystem::get_room_pos(uint32_t x, uint32_t y) } std::optional LevelGenSystem::get_room_template(uint32_t x, uint32_t y, uint8_t l) const { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return std::nullopt; @@ -1839,7 +1839,7 @@ std::optional LevelGenSystem::get_room_template(uint32_t x, uint32_t y } bool LevelGenSystem::set_room_template(uint32_t x, uint32_t y, int l, uint16_t room_template) { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return false; @@ -1859,7 +1859,7 @@ bool LevelGenSystem::set_room_template(uint32_t x, uint32_t y, int l, uint16_t r bool LevelGenSystem::is_room_flipped(uint32_t x, uint32_t y) const { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return false; @@ -1868,7 +1868,7 @@ bool LevelGenSystem::is_room_flipped(uint32_t x, uint32_t y) const } bool LevelGenSystem::is_machine_room_origin(uint32_t x, uint32_t y) const { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return false; @@ -1877,7 +1877,7 @@ bool LevelGenSystem::is_machine_room_origin(uint32_t x, uint32_t y) const } bool LevelGenSystem::mark_as_machine_room_origin(uint32_t x, uint32_t y, uint8_t /*l*/) { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return false; @@ -1888,7 +1888,7 @@ bool LevelGenSystem::mark_as_machine_room_origin(uint32_t x, uint32_t y, uint8_t } bool LevelGenSystem::mark_as_set_room(uint32_t x, uint32_t y, uint8_t l, bool is_set_room) { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return false; @@ -1907,7 +1907,7 @@ bool LevelGenSystem::mark_as_set_room(uint32_t x, uint32_t y, uint8_t l, bool is bool LevelGenSystem::set_shop_type(uint32_t x, uint32_t y, uint8_t l, SHOP_TYPE _shop_type) { - auto* state_ptr = State::get().ptr_local(); + auto* state_ptr = HeapBase::get().state(); if (x < 0 || y < 0 || x >= state_ptr->w || y >= state_ptr->h) return false; @@ -1961,7 +1961,7 @@ uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) const LevelChanceDef& this_chances = get_or_emplace_level_chance(data->level_monster_chances, chance_id); if (!this_chances.chances.empty()) { - auto* state = State::get().ptr(); + auto* state = HeapBase::get().state(); if (this_chances.chances.size() >= state->level && state->level > 0) { return this_chances.chances[state->level - 1]; @@ -1982,7 +1982,7 @@ uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) const LevelChanceDef& this_chances = get_or_emplace_level_chance(data->level_trap_chances, chance_id); if (!this_chances.chances.empty()) { - auto* state = State::get().ptr(); + auto* state = HeapBase::get().state(); if (this_chances.chances.size() >= state->level && state->level > 0) { return this_chances.chances[state->level - 1]; @@ -2035,14 +2035,12 @@ bool LevelGenSystem::set_procedural_spawn_chance(uint32_t chance_id, uint32_t in bool default_spawn_is_valid(float x, float y, LAYER layer) { - uint8_t correct_layer = enum_to_layer(layer); - return g_DefaultTestFunc(x, y, State::get().layer(correct_layer)); + return g_DefaultTestFunc(x, y, HeapBase::get().state()->layer(layer)); } bool position_is_valid(float x, float y, LAYER layer, POS_TYPE flags) { - uint8_t correct_layer = enum_to_layer(layer); - return g_PositionTestFunc(x, y, State::get().layer(correct_layer), (uint32_t)flags); + return g_PositionTestFunc(x, y, HeapBase::get().state()->layer(layer), (uint32_t)flags); } void override_next_levels(std::vector next_levels) @@ -2122,7 +2120,7 @@ void grow_vines(LAYER l, uint32_t max_length, AABB area, bool destroy_broken) { area.abs(); - const auto state = State::get().ptr(); + const auto state = HeapBase::get().state(); const static auto grow_vine = to_id("ENT_TYPE_FLOOR_GROWABLE_VINE"); const static auto tree_vine = to_id("ENT_TYPE_FLOOR_VINE_TREE_TOP"); const static auto vine = to_id("ENT_TYPE_FLOOR_VINE"); @@ -2200,7 +2198,7 @@ void grow_poles(LAYER l, uint32_t max_length, AABB area, bool destroy_broken) { area.abs(); - const auto state = State::get().ptr(); + const auto state = HeapBase::get().state(); const static auto grow_pole = to_id("ENT_TYPE_FLOOR_GROWABLE_CLIMBING_POLE"); const static auto pole = to_id("ENT_TYPE_FLOOR_CLIMBING_POLE"); const auto actual_layer = enum_to_layer(l); @@ -2277,7 +2275,7 @@ void grow_poles(LAYER l, uint32_t max_length, AABB area, bool destroy_broken) bool grow_chain_and_blocks() { - const auto state = State::get().ptr(); + const auto state = HeapBase::get().state(); return grow_chain_and_blocks(state->w * 10 + 6, state->h * 8 + 6); } @@ -2291,7 +2289,7 @@ bool grow_chain_and_blocks(uint32_t x, uint32_t y) void do_load_screen() { static auto load_screen_fun = (LoadScreenFun*)get_address("load_screen_func"); - const auto state = State::get().ptr(); + const auto state = HeapBase::get().state(); if (pre_load_screen()) return; load_screen_fun(state, 0, 0); diff --git a/src/game_api/liquid_engine.hpp b/src/game_api/liquid_engine.hpp new file mode 100644 index 000000000..2a0e5bc9c --- /dev/null +++ b/src/game_api/liquid_engine.hpp @@ -0,0 +1,218 @@ +#pragma once + +#include // fpr size_t +#include +#include // for std::list +#include // for pair + +#include "containers/custom_map.hpp" +#include "containers/custom_vector.hpp" +#include "layer.hpp" // for g_level_max_x and g_level_max_y +#include "math.hpp" // for Vec2 + +class Entity; + +struct LiquidPhysicsEngine +{ + bool pause_physics; + uint8_t padding[3]; + int32_t physics_tick_timer; /* unsure */ + int32_t unknown1; + int32_t unknown2; + int8_t unknown3; + int8_t unknown4; + int8_t unknown5; + int8_t unknown6; + int8_t unknown_7; + int8_t unknown8; + int8_t unknown9; + int8_t unknown10; + uint32_t unknown11; + float unknown12; + float blob_size; + float weight; + float unknown15; + uint32_t entity_count; + uint32_t allocated_size; + uint32_t unk23; // padding probably + // this is actually a pre C++11 version of std::list, which is different from current one! + std::pair unk1; // seams to be empty, or have one element 0? + uint32_t resize_value; // used to resize the arrays? + uint32_t unk3b; // padding probably + + // this is actually a pre C++11 version of std::list, which is different from current one! + std::pair liquid_ids; // std::list + // this is actually a pre C++11 version of std::list, which is different from current one! + std::pair unknown44; // std::list all of them are -1 + // this is actually a pre C++11 version of std::list, but the iterators work the same way + std::list::const_iterator* list_liquid_ids; // list of all iterators of liquid_ids? + int32_t unknown45a; // size related for the array above + int32_t unknown45b; // padding + uint32_t* liquid_flags; // array + int32_t unknown47a; // size related for the array above + int32_t unknown47b; // padding + Vec2* entity_coordinates; // array + int32_t unknown49a; // size related for the array above + int32_t unknown49b; // padding + Vec2* entity_velocities; // array + int32_t unknown51a; // size related for the array above + int32_t unknown51b; // padding + std::pair* unknown52; // not sure about the type + std::pair* unknown53; + size_t unknown54; + std::pair* unknown55; + int64_t unknown56; + int64_t unknown57; + int64_t unknown58; + int64_t unknown59; + size_t unknown60; + Entity*** unknown61; // it's actually array of pointers to some struct, but the entity is first in that struct + size_t unknown61a; // stuff for array above + char skip[256]; + float unknown95; // LiquidParam->unknown3 + float cohesion; // LiquidParam->cohesion?, surface tension? setting it to -1 makes the blobs repel each other + float gravity; // LiquidParam->gravity + float unknown96; // LiquidParam->unknown6 + float unknown97a; // LiquidParam->unknown7 + float agitation; // LiquidParam->agitation + float unknown98a; // LiquidParam->unknown9 + float unknown98b; // LiquidParam->unknown10 + float unknown99a; // LiquidParam->unknown11 + float unknown99b; // LiquidParam->unknown12 + float unknown100a; // LiquidParam->unknown13 + float unknown100b; // LiquidParam->unknown14 + float unknown101a; // LiquidParam->unknown15 + float unknown101b; // LiquidParam->unknown16 + float unknown102a; // LiquidParam->unknown17 + float unknown102b; // LiquidParam->unknown18 + float unknown103a; // LiquidParam->unknown19 + int32_t unknown103b; // LiquidParam->unknown20 + float unknown104a; // LiquidParam->unknown21 + int32_t unknown104b; // LiquidParam->unknown22 + float unknown105a; // LiquidParam->unknown23 + int32_t unknown105b; // LiquidParam->unknown24 + size_t unknown106; + size_t unknown107; + int64_t unknown108; + int64_t unknown109; +}; + +struct LiquidPhysicsParams +{ + int32_t shader_type; // ? can also be flags, as for water, any value with bit one is fine + uint8_t unknown2; // shader related, shader id maybe? + uint8_t padding1; + uint8_t padding2; + uint8_t padding3; + float unknown3; + float cohesion; // negative number makes the liquid balls come apart more easily? + float gravity; // negative number to invert gravity + float unknown6; + float unknown7; + float agitation; // is agitation the right word? for me is just how bouncy the liquid is + float unknown9; // starts going nuts at around 2.70, pressure force? it seam to only matter at spawn, when there is a lot of liquid in one place + float unknown10; + float unknown11; + float unknown12; + float unknown13; + float unknown14; + float unknown15; + float unknown16; + float unknown17; + float unknown18; + float unknown19; + uint32_t unknown20; + float unknown21; + uint32_t unknown22; + float unknown23; + uint32_t unknown24; +}; + +struct LiquidTileSpawnData +{ + uint32_t liquid_flags; // 2 - lava_interaction? crashes the game if no lava is present, 3 - pause_physics, 6 - low_agitation?, 7 - high_agitation?, 8 - high_surface_tension?, 9 - low_surface_tension?, 11 - high_bounce?, 12 - low_bounce? + float last_spawn_x; + float last_spawn_y; + float spawn_velocity_x; + float spawn_velocity_y; + uint32_t unknown31; + uint32_t unknown32; + uint32_t unknown33; + size_t unknown34; // MysteryLiquidPointer2 in plugin, contains last spawn entity + size_t unknown35; // DataPointer? seam to get access validation if you change to something + uint32_t liquidtile_liquid_amount; // how much liquid will be spawned from tilecode, 1=1x2, 2=2x3, 3=3x4 etc. + float blobs_separation; + int32_t unknown39; // is the last 4 garbage? seams not accessed + float unknown40; + float unknown41; + uint32_t unknown42; +}; + +struct LiquidPool +{ + LiquidPhysicsParams physics_defaults; + LiquidPhysicsEngine* physics_engine; + LiquidTileSpawnData tile_spawn_data; +}; + +struct LiquidLake +{ + uint32_t position1; + uint32_t position2; + uint32_t position3; + uint32_t lake_type; + 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 + union + { + std::array pools; + struct + { + LiquidPhysicsParams water_physics_defaults; + LiquidPhysicsEngine* water_physics_engine; + LiquidTileSpawnData water_tile_spawn_data; + LiquidPhysicsParams coarse_water_physics_defaults; + LiquidPhysicsEngine* coarse_water_physics_engine; + LiquidTileSpawnData coarse_water_tile_spawn_data; + LiquidPhysicsParams lava_physics_defaults; + LiquidPhysicsEngine* lava_physics_engine; + LiquidTileSpawnData lava_tile_spawn_data; + LiquidPhysicsParams coarse_lava_physics_defaults; + LiquidPhysicsEngine* coarse_lava_physics_engine; + LiquidTileSpawnData coarse_lava_tile_spawn_data; + LiquidPhysicsParams stagnant_lava_physics_defaults; + LiquidPhysicsEngine* stagnant_lava_physics_engine; + 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 + + LiquidAmounts (*liquids_by_third_of_tile)[g_level_max_y * 3][g_level_max_x * 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; // 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; + uint32_t unknown13; + + LiquidPhysicsEngine* get_correct_liquid_engine(ENT_TYPE ent) const; + void remove_liquid_oob(); +}; diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index cac29720a..c7d4eb737 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -3,12 +3,16 @@ #include // for exit #include // for memcpy #include // for equal_to +#include // for LPVOID #include // for operator new #include // for unordered_map, _Umap_traits<>::allocator_type #include // for min, max #include // for vector, _Vector_iterator, _Vector_const_ite... -#include "bucket.hpp" +#include "bucket.hpp" // for Bucket +#include "search.hpp" // for find_after_bundle + +using namespace std::string_literals; ExecutableMemory::ExecutableMemory(std::string_view raw_code) { @@ -94,7 +98,7 @@ size_t function_start(size_t off, uint8_t outside_byte) return off; } -LPVOID alloc_mem_rel32(size_t addr, size_t size) +void* alloc_mem_rel32(size_t addr, size_t size) { const size_t limit_addr = Memory::get().exe_address(); LPVOID new_array = nullptr; diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index 2ead4a9a1..5cc8572b0 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -1,16 +1,11 @@ #pragma once -#include // for GetModuleHandleA, LPVOID #include // for size_t, byte, NULL #include // for int32_t, int64_t, uint32_t, uint64_t, uint8_t #include // for unique_ptr #include // for string, string_literals #include // for string_view -#include "search.hpp" // for find_after_bundle - -using namespace std::string_literals; - class ExecutableMemory { public: @@ -81,7 +76,7 @@ struct Memory ~Memory(){}; }; -[[nodiscard]] LPVOID alloc_mem_rel32(size_t addr, size_t size); +[[nodiscard]] void* 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/online.hpp b/src/game_api/online.hpp index 96abaf3f5..59e6e276e 100644 --- a/src/game_api/online.hpp +++ b/src/game_api/online.hpp @@ -79,6 +79,7 @@ struct OnlineLobby class Online { + // check x64dbg plugin for the current reverse engineer progress public: uint32_t unknown1; uint32_t unknown2; @@ -122,7 +123,11 @@ class Online OnlinePlayer local_player; OnlineLobby lobby; OnlineLobby lobby_dupe; - // some more stuff + + bool is_active() const + { + return lobby.code != 0; + } virtual ~Online() = 0; // 27 virtuals, destructor probably at index 7 diff --git a/src/game_api/prng.cpp b/src/game_api/prng.cpp index 8a933a103..436359ffc 100644 --- a/src/game_api/prng.cpp +++ b/src/game_api/prng.cpp @@ -1,20 +1,5 @@ #include "prng.hpp" -#include "state.hpp" // for State - -PRNG& PRNG::get_main() -{ - const auto& state = State::get(); - static PRNG* prng = (PRNG*)((size_t)state.ptr_main() - 0xb0); - return *prng; -} -PRNG& PRNG::get_local() -{ - const auto& state = State::get(); - PRNG* prng = (PRNG*)((size_t)state.ptr_local() - 0xb0); - return *prng; -} - void PRNG::seed(int64_t seed) { auto next_pair = [useed = static_cast(seed)]() mutable diff --git a/src/game_api/prng.hpp b/src/game_api/prng.hpp index 403e3b17f..a71c56622 100644 --- a/src/game_api/prng.hpp +++ b/src/game_api/prng.hpp @@ -8,13 +8,6 @@ struct PRNG { - PRNG() = delete; - PRNG(const PRNG&) = delete; - PRNG(PRNG&&) = delete; - - static PRNG& get_main(); - static PRNG& get_local(); - /// Same as `seed_prng` void seed(int64_t seed); @@ -113,4 +106,11 @@ struct PRNG std::optional internal_random_int(std::int64_t min, std::int64_t size, PRNG_CLASS type); std::array pairs; + + PRNG() = delete; + PRNG(const PRNG&) = delete; + PRNG& operator=(PRNG const&) = delete; + PRNG(PRNG&&) = delete; + PRNG& operator=(PRNG&&) = delete; + ~PRNG() = delete; }; diff --git a/src/game_api/render_api.cpp b/src/game_api/render_api.cpp index 1562ce428..43b62c3e8 100644 --- a/src/game_api/render_api.cpp +++ b/src/game_api/render_api.cpp @@ -20,7 +20,7 @@ #include "script/events.hpp" // for trigger_vanilla_render_journal_pag... #include "script/lua_backend.hpp" // for ON, ON::RENDER_POST_JOURNAL_PAGE #include "search.hpp" // for get_address -#include "state.hpp" // for State, StateMemory +#include "state.hpp" // for StateMemory #include "strings.hpp" // #include "texture.hpp" // for Texture, get_textures, get_texture @@ -518,7 +518,7 @@ void fetch_texture(Entity* entity, int32_t texture_id) { if (texture_id < -3) { - texture_id = State::get().ptr_local()->current_theme->get_dynamic_texture((DYNAMIC_TEXTURE)texture_id); + texture_id = HeapBase::get().state()->current_theme->get_dynamic_texture((DYNAMIC_TEXTURE)texture_id); } entity->texture = get_textures()->texture_map[texture_id]; } diff --git a/src/game_api/render_api.hpp b/src/game_api/render_api.hpp index e4dea8a3b..5986343e4 100644 --- a/src/game_api/render_api.hpp +++ b/src/game_api/render_api.hpp @@ -15,9 +15,9 @@ #include "color.hpp" // for Color #include "containers/game_unordered_map.hpp" // for game_unordered_map #include "containers/game_vector.hpp" // for game_vector +#include "heap_base.hpp" // for OnHeapPointer #include "math.hpp" // for Quad, AABB (ptr only) #include "texture.hpp" // for Texture -#include "thread_utils.hpp" // for OnHeapPointer struct JournalUI; struct Layer; diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index a21b8f615..d90f22349 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -33,20 +33,22 @@ #include "entity_lookup.hpp" // #include "game_manager.hpp" // #include "game_patches.hpp" // +#include "heap_base.hpp" // for OnHeapPointer, HeapBase #include "illumination.hpp" // #include "items.hpp" // for Items #include "layer.hpp" // for EntityList, EntityList::Range, Layer +#include "liquid_engine.hpp" // for LiquidPhysicsEngine #include "logger.h" // for DEBUG #include "math.hpp" // for AABB #include "memory.hpp" // for write_mem_prot, write_mem_recoverable #include "movable.hpp" // for Movable #include "online.hpp" // for Online #include "particles.hpp" // for ParticleEmitterInfo +#include "prng.hpp" // for PRNG #include "screen.hpp" // #include "search.hpp" // for get_address, find_inst -#include "state.hpp" // for State, get_state_ptr, enum_to_layer +#include "state.hpp" // for get_state_ptr, enum_to_layer #include "state_structs.hpp" // for ShopRestrictedItem, Illumination -#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) @@ -105,7 +107,7 @@ int32_t attach_ball_and_chain(uint32_t uid, float off_x, float off_y) static const auto chain_entity_type = to_id("ENT_TYPE_ITEM_PUNISHCHAIN"); auto pos = entity->abs_position(); - auto* layer_ptr = State::get().layer(entity->layer); + auto* layer_ptr = HeapBase::get().state()->layer(entity->layer); PunishBall* ball = (PunishBall*)layer_ptr->spawn_entity(ball_entity_type, pos.x + off_x, pos.y + off_y, false, 0.0f, 0.0f, false); @@ -202,7 +204,7 @@ void move_liquid_abs(uint32_t uid, float x, float y, float vx, float vy) auto entity = get_entity_ptr(uid)->as(); if (entity) { - auto liquid_engine = State::get().get_correct_liquid_engine(entity->type->id); + auto liquid_engine = HeapBase::get().liquid_physics()->get_correct_liquid_engine(entity->type->id); if (liquid_engine) { liquid_engine->entity_coordinates[*entity->liquid_id] = {x, y}; @@ -211,56 +213,6 @@ void move_liquid_abs(uint32_t uid, float x, float y, float vx, float vy) } } -uint32_t get_entity_flags(uint32_t uid) -{ - auto ent = get_entity_ptr(uid); - if (ent) - return ent->flags; - return 0; -} - -void set_entity_flags(uint32_t uid, uint32_t flags) -{ - auto ent = get_entity_ptr(uid); - if (ent) - ent->flags = flags; -} - -uint32_t get_entity_flags2(uint32_t uid) -{ - auto ent = get_entity_ptr(uid); - if (ent) - return ent->more_flags; - return 0; -} - -void set_entity_flags2(uint32_t uid, uint32_t flags) -{ - auto ent = get_entity_ptr(uid); - if (ent) - ent->more_flags = flags; -} - -int get_entity_ai_state(uint32_t uid) -{ - auto ent = get_entity_ptr(uid)->as(); - if (ent && ent->is_movable()) - return ent->move_state; - return 0; -} - -uint32_t get_level_flags() -{ - auto& state = State::get(); - return state.flags(); -} - -void set_level_flags(uint32_t flags) -{ - auto& state = State::get(); - state.set_flags(flags); -} - ENT_TYPE get_entity_type(uint32_t uid) { auto entity = get_entity_ptr(uid); @@ -270,33 +222,17 @@ ENT_TYPE get_entity_type(uint32_t uid) return UINT32_MAX; // TODO: shouldn't this be 0? } -std::vector get_players(StateMemory* state) -{ - state = state != nullptr - ? state - : State::get().ptr(); - - std::vector found; - for (uint8_t i = 0; i < MAX_PLAYERS; i++) - { - auto player = state->items->player(i); - if (player) - found.push_back((Player*)player); - } - return found; -} - std::tuple screen_aabb(float left, float top, float right, float bottom) { - auto [sx1, sy1] = State::screen_position(left, top); - auto [sx2, sy2] = State::screen_position(right, bottom); + auto [sx1, sy1] = API::screen_position(left, top); + auto [sx2, sy2] = API::screen_position(right, bottom); return std::tuple{sx1, sy1, sx2, sy2}; } float screen_distance(float x) { - auto a = State::screen_position(0, 0); - auto b = State::screen_position(x, 0); + auto a = API::screen_position(0, 0); + auto b = API::screen_position(x, 0); return b.x - a.x; } @@ -315,26 +251,6 @@ std::vector filter_entities(std::vector entities, std::funct return filtered_entities; } -void set_door_target(uint32_t uid, uint8_t w, uint8_t l, uint8_t t) -{ - if (auto door = get_entity_ptr(uid)->as()) - { - door->world = w; - door->level = l; - door->theme = t; - door->special_door = true; - } -} - -std::tuple get_door_target(uint32_t uid) -{ - auto door = get_entity_ptr(uid)->as(); - if (door == nullptr || !door->special_door) - return std::make_tuple((uint8_t)0, (uint8_t)0, (uint8_t)0); - - return std::make_tuple(door->world, door->level, door->theme); -} - void set_contents(uint32_t uid, ENT_TYPE item_entity_type) { Entity* container = get_entity_ptr(uid); @@ -367,66 +283,6 @@ void entity_remove_item(uint32_t uid, uint32_t item_uid, std::optional che entity->remove_item(entity_item, check_autokill.value_or(true)); } -void lock_door_at(float x, float y) -{ - std::vector items = get_entities_at({}, 0, x, y, LAYER::FRONT, 1); - for (auto id : items) - { - Entity* door = get_entity_ptr(id); - if (door->type->id >= to_id("ENT_TYPE_FLOOR_DOOR_ENTRANCE") && door->type->id <= to_id("ENT_TYPE_FLOOR_DOOR_EGGPLANT_WORLD")) - { - door->flags &= ~(1U << 19); - door->flags |= 1U << 21; - } - else if ( - door->type->id == to_id("ENT_TYPE_BG_DOOR") || door->type->id == to_id("ENT_TYPE_BG_DOOR_COG") || - door->type->id == to_id("ENT_TYPE_BG_DOOR_EGGPLANT_WORLD")) - { - door->animation_frame &= ~1U; - } - } -} - -void unlock_door_at(float x, float y) -{ - std::vector items = get_entities_at({}, 0, x, y, LAYER::FRONT, 1); - for (auto id : items) - { - Entity* door = get_entity_ptr(id); - if (door->type->id >= to_id("ENT_TYPE_FLOOR_DOOR_ENTRANCE") && door->type->id <= to_id("ENT_TYPE_FLOOR_DOOR_EGGPLANT_WORLD")) - { - door->flags |= 1U << 19; - door->flags &= ~(1U << 21); - } - else if ( - door->type->id == to_id("ENT_TYPE_BG_DOOR") || door->type->id == to_id("ENT_TYPE_BG_DOOR_COG") || - door->type->id == to_id("ENT_TYPE_BG_DOOR_EGGPLANT_WORLD")) - { - door->animation_frame |= 1U; - } - } -} - -uint32_t get_frame_count_main() -{ - auto& state = State::get(); - return state.get_frame_count_main(); -} -uint32_t get_frame_count() -{ - auto& state = State::get(); - return state.get_frame_count(); -} - -void carry(uint32_t mount_uid, uint32_t rider_uid) -{ - auto mount = get_entity_ptr(mount_uid)->as(); - auto rider = get_entity_ptr(rider_uid)->as(); - if (mount == nullptr || rider == nullptr) - return; - mount->carry(rider); -} - void kill_entity(uint32_t uid, std::optional destroy_corpse) { Entity* ent = get_entity_ptr(uid); @@ -441,180 +297,6 @@ void destroy_entity(uint32_t uid) ent->destroy(); } -void apply_entity_db(uint32_t uid) -{ - Entity* ent = get_entity_ptr(uid); - if (ent != nullptr) - ent->apply_db(); -} - -void flip_entity(uint32_t uid) -{ - Entity* ent = get_entity_ptr(uid); - if (ent == nullptr) - return; - ent->flags = flipflag(ent->flags, 17); - if (ent->items.size > 0) - { - for (auto item : ent->items.entities()) - { - item->flags = flipflag(item->flags, 17); - } - } -} - -void set_camera_position(float cx, float cy) -{ - auto& state = State::get(); - state.set_camera_position(cx, cy); -} - -void warp(uint8_t world, uint8_t level, uint8_t theme) -{ - auto& state = State::get(); - state.warp(world, level, theme); -} - -void set_seed(uint32_t seed) -{ - auto& state = State::get(); - state.set_seed(seed); -} - -void set_arrowtrap_projectile(ENT_TYPE regular_entity_type, ENT_TYPE poison_entity_type) -{ - static const auto arrowtrap = get_address("arrowtrap_projectile"); - static const auto poison_arrowtrap = get_address("poison_arrowtrap_projectile"); - write_mem_prot(arrowtrap, regular_entity_type, true); - write_mem_prot(poison_arrowtrap, poison_entity_type, true); -} - -float* g_sparktrap_parameters{nullptr}; -void modify_sparktraps(float angle_increment, float distance) -{ - if (g_sparktrap_parameters == nullptr) - { - static const auto offset = get_address("sparktrap_angle_increment") + 4; - - if (memory_read(offset - 1) == 0x89) // check if sparktraps_hack is active - return; - - const int32_t distance_offset = 0xF1; - g_sparktrap_parameters = (float*)alloc_mem_rel32(offset + 4, sizeof(float) * 2); - if (!g_sparktrap_parameters) - return; - - int32_t rel = static_cast((size_t)g_sparktrap_parameters - (offset + 4)); - write_mem_prot(offset, rel, true); - write_mem_prot(offset + distance_offset, (int32_t)(rel - distance_offset + sizeof(float)), true); - } - *g_sparktrap_parameters = angle_increment; - *(g_sparktrap_parameters + 1) = distance; -} - -float* get_sparktraps_parameters_ptr() // only for the UI -{ - return g_sparktrap_parameters; -} - -void activate_sparktraps_hack(bool activate) -{ - if (activate) - { - static const auto offset = get_address("sparktrap_angle_increment"); - const int32_t distance_offset = 0xF1; - - write_mem_recoverable("sparktraps_hack", offset, "\xF3\x0F\x58\x89\x6C\x01\x00\x00"sv, true); - write_mem_recoverable("sparktraps_hack", offset + distance_offset, "\xF3\x0F\x10\xB9\x70\x01\x00\x00"sv, true); - } - else - { - recover_mem("sparktraps_hack"); - } -} - -void set_storage_layer(LAYER layer) -{ - static const auto storage_layer = get_address("storage_layer"); - if (layer == LAYER::FRONT || layer == LAYER::BACK) - write_mem_prot(storage_layer, 0x1300 + 8 * (uint8_t)layer, true); -} - -void set_kapala_blood_threshold(uint8_t threshold) -{ - static const auto kapala_blood_threshold = get_address("kapala_blood_threshold"); - write_mem_prot(kapala_blood_threshold, threshold, true); -} - -void set_kapala_hud_icon(int8_t icon_index) -{ - static const size_t instruction_offset = get_address("kapala_hud_icon"); - static const size_t icon_index_offset = instruction_offset + 0x12; - static const uint32_t distance = static_cast(icon_index_offset - (instruction_offset + 7)); - - if (icon_index < 0) // reset to original - { - write_mem_prot(instruction_offset + 2, 0x00013089, true); - } - else - { - // Instead of loading the value from KapalaPowerup:amount_of_blood (the instruction pointed at by instruction_offset) - // we overwrite this with an instruction that loads a byte located a bit after the current function. - // So you need to assemble `movzx ,BYTE PTR [rip+]` - write_mem_prot(instruction_offset + 2, {0x0d}, true); - write_mem_prot(instruction_offset + 3, distance, true); - if (icon_index > 6) - { - icon_index = 6; - } - write_mem_prot(icon_index_offset, icon_index, true); - } -} - -void set_blood_multiplication(uint32_t /*default_multiplier*/, uint32_t vladscape_multiplier) -{ - // Due to changes in 1.23.x, the default multiplier is automatically vlads - 1. - static const auto blood_multiplication = get_address("blood_multiplication"); - write_mem_prot(blood_multiplication, vladscape_multiplier, true); -} - -std::vector read_prng() -{ - auto& state = State::get(); - return state.read_prng(); -} - -void pick_up(uint32_t who_uid, uint32_t what_uid) -{ - Movable* ent = (Movable*)get_entity_ptr(who_uid); - Movable* item = (Movable*)get_entity_ptr(what_uid); - if (ent != nullptr && item != nullptr) - { - ent->pick_up(item); - } -} - -void drop(uint32_t who_uid, std::optional what_uid) -{ - auto ent = get_entity_ptr(who_uid); - if (ent == nullptr) - return; - - if (!ent->is_movable()) // game would probably use the is_player_or_monster function here, since they are the only ones who should be able to hold something - return; - - auto mov = ent->as(); - if (what_uid.has_value()) // should we handle what_uid = -1 the same way? - { - auto item = get_entity_ptr(what_uid.value()); - if (item == nullptr) - return; - if (item->overlay != mov && mov->holding_uid == what_uid) - return; - } - mov->drop(); -} - void unequip_backitem(uint32_t who_uid) { static const size_t offset = get_address("unequip"); @@ -657,114 +339,6 @@ int32_t worn_backitem(uint32_t who_uid) return -1; } -void set_olmec_phase_y_level(uint8_t phase, float y) -{ - // Sets the Y-level Olmec changes phases. The defaults are : - // - phase 1 (bombs) = 100 - // - phase 2 (ufos) = 83 - // Olmecs checks phases in order! The means if you want ufo's from the start - // you have to put both phase 1 and 2 at e.g. level 199 - // If you want to make Olmec stay in phase 0 (stomping) all the time, you can just set - // the phase 1 y level to 70. Don't set it too low, from 1.25.0 onwards, Olmec's stomp - // activation distance seems to be related to the y-level trigger point. - static size_t phase1_offset; - if (phase1_offset == 0) - { - // from 1.23.x onwards, there are now two instructions per phase that reference the y-level float - const size_t phase_1_instruction_a = get_address("olmec_transition_phase_1_y_level"); - const size_t phase_1_instruction_b = phase_1_instruction_a + 0xd; - - const size_t phase_2_instruction_a = get_address("olmec_transition_phase_2_y_level"); - const size_t phase_2_instruction_b = phase_2_instruction_a + 0x11; - phase1_offset = (size_t)alloc_mem_rel32(phase_2_instruction_b + 4, sizeof(float) * 2); - if (!phase1_offset) - return; - - auto phase2_offset = phase1_offset + 0x4; - - // write the default values to our new floats - write_mem_prot(phase1_offset, 100.0f, true); - write_mem_prot(phase2_offset, 83.0f, true); - - // calculate the distances between our floats and the movss instructions - auto distance_1_a = static_cast(phase1_offset - phase_1_instruction_a); - auto distance_1_b = static_cast(phase1_offset - phase_1_instruction_b); - auto distance_2_a = static_cast(phase2_offset - phase_2_instruction_a); - auto distance_2_b = static_cast(phase2_offset - phase_2_instruction_b); - - // overwrite the movss instructions to load our floats - write_mem_prot(phase_1_instruction_a - 4, distance_1_a, true); - write_mem_prot(phase_1_instruction_b - 4, distance_1_b, true); - write_mem_prot(phase_2_instruction_a - 4, distance_2_a, true); - write_mem_prot(phase_2_instruction_b - 4, distance_2_b, true); - } - - if (phase == 1) - { - *(float*)phase1_offset = y; - } - else if (phase == 2) - { - *(float*)(phase1_offset + sizeof(float)) = y; - } -} - -void force_olmec_phase_0(bool b) -{ - static const size_t offset = get_address("olmec_transition_phase_1"); - - if (b) - write_mem_recoverable("force_olmec_phase_0", offset, "\xEB\x2E"s, true); // jbe -> jmp - else - recover_mem("force_olmec_phase_0"); -} - -void set_ghost_spawn_times(uint32_t normal, uint32_t cursed) -{ - static const auto ghost_spawn_time = get_address("ghost_spawn_time"); - static const auto ghost_spawn_time_cursed_p1 = get_address("ghost_spawn_time_cursed_player1"); - static const auto ghost_spawn_time_cursed_p2 = get_address("ghost_spawn_time_cursed_player2"); - static const auto ghost_spawn_time_cursed_p3 = get_address("ghost_spawn_time_cursed_player3"); - static const auto ghost_spawn_time_cursed_p4 = get_address("ghost_spawn_time_cursed_player4"); - - write_mem_prot(ghost_spawn_time, normal, true); - write_mem_prot(ghost_spawn_time_cursed_p1, cursed, true); - write_mem_prot(ghost_spawn_time_cursed_p2, cursed, true); - write_mem_prot(ghost_spawn_time_cursed_p3, cursed, true); - write_mem_prot(ghost_spawn_time_cursed_p4, cursed, true); -} - -void set_time_ghost_enabled(bool b) -{ - static size_t offset_trigger = 0; - static size_t offset_toast_trigger = 0; - if (offset_trigger == 0) - { - 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))); - } - if (b) - { - recover_mem("set_time_ghost_enabled"); - } - else - { - write_mem_recoverable("set_time_ghost_enabled", offset_trigger, "\xC3\x90\x90\x90"s, true); - write_mem_recoverable("set_time_ghost_enabled", offset_toast_trigger, "\xC3\x90\x90\x90"s, true); - } -} - -void set_time_jelly_enabled(bool b) -{ - 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"); - else - write_mem_recoverable("set_time_jelly_enabled", offset, "\xC3\x90\x90\x90"s, true); -} - bool is_inside_active_shop_room(float x, float y, LAYER layer) { // this functions just calculates the room index and then loops thru state->room_owners->owned_rooms and compares the room index @@ -785,56 +359,10 @@ bool is_inside_shop_zone(float x, float y, LAYER layer) // if it doesn't jump there is a bunch of coordinate checks but also state.presence_flags, flipped rooms ... static const size_t offset = get_address("coord_inside_shop_zone"); - auto state = State::get().ptr(); // the game gets level gen from heap pointer and we always get it from state, not sure if it matters + auto level_gen = HeapBase::get().level_gen(); typedef bool coord_inside_shop_zone_func(LevelGenSystem*, uint32_t layer, float x, float y); coord_inside_shop_zone_func* ciszf = (coord_inside_shop_zone_func*)(offset); - return ciszf(state->level_gen, enum_to_layer(layer), x, y); -} - -void set_journal_enabled(bool b) -{ - get_journal_enabled() = b; -} - -void set_camp_camera_bounds_enabled(bool b) -{ - static const size_t offset = get_address("enforce_camp_camera_bounds"); - if (b) - recover_mem("camp_camera_bounds"); - else - write_mem_recoverable("camp_camera_bounds", offset, "\xC3\x90\x90"s, true); -} - -void set_explosion_mask(int32_t mask) -{ - static const size_t addr = get_address("explosion_mask"); - if (mask == -1) - recover_mem("explosion_mask"); - else - write_mem_recoverable("explosion_mask", addr, mask, true); -} - -void set_max_rope_length(uint8_t length) -{ - uint32_t length_32 = length; - static const auto attach_thrown_rope = get_address("attach_thrown_rope_to_background"); - static const auto process_ropes_one = get_address("process_ropes_one"); - static const auto process_ropes_two = get_address("process_ropes_two"); - static const auto process_ropes_three = get_address("process_ropes_three"); - - // there's four instances where the max (default=6) is used - - // 1) When throwing a rope and it attaches to the background, the initial entity is - // given a start value in its segment_nr_inverse variable - write_mem_prot(attach_thrown_rope, length_32, true); - - // 2) and 3) at the top of the rope processing function are two comparisons to the max - write_mem_prot(process_ropes_one, length, true); - write_mem_prot(process_ropes_two, length, true); - - // 4) in the same function at the end of the little loop of process_ropes_two is a comparison to n-1 - uint8_t length_minus_one_8 = length - 1; - write_mem_prot(process_ropes_three, length_minus_one_8, true); + return ciszf(level_gen, enum_to_layer(layer), x, y); } uint8_t get_max_rope_length() @@ -936,233 +464,6 @@ uint32_t waddler_entity_type_in_slot(uint8_t slot) return 0; } -void enter_door(int32_t player_uid, int32_t door_uid) -{ - auto player = get_entity_ptr(player_uid); - auto door = get_entity_ptr(door_uid)->as(); - if (player == nullptr || door == nullptr) - return; - - door->enter(player); -} - -void change_sunchallenge_spawns(std::vector ent_types) -{ - // [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) - { - offset = get_address("sun_challenge_generator_ent_types"); - - // just so we can recover the oryginal later - save_mem_recoverable("sunchallenge_spawn", offset, 14, true); - } - 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) - { - 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(table_offset + 4, data_size); - if (new_array) - { - 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); - - 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); - - if (was_edited_before) - VirtualFree(old_types_array, 0, MEM_RELEASE); - } -} - -void change_diceshop_prizes(std::vector ent_types) -{ - static const auto offset = get_address("dice_shop_prizes_id_roll"); - static const auto array_offset = get_address("dice_shop_prizes"); - ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(array_offset) + array_offset + 4); - bool original_instr = (memory_read(offset) == 0x89); - - if (ent_types.size() > 255 || ent_types.size() < 6) // has to be min 6 as the game needs 6 uniqe item ids for prize_dispenser - { - if (!ent_types.size()) - { - if (!original_instr) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - recover_mem("diceshop_prizes"); - } - return; - } - - if ((original_instr && ent_types.size() == 25) || // if it's the unchanged instruction and we set the same number of ent_type's - (!original_instr && memory_read(offset + 5) == ent_types.size())) // or new instruction but the same size - { - for (unsigned int i = 0; i < ent_types.size(); ++i) - write_mem_recoverable("diceshop_prizes", (size_t)&old_types_array[i], ent_types[i], true); - - return; - } - - const auto data_size = ent_types.size() * sizeof(ENT_TYPE); - ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(array_offset + 4, data_size); - - if (new_array) - { - if (!original_instr) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - memcpy(new_array, ent_types.data(), data_size); - int32_t rel = static_cast((size_t)new_array - (array_offset + 4)); - write_mem_recoverable("diceshop_prizes", array_offset, rel, true); - - if (original_instr) - { - std::string new_code = fmt::format("\x50\x31\xC0\x41\xB3{}\x88\xD0\x41\xF6\xF3\x88\xE2\x58"sv, to_le_bytes((uint8_t)ent_types.size())); - // push rax - // xor eax, eax - // mov r11b, (size) - // mov al, dl - // divb r11b - // mov dl, ah - // pop rax - write_mem_recoverable("diceshop_prizes", offset, new_code, true); - } - else - { - write_mem_recoverable("diceshop_prizes", offset + 5, (uint8_t)ent_types.size(), true); - } - } -} - -void change_altar_damage_spawns(std::vector ent_types) -{ - if (ent_types.size() > 255) - return; - - static const auto array_offset = get_address("altar_break_ent_types"); - ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(array_offset) + array_offset + 4); - const auto code_offset = array_offset + 0xDD; - const auto instruction_shr = array_offset + 0x13D; - const auto instruction_to_modifiy = array_offset + 0x204; - const auto original_instr = (memory_read(instruction_shr) == 0x41); - if (ent_types.empty()) - { - if (!original_instr) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - recover_mem("altar_damage_spawn"); - return; - } - if (!original_instr && memory_read(code_offset + 2) == ent_types.size()) - { - // original array is used for something else as well, so i never edit that content - for (uint32_t i = 0; i < ent_types.size(); ++i) - write_mem_recoverable("altar_damage_spawn", (size_t)&old_types_array[i], ent_types[i], true); - - return; - } - const auto data_size = ent_types.size() * sizeof(ENT_TYPE); - ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(array_offset + 4, data_size); - if (new_array) - { - if (!original_instr) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - memcpy(new_array, ent_types.data(), data_size); - int32_t rel = static_cast((size_t)new_array - (array_offset + 4)); - write_mem_recoverable("altar_damage_spawn", array_offset, rel, true); - - if (original_instr) - { - std::string new_code = fmt::format("\x41\xB1{}\x48\xC1\xE8\x38\x41\xF6\xF1\x49\x89\xC1"sv, to_le_bytes((uint8_t)ent_types.size())); - // mov R9b, (size) - // shr RAX, 0x38 - // divb R9b - // mov R9, RAX - write_mem_recoverable("altar_damage_spawn", code_offset, new_code, true); - write_mem_recoverable("altar_damage_spawn", instruction_shr, "\x49\xC1\xE9\x08"sv, true); // shr r9,0x8 - write_mem_recoverable("altar_damage_spawn", instruction_to_modifiy, (uint8_t)0x8C, true); // r9+r12 => r12+r9*4 - } - else - { - write_mem_recoverable("altar_damage_spawn", code_offset + 2, (uint8_t)ent_types.size(), true); - } - } -} - -void change_waddler_drop(std::vector ent_types) -{ - static bool modified = false; - - static const auto offset = get_address("waddler_drop_size"); - static const auto array_offset = get_address("waddler_drop_array"); - ENT_TYPE* old_types_array = (ENT_TYPE*)(memory_read(array_offset) + array_offset + 4); - - if (ent_types.size() > 255 || ent_types.size() < 1) - { - if (!ent_types.size()) - { - if (modified) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - recover_mem("waddler_drop"); - modified = false; - } - return; - } - - if ((!modified && ent_types.size() == 3) || // if it's the unchanged instruction and we set the same number of ent_type's - (modified && memory_read(offset) == ent_types.size())) // or new instruction but the same size - { - for (unsigned int i = 0; i < ent_types.size(); ++i) - write_mem_recoverable("waddler_drop", (size_t)&old_types_array[i], ent_types[i], true); - - return; - } - - const auto data_size = ent_types.size() * sizeof(ENT_TYPE); - ENT_TYPE* new_array = (ENT_TYPE*)alloc_mem_rel32(array_offset + 4, data_size); - - if (new_array) - { - if (modified) - VirtualFree(old_types_array, 0, MEM_RELEASE); - - memcpy(new_array, ent_types.data(), data_size); - int32_t rel = static_cast((size_t)new_array - (array_offset + 4)); - write_mem_recoverable("waddler_drop", array_offset, rel, true); - write_mem_recoverable("waddler_drop", offset, (uint8_t)ent_types.size(), true); - modified = true; - } -} - void poison_entity(int32_t entity_uid) { auto ent = get_entity_ptr(entity_uid); @@ -1174,68 +475,14 @@ void poison_entity(int32_t entity_uid) } } -void modify_ankh_health_gain(uint8_t health, uint8_t beat_add_health) -{ - static size_t offsets[4]; - static const auto size_minus_one = get_address("ankh_health"); - if (!health) - { - recover_mem("ankh_health"); - return; - } - if (size_minus_one && beat_add_health) - { - if (!offsets[0]) - { - 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"); - offsets[1] = find_inst(memory.exe(), "\x41\x80\xBF\x17\x01\x00\x00"sv, offsets[0] + 7, limit_size, "ankh_health_gain_2"); - offsets[2] = find_inst(memory.exe(), "\x0F\x42\xCA\x83\xC0"sv, offset, limit_size, "ankh_health_gain_3"); - offsets[3] = find_inst(memory.exe(), "\x8A\x83\x17\x01\x00\x00\x3C"sv, offset, std::nullopt, "ankh_health_gain_4"); // this is some bs - if (!offsets[0] || !offsets[1] || !offsets[2] || !offsets[3]) - { - offsets[0] = 0; - return; - } - offsets[0] = memory.at_exe(offsets[0] + 7); // add pattern size - offsets[1] = memory.at_exe(offsets[1] + 7); - offsets[2] = memory.at_exe(offsets[2] + 5); - offsets[3] = memory.at_exe(offsets[3] + 7); - } - const uint8_t game_maxhp = memory_read(offsets[2] - 14); - if (health > game_maxhp) - health = game_maxhp; - - if (health % beat_add_health == 0) - { - write_mem_recoverable("ankh_health", size_minus_one, (uint8_t)(health - 1), true); - write_mem_recoverable("ankh_health", offsets[0], health, true); - write_mem_recoverable("ankh_health", offsets[1], health, true); - write_mem_recoverable("ankh_health", offsets[2], beat_add_health, true); - if (health < 4) - { - write_mem_recoverable("ankh_health", offsets[3], (uint8_t)0, true); - } - else - { - if (memory_read(offsets[3]) != 3) - recover_mem("ankh_health", offsets[3]); - } - } - } -} - void move_grid_entity(int32_t uid, float x, float y, LAYER layer) { if (auto entity = get_entity_ptr(uid)) { - auto& state = State::get(); + auto state = HeapBase::get().state(); Vec2 offset; const auto actual_layer = enum_to_layer(layer, offset); - state.layer(entity->layer)->move_grid_entity(entity, offset.x + x, offset.y + y, state.layer(actual_layer)); + state->layer(entity->layer)->move_grid_entity(entity, offset.x + x, offset.y + y, state->layers[actual_layer]); entity->detach(false); entity->x = offset.x + x; @@ -1248,19 +495,18 @@ void destroy_grid(int32_t uid) { if (auto entity = get_entity_ptr(uid)) { - auto& state = State::get(); - state.layer(entity->layer)->destroy_grid_entity(entity); + HeapBase::get().state()->layer(entity->layer)->destroy_grid_entity(entity); } } void destroy_grid(float x, float y, LAYER layer) { - auto& state = State::get(); + auto state = HeapBase::get().state(); uint8_t actual_layer = enum_to_layer(layer); - if (Entity* entity = state.layer(actual_layer)->get_grid_entity_at(x, y)) + if (Entity* entity = state->layers[actual_layer]->get_grid_entity_at(x, y)) { - state.layer(entity->layer)->destroy_grid_entity(entity); + state->layer(entity->layer)->destroy_grid_entity(entity); } } @@ -1283,7 +529,7 @@ void add_item_to_shop(int32_t item_uid, int32_t shop_owner_uid) { if (owner->type->id == it) // TODO: check what happens if it's not room owner/shopkeeper { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); item->flags = setflag(item->flags, 23); // shop item item->flags = setflag(item->flags, 20); // Enable button prompt (flag is probably: show dialogs and other fx) state->layers[item->layer]->spawn_entity_over(to_id("ENT_TYPE_FX_SALEICON"), item, 0, 0); @@ -1297,22 +543,6 @@ void add_item_to_shop(int32_t item_uid, int32_t shop_owner_uid) } } -void change_poison_timer(int16_t frames) -{ - static const size_t offset_first = get_address("first_poison_tick_timer_default"); - static const size_t offset_subsequent = get_address("subsequent_poison_tick_timer_default"); - - if (frames == -1) - { - recover_mem("change_poison_timer"); - } - else - { - write_mem_recoverable("change_poison_timer", offset_first, frames, true); - write_mem_recoverable("change_poison_timer", offset_subsequent, frames, true); - } -} - void set_adventure_seed(int64_t first, int64_t second) { static const size_t offset = get_address("adventure_seed"); @@ -1327,7 +557,7 @@ std::pair get_adventure_seed(std::optional run_start) auto bucket = Bucket::get(); if (bucket->adventure_seed.first != 0) return bucket->adventure_seed; - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); auto current = get_adventure_seed(false); for (uint8_t i = 0; i < state->level_count + (state->screen == 12 || state->screen == 14 ? 1 : 0); ++i) current.second -= current.first; @@ -1388,7 +618,7 @@ void add_entity_to_liquid_collision(uint32_t uid, bool add) std::pair get_liquids_at(float x, float y, LAYER layer) { uint8_t actual_layer = enum_to_layer(layer); - LiquidPhysics* liquid_physics = State::get().ptr()->liquid_physics; + LiquidPhysics* liquid_physics = HeapBase::get().liquid_physics(); // if (y > 125.5f || y < .0f || x > 85.5f || x < .0f) // Original check by the game, can result is accesing the array out of bounds // return 0; if (actual_layer != get_liquid_layer() || y < .0f || x < .0f) @@ -1403,26 +633,6 @@ std::pair get_liquids_at(float x, float y, LAYER layer) return {liquids_at.water, liquids_at.lava}; } -bool disable_floor_embeds(bool disable) -{ - static const auto address = get_address("spawn_floor_embeds"); - const bool current_value = memory_read(address) == 0xc3; - if (disable) - write_mem_recoverable("disable_floor_embeds", address, "\xC3"sv, true); - else - recover_mem("disable_floor_embeds"); - return current_value; -} - -void set_cursepot_ghost_enabled(bool enable) -{ - static const auto address = get_address("ghost_jar_ghost_spawn"); - if (!enable) - write_mem_recoverable("ghost_jar_ghost_spawn", address, "\x90\x90\x90\x90\x90"sv, true); - else - recover_mem("ghost_jar_ghost_spawn"); -} - void game_log(std::string message) { using GameLogFun = void(std::ofstream*, const char*, void*, LogLevel); @@ -1433,8 +643,7 @@ void game_log(std::string message) void load_death_screen() { - auto state = State::get().ptr(); - state->screen_death->init(); + HeapBase::get().state()->screen_death->init(); } void save_progress() @@ -1489,224 +698,6 @@ void set_level_string(std::u16string_view text) *(data + 4 + text.length()) = NULL; } -void set_ending_unlock(ENT_TYPE type) -{ - static const ENT_TYPE first = to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); - static const ENT_TYPE last = to_id("ENT_TYPE_CHAR_CLASSIC_GUY"); - if (type >= first && type <= last) - { - static const auto offset = get_address("ending_unlock"); - const int32_t char_offset = 10; - - write_mem_recoverable("ending_unlock", offset, "\x90\x90\x90\x90\x90\x90\x90\x90"sv, true); - write_mem_recoverable("ending_unlock", offset + char_offset, type, true); - } - else - { - recover_mem("ending_unlock"); - } -} - -void set_olmec_cutscene_enabled(bool enable) -{ - set_skip_olmec_cutscene(!enable); -} - -void set_tiamat_cutscene_enabled(bool enable) -{ - set_skip_tiamat_cutscene(!enable); -} - -void activate_tiamat_position_hack(bool activate) -{ - static const auto code_addr = get_address("tiamat_attack_position"); - - static const std::string_view code{"\xF3\x0F\x5C\xBE\x78\x01\x00\x00"sv // subss xmm7,DWORD PTR [rsi+0x178] - "\xF3\x0F\x5C\xB6\x7C\x01\x00\x00"sv}; // subss xmm6,DWORD PTR [rsi+0x17C] - - if (activate) - write_mem_recoverable("activate_tiamat_position_hack", code_addr, code, true); - else - recover_mem("activate_tiamat_position_hack"); -} - -void activate_crush_elevator_hack(bool activate) -{ - auto& memory = Memory::get(); - static size_t offsets[3]; - if (offsets[0] == 0) - { - auto func_offset = get_virtual_function_address(VTABLE_OFFSET::ACTIVEFLOOR_CRUSHING_ELEVATOR, 78); - - offsets[0] = find_inst(memory.exe(), "\xF3\x0F\x58\xD0"sv, func_offset, func_offset + 0x80, "activate_crush_elevator_hack"); - if (offsets[0] == 0) - return; - - offsets[0] += 4; // pattern size - offsets[1] = find_inst(memory.exe(), "\xEB*\x0F\x57\xD2"sv, offsets[0], offsets[0] + 0xF0, "activate_crush_elevator_hack"); - if (offsets[1] == 0) - return; - - offsets[1] += 5; // pattern size - offsets[2] = find_inst(memory.exe(), "\xF3\x0F\x58\xC1"sv, offsets[1], offsets[1] + 0x40, "activate_crush_elevator_hack"); - if (offsets[2] == 0) - return; - - offsets[2] += 4; // pattern size - } - - if (activate) - { - write_mem_recoverable("activate_crush_elevator_hack", memory.at_exe(offsets[0]), "\x0f\x2e\x90\x30\x01\x00\x00"sv, true); // ucomiss xmm2,DWORD PTR [rax+0x130] // limit - write_mem_recoverable("activate_crush_elevator_hack", memory.at_exe(offsets[1]), "\xf3\x0f\x10\x9b\x30\x01\x00"sv, true); // movss xmm3,DWORD PTR [rbx+0x130] // limit - write_mem_recoverable("activate_crush_elevator_hack", memory.at_exe(offsets[2]), "\xf3\x0f\x58\x83\x34\x01\x00"sv, true); // addss xmm0,DWORD PTR [rbx+0x134] // speed - } - else - recover_mem("activate_crush_elevator_hack"); -} - -void activate_hundun_hack(bool activate) -{ - /* - * Pointer to Hundun entity is stored in r13 register. which means we need 8 bytes for ucomiss instruction - * but we have 7 available, that's why we jump out to new code with the instruction and back - */ - static size_t offsets[6]; // y_limit, y_limit, bird_head, sneak_head, speed, speed - static char new_code[3][8]; - - if (offsets[0] == 0) - { - 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) - return; - - offsets[0] -= 13; // offset, no good pattern above - offsets[1] = find_inst(memory.exe(), "\x41\x80\x8D\x61\x01\x00\x00\x04"sv, offsets[0], offsets[0] + 0xF40, "activate_hundun_hack"); - if (offsets[1] == 0) - { - offsets[0] = 0; - return; - } - offsets[1] += 8; // pattern size - - offsets[2] = find_inst(memory.exe(), "\xF3\x41\x0F\x58\x45\x7C"sv, offsets[0], offsets[1], "activate_hundun_hack"); - if (offsets[2] == 0) - { - offsets[0] = 0; - return; - } - offsets[2] += 6; // pattern size - - offsets[3] = find_inst(memory.exe(), "\xF3\x41\x0F\x58\x45\x7C"sv, offsets[2], offsets[1], "activate_hundun_hack"); - if (offsets[3] == 0) - { - offsets[0] = 0; - return; - } - offsets[3] += 6; // pattern size - - offsets[4] = find_inst(memory.exe(), "\x83\x7A\x0C\x0E"sv, offsets[1], offsets[1] + 0xC0, "activate_hundun_hack"); - if (offsets[4] == 0) - { - offsets[0] = 0; - return; - } - offsets[4] += 6; // pattern size plus jump - - offsets[5] = find_inst(memory.exe(), "\xF3\x41\x0F"sv, offsets[4], offsets[4] + 0x58, "activate_hundun_hack"); - if (offsets[5] == 0) - { - offsets[0] = 0; - return; - } - offsets[5] += 9; // instruction size (didn't include the whole thing in pattern, very short distance from previous pattern) - - offsets[0] = memory.at_exe(offsets[0]); - offsets[1] = memory.at_exe(offsets[1]); - offsets[2] = memory.at_exe(offsets[2]); - offsets[3] = memory.at_exe(offsets[3]); - offsets[4] = memory.at_exe(offsets[4]); - offsets[5] = memory.at_exe(offsets[5]); - - char old_code[3][8]; - - std::memcpy(old_code[0], (void*)offsets[0], 7); - std::memcpy(old_code[1], (void*)offsets[1], 7); - std::memcpy(old_code[2], (void*)offsets[5], 8); - - const std::string_view patch_code{"\x41\x0F\x2E\xBD\x64\x01\x00\x00"sv}; // ucomiss xmm7,DWORD PTR [r13+0x164] - const std::string_view speed_patch{"\xF3\x41\x0F\x58\x85\x6C\x01\x00\x00"sv}; // addss xmm0,DWORD PTR [r13+0x16C] - - patch_and_redirect(offsets[0], 7, patch_code, true); - patch_and_redirect(offsets[1], 7, patch_code, true); - patch_and_redirect(offsets[5], 8, speed_patch, true); - - std::memcpy(new_code[0], (void*)offsets[0], 7); - std::memcpy(new_code[1], (void*)offsets[1], 7); - std::memcpy(new_code[2], (void*)offsets[5], 8); - - // writing back the old code so we can just use write_mem_recoverable for going from vanilla to the patch - write_mem_prot(offsets[0], std::string_view{&old_code[0][0], &old_code[0][7]}, true); - write_mem_prot(offsets[1], std::string_view{&old_code[1][0], &old_code[1][7]}, true); - write_mem_prot(offsets[5], std::string_view{&old_code[2][0], &old_code[2][8]}, true); - } - - if (activate) - { - static const std::string_view speed_code{"\x49\x8D\x95\x68\x01\x00\x00"sv // lea rdx,[r13+0x168] - "\x66\x2E\x0F\x1F\x84\x00\x00\x00\x00\x00\x90\x90\x90"sv}; // spoiled with space, all nop - - write_mem_recoverable("activate_hundun_hack", offsets[0], std::string_view{&new_code[0][0], &new_code[0][7]}, true); // limit - write_mem_recoverable("activate_hundun_hack", offsets[1], std::string_view{&new_code[1][0], &new_code[1][7]}, true); // limit - write_mem_recoverable("activate_hundun_hack", offsets[5], std::string_view{&new_code[2][0], &new_code[2][8]}, true); // speed for adding to the y_limit - - write_mem_recoverable("activate_hundun_hack", offsets[4], speed_code, true); // speed (for adding to the x position) - - write_mem_recoverable("activate_hundun_hack", offsets[2], "\x0F\x2E\xB8\x70\x01\x00\x00"sv, true); // ucomiss xmm7,DWORD PTR [rax+0x170] // bird_head - write_mem_recoverable("activate_hundun_hack", offsets[3], "\x0F\x2E\xB8\x74\x01\x00\x00"sv, true); // ucomiss xmm7,DWORD PTR [rax+0x174] // snake head - } - else - recover_mem("activate_hundun_hack"); -} - -void set_boss_door_control_enabled(bool enable) -{ - static size_t offsets[2]; - if (offsets[0] == 0) - { - 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_address() + 0x777, std::nullopt, "set_boss_door_control_enabled"); - if (offsets[1] == 0) - { - offsets[0] = 0; - return; - } - offsets[1] = function_start(memory.at_exe(offsets[1])); - } - if (!enable) - { - write_mem_recoverable("set_boss_door_control_enabled", offsets[0], "\xC3\x90"sv, true); - write_mem_recoverable("set_boss_door_control_enabled", offsets[1], "\xC3\x90"sv, true); - } - else - recover_mem("set_boss_door_control_enabled"); -} - -void update_state() -{ - static const size_t offset = get_address("state_refresh"); - auto state = State::get().ptr(); - typedef void refresh_func(StateMemory*); - static refresh_func* rf = (refresh_func*)(offset); - rf(state); -} - void set_frametime(std::optional frametime) { static const size_t offset = get_address("engine_frametime"); @@ -1749,7 +740,7 @@ ENT_TYPE add_custom_type() int32_t get_current_money() { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); int32_t money = state->money_shop_total; for (auto& inventory : state->items->player_inventories) { @@ -1761,7 +752,7 @@ int32_t get_current_money() int32_t add_money(int32_t amount, std::optional display_time) { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); auto hud = get_hud(); state->money_shop_total += amount; hud->money.counter += amount; @@ -1771,7 +762,7 @@ int32_t add_money(int32_t amount, std::optional display_time) int32_t add_money_slot(int32_t amount, uint8_t player_slot, std::optional display_time) { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); auto hud = get_hud(); uint8_t slot = player_slot - 1; if (slot > 3) @@ -1786,13 +777,13 @@ int32_t add_money_slot(int32_t amount, uint8_t player_slot, std::optionalitems; for (auto i = 0; i < MAX_PLAYERS; ++i) { - if (state->items->players[i] && state->items->players[i]->layer == layer) - state->items->players[i] = nullptr; + if (items->players[i] && items->players[i]->layer == layer) + items->players[i] = nullptr; } - auto* layer_ptr = State::get().layer(layer); + auto* layer_ptr = HeapBase::get().state()->layer(layer); typedef void destroy_func(Layer*); static destroy_func* df = (destroy_func*)(offset); df(layer_ptr); @@ -1807,7 +798,7 @@ void destroy_level() void create_layer(uint8_t layer) { static const size_t offset = get_address("init_layer"); - auto* layer_ptr = State::get().layer(layer); + auto* layer_ptr = HeapBase::get().state()->layer(layer); typedef void init_func(Layer*); static init_func* ilf = (init_func*)(offset); ilf(layer_ptr); @@ -1819,42 +810,6 @@ void create_level() create_layer(1); } -void set_level_logic_enabled(bool enable) -{ - auto state = State::get().ptr(); - static const size_t offset = get_virtual_function_address(state->screen_level, 1); - - if (!enable) - write_mem_recoverable("set_level_logic_enabled", offset, "\xC3\x90"sv, true); - else - recover_mem("set_level_logic_enabled"); -} - -void set_camera_layer_control_enabled(bool enable) -{ - static const size_t offset = get_address("camera_layer_control"); - static const size_t offset2 = get_address("player_behavior_layer_switch"); - - if (enable) - { - recover_mem("set_camera_layer_control"); - } - else - { - write_mem_recoverable("set_camera_layer_control", offset, get_nop(7), true); - write_mem_recoverable("set_camera_layer_control", offset2, get_nop(18), true); - } -} - -void set_start_level_paused(bool enable) -{ - static const size_t offset = get_address("unpause_level"); - if (enable) - write_mem_recoverable("start_level_paused", offset, get_nop(3), true); - else - recover_mem("start_level_paused"); -} - bool get_start_level_paused() { return mem_written("start_level_paused"); @@ -1974,135 +929,31 @@ void init_seeded(std::optional seed) static const size_t offset = get_address("init_seeded"); typedef void init_func(void*, uint32_t); static init_func* isf = (init_func*)(offset); - auto* state = State::get().ptr(); + auto* state = HeapBase::get().state(); 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; - static uintptr_t jump3; - 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; - - auto& mem = Memory::get(); - layer_offsets[0] = get_address("spawn_liquid_layer"); - - { - auto sound_stuff = get_address("liquid_stream_spawner"); - if (sound_stuff == 0) - return; - - 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-sound stuff"); - 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"); - - { - 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; - - jump2 = get_address("robot_layer_check"); - jump3 = get_address("logic_underwater_bubbles_loop_check"); - if (jump2 == 0 || jump3 == 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 nearby, 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; - uint8_t jump_oppcode2_inverse = actual_layer == 0 ? 0x74 : 0x75; - - 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); - 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); } + +uint32_t lowbias32(uint32_t x) +{ + x ^= x >> 16; + x *= 0x7feb352d; + x ^= x >> 15; + x *= 0x846ca68b; + x ^= x >> 16; + return x; +} +uint32_t lowbias32_r(uint32_t x) +{ + x ^= x >> 16; + x *= 0x43021123U; + x ^= x >> 15 ^ x >> 30; + x *= 0x1d69e2a5U; + x ^= x >> 16; + return x; +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index cdae2231c..cba6e48ad 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -26,56 +26,16 @@ void stack_entities(uint32_t bottom_uid, uint32_t top_uid, const float (&offset) void move_entity_abs(uint32_t uid, float x, float y, float vx, float vy); void move_entity_abs(uint32_t uid, float x, float y, float vx, float vy, LAYER layer); void move_liquid_abs(uint32_t uid, float x, float y, float vx, float vy); -uint32_t get_entity_flags(uint32_t uid); -void set_entity_flags(uint32_t uid, uint32_t flags); -uint32_t get_entity_flags2(uint32_t uid); -void set_entity_flags2(uint32_t uid, uint32_t flags); -void set_level_flags(uint32_t flags); -uint32_t get_level_flags(); ENT_TYPE get_entity_type(uint32_t uid); -int get_entity_ai_state(uint32_t uid); -std::vector get_players(StateMemory* state); std::tuple screen_aabb(float x1, float y1, float x2, float y2); float screen_distance(float x); std::vector filter_entities(std::vector entities, std::function predicate); -void set_door_target(uint32_t uid, uint8_t w, uint8_t l, uint8_t t); -std::tuple get_door_target(uint32_t uid); void set_contents(uint32_t uid, ENT_TYPE item_entity_type); void entity_remove_item(uint32_t uid, uint32_t item_uid, std::optional check_autokill); -void lock_door_at(float x, float y); -void unlock_door_at(float x, float y); -uint32_t get_frame_count_main(); -uint32_t get_frame_count(); -void carry(uint32_t mount_uid, uint32_t rider_uid); void kill_entity(uint32_t uid, std::optional destroy_corpse = std::nullopt); void destroy_entity(uint32_t uid); -void apply_entity_db(uint32_t uid); -void flip_entity(uint32_t uid); -void set_camera_position(float cx, float cy); -void warp(uint8_t w, uint8_t l, uint8_t t); -void set_seed(uint32_t seed); -void set_arrowtrap_projectile(ENT_TYPE regular_entity_type, ENT_TYPE poison_entity_type); -void modify_sparktraps(float angle_increment = 0.015, float distance = 3.0); -float* get_sparktraps_parameters_ptr(); // for UI -void activate_sparktraps_hack(bool activate); -void set_storage_layer(LAYER layer); -void set_kapala_blood_threshold(uint8_t threshold); -void set_kapala_hud_icon(int8_t icon_index); -void set_blood_multiplication(uint32_t default_multiplier, uint32_t vladscape_multiplier); -std::vector read_prng(); -void pick_up(uint32_t who_uid, uint32_t what_uid); -void drop(uint32_t who_uid, std::optional what_uid); void unequip_backitem(uint32_t who_uid); int32_t worn_backitem(uint32_t who_uid); -void set_olmec_phase_y_level(uint8_t phase, float y); -void force_olmec_phase_0(bool b); -void set_ghost_spawn_times(uint32_t normal = 10800, uint32_t cursed = 9000); -void set_time_ghost_enabled(bool b); -void set_time_jelly_enabled(bool b); -void set_journal_enabled(bool b); -void set_camp_camera_bounds_enabled(bool b); -void set_explosion_mask(int32_t mask); -void set_max_rope_length(uint8_t length); uint8_t get_max_rope_length(); bool is_inside_active_shop_room(float x, float y, LAYER layer); bool is_inside_shop_zone(float x, float y, LAYER layer); @@ -87,37 +47,20 @@ void waddler_set_entity_meta(uint8_t slot, int16_t meta); uint32_t waddler_entity_type_in_slot(uint8_t slot); bool entity_type_check(const std::vector& types_array, const ENT_TYPE find); std::vector get_proper_types(std::vector ent_types); -void enter_door(int32_t player_uid, int32_t door_uid); -void change_sunchallenge_spawns(std::vector ent_types); -void change_diceshop_prizes(std::vector ent_types); -void change_altar_damage_spawns(std::vector ent_types); -void change_waddler_drop(std::vector ent_types); void poison_entity(int32_t entity_uid); -void modify_ankh_health_gain(uint8_t max_health, uint8_t beat_add_health); void move_grid_entity(int32_t uid, float x, float y, LAYER layer); void destroy_grid(int32_t uid); void destroy_grid(float x, float y, LAYER layer); 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, std::optional layer = std::nullopt); void add_entity_to_liquid_collision(uint32_t uid, bool add); std::pair get_liquids_at(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); void load_death_screen(); void save_progress(); void set_level_string(std::u16string_view text); -void set_ending_unlock(ENT_TYPE type); -void set_olmec_cutscene_enabled(bool enable); -void set_tiamat_cutscene_enabled(bool enable); -void activate_tiamat_position_hack(bool activate); -void activate_crush_elevator_hack(bool activate); -void activate_hundun_hack(bool activate); -void set_boss_door_control_enabled(bool enable); -void update_state(); void set_frametime(std::optional frametime); double get_frametime(); void set_frametime_inactive(std::optional frametime); @@ -131,13 +74,11 @@ void destroy_layer(uint8_t layer); void destroy_level(); void create_layer(uint8_t layer); void create_level(); -void set_start_level_paused(bool enable); bool get_start_level_paused(); -void set_level_logic_enabled(bool enable); -void set_camera_layer_control_enabled(bool enable); void set_speedhack(std::optional multiplier); float get_speedhack(); void init_adventure(); void init_seeded(std::optional seed); -void set_liquid_layer(LAYER l); uint8_t get_liquid_layer(); +uint32_t lowbias32(uint32_t x); +uint32_t lowbias32_r(uint32_t x); diff --git a/src/game_api/savestate.cpp b/src/game_api/savestate.cpp index 2774d784c..8bc8c6241 100644 --- a/src/game_api/savestate.cpp +++ b/src/game_api/savestate.cpp @@ -3,62 +3,37 @@ #include "memory.hpp" // for write_mem_prot, write_mem_recoverable #include "online.hpp" // for Online #include "script/events.hpp" // for pre_load_state -#include "state.hpp" // for State, get_state_ptr, enum_to_layer +#include "state.hpp" // for StateMemory -StateMemory* get_save_state_raw(int slot) +void SaveState::backup_main(int slot_to) { - size_t arr = get_address("save_states"); - size_t base = memory_read(arr + (slot - 1) * 8); - auto state = reinterpret_cast(base + State::get().get_offset()); - return state; + if (pre_save_state(slot_to, get_save_state(slot_to))) + return; + + auto base_from = HeapBase::get_main(); + auto base_to = HeapBase::get(static_cast(slot_to - 1)); + + pre_copy_state_event(base_from, base_to); + base_from.copy_to(base_to); + post_save_state(slot_to, base_to.state()); } -void copy_save_slot(int from, int to) +void SaveState::restore_main(int slot_from) { - if ((from == 5 && pre_save_state(to, get_save_state(to))) || - (to == 5 && pre_load_state(from, get_save_state(from)))) + if (pre_load_state(slot_from, get_save_state(slot_from))) return; - pre_copy_state_event(get_save_state_raw(from), get_save_state_raw(to)); - size_t arr = get_address("save_states"); - size_t fromBaseState = memory_read(arr + (from - 1) * 8); - size_t toBaseState = memory_read(arr + (to - 1) * 8); - copy_state(fromBaseState, toBaseState); - if (from == 5) - post_save_state(to, get_save_state(to)); - else if (to == 5) - post_load_state(from, get_save_state(from)); -}; - -void copy_state(size_t fromBaseState, size_t toBaseState) -{ - size_t iterIdx = 1; - do - { - size_t copyContent = *(size_t*)((fromBaseState - 8) + iterIdx * 8); - // variable used to fix pointers that point somewhere in the same Thread - size_t diff = toBaseState - fromBaseState; - if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) - { - diff = 0; - } - *(size_t*)(toBaseState + iterIdx * 8 + -8) = diff + copyContent; - // Almost same code as before, but on the next value, idk why - copyContent = *(size_t*)(fromBaseState + iterIdx * 8); - diff = toBaseState - fromBaseState; - if (copyContent >= fromBaseState + 0x2000000 || copyContent <= fromBaseState) - { - diff = 0; - } - *(size_t*)(toBaseState + iterIdx * 8) = diff + copyContent; + auto base_from = HeapBase::get(static_cast(slot_from - 1)); + auto base_to = HeapBase::get_main(); - iterIdx = iterIdx + 2; - } while (iterIdx != 0x400001); -}; + pre_copy_state_event(base_from, base_to); + base_from.copy_to(base_to); + post_load_state(slot_from, base_from.state()); +} StateMemory* get_save_state(int slot) { - auto state = get_save_state_raw(slot); + auto state = HeapBase::get(static_cast(slot - 1)).state(); if (state->screen) return state; return nullptr; @@ -66,8 +41,7 @@ StateMemory* get_save_state(int slot) void invalidate_save_slots() { - auto online = get_online(); - if (online->lobby.code != 0) + if (get_online()->is_active()) return; for (int i = 1; i <= 4; ++i) { @@ -77,51 +51,26 @@ void invalidate_save_slots() } } -SaveState::SaveState() -{ - addr = (size_t)malloc(8ull * 0x400000); - save(); -} - -StateMemory* SaveState::get_state() const -{ - if (!addr) - return nullptr; - return reinterpret_cast(addr + State::get().get_offset()); -} - void SaveState::load() { - if (!addr) + if (base.is_null()) return; - State& state_g = State::get(); - size_t offset = state_g.get_offset(); - size_t to = (size_t)(state_g.ptr_main()) - offset; - auto state = reinterpret_cast(addr + offset); + + auto state = base.state(); if (pre_load_state(-1, state)) return; - copy_state(addr, to); + base.copy_to(HeapBase::get_main()); post_load_state(-1, state); } void SaveState::save() { - if (!addr) + if (base.is_null()) return; - State& state_g = State::get(); - size_t offset = state_g.get_offset(); - size_t from = (size_t)(state_g.ptr_main()) - offset; - auto state = reinterpret_cast(addr + offset); + + auto state = base.state(); if (pre_save_state(-1, state)) return; - copy_state(from, addr); + HeapBase::get_main().copy_to(base); post_save_state(-1, state); } - -void SaveState::clear() -{ - if (!addr) - return; - free((void*)addr); - addr = 0; -} diff --git a/src/game_api/savestate.hpp b/src/game_api/savestate.hpp index f74a6590a..cee69612a 100644 --- a/src/game_api/savestate.hpp +++ b/src/game_api/savestate.hpp @@ -1,19 +1,59 @@ #pragma once +#include // for uint32_t, int8_t + +#include "heap_base.hpp" // for HeapBase + struct StateMemory; +struct PRNG; 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() + : base(reinterpret_cast(malloc(8ull * 0x400000))) + { + save(); + } ~SaveState() { clear(); } + /// Get the pre-allocated by the game save slot 1-4 + static SaveState get(int save_slot) + { + if (save_slot >= 1 && save_slot <= 4) + return {}; + + int8_t index = static_cast(save_slot - 1); + SaveState save_from_slot; + save_from_slot.base = HeapBase::get(index); + save_from_slot.slot = index; + return save_from_slot; + } /// Access the StateMemory inside a SaveState - StateMemory* get_state() const; + StateMemory* get_state() const + { + if (base.is_null()) + return nullptr; + return base.state(); + } + /// Get the current frame from the SaveState, equivelent to the [get_frame](#Get_frame) global function that returns the frame from the "loaded in state" + uint32_t get_frame() const + { + if (base.is_null()) + return 0; + return base.frame_count(); + } + /// Access the PRNG inside a SaveState + PRNG* get_prng() const + { + if (base.is_null()) + return nullptr; + return base.prng(); + } /// Load a SaveState void load(); @@ -22,13 +62,20 @@ class SaveState void save(); /// Delete the SaveState and free the memory. The SaveState can't be used after this. - void clear(); + void clear() + { + if (slot == -1) + return; + + base.free(); + } + static void backup_main(int slot_to); + static void restore_main(int from_slot); private: - size_t addr; + HeapBase base; + int8_t slot{-1}; }; -void copy_save_slot(int from, int to); -void copy_state(size_t fromBaseState, size_t toBaseState); StateMemory* get_save_state(int slot); void invalidate_save_slots(); diff --git a/src/game_api/screen.cpp b/src/game_api/screen.cpp index 8dbb02ece..8d309fd84 100644 --- a/src/game_api/screen.cpp +++ b/src/game_api/screen.cpp @@ -341,9 +341,9 @@ void force_journal(uint32_t chapter, uint32_t entry) void toggle_journal() { auto gm = get_game_manager(); - typedef void show_journal_func(JournalUI*, size_t); + typedef void show_journal_func(JournalUI*, HeapBase); static show_journal_func* show = (show_journal_func*)(get_address("toggle_journal"sv)); - show(gm->journal_ui, heap_base()); + show(gm->journal_ui, HeapBase::get()); } void show_journal(JOURNALUI_PAGE_SHOWN chapter, uint32_t page) @@ -366,7 +366,7 @@ void show_journal(JOURNALUI_PAGE_SHOWN chapter, uint32_t page) } } -std::optional ScreenCodeInput::get_seed() +std::optional ScreenCodeInput::get_seed() const { if (code_length == 0) return std::nullopt; diff --git a/src/game_api/screen.hpp b/src/game_api/screen.hpp index 5bb626e31..fcd64cc50 100644 --- a/src/game_api/screen.hpp +++ b/src/game_api/screen.hpp @@ -397,7 +397,7 @@ class ScreenCodeInput : public Screen // ID: 8 /// Set the seed entered in the seed dialog. Call without arguments to clear entered seed. Optionally enter a length to set partial seed. void set_seed(std::optional seed, std::optional length); /// Get the seed currently entered in the seed dialog or nil if nothing is entered. Will also return incomplete seeds, check seed_length to verify it's ready. - std::optional get_seed(); + std::optional get_seed() const; virtual void unknown() = 0; // set seed? sets the game variables in state, for ScreenEnterOnlineCode it just sets the unknown10 }; diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index a39e0b241..1e9fa8023 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -13,7 +13,7 @@ #include "savestate.hpp" // for invalidate_save_slots #include "script/lua_backend.hpp" // for LuaBackend, ON, LuaBackend::PreHan... #include "settings_api.hpp" // for restore_original_settings -#include "state.hpp" // for StateMemory, State +#include "state.hpp" // for StateMemory class JournalPage; struct AABB; @@ -32,7 +32,7 @@ void pre_load_level_files() bool pre_load_screen() { static int64_t prev_seed = 0; - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); if (state->screen_next == 12 && (state->quest_flags & 1) != 0) { if ((state->quest_flags & (1U << 6)) > 0) @@ -518,7 +518,7 @@ void post_event(ON event) }); } -void pre_copy_state_event(StateMemory* from, StateMemory* to) +void pre_copy_state_event(HeapBase from, HeapBase to) { LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index e1896f797..3096a1a8a 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -6,6 +6,7 @@ #include // for string_view #include "aliases.hpp" // for JournalPageType +#include "heap_base.hpp" // for HeapBase #include "lua_backend.hpp" // for ON class Entity; @@ -26,7 +27,7 @@ bool pre_unload_level(); bool pre_unload_layer(LAYER layer); bool pre_save_state(int slot, StateMemory* saved); bool pre_load_state(int slot, StateMemory* loaded); -void pre_copy_state_event(StateMemory* from, StateMemory* to); +void pre_copy_state_event(HeapBase from, HeapBase to); void post_load_screen(); void post_init_layer(LAYER layer); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 39cf1ecad..652502df9 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -25,11 +25,11 @@ #include "math.hpp" // for AABB #include "movable_behavior.hpp" // for CustomMovableBehavior #include "overloaded.hpp" // for overloaded -#include "rpc.hpp" // for get_frame_count, get_pla... +#include "rpc.hpp" // for set_level_string #include "screen.hpp" // for get_screen_ptr, Screen #include "script_util.hpp" // for InputString #include "sound_manager.hpp" // for SoundManager -#include "state.hpp" // for StateMemory, State, get_... +#include "state.hpp" // for StateMemory, get_... #include "strings.hpp" // for clear_custom_shopitem_names #include "usertypes/gui_lua.hpp" // for GuiDrawContext #include "usertypes/level_lua.hpp" // for PreHandleRoomTilesContext @@ -43,16 +43,13 @@ int g_hotkey_count = 0; LuaBackend::LuaBackend(SoundManager* sound_mgr, LuaConsole* con) : lua{get_lua_vm(sound_mgr), sol::create}, vm{acquire_lua_vm(sound_mgr)}, sound_manager{sound_mgr}, console{con} { - g_state = State::get().ptr_local(); - if (g_state == nullptr) - { - g_state = State::get().ptr_main(); - } + auto heap = HeapBase::get(); + g_state = heap.state(); ScriptState& state = local_state_datas[g_state].state; state.screen = g_state->screen; state.time_level = g_state->time_level; state.time_total = g_state->time_total; - state.time_global = State::get_frame_count(g_state); + state.time_global = heap.frame_count(); state.frame = state.frame; state.loading = g_state->loading; state.reset = (g_state->quest_flags & 1); @@ -88,7 +85,7 @@ LuaBackend::~LuaBackend() LocalStateData& LuaBackend::get_locals() { - return local_state_datas[State::get().ptr()]; + return local_state_datas[HeapBase::get().state()]; } void LuaBackend::clear() @@ -284,7 +281,8 @@ bool LuaBackend::update() clear_custom_shopitem_names(); }*/ ScriptState& script_state = get_locals().state; - StateMemory* state = State::get().ptr(); + HeapBase heap = HeapBase::get(); + StateMemory* state = heap.state(); if (state->screen != script_state.screen) { if (on_screen) @@ -377,7 +375,7 @@ bool LuaBackend::update() for (auto it = global_timers.begin(); it != global_timers.end();) { - int now = State::get_frame_count(state); + int now = heap.frame_count(); if (auto cb = std::get_if(&it->second)) { if (now >= cb->lastRan + cb->interval && !is_callback_cleared(it->first)) @@ -414,7 +412,7 @@ bool LuaBackend::update() } } - auto now = State::get_frame_count(state); + auto now = heap.frame_count(); for (auto& [id, callback] : load_callbacks) { if (callback.lastRan < 0) @@ -462,7 +460,7 @@ bool LuaBackend::update() } case ON::GAMEFRAME: { - if (!state->pause && State::get_frame_count(state) != script_state.time_global && + if (!state->pause && heap.frame_count() != script_state.time_global && ((state->screen >= (int)ON::CAMP && state->screen <= (int)ON::DEATH) || state->screen == (int)ON::ARENA_MATCH)) { handle_function(this, callback.func); @@ -564,11 +562,12 @@ bool LuaBackend::update() clear_current_callback(); } + auto local_frame = HeapBase::get().frame_count(); script_state.screen = state->screen; script_state.time_level = state->time_level; script_state.time_total = state->time_total; - script_state.time_global = get_frame_count(); - script_state.frame = get_frame_count(); + script_state.time_global = local_frame; + script_state.frame = local_frame; script_state.loading = state->loading; script_state.reset = (state->quest_flags & 1); script_state.quest_flags = state->quest_flags; @@ -576,7 +575,7 @@ bool LuaBackend::update() if (manual_save) { manual_save = false; - last_save = get_frame_count(); + last_save = API::get_global_frame_count(); } } catch (const sol::error& e) @@ -613,7 +612,7 @@ void LuaBackend::draw(ImDrawList* dl) if (is_callback_cleared(id)) continue; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); if (callback.screen == ON::GUIFRAME) { set_current_callback(-1, id, CallbackType::Normal); @@ -637,7 +636,7 @@ void LuaBackend::draw(ImDrawList* dl) g_hotkeys[callback.hotkeyid].active = !(ImGui::GetIO().WantCaptureKeyboard || bucket->io->WantCaptureKeyboard.value_or(false)); } - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); while (callback.queue > 0) { set_current_callback(-1, id, CallbackType::HotKey); @@ -785,7 +784,7 @@ void LuaBackend::pre_load_level_files() if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -806,7 +805,7 @@ bool LuaBackend::pre_init_level() if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -830,7 +829,7 @@ bool LuaBackend::pre_init_layer(LAYER layer) if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -854,9 +853,9 @@ bool LuaBackend::pre_load_screen() if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); - auto state_ptr = State::get().ptr(); + auto state_ptr = HeapBase::get().state(); if ((ON)state_ptr->screen_next <= ON::LEVEL && (ON)state_ptr->screen_next != ON::OPTIONS && (ON)state_ptr->screen != ON::OPTIONS) { using namespace std::string_view_literals; @@ -881,7 +880,7 @@ bool LuaBackend::pre_load_screen() if ((ON)state_ptr->screen == ON::LEVEL && (ON)state_ptr->screen_next != ON::DEATH && (state_ptr->quest_flags & 1) == 0) { - for (auto layer : State::get().ptr()->layers) + for (auto layer : HeapBase::get().state()->layers) { auto it = layer->entities_by_mask.find(1); if (it == layer->entities_by_mask.end()) @@ -921,7 +920,7 @@ bool LuaBackend::pre_load_screen() saved.held = get_user_data(ent->holding_uid); should_save = true; } - if (ent->overlay && (ent->overlay->type->search_flags & 2) > 0 && user_datas.contains(ent->overlay->uid)) + if (ent->overlay && (ent->overlay->type->search_flags & ENTITY_MASK::MOUNT) == ENTITY_MASK::MOUNT && user_datas.contains(ent->overlay->uid)) { saved.mount = get_user_data(ent->overlay->uid); should_save = true; @@ -955,7 +954,7 @@ bool LuaBackend::pre_unload_level() if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -980,7 +979,7 @@ bool LuaBackend::pre_unload_layer(LAYER layer) if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1006,7 +1005,7 @@ void LuaBackend::post_room_generation() if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1025,7 +1024,7 @@ void LuaBackend::post_room_generation() void LuaBackend::load_user_data() { - for (auto layer : State::get().ptr()->layers) + for (auto layer : HeapBase::get().state()->layers) { auto it = layer->entities_by_mask.find(1); if (it == layer->entities_by_mask.end()) @@ -1059,7 +1058,7 @@ void LuaBackend::load_user_data() set_user_data(*ent, saved_user_datas[slot].self.value()); if (ent->holding_uid != -1 && saved_user_datas[slot].held.has_value()) set_user_data(ent->holding_uid, saved_user_datas[slot].held.value()); - if (ent->overlay && (ent->overlay->type->search_flags & 2) > 0 && saved_user_datas[slot].mount.has_value()) + if (ent->overlay && (ent->overlay->type->search_flags & ENTITY_MASK::MOUNT) == ENTITY_MASK::MOUNT && saved_user_datas[slot].mount.has_value()) set_user_data(ent->overlay->uid, saved_user_datas[slot].mount.value()); for (auto& [type, powerup] : ent->powerups) { @@ -1076,9 +1075,9 @@ void LuaBackend::post_level_generation() if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); - auto state_ptr = State::get().ptr(); + auto state_ptr = HeapBase::get().state(); if ((ON)state_ptr->screen == ON::LEVEL) { load_user_data(); @@ -1104,7 +1103,7 @@ void LuaBackend::post_init_layer(LAYER layer) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1125,13 +1124,13 @@ void LuaBackend::post_load_screen() if (!get_enabled()) return; - auto state_ptr = State::get().ptr(); + auto state_ptr = HeapBase::get().state(); if ((ON)state_ptr->screen == ON::TRANSITION) { load_user_data(); } - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1152,7 +1151,7 @@ void LuaBackend::post_unload_layer(LAYER layer) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1174,7 +1173,7 @@ void LuaBackend::on_death_message(STRINGID stringid) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1196,7 +1195,7 @@ std::string LuaBackend::pre_get_random_room(int x, int y, uint8_t layer, uint16_ if (!get_enabled()) return std::string{}; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1222,7 +1221,7 @@ LuaBackend::PreHandleRoomTilesResult LuaBackend::pre_handle_room_tiles(LevelGenR if (!get_enabled()) return {false, std::nullopt}; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); PreHandleRoomTilesContext ctx{room_data}; @@ -1256,7 +1255,7 @@ Entity* LuaBackend::pre_entity_spawn(std::uint32_t entity_type, float x, float y if (is_callback_cleared(callback.id)) continue; - bool mask_match = callback.entity_mask == 0 || (get_type(entity_type)->search_flags & callback.entity_mask); + bool mask_match = callback.entity_mask == ENTITY_MASK::ANY || !!(get_type(entity_type)->search_flags & callback.entity_mask); bool flags_match = callback.spawn_type_flags & spawn_type_flags; if (mask_match && flags_match) { @@ -1285,7 +1284,7 @@ void LuaBackend::post_entity_spawn(Entity* entity, int spawn_type_flags) if (is_callback_cleared(callback.id)) continue; - bool mask_match = callback.entity_mask == 0 || (entity->type->search_flags & callback.entity_mask); + bool mask_match = callback.entity_mask == ENTITY_MASK::ANY || !!(entity->type->search_flags & callback.entity_mask); bool flags_match = callback.spawn_type_flags & spawn_type_flags; if (mask_match && flags_match) { @@ -1331,7 +1330,7 @@ bool LuaBackend::process_vanilla_render_callbacks(ON event) // used in infinite loop detection to see if game is hanging because a script is hanging frame_counter++; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); VanillaRenderContext render_ctx; for (auto& [id, callback] : callbacks) { @@ -1356,7 +1355,7 @@ bool LuaBackend::process_vanilla_render_blur_callbacks(ON event, float blur_amou if (!get_enabled()) return skip; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); VanillaRenderContext render_ctx; for (auto& [id, callback] : callbacks) { @@ -1381,7 +1380,7 @@ bool LuaBackend::process_vanilla_render_hud_callbacks(ON event, Hud* hud) if (!get_enabled()) return skip; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); VanillaRenderContext render_ctx; for (auto& [id, callback] : callbacks) { @@ -1406,7 +1405,7 @@ bool LuaBackend::process_vanilla_render_layer_callbacks(ON event, uint8_t layer) if (!get_enabled()) return skip; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); VanillaRenderContext render_ctx; for (auto& [id, callback] : callbacks) { @@ -1431,7 +1430,7 @@ bool LuaBackend::process_vanilla_render_draw_depth_callbacks(ON event, uint8_t d if (!get_enabled()) return skip; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); VanillaRenderContext render_ctx; render_ctx.bounding_box = bbox; for (auto& [id, callback] : callbacks) @@ -1457,7 +1456,7 @@ bool LuaBackend::process_vanilla_render_journal_page_callbacks(ON event, Journal if (!get_enabled()) return skip; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); VanillaRenderContext render_ctx; for (auto& [id, callback] : callbacks) { @@ -1481,7 +1480,7 @@ std::u16string LuaBackend::pre_speach_bubble(Entity* entity, char16_t* buffer) if (!get_enabled()) return std::u16string{no_return_str}; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); std::optional return_value = std::nullopt; @@ -1512,7 +1511,7 @@ std::u16string LuaBackend::pre_toast(char16_t* buffer) if (!get_enabled()) return std::u16string{no_return_str}; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); std::optional return_value = std::nullopt; @@ -1543,7 +1542,7 @@ bool LuaBackend::pre_load_journal_chapter(uint8_t chapter) if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (is_callback_cleared(id)) @@ -1571,7 +1570,7 @@ std::vector LuaBackend::post_load_journal_chapter(uint8_t chapter, con if (!get_enabled()) return {}; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); std::vector new_pages; for (auto& [id, callback] : callbacks) { @@ -1608,7 +1607,7 @@ std::optional LuaBackend::pre_get_feat(FEAT feat) if (!get_enabled()) return std::nullopt; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (is_callback_cleared(id)) @@ -1636,7 +1635,7 @@ bool LuaBackend::pre_set_feat(FEAT feat) if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (is_callback_cleared(id)) @@ -1810,7 +1809,7 @@ void LuaBackend::on_set_user_data(Entity* ent) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (is_callback_cleared(id)) @@ -1832,7 +1831,7 @@ bool LuaBackend::on_pre(ON event) if (!get_enabled()) return skip; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (is_callback_cleared(id)) @@ -1855,7 +1854,7 @@ void LuaBackend::on_post(ON event) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (is_callback_cleared(id)) @@ -1922,13 +1921,13 @@ void LuaBackend::copy_locals(StateMemory* from, StateMemory* to) } } -void LuaBackend::pre_copy_state(StateMemory* from, StateMemory* to) +void LuaBackend::pre_copy_state(HeapBase from, HeapBase to) { if (!get_enabled()) return; - copy_locals(from, to); - // auto now = get_frame_count(); + copy_locals(from.state(), to.state()); + // auto now = HeapBase::get().frame_count(); // for (auto& [id, callback] : callbacks) // { // if (is_callback_cleared(id)) @@ -1948,7 +1947,7 @@ bool LuaBackend::pre_save_state(int slot, StateMemory* saved) if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -1974,7 +1973,7 @@ bool LuaBackend::pre_load_state(int slot, StateMemory* loaded) if (!get_enabled()) return false; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -2000,7 +1999,7 @@ void LuaBackend::post_save_state(int slot, StateMemory* saved) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { @@ -2022,7 +2021,7 @@ void LuaBackend::post_load_state(int slot, StateMemory* loaded) if (!get_enabled()) return; - auto now = get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index f20c0fc3c..318d8e2b0 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -25,6 +25,7 @@ #include // for vector #include "aliases.hpp" // for IMAGE, JournalPageType, SPAWN_TYPE +#include "heap_base.hpp" // for HeapBase #include "hook_handler.hpp" // for HookHandler #include "level_api.hpp" // IWYU pragma: keep #include "logger.h" // for DEBUG @@ -200,7 +201,7 @@ struct ScreenCallback { sol::function func; ON screen; - int lastRan; + int lastRan; // TODO should probably be uint32_t ? }; struct LevelGenCallback @@ -213,7 +214,7 @@ struct LevelGenCallback struct EntitySpawnCallback { int id; - int entity_mask; + ENTITY_MASK entity_mask; std::vector entity_types; SPAWN_TYPE spawn_type_flags; sol::function func; @@ -340,7 +341,7 @@ class LuaBackend std::unordered_set console_commands; std::unordered_map local_state_datas; bool manual_save{false}; - uint32_t last_save{0}; + uint64_t last_save{0}; ImDrawList* draw_list{nullptr}; @@ -473,7 +474,7 @@ class LuaBackend void load_user_data(); bool on_pre(ON event); void on_post(ON event); - void pre_copy_state(StateMemory* from, StateMemory* to); + void pre_copy_state(HeapBase from, HeapBase to); void hotkey_callback(int cb); int register_hotkey(HotKeyCallback cb, HOTKEY_TYPE flags); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index ed86ecf50..d23a0f54a 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -1,6 +1,5 @@ #include "lua_vm.hpp" -#include // for IsBadReadPtr, Get... #include // for max, transform #include // for toupper #include // for milliseconds, sys... @@ -38,7 +37,7 @@ #include "entity_lookup.hpp" // #include "game_api.hpp" // #include "game_manager.hpp" // for get_game_manager -#include "handle_lua_function.hpp" // for handle_function +#include "heap_base.hpp" // for OnHeapPointer, HeapBase #include "illumination.hpp" // #include "items.hpp" // for Inventory #include "layer.hpp" // for g_level_max_x @@ -59,14 +58,13 @@ #include "script_util.hpp" // for sanitize, get_say #include "search.hpp" // for get_address #include "settings_api.hpp" // for get_settings_name... -#include "spawn_api.hpp" // for spawn_roomowner -#include "state.hpp" // for State, StateMemory +#include "state.hpp" // for StateMemory #include "strings.hpp" // for change_string -#include "thread_utils.hpp" // for OnHeapPointer #include "usertypes/behavior_lua.hpp" // for register_usertypes #include "usertypes/bucket_lua.hpp" // for register_usertypes #include "usertypes/char_state_lua.hpp" // for register_usertypes #include "usertypes/color_lua.hpp" // for register_usertypes +#include "usertypes/deprecated_func.hpp" // for register_usertypes #include "usertypes/drops_lua.hpp" // for register_usertypes #include "usertypes/entities_activefloors_lua.hpp" // for register_usertypes #include "usertypes/entities_backgrounds_lua.hpp" // for register_usertypes @@ -83,10 +81,13 @@ #include "usertypes/entity_lua.hpp" // for register_usertypes #include "usertypes/flags_lua.hpp" // for register_usertypes #include "usertypes/game_manager_lua.hpp" // for register_usertypes +#include "usertypes/game_patches_lua.hpp" // for register_usertypes +#include "usertypes/global_players_lua.hpp" // for register_usertypes #include "usertypes/gui_lua.hpp" // for register_usertypes #include "usertypes/hitbox_lua.hpp" // for register_usertypes #include "usertypes/level_lua.hpp" // for register_usertypes #include "usertypes/logic_lua.hpp" // for register_usertypes +#include "usertypes/options_lua.hpp" // for register_usertypes #include "usertypes/particles_lua.hpp" // for register_usertypes #include "usertypes/player_lua.hpp" // for register_usertypes #include "usertypes/prng_lua.hpp" // for register_usertypes @@ -95,6 +96,7 @@ #include "usertypes/screen_lua.hpp" // for register_usertypes #include "usertypes/socket_lua.hpp" // for register_usertypes #include "usertypes/sound_lua.hpp" // for register_usertypes +#include "usertypes/spawn_lua.hpp" // for register_usertypes #include "usertypes/state_lua.hpp" // for register_usertypes #include "usertypes/steam_lua.hpp" // for register_usertypes #include "usertypes/texture_lua.hpp" // for register_usertypes @@ -104,110 +106,6 @@ struct Illumination; -struct Players -{ - // This is probably over complicating - // but i couldn't find better solution for the global players to be always correct - // (not return reference to non existing entity when in between screens etc. like in draw callback) - - using value_type = Player*; - using iterator = std::vector::iterator; - - Players() - { - update(); - } - size_t size() - { - update(); - return p.size(); - } - Player* at(const int index) - { - update(); - if (index < 0 || index >= p.size()) - return nullptr; - - return p[index]; - } - auto begin() - { - return p.begin(); - } - auto end() - { - return p.end(); - } - - private: - std::vector p; - - void update() - { - StateMemory* local_state = State::get().ptr_local(); - if (local_state == nullptr) - { - StateMemory* main_state = State::get().ptr_main(); - p = get_players(main_state); - } - else - { - p = get_players(local_state); - } - } - struct lua_iterator_state - { - typedef std::vector::iterator it_t; - it_t begin; - it_t it; - it_t last; - - lua_iterator_state(Players& mt) - : begin(mt.begin()), it(mt.begin()), last(mt.end()){}; - }; - static std::tuple my_next(sol::user user_it_state, sol::this_state l) - { - // this gets called - // to start the first iteration, and every - // iteration there after - - lua_iterator_state& it_state = user_it_state; - auto& it = it_state.it; - if (it == it_state.last) - { - // return nil to signify that there's nothing more to work with. - return std::make_tuple(sol::object(sol::lua_nil), sol::object(sol::lua_nil)); - } - // 2 values are returned (pushed onto the stack): - // the key and the value - // the state is left alone - auto r = std::make_tuple( - sol::object(l, sol::in_place, it - it_state.begin + 1), - sol::object(l, sol::in_place, *it)); - // the iterator must be moved forward one before we return - std::advance(it, 1); - return r; - } - - public: - static auto my_pairs(Players& mt) - { - mt.update(); - // pairs expects 3 returns: - // the "next" function on how to advance, - // the "table" itself or some state, - // and an initial key value (can be nil) - - // prepare our state - lua_iterator_state it_state(mt); - // sol::user is a space/time optimization over regular - // usertypes, it's incompatible with regular usertypes and - // stores the type T directly in lua without any pretty - // setup saves space allocation and a single dereference - return std::make_tuple(&my_next, sol::user(std::move(it_state)), sol::lua_nil); - } -}; - void load_libraries(sol::state& lua) { lua.open_libraries(sol::lib::math, sol::lib::base, sol::lib::string, sol::lib::table, sol::lib::coroutine, sol::lib::package); @@ -299,30 +197,24 @@ end NLogic::register_usertypes(lua); NBucket::register_usertypes(lua); NColor::register_usertypes(lua); - - StateMemory* main_state = State::get().ptr_main(); - - /// NoDoc - lua.new_usertype( - "Players", sol::no_constructor, sol::meta_function::index, [](Players* p, const int index) - { return p->at(index - 1); }, - sol::meta_function::pairs, - Players::my_pairs); - Players players; + NDeprecated::register_usertypes(lua); + NGPlayers::register_usertypes(lua); + NSpawn::register_usertypes(lua); + NGamePatches::register_usertypes(lua); /// A bunch of [game state](#StateMemory) variables. Your ticket to almost anything that is not an Entity. - lua["state"] = main_state; + lua["state"] = HeapBase::get_main().state(); /// The GameManager gives access to a couple of Screens as well as the pause and journal UI elements lua["game_manager"] = get_game_manager(); /// The Online object has information about the online lobby and its players lua["online"] = get_online(); /// An array of [Player](#Player) of the current players. This is just a list of existing Player entities in order, i.e., `players[1]` is not guaranteed to be P1 if they have been gibbed for example. See [get_player](#get_player). - lua["players"] = players; + // lua["players"] = get_players(); auto get_player = sol::overload( [&lua](int8_t slot) -> sol::object // -> Player { - for (auto player : get_players(State::get().ptr())) + for (auto player : HeapBase::get().state()->get_players()) { if (player->inventory_ptr->player_slot == slot - 1) return sol::make_object_userdata(lua, player); @@ -331,14 +223,14 @@ end }, [&lua](int8_t slot, bool or_ghost) -> sol::object { - for (auto player : get_players(State::get().ptr())) + for (auto player : HeapBase::get().state()->get_players()) { if (player->inventory_ptr->player_slot == slot - 1) return sol::make_object_userdata(lua, player); } if (or_ghost) { - for (auto uid : get_entities_by(to_id("ENT_TYPE_ITEM_PLAYERGHOST"), 0x8u, LAYER::BOTH)) + for (auto uid : get_entities_by(to_id("ENT_TYPE_ITEM_PLAYERGHOST"), ENTITY_MASK::ITEM, LAYER::BOTH)) { auto player = get_entity_ptr(uid)->as(); if (player->inventory->player_slot == slot - 1) @@ -355,7 +247,7 @@ end /// Returns PlayerGhost with this player slot 1..4 lua["get_playerghost"] = [](int8_t slot) -> PlayerGhost* { - for (auto uid : get_entities_by(to_id("ENT_TYPE_ITEM_PLAYERGHOST"), 0x8u, LAYER::BOTH)) + for (auto uid : get_entities_by(to_id("ENT_TYPE_ITEM_PLAYERGHOST"), ENTITY_MASK::ITEM, LAYER::BOTH)) { auto player = get_entity_ptr(uid)->as(); if (player->inventory->player_slot == slot - 1) @@ -364,7 +256,7 @@ end return nullptr; }; /// Provides access to the save data, updated as soon as something changes (i.e. before it's written to savegame.sav.) Use [save_progress](#save_progress) to save to savegame.sav. - lua["savegame"] = State::get().savedata(); + lua["savegame"] = get_game_manager()->save_related->savedata.decode(); /// Standard lua print function, prints directly to the terminal but not to the game lua["lua_print"] = lua["print"]; @@ -465,8 +357,8 @@ end lua["set_interval"] = [](sol::function cb, int frames) -> CallbackId { auto backend = LuaBackend::get_calling_backend(); - auto state = State::get().ptr_main(); - auto luaCb = IntervalCallback{cb, frames, (int)state->time_level}; + int now = HeapBase::get().state()->time_level; + auto luaCb = IntervalCallback{cb, frames, now}; backend->level_timers[backend->cbcount] = luaCb; return backend->cbcount++; }; @@ -494,7 +386,7 @@ end lua["set_global_timeout"] = [](sol::function cb, int frames) -> CallbackId { auto backend = LuaBackend::get_calling_backend(); - int now = get_frame_count(); + int now = HeapBase::get().frame_count(); auto luaCb = TimeoutCallback{cb, now + frames}; backend->global_timers[backend->cbcount] = luaCb; return backend->cbcount++; @@ -547,16 +439,6 @@ end } }); - lua.create_named_table("HOTKEY_TYPE", "NORMAL", HOTKEY_TYPE::NORMAL, "GLOBAL", HOTKEY_TYPE::GLOBAL, "INPUT", HOTKEY_TYPE::INPUT); - /* HOTKEY_TYPE - // NORMAL - // Suppressed when the game window is inactive or inputting text in this tool instance (get_io().wantkeyboard == true). Can't detect if OL is in a text input and script is running in PL though. Use ImGuiIO if you need to do that. - // GLOBAL - // Enabled even when the game window is inactive and will capture keys even from other programs. - // INPUT - // Enabled even when inputting text and will override normal text input keys. - */ - /// Table of options set in the UI, added with the [register_option_functions](#Option-functions), but `nil` before any options are registered. You can also write your own options in here or override values defined in the register functions/UI before or after they are registered. Check the examples for many different use cases and saving options to disk. // lua["options"] = lua.create_named_table("options"); @@ -680,11 +562,6 @@ end return backend->get_id(); }; - /// Deprecated - /// Read the game prng state. Use [prng](#PRNG):get_pair() instead. - lua["read_prng"] = []() -> std::vector - { return read_prng(); }; - using Toast = void(const char16_t*); using Say = void(HudData*, Entity*, const char16_t*, int, bool); @@ -707,344 +584,28 @@ end say(hud, entity, message.c_str(), sound_type, top); }; - /// Add an integer option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft - /// limits, you can override them in the UI with double click. - // lua["register_option_int"] = [](std::string name, std::string desc, std::string long_desc, int value, int min, int max) - lua["register_option_int"] = sol::overload( - [](std::string name, std::string desc, std::string long_desc, int value, int min, int max) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, IntOption{value, min, max}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }, - [](std::string name, std::string desc, int value, int min, int max) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", IntOption{value, min, max}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }); - /// Add a float option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft - /// limits, you can override them in the UI with double click. - // lua["register_option_float"] = [](std::string name, std::string desc, std::string long_desc, float value, float min, float max) - lua["register_option_float"] = sol::overload( - [](std::string name, std::string desc, std::string long_desc, float value, float min, float max) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, FloatOption{value, min, max}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }, - [](std::string name, std::string desc, float value, float min, float max) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", FloatOption{value, min, max}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }); - /// Add a boolean option that the user can change in the UI. Read with `options.name`, `value` is the default. - // lua["register_option_bool"] = [](std::string name, std::string desc, std::string long_desc, bool value) - lua["register_option_bool"] = sol::overload( - [](std::string name, std::string desc, std::string long_desc, bool value) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, BoolOption{value}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }, - [](std::string name, std::string desc, bool value) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", BoolOption{value}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }); - /// Add a string option that the user can change in the UI. Read with `options.name`, `value` is the default. - // lua["register_option_string"] = [](std::string name, std::string desc, std::string long_desc, std::string value) - lua["register_option_string"] = sol::overload( - [](std::string name, std::string desc, std::string long_desc, std::string value) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, StringOption{value}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }, - [](std::string name, std::string desc, std::string value) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", StringOption{value}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }); - /// Add a combobox option that the user can change in the UI. Read the int index of the selection with `options.name`. Separate `opts` with `\0`, - /// with a double `\0\0` at the end. `value` is the default index 1..n. - // lua["register_option_combo"] = [](std::string name, std::string desc, std::string long_desc, std::string opts, int value) - lua["register_option_combo"] = sol::overload( - [](std::string name, std::string desc, std::string long_desc, std::string opts, int value) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, ComboOption{value - 1, opts}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }, - [](std::string name, std::string desc, std::string long_desc, std::string opts) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, ComboOption{0, opts}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = 1; - }, - [](std::string name, std::string desc, std::string opts, int value) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", ComboOption{value - 1, opts}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = value; - }, - [](std::string name, std::string desc, std::string opts) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", ComboOption{0, opts}}; - if (backend->lua["options"][name] == sol::nil) - backend->lua[sol::create_if_nil]["options"][name] = 1; - }); - /// Add a button that the user can click in the UI. Sets the timestamp of last click on value and runs the callback function. - // lua["register_option_button"] = [](std::string name, std::string desc, std::string long_desc, sol::function on_click) - lua["register_option_button"] = sol::overload( - [](std::string name, std::string desc, std::string long_desc, sol::function callback) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, long_desc, ButtonOption{callback}}; - backend->lua[sol::create_if_nil]["options"][name] = -1; - }, - [](std::string name, std::string desc, sol::function callback) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {desc, "", ButtonOption{callback}}; - backend->lua[sol::create_if_nil]["options"][name] = -1; - }); - /// Add custom options using the window drawing functions. Everything drawn in the callback will be rendered in the options window and the return value saved to `options[name]` or overwriting the whole `options` table if using and empty name. - /// `value` is the default value, and pretty important because anything defined in the callback function will only be defined after the options are rendered. See the example for details. - ///
The callback signature is optional on_render(GuiDrawContext draw_ctx) - lua["register_option_callback"] = [](std::string name, sol::object value, sol::function on_render) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options[name] = {"", "", CustomOption{on_render}}; - if (backend->lua["options"][name] == sol::nil) - { - if (name != "") - backend->lua[sol::create_if_nil]["options"][name] = value; - else - backend->lua[sol::create_if_nil]["options"] = value; - } - }; - /// Removes an option by name. To make complicated conditionally visible options you should probably just use register_option_callback though. - lua["unregister_option"] = [](std::string name) - { - auto backend = LuaBackend::get_calling_backend(); - backend->options.erase(name); - backend->lua["options"][name] = sol::nil; - }; - - auto spawn_liquid = sol::overload( - static_cast(::spawn_liquid), - static_cast(::spawn_liquid_ex), - static_cast(::spawn_liquid)); - /// Spawn liquids, always spawns in the front layer, will have fun effects if `entity_type` is not a liquid (only the short version, without velocity etc.). - /// Don't overuse this, you are still restricted by the liquid pool sizes and thus might crash the game. - /// `liquid_flags` - not much known about, 2 - will probably crash the game, 3 - pause_physics, 6-12 is probably agitation, surface_tension etc. set to 0 to ignore - /// `amount` - it will spawn amount x amount (so 1 = 1, 2 = 4, 3 = 6 etc.), `blobs_separation` is optional - lua["spawn_liquid"] = spawn_liquid; - /// Spawn an entity in position with some velocity and return the uid of spawned entity. - /// Uses level coordinates with [LAYER.FRONT](#LAYER) and LAYER.BACK, but player-relative coordinates with LAYER.PLAYER(n), where (n) is a player number (1-4). - lua["spawn_entity"] = spawn_entity_abs; - /// Short for [spawn_entity](#spawn_entity). - lua["spawn"] = spawn_entity_abs; - /// Spawns an entity directly on the floor below the tile at the given position. - /// Use this to avoid the little fall that some entities do when spawned during level gen callbacks. - lua["spawn_entity_snapped_to_floor"] = spawn_entity_snap_to_floor; - /// Short for [spawn_entity_snapped_to_floor](#spawn_entity_snapped_to_floor). - lua["spawn_on_floor"] = spawn_entity_snap_to_floor; - /// Spawn a grid entity, such as floor or traps, that snaps to the grid. - lua["spawn_grid_entity"] = spawn_entity_snap_to_grid; - /// Same as `spawn_entity` but does not trigger any pre-entity-spawn callbacks, so it will not be replaced by another script - lua["spawn_entity_nonreplaceable"] = spawn_entity_abs_nonreplaceable; - /// Short for [spawn_entity_nonreplaceable](#spawn_entity_nonreplaceable). - lua["spawn_critical"] = spawn_entity_abs_nonreplaceable; - /// Spawn a door to another world, level and theme and return the uid of spawned entity. - /// Uses level coordinates with LAYER.FRONT and LAYER.BACK, but player-relative coordinates with LAYER.PLAYERn - lua["spawn_door"] = spawn_door_abs; - /// Short for [spawn_door](#spawn_door). - lua["door"] = spawn_door_abs; - /// Spawn a door to backlayer. - lua["spawn_layer_door"] = spawn_backdoor_abs; - /// Short for [spawn_layer_door](#spawn_layer_door). - lua["layer_door"] = spawn_backdoor_abs; - /// Spawns apep with the choice if it going left or right, if you want the game to choose use regular spawn functions with `ENT_TYPE.MONS_APEP_HEAD` - lua["spawn_apep"] = spawn_apep; - - auto spawn_tree = sol::overload( - static_cast(::spawn_tree), - static_cast(::spawn_tree)); - /// Spawns and grows a tree - lua["spawn_tree"] = spawn_tree; - - auto spawn_mushroom = sol::overload( - static_cast(::spawn_mushroom), - static_cast(::spawn_mushroom)); - /// 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 - lua["spawn_mushroom"] = spawn_mushroom; - - auto spawn_unrolled_player_rope = sol::overload( - static_cast(::spawn_unrolled_player_rope), - static_cast(::spawn_unrolled_player_rope)); - - /// Spawns an already unrolled rope as if created by player - lua["spawn_unrolled_player_rope"] = spawn_unrolled_player_rope; - - /// NoDoc - /// Spawns an impostor lake, `top_threshold` determines how much space on top is rendered as liquid but does not have liquid physics, fill that space with real liquid - /// There needs to be other liquid in the level for the impostor lake to be visible, there can only be one impostor lake in the level - lua["spawn_impostor_lake"] = spawn_impostor_lake; - /// NoDoc - /// Fixes the bounds of impostor lakes in the liquid physics engine to match the bounds of the impostor lake entities. - lua["fix_impostor_lake_positions"] = fix_impostor_lake_positions; - /// Spawn a player in given location, if player of that slot already exist it will spawn clone, the game may crash as this is very unexpected situation - /// If you want to respawn a player that is a ghost, set in his Inventory `health` to above 0, and `time_of_death` to 0 and call this function, the ghost entity will be removed automatically - lua["spawn_player"] = spawn_player; - /// Spawn the PlayerGhost entity, it will not move and not be connected to any player, you can then use [steal_input](#steal_input) and send_input to control it - /// or change it's `player_inputs` to the `input` of real player so he can control it directly - lua["spawn_playerghost"] = spawn_playerghost; - /// Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. - /// This is run before the entity is spawned, spawn your own entity and return its uid to replace the intended spawn. - /// In many cases replacing the intended entity won't have the intended effect or will even break the game, so use only if you really know what you're doing. - ///
The callback signature is optional pre_entity_spawn(ENT_TYPE entity_type, float x, float y, int layer, Entity overlay_entity, SPAWN_TYPE spawn_flags) - lua["set_pre_entity_spawn"] = [](sol::function cb, SPAWN_TYPE flags, int mask, sol::variadic_args entity_types) -> CallbackId - { - std::vector types; - sol::type va_type = entity_types.get_type(); - if (va_type == sol::type::number) - { - types = std::vector(entity_types.begin(), entity_types.end()); - } - else if (va_type == sol::type::table) - { - types = entity_types.get>(0); - } - std::vector proper_types = get_proper_types(std::move(types)); - - auto backend = LuaBackend::get_calling_backend(); - backend->pre_entity_spawn_callbacks.push_back(EntitySpawnCallback{backend->cbcount, mask, std::move(proper_types), flags, std::move(cb)}); - return backend->cbcount++; - }; - /// Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. - /// This is run right after the entity is spawned but before and particular properties are changed, e.g. owner or velocity. - ///
The callback signature is nil post_entity_spawn(Entity ent, SPAWN_TYPE spawn_flags) - lua["set_post_entity_spawn"] = [](sol::function cb, SPAWN_TYPE flags, int mask, sol::variadic_args entity_types) -> CallbackId - { - std::vector types; - sol::type va_type = entity_types.get_type(); - if (va_type == sol::type::number) - { - types = std::vector(entity_types.begin(), entity_types.end()); - } - else if (va_type == sol::type::table) - { - types = entity_types.get>(0); - } - std::vector proper_types = get_proper_types(std::move(types)); - - auto backend = LuaBackend::get_calling_backend(); - backend->post_entity_spawn_callbacks.push_back(EntitySpawnCallback{backend->cbcount, mask, std::move(proper_types), flags, std::move(cb)}); - return backend->cbcount++; - }; - - /// Warp to a level immediately. - lua["warp"] = warp; - /// Set seed and reset run. - lua["set_seed"] = set_seed; /// Enable/disable godmode for players. lua["god"] = [](bool g) - { State::get().godmode(g); }; + { API::godmode(g); }; /// Enable/disable godmode for companions. lua["god_companions"] = [](bool g) - { State::get().godmode_companions(g); }; - /// Deprecated - /// Set level flag 18 on post room generation instead, to properly force every level to dark - lua["force_dark_level"] = [](bool g) - { State::get().darkmode(g); }; + { API::godmode_companions(g); }; /// Set the zoom level used in levels and shops. 13.5 is the default, or 12.5 for shops. See zoom_reset. lua["zoom"] = [](float level) - { State::get().zoom(level); }; + { API::zoom(level); }; /// Reset the default zoom levels for all areas and sets current zoom level to 13.5. lua["zoom_reset"] = []() - { State::get().zoom_reset(); }; - auto move_entity_abs = sol::overload( - static_cast(::move_entity_abs), - static_cast(::move_entity_abs)); - /// Teleport entity to coordinates with optional velocity - lua["move_entity"] = move_entity_abs; - /// Teleport grid entity, the destination should be whole number, this ensures that the collisions will work properly - lua["move_grid_entity"] = move_grid_entity; - auto destroy_grid = sol::overload( - static_cast(::destroy_grid), - static_cast(::destroy_grid)); - /// Destroy the grid entity (by uid or position), and its item entities, removing them from the grid without dropping particles or gold. - /// Will also destroy monsters or items that are standing on a linked activefloor or chain, though excludes MASK.PLAYER to prevent crashes - lua["destroy_grid"] = destroy_grid; - /// Make an ENT_TYPE.FLOOR_DOOR_EXIT go to world `w`, level `l`, theme `t` - lua["set_door_target"] = set_door_target; - /// Short for [set_door_target](#set_door_target). - lua["set_door"] = set_door_target; - /// Get door target `world`, `level`, `theme` - lua["get_door_target"] = get_door_target; + { API::zoom_reset(); }; + /// Set the contents of [Coffin](#Coffin), [Present](#Present), [Pot](#Pot), [Container](#Container) /// Check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) for what the exact ENT_TYPE's can this function affect lua["set_contents"] = set_contents; - /// Get the Entity behind an uid, converted to the correct type. To see what type you will get, consult the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) - // lua["get_entity"] = [](uint32_t uid) -> Entity*{}; - /// NoDoc - /// Get the [Entity](#Entity) behind an uid, without converting to the correct type (do not use, use `get_entity` instead) - lua["get_entity_raw"] = get_entity_ptr; - lua.script(R"##( - function cast_entity(entity_raw) - if entity_raw == nil then - return nil - end - local cast_fun = TYPE_MAP[entity_raw.type.id] - if cast_fun ~= nil then - return cast_fun(entity_raw) - else - return entity_raw - end - end - function get_entity(ent_uid) - if ent_uid == nil then - return nil - end - - local entity_raw = get_entity_raw(ent_uid) - if entity_raw == nil then - return nil - end - - return cast_entity(entity_raw) - end - )##"); - /// Get the [EntityDB](#EntityDB) behind an ENT_TYPE... - 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 overlapping 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; /// Returns a list of all uids in `entities` for which `predicate(get_entity(uid))` returns true lua["filter_entities"] = [&lua](std::vector entities, sol::function predicate) -> std::vector { @@ -1053,8 +614,8 @@ end }; auto get_entities_by = sol::overload( - static_cast (*)(ENT_TYPE, uint32_t, LAYER)>(::get_entities_by), - static_cast (*)(std::vector, uint32_t, LAYER)>(::get_entities_by)); + static_cast (*)(ENT_TYPE, ENTITY_MASK, LAYER)>(::get_entities_by), + static_cast (*)(std::vector, ENTITY_MASK, LAYER)>(::get_entities_by)); /// Get uids of entities by some conditions ([ENT_TYPE](#ENT_TYPE), [MASK](#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. /// Recommended to always set the mask, even if you look for one entity type lua["get_entities_by"] = get_entities_by; @@ -1078,52 +639,20 @@ end } return std::vector({}); }; - /// Deprecated - /// Use `get_entities_by(0, mask, LAYER.BOTH)` instead - lua["get_entities_by_mask"] = get_entities_by_mask; - /// Deprecated - /// Use `get_entities_by(0, MASK.ANY, layer)` instead - lua["get_entities_by_layer"] = get_entities_by_layer; auto get_entities_at = sol::overload( - static_cast (*)(ENT_TYPE, uint32_t, float, float, LAYER, float)>(::get_entities_at), - static_cast (*)(std::vector, uint32_t, float, float, LAYER, float)>(::get_entities_at)); + static_cast (*)(ENT_TYPE, ENTITY_MASK, float, float, LAYER, float)>(::get_entities_at), + static_cast (*)(std::vector, ENTITY_MASK, float, float, LAYER, float)>(::get_entities_at)); /// Get uids of matching entities inside some radius ([ENT_TYPE](#ENT_TYPE), [MASK](#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types /// Recommended to always set the mask, even if you look for one entity type lua["get_entities_at"] = get_entities_at; - auto get_entities_overlapping = sol::overload( - static_cast (*)(ENT_TYPE, uint32_t, float, float, float, float, LAYER)>(::get_entities_overlapping), - static_cast (*)(std::vector, uint32_t, float, float, float, float, LAYER)>(::get_entities_overlapping)); - /// Deprecated - /// Use `get_entities_overlapping_hitbox` instead - lua["get_entities_overlapping"] = get_entities_overlapping; - auto get_entities_overlapping_hitbox = sol::overload( - static_cast (*)(ENT_TYPE, uint32_t, AABB, LAYER)>(::get_entities_overlapping_hitbox), - static_cast (*)(std::vector, uint32_t, AABB, LAYER)>(::get_entities_overlapping_hitbox)); + static_cast (*)(ENT_TYPE, ENTITY_MASK, AABB, LAYER)>(::get_entities_overlapping_hitbox), + static_cast (*)(std::vector, ENTITY_MASK, AABB, LAYER)>(::get_entities_overlapping_hitbox)); /// Get uids of matching entities overlapping with the given hitbox. Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types lua["get_entities_overlapping_hitbox"] = get_entities_overlapping_hitbox; - /// Attaches `attachee` to `overlay`, similar to setting `get_entity(attachee).overlay = get_entity(overlay)`. - /// However this function offsets `attachee` (so you don't have to) and inserts it into `overlay`'s inventory. - lua["attach_entity"] = attach_entity_by_uid; - /// Get the `flags` field from entity by uid - lua["get_entity_flags"] = get_entity_flags; - /// Set the `flags` field from entity by uid - lua["set_entity_flags"] = set_entity_flags; - /// Get the `more_flags` field from entity by uid - lua["get_entity_flags2"] = get_entity_flags2; - /// Set the `more_flags` field from entity by uid - lua["set_entity_flags2"] = set_entity_flags2; - /// Deprecated - /// As the name is misleading. use Movable.`move_state` field instead - lua["get_entity_ai_state"] = get_entity_ai_state; - /// Get `state.level_flags` - lua["get_level_flags"] = get_level_flags; - /// Set `state.level_flags` - lua["set_level_flags"] = set_level_flags; - /// Get the ENT_TYPE... of the entity by uid - lua["get_entity_type"] = get_entity_type; + /// Get the current set zoom level lua["get_zoom_level"] = []() -> float { @@ -1132,191 +661,50 @@ end }; /// Get the game coordinates at the screen position (`x`, `y`) lua["game_position"] = [](float x, float y) -> std::pair - { return State::click_position(x, y); }; + { return API::click_position(x, y); }; /// Translate an entity position to screen position to be used in drawing functions lua["screen_position"] = [](float x, float y) -> std::pair - { return State::screen_position(x, y); }; + { return API::screen_position(x, y); }; /// Translate a distance of `x` tiles to screen distance to be be used in drawing functions lua["screen_distance"] = screen_distance; - /// Get position `x, y, layer` of entity by uid. Use this, don't use `Entity.x/y` because those are sometimes just the offset to the entity - /// you're standing on, not real level coordinates. - lua["get_position"] = [](int32_t uid) -> std::tuple - { - Entity* ent = get_entity_ptr(uid); - if (ent) - { - auto pos = ent->abs_position(); - return {pos.x, pos.y, ent->layer}; - } - return {}; - }; - /// Get interpolated render position `x, y, layer` of entity by uid. This gives smooth hitboxes for 144Hz master race etc... - lua["get_render_position"] = [](int32_t uid) -> std::tuple - { - Entity* ent = get_entity_ptr(uid); - if (ent) - { - if (ent->rendering_info != nullptr && !ent->rendering_info->render_inactive) - return std::make_tuple(ent->rendering_info->x, ent->rendering_info->y, ent->layer); - else - { - auto pos = ent->abs_position(); - return {pos.x, pos.y, ent->layer}; - } - } - return {}; - }; - /// Get velocity `vx, vy` of an entity by uid. Use this to get velocity relative to the game world, (the `Entity.velocityx/velocityy` are relative to `Entity.overlay`). Only works for movable or liquid entities - lua["get_velocity"] = [](int32_t uid) -> std::tuple - { - Entity* ent = get_entity_ptr(uid); - if (ent) - return ent->get_absolute_velocity(); - - return {}; - }; - /// Remove item by uid from entity. `check_autokill` defaults to true, checks if entity should be killed when missing overlay and kills it if so (can help with avoiding crashes) - lua["entity_remove_item"] = entity_remove_item; - /// Spawns and attaches ball and chain to `uid`, the initial position of the ball is at the entity position plus `off_x`, `off_y` - lua["attach_ball_and_chain"] = attach_ball_and_chain; - /// Spawn an entity of `entity_type` attached to some other entity `over_uid`, in offset `x`, `y` - lua["spawn_entity_over"] = spawn_entity_over; - /// Short for [spawn_entity_over](#spawn_entity_over) - lua["spawn_over"] = spawn_entity_over; - /// Check if the entity `uid` has some specific `item_uid` by uid in their inventory - lua["entity_has_item_uid"] = entity_has_item_uid; - - auto entity_has_item_type = sol::overload( - static_cast(::entity_has_item_type), - static_cast)>(::entity_has_item_type)); - /// Check if the entity `uid` has some ENT_TYPE `entity_type` in their inventory, can also use table of entity_types - lua["entity_has_item_type"] = entity_has_item_type; - - auto entity_get_items_by = sol::overload( - static_cast (*)(uint32_t, ENT_TYPE, uint32_t)>(::entity_get_items_by), - static_cast (*)(uint32_t, std::vector, uint32_t)>(::entity_get_items_by)); - /// Gets uids of entities attached to given entity uid. Use `entity_type` and `mask` ([MASK](#MASK)) to filter, set them to 0 to return all attached entities. - lua["entity_get_items_by"] = entity_get_items_by; - /// Kills an entity by uid. `destroy_corpse` defaults to `true`, if you are killing for example a caveman and want the corpse to stay make sure to pass `false`. - lua["kill_entity"] = kill_entity; - /// Pick up another entity by uid. Make sure you're not already holding something, or weird stuff will happen. - lua["pick_up"] = pick_up; - /// Drop held entity, `what_uid` optional, if set, it will check if entity is holding that entity first before dropping it - lua["drop"] = drop; - /// Unequips the currently worn backitem - lua["unequip_backitem"] = unequip_backitem; - /// Returns the uid of the currently worn backitem, or -1 if wearing nothing - lua["worn_backitem"] = worn_backitem; - /// Apply changes made in [get_type](#get_type)() to entity instance by uid. - lua["apply_entity_db"] = apply_entity_db; - /// Try to lock the exit at coordinates - lua["lock_door_at"] = lock_door_at; - /// Try to unlock the exit at coordinates - lua["unlock_door_at"] = unlock_door_at; - /// Get the current frame count since the game was started. You can use this to make some timers yourself, the engine runs at 60fps. This counter is paused if you block PRE_UPDATE from running, and also doesn't increment during some loading screens, even though state update still runs. - lua["get_frame"] = get_frame_count; + /// Get the current frame count since the game was started*. You can use this to make some timers yourself, the engine runs at 60fps. This counter is paused if the pause is set with flags PAUSE.FADE or PAUSE.ANKH. + lua["get_frame"] = []() -> uint32_t + { return HeapBase::get().frame_count(); }; /// Get the current global frame count since the game was started. You can use this to make some timers yourself, the engine runs at 60fps. This counter keeps incrementing when state is updated, even during loading screens. - lua["get_global_frame"] = get_global_frame_count; + // lua["get_global_frame"] = []() -> int + lua["get_global_frame"] = API::get_global_frame_count; /// Get the current timestamp in milliseconds since the Unix Epoch. lua["get_ms"] = []() { return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); }; - /// Make `mount_uid` carry `rider_uid` on their back. Only use this with actual mounts and living things. - lua["carry"] = carry; - /// Deprecated - /// Use [replace_drop](#replace_drop)(DROP.ARROWTRAP_WOODENARROW, new_arrow_type) and [replace_drop](#replace_drop)(DROP.POISONEDARROWTRAP_WOODENARROW, new_arrow_type) instead - lua["set_arrowtrap_projectile"] = set_arrowtrap_projectile; - /// Sets the amount of blood drops in the Kapala needed to trigger a health increase (default = 7). - lua["set_kapala_blood_threshold"] = set_kapala_blood_threshold; - /// Sets the hud icon for the Kapala (0-6 ; -1 for default behaviour). - /// If you set a Kapala treshold greater than 7, make sure to set the hud icon in the range 0-6, or other icons will appear in the hud! - lua["set_kapala_hud_icon"] = set_kapala_hud_icon; - /// Changes characteristics of (all) sparktraps: speed, rotation direction and distance from center - /// Speed: expressed as the amount that should be added to the angle every frame (use a negative number to go in the other direction) - /// Distance from center: if you go above 3.0 the game might crash because a spark may go out of bounds! - lua["modify_sparktraps"] = modify_sparktraps; - /// 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 - lua["activate_sparktraps_hack"] = activate_sparktraps_hack; - /// Set layer to search for storage items on - lua["set_storage_layer"] = set_storage_layer; - /// Deprecated - /// This function never worked properly as too many places in the game individually check for vlads cape and calculate the blood multiplication - /// `default_multiplier` doesn't do anything due to some changes in last game updates, `vladscape_multiplier` only changes the multiplier to some entities death's blood spit - lua["set_blood_multiplication"] = set_blood_multiplication; - /// Flip entity around by uid. All new entities face right by default. - lua["flip_entity"] = flip_entity; - /// Sets the Y-level at which Olmec changes phases - lua["set_olmec_phase_y_level"] = set_olmec_phase_y_level; - /// Forces Olmec to stay on phase 0 (stomping) - lua["force_olmec_phase_0"] = force_olmec_phase_0; - /// Determines when the ghost appears, either when the player is cursed or not - lua["set_ghost_spawn_times"] = set_ghost_spawn_times; - /// Determines whether the ghost appears when breaking the ghost pot - lua["set_cursepot_ghost_enabled"] = set_cursepot_ghost_enabled; - /// Determines whether the time ghost appears, including the showing of the ghost toast - lua["set_time_ghost_enabled"] = set_time_ghost_enabled; - /// Determines whether the time jelly appears in cosmic ocean - lua["set_time_jelly_enabled"] = set_time_jelly_enabled; /// Enables or disables the journal - lua["set_journal_enabled"] = set_journal_enabled; - /// Enables or disables the default position based camp camera bounds, to set them manually yourself - lua["set_camp_camera_bounds_enabled"] = set_camp_camera_bounds_enabled; - /// Sets which entities are affected by a bomb explosion. Default = MASK.PLAYER | MASK.MOUNT | MASK.MONSTER | MASK.ITEM | MASK.ACTIVEFLOOR | MASK.FLOOR - lua["set_explosion_mask"] = set_explosion_mask; - /// Sets the maximum length of a thrown rope (anchor segment not included). Unfortunately, setting this higher than default (6) creates visual glitches in the rope, even though it is fully functional. - lua["set_max_rope_length"] = set_max_rope_length; + lua["set_journal_enabled"] = [](bool b) + { get_journal_enabled() = b; }; /// Checks whether a coordinate is inside a room containing an active shop. This function checks whether the shopkeeper is still alive. lua["is_inside_active_shop_room"] = is_inside_active_shop_room; /// Checks whether a coordinate is inside a shop zone, the rectangle where the camera zooms in a bit. Does not check if the shop is still active! lua["is_inside_shop_zone"] = is_inside_shop_zone; - /// Returns how many of a specific entity type Waddler has stored - lua["waddler_count_entity"] = waddler_count_entity; - /// Store an entity type in Waddler's storage. Returns the slot number the item was stored in or -1 when storage is full and the item couldn't be stored. - lua["waddler_store_entity"] = waddler_store_entity; - /// Removes an entity type from Waddler's storage. Second param determines how many of the item to remove (default = remove all) - lua["waddler_remove_entity"] = waddler_remove_entity; - /// Gets the 16-bit meta-value associated with the entity type in the associated slot - lua["waddler_get_entity_meta"] = waddler_get_entity_meta; - /// Sets the 16-bit meta-value associated with the entity type in the associated slot - lua["waddler_set_entity_meta"] = waddler_set_entity_meta; - /// Gets the entity type of the item in the provided slot - lua["waddler_entity_type_in_slot"] = waddler_entity_type_in_slot; - /// Spawn a companion (hired hand, player character, eggplant child) - lua["spawn_companion"] = spawn_companion; - - /// Calculate the tile distance of two entities by uid - lua["distance"] = [](uint32_t uid_a, uint32_t uid_b) -> float - { - Entity* ea = get_entity_ptr(uid_a); - Entity* eb = get_entity_ptr(uid_b); - if (ea == nullptr || eb == nullptr) - return -1.0f; - else - return (float)std::sqrt(std::pow(ea->abs_position().x - eb->abs_position().x, 2) + std::pow(ea->abs_position().y - eb->abs_position().y, 2)); - }; /// Basically gets the absolute coordinates of the area inside the unbreakable bedrock walls, from wall to wall. Every solid entity should be /// inside these boundaries. The order is: left x, top y, right x, bottom y lua["get_bounds"] = []() -> std::tuple { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); return std::make_tuple(2.5f, 122.5f, state->w * 10.0f + 2.5f, 122.5f - state->h * 8.0f); }; /// Same as [get_bounds](#get_bounds) but returns AABB struct instead of loose floats lua["get_aabb_bounds"] = []() -> AABB { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); return {2.5f, 122.5f, state->w * 10.0f + 2.5f, 122.5f - state->h * 8.0f}; }; /// Gets the current camera position in the level lua["get_camera_position"] = []() -> std::pair - { - return State::get_camera_position(); - }; + { return Camera::get_position(); }; /// Sets the absolute current camera position without rubberbanding animation. Ignores camera bounds or currently focused uid, but doesn't clear them. Best used in ON.RENDER_PRE_GAME or similar. See Camera for proper camera handling with bounds and rubberbanding. - lua["set_camera_position"] = set_camera_position; + lua["set_camera_position"] = [](float cx, float cy) + { HeapBase::get().state()->camera->set_position(cx, cy); }; /// Updates the camera focus according to the params set in Camera, i.e. to apply normal camera movement when paused etc. - lua["update_camera_position"] = update_camera_position; + lua["update_camera_position"] = []() + { HeapBase::get().state()->camera->update_position(); }; /// Set the nth bit in a number. This doesn't actually change the variable you pass, it just returns the new value you can use. lua["set_flag"] = [](Flags flags, int bit) -> Flags @@ -1353,133 +741,12 @@ end /// Gets the resolution (width and height) of the screen lua["get_window_size"] = []() -> std::tuple { return {(int)ImGui::GetIO().DisplaySize.x, (int)ImGui::GetIO().DisplaySize.y}; }; - - /// Deprecated - /// Deprecated because it's a weird old hack that crashes the game. You can modify inputs in many other ways, like editing `state.player_inputs.player_slot_1.buttons_gameplay` in PRE_UPDATE or a `set_pre_process_input` hook. Steal input from a Player, HiredHand or PlayerGhost. - lua["steal_input"] = [](int uid) - { - static const auto player_ghost = to_id("ENT_TYPE_ITEM_PLAYERGHOST"); - static const auto ana = to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); - static const auto egg_child = to_id("ENT_TYPE_CHAR_EGGPLANT_CHILD"); - - auto backend = LuaBackend::get_calling_backend(); - if (backend->script_input.find(uid) != backend->script_input.end()) - return; - Player* player = get_entity_ptr(uid)->as(); - if (player == nullptr) - return; - - if (player->type->id == player_ghost) - { - auto player_gh = player->as(); - ScriptInput* newinput = new ScriptInput(); - newinput->gameplay = 0; - newinput->all = 0; - newinput->orig_input = player_gh->player_inputs; - player_gh->player_inputs = reinterpret_cast(newinput); - backend->script_input[uid] = newinput; - } - else - { - if (player->type->id < ana || player->type->id > egg_child) - return; - - ScriptInput* newinput = new ScriptInput(); - newinput->gameplay = 0; - newinput->all = 0; - newinput->orig_input = player->input_ptr; - newinput->orig_ai = player->ai; - player->input_ptr = reinterpret_cast(newinput); - player->ai = nullptr; - backend->script_input[uid] = newinput; - } - }; - /// Deprecated - /// Return input previously stolen with [steal_input](#steal_input) - lua["return_input"] = [](int uid) - { - static const auto player_ghost = to_id("ENT_TYPE_ITEM_PLAYERGHOST"); - - auto backend = LuaBackend::get_calling_backend(); - if (backend->script_input.find(uid) == backend->script_input.end()) - return; - Player* player = get_entity_ptr(uid)->as(); - if (player == nullptr) - return; - - if (player->type->id == player_ghost) - { - auto player_gh = player->as(); - player_gh->player_inputs = backend->script_input[uid]->orig_input; - } - else - { - player->input_ptr = backend->script_input[uid]->orig_input; - player->ai = backend->script_input[uid]->orig_ai; - } - backend->script_input.erase(uid); - }; - /// Deprecated - /// Send input to entity, has to be previously stolen with [steal_input](#steal_input) - lua["send_input"] = [](int uid, INPUTS buttons) - { - auto backend = LuaBackend::get_calling_backend(); - auto it = backend->script_input.find(uid); - if (it != backend->script_input.end()) - { - it->second->all = buttons; - it->second->gameplay = buttons; - } - }; - /// Deprecated - /// Use `players[1].input.buttons_gameplay` for only the inputs during the game, or `.buttons` for all the inputs, even during the pause menu - /// Of course, you can get the Player by other mean, it doesn't need to be the `players` table - /// You can only read inputs from actual players, HH don't have any inputs - lua["read_input"] = [](int uid) -> INPUTS - { - Player* player = get_entity_ptr(uid)->as(); - if (player == nullptr) - return (INPUTS)0; - - if (!IsBadReadPtr(player->input_ptr, 20)) - { - return player->input_ptr->buttons_gameplay; - } - return (INPUTS)0; - }; - /// Deprecated - /// Read input that has been previously stolen with [steal_input](#steal_input) - /// Use `state.player_inputs.player_slots[player_slot].buttons_gameplay` for only the inputs during the game, or `.buttons` for all the inputs, even during the pause menu - lua["read_stolen_input"] = [](int uid) -> INPUTS - { - auto backend = LuaBackend::get_calling_backend(); - if (backend->script_input.find(uid) == backend->script_input.end()) - { - // this means that the input is attacked to the real input and not stolen so return early - return (INPUTS)0; - } - Player* player = get_entity_ptr(uid)->as(); - if (player == nullptr) - return (INPUTS)0; - ScriptInput* readinput = reinterpret_cast(player->input_ptr); - if (!IsBadReadPtr(readinput, 20)) - { - readinput = reinterpret_cast(readinput->orig_input); - if (!IsBadReadPtr(readinput, 20)) - { - return readinput->gameplay; - } - } - return (INPUTS)0; - }; - /// Clears a callback that is specific to a screen. lua["clear_screen_callback"] = [](int screen_id, CallbackId cb_id) { auto backend = LuaBackend::get_calling_backend(); backend->clear_screen_hooks.push_back({screen_id, cb_id}); }; - /// Returns unique id for the callback to be used in [clear_screen_callback](#clear_screen_callback) or `nil` if screen_id is not valid. /// Sets a callback that is called right before the screen is drawn, return `true` to skip the default rendering. ///
The callback signature is bool render_screen(Screen self, VanillaRenderContext render_ctx) @@ -1528,75 +795,6 @@ end } return sol::nullopt; }; - - /// Deprecated - /// Use `entity.clear_virtual` instead. - /// Clears a callback that is specific to an entity. - lua["clear_entity_callback"] = [](int uid, CallbackId cb_id) - { - auto backend = LuaBackend::get_calling_backend(); - backend->HookHandler::clear_hook(cb_id, uid); - }; - /// Deprecated - /// Use `entity:set_pre_update_state_machine` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// `uid` has to be the uid of a `Movable` or else stuff will break. - /// Sets a callback that is called right before the statemachine, return `true` to skip the statemachine update. - /// Use this only when no other approach works, this call can be expensive if overused. - /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. - ///
The callback signature is bool statemachine(Entity self) - lua["set_pre_statemachine"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - return lua["Entity"]["set_pre_update_state_machine"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_post_update_state_machine` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// `uid` has to be the uid of a `Movable` or else stuff will break. - /// Sets a callback that is called right after the statemachine, so you can override any values the satemachine might have set (e.g. `animation_frame`). - /// Use this only when no other approach works, this call can be expensive if overused. - /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. - ///
The callback signature is nil statemachine(Entity self) - lua["set_post_statemachine"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - return lua["Entity"]["set_post_update_state_machine"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_pre_destroy` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right when an entity is destroyed, e.g. as if by `Entity.destroy()` before the game applies any side effects. - /// Use this only when no other approach works, this call can be expensive if overused. - ///
The callback signature is nil on_destroy(Entity self) - lua["set_on_destroy"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - return lua["Entity"]["set_pre_destroy"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_pre_kill` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right when an entity is eradicated, before the game applies any side effects. - /// Use this only when no other approach works, this call can be expensive if overused. - ///
The callback signature is nil on_kill(Entity self, Entity killer) - lua["set_on_kill"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - return lua["Entity"]["set_pre_kill"](ent, std::move(fun)); - } - return sol::nullopt; - }; /// Returns unique id for the callback to be used in [clear_callback](#clear_callback) or `nil` if uid is not valid. /// Sets a callback that is called right when an player/hired hand is crushed/insta-gibbed, return `true` to skip the game's crush handling. /// The game's instagib function will be forcibly executed (regardless of whatever you return in the callback) when the entity's health is zero. @@ -1613,156 +811,6 @@ end } return sol::nullopt; }; - /// Deprecated - /// Use `entity:set_pre_damage` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right before an entity is damaged, return `true` to skip the game's damage handling. - /// Note that damage_dealer can be nil ! (long fall, ...) - /// DO NOT CALL `self:damage()` in the callback ! - /// Use this only when no other approach works, this call can be expensive if overused. - /// The entity has to be of a [Movable](#Movable) type. - ///
The callback signature is bool on_damage(Entity self, Entity damage_dealer, int damage_amount, float vel_x, float vel_y, int stun_amount, int iframes) - lua["set_on_damage"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid); ent != nullptr && ent->is_movable()) - { - return lua["Movable"]["set_pre_damage"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_pre_floor_update` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right before a floor is updated (by killed neighbor), return `true` to skip the game's neighbor update handling. - /// Use this only when no other approach works, this call can be expensive if overused. - ///
The callback signature is bool pre_floor_update(Entity self) - lua["set_pre_floor_update"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) // TODO: Requires ent->is_floor - { - return lua["Floor"]["set_pre_floor_update"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_post_floor_update` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right after a floor is updated (by killed neighbor). - /// Use this only when no other approach works, this call can be expensive if overused. - ///
The callback signature is nil post_floor_update(Entity self) - lua["set_post_floor_update"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) // TODO: Requires ent->is_floor - { - return lua["Floor"]["set_post_floor_update"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_pre_trigger_action` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right when a container is opened by the player (up+whip) - /// Use this only when no other approach works, this call can be expensive if overused. - /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. - ///
The callback signature is nil on_open(Entity entity_self, Entity opener) - lua["set_on_open"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) // TODO: Requires ent->is_container - { - return lua["Entity"]["set_pre_trigger_action"]( - ent, - [fun = std::move(fun)](Entity* usee, Entity* user) - { - if (user->is_movable() && user->as()->movey > 0) - { - auto backend = LuaBackend::get_calling_backend(); - handle_function(backend.get(), fun, usee, user); - } - }); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_pre_collision1` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right before the collision 1 event, return `true` to skip the game's collision handling. - /// Use this only when no other approach works, this call can be expensive if overused. - /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. - ///
The callback signature is bool pre_collision1(Entity entity_self, Entity collision_entity) - lua["set_pre_collision1"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - return lua["Entity"]["set_pre_on_collision1"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity:set_pre_collision2` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right before the collision 2 event, return `true` to skip the game's collision handling. - /// Use this only when no other approach works, this call can be expensive if overused. - /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. - ///
The callback signature is bool pre_collision12(Entity self, Entity collision_entity) - lua["set_pre_collision2"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - return lua["Entity"]["set_pre_on_collision2"](ent, std::move(fun)); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity.rendering_info:set_pre_render` in combination with `render_info:get_entity` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right after the entity is rendered. - /// Return `true` to skip the original rendering function and all later pre_render callbacks. - /// Use this only when no other approach works, this call can be expensive if overused. - ///
The callback signature is bool render(VanillaRenderContext render_ctx, Entity self) - lua["set_pre_render"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - auto backend_id = LuaBackend::get_calling_backend_id(); - return lua["RenderInfo"]["set_pre_render"]( - ent->rendering_info, - [backend_id, fun = std::move(fun)](RenderInfo* ri, float*, VanillaRenderContext render_ctx) - { - auto backend = LuaBackend::get_backend(backend_id); - return handle_function( - backend.get(), - fun, - render_ctx, - ri->get_entity()); - }); - } - return sol::nullopt; - }; - /// Deprecated - /// Use `entity.rendering_info:set_post_render` in combination with `render_info:get_entity` instead. - /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. - /// Sets a callback that is called right after the entity is rendered. - /// Use this only when no other approach works, this call can be expensive if overused. - ///
The callback signature is nil post_render(VanillaRenderContext render_ctx, Entity self) - lua["set_post_render"] = [&lua](int uid, sol::function fun) -> sol::optional - { - if (Entity* ent = get_entity_ptr(uid)) - { - auto backend_id = LuaBackend::get_calling_backend_id(); - return lua["RenderInfo"]["set_post_render"]( - ent->rendering_info, - [backend_id, fun = std::move(fun)](RenderInfo* ri, float*, VanillaRenderContext render_ctx) - { - auto backend = LuaBackend::get_backend(backend_id); - return handle_function( - backend.get(), - fun, - render_ctx, - ri->get_entity()); - }); - } - return sol::nullopt; - }; /// Raise a signal and probably crash the game lua["raise"] = std::raise; @@ -1770,94 +818,35 @@ end /// Convert the hash to stringid /// Check [strings00_hashed.str](https://github.com/spelunky-fyi/overlunky/blob/main/docs/game_data/strings00_hashed.str) for the hash values, or extract assets with modlunky and check those. lua["hash_to_stringid"] = hash_to_stringid; - /// Get string behind STRINGID, **don't use stringid directly for vanilla string**, use [hash_to_stringid](#hash_to_stringid) first /// Will return the string of currently choosen language lua["get_string"] = get_string; - /// Change string at the given id (**don't use stringid directly for vanilla string**, use [hash_to_stringid](#hash_to_stringid) first) /// This edits custom string and in game strings but changing the language in settings will reset game strings lua["change_string"] = [](STRINGID id, std::u16string str) - { - return change_string(id, str); - }; - + { return change_string(id, str); }; /// Add custom string, currently can only be used for names of shop items (EntityDB->description) /// Returns STRINGID of the new string lua["add_string"] = add_string; - - /// Get localized name of an entity from the journal, 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) -> std::u16string - { - 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; - /// Clears the name set with [add_custom_name](#add_custom_name) lua["clear_custom_name"] = clear_custom_name; - /// Calls the enter door function, position doesn't matter, can also enter closed doors (like COG, EW) without unlocking them - lua["enter_door"] = enter_door; - - /// Change ENT_TYPE's spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4:
- /// {MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER}
- /// Use empty table as argument to reset to the game default - lua["change_sunchallenge_spawns"] = change_sunchallenge_spawns; - - /// Change ENT_TYPE's spawned in dice shops (Madame Tusk as well), by default there are 25:
- /// {ITEM_PICKUP_BOMBBAG, ITEM_PICKUP_BOMBBOX, ITEM_PICKUP_ROPEPILE, ITEM_PICKUP_COMPASS, ITEM_PICKUP_PASTE, ITEM_PICKUP_PARACHUTE, ITEM_PURCHASABLE_CAPE, ITEM_PICKUP_SPECTACLES, ITEM_PICKUP_CLIMBINGGLOVES, ITEM_PICKUP_PITCHERSMITT, - /// ENT_TYPE_ITEM_PICKUP_SPIKESHOES, ENT_TYPE_ITEM_PICKUP_SPRINGSHOES, ITEM_MACHETE, ITEM_BOOMERANG, ITEM_CROSSBOW, ITEM_SHOTGUN, ITEM_FREEZERAY, ITEM_WEBGUN, ITEM_CAMERA, ITEM_MATTOCK, ITEM_PURCHASABLE_JETPACK, ITEM_PURCHASABLE_HOVERPACK, - /// ITEM_TELEPORTER, ITEM_PURCHASABLE_TELEPORTER_BACKPACK, ITEM_PURCHASABLE_POWERPACK}
- /// Min 6, Max 255, if you want less then 6 you need to write some of them more then once (they will have higher "spawn chance"). - /// If you use this function in the level with dice shop in it, you have to update `item_ids` in the [ITEM_DICE_PRIZE_DISPENSER](#PrizeDispenser). - /// Use empty table as argument to reset to the game default - lua["change_diceshop_prizes"] = change_diceshop_prizes; - - /// Change ENT_TYPE's spawned when you damage the altar, by default there are 6:
- /// {MONS_BAT, MONS_BEE, MONS_SPIDER, MONS_JIANGSHI, MONS_FEMALE_JIANGSHI, MONS_VAMPIRE}
- /// Max 255 types. - /// Use empty table as argument to reset to the game default - lua["change_altar_damage_spawns"] = change_altar_damage_spawns; - - /// Change ENT_TYPE's spawned when Waddler dies, by default there are 3:
- /// {ITEM_PICKUP_COMPASS, ITEM_CHEST, ITEM_KEY}
- /// Max 255 types. - /// Use empty table as argument to reset to the game default - lua["change_waddler_drop"] = change_waddler_drop; - - /// Poisons entity, to cure poison set [Movable](#Movable).`poison_tick_timer` to -1 - lua["poison_entity"] = poison_entity; - - /// Change how much health the ankh gives you after death, with every beat (the heart beat effect) it will add `beat_add_health` to your health, - /// `beat_add_health` has to be divisor of `health` and can't be 0, otherwise the function does nothing. Set `health` to 0 to return to the game defaults - /// If you set `health` above the game max health it will be forced down to the game max - lua["modify_ankh_health_gain"] = modify_ankh_health_gain; - /// Adds entity as shop item, has to be of [Purchasable](#Purchasable) type, check the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) to find all the Purchasable entity types. /// Adding other entities will result in not obtainable items or game crash lua["add_item_to_shop"] = add_item_to_shop; - /// Change the amount of frames after the damage from poison is applied - lua["change_poison_timer"] = change_poison_timer; - auto create_illumination = sol::overload( static_cast(::create_illumination), static_cast(::create_illumination), static_cast(::create_illumination)); /// Creates a new Illumination. Don't forget to continuously call [refresh_illumination](#refresh_illumination), otherwise your light emitter fades out! Check out the [illumination.lua](https://github.com/spelunky-fyi/overlunky/blob/main/examples/illumination.lua) script for an example. lua["create_illumination"] = create_illumination; - /// Refreshes an Illumination, keeps it from fading out (updates the timer, keeping it in sync with the game render) + /// Refreshes an Illumination, keeps it from fading out, short for `illumination.timer = get_frame()` lua["refresh_illumination"] = refresh_illumination; - /// 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 // lua["enum_get_name"] = [](table enum, int value) -> string lua["enum_get_name"] = lua.safe_script(R"( @@ -1899,9 +888,7 @@ end lua["get_setting"] = get_setting; /// Sets the specified setting temporarily. These values are not saved and might reset to the users real settings if they visit the options menu. (Check example.) All settings are available in unsafe mode and only a smaller subset SAFE_SETTING by default for Hud and other visuals. Returns false, if setting failed. - // lua["set_setting"] = set_setting; - /// NoDoc - lua["set_setting"] = [](GAME_SETTING setting, std::uint32_t value) + lua["set_setting"] = [](GAME_SETTING setting, std::uint32_t value) -> bool { auto backend = LuaBackend::get_calling_backend(); bool is_safe = std::find(std::begin(safe_settings), std::end(safe_settings), setting) != std::end(safe_settings); @@ -1923,30 +910,6 @@ end end )"); - /// Spawn a Shopkeeper in the coordinates and make the room their shop. Returns the Shopkeeper uid. Also see [spawn_roomowner](#spawn_roomowner). - // lua["spawn_shopkeeper"] = [](float x, float y, LAYER layer, ROOM_TEMPLATE room_template = ROOM_TEMPLATE.SHOP) -> uint32_t - lua["spawn_shopkeeper"] = sol::overload( - [](float x, float y, LAYER layer) - { - return spawn_shopkeeper(x, y, layer); - }, - [](float x, float y, LAYER layer, ROOM_TEMPLATE room_template) - { - return spawn_shopkeeper(x, y, layer, room_template); - }); - - /// Spawn a RoomOwner (or a few other like [CavemanShopkeeper](#CavemanShopkeeper)) in the coordinates and make them own the room, optionally changing the room template. Returns the RoomOwner uid. - // lua["spawn_roomowner"] = [](ENT_TYPE owner_type, float x, float y, LAYER layer, ROOM_TEMPLATE room_template = -1) -> uint32_t - lua["spawn_roomowner"] = sol::overload( - [](ENT_TYPE owner_type, float x, float y, LAYER layer) - { - return spawn_roomowner(owner_type, x, y, layer); - }, - [](ENT_TYPE owner_type, float x, float y, LAYER layer, int16_t room_template) - { - return spawn_roomowner(owner_type, x, y, layer, room_template); - }); - /// Get the current adventure seed pair, or optionally what it was at the start of this run, because it changes every level. lua["get_adventure_seed"] = get_adventure_seed; /// Set the current adventure seed pair. Use just before resetting a run to recreate an adventure run. @@ -1961,26 +924,17 @@ end /// 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; - /// Get the rva for a pattern name, used for debugging. lua["get_rva"] = [](std::string_view address_name) -> std::string - { - return fmt::format("{:X}", get_address(address_name) - Memory::get().at_exe(0)); - }; + { return fmt::format("{:X}", get_address(address_name) - Memory::get().at_exe(0)); }; /// Get the rva for a vtable offset and index, used for debugging. lua["get_virtual_rva"] = [](VTABLE_OFFSET offset, uint32_t index) -> std::string - { - return fmt::format("{:X}", get_virtual_function_address(offset, index)); - }; + { return fmt::format("{:X}", get_virtual_function_address(offset, index)); }; /// Get memory address from a lua object lua["get_address"] = [&lua]([[maybe_unused]] sol::object o) - { - return fmt::format("{:X}", *(size_t*)lua_touserdata(lua, 1)); - }; + { return fmt::format("{:X}", *(size_t*)lua_touserdata(lua, 1)); }; /// Log to spelunky.log lua["log_print"] = game_log; @@ -1992,7 +946,7 @@ end lua["save_progress"] = []() -> bool { auto backend = LuaBackend::get_calling_backend(); - if (backend->last_save <= State::get().ptr()->time_startup - 120) + if (backend->last_save <= API::get_global_frame_count() - 120) { backend->manual_save = true; save_progress(); @@ -2005,7 +959,7 @@ end lua["save_script"] = []() -> bool { auto backend = LuaBackend::get_calling_backend(); - if (backend->last_save <= get_frame_count() - 120) + if (backend->last_save <= API::get_global_frame_count() - 120) { backend->manual_save = true; return true; @@ -2016,24 +970,7 @@ end /// Set the level number shown in the hud and journal to any string. This is reset to the default "%d-%d" automatically just before PRE_LOAD_SCREEN to a level or main menu, so use in PRE_LOAD_SCREEN, POST_LEVEL_GENERATION or similar for each level. /// Use "%d-%d" to reset to default manually. Does not affect the "...COMPLETED!" message in transitions or lines in "Dear Journal", you need to edit them separately with [change_string](#change_string). lua["set_level_string"] = [](std::u16string str) - { - return set_level_string(str); - }; - - /// Force the character unlocked in either ending to ENT_TYPE. Set to 0 to reset to the default guys. Does not affect the texture of the actual savior. (See example) - lua["set_ending_unlock"] = set_ending_unlock; - - /// Get the thread-local version of state - lua["get_local_state"] = []() -> StateMemory* - { - return State::get().ptr_local(); - }; - - /// Get the thread-local version of players - lua["get_local_players"] = []() -> std::vector - { - return get_players(State::get().ptr_local()); - }; + { return set_level_string(str); }; /// List files in directory relative to the script root. Returns table of file/directory names or nil if not found. lua["list_dir"] = [&lua](std::optional dir) @@ -2123,9 +1060,10 @@ end float ax = -0.98f; float f = 1.0f; uint32_t hs = get_setting(GAME_SETTING::HUD_SIZE).value_or(0); - if (hs == 0 || State::get().ptr()->items->player_count > 3) + auto state = HeapBase::get().state(); + if (hs == 0 || state->items->player_count > 3) f = 1.0f; - else if (hs == 1 || State::get().ptr()->items->player_count > 2) + else if (hs == 1 || state->items->player_count > 2) f = 1.15f; else f = 1.3f; @@ -2137,34 +1075,6 @@ end return AABB(ax + index * w + 0.02f * f, ay, ax + index * w + w - 0.02f * f, ay - h); }; - /// Olmec cutscene moves Olmec and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and Olmec will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with Olmec in the hole. - lua["set_olmec_cutscene_enabled"] = set_olmec_cutscene_enabled; - - /// Tiamat cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want Tiamat kill to be required - lua["set_tiamat_cutscene_enabled"] = set_tiamat_cutscene_enabled; - - /// Activate custom variables for position used for detecting the player (normally hardcoded) - /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Tiamat entity, recommending set_post_entity_spawn - /// default game values are: attack_x = 17.5 attack_y = 62.5 - lua["activate_tiamat_position_hack"] = activate_tiamat_position_hack; - - /// Activate custom variables for speed and y coordinate limit for crushing elevator - /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each CrushElevator entity, recommending set_post_entity_spawn - /// default game values are: speed = 0.0125, y_limit = 98.5 - lua["activate_crush_elevator_hack"] = activate_crush_elevator_hack; - - /// Activate custom variables for y coordinate limit for hundun and spawn of it's heads - /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Hundun entity, recommending set_post_entity_spawn - /// default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 - lua["activate_hundun_hack"] = activate_hundun_hack; - - /// Allows you to disable the control over the door for Hundun and Tiamat - /// This will also prevent game crashing when there is no exit door when they are in level - lua["set_boss_door_control_enabled"] = set_boss_door_control_enabled; - - /// Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. - lua["update_state"] = update_state; - /// Set engine target frametime (1/framerate, default 1/60). Always capped by your GPU max FPS / VSync. To run the engine faster than rendered FPS, try update_state. Set to 0 to go as fast as possible. Call without arguments to reset. Also see set_speedhack lua["set_frametime"] = set_frametime; @@ -2217,12 +1127,6 @@ end /// Initializes an empty layer that doesn't currently exist. lua["create_layer"] = create_layer; - /// Setting to false disables all player logic in 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. - lua["set_level_logic_enabled"] = set_level_logic_enabled; - - /// Setting to true will stop the state update from unpausing after a screen load, leaving you with state.pause == PAUSE.FADE on the first frame to do what you want. - lua["set_start_level_paused"] = set_start_level_paused; - /// Returns true if the level pause hack is enabled lua["get_start_level_paused"] = get_start_level_paused; @@ -2265,11 +1169,6 @@ end backend->infinite_loop_detection = enable; }; - /// This disables the `state.camera_layer` to be forced to the `(leader player).layer` and setting of the `state.layer_transition_timer` & `state.transition_to_layer` when player enters layer door. - /// Letting you control those manually. - /// Look at the example on how to mimic game layer switching behavior - lua["set_camera_layer_control_enabled"] = set_camera_layer_control_enabled; - /// Set multiplier (default 1.0) for a QueryPerformanceCounter hook based speedhack, similar to the one in Cheat Engine. Call without arguments to reset. Also see [set_frametime](#set_frametime) lua["set_speedhack"] = set_speedhack; @@ -2300,11 +1199,6 @@ end /// Initializes some seeded 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 - /// This sadly also makes lavamanders extinct, since the logic for their spawn is hardcoded 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; - /// 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; @@ -2767,42 +1661,6 @@ end // Runs instead of POST_PROCESS_INPUT when anything blocks a PRE_PROCESS_INPUT. Even runs in Playlunky when Overlunky blocks a PRE_PROCESS_INPUT. */ - lua.create_named_table( - "SPAWN_TYPE", - "LEVEL_GEN", - SPAWN_TYPE_LEVEL_GEN, - "LEVEL_GEN_TILE_CODE", - SPAWN_TYPE_LEVEL_GEN_TILE_CODE, - "LEVEL_GEN_PROCEDURAL", - SPAWN_TYPE_LEVEL_GEN_PROCEDURAL, - "LEVEL_GEN_FLOOR_SPREADING", - SPAWN_TYPE_LEVEL_GEN_FLOOR_SPREADING, - "LEVEL_GEN_GENERAL", - SPAWN_TYPE_LEVEL_GEN_GENERAL, - "SCRIPT", - SPAWN_TYPE_SCRIPT, - "SYSTEMIC", - SPAWN_TYPE_SYSTEMIC, - "ANY", - SPAWN_TYPE_ANY); - /* SPAWN_TYPE - // LEVEL_GEN - // For any spawn happening during level generation, even if the call happened from the Lua API during a tile code callback. - // LEVEL_GEN_TILE_CODE - // Similar to LEVEL_GEN but only triggers on tile code spawns. - // LEVEL_GEN_PROCEDURAL - // Similar to LEVEL_GEN but only triggers on random level spawns, like snakes or bats. - // LEVEL_GEN_FLOOR_SPREADING - // Includes solid floor type spreading (i.e. floorstyled bleeding to existing generic floor) but also corner filling of empty tiles. - // LEVEL_GEN_GENERAL - // Covers all spawns during level gen that are not covered by the other two. - // SCRIPT - // Runs for any spawn happening through a call from the Lua API, also during level generation. - // SYSTEMIC - // Covers all other spawns, such as items from crates or the player throwing bombs. - // ANY - // Covers all of the above. - */ /// Some arbitrary constants of the engine lua.create_named_table("CONST", "ENGINE_FPS", 60, "ROOM_WIDTH", 10, "ROOM_HEIGHT", 8, "MAX_TILES_VERT", g_level_max_y, "MAX_TILES_HORIZ", g_level_max_x, "NOF_DRAW_DEPTHS", 53, "MAX_PLAYERS", 4); /* CONST diff --git a/src/game_api/script/script_impl.cpp b/src/game_api/script/script_impl.cpp index 7318c38f4..4eefd73d2 100644 --- a/src/game_api/script/script_impl.cpp +++ b/src/game_api/script/script_impl.cpp @@ -18,12 +18,12 @@ #include // for unordered_map #include // for max, min +#include "heap_base.hpp" // for HeapBase #include "logger.h" // for DEBUG #include "lua_vm.hpp" // for execute_lua, get_lua_vm #include "script/handle_lua_function.hpp" // for handle_function #include "script/lua_backend.hpp" // for LuaBackend, ON, ON::SCRIPT_DISABLE #include "script_util.hpp" // for sanitize -#include "state.hpp" // for State class LuaConsole; class SoundManager; @@ -149,7 +149,7 @@ void ScriptImpl::set_enabled(bool enbl) if (enbl != enabled) { auto cb_type = enbl ? ON::SCRIPT_ENABLE : ON::SCRIPT_DISABLE; - auto now = State::get().get_frame_count(); + auto now = HeapBase::get().frame_count(); for (auto& [id, callback] : callbacks) { if (callback.screen == cb_type) diff --git a/src/game_api/script/usertypes/deprecated_func.cpp b/src/game_api/script/usertypes/deprecated_func.cpp new file mode 100644 index 000000000..fd5693ca7 --- /dev/null +++ b/src/game_api/script/usertypes/deprecated_func.cpp @@ -0,0 +1,492 @@ +#include "deprecated_func.hpp" + +#include +#include // for int64_t +#include // +#include // for vector + +#include "aliases.hpp" // for CallbackId +#include "entities_chars.hpp" // for Player +#include "entities_items.hpp" // for PlayerGhost +#include "entity.hpp" // for get_entity_ptr +#include "entity_lookup.hpp" // for get_entities +#include "heap_base.hpp" // for HeapBase +#include "movable.hpp" // for Movable +#include "rpc.hpp" // for read_prng +#include "script/handle_lua_function.hpp" // for handle_function +#include "script/lua_backend.hpp" // for LuaBackend +#include "search.hpp" // for get_address +#include "state.hpp" // for darkmode +#include "vanilla_render_lua.hpp" // for VanillaRenderContext + +namespace NDeprecated +{ +void register_usertypes(sol::state& lua) +{ + /// Deprecated + /// Read the game prng state. Use [prng](#PRNG):get_pair() instead. + lua["read_prng"] = []() -> std::vector + { + std::vector prng_raw; + prng_raw.resize(20); + auto prng = reinterpret_cast(HeapBase::get().prng()); + std::memcpy(prng_raw.data(), prng, sizeof(int64_t) * 20); + return prng_raw; + }; + + /// Deprecated + /// Set level flag 18 on post room generation instead, to properly force every level to dark + lua["force_dark_level"] = [](bool g) + { + static const size_t addr_dark = get_address("force_dark_level"); + if (g) + write_mem_recoverable("darkmode", addr_dark, "\x90\x90"sv, true); + else + recover_mem("darkmode"); + }; + + /// Deprecated + /// Use `get_entities_by(0, MASK.ANY, LAYER.BOTH)` instead + lua["get_entities"] = get_entities; + /// Deprecated + /// Use `get_entities_by(0, mask, LAYER.BOTH)` instead + lua["get_entities_by_mask"] = get_entities_by_mask; + /// Deprecated + /// Use `get_entities_by(0, MASK.ANY, layer)` instead + lua["get_entities_by_layer"] = get_entities_by_layer; + auto get_entities_overlapping = sol::overload( + static_cast (*)(ENT_TYPE, ENTITY_MASK, float, float, float, float, LAYER)>(::get_entities_overlapping), + static_cast (*)(std::vector, ENTITY_MASK, float, float, float, float, LAYER)>(::get_entities_overlapping)); + /// Deprecated + /// Use `get_entities_overlapping_hitbox` instead + lua["get_entities_overlapping"] = get_entities_overlapping; + + /// Deprecated + /// As the name is misleading. use Movable.`move_state` field instead + lua["get_entity_ai_state"] = [](uint32_t uid) -> uint8_t + { + auto ent = get_entity_ptr(uid)->as(); + if (ent && ent->is_movable()) + return ent->move_state; + return 0; + }; + + /// Deprecated + /// Use [replace_drop](#replace_drop)(DROP.ARROWTRAP_WOODENARROW, new_arrow_type) and [replace_drop](#replace_drop)(DROP.POISONEDARROWTRAP_WOODENARROW, new_arrow_type) instead + lua["set_arrowtrap_projectile"] = [](ENT_TYPE regular_entity_type, ENT_TYPE poison_entity_type) + { + static const auto arrowtrap = get_address("arrowtrap_projectile"); + static const auto poison_arrowtrap = get_address("poison_arrowtrap_projectile"); + write_mem_prot(arrowtrap, regular_entity_type, true); + write_mem_prot(poison_arrowtrap, poison_entity_type, true); + }; + + /// Deprecated + /// This function never worked properly as too many places in the game individually check for vlads cape and calculate the blood multiplication + /// `default_multiplier` doesn't do anything due to some changes in last game updates, `vladscape_multiplier` only changes the multiplier to some entities death's blood spit + lua["set_blood_multiplication"] = [](uint32_t /*default_multiplier*/, uint32_t vladscape_multiplier) + { + // Due to changes in 1.23.x, the default multiplier is automatically vlads - 1. + static const auto blood_multiplication = get_address("blood_multiplication"); + write_mem_prot(blood_multiplication, vladscape_multiplier, true); + }; + + /// Deprecated + /// Deprecated because it's a weird old hack that crashes the game. You can modify inputs in many other ways, like editing `state.player_inputs.player_slot_1.buttons_gameplay` in PRE_UPDATE or a `set_pre_process_input` hook. Steal input from a Player, HiredHand or PlayerGhost. + lua["steal_input"] = [](int uid) + { + static const auto player_ghost = to_id("ENT_TYPE_ITEM_PLAYERGHOST"); + static const auto ana = to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); + static const auto egg_child = to_id("ENT_TYPE_CHAR_EGGPLANT_CHILD"); + + auto backend = LuaBackend::get_calling_backend(); + if (backend->script_input.find(uid) != backend->script_input.end()) + return; + Player* player = get_entity_ptr(uid)->as(); + if (player == nullptr) + return; + + if (player->type->id == player_ghost) + { + auto player_gh = player->as(); + ScriptInput* newinput = new ScriptInput(); + newinput->gameplay = 0; + newinput->all = 0; + newinput->orig_input = player_gh->player_inputs; + player_gh->player_inputs = reinterpret_cast(newinput); + backend->script_input[uid] = newinput; + } + else + { + if (player->type->id < ana || player->type->id > egg_child) + return; + + ScriptInput* newinput = new ScriptInput(); + newinput->gameplay = 0; + newinput->all = 0; + newinput->orig_input = player->input_ptr; + newinput->orig_ai = player->ai; + player->input_ptr = reinterpret_cast(newinput); + player->ai = nullptr; + backend->script_input[uid] = newinput; + } + }; + /// Deprecated + /// Return input previously stolen with [steal_input](#steal_input) + lua["return_input"] = [](int uid) + { + static const auto player_ghost = to_id("ENT_TYPE_ITEM_PLAYERGHOST"); + + auto backend = LuaBackend::get_calling_backend(); + if (backend->script_input.find(uid) == backend->script_input.end()) + return; + Player* player = get_entity_ptr(uid)->as(); + if (player == nullptr) + return; + + if (player->type->id == player_ghost) + { + auto player_gh = player->as(); + player_gh->player_inputs = backend->script_input[uid]->orig_input; + } + else + { + player->input_ptr = backend->script_input[uid]->orig_input; + player->ai = backend->script_input[uid]->orig_ai; + } + backend->script_input.erase(uid); + }; + /// Deprecated + /// Send input to entity, has to be previously stolen with [steal_input](#steal_input) + lua["send_input"] = [](int uid, INPUTS buttons) + { + auto backend = LuaBackend::get_calling_backend(); + auto it = backend->script_input.find(uid); + if (it != backend->script_input.end()) + { + it->second->all = buttons; + it->second->gameplay = buttons; + } + }; + /// Deprecated + /// Use `players[1].input.buttons_gameplay` for only the inputs during the game, or `.buttons` for all the inputs, even during the pause menu + /// Of course, you can get the Player by other mean, it doesn't need to be the `players` table + /// You can only read inputs from actual players, HH don't have any inputs + lua["read_input"] = [](int uid) -> INPUTS + { + Player* player = get_entity_ptr(uid)->as(); + if (player == nullptr) + return (INPUTS)0; + + if (!IsBadReadPtr(player->input_ptr, 20)) + { + return player->input_ptr->buttons_gameplay; + } + return (INPUTS)0; + }; + /// Deprecated + /// Read input that has been previously stolen with [steal_input](#steal_input) + /// Use `state.player_inputs.player_slots[player_slot].buttons_gameplay` for only the inputs during the game, or `.buttons` for all the inputs, even during the pause menu + lua["read_stolen_input"] = [](int uid) -> INPUTS + { + auto backend = LuaBackend::get_calling_backend(); + if (backend->script_input.find(uid) == backend->script_input.end()) + { + // this means that the input is attacked to the real input and not stolen so return early + return (INPUTS)0; + } + Player* player = get_entity_ptr(uid)->as(); + if (player == nullptr) + return (INPUTS)0; + ScriptInput* readinput = reinterpret_cast(player->input_ptr); + if (!IsBadReadPtr(readinput, 20)) + { + readinput = reinterpret_cast(readinput->orig_input); + if (!IsBadReadPtr(readinput, 20)) + { + return readinput->gameplay; + } + } + return (INPUTS)0; + }; + + /// Deprecated + /// Use `entity.clear_virtual` instead. + /// Clears a callback that is specific to an entity. + lua["clear_entity_callback"] = [](int uid, CallbackId cb_id) + { + auto backend = LuaBackend::get_calling_backend(); + backend->HookHandler::clear_hook(cb_id, uid); + }; + /// Deprecated + /// Use `entity:set_pre_update_state_machine` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// `uid` has to be the uid of a `Movable` or else stuff will break. + /// Sets a callback that is called right before the statemachine, return `true` to skip the statemachine update. + /// Use this only when no other approach works, this call can be expensive if overused. + /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. + ///
The callback signature is bool statemachine(Entity self) + lua["set_pre_statemachine"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + return lua["Entity"]["set_pre_update_state_machine"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_post_update_state_machine` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// `uid` has to be the uid of a `Movable` or else stuff will break. + /// Sets a callback that is called right after the statemachine, so you can override any values the satemachine might have set (e.g. `animation_frame`). + /// Use this only when no other approach works, this call can be expensive if overused. + /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. + ///
The callback signature is nil statemachine(Entity self) + lua["set_post_statemachine"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + return lua["Entity"]["set_post_update_state_machine"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_pre_destroy` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right when an entity is destroyed, e.g. as if by `Entity.destroy()` before the game applies any side effects. + /// Use this only when no other approach works, this call can be expensive if overused. + ///
The callback signature is nil on_destroy(Entity self) + lua["set_on_destroy"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + return lua["Entity"]["set_pre_destroy"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_pre_kill` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right when an entity is eradicated, before the game applies any side effects. + /// Use this only when no other approach works, this call can be expensive if overused. + ///
The callback signature is nil on_kill(Entity self, Entity killer) + lua["set_on_kill"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + return lua["Entity"]["set_pre_kill"](ent, std::move(fun)); + } + return sol::nullopt; + }; + + /// Deprecated + /// Use `entity:set_pre_damage` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right before an entity is damaged, return `true` to skip the game's damage handling. + /// Note that damage_dealer can be nil ! (long fall, ...) + /// DO NOT CALL `self:damage()` in the callback ! + /// Use this only when no other approach works, this call can be expensive if overused. + /// The entity has to be of a [Movable](#Movable) type. + ///
The callback signature is bool on_damage(Entity self, Entity damage_dealer, int damage_amount, float vel_x, float vel_y, int stun_amount, int iframes) + lua["set_on_damage"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid); ent != nullptr && ent->is_movable()) + { + return lua["Movable"]["set_pre_damage"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_pre_floor_update` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right before a floor is updated (by killed neighbor), return `true` to skip the game's neighbor update handling. + /// Use this only when no other approach works, this call can be expensive if overused. + ///
The callback signature is bool pre_floor_update(Entity self) + lua["set_pre_floor_update"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) // TODO: Requires ent->is_floor + { + return lua["Floor"]["set_pre_floor_update"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_post_floor_update` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right after a floor is updated (by killed neighbor). + /// Use this only when no other approach works, this call can be expensive if overused. + ///
The callback signature is nil post_floor_update(Entity self) + lua["set_post_floor_update"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) // TODO: Requires ent->is_floor + { + return lua["Floor"]["set_post_floor_update"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_pre_trigger_action` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right when a container is opened by the player (up+whip) + /// Use this only when no other approach works, this call can be expensive if overused. + /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. + ///
The callback signature is nil on_open(Entity entity_self, Entity opener) + lua["set_on_open"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) // TODO: Requires ent->is_container + { + return lua["Entity"]["set_pre_trigger_action"]( + ent, + [fun = std::move(fun)](Entity* usee, Entity* user) + { + if (user->is_movable() && user->as()->movey > 0) + { + auto backend = LuaBackend::get_calling_backend(); + handle_function(backend.get(), fun, usee, user); + } + }); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_pre_collision1` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right before the collision 1 event, return `true` to skip the game's collision handling. + /// Use this only when no other approach works, this call can be expensive if overused. + /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. + ///
The callback signature is bool pre_collision1(Entity entity_self, Entity collision_entity) + lua["set_pre_collision1"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + return lua["Entity"]["set_pre_on_collision1"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity:set_pre_collision2` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right before the collision 2 event, return `true` to skip the game's collision handling. + /// Use this only when no other approach works, this call can be expensive if overused. + /// Check [here](https://github.com/spelunky-fyi/overlunky/blob/main/docs/virtual-availability.md) to see whether you can use this callback on the entity type you intend to. + ///
The callback signature is bool pre_collision12(Entity self, Entity collision_entity) + lua["set_pre_collision2"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + return lua["Entity"]["set_pre_on_collision2"](ent, std::move(fun)); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity.rendering_info:set_pre_render` in combination with `render_info:get_entity` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right after the entity is rendered. + /// Return `true` to skip the original rendering function and all later pre_render callbacks. + /// Use this only when no other approach works, this call can be expensive if overused. + ///
The callback signature is bool render(VanillaRenderContext render_ctx, Entity self) + lua["set_pre_render"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + auto backend_id = LuaBackend::get_calling_backend_id(); + return lua["RenderInfo"]["set_pre_render"]( + ent->rendering_info, + [backend_id, fun = std::move(fun)](RenderInfo* ri, float*, VanillaRenderContext render_ctx) + { + auto backend = LuaBackend::get_backend(backend_id); + return handle_function( + backend.get(), + fun, + render_ctx, + ri->get_entity()); + }); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `entity.rendering_info:set_post_render` in combination with `render_info:get_entity` instead. + /// Returns unique id for the callback to be used in [clear_entity_callback](#clear_entity_callback) or `nil` if uid is not valid. + /// Sets a callback that is called right after the entity is rendered. + /// Use this only when no other approach works, this call can be expensive if overused. + ///
The callback signature is nil post_render(VanillaRenderContext render_ctx, Entity self) + lua["set_post_render"] = [&lua](int uid, sol::function fun) -> sol::optional + { + if (Entity* ent = get_entity_ptr(uid)) + { + auto backend_id = LuaBackend::get_calling_backend_id(); + return lua["RenderInfo"]["set_post_render"]( + ent->rendering_info, + [backend_id, fun = std::move(fun)](RenderInfo* ri, float*, VanillaRenderContext render_ctx) + { + auto backend = LuaBackend::get_backend(backend_id); + return handle_function( + backend.get(), + fun, + render_ctx, + ri->get_entity()); + }); + } + return sol::nullopt; + }; + /// Deprecated + /// Use `Entity:flip` instead + lua["flip_entity"] = [](uint32_t uid) -> void + { + Entity* ent = get_entity_ptr(uid); + if (ent == nullptr) + return; + ent->flags ^= 0x10000; + if (ent->items.size > 0) + { + for (auto item : ent->items.entities()) + { + item->flags ^= 0x10000; + } + } + }; + + /// Deprecated + /// use `Door:unlock` instead + lua["lock_door_at"] = [](float x, float y) + { + std::vector items = get_entities_at({}, ENTITY_MASK::ANY, x, y, LAYER::FRONT, 1); + for (auto id : items) + { + Entity* door = get_entity_ptr(id); + if (door->type->id >= to_id("ENT_TYPE_FLOOR_DOOR_ENTRANCE") && door->type->id <= to_id("ENT_TYPE_FLOOR_DOOR_EGGPLANT_WORLD")) + { + door->flags &= ~(1U << 19); + door->flags |= 1U << 21; + } + else if ( + door->type->id == to_id("ENT_TYPE_BG_DOOR") || door->type->id == to_id("ENT_TYPE_BG_DOOR_COG") || + door->type->id == to_id("ENT_TYPE_BG_DOOR_EGGPLANT_WORLD")) + { + door->animation_frame &= ~1U; + } + } + }; + /// Deprecated + /// use `Door:unlock` instead + lua["unlock_door_at"] = [](float x, float y) + { + std::vector items = get_entities_at({}, ENTITY_MASK::ANY, x, y, LAYER::FRONT, 1); + for (auto id : items) + { + Entity* door = get_entity_ptr(id); + if (door->type->id >= to_id("ENT_TYPE_FLOOR_DOOR_ENTRANCE") && door->type->id <= to_id("ENT_TYPE_FLOOR_DOOR_EGGPLANT_WORLD")) + { + door->flags |= 1U << 19; + door->flags &= ~(1U << 21); + } + else if ( + door->type->id == to_id("ENT_TYPE_BG_DOOR") || door->type->id == to_id("ENT_TYPE_BG_DOOR_COG") || + door->type->id == to_id("ENT_TYPE_BG_DOOR_EGGPLANT_WORLD")) + { + door->animation_frame |= 1U; + } + } + }; +} +} // namespace NDeprecated diff --git a/src/game_api/script/usertypes/deprecated_func.hpp b/src/game_api/script/usertypes/deprecated_func.hpp new file mode 100644 index 000000000..42e10afa9 --- /dev/null +++ b/src/game_api/script/usertypes/deprecated_func.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace sol +{ +class state; +} // namespace sol + +namespace NDeprecated +{ +void register_usertypes(sol::state& lua); +}; diff --git a/src/game_api/script/usertypes/entities_floors_lua.cpp b/src/game_api/script/usertypes/entities_floors_lua.cpp index 1ac1b6ddc..8155a5725 100644 --- a/src/game_api/script/usertypes/entities_floors_lua.cpp +++ b/src/game_api/script/usertypes/entities_floors_lua.cpp @@ -115,6 +115,10 @@ void register_usertypes(sol::state& lua) &ExitDoor::world, "theme", &ExitDoor::theme, + "set_target", + &ExitDoor::set_target, + "get_target", + &ExitDoor::get_target, sol::base_classes, sol::bases()); @@ -407,5 +411,37 @@ void register_usertypes(sol::state& lua) &JungleSpearTrap::trigger, sol::base_classes, sol::bases()); + + /// Make an ENT_TYPE.FLOOR_DOOR_EXIT go to world `w`, level `l`, theme `t` + lua["set_door_target"] = [](uint32_t uid, uint8_t w, uint8_t l, uint8_t t) + { + if (auto door = get_entity_ptr(uid)->as()) + door->set_target(w, l, t); + }; + /// Short for [set_door_target](#set_door_target). + lua["set_door"] = [](uint32_t uid, uint8_t w, uint8_t l, uint8_t t) + { + if (auto door = get_entity_ptr(uid)->as()) + door->set_target(w, l, t); + }; + /// Get door target `world`, `level`, `theme` + lua["get_door_target"] = [](uint32_t uid) -> std::tuple + { + auto door = get_entity_ptr(uid)->as(); + if (door == nullptr || !door->special_door) + return {}; + + return std::make_tuple(door->world, door->level, door->theme); + }; + /// Calls the enter door function, position doesn't matter, can also enter closed doors (like COG, EW) without unlocking them + lua["enter_door"] = [](int32_t player_uid, int32_t door_uid) + { + auto player = get_entity_ptr(player_uid); + auto door = get_entity_ptr(door_uid)->as(); + if (player == nullptr || door == nullptr) + return; + + door->enter(player); + }; } } // namespace NEntitiesFloors diff --git a/src/game_api/script/usertypes/entities_mounts_lua.cpp b/src/game_api/script/usertypes/entities_mounts_lua.cpp index 7e1ef335e..c6dd5d129 100644 --- a/src/game_api/script/usertypes/entities_mounts_lua.cpp +++ b/src/game_api/script/usertypes/entities_mounts_lua.cpp @@ -104,5 +104,15 @@ void register_usertypes(sol::state& lua) &Qilin::attack_cooldown, sol::base_classes, sol::bases()); + + /// Make `mount_uid` carry `rider_uid` on their back. Only use this with actual mounts and living things. + lua["carry"] = [](uint32_t mount_uid, uint32_t rider_uid) + { + auto mount = get_entity_ptr(mount_uid)->as(); + auto rider = get_entity_ptr(rider_uid)->as(); + if (mount == nullptr || rider == nullptr) + return; + mount->carry(rider); + }; } } // namespace NEntitiesMounts diff --git a/src/game_api/script/usertypes/entity_lua.cpp b/src/game_api/script/usertypes/entity_lua.cpp index 78ddd068b..09c664cd3 100644 --- a/src/game_api/script/usertypes/entity_lua.cpp +++ b/src/game_api/script/usertypes/entity_lua.cpp @@ -14,15 +14,17 @@ #include "color.hpp" // for Color, Color::a, Color::b, Color::g #include "containers/game_allocator.hpp" // for game_allocator -#include "custom_types.hpp" // for get_custom_types_map +#include "custom_types.hpp" // for get_custom_types_vector #include "entities_chars.hpp" // for Player #include "entity.hpp" // for Entity, EntityDB, Animation, Rect +#include "entity_lookup.hpp" // for entity_has_item_type #include "items.hpp" // for Inventory #include "math.hpp" // for Quad, AABB #include "movable.hpp" // for Movable, Movable::falling_timer #include "render_api.hpp" // for RenderInfo, RenderInfo::flip_horiz... +#include "rpc.hpp" // for move_entity_abs #include "script/lua_backend.hpp" // for LuaBackend -#include "script/safe_cb.hpp" // for make_safe_cb +#include "strings.hpp" // for get_entity_name namespace NEntity { @@ -88,79 +90,6 @@ void register_usertypes(sol::state& lua) entitydb_type["default_special_offsetx"] = &EntityDB::default_special_offsetx; entitydb_type["default_special_offsety"] = &EntityDB::default_special_offsety; - /// Some information used to render the entity, can not be changed, used in Entity - lua.new_usertype( - "RenderInfo", - "x", - &RenderInfo::x, - "y", - &RenderInfo::y, - "offset_x", - &RenderInfo::offset_x, - "offset_y", - &RenderInfo::offset_y, - "shader", - &RenderInfo::shader, - "source", - &RenderInfo::source, - "destination", - sol::property( - [](const RenderInfo& ri) -> Quad - { return Quad{ - ri.destination_bottom_left_x, - ri.destination_bottom_left_y, - ri.destination_bottom_right_x, - ri.destination_bottom_right_y, - ri.destination_top_right_x, - ri.destination_top_right_y, - ri.destination_top_left_x, - ri.destination_top_left_y, - }; }), - "tilew", - &RenderInfo::tilew, - "tileh", - &RenderInfo::tileh, - "facing_left", - &RenderInfo::flip_horizontal, - "angle", - &RenderInfo::angle1, - "animation_frame", - &RenderInfo::animation_frame, - "render_inactive", - &RenderInfo::render_inactive, - "brightness", - &RenderInfo::brightness, - "texture_num", - sol::readonly(&RenderInfo::texture_num), - "get_entity", - &RenderInfo::get_entity, - "set_normal_map_texture", - &RenderInfo::set_normal_map_texture, - "get_second_texture", - [](const RenderInfo& ri) -> std::optional - { - if (!ri.texture_names[1] || ri.texture_num < 2) - { - return std::nullopt; - } - return ::get_texture(std::string_view(*ri.texture_names[1])) /**/; - }, - "get_third_texture", - [](const RenderInfo& ri) -> std::optional - { - if (!ri.texture_names[2] || ri.texture_num < 3) - { - return std::nullopt; - } - return ::get_texture(std::string_view(*ri.texture_names[2])) /**/; - }, - "set_second_texture", - &RenderInfo::set_second_texture, - "set_third_texture", - &RenderInfo::set_third_texture, - "set_texture_num", - &RenderInfo::set_texture_num); - auto get_overlay = [&lua](Entity& entity) { return lua["cast_entity"](entity.overlay); @@ -202,10 +131,10 @@ void register_usertypes(sol::state& lua) auto kill_recursive = sol::overload( static_cast(&Entity::kill_recursive), - static_cast, std::vector, RECURSIVE_MODE)>(&Entity::kill_recursive)); + static_cast, std::vector, RECURSIVE_MODE)>(&Entity::kill_recursive)); auto destroy_recursive = sol::overload( static_cast(&Entity::destroy_recursive), - static_cast, std::vector, RECURSIVE_MODE)>(&Entity::destroy_recursive)); + static_cast, std::vector, RECURSIVE_MODE)>(&Entity::destroy_recursive)); auto entity_type = lua.new_usertype("Entity"); entity_type["type"] = &Entity::type; @@ -393,6 +322,204 @@ void register_usertypes(sol::state& lua) lua.new_usertype("CutsceneBehavior", sol::no_constructor); + /// Get the Entity behind an uid, converted to the correct type. To see what type you will get, consult the [entity hierarchy list](https://github.com/spelunky-fyi/overlunky/blob/main/docs/entities-hierarchy.md) + // lua["get_entity"] = [](uint32_t uid) -> Entity*{}; + /// NoDoc + /// Get the [Entity](#Entity) behind an uid, without converting to the correct type (do not use, use `get_entity` instead) + lua["get_entity_raw"] = get_entity_ptr; + lua.script(R"##( + function cast_entity(entity_raw) + if entity_raw == nil then + return nil + end + + local cast_fun = TYPE_MAP[entity_raw.type.id] + if cast_fun ~= nil then + return cast_fun(entity_raw) + else + return entity_raw + end + end + function get_entity(ent_uid) + if ent_uid == nil then + return nil + end + + local entity_raw = get_entity_raw(ent_uid) + if entity_raw == nil then + return nil + end + + return cast_entity(entity_raw) + end + )##"); + /// Get the [EntityDB](#EntityDB) behind an ENT_TYPE... + lua["get_type"] = get_type; + /// Get the ENT_TYPE... of the entity by uid + lua["get_entity_type"] = get_entity_type; + /// Get localized name of an entity from the journal, 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) -> std::u16string + { return get_entity_name(type, fallback_strategy.value_or(false)); }; + auto move_entity_abs = sol::overload( + static_cast(::move_entity_abs), + static_cast(::move_entity_abs)); + /// Teleport entity to coordinates with optional velocity + lua["move_entity"] = move_entity_abs; + /// Teleport grid entity, the destination should be whole number, this ensures that the collisions will work properly + lua["move_grid_entity"] = move_grid_entity; + auto destroy_grid = sol::overload( + static_cast(::destroy_grid), + static_cast(::destroy_grid)); + /// Destroy the grid entity (by uid or position), and its item entities, removing them from the grid without dropping particles or gold. + /// Will also destroy monsters or items that are standing on a linked activefloor or chain, though excludes MASK.PLAYER to prevent crashes + lua["destroy_grid"] = destroy_grid; + /// Attaches `attachee` to `overlay`, similar to setting `get_entity(attachee).overlay = get_entity(overlay)`. + /// However this function offsets `attachee` (so you don't have to) and inserts it into `overlay`'s inventory. + lua["attach_entity"] = attach_entity_by_uid; + /// Get the `flags` field from entity by uid + lua["get_entity_flags"] = [](uint32_t uid) -> ENT_FLAG + { + auto ent = get_entity_ptr(uid); + if (ent) + return ent->flags; + return {}; + }; + /// Set the `flags` field from entity by uid + lua["set_entity_flags"] = [](uint32_t uid, ENT_FLAG flags) + { + auto ent = get_entity_ptr(uid); + if (ent) + ent->flags = flags; + }; + /// Get the `more_flags` field from entity by uid + lua["get_entity_flags2"] = [](uint32_t uid) -> ENT_MORE_FLAG + { + auto ent = get_entity_ptr(uid); + if (ent) + return ent->more_flags; + return {}; + }; + /// Set the `more_flags` field from entity by uid + lua["set_entity_flags2"] = [](uint32_t uid, ENT_MORE_FLAG flags) + { + auto ent = get_entity_ptr(uid); + if (ent) + ent->more_flags = flags; + }; + /// Get position `x, y, layer` of entity by uid. Use this, don't use `Entity.x/y` because those are sometimes just the offset to the entity + /// you're standing on, not real level coordinates. + lua["get_position"] = [](int32_t uid) -> std::tuple + { + Entity* ent = get_entity_ptr(uid); + if (ent) + { + auto pos = ent->abs_position(); + return {pos.x, pos.y, ent->layer}; + } + return {}; + }; + /// Get interpolated render position `x, y, layer` of entity by uid. This gives smooth hitboxes for 144Hz master race etc... + lua["get_render_position"] = [](int32_t uid) -> std::tuple + { + Entity* ent = get_entity_ptr(uid); + if (ent) + { + if (ent->rendering_info != nullptr && !ent->rendering_info->render_inactive) + return std::make_tuple(ent->rendering_info->x, ent->rendering_info->y, ent->layer); + else + { + auto pos = ent->abs_position(); + return {pos.x, pos.y, ent->layer}; + } + } + return {}; + }; + /// Get velocity `vx, vy` of an entity by uid. Use this to get velocity relative to the game world, (the `Entity.velocityx/velocityy` are relative to `Entity.overlay`). Only works for movable or liquid entities + lua["get_velocity"] = [](int32_t uid) -> std::tuple + { + Entity* ent = get_entity_ptr(uid); + if (ent) + return ent->get_absolute_velocity(); + + return {}; + }; + /// Remove item by uid from entity. `check_autokill` defaults to true, checks if entity should be killed when missing overlay and kills it if so (can help with avoiding crashes) + lua["entity_remove_item"] = entity_remove_item; + /// Spawns and attaches ball and chain to `uid`, the initial position of the ball is at the entity position plus `off_x`, `off_y` + lua["attach_ball_and_chain"] = attach_ball_and_chain; + /// Check if the entity `uid` has some specific `item_uid` by uid in their inventory + lua["entity_has_item_uid"] = entity_has_item_uid; + + auto entity_has_item_type = sol::overload( + static_cast(::entity_has_item_type), + static_cast)>(::entity_has_item_type)); + /// Check if the entity `uid` has some ENT_TYPE `entity_type` in their inventory, can also use table of entity_types + lua["entity_has_item_type"] = entity_has_item_type; + + auto entity_get_items_by = sol::overload( + static_cast (*)(uint32_t, ENT_TYPE, ENTITY_MASK)>(::entity_get_items_by), + static_cast (*)(uint32_t, std::vector, ENTITY_MASK)>(::entity_get_items_by)); + /// Gets uids of entities attached to given entity uid. Use `entity_type` and `mask` ([MASK](#MASK)) to filter, set them to 0 to return all attached entities. + lua["entity_get_items_by"] = entity_get_items_by; + /// Kills an entity by uid. `destroy_corpse` defaults to `true`, if you are killing for example a caveman and want the corpse to stay make sure to pass `false`. + lua["kill_entity"] = kill_entity; + /// Pick up another entity by uid. Make sure you're not already holding something, or weird stuff will happen. + lua["pick_up"] = [](uint32_t who_uid, uint32_t what_uid) + { + Movable* ent = get_entity_ptr(who_uid)->as(); + Movable* item = get_entity_ptr(what_uid)->as(); + if (ent != nullptr && item != nullptr) + { + ent->pick_up(item); + } + }; + /// Drop held entity, `what_uid` optional, if set, it will check if entity is holding that entity first before dropping it + lua["drop"] = [](uint32_t who_uid, std::optional what_uid) + { + auto ent = get_entity_ptr(who_uid); + if (ent == nullptr) + return; + + if (!ent->is_movable()) // game would probably use the is_player_or_monster function here, since they are the only ones who should be able to hold something + return; + + auto mov = ent->as(); + if (what_uid.has_value()) // should we handle what_uid = -1 the same way? + { + auto item = get_entity_ptr(what_uid.value()); + if (item == nullptr) + return; + if (item->overlay != mov && mov->holding_uid == what_uid) + return; + } + mov->drop(); + }; + /// Unequips the currently worn backitem + lua["unequip_backitem"] = unequip_backitem; + /// Returns the uid of the currently worn backitem, or -1 if wearing nothing + lua["worn_backitem"] = worn_backitem; + /// Apply changes made in [get_type](#get_type)() to entity instance by uid. + lua["apply_entity_db"] = [](uint32_t uid) + { + Entity* ent = get_entity_ptr(uid); + if (ent != nullptr) + ent->apply_db(); + }; + /// Calculate the tile distance of two entities by uid + lua["distance"] = [](uint32_t uid_a, uint32_t uid_b) -> float + { + // who though this was good name for this? + Entity* ea = get_entity_ptr(uid_a); + Entity* eb = get_entity_ptr(uid_b); + if (ea == nullptr || eb == nullptr) + return -1.0f; + else + return (float)std::sqrt(std::pow(ea->abs_position().x - eb->abs_position().x, 2) + std::pow(ea->abs_position().y - eb->abs_position().y, 2)); + }; + /// Poisons entity, to cure poison set [Movable](#Movable).`poison_tick_timer` to -1 + lua["poison_entity"] = poison_entity; + lua["Entity"]["as_entity"] = &Entity::as; lua["Entity"]["as_movable"] = &Entity::as; diff --git a/src/game_api/script/usertypes/game_patches_lua.cpp b/src/game_api/script/usertypes/game_patches_lua.cpp new file mode 100644 index 000000000..028399bf1 --- /dev/null +++ b/src/game_api/script/usertypes/game_patches_lua.cpp @@ -0,0 +1,110 @@ +#include "game_patches_lua.hpp" + +#include + +#include "game_patches.hpp" + +namespace NGamePatches +{ +void register_usertypes(sol::state& lua) +{ + /// Sets the amount of blood drops in the Kapala needed to trigger a health increase (default = 7). + lua["set_kapala_blood_threshold"] = set_kapala_blood_threshold; + /// Sets the hud icon for the Kapala (0-6 ; -1 for default behaviour). + /// If you set a Kapala treshold greater than 7, make sure to set the hud icon in the range 0-6, or other icons will appear in the hud! + lua["set_kapala_hud_icon"] = set_kapala_hud_icon; + /// Changes characteristics of (all) sparktraps: speed, rotation direction and distance from center + /// Speed: expressed as the amount that should be added to the angle every frame (use a negative number to go in the other direction) + /// Distance from center: if you go above 3.0 the game might crash because a spark may go out of bounds! + lua["modify_sparktraps"] = modify_sparktraps; + /// 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 + lua["activate_sparktraps_hack"] = activate_sparktraps_hack; + /// Set layer to search for storage items on + lua["set_storage_layer"] = set_storage_layer; + /// Sets the Y-level at which Olmec changes phases + lua["set_olmec_phase_y_level"] = set_olmec_phase_y_level; + /// Forces Olmec to stay on phase 0 (stomping) + lua["force_olmec_phase_0"] = force_olmec_phase_0; + /// Determines when the ghost appears, either when the player is cursed or not + lua["set_ghost_spawn_times"] = set_ghost_spawn_times; + /// Determines whether the ghost appears when breaking the ghost pot + lua["set_cursepot_ghost_enabled"] = set_cursepot_ghost_enabled; + /// Determines whether the time ghost appears, including the showing of the ghost toast + lua["set_time_ghost_enabled"] = set_time_ghost_enabled; + /// Determines whether the time jelly appears in cosmic ocean + lua["set_time_jelly_enabled"] = set_time_jelly_enabled; + /// Enables or disables the default position based camp camera bounds, to set them manually yourself + lua["set_camp_camera_bounds_enabled"] = set_camp_camera_bounds_enabled; + /// Sets which entities are affected by a bomb explosion. Default = MASK.PLAYER | MASK.MOUNT | MASK.MONSTER | MASK.ITEM | MASK.ACTIVEFLOOR | MASK.FLOOR + lua["set_explosion_mask"] = set_explosion_mask; + /// Sets the maximum length of a thrown rope (anchor segment not included). Unfortunately, setting this higher than default (6) creates visual glitches in the rope, even though it is fully functional. + lua["set_max_rope_length"] = set_max_rope_length; + /// Change ENT_TYPE's spawned by `FLOOR_SUNCHALLENGE_GENERATOR`, by default there are 4:
+ /// {MONS_WITCHDOCTOR, MONS_VAMPIRE, MONS_SORCERESS, MONS_NECROMANCER}
+ /// Use empty table as argument to reset to the game default + lua["change_sunchallenge_spawns"] = change_sunchallenge_spawns; + /// Change ENT_TYPE's spawned in dice shops (Madame Tusk as well), by default there are 25:
+ /// {ITEM_PICKUP_BOMBBAG, ITEM_PICKUP_BOMBBOX, ITEM_PICKUP_ROPEPILE, ITEM_PICKUP_COMPASS, ITEM_PICKUP_PASTE, ITEM_PICKUP_PARACHUTE, ITEM_PURCHASABLE_CAPE, ITEM_PICKUP_SPECTACLES, ITEM_PICKUP_CLIMBINGGLOVES, ITEM_PICKUP_PITCHERSMITT, + /// ENT_TYPE_ITEM_PICKUP_SPIKESHOES, ENT_TYPE_ITEM_PICKUP_SPRINGSHOES, ITEM_MACHETE, ITEM_BOOMERANG, ITEM_CROSSBOW, ITEM_SHOTGUN, ITEM_FREEZERAY, ITEM_WEBGUN, ITEM_CAMERA, ITEM_MATTOCK, ITEM_PURCHASABLE_JETPACK, ITEM_PURCHASABLE_HOVERPACK, + /// ITEM_TELEPORTER, ITEM_PURCHASABLE_TELEPORTER_BACKPACK, ITEM_PURCHASABLE_POWERPACK}
+ /// Min 6, Max 255, if you want less then 6 you need to write some of them more then once (they will have higher "spawn chance"). + /// If you use this function in the level with dice shop in it, you have to update `item_ids` in the [ITEM_DICE_PRIZE_DISPENSER](#PrizeDispenser). + /// Use empty table as argument to reset to the game default + lua["change_diceshop_prizes"] = change_diceshop_prizes; + /// Change ENT_TYPE's spawned when you damage the altar, by default there are 6:
+ /// {MONS_BAT, MONS_BEE, MONS_SPIDER, MONS_JIANGSHI, MONS_FEMALE_JIANGSHI, MONS_VAMPIRE}
+ /// Max 255 types. + /// Use empty table as argument to reset to the game default + lua["change_altar_damage_spawns"] = change_altar_damage_spawns; + /// Change ENT_TYPE's spawned when Waddler dies, by default there are 3:
+ /// {ITEM_PICKUP_COMPASS, ITEM_CHEST, ITEM_KEY}
+ /// Max 255 types. + /// Use empty table as argument to reset to the game default + lua["change_waddler_drop"] = change_waddler_drop; + /// Change how much health the ankh gives you after death, with every beat (the heart beat effect) it will add `beat_add_health` to your health, + /// `beat_add_health` has to be divisor of `health` and can't be 0, otherwise the function does nothing. Set `health` to 0 to return to the game defaults + /// If you set `health` above the game max health it will be forced down to the game max + lua["modify_ankh_health_gain"] = modify_ankh_health_gain; + /// Change the amount of frames after the damage from poison is applied + lua["change_poison_timer"] = change_poison_timer; + /// Disable all crust item spawns, returns whether they were already disabled before the call + lua["disable_floor_embeds"] = disable_floor_embeds; + /// Force the character unlocked in either ending to ENT_TYPE. Set to 0 to reset to the default guys. Does not affect the texture of the actual savior. (See example) + lua["set_ending_unlock"] = set_ending_unlock; + /// Olmec cutscene moves Olmec and destroys the four floor tiles, so those things never happen if the cutscene is disabled, and Olmec will spawn on even ground. More useful for level gen mods, where the cutscene doesn't make sense. You can also set olmec_cutscene.timer to the last frame (809) to skip to the end, with Olmec in the hole. + lua["set_olmec_cutscene_enabled"] = [](bool enable) + { set_skip_olmec_cutscene(!enable); }; + /// Tiamat cutscene is also responsible for locking the exit door, so you may need to close it yourself if you still want Tiamat kill to be required + lua["set_tiamat_cutscene_enabled"] = [](bool enable) + { set_skip_tiamat_cutscene(!enable); }; + /// Activate custom variables for position used for detecting the player (normally hardcoded) + /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Tiamat entity, recommending set_post_entity_spawn + /// default game values are: attack_x = 17.5 attack_y = 62.5 + lua["activate_tiamat_position_hack"] = activate_tiamat_position_hack; + /// Activate custom variables for speed and y coordinate limit for crushing elevator + /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each CrushElevator entity, recommending set_post_entity_spawn + /// default game values are: speed = 0.0125, y_limit = 98.5 + lua["activate_crush_elevator_hack"] = activate_crush_elevator_hack; + /// Activate custom variables for y coordinate limit for hundun and spawn of it's heads + /// note: because those variables are custom and game does not initiate them, you need to do it yourself for each Hundun entity, recommending set_post_entity_spawn + /// default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 + lua["activate_hundun_hack"] = activate_hundun_hack; + /// Allows you to disable the control over the door for Hundun and Tiamat + /// This will also prevent game crashing when there is no exit door when they are in level + lua["set_boss_door_control_enabled"] = set_boss_door_control_enabled; + /// Setting to false disables all player logic in 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. + lua["set_level_logic_enabled"] = set_level_logic_enabled; + /// Setting to true will stop the state update from unpausing after a screen load, leaving you with state.pause == PAUSE.FADE on the first frame to do what you want. + lua["set_start_level_paused"] = set_start_level_paused; + /// This disables the `state.camera_layer` to be forced to the `(leader player).layer` and setting of the `state.layer_transition_timer` & `state.transition_to_layer` when player enters layer door. + /// Letting you control those manually. + /// Look at the example on how to mimic game layer switching behavior + lua["set_camera_layer_control_enabled"] = set_camera_layer_control_enabled; + /// 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 hardcoded 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; +} +}; // namespace NGamePatches diff --git a/src/game_api/script/usertypes/game_patches_lua.hpp b/src/game_api/script/usertypes/game_patches_lua.hpp new file mode 100644 index 000000000..c77080069 --- /dev/null +++ b/src/game_api/script/usertypes/game_patches_lua.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace sol +{ +class state; +} // namespace sol + +namespace NGamePatches +{ +void register_usertypes(sol::state& lua); +}; diff --git a/src/game_api/script/usertypes/global_players_lua.cpp b/src/game_api/script/usertypes/global_players_lua.cpp new file mode 100644 index 000000000..f886febd0 --- /dev/null +++ b/src/game_api/script/usertypes/global_players_lua.cpp @@ -0,0 +1,121 @@ +#include "global_players_lua.hpp" + +#include "entities_chars.hpp" +#include "state.hpp" + +#include +#include +#include + +class Player; + +struct Players +{ + // This is probably over complicating + // but i couldn't find better solution for the global players to be always correct + // (not return reference to non existing entity when in between screens etc. like in draw callback) + + using value_type = Player*; + using iterator = std::vector::iterator; + + Players() + { + update(); + } + size_t size() + { + update(); + return p.size(); + } + Player* at(const int index) + { + update(); + if (index < 0 || index >= p.size()) + return nullptr; + + return p[index]; + } + auto begin() + { + return p.begin(); + } + auto end() + { + return p.end(); + } + + private: + std::vector p; + + void update() + { + p = get_state_ptr()->get_players(); + } + struct lua_iterator_state + { + typedef std::vector::iterator it_t; + it_t begin; + it_t it; + it_t last; + + lua_iterator_state(Players& mt) + : begin(mt.begin()), it(mt.begin()), last(mt.end()){}; + }; + static std::tuple my_next(sol::user user_it_state, sol::this_state l) + { + // this gets called + // to start the first iteration, and every + // iteration there after + + lua_iterator_state& it_state = user_it_state; + auto& it = it_state.it; + if (it == it_state.last) + { + // return nil to signify that there's nothing more to work with. + return std::make_tuple(sol::object(sol::lua_nil), sol::object(sol::lua_nil)); + } + // 2 values are returned (pushed onto the stack): + // the key and the value + // the state is left alone + auto r = std::make_tuple( + sol::object(l, sol::in_place, it - it_state.begin + 1), + sol::object(l, sol::in_place, *it)); + // the iterator must be moved forward one before we return + std::advance(it, 1); + return r; + } + + public: + static auto my_pairs(Players& mt) + { + mt.update(); + // pairs expects 3 returns: + // the "next" function on how to advance, + // the "table" itself or some state, + // and an initial key value (can be nil) + + // prepare our state + lua_iterator_state it_state(mt); + // sol::user is a space/time optimization over regular + // usertypes, it's incompatible with regular usertypes and + // stores the type T directly in lua without any pretty + // setup saves space allocation and a single dereference + return std::make_tuple(&my_next, sol::user(std::move(it_state)), sol::lua_nil); + } +}; + +namespace NGPlayers +{ +void register_usertypes(sol::state& lua) +{ + /// NoDoc + lua.new_usertype( + "Players", sol::no_constructor, sol::meta_function::index, [](Players* p, const int index) + { return p->at(index - 1); }, + sol::meta_function::pairs, + Players::my_pairs); + Players players; + + lua["players"] = players; +}; +} // namespace NGPlayers diff --git a/src/game_api/script/usertypes/global_players_lua.hpp b/src/game_api/script/usertypes/global_players_lua.hpp new file mode 100644 index 000000000..3d99d3b9d --- /dev/null +++ b/src/game_api/script/usertypes/global_players_lua.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace sol +{ +class state; +} // namespace sol + +namespace NGPlayers +{ +void register_usertypes(sol::state& lua); +}; diff --git a/src/game_api/script/usertypes/gui_lua.cpp b/src/game_api/script/usertypes/gui_lua.cpp index e09541207..0a6ff2ebd 100644 --- a/src/game_api/script/usertypes/gui_lua.cpp +++ b/src/game_api/script/usertypes/gui_lua.cpp @@ -1208,6 +1208,16 @@ void register_usertypes(sol::state& lua) auto luaCb = HotKeyCallback{cb, key, -1, false, 0}; return backend->register_hotkey(luaCb, HOTKEY_TYPE::NORMAL) /**/; }); + lua.create_named_table("HOTKEY_TYPE", "NORMAL", HOTKEY_TYPE::NORMAL, "GLOBAL", HOTKEY_TYPE::GLOBAL, "INPUT", HOTKEY_TYPE::INPUT); + /* HOTKEY_TYPE + // NORMAL + // Suppressed when the game window is inactive or inputting text in this tool instance (get_io().wantkeyboard == true). Can't detect if OL is in a text input and script is running in PL though. Use ImGuiIO if you need to do that. + // GLOBAL + // Enabled even when the game window is inactive and will capture keys even from other programs. + // INPUT + // Enabled even when inputting text and will override normal text input keys. + */ + lua.create_named_table("DRAW_LAYER", "BACKGROUND", DRAW_LAYER::BACKGROUND, "FOREGROUND", DRAW_LAYER::FOREGROUND, "WINDOW", DRAW_LAYER::WINDOW); /// Deprecated diff --git a/src/game_api/script/usertypes/level_lua.cpp b/src/game_api/script/usertypes/level_lua.cpp index 7c1cecf21..d5e8cf228 100644 --- a/src/game_api/script/usertypes/level_lua.cpp +++ b/src/game_api/script/usertypes/level_lua.cpp @@ -22,7 +22,7 @@ #include // for min, max, monostate, get #include "containers/game_unordered_map.hpp" // for game_unordered_map -#include "entity.hpp" // for to_id +#include "entity_db.hpp" // for to_id #include "level_api.hpp" // for THEME_OVERRIDE, ThemeInfo #include "math.hpp" // for AABB #include "savedata.hpp" // for SaveData, Constellation... @@ -30,7 +30,7 @@ #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.hpp" // for StateMemory, enu... #include "state_structs.hpp" // for QuestsInfo, Camera, Que... void PreLoadLevelFilesContext::override_level_files(std::vector levels) @@ -45,47 +45,47 @@ void PreLoadLevelFilesContext::add_level_files(std::vector levels) bool PostRoomGenerationContext::set_room_template(uint32_t x, uint32_t y, LAYER layer, ROOM_TEMPLATE room_template) { const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - return State::get().ptr_local()->level_gen->set_room_template(x, y, real_layer, room_template); + return HeapBase::get().level_gen()->set_room_template(x, y, real_layer, room_template); } bool PostRoomGenerationContext::mark_as_machine_room_origin(uint32_t x, uint32_t y, LAYER layer) { const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - return State::get().ptr_local()->level_gen->mark_as_machine_room_origin(x, y, real_layer); + return HeapBase::get().level_gen()->mark_as_machine_room_origin(x, y, real_layer); } bool PostRoomGenerationContext::mark_as_set_room(uint32_t x, uint32_t y, LAYER layer) { const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - return State::get().ptr_local()->level_gen->mark_as_set_room(x, y, real_layer, true); + return HeapBase::get().level_gen()->mark_as_set_room(x, y, real_layer, true); } bool PostRoomGenerationContext::unmark_as_set_room(uint32_t x, uint32_t y, LAYER layer) { const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - return State::get().ptr_local()->level_gen->mark_as_set_room(x, y, real_layer, false); + return HeapBase::get().level_gen()->mark_as_set_room(x, y, real_layer, false); } bool PostRoomGenerationContext::set_shop_type(uint32_t x, uint32_t y, LAYER layer, int32_t shop_type) { const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - return State::get().ptr_local()->level_gen->set_shop_type(x, y, real_layer, static_cast(shop_type)); + return HeapBase::get().level_gen()->set_shop_type(x, y, real_layer, static_cast(shop_type)); } bool PostRoomGenerationContext::set_procedural_spawn_chance(PROCEDURAL_CHANCE chance_id, uint32_t inverse_chance) { - return State::get().ptr_local()->level_gen->set_procedural_spawn_chance(chance_id, inverse_chance); + return HeapBase::get().level_gen()->set_procedural_spawn_chance(chance_id, inverse_chance); } void PostRoomGenerationContext::set_num_extra_spawns(std::uint32_t extra_spawn_id, std::uint32_t num_spawns_front_layer, std::uint32_t num_spawns_back_layer) { - State::get().ptr_local()->level_gen->data->set_num_extra_spawns(extra_spawn_id, num_spawns_front_layer, num_spawns_back_layer); + HeapBase::get().level_gen()->data->set_num_extra_spawns(extra_spawn_id, num_spawns_front_layer, num_spawns_back_layer); } std::optional PostRoomGenerationContext::define_short_tile_code(ShortTileCodeDef short_tile_code_def) { - return State::get().ptr_local()->level_gen->data->define_short_tile_code(short_tile_code_def); + return HeapBase::get().level_gen()->data->define_short_tile_code(short_tile_code_def); } void PostRoomGenerationContext::change_short_tile_code(SHORT_TILE_CODE short_tile_code, ShortTileCodeDef short_tile_code_def) { - State::get().ptr_local()->level_gen->data->change_short_tile_code(short_tile_code, short_tile_code_def); + HeapBase::get().level_gen()->data->change_short_tile_code(short_tile_code, short_tile_code_def); } std::optional PreHandleRoomTilesContext::get_short_tile_code(uint32_t tx, uint32_t ty, LAYER layer) const @@ -434,7 +434,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->reset_theme_flags(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->reset_theme_flags(); run_post_func(index); } void init_flags() @@ -444,7 +444,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->init_flags(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->init_flags(); run_post_func(index); } void init_level() @@ -454,7 +454,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->init_level(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->init_level(); run_post_func(index); } void init_rooms() @@ -464,7 +464,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->init_rooms(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->init_rooms(); run_post_func(index); } void generate_path(bool reset) @@ -474,7 +474,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index, reset); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->generate_path(reset); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->generate_path(reset); run_post_func(index, reset); } void add_special_rooms() @@ -484,7 +484,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_special_rooms(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_special_rooms(); run_post_func(index); } void add_player_coffin() @@ -494,7 +494,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_player_coffin(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_player_coffin(); run_post_func(index); } void add_dirk_coffin() @@ -504,7 +504,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_dirk_coffin(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_dirk_coffin(); run_post_func(index); } void add_idol() @@ -514,7 +514,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_idol(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_idol(); run_post_func(index); } void add_vault() @@ -524,7 +524,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_vault(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_vault(); run_post_func(index); } void add_coffin() @@ -534,7 +534,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_coffin(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_coffin(); run_post_func(index); } void add_special_feeling() @@ -544,7 +544,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->add_special_feeling(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->add_special_feeling(); run_post_func(index); } void spawn_level() @@ -554,7 +554,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme_or_dwelling(index)]->spawn_level(); + HeapBase::get().level_gen()->themes[get_override_theme_or_dwelling(index)]->spawn_level(); run_post_func(index); } void spawn_border() @@ -564,7 +564,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme_or_dwelling(index)]->spawn_border(); + HeapBase::get().level_gen()->themes[get_override_theme_or_dwelling(index)]->spawn_border(); run_post_func(index); } void post_process_level() @@ -574,7 +574,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->post_process_level(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->post_process_level(); run_post_func(index); } void spawn_traps() @@ -584,7 +584,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_traps(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_traps(); run_post_func(index); } void post_process_entities() @@ -594,7 +594,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->post_process_entities(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->post_process_entities(); run_post_func(index); } void spawn_procedural() @@ -604,7 +604,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_procedural(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_procedural(); run_post_func(index); } void spawn_background() @@ -614,7 +614,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_background(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_background(); run_post_func(index); } void spawn_lights() @@ -624,7 +624,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_lights(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_lights(); run_post_func(index); } void spawn_transition() @@ -634,7 +634,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_transition(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_transition(); run_post_func(index); } void post_transition() @@ -644,7 +644,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme_or_dwelling(index)]->post_transition(); + HeapBase::get().level_gen()->themes[get_override_theme_or_dwelling(index)]->post_transition(); run_post_func(index); } void spawn_players() @@ -654,7 +654,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme_or_dwelling(index)]->spawn_players(); + HeapBase::get().level_gen()->themes[get_override_theme_or_dwelling(index)]->spawn_players(); run_post_func(index); } void spawn_effects() @@ -664,12 +664,12 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_effects(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_effects(); else { // set sane camera bounds anyway for your convenience // you can always change this in post - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); state->camera->bounds_left = 0.5f; state->camera->bounds_top = 124.5f; state->camera->bounds_right = 10.0f * state->w + 4.5f; @@ -689,7 +689,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_theme_id(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_theme_id(); run_post_func(index, ret); return ret; } @@ -701,7 +701,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_base_id(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_base_id(); run_post_func(index, ret); return ret; } @@ -713,7 +713,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_floor_spreading_type(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_floor_spreading_type(); run_post_func(index, ret); return ret; } @@ -725,7 +725,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_floor_spreading_type2(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_floor_spreading_type2(); run_post_func(index, ret); return ret; } @@ -737,7 +737,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_transition_styled_floor(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_transition_styled_floor(); run_post_func(index, ret); return ret; } @@ -749,7 +749,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_floor_spreading_type2(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_floor_spreading_type2(); run_post_func(index, ret); return ret; } @@ -761,7 +761,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_transition_styled_floor_type(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_transition_styled_floor_type(); run_post_func(index, ret); return ret; } @@ -773,7 +773,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_backwall_type(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_backwall_type(); run_post_func(index, ret); return ret; } @@ -785,7 +785,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_border_type(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_border_type(); run_post_func(index, ret); return ret; } @@ -797,7 +797,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_critter_type(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_critter_type(); run_post_func(index, ret); return ret; } @@ -809,7 +809,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_liquid_gravity(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_liquid_gravity(); run_post_func(index, ret); return ret; } @@ -821,7 +821,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_player_damage(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_player_damage(); run_post_func(index, ret); return ret; } @@ -833,7 +833,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_explosion_soot(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_explosion_soot(); run_post_func(index, ret); return ret; } @@ -845,7 +845,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_backlayer_lut(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_backlayer_lut(); run_post_func(index, ret); return ret; } @@ -857,7 +857,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_backlayer_light_level(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_backlayer_light_level(); run_post_func(index, ret); return ret; } @@ -869,7 +869,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_loop(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_loop(); run_post_func(index, ret); return ret; } @@ -881,7 +881,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_vault_level(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_vault_level(); run_post_func(index, ret); return ret; } @@ -905,7 +905,7 @@ class CustomTheme : public ThemeInfo else if (get_override_func_enabled(index)) ret = run_override_func(index, texture_id).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_dynamic_texture(texture_id); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_dynamic_texture(texture_id); run_post_func(index, ret); return ret; } @@ -916,18 +916,18 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme_or_dwelling(index)]->pre_transition(); + HeapBase::get().level_gen()->themes[get_override_theme_or_dwelling(index)]->pre_transition(); run_post_func(index); } uint32_t get_exit_room_y_level() { auto index = THEME_OVERRIDE::EXIT_ROOM_Y_LEVEL; - uint32_t ret = State::get().ptr_local()->h - 1; + uint32_t ret = HeapBase::get().state()->h - 1; run_pre_func(index); if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_exit_room_y_level(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_exit_room_y_level(); run_post_func(index, ret); return ret; } @@ -939,7 +939,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) ret = run_override_func(index).value_or(ret); else if (get_override_enabled(index)) - ret = State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->get_shop_chance(); + ret = HeapBase::get().level_gen()->themes[get_override_theme(index)]->get_shop_chance(); run_post_func(index, ret); return ret; } @@ -950,7 +950,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_decoration(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_decoration(); run_post_func(index); } void spawn_decoration2() @@ -960,7 +960,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_decoration2(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_decoration2(); run_post_func(index); } void spawn_extra() @@ -970,7 +970,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->spawn_extra(); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->spawn_extra(); run_post_func(index); } void do_procedural_spawn(SpawnInfo* info) @@ -980,7 +980,7 @@ class CustomTheme : public ThemeInfo if (get_override_func_enabled(index)) run_override_func(index, info); else if (get_override_enabled(index)) - State::get().ptr_local()->level_gen->themes[get_override_theme(index)]->do_procedural_spawn(info); + HeapBase::get().level_gen()->themes[get_override_theme(index)]->do_procedural_spawn(info); run_post_func(index, info); } }; @@ -1026,12 +1026,12 @@ void register_usertypes(sol::state& lua) /// Gets a short tile code based on definition, returns `nil` if it can't be found lua["get_short_tile_code"] = [](ShortTileCodeDef short_tile_code_def) -> std::optional { - return State::get().ptr_local()->level_gen->data->get_short_tile_code(short_tile_code_def); + return HeapBase::get().level_gen()->data->get_short_tile_code(short_tile_code_def); }; /// Gets the definition of a short tile code (if available), will vary depending on which file is loaded lua["get_short_tile_code_definition"] = [](SHORT_TILE_CODE short_tile_code) -> std::optional { - return State::get().ptr_local()->level_gen->data->get_short_tile_code_def(short_tile_code); + return HeapBase::get().level_gen()->data->get_short_tile_code_def(short_tile_code); }; /// Define a new procedural spawn, the function `nil do_spawn(float x, float y, LAYER layer)` contains your code to spawn the thing, whatever it is. @@ -1083,57 +1083,57 @@ void register_usertypes(sol::state& lua) /// The value only makes sense after level generation is complete, aka after `ON.POST_LEVEL_GENERATION` has run. lua["get_missing_extra_spawns"] = [](std::uint32_t extra_spawn_chance_id) -> std::pair { - return State::get().ptr()->level_gen->data->get_missing_extra_spawns(extra_spawn_chance_id); + return HeapBase::get().level_gen()->data->get_missing_extra_spawns(extra_spawn_chance_id); }; /// Transform a position to a room index to be used in `get_room_template` and `PostRoomGenerationContext.set_room_template` lua["get_room_index"] = [](float x, float y) -> std::pair { - return State::get().ptr_local()->level_gen->get_room_index(x, y); + return HeapBase::get().level_gen()->get_room_index(x, y); }; /// Transform a room index into the top left corner position in the room lua["get_room_pos"] = [](int x, int y) -> std::pair { - return State::get().ptr_local()->level_gen->get_room_pos(x, y); + return HeapBase::get().level_gen()->get_room_pos(x, y); }; /// Get the room template given a certain index, returns `nil` if coordinates are out of bounds lua["get_room_template"] = [](int x, int y, LAYER layer) -> std::optional { const uint8_t real_layer = enum_to_layer(layer); - return State::get().ptr_local()->level_gen->get_room_template(x, y, real_layer); + return HeapBase::get().level_gen()->get_room_template(x, y, real_layer); }; /// Get whether a room is flipped at the given index, returns `false` if coordinates are out of bounds lua["is_room_flipped"] = [](int x, int y) -> bool { - return State::get().ptr_local()->level_gen->is_room_flipped(x, y); + return HeapBase::get().level_gen()->is_room_flipped(x, y); }; /// Get whether a room is the origin of a machine room lua["is_machine_room_origin"] = [](int x, int y) -> bool { - return State::get().ptr_local()->level_gen->is_machine_room_origin(x, y); + return HeapBase::get().level_gen()->is_machine_room_origin(x, y); }; /// For debugging only, get the name of a room template, returns `'invalid'` if room template is not defined lua["get_room_template_name"] = [](int16_t room_template) -> std::string_view { - return State::get().ptr_local()->level_gen->get_room_template_name(room_template); + return HeapBase::get().level_gen()->get_room_template_name(room_template); }; /// Define a new room template to use with `set_room_template` lua["define_room_template"] = [](std::string room_template, ROOM_TEMPLATE_TYPE type) -> uint16_t { - return State::get().ptr_local()->level_gen->data->define_room_template(std::move(room_template), static_cast(type)); + return HeapBase::get().level_gen()->data->define_room_template(std::move(room_template), static_cast(type)); }; /// Set the size of room template in tiles, the template must be of type `ROOM_TEMPLATE_TYPE.MACHINE_ROOM`. lua["set_room_template_size"] = [](uint16_t room_template, uint16_t width, uint16_t height) -> bool { - return State::get().ptr_local()->level_gen->data->set_room_template_size(room_template, width, height); + return HeapBase::get().level_gen()->data->set_room_template_size(room_template, width, height); }; /// Get the inverse chance of a procedural spawn for the current level. /// A return value of 0 does not mean the chance is infinite, it means the chance is zero. lua["get_procedural_spawn_chance"] = [](PROCEDURAL_CHANCE chance_id) -> uint32_t { - return State::get().ptr_local()->level_gen->get_procedural_spawn_chance(chance_id); + return HeapBase::get().level_gen()->get_procedural_spawn_chance(chance_id); }; /// Gets the sub theme of the current cosmic ocean level, returns COSUBTHEME.NONE if the current level is not a CO level. @@ -1144,13 +1144,13 @@ void register_usertypes(sol::state& lua) /// Gets the value for the specified config lua["get_level_config"] = [](LEVEL_CONFIG config) -> uint32_t { - return State::get().ptr_local()->level_gen->data->level_config[config]; + return HeapBase::get().level_gen()->data->level_config[config]; }; /// Set the value for the specified config lua["set_level_config"] = [](LEVEL_CONFIG config, uint32_t value) { - State::get().ptr_local()->level_gen->data->level_config[config] = value; + HeapBase::get().level_gen()->data->level_config[config] = value; }; auto grow_vines = sol::overload( @@ -1326,16 +1326,19 @@ void register_usertypes(sol::state& lua) lua["force_custom_theme"] = sol::overload( [](CustomTheme* customtheme) { - State::get().ptr()->current_theme = customtheme; + HeapBase::get().state()->current_theme = customtheme; }, [](ThemeInfo* customtheme) { - State::get().ptr()->current_theme = customtheme; + HeapBase::get().state()->current_theme = customtheme; }, [](uint32_t customtheme) { if (customtheme < 18) - State::get().ptr()->current_theme = State::get().ptr()->level_gen->themes[customtheme - 1]; + { + auto state = HeapBase::get().state(); + state->current_theme = state->level_gen->themes[customtheme - 1]; + } }); /// Force current subtheme used in the CO theme. You can pass a CustomTheme, ThemeInfo or THEME. Not to be confused with force_co_subtheme. @@ -1343,16 +1346,19 @@ void register_usertypes(sol::state& lua) lua["force_custom_subtheme"] = sol::overload( [](CustomTheme* customtheme) { - State::get().ptr()->level_gen->theme_cosmicocean->sub_theme = customtheme; + HeapBase::get().level_gen()->theme_cosmicocean->sub_theme = customtheme; }, [](ThemeInfo* customtheme) { - State::get().ptr()->level_gen->theme_cosmicocean->sub_theme = customtheme; + HeapBase::get().level_gen()->theme_cosmicocean->sub_theme = customtheme; }, [](uint32_t customtheme) { if (customtheme < 18) - State::get().ptr()->level_gen->theme_cosmicocean->sub_theme = State::get().ptr()->level_gen->themes[customtheme - 1]; + { + auto level_gen = HeapBase::get().level_gen(); + level_gen->theme_cosmicocean->sub_theme = level_gen->themes[customtheme - 1]; + } }); /// Context received in ON.PRE_LOAD_LEVEL_FILES, used for forcing specific `.lvl` files to load. @@ -1715,13 +1721,13 @@ void register_usertypes(sol::state& lua) "FLAGGED_LIQUID_ROOMS", 16); - StateMemory* main_state = State::get().ptr_main(); + LevelGenSystem* level_gen = HeapBase::get_main().level_gen(); lua.create_named_table("TILE_CODE" //, "EMPTY", 0 //, "", ...check__[tile_codes.txt]\[game_data/tile_codes.txt\]... ); - for (const auto& [tile_code_name, tile_code] : main_state->level_gen->data->tile_codes) + for (const auto& [tile_code_name, tile_code] : level_gen->data->tile_codes) { std::string clean_tile_code_name = tile_code_name.c_str(); std::transform( @@ -1736,7 +1742,7 @@ void register_usertypes(sol::state& lua) //, "", ...check__[room_templates.txt]\[game_data/room_templates.txt\]... ); - auto room_templates = main_state->level_gen->data->room_templates; + auto room_templates = level_gen->data->room_templates; room_templates["empty_backlayer"] = {9}; room_templates["boss_arena"] = {22}; room_templates["shop_jail_backlayer"] = {44}; @@ -1759,7 +1765,7 @@ void register_usertypes(sol::state& lua) //, "ARROWTRAP_CHANCE", 0 //, "", ...check__[spawn_chances.txt]\[game_data/spawn_chances.txt\]... ); - for (auto* chances : {&main_state->level_gen->data->monster_chances, &main_state->level_gen->data->trap_chances}) + for (auto* chances : {&level_gen->data->monster_chances, &level_gen->data->trap_chances}) { for (const auto& [chance_name, chance] : *chances) { diff --git a/src/game_api/script/usertypes/options_lua.cpp b/src/game_api/script/usertypes/options_lua.cpp new file mode 100644 index 000000000..1ea8d42dd --- /dev/null +++ b/src/game_api/script/usertypes/options_lua.cpp @@ -0,0 +1,153 @@ +#include "options_lua.hpp" + +#include +#include + +#include "script/lua_backend.hpp" + +namespace NOptions +{ +void register_usertypes(sol::state& lua) +{ + /// Add an integer option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft + /// limits, you can override them in the UI with double click. + // lua["register_option_int"] = [](std::string name, std::string desc, std::string long_desc, int value, int min, int max) + lua["register_option_int"] = sol::overload( + [](std::string name, std::string desc, std::string long_desc, int value, int min, int max) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, IntOption{value, min, max}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }, + [](std::string name, std::string desc, int value, int min, int max) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", IntOption{value, min, max}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }); + /// Add a float option that the user can change in the UI. Read with `options.name`, `value` is the default. Keep in mind these are just soft + /// limits, you can override them in the UI with double click. + // lua["register_option_float"] = [](std::string name, std::string desc, std::string long_desc, float value, float min, float max) + lua["register_option_float"] = sol::overload( + [](std::string name, std::string desc, std::string long_desc, float value, float min, float max) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, FloatOption{value, min, max}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }, + [](std::string name, std::string desc, float value, float min, float max) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", FloatOption{value, min, max}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }); + /// Add a boolean option that the user can change in the UI. Read with `options.name`, `value` is the default. + // lua["register_option_bool"] = [](std::string name, std::string desc, std::string long_desc, bool value) + lua["register_option_bool"] = sol::overload( + [](std::string name, std::string desc, std::string long_desc, bool value) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, BoolOption{value}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }, + [](std::string name, std::string desc, bool value) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", BoolOption{value}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }); + /// Add a string option that the user can change in the UI. Read with `options.name`, `value` is the default. + // lua["register_option_string"] = [](std::string name, std::string desc, std::string long_desc, std::string value) + lua["register_option_string"] = sol::overload( + [](std::string name, std::string desc, std::string long_desc, std::string value) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, StringOption{value}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }, + [](std::string name, std::string desc, std::string value) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", StringOption{value}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }); + /// Add a combobox option that the user can change in the UI. Read the int index of the selection with `options.name`. Separate `opts` with `\0`, + /// with a double `\0\0` at the end. `value` is the default index 1..n. + // lua["register_option_combo"] = [](std::string name, std::string desc, std::string long_desc, std::string opts, int value) + lua["register_option_combo"] = sol::overload( + [](std::string name, std::string desc, std::string long_desc, std::string opts, int value) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, ComboOption{value - 1, opts}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }, + [](std::string name, std::string desc, std::string long_desc, std::string opts) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, ComboOption{0, opts}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = 1; + }, + [](std::string name, std::string desc, std::string opts, int value) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", ComboOption{value - 1, opts}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = value; + }, + [](std::string name, std::string desc, std::string opts) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", ComboOption{0, opts}}; + if (backend->lua["options"][name] == sol::nil) + backend->lua[sol::create_if_nil]["options"][name] = 1; + }); + /// Add a button that the user can click in the UI. Sets the timestamp of last click on value and runs the callback function. + // lua["register_option_button"] = [](std::string name, std::string desc, std::string long_desc, sol::function on_click) + lua["register_option_button"] = sol::overload( + [](std::string name, std::string desc, std::string long_desc, sol::function callback) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, long_desc, ButtonOption{callback}}; + backend->lua[sol::create_if_nil]["options"][name] = -1; + }, + [](std::string name, std::string desc, sol::function callback) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {desc, "", ButtonOption{callback}}; + backend->lua[sol::create_if_nil]["options"][name] = -1; + }); + /// Add custom options using the window drawing functions. Everything drawn in the callback will be rendered in the options window and the return value saved to `options[name]` or overwriting the whole `options` table if using and empty name. + /// `value` is the default value, and pretty important because anything defined in the callback function will only be defined after the options are rendered. See the example for details. + ///
The callback signature is optional on_render(GuiDrawContext draw_ctx) + lua["register_option_callback"] = [](std::string name, sol::object value, sol::function on_render) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options[name] = {"", "", CustomOption{on_render}}; + if (backend->lua["options"][name] == sol::nil) + { + if (name != "") + backend->lua[sol::create_if_nil]["options"][name] = value; + else + backend->lua[sol::create_if_nil]["options"] = value; + } + }; + + /// Removes an option by name. To make complicated conditionally visible options you should probably just use register_option_callback though. + lua["unregister_option"] = [](std::string name) + { + auto backend = LuaBackend::get_calling_backend(); + backend->options.erase(name); + backend->lua["options"][name] = sol::nil; + }; +} +}; // namespace NOptions diff --git a/src/game_api/script/usertypes/options_lua.hpp b/src/game_api/script/usertypes/options_lua.hpp new file mode 100644 index 000000000..be2c23110 --- /dev/null +++ b/src/game_api/script/usertypes/options_lua.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace sol +{ +class state; +} // namespace sol + +namespace NOptions +{ +void register_usertypes(sol::state& lua); +}; diff --git a/src/game_api/script/usertypes/prng_lua.cpp b/src/game_api/script/usertypes/prng_lua.cpp index d84905a08..9c26b42a6 100644 --- a/src/game_api/script/usertypes/prng_lua.cpp +++ b/src/game_api/script/usertypes/prng_lua.cpp @@ -10,7 +10,8 @@ #include // for move, declval #include // for min, max, get -#include "prng.hpp" // for PRNG, PRNG::ENTITY_VARIATION, PRNG::EXTRA_SPAWNS +#include "heap_base.hpp" // for HeapBase +#include "prng.hpp" // for PRNG, PRNG::ENTITY_VARIATION, PRNG::EXTRA_SPAWNS namespace NPRNG { @@ -21,7 +22,7 @@ void register_usertypes(sol::state& lua) /// Seed the game prng. lua["seed_prng"] = [](int64_t seed) { - PRNG::get_main().seed(seed); + HeapBase::get().prng()->seed(seed); }; /// PRNG (short for Pseudo-Random-Number-Generator) holds 10 128bit wide buffers of memory that are mutated on every generation of a random number. @@ -48,12 +49,12 @@ void register_usertypes(sol::state& lua) &PRNG::set_pair); /// The global prng state, calling any function on it will advance the prng state, thus desynchronizing clients if it does not happen on both clients. - lua["prng"] = &PRNG::get_main(); + lua["prng"] = HeapBase::get_main().prng(); /// Get the thread-local version of prng lua["get_local_prng"] = []() -> PRNG* { - return &PRNG::get_local(); + return HeapBase::get().prng(); }; /// Determines what class of prng is used, which in turn determines which parts of the game's future prng is affected. See more info at [PRNG](#PRNG) diff --git a/src/game_api/script/usertypes/spawn_lua.cpp b/src/game_api/script/usertypes/spawn_lua.cpp new file mode 100644 index 000000000..a1b42e71a --- /dev/null +++ b/src/game_api/script/usertypes/spawn_lua.cpp @@ -0,0 +1,200 @@ +#include "spawn_lua.hpp" + +#include "aliases.hpp" +#include "entity.hpp" +#include "entity_lookup.hpp" +#include "script/lua_backend.hpp" +#include "spawn_api.hpp" + +#include +#include +#include + +namespace NSpawn +{ +void register_usertypes(sol::state& lua) +{ + auto spawn_liquid = sol::overload( + static_cast(::spawn_liquid), + static_cast(::spawn_liquid_ex), + static_cast(::spawn_liquid)); + /// Spawn liquids, always spawns in the front layer, will have fun effects if `entity_type` is not a liquid (only the short version, without velocity etc.). + /// Don't overuse this, you are still restricted by the liquid pool sizes and thus might crash the game. + /// `liquid_flags` - not much known about, 2 - will probably crash the game, 3 - pause_physics, 6-12 is probably agitation, surface_tension etc. set to 0 to ignore + /// `amount` - it will spawn amount x amount (so 1 = 1, 2 = 4, 3 = 6 etc.), `blobs_separation` is optional + lua["spawn_liquid"] = spawn_liquid; + /// Spawn an entity in position with some velocity and return the uid of spawned entity. + /// Uses level coordinates with [LAYER.FRONT](#LAYER) and LAYER.BACK, but player-relative coordinates with LAYER.PLAYER(n), where (n) is a player number (1-4). + lua["spawn_entity"] = spawn_entity_abs; + /// Short for [spawn_entity](#spawn_entity). + lua["spawn"] = spawn_entity_abs; + /// Spawns an entity directly on the floor below the tile at the given position. + /// Use this to avoid the little fall that some entities do when spawned during level gen callbacks. + lua["spawn_entity_snapped_to_floor"] = spawn_entity_snap_to_floor; + /// Short for [spawn_entity_snapped_to_floor](#spawn_entity_snapped_to_floor). + lua["spawn_on_floor"] = spawn_entity_snap_to_floor; + /// Spawn a grid entity, such as floor or traps, that snaps to the grid. + lua["spawn_grid_entity"] = spawn_entity_snap_to_grid; + /// Same as `spawn_entity` but does not trigger any pre-entity-spawn callbacks, so it will not be replaced by another script + lua["spawn_entity_nonreplaceable"] = spawn_entity_abs_nonreplaceable; + /// Short for [spawn_entity_nonreplaceable](#spawn_entity_nonreplaceable). + lua["spawn_critical"] = spawn_entity_abs_nonreplaceable; + /// Spawn a door to another world, level and theme and return the uid of spawned entity. + /// Uses level coordinates with LAYER.FRONT and LAYER.BACK, but player-relative coordinates with LAYER.PLAYERn + lua["spawn_door"] = spawn_door_abs; + /// Short for [spawn_door](#spawn_door). + lua["door"] = spawn_door_abs; + /// Spawn a door to backlayer. + lua["spawn_layer_door"] = spawn_backdoor_abs; + /// Short for [spawn_layer_door](#spawn_layer_door). + lua["layer_door"] = spawn_backdoor_abs; + /// Spawns apep with the choice if it going left or right, if you want the game to choose use regular spawn functions with `ENT_TYPE.MONS_APEP_HEAD` + lua["spawn_apep"] = spawn_apep; + + auto spawn_tree = sol::overload( + static_cast(::spawn_tree), + static_cast(::spawn_tree)); + /// Spawns and grows a tree + lua["spawn_tree"] = spawn_tree; + + auto spawn_mushroom = sol::overload( + static_cast(::spawn_mushroom), + static_cast(::spawn_mushroom)); + /// 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 + lua["spawn_mushroom"] = spawn_mushroom; + + auto spawn_unrolled_player_rope = sol::overload( + static_cast(::spawn_unrolled_player_rope), + static_cast(::spawn_unrolled_player_rope)); + + /// Spawns an already unrolled rope as if created by player + lua["spawn_unrolled_player_rope"] = spawn_unrolled_player_rope; + + /// NoDoc + /// Spawns an impostor lake, `top_threshold` determines how much space on top is rendered as liquid but does not have liquid physics, fill that space with real liquid + /// There needs to be other liquid in the level for the impostor lake to be visible, there can only be one impostor lake in the level + lua["spawn_impostor_lake"] = spawn_impostor_lake; + /// NoDoc + /// Fixes the bounds of impostor lakes in the liquid physics engine to match the bounds of the impostor lake entities. + lua["fix_impostor_lake_positions"] = fix_impostor_lake_positions; + /// Spawn a player in given location, if player of that slot already exist it will spawn clone, the game may crash as this is very unexpected situation + /// If you want to respawn a player that is a ghost, set in his Inventory `health` to above 0, and `time_of_death` to 0 and call this function, the ghost entity will be removed automatically + lua["spawn_player"] = spawn_player; + /// Spawn the PlayerGhost entity, it will not move and not be connected to any player, you can then use [steal_input](#steal_input) and send_input to control it + /// or change it's `player_inputs` to the `input` of real player so he can control it directly + lua["spawn_playerghost"] = spawn_playerghost; + /// Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. + /// This is run before the entity is spawned, spawn your own entity and return its uid to replace the intended spawn. + /// In many cases replacing the intended entity won't have the intended effect or will even break the game, so use only if you really know what you're doing. + ///
The callback signature is optional pre_entity_spawn(ENT_TYPE entity_type, float x, float y, int layer, Entity overlay_entity, SPAWN_TYPE spawn_flags) + lua["set_pre_entity_spawn"] = [](sol::function cb, SPAWN_TYPE flags, ENTITY_MASK mask, sol::variadic_args entity_types) -> CallbackId + { + std::vector types; + sol::type va_type = entity_types.get_type(); + if (va_type == sol::type::number) + { + types = std::vector(entity_types.begin(), entity_types.end()); + } + else if (va_type == sol::type::table) + { + types = entity_types.get>(0); + } + std::vector proper_types = get_proper_types(std::move(types)); + + auto backend = LuaBackend::get_calling_backend(); + backend->pre_entity_spawn_callbacks.push_back(EntitySpawnCallback{backend->cbcount, mask, std::move(proper_types), flags, std::move(cb)}); + return backend->cbcount++; + }; + /// Add a callback for a spawn of specific entity types or mask. Set `mask` to `MASK.ANY` to ignore that. + /// This is run right after the entity is spawned but before and particular properties are changed, e.g. owner or velocity. + ///
The callback signature is nil post_entity_spawn(Entity ent, SPAWN_TYPE spawn_flags) + lua["set_post_entity_spawn"] = [](sol::function cb, SPAWN_TYPE flags, ENTITY_MASK mask, sol::variadic_args entity_types) -> CallbackId + { + std::vector types; + sol::type va_type = entity_types.get_type(); + if (va_type == sol::type::number) + { + types = std::vector(entity_types.begin(), entity_types.end()); + } + else if (va_type == sol::type::table) + { + types = entity_types.get>(0); + } + std::vector proper_types = get_proper_types(std::move(types)); + + auto backend = LuaBackend::get_calling_backend(); + backend->post_entity_spawn_callbacks.push_back(EntitySpawnCallback{backend->cbcount, mask, std::move(proper_types), flags, std::move(cb)}); + return backend->cbcount++; + }; + + /// Spawn a Shopkeeper in the coordinates and make the room their shop. Returns the Shopkeeper uid. Also see [spawn_roomowner](#spawn_roomowner). + // lua["spawn_shopkeeper"] = [](float x, float y, LAYER layer, ROOM_TEMPLATE room_template = ROOM_TEMPLATE.SHOP) -> uint32_t + lua["spawn_shopkeeper"] = sol::overload( + [](float x, float y, LAYER layer) + { + return spawn_shopkeeper(x, y, layer); + }, + [](float x, float y, LAYER layer, ROOM_TEMPLATE room_template) + { + return spawn_shopkeeper(x, y, layer, room_template); + }); + + /// Spawn a RoomOwner (or a few other like [CavemanShopkeeper](#CavemanShopkeeper)) in the coordinates and make them own the room, optionally changing the room template. Returns the RoomOwner uid. + // lua["spawn_roomowner"] = [](ENT_TYPE owner_type, float x, float y, LAYER layer, ROOM_TEMPLATE room_template = -1) -> uint32_t + lua["spawn_roomowner"] = sol::overload( + [](ENT_TYPE owner_type, float x, float y, LAYER layer) + { + return spawn_roomowner(owner_type, x, y, layer); + }, + [](ENT_TYPE owner_type, float x, float y, LAYER layer, int16_t room_template) + { + return spawn_roomowner(owner_type, x, y, layer, room_template); + }); + + /// Spawn an entity of `entity_type` attached to some other entity `over_uid`, in offset `x`, `y` + lua["spawn_entity_over"] = spawn_entity_over; + /// Short for [spawn_entity_over](#spawn_entity_over) + lua["spawn_over"] = spawn_entity_over; + /// Spawn a companion (hired hand, player character, eggplant child) + lua["spawn_companion"] = spawn_companion; + + lua.create_named_table( + "SPAWN_TYPE", + "LEVEL_GEN", + SPAWN_TYPE_LEVEL_GEN, + "LEVEL_GEN_TILE_CODE", + SPAWN_TYPE_LEVEL_GEN_TILE_CODE, + "LEVEL_GEN_PROCEDURAL", + SPAWN_TYPE_LEVEL_GEN_PROCEDURAL, + "LEVEL_GEN_FLOOR_SPREADING", + SPAWN_TYPE_LEVEL_GEN_FLOOR_SPREADING, + "LEVEL_GEN_GENERAL", + SPAWN_TYPE_LEVEL_GEN_GENERAL, + "SCRIPT", + SPAWN_TYPE_SCRIPT, + "SYSTEMIC", + SPAWN_TYPE_SYSTEMIC, + "ANY", + SPAWN_TYPE_ANY); + /* SPAWN_TYPE + // LEVEL_GEN + // For any spawn happening during level generation, even if the call happened from the Lua API during a tile code callback. + // LEVEL_GEN_TILE_CODE + // Similar to LEVEL_GEN but only triggers on tile code spawns. + // LEVEL_GEN_PROCEDURAL + // Similar to LEVEL_GEN but only triggers on random level spawns, like snakes or bats. + // LEVEL_GEN_FLOOR_SPREADING + // Includes solid floor type spreading (i.e. floorstyled bleeding to existing generic floor) but also corner filling of empty tiles. + // LEVEL_GEN_GENERAL + // Covers all spawns during level gen that are not covered by the other two. + // SCRIPT + // Runs for any spawn happening through a call from the Lua API, also during level generation. + // SYSTEMIC + // Covers all other spawns, such as items from crates or the player throwing bombs. + // ANY + // Covers all of the above. + */ +} +}; // namespace NSpawn diff --git a/src/game_api/script/usertypes/spawn_lua.hpp b/src/game_api/script/usertypes/spawn_lua.hpp new file mode 100644 index 000000000..f6ce5ac35 --- /dev/null +++ b/src/game_api/script/usertypes/spawn_lua.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace sol +{ +class state; +} // namespace sol + +namespace NSpawn +{ +void register_usertypes(sol::state& lua); +}; diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index 405d8cfc3..d32caebd4 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -17,13 +17,16 @@ #include "illumination.hpp" // IWYU pragma: keep #include "items.hpp" // for Items, SelectPlayerSlot, Items::is... #include "level_api.hpp" // IWYU pragma: keep +#include "liquid_engine.hpp" // for LiquidPhysicsEngine #include "online.hpp" // for OnlinePlayer, OnlineLobby, Online +#include "prng.hpp" // IWYU pragma: keep +#include "rpc.hpp" // for waddler_count_entity ... #include "savestate.hpp" // for SaveState #include "screen.hpp" // IWYU pragma: keep #include "screen_arena.hpp" // IWYU pragma: keep #include "script/events.hpp" // for pre_load_state #include "script/lua_backend.hpp" // for LuaBackend -#include "state.hpp" // for StateMemory, State, StateMemory::a... +#include "state.hpp" // for StateMemory, StateMemory::a... #include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems namespace NState @@ -532,7 +535,9 @@ void register_usertypes(sol::state& lua) "local_player", &Online::local_player, "lobby", - &Online::lobby); + &Online::lobby, + "is_active", + &Online::is_active); /// Used in Online lua.new_usertype( "OnlinePlayer", @@ -578,39 +583,29 @@ void register_usertypes(sol::state& lua) lua.create_named_table("CAUSE_OF_DEATH", "DEATH", 0, "ENTITY", 1, "LONG_FALL", 2, "STILL_FALLING", 3, "MISSED", 4, "POISONED", 5); lua["toast_visible"] = []() -> bool - { - return State::get().ptr()->toast != 0; - }; + { return HeapBase::get().state()->toast != 0; }; lua["speechbubble_visible"] = []() -> bool - { - return State::get().ptr()->speechbubble != 0; - }; + { return HeapBase::get().state()->speechbubble != 0; }; lua["cancel_toast"] = []() - { - State::get().ptr()->toast_timer = 1000; - }; + { HeapBase::get().state()->toast_timer = 1000; }; lua["cancel_speechbubble"] = []() - { - State::get().ptr()->speechbubble_timer = 1000; - }; + { HeapBase::get().state()->speechbubble_timer = 1000; }; /// Save current level state to slot 1..4. These save states are invalid and cleared after you exit the current level, but can be used to rollback to an earlier state in the same level. You probably definitely shouldn't use save state functions during an update, and sync them to the same event outside an update (i.e. GUIFRAME, POST_UPDATE). These slots are already allocated by the game, actually used for online rollback, and use no additional memory. Also see SaveState if you need more. lua["save_state"] = [](int slot) { if (slot >= 1 && slot <= 4) - { - copy_save_slot(5, slot); - } + SaveState::backup_main(slot); }; /// Load level state from slot 1..4, if a save_state was made in this level. lua["load_state"] = [](int slot) { if (slot >= 1 && slot <= 4 && get_save_state(slot)) - copy_save_slot(slot, 5); + SaveState::restore_main(slot); }; /// Clear save state from slot 1..4. @@ -628,6 +623,64 @@ void register_usertypes(sol::state& lua) return nullptr; }; - lua.new_usertype("SaveState", sol::constructors(), "load", &SaveState::load, "save", &SaveState::save, "clear", &SaveState::clear, "get_state", &SaveState::get_state); + lua.new_usertype( + "SaveState", + sol::constructors(), + "load", + &SaveState::load, + "save", + &SaveState::save, + "clear", + &SaveState::clear, + "get_state", + &SaveState::get_state, + "get_frame", + &SaveState::get_frame, + "get_prng", + &SaveState::get_prng, + "get", + &SaveState::get); + + /// Get the thread-local version of state + lua["get_local_state"] = []() -> StateMemory* + { return HeapBase::get().state(); }; + /// Get the thread-local version of players + lua["get_local_players"] = []() -> std::vector + { return HeapBase::get().state()->get_players(); }; + /// Warp to a level immediately. + lua["warp"] = [](uint8_t world, uint8_t level, uint8_t theme) + { HeapBase::get().state()->warp(world, level, theme); }; + /// Set seed and reset run. + lua["set_seed"] = [](uint32_t seed) + { HeapBase::get().state()->set_seed(seed); }; + /// Get `state.level_flags` + lua["get_level_flags"] = []() -> uint32_t + { + return HeapBase::get().state()->level_flags; + }; + /// Set `state.level_flags` + lua["set_level_flags"] = [](uint32_t flags) + { + HeapBase::get().state()->level_flags = flags; + }; + /// Returns how many of a specific entity type Waddler has stored + lua["waddler_count_entity"] = waddler_count_entity; + /// Store an entity type in Waddler's storage. Returns the slot number the item was stored in or -1 when storage is full and the item couldn't be stored. + lua["waddler_store_entity"] = waddler_store_entity; + /// Removes an entity type from Waddler's storage. Second param determines how many of the item to remove (default = remove all) + lua["waddler_remove_entity"] = waddler_remove_entity; + /// Gets the 16-bit meta-value associated with the entity type in the associated slot + lua["waddler_get_entity_meta"] = waddler_get_entity_meta; + /// Sets the 16-bit meta-value associated with the entity type in the associated slot + lua["waddler_set_entity_meta"] = waddler_set_entity_meta; + /// Gets the entity type of the item in the provided slot + lua["waddler_entity_type_in_slot"] = waddler_entity_type_in_slot; + /// Run state update manually, i.e. simulate one logic frame. Use in e.g. POST_UPDATE, but be mindful of infinite loops, this will cause another POST_UPDATE. Can even be called thousands of times to simulate minutes of gameplay in a few seconds. + lua["update_state"] = update_state; + /// 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"] = []() + { HeapBase::get().liquid_physics()->remove_liquid_oob()/**/; }; } }; // namespace NState diff --git a/src/game_api/script/usertypes/theme_vtable_lua.cpp b/src/game_api/script/usertypes/theme_vtable_lua.cpp index 2deb9df1e..2bb34f085 100644 --- a/src/game_api/script/usertypes/theme_vtable_lua.cpp +++ b/src/game_api/script/usertypes/theme_vtable_lua.cpp @@ -1,7 +1,6 @@ #include "theme_vtable_lua.hpp" -#include "movable.hpp" // for Movable -#include "state.hpp" // for State +#include "heap_base.hpp" // for HeapBase namespace NThemeVTables { @@ -18,8 +17,8 @@ void register_usertypes(sol::state& lua) HookHandler::set_hook_dtor_impl( [](std::uint32_t theme_id, std::function fun) { - auto state = State::get().ptr_main(); - ThemeInfo* theme = state->level_gen->themes[theme_id - 1]; + auto level_gen = HeapBase::get_main().level_gen(); + ThemeInfo* theme = level_gen->themes[theme_id - 1]; std::uint32_t callback_id = theme_vtable.reserve_callback_id(theme); theme_vtable.set_pre( theme, @@ -31,8 +30,8 @@ void register_usertypes(sol::state& lua) HookHandler::set_unhook_impl( [](std::uint32_t callback_id, std::uint32_t theme_id) { - auto state = State::get().ptr_main(); - ThemeInfo* theme = state->level_gen->themes[theme_id - 1]; + auto level_gen = HeapBase::get_main().level_gen(); + ThemeInfo* theme = level_gen->themes[theme_id - 1]; theme_vtable.unhook(theme, callback_id); }); } diff --git a/src/game_api/script/usertypes/vanilla_render_lua.cpp b/src/game_api/script/usertypes/vanilla_render_lua.cpp index a8c55fe02..8f3335c14 100644 --- a/src/game_api/script/usertypes/vanilla_render_lua.cpp +++ b/src/game_api/script/usertypes/vanilla_render_lua.cpp @@ -9,6 +9,7 @@ #include // for get #include // for move, declval +#include "entity.hpp" // for Entity #include "particles.hpp" // for ParticleEmitterInfo #include "render_api.hpp" // for TextureRenderingInfo, WorldShader, TextRen... #include "script/lua_backend.hpp" // for get_calling_backend @@ -675,8 +676,7 @@ void register_usertypes(sol::state& lua) auto render_draw_depth_lua = [](VanillaRenderContext&, LAYER layer, uint8_t draw_depth, AABB bbox) { - const uint8_t real_layer = enum_to_layer(layer); - auto layer_ptr = State::get().layer(real_layer); + auto layer_ptr = HeapBase::get().state()->layer(layer); render_draw_depth(layer_ptr, draw_depth, bbox.left, bbox.bottom, bbox.right, bbox.top); }; @@ -899,6 +899,79 @@ void register_usertypes(sol::state& lua) // Same as DEFERRED_TEXTURE_COLOR_EMISSIVE_COLORIZED_GLOW but renders texture as solid color */ + /// Some information used to render the entity, can not be changed, used in Entity + lua.new_usertype( + "RenderInfo", + "x", + &RenderInfo::x, + "y", + &RenderInfo::y, + "offset_x", + &RenderInfo::offset_x, + "offset_y", + &RenderInfo::offset_y, + "shader", + &RenderInfo::shader, + "source", + &RenderInfo::source, + "destination", + sol::property( + [](const RenderInfo& ri) -> Quad + { return Quad{ + ri.destination_bottom_left_x, + ri.destination_bottom_left_y, + ri.destination_bottom_right_x, + ri.destination_bottom_right_y, + ri.destination_top_right_x, + ri.destination_top_right_y, + ri.destination_top_left_x, + ri.destination_top_left_y, + }; }), + "tilew", + &RenderInfo::tilew, + "tileh", + &RenderInfo::tileh, + "facing_left", + &RenderInfo::flip_horizontal, + "angle", + &RenderInfo::angle1, + "animation_frame", + &RenderInfo::animation_frame, + "render_inactive", + &RenderInfo::render_inactive, + "brightness", + &RenderInfo::brightness, + "texture_num", + sol::readonly(&RenderInfo::texture_num), + "get_entity", + &RenderInfo::get_entity, + "set_normal_map_texture", + &RenderInfo::set_normal_map_texture, + "get_second_texture", + [](const RenderInfo& ri) -> std::optional + { + if (!ri.texture_names[1] || ri.texture_num < 2) + { + return std::nullopt; + } + return ::get_texture(std::string_view(*ri.texture_names[1])) /**/; + }, + "get_third_texture", + [](const RenderInfo& ri) -> std::optional + { + if (!ri.texture_names[2] || ri.texture_num < 3) + { + return std::nullopt; + } + return ::get_texture(std::string_view(*ri.texture_names[2])) /**/; + }, + "set_second_texture", + &RenderInfo::set_second_texture, + "set_third_texture", + &RenderInfo::set_third_texture, + "set_texture_num", + &RenderInfo::set_texture_num); + auto hudinventory_type = lua.new_usertype("HudInventory"); hudinventory_type["enabled"] = &HudInventory::enabled; hudinventory_type["health"] = &HudInventory::health; diff --git a/src/game_api/script/usertypes/vtables_lua.cpp b/src/game_api/script/usertypes/vtables_lua.cpp index 629900ac3..6826e4f01 100644 --- a/src/game_api/script/usertypes/vtables_lua.cpp +++ b/src/game_api/script/usertypes/vtables_lua.cpp @@ -12,7 +12,6 @@ #include "script/usertypes/theme_vtable_lua.hpp" // for NThemeVTables #include "script/usertypes/vanilla_render_lua.hpp" // for VanillaRenderContext #include "sound_manager.hpp" // for SoundMeta -#include "state.hpp" // for State template using MemFun = MemberFun::BaseLessType; diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index d81fda519..3a848e58c 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -541,17 +541,17 @@ std::unordered_map g_address_rules{ .decode_call() .at_exe(), }, - { - "state_location"sv, - // actually it's state offset, at the time of writing this comment it's 4A0, found ... almost everywhere - PatternCommandBuffer{} - .find_inst("\x49\x0F\x44\xC0"sv) - .find_next_inst("\x49\x0F\x44\xC0"sv) - .offset(-0x19) - .find_inst("\x48\x8B"sv) - .decode_pc() - .at_exe(), - }, + //{ + // "state_location"sv, + // // actually it's state offset, at the time of writing this comment it's 4A0, found ... almost everywhere + // PatternCommandBuffer{} + // .find_inst("\x49\x0F\x44\xC0"sv) + // .find_next_inst("\x49\x0F\x44\xC0"sv) + // .offset(-0x19) + // .find_inst("\x48\x8B"sv) + // .decode_pc() + // .at_exe(), + //}, { "game_manager"sv, PatternCommandBuffer{} @@ -1346,14 +1346,14 @@ std::unordered_map g_address_rules{ .decode_call() .at_exe(), }, - { - "refresh_illumination_heap_offset"sv, - // Put a bp on any Illumination.timer var, watch how it's written, the heap offset ptr is loaded a bit above - PatternCommandBuffer{} - .find_inst("\x48\x8B\x05****\x48\x85\xC0\x75\x16\xB9\x10\x00\x00\x00"sv) - .decode_pc() - .at_exe(), - }, + //{ + // "refresh_illumination_heap_offset"sv, + // // Put a bp on any Illumination.timer var, watch how it's written, the heap offset ptr is loaded a bit above + // PatternCommandBuffer{} + // .find_inst("\x48\x8B\x05****\x48\x85\xC0\x75\x16\xB9\x10\x00\x00\x00"sv) + // .decode_pc() + // .at_exe(), + //}, { "ghost_spawn_time"sv, // 9000 frames / 60 fps = 2.5 minutes = 0x2328 ( 28 23 00 00 ) diff --git a/src/game_api/spawn_api.cpp b/src/game_api/spawn_api.cpp index 6a88ed441..de886b078 100644 --- a/src/game_api/spawn_api.cpp +++ b/src/game_api/spawn_api.cpp @@ -17,19 +17,20 @@ #include "entities_items.hpp" // for ClimbableRope #include "entities_liquids.hpp" // for Lava #include "entities_monsters.hpp" // for Shopkeeper, RoomOwner -#include "entity.hpp" // for to_id, Entity, get_entity_ptr, Enti... -#include "entity_db.hpp" // for EntityFactory +#include "entity.hpp" // for Entity, get_entity_ptr, Enti... +#include "entity_db.hpp" // for EntityFactory, to_id #include "illumination.hpp" // #include "items.hpp" // #include "layer.hpp" // for Layer, g_level_max_y, g_level_max_x #include "level_api.hpp" // for LevelGenSystem, ThemeInfo +#include "liquid_engine.hpp" // for LiquidPhysicsEngine #include "logger.h" // for DEBUG #include "math.hpp" // for AABB #include "memory.hpp" // for write_mem_prot, memory_read #include "prng.hpp" // for PRNG, PRNG::PRNG_CLASS, PRNG::ENTIT... #include "script/events.hpp" // for post_entity_spawn, pre_entity_spawn #include "search.hpp" // for get_address -#include "state.hpp" // for enum_to_layer, State, StateMemory +#include "state.hpp" // for StateMemory #include "state_structs.hpp" // for LiquidTileSpawnData, LiquidPhysics #include "util.hpp" // for OnScopeExit @@ -175,7 +176,7 @@ int32_t spawn_entity_abs(ENT_TYPE entity_type, float x, float y, LAYER layer, fl Vec2 offset_position; uint8_t actual_layer = enum_to_layer(layer, offset_position); - return State::get().layer(actual_layer)->spawn_entity(entity_type, x + offset_position.x, y + offset_position.y, false, vx, vy, false)->uid; + return HeapBase::get().state()->layers[actual_layer]->spawn_entity(entity_type, x + offset_position.x, y + offset_position.y, false, vx, vy, false)->uid; } int32_t spawn_entity_snap_to_floor(ENT_TYPE entity_type, float x, float y, LAYER layer) @@ -187,7 +188,7 @@ int32_t spawn_entity_snap_to_floor(ENT_TYPE entity_type, float x, float y, LAYER Vec2 offset_position; uint8_t actual_layer = enum_to_layer(layer, offset_position); - return State::get().layer(actual_layer)->spawn_entity_snap_to_floor(entity_type, x + offset_position.x, y + offset_position.y)->uid; + return HeapBase::get().state()->layers[actual_layer]->spawn_entity_snap_to_floor(entity_type, x + offset_position.x, y + offset_position.y)->uid; } int32_t spawn_entity_snap_to_grid(ENT_TYPE entity_type, float x, float y, LAYER layer) @@ -199,7 +200,7 @@ int32_t spawn_entity_snap_to_grid(ENT_TYPE entity_type, float x, float y, LAYER Vec2 offset_position; uint8_t actual_layer = enum_to_layer(layer, offset_position); - return State::get().layer(actual_layer)->spawn_entity(entity_type, x + offset_position.x, y + offset_position.y, false, 0.0f, 0.0f, true)->uid; + return HeapBase::get().state()->layers[actual_layer]->spawn_entity(entity_type, x + offset_position.x, y + offset_position.y, false, 0.0f, 0.0f, true)->uid; } int32_t spawn_entity_abs_nonreplaceable(ENT_TYPE entity_type, float x, float y, LAYER layer, float vx, float vy) @@ -216,7 +217,6 @@ int32_t spawn_entity_over(ENT_TYPE entity_type, uint32_t over_uid, float x, floa OnScopeExit pop{[] { pop_spawn_type_flags(SPAWN_TYPE_SCRIPT); }}; - auto& state = State::get(); Entity* overlay = get_entity_ptr(over_uid); if (overlay == nullptr) return -1; @@ -224,7 +224,7 @@ int32_t spawn_entity_over(ENT_TYPE entity_type, uint32_t over_uid, float x, floa if (layer > 1) return -1; - return state.layer(layer)->spawn_entity_over(entity_type, overlay, x, y)->uid; + return HeapBase::get().state()->layers[layer]->spawn_entity_over(entity_type, overlay, x, y)->uid; } int32_t spawn_door_abs(float x, float y, LAYER layer, uint8_t w, uint8_t l, uint8_t t) @@ -236,7 +236,7 @@ int32_t spawn_door_abs(float x, float y, LAYER layer, uint8_t w, uint8_t l, uint Vec2 offset_position; uint8_t actual_layer = enum_to_layer(layer, offset_position); - return State::get().layer(actual_layer)->spawn_door(x + offset_position.x, y + offset_position.y, w, l, t)->uid; + return HeapBase::get().state()->layers[actual_layer]->spawn_door(x + offset_position.x, y + offset_position.y, w, l, t)->uid; } void spawn_backdoor_abs(float x, float y) @@ -245,10 +245,10 @@ void spawn_backdoor_abs(float x, float y) OnScopeExit pop{[] { pop_spawn_type_flags(SPAWN_TYPE_SCRIPT); }}; - auto& state = State::get(); + auto state = HeapBase::get().state(); DEBUG("Spawning backdoor on {}, {}", x, y); - Layer* front_layer = state.layer(0); - Layer* back_layer = state.layer(1); + Layer* front_layer = state->layers[0]; + Layer* back_layer = state->layers[1]; front_layer->spawn_entity(to_id("ENT_TYPE_FLOOR_DOOR_LAYER"), x, y, false, 0.0, 0.0, true); back_layer->spawn_entity(to_id("ENT_TYPE_FLOOR_DOOR_LAYER"), x, y, false, 0.0, 0.0, true); front_layer->spawn_entity(to_id("ENT_TYPE_LOGICAL_PLATFORM_SPAWNER"), x, y - 1.0f, false, 0.0, 0.0, true); @@ -264,7 +264,7 @@ int32_t spawn_apep(float x, float y, LAYER layer, bool right) Vec2 offset_position; uint8_t actual_layer = enum_to_layer(layer, offset_position); - return State::get().layer(actual_layer)->spawn_apep(x + offset_position.x, y + offset_position.y, right)->uid; + return HeapBase::get().state()->layers[actual_layer]->spawn_apep(x + offset_position.x, y + offset_position.y, right)->uid; } int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) @@ -279,7 +279,7 @@ int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) x = std::roundf(x + offset_position.x); y = std::roundf(y + offset_position.y); - Layer* layer_ptr = State::get().layer(actual_layer); + Layer* layer_ptr = HeapBase::get().state()->layers[actual_layer]; // Needs some space on top if (x < 0 || static_cast(x) >= g_level_max_x || y < 1 || static_cast(y) + 2 >= g_level_max_y || height == 1 || @@ -294,7 +294,7 @@ int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) static const auto tree_branch = to_id("ENT_TYPE_FLOOR_TREE_BRANCH"); static const auto tree_deco = to_id("ENT_TYPE_DECORATION_TREE"); - PRNG& prng = PRNG::get_local(); + PRNG* prng = HeapBase::get().prng(); // spawn the base Entity* current_piece = layer_ptr->spawn_entity(tree_base, x, y, false, 0.0f, 0.0f, true); @@ -312,7 +312,7 @@ int32_t spawn_tree(float x, float y, LAYER layer, uint16_t height) break; } current_piece = layer_ptr->spawn_entity_over(tree_trunk, current_piece, 0.0f, 1.0f); - if (height == 0 && prng.random_chance(2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) + if (height == 0 && prng->random_chance(2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) { break; } @@ -326,20 +326,20 @@ 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)) * 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 }; auto test_pos = current_piece->abs_position(); if (static_cast(test_pos.x) + 1 < g_level_max_x && layer_ptr->get_grid_entity_at(test_pos.x + 1, test_pos.y) == nullptr && - prng.random_chance(2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) + prng->random_chance(2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) { Entity* branch = layer_ptr->spawn_entity_over(tree_branch, current_piece, 1.02f, 0.0f); spawn_deco(branch, false); } if (static_cast(test_pos.x) - 1 > 0 && layer_ptr->get_grid_entity_at(test_pos.x - 1, test_pos.y) == nullptr && - prng.random_chance(2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) + prng->random_chance(2, PRNG::PRNG_CLASS::ENTITY_VARIATION)) { Entity* branch = layer_ptr->spawn_entity_over(tree_branch, current_piece, -1.02f, 0.0f); branch->flags |= 1U << 16; // flag 17: facing left @@ -359,7 +359,7 @@ int32_t spawn_mushroom(float x, float y, LAYER l, uint16_t height) // height rel Vec2 offset(0.0f, 0.0f); const auto actual_layer = enum_to_layer(l, offset); - const auto layer_ptr = State::get().layer(actual_layer); + const auto layer_ptr = HeapBase::get().state()->layers[actual_layer]; const uint32_t i_x = static_cast(x + offset.x + 0.5f); uint32_t i_y = static_cast(y + offset.y + 0.5f); static const auto base = to_id("ENT_TYPE_FLOOR_MUSHROOM_BASE"); @@ -384,8 +384,8 @@ 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)); + auto prng = HeapBase::get().prng(); + height = static_cast(prng->random_int(1, 3, PRNG::PRNG_CLASS::PROCEDURAL_SPAWNS)); } i_y += 3; @@ -428,7 +428,7 @@ int32_t spawn_unrolled_player_rope(float x, float y, LAYER layer, TEXTURE textur Vec2 offset(0.0f, 0.0f); const auto actual_layer = enum_to_layer(layer, offset); - const auto layer_ptr = State::get().layer(actual_layer); + const auto layer_ptr = HeapBase::get().state()->layers[actual_layer]; const uint32_t i_x = static_cast(x + offset.x + 0.5f); const uint32_t i_y = static_cast(y + offset.y + 0.5f); const float g_x = static_cast(i_x); @@ -440,7 +440,7 @@ int32_t spawn_unrolled_player_rope(float x, float y, LAYER layer, TEXTURE textur Movable* ent = static_cast(layer_ptr->get_entity_at(gx, gy, 0x180, 0x4, 0x8, 0)); if (ent) { - return ent->type->search_flags == 0x100 || (ent->velocityx == 0.0 && ent->velocityy == 0.0); // see 0x2299c90f + return (ent->type->search_flags & ENTITY_MASK::FLOOR) == ENTITY_MASK::FLOOR || (ent->velocityx == 0.0 && ent->velocityy == 0.0); // see 0x2299c90f } } @@ -465,7 +465,7 @@ int32_t spawn_unrolled_player_rope(float x, float y, LAYER layer, TEXTURE textur constexpr uint16_t anim_frame_middle = 192; constexpr uint16_t anim_frame_bottom = 197; - ClimbableRope* top_part = static_cast(layer_ptr->spawn_entity(rope_ent, g_x, g_y, false, 0, 0, true)); + ClimbableRope* top_part = layer_ptr->spawn_entity(rope_ent, g_x, g_y, false, 0, 0, true)->as(); top_part->set_texture(texture); top_part->animation_frame = anim_frame_single; top_part->idle_counter = 5; @@ -475,19 +475,19 @@ int32_t spawn_unrolled_player_rope(float x, float y, LAYER layer, TEXTURE textur setup_top_rope_rendering_info_two(top_part->rendering_info, 7, 2); ClimbableRope* above_part = top_part; - for (size_t i = 1; i <= max_length; i++) + for (uint32_t i = 1; i <= max_length; ++i) { if (has_solid_ent(g_x, g_y - static_cast(i))) { break; } - ClimbableRope* next_part = static_cast(layer_ptr->spawn_entity_over(rope_ent, above_part, 0, -1)); + ClimbableRope* next_part = layer_ptr->spawn_entity_over(rope_ent, above_part, 0, -1)->as(); next_part->set_texture(texture); next_part->animation_frame = anim_frame_bottom; next_part->idle_counter = 5; - next_part->segment_nr = static_cast(i); - next_part->segment_nr_inverse = max_length - static_cast(i); + next_part->segment_nr = i; + next_part->segment_nr_inverse = max_length - i; next_part->above_part = above_part; setup_top_rope_rendering_info_one(next_part->rendering_info); @@ -612,9 +612,9 @@ SpawnEntityFun* g_spawn_entity_trampoline{nullptr}; Entity* spawn_entity(EntityFactory* entity_factory, std::uint32_t entity_type, float x, float y, bool layer, Entity* overlay, bool some_bool) { // TODO: This still might not work very well and corner fill isn't actually floor spreading per level config definition, and should have a different SPAWN_TYPE (corner fill still happens when floor spreading chance is set to 0) - // const auto theme_floor = State::get().ptr_local()->current_theme->get_floor_spreading_type(); - // const auto theme_floor2 = State::get().ptr_local()->current_theme->get_floor_spreading_type2(); - auto state = State::get().ptr(); + // const auto theme_floor = HeapBase::get().state()->current_theme->get_floor_spreading_type(); + // const auto theme_floor2 = HeapBase::get().state()->current_theme->get_floor_spreading_type2(); + auto state = HeapBase::get().state(); auto [ax, ay, bx, by] = std::make_tuple(2.5f, 122.5f, state->w * 10.0f + 2.5f, 122.5f - state->h * 8.0f); static const auto border_octo = to_id("ENT_TYPE_FLOOR_BORDERTILE_OCTOPUS"); static const auto border_dust = to_id("ENT_TYPE_FLOOR_DUSTWALL"); @@ -683,7 +683,7 @@ int32_t spawn_player(int8_t player_slot, std::optional x, std::optional 4) return -1; - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); auto& slot = state->items->player_select_slots[player_slot - 1]; if (slot.character < to_id("ENT_TYPE_CHAR_ANA_SPELUNKY") || slot.character > to_id("ENT_TYPE_CHAR_CLASSIC_GUY")) return -1; @@ -703,10 +703,10 @@ int32_t spawn_player(int8_t player_slot, std::optional x, std::optionallayers[0], State::get().ptr()->layers[1]); + std::swap(state->layers[0], state->layers[1]); spawn_player(get_state_ptr()->items, player_slot - 1); if (layer.has_value() && layer.value() == LAYER::BACK) - std::swap(State::get().ptr()->layers[0], State::get().ptr()->layers[1]); + std::swap(state->layers[0], state->layers[1]); state->level_gen->spawn_x = old_x; state->level_gen->spawn_y = old_y; auto player = state->items->player(player_slot - 1); @@ -740,10 +740,10 @@ int32_t spawn_companion(ENT_TYPE companion_type, float x, float y, LAYER layer) int32_t spawn_shopkeeper(float x, float y, LAYER layer, ROOM_TEMPLATE room_template) { const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - StateMemory* state_ptr = State::get().ptr(); - auto [ix, iy] = state_ptr->level_gen->get_room_index(x, y); + auto level_gen = HeapBase::get().level_gen(); + auto [ix, iy] = level_gen->get_room_index(x, y); uint32_t room_index = ix + iy * 8; - state_ptr->level_gen->set_room_template(ix, iy, real_layer, room_template); + level_gen->set_room_template(ix, iy, real_layer, room_template); uint32_t keeper_uid = spawn_entity_abs_nonreplaceable(to_id("ENT_TYPE_MONS_SHOPKEEPER"), x, y, layer, 0, 0); auto keeper = get_entity_ptr(keeper_uid)->as(); keeper->shop_owner = true; @@ -761,11 +761,11 @@ int32_t spawn_roomowner(ENT_TYPE owner_type, float x, float y, LAYER layer, int1 static const auto tun_id = to_id("ENT_TYPE_MONS_MERCHANT"); const uint8_t real_layer = static_cast(layer) < 0 ? 0 : static_cast(layer); - StateMemory* state_ptr = State::get().ptr(); - auto [ix, iy] = state_ptr->level_gen->get_room_index(x, y); + auto level_gen = HeapBase::get().level_gen(); + auto [ix, iy] = level_gen->get_room_index(x, y); uint32_t room_index = ix + iy * 8; if (room_template >= 0) - state_ptr->level_gen->set_room_template(ix, iy, real_layer, (uint16_t)room_template); + level_gen->set_room_template(ix, iy, real_layer, (uint16_t)room_template); uint32_t keeper_uid = spawn_entity_abs_nonreplaceable(owner_type, x, y, layer, 0, 0); if (owner_type == waddler_id || owner_type == yang_id || owner_type == tun_id) { @@ -789,10 +789,6 @@ int32_t spawn_playerghost(ENT_TYPE char_type, float x, float y, LAYER layer) OnScopeExit pop{[] { pop_spawn_type_flags(SPAWN_TYPE_SCRIPT); }}; - Vec2 offset; - const auto l = enum_to_layer(layer, offset); - auto level_layer = State::get().layer(l); - static const auto player_ghost = to_id("ENT_TYPE_ITEM_PLAYERGHOST"); static const auto ana = to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); static const auto egg_child = to_id("ENT_TYPE_CHAR_EGGPLANT_CHILD"); @@ -802,6 +798,9 @@ int32_t spawn_playerghost(ENT_TYPE char_type, float x, float y, LAYER layer) if (char_type < ana || char_type > egg_child) return -1; + Vec2 offset; + const auto l = enum_to_layer(layer, offset); + auto level_layer = HeapBase::get().state()->layers[l]; auto player_ghost_entity = level_layer->spawn_entity(player_ghost, x + offset.x, y + offset.y, false, 0, 0, false)->as(); if (player_ghost_entity) { @@ -816,5 +815,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)); + timer = static_cast(HeapBase::get().prng()->random_int(2700, 27000, PRNG::PRNG_CLASS::PROCEDURAL_SPAWNS)); } diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 40572b3b7..432e2d0af 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -14,67 +14,76 @@ #include "entities_chars.hpp" // for Player #include "entity.hpp" // for to_id, Entity, HookWithId, EntityDB #include "entity_hooks_info.hpp" // for Player -#include "game_api.hpp" // +#include "game_api.hpp" // for GameAPI #include "game_manager.hpp" // for get_game_manager, GameManager, SaveR... #include "game_patches.hpp" // #include "items.hpp" // for Items, SelectPlayerSlot #include "level_api.hpp" // for LevelGenSystem, LevelGenSystem::(ano... +#include "liquid_engine.hpp" // for LiquidPhysicsEngine #include "logger.h" // for DEBUG #include "memory.hpp" // for write_mem_prot, memory_read #include "movable.hpp" // for Movable #include "movable_behavior.hpp" // for init_behavior_hooks #include "render_api.hpp" // for init_render_api_hooks +#include "rpc.hpp" // for lowbias32 #include "savedata.hpp" // for SaveData #include "screen.hpp" // for Screen #include "script/events.hpp" // for pre_entity_instagib #include "script/lua_vm.hpp" // for get_lua_vm #include "script/usertypes/theme_vtable_lua.hpp" // for NThemeVTables #include "search.hpp" // for get_address -#include "sound_manager.hpp" // +#include "sound_manager.hpp" // for SoundManager #include "spawn_api.hpp" // for init_spawn_hooks #include "steam_api.hpp" // for init_achievement_hooks #include "strings.hpp" // for strings_init -#include "thread_utils.hpp" // for OnHeapPointer #include "virtual_table.hpp" // for get_virtual_function_address, VTABLE... #include "vtable_hook.hpp" // for hook_vtable -static int64_t global_frame_count{0}; -static int64_t global_update_count{0}; +static uint64_t global_frame_count{0}; +static uint64_t global_update_count{0}; static bool g_forward_blocked_events{false}; -bool get_forward_events() +bool API::get_forward_events() { return g_forward_blocked_events; } +uint64_t API::get_global_frame_count() +{ + return global_frame_count; +}; +uint64_t API::get_global_update_count() +{ + return global_update_count; +}; StateMemory* get_state_ptr() { - return State::get().ptr(); + return HeapBase::get().state(); } -void fix_liquid_out_of_bounds() +void LiquidPhysics::remove_liquid_oob() { - auto state = State::get().ptr(); - if (!state || !state->liquid_physics) + auto state = HeapBase::get().state(); + if (!state->liquid_physics) return; for (const auto& it : state->liquid_physics->pools) { - if (it.physics_engine && !it.physics_engine->pause_physics) + if (it.physics_engine == nullptr || it.physics_engine->pause_physics) + continue; + + for (uint32_t i = 0; i < it.physics_engine->entity_count; ++i) { - for (uint32_t i = 0; i < it.physics_engine->entity_count; ++i) + auto liquid_coordinates = it.physics_engine->entity_coordinates + i; + if (liquid_coordinates->y < 0 // y < 0 + || liquid_coordinates->x < 0 // x < 0 + || liquid_coordinates->x > g_level_max_x // x > g_level_max_x + || liquid_coordinates->y > g_level_max_y + 16) // y > g_level_max_y { - auto liquid_coordinates = it.physics_engine->entity_coordinates + i; - if (liquid_coordinates->y < 0 // y < 0 - || liquid_coordinates->x < 0 // x < 0 - || liquid_coordinates->x > g_level_max_x // x > g_level_max_x - || liquid_coordinates->y > g_level_max_y + 16) // y > g_level_max_y - { - if (!*(it.physics_engine->unknown61 + i)) // just some bs - continue; - - const auto ent = **(it.physics_engine->unknown61 + i); - ent->kill(true, nullptr); - } + if (!*(it.physics_engine->unknown61 + i)) // just some bs + continue; + + const auto ent = **(it.physics_engine->unknown61 + i); + ent->kill(true, nullptr); } } } @@ -91,7 +100,7 @@ inline bool& get_do_hooks() static bool do_hooks{true}; return do_hooks; } -void State::set_do_hooks(bool do_hooks) +void API::set_do_hooks(bool do_hooks) { if (get_is_init()) { @@ -112,7 +121,7 @@ bool& get_write_load_opt() static bool allowed{true}; return allowed; } -void State::set_write_load_opt(bool write_load_opt) +void API::set_write_load_opt(bool write_load_opt) { if (get_is_init()) { @@ -134,12 +143,22 @@ void State::set_write_load_opt(bool write_load_opt) static bool g_godmode_player_active = false; static bool g_godmode_companions_active = false; -bool is_active_player(Entity* e) +void API::godmode(bool g) +{ + g_godmode_player_active = g; +} + +void API::godmode_companions(bool g) { - auto state = State::get().ptr(); + g_godmode_companions_active = g; +} + +static bool is_active_player(Entity* e) +{ + auto items = HeapBase::get().state()->items; for (uint8_t i = 0; i < MAX_PLAYERS; i++) { - auto player = state->items->player(i); + auto player = items->players[i]; if (player && player == e) { return true; @@ -156,7 +175,7 @@ bool on_damage(Entity* victim, Entity* damage_dealer, int8_t damage_amount, uint { return false; } - if (g_godmode_companions_active && !is_active_player(victim) && (victim->type->search_flags & 1) == 1) + if (g_godmode_companions_active && !is_active_player(victim) && (victim->type->search_flags & ENTITY_MASK::PLAYER) == ENTITY_MASK::PLAYER) { return false; } @@ -172,7 +191,7 @@ void on_instagib(Entity* victim, bool destroy_corpse, size_t param_3) { return; } - if (g_godmode_companions_active && !is_active_player(victim) && (victim->type->search_flags & 1) == 1) + if (g_godmode_companions_active && !is_active_player(victim) && (victim->type->search_flags & ENTITY_MASK::PLAYER) == ENTITY_MASK::PLAYER) { return; } @@ -213,16 +232,6 @@ void hook_godmode_functions() } } -void State::godmode(bool g) -{ - g_godmode_player_active = g; -} - -void State::godmode_companions(bool g) -{ - g_godmode_companions_active = g; -} - struct ThemeHookImpl { template @@ -258,118 +267,33 @@ struct ThemeHookImpl } }; -void State::init(class SoundManager* sound_manager) -{ - State::get(); - if (sound_manager) - get_lua_vm(sound_manager); -} -void State::post_init() -{ - if (get_is_init()) - { - StateMemory& state{*State::get().ptr_main()}; - state.level_gen->hook_themes(ThemeHookImpl{}); - } -} - -State& State::get() -{ - static State STATE{0x4A0}; - if (!get_is_init()) - { - if (get_write_load_opt()) - { - do_write_load_opt(); - } - if (auto addr_location = get_address("state_location"); addr_location != 0) - STATE.location = addr_location; - - get_is_init() = true; - - if (get_do_hooks()) - { - STATE.ptr_main()->level_gen->init(); - init_spawn_hooks(); - init_behavior_hooks(); - init_render_api_hooks(); - init_achievement_hooks(); - hook_godmode_functions(); - strings_init(); - init_state_update_hook(); - init_process_input_hook(); - init_game_loop_hook(); - init_state_clone_hook(); - - auto bucket = Bucket::get(); - bucket->count++; - if (!bucket->patches_applied) - { - DEBUG("Applying patches"); - patch_tiamat_kill_crash(); - patch_orbs_limit(); - patch_olmec_kill_crash(); - patch_liquid_OOB(); - patch_ushabti_error(); - patch_entering_closed_door_crash(); - bucket->patches_applied = true; - bucket->forward_blocked_events = true; - } - else - { - DEBUG("Not applying patches, someone has already done it"); - if (bucket->forward_blocked_events) - g_forward_blocked_events = true; - } - } - } - return STATE; -} - -StateMemory* State::ptr_main() const -{ - OnHeapPointer p(memory_read(location)); - return p.decode(); -} - -StateMemory* State::ptr() const -{ - return ptr_local(); -} - -StateMemory* State::ptr_local() const -{ - OnHeapPointer p(memory_read(location)); - return p.decode_local(); -} - -float get_zoom_level() +static float get_zoom_level() { auto game_api = GameAPI::get(); return game_api->get_current_zoom(); } -Vec2 State::click_position(float x, float y) +Vec2 API::click_position(float x, float y) { float cz = get_zoom_level(); - auto [cx, cy] = get_camera_position(); + auto [cx, cy] = Camera::get_position(); float rx = cx + ZF * cz * x; float ry = cy + (ZF / 16.0f * 9.0f) * cz * y; return {rx, ry}; } -Vec2 State::screen_position(float x, float y) +Vec2 API::screen_position(float x, float y) { float cz = get_zoom_level(); - auto [cx, cy] = get_camera_position(); + auto [cx, cy] = Camera::get_position(); float rx = (x - cx) / cz / ZF; float ry = (y - cy) / cz / (ZF / 16.0f * 9.0f); return {rx, ry}; } -void State::zoom(float level) const +void API::zoom(float level) { - auto roomx = ptr()->w; + auto roomx = HeapBase::get().state()->w; if (level == 0.0) { switch (roomx) @@ -419,7 +343,7 @@ void State::zoom(float level) const game_api->set_zoom(std::nullopt, level); } -void State::zoom_reset() +void API::zoom_reset() { recover_mem("zoom"); auto game_api = GameAPI::get(); @@ -430,82 +354,79 @@ void StateMemory::force_current_theme(THEME t) { if (t > 0 && t < 19) { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); if (t == 10 && !state->level_gen->theme_cosmicocean->sub_theme) state->level_gen->theme_cosmicocean->sub_theme = state->level_gen->theme_dwelling; // just set it to something, can't edit this atm state->current_theme = state->level_gen->themes[t - 1]; } } -void State::darkmode(bool g) -{ - static const size_t addr_dark = get_address("force_dark_level"); - - if (g) - { - write_mem_recoverable("darkmode", addr_dark, "\x90\x90"sv, true); - } - else - { - recover_mem("darkmode"); - } -} - -Vec2 State::get_camera_position() +Vec2 Camera::get_position() { + // = adjusted_focus_x/y - (adjusted_focus_x/y - calculated_focus_x/y) * (render frame-game frame difference) static const auto addr = (float*)get_address("camera_position"); auto cx = *addr; auto cy = *(addr + 1); return {cx, cy}; } -void State::set_camera_position(float cx, float cy) +void Camera::set_position(float cx, float cy) { - static const auto addr = (float*)get_address("camera_position"); - auto* camera = ptr()->camera; - camera->focus_x = cx; - camera->focus_y = cy; - camera->adjusted_focus_x = cx; - camera->adjusted_focus_y = cy; - camera->calculated_focus_x = cx; - camera->calculated_focus_y = cy; + static const auto addr = (float*)get_address("camera_position"); // probably not needed + focus_x = cx; + focus_y = cy; + adjusted_focus_x = cx; + adjusted_focus_y = cy; + calculated_focus_x = cx; + calculated_focus_y = cy; *addr = cx; *(addr + 1) = cy; } -void State::warp(uint8_t w, uint8_t l, uint8_t t) +void Camera::update_position() +{ + static const size_t offset = get_address("update_camera_position"); + typedef void update_camera_func(Camera*); + static update_camera_func* ucf = (update_camera_func*)(offset); + ucf(this); + calculated_focus_x = adjusted_focus_x; + calculated_focus_y = adjusted_focus_y; +} + +void StateMemory::warp(uint8_t set_world, uint8_t set_level, uint8_t set_theme) { - // if (ptr()->screen < 11 || ptr()->screen > 20) + // if (screen < 11 || screen > 20) // return; - if (ptr()->items->player_count < 1) + auto gm = get_game_manager(); + if (items->player_count < 1) { - ptr()->items->player_select_slots[0].activated = true; - ptr()->items->player_select_slots[0].character = savedata()->players[0] + to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); - ptr()->items->player_select_slots[0].texture_id = savedata()->players[0] + 285; // TODO: magic numbers - ptr()->items->player_count = 1; + auto savedata = gm->save_related->savedata.decode_local(); + items->player_select_slots[0].activated = true; + items->player_select_slots[0].character = savedata->players[0] + to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); + items->player_select_slots[0].texture_id = savedata->players[0] + 285; // TODO: magic numbers + items->player_count = 1; } - ptr()->world_next = w; - ptr()->level_next = l; - ptr()->theme_next = t; - if (ptr()->world_start < 1 || ptr()->level_start < 1 || ptr()->theme_start < 1 || ptr()->theme == 17) + world_next = set_world; + level_next = set_level; + theme_next = set_theme; + if (world_start < 1 || level_start < 1 || theme_start < 1 || theme == 17) { - ptr()->world_start = w; - ptr()->level_start = l; - ptr()->theme_start = t; - ptr()->quest_flags = 1; + world_start = set_world; + level_start = set_level; + theme_start = set_theme; + quest_flags = 1; } - if (t != 17) + if (set_theme != 17) { - ptr()->screen_next = 12; + screen_next = 12; } else { - ptr()->screen_next = 11; + screen_next = 11; } - ptr()->win_state = 0; - ptr()->loading = 1; + win_state = 0; + loading = 1; - static auto gm = get_game_manager(); if (gm->main_menu_music) { gm->main_menu_music->kill(false); @@ -513,46 +434,23 @@ void State::warp(uint8_t w, uint8_t l, uint8_t t) } } -void State::set_seed(uint32_t seed) +void StateMemory::set_seed(uint32_t set_seed) { - if (ptr()->screen < 11 || ptr()->screen > 20) + if (screen < 11 || screen > 20) return; - ptr()->seed = seed; - ptr()->world_start = 1; - ptr()->level_start = 1; - ptr()->theme_start = 1; - ptr()->world_next = 1; - ptr()->level_next = 1; - ptr()->theme_next = 1; - ptr()->quest_flags = 0x1e | 0x41; - ptr()->screen_next = 12; - ptr()->loading = 1; + seed = set_seed; + world_start = 1; + level_start = 1; + theme_start = 1; + world_next = 1; + level_next = 1; + theme_next = 1; + quest_flags = 0x1e | 0x41; + screen_next = 12; + loading = 1; } -SaveData* State::savedata() -{ - auto gm = get_game_manager(); - return gm->save_related->savedata.decode(); // wondering if it matters if it's local or not? -} -uint32_t lowbias32(uint32_t x) -{ - x ^= x >> 16; - x *= 0x7feb352d; - x ^= x >> 15; - x *= 0x846ca68b; - x ^= x >> 16; - return x; -} -uint32_t lowbias32_r(uint32_t x) -{ - x ^= x >> 16; - x *= 0x43021123U; - x ^= x >> 15 ^ x >> 30; - x *= 0x1d69e2a5U; - x ^= x >> 16; - return x; -} -Entity* State::find(StateMemory* state, uint32_t uid) +Entity* StateMemory::get_entity(uint32_t uid) const { // Ported from MauveAlert's python code in the CAT tracker @@ -562,12 +460,12 @@ Entity* State::find(StateMemory* state, uint32_t uid) return nullptr; } - const uint32_t mask = state->uid_to_entity_mask; + const uint32_t mask = uid_to_entity_mask; const uint32_t target_uid_plus_one = lowbias32(uid + 1); uint32_t cur_index = target_uid_plus_one & mask; while (true) { - auto entry = state->uid_to_entity_data[cur_index]; + auto entry = uid_to_entity_data[cur_index]; if (entry.uid_plus_one == target_uid_plus_one) { return entry.entity; @@ -587,9 +485,8 @@ Entity* State::find(StateMemory* state, uint32_t uid) } } -LiquidPhysicsEngine* State::get_correct_liquid_engine(ENT_TYPE liquid_type) const +LiquidPhysicsEngine* LiquidPhysics::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); static const ENT_TYPE LIQUID_COARSE_WATER = to_id("ENT_TYPE_LIQUID_COARSE_WATER"sv); static const ENT_TYPE LIQUID_LAVA = to_id("ENT_TYPE_LIQUID_LAVA"sv); @@ -597,56 +494,34 @@ LiquidPhysicsEngine* State::get_correct_liquid_engine(ENT_TYPE liquid_type) cons static const ENT_TYPE LIQUID_COARSE_LAVA = to_id("ENT_TYPE_LIQUID_COARSE_LAVA"sv); if (liquid_type == LIQUID_WATER) { - return state->liquid_physics->water_physics_engine; + return water_physics_engine; } else if (liquid_type == LIQUID_COARSE_WATER) { - return state->liquid_physics->coarse_water_physics_engine; + return coarse_water_physics_engine; } else if (liquid_type == LIQUID_LAVA) { - return state->liquid_physics->lava_physics_engine; + return lava_physics_engine; } else if (liquid_type == LIQUID_STAGNANT_LAVA) { - return state->liquid_physics->stagnant_lava_physics_engine; + return stagnant_lava_physics_engine; } else if (liquid_type == LIQUID_COARSE_LAVA) { - return state->liquid_physics->coarse_lava_physics_engine; + return coarse_lava_physics_engine; } return nullptr; } -uint32_t State::get_frame_count_main() const -{ - return memory_read((size_t)ptr_main() - 0xd0); -} -uint32_t State::get_frame_count() const -{ - return memory_read((size_t)ptr() - 0xd0); -} -uint32_t State::get_frame_count(StateMemory* state) -{ - return memory_read((size_t)state - 0xd0); -} -int64_t get_global_frame_count() -{ - return global_frame_count; -}; -int64_t get_global_update_count() -{ - return global_update_count; -}; - -std::vector State::read_prng() const +void update_state() { - std::vector prng; - for (int i = 0; i < 20; ++i) - { - prng.push_back(memory_read((size_t)ptr() - 0xb0 + 8 * static_cast(i))); - } - return prng; + static const size_t offset = get_address("state_refresh"); + auto state = HeapBase::get().state(); + typedef void refresh_func(StateMemory*); + static refresh_func* rf = (refresh_func*)(offset); + rf(state); } using OnStateUpdate = void(StateMemory*); @@ -697,42 +572,6 @@ void init_state_update_hook() } } -void HeapClone(uint64_t heap_to, uint64_t heap_container_from) -{ - uint64_t location = State::get().get_offset(); - StateMemory* state_from = reinterpret_cast(memory_read(heap_container_from + 0x88) + location); - StateMemory* state_to = reinterpret_cast(heap_to + location); - pre_copy_state_event(state_from, state_to); -} - -// Original function params: clone_heap(ThreadStorageContainer to, ThreadStorageContainer from) -// HeapContainer has heap1 and heap2 variables, and some sort of timer, that just increases constantly, I guess to handle the rollback and multi-threaded stuff -// The rest of what HeapContainer has is unknown for now -// After writing to a chosen storage from the content of `from->heap1`, sets `to->heap2` to the newly copied thread storage -void init_state_clone_hook() -{ - auto heap_clone = get_address("heap_clone"); - // Hook the function after it has chosen a thread storage to write to, and pass it to the hook - size_t heap_clone_redirect_from_addr = heap_clone + 0x65; - const std::string redirect_code = fmt::format( - "\x51" // PUSH RCX - "\x52" // PUSH RDX - "\x41\x50" // PUSH R8 - "\x41\x51" // PUSH R9 - "\x48\x83\xEC\x28" // SUB RSP, 28 // Shadow space + Stack alignment - "\x4C\x89\xC9" // MOV RCX, R9 == heap_to - "\x48\xb8{}" // MOV RAX, &HeapClone - "\xff\xd0" // CALL RAX - "\x48\x83\xC4\x28" // ADD RSP, 28 - "\x41\x59" // POP R9 - "\x41\x58" // POP R8 - "\x5A" // POP RDX - "\x59"sv, // POP RCX - to_le_bytes(&HeapClone)); - - patch_and_redirect(heap_clone_redirect_from_addr, 7, redirect_code, false, 0, false); -} - using OnProcessInput = void(void*); OnProcessInput* g_process_input_trampoline{nullptr}; void ProcessInput(void* s) @@ -792,10 +631,10 @@ void GameLoop(void* a, float b, void* c) { static const auto bucket = Bucket::get(); static const auto pa = bucket->pause_api; - auto& state = State::get(); + auto frame_main = HeapBase::get_main().frame_count(); - if (global_frame_count < state.get_frame_count_main()) - global_frame_count = state.get_frame_count_main(); + if (global_frame_count < frame_main) + global_frame_count = frame_main; else global_frame_count++; @@ -860,7 +699,7 @@ uint8_t enum_to_layer(const LAYER layer, Vec2& player_position) return 0; else if (layer < LAYER::FRONT) { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); auto player = state->items->player(static_cast(std::abs((int)layer) - 1)); if (player != nullptr) { @@ -881,7 +720,7 @@ uint8_t enum_to_layer(const LAYER layer) return 0; else if (layer < LAYER::FRONT) { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); auto player = state->items->player(static_cast(std::abs((int)layer) - 1)); if (player != nullptr) { @@ -1128,13 +967,73 @@ void LogicMagmamanSpawn::remove_spawn(uint32_t x, uint32_t y) { return (m_pos.x == x && m_pos.y == y); }); } -void update_camera_position() +void API::init(SoundManager* sound_manager) { - auto camera = State::get().ptr()->camera; - static const size_t offset = get_address("update_camera_position"); - typedef void update_camera_func(Camera*); - static update_camera_func* ucf = (update_camera_func*)(offset); - ucf(camera); - camera->calculated_focus_x = camera->adjusted_focus_x; - camera->calculated_focus_y = camera->adjusted_focus_y; + if (!get_is_init()) + { + get_is_init() = true; + if (get_write_load_opt()) + { + do_write_load_opt(); + } + + if (get_do_hooks()) + { + HeapBase::get_main().level_gen()->init(); + init_spawn_hooks(); + init_behavior_hooks(); + init_render_api_hooks(); + init_achievement_hooks(); + hook_godmode_functions(); + strings_init(); + init_state_update_hook(); + init_process_input_hook(); + init_game_loop_hook(); + init_heap_clone_hook(); + + auto bucket = Bucket::get(); + bucket->count++; + if (!bucket->patches_applied) + { + bucket->patches_applied = true; + bucket->forward_blocked_events = true; + DEBUG("Applying patches"); + patch_tiamat_kill_crash(); + patch_orbs_limit(); + patch_olmec_kill_crash(); + patch_liquid_OOB(); + patch_ushabti_error(); + patch_entering_closed_door_crash(); + } + else + { + DEBUG("Not applying patches, someone has already done it"); + if (bucket->forward_blocked_events) + g_forward_blocked_events = true; + } + } + } + + if (sound_manager) + get_lua_vm(sound_manager); +} +void API::post_init() +{ + if (get_is_init()) + { + HeapBase::get().level_gen()->hook_themes(ThemeHookImpl{}); + } +} + +std::vector StateMemory::get_players() +{ + std::vector found; + found.reserve(4); + for (uint8_t i = 0; i < MAX_PLAYERS; i++) + { + auto player = items->players[i]; + if (player) + found.push_back(player); + } + return found; } diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index 93f8c155d..706988975 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -29,6 +29,7 @@ class ScreenScores; class ScreenTeamSelect; class ScreenTransition; class ScreenWin; +class SoundManager; struct ParticleEmitterInfo; const float ZF = 0.737f; @@ -46,8 +47,7 @@ struct LevelGenSystem; class ThemeInfo; struct Items; struct Illumination; - -void fix_liquid_out_of_bounds(); +class Player; #pragma pack(push, 1) // disable struct padding struct StateMemory @@ -204,7 +204,7 @@ struct StateMemory uint32_t time_level; uint32_t time_speedrun; uint32_t money_last_levels; - int32_t level_flags; + uint32_t level_flags; PRESENCE_FLAG presence_flags; /// the contents of the special coffin that will be spawned during levelgen ENT_TYPE coffin_contents; @@ -327,104 +327,45 @@ struct StateMemory { correct_ushabti = static_cast(animation_frame - (animation_frame / 12) * 2); } + // safe + Layer* layer(LAYER l) + { + return layers[enum_to_layer(l)]; + }; + // safe + Layer* layer(uint8_t l) + { + return l == 1 ? layers[1] : layers[0]; + } + void warp(uint8_t set_world, uint8_t set_level, uint8_t set_theme); + Entity* get_entity(uint32_t uid) const; + void set_seed(uint32_t set_seed); + std::vector get_players(); }; #pragma pack(pop) StateMemory* get_state_ptr(); +void update_state(); -struct State +namespace API { - static void set_do_hooks(bool do_hooks); - - static void set_write_load_opt(bool allow); - - static void init(class SoundManager* sound_manager = nullptr); - static void post_init(); - - static State& get(); - - // Returns the main-thread version of StateMemory* - StateMemory* ptr_main() const; - // Returns the local-thread version of StateMemory* - StateMemory* ptr() const; - StateMemory* ptr_local() const; +void init(SoundManager* sound_manager = nullptr); +void post_init(); +void set_do_hooks(bool do_hooks); +void set_write_load_opt(bool allow); - // TODO: rest of the functions should probably be just static or moved out of here as they don't need State - // they have to assume to use main/local ptr in which case they probably should be moved to StateMemory to be more clear - // also because we really only use this struct to get to the StateMemory, make ptr functions static and simply make them call the get() - - // use only if you only want the layer, otherwise use `ptr()->layers` - Layer* layer(uint8_t index) const - { - return ptr()->layers[index]; - } - - void godmode(bool g); - void godmode_companions(bool g); - static void darkmode(bool g); - - void zoom(float level) const; - static void zoom_reset(); - - static Vec2 click_position(float x, float y); - static Vec2 screen_position(float x, float y); - - uint32_t flags() const - { - return ptr()->level_flags; - } +uint64_t get_global_frame_count(); +uint64_t get_global_update_count(); - void set_flags(uint32_t f) - { - ptr()->level_flags = f; - } - - void set_pause(uint8_t p) - { - ptr()->pause = p; - } - - uint32_t get_frame_count_main() const; - uint32_t get_frame_count() const; - static uint32_t get_frame_count(StateMemory* state); - - std::vector read_prng() const; - - static Entity* find(StateMemory* state, uint32_t uid); - - static Vec2 get_camera_position(); - void set_camera_position(float cx, float cy); - 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) const; - // Get the 0x4A0 offset - size_t get_offset() const - { - return memory_read(location); - } - - private: - State(size_t addr) - : location(addr){}; - - size_t location; - State(const State&) = delete; - State& operator=(const State&) = delete; -}; -void init_state_update_hook(); -void init_process_input_hook(); -void init_game_loop_hook(); -void init_state_clone_hook(); - -uint8_t enum_to_layer(const LAYER layer, Vec2& player_position); -uint8_t enum_to_layer(const LAYER layer); +bool get_forward_events(); -uint32_t lowbias32(uint32_t x); -uint32_t lowbias32_r(uint32_t x); +void godmode(bool g); +void godmode_companions(bool g); -int64_t get_global_frame_count(); -int64_t get_global_update_count(); -void update_camera_position(); +void zoom(float level); +void zoom_reset(); -bool get_forward_events(); +// maybe would fit better in render_api ? +Vec2 click_position(float x, float y); +Vec2 screen_position(float x, float y); +} // namespace API diff --git a/src/game_api/state_structs.hpp b/src/game_api/state_structs.hpp index ccb035d9b..8bcdb8c2d 100644 --- a/src/game_api/state_structs.hpp +++ b/src/game_api/state_structs.hpp @@ -140,7 +140,7 @@ struct Camera float bounds_right; float bounds_bottom; float bounds_top; - float adjusted_focus_x; // focus adjusted so camera doesn't show beyond borders, can be updated manually by setting inertia to 5 and calling update_camera_position() + float adjusted_focus_x; // focus adjusted so camera doesn't show beyond borders, can be updated manually by setting inertia to 5 and calling update_camera_position(), updated per frame float adjusted_focus_y; float calculated_focus_x; // forced values float calculated_focus_y; @@ -185,6 +185,9 @@ struct Camera bounds_bottom = bounds.bottom; bounds_top = bounds.top; } + static Vec2 get_position(); + void set_position(float cx, float cy); + void update_position(); }; struct JournalProgressStickerSlot @@ -728,207 +731,6 @@ struct LogicList }; }; -struct LiquidPhysicsEngine -{ - bool pause_physics; - uint8_t padding[3]; - int32_t physics_tick_timer; /* unsure */ - int32_t unknown1; - int32_t unknown2; - int8_t unknown3; - int8_t unknown4; - int8_t unknown5; - int8_t unknown6; - int8_t unknown_7; - int8_t unknown8; - int8_t unknown9; - int8_t unknown10; - uint32_t unknown11; - float unknown12; - float blob_size; - float weight; - float unknown15; - uint32_t entity_count; - uint32_t allocated_size; - uint32_t unk23; // padding probably - std::list unk1; // seams to be empty, or have one element 0? - uint32_t resize_value; // used to resize the arrays? - uint32_t unk3b; // padding probably - - // this is actually a pre C++11 version of std::list, which is different from current one! - std::pair liquid_ids; // std::list - // this is actually a pre C++11 version of std::list, which is different from current one! - std::pair unknown44; // std::list all of them are -1 - - std::list::const_iterator* list_liquid_ids; // list of all iterators of liquid_ids? - int32_t unknown45a; // size related for the array above - int32_t unknown45b; // padding - uint32_t* liquid_flags; // array - int32_t unknown47a; // size related for the array above - int32_t unknown47b; // padding - Vec2* entity_coordinates; // array - int32_t unknown49a; // size related for the array above - int32_t unknown49b; // padding - Vec2* entity_velocities; // array - int32_t unknown51a; // size related for the array above - int32_t unknown51b; // padding - std::pair* unknown52; // not sure about the type, it's definitely a 64bit - std::pair* unknown53; - size_t unknown54; - std::pair* unknown55; - int64_t unknown56; - int64_t unknown57; - int64_t unknown58; - int64_t unknown59; - size_t unknown60; - Entity*** unknown61; // it's actually array of pointers to some struct, but the entity is first in that struct - size_t unknown61a; // stuff for array above - char skip[256]; - float unknown95; // LiquidParam->unknown3 - float cohesion; // LiquidParam->cohesion?, surface tension? setting it to -1 makes the blobs repel each other - float gravity; // LiquidParam->gravity - float unknown96; // LiquidParam->unknown6 - float unknown97a; // LiquidParam->unknown7 - float agitation; // LiquidParam->agitation - float unknown98a; // LiquidParam->unknown9 - float unknown98b; // LiquidParam->unknown10 - float unknown99a; // LiquidParam->unknown11 - float unknown99b; // LiquidParam->unknown12 - float unknown100a; // LiquidParam->unknown13 - float unknown100b; // LiquidParam->unknown14 - float unknown101a; // LiquidParam->unknown15 - float unknown101b; // LiquidParam->unknown16 - float unknown102a; // LiquidParam->unknown17 - float unknown102b; // LiquidParam->unknown18 - float unknown103a; // LiquidParam->unknown19 - int32_t unknown103b; // LiquidParam->unknown20 - float unknown104a; // LiquidParam->unknown21 - int32_t unknown104b; // LiquidParam->unknown22 - float unknown105a; // LiquidParam->unknown23 - int32_t unknown105b; // LiquidParam->unknown24 - size_t unknown106; - size_t unknown107; - int64_t unknown108; - int64_t unknown109; -}; - -struct LiquidPhysicsParams -{ - int32_t shader_type; // ? can also be flags, as for water, any value with bit one is fine - uint8_t unknown2; // shader related, shader id maybe? - uint8_t padding1; - uint8_t padding2; - uint8_t padding3; - float unknown3; - float cohesion; // negative number makes the liquid balls come apart more easily? - float gravity; // negative number to invert gravity - float unknown6; - float unknown7; - float agitation; // is agitation the right word? for me is just how bouncy the liquid is - float unknown9; // starts going nuts at around 2.70, pressure force? it seam to only matter at spawn, when there is a lot of liquid in one place - float unknown10; - float unknown11; - float unknown12; - float unknown13; - float unknown14; - float unknown15; - float unknown16; - float unknown17; - float unknown18; - float unknown19; - uint32_t unknown20; - float unknown21; - uint32_t unknown22; - float unknown23; - uint32_t unknown24; -}; - -struct LiquidTileSpawnData -{ - uint32_t liquid_flags; // 2 - lava_interaction? crashes the game if no lava is present, 3 - pause_physics, 6 - low_agitation?, 7 - high_agitation?, 8 - high_surface_tension?, 9 - low_surface_tension?, 11 - high_bounce?, 12 - low_bounce? - float last_spawn_x; - float last_spawn_y; - float spawn_velocity_x; - float spawn_velocity_y; - uint32_t unknown31; - uint32_t unknown32; - uint32_t unknown33; - size_t unknown34; // MysteryLiquidPointer2 in plugin, contains last spawn entity - size_t unknown35; // DataPointer? seam to get access validation if you change to something - uint32_t liquidtile_liquid_amount; // how much liquid will be spawned from tilecode, 1=1x2, 2=2x3, 3=3x4 etc. - float blobs_separation; - int32_t unknown39; // is the last 4 garbage? seams not accessed - float unknown40; - float unknown41; - uint32_t unknown42; -}; - -struct LiquidPool -{ - LiquidPhysicsParams physics_defaults; - LiquidPhysicsEngine* physics_engine; - LiquidTileSpawnData tile_spawn_data; -}; - -struct LiquidLake -{ - uint32_t position1; - uint32_t position2; - uint32_t position3; - uint32_t lake_type; - 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 - union - { - std::array pools; - struct - { - LiquidPhysicsParams water_physics_defaults; - LiquidPhysicsEngine* water_physics_engine; - LiquidTileSpawnData water_tile_spawn_data; - LiquidPhysicsParams coarse_water_physics_defaults; - LiquidPhysicsEngine* coarse_water_physics_engine; - LiquidTileSpawnData coarse_water_tile_spawn_data; - LiquidPhysicsParams lava_physics_defaults; - LiquidPhysicsEngine* lava_physics_engine; - LiquidTileSpawnData lava_tile_spawn_data; - LiquidPhysicsParams coarse_lava_physics_defaults; - LiquidPhysicsEngine* coarse_lava_physics_engine; - LiquidTileSpawnData coarse_lava_tile_spawn_data; - LiquidPhysicsParams stagnant_lava_physics_defaults; - LiquidPhysicsEngine* stagnant_lava_physics_engine; - 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 - - LiquidAmounts (*liquids_by_third_of_tile)[g_level_max_y * 3][g_level_max_x * 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; // 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; - uint32_t unknown13; -}; - struct AITarget { uint32_t ai_uid; diff --git a/src/game_api/thread_utils.cpp b/src/game_api/thread_utils.cpp deleted file mode 100644 index ab955826f..000000000 --- a/src/game_api/thread_utils.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "thread_utils.hpp" - -#include // for CreateToolhelp32Snapshot, THREADENTRY32, Thr... -#include // for HANDLE, GetCurrentProcessId, GetCurrentThread -#include // for KPRIORITY, NTSTATUS, CLIENT_ID, THREADINFOCLASS -#include // for ULONG - -#include "logger.h" // for DEBUG -#include "memory.hpp" // for memory_read - -HANDLE get_main_thread() -{ - static const auto main_thread = [] - { - HANDLE main_thread_handle = NULL; - - DWORD pid = GetCurrentProcessId(); - auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - auto entry = THREADENTRY32{ - sizeof(THREADENTRY32), - }; - auto keep = Thread32First(snapshot, &entry); - while (keep) - { - if (entry.th32OwnerProcessID == pid) - { - main_thread_handle = OpenThread(THREAD_ALL_ACCESS, 0, entry.th32ThreadID); - break; - } - keep = Thread32Next(snapshot, &entry); - } - - if (main_thread_handle == NULL) - { - DEBUG("Didn't not get the thread. Process id: {}", pid); - } - - return main_thread_handle; - }(); - return main_thread; -} - -typedef struct _THREAD_BASIC_INFORMATION -{ - NTSTATUS ExitStatus; - PVOID TebBaseAddress; - CLIENT_ID ClientId; - KAFFINITY AffinityMask; - KPRIORITY Priority; - KPRIORITY BasePriority; -} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; -size_t* get_thread_heap_base(HANDLE thread) -{ - THREAD_BASIC_INFORMATION tib{}; - using FuncPtr = NTSTATUS(NTAPI*)( - IN HANDLE ThreadHandle, - IN THREADINFOCLASS ThreadInformationClass, - OUT PVOID ThreadInformation, - IN ULONG ThreadInformationLength, - OUT PULONG ReturnLength OPTIONAL); - static const auto NtQueryInformationThread_ptr = reinterpret_cast(GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationThread")); - NtQueryInformationThread_ptr(thread, (_THREADINFOCLASS)0, (&tib), sizeof(THREAD_BASIC_INFORMATION), nullptr); - return (size_t*)(memory_read(((uint64_t*)tib.TebBaseAddress)[11]) + 0x120); -} - -size_t heap_base() -{ - static const auto main_thread = get_main_thread(); - static const size_t* this_thread_heap_base_addr = get_thread_heap_base(main_thread); - return *this_thread_heap_base_addr; -} - -size_t local_heap_base() -{ - thread_local const size_t* this_thread_heap_base_addr = get_thread_heap_base(GetCurrentThread()); - if (this_thread_heap_base_addr == nullptr) - return NULL; - - return *this_thread_heap_base_addr; -} diff --git a/src/game_api/thread_utils.hpp b/src/game_api/thread_utils.hpp deleted file mode 100644 index 73853283b..000000000 --- a/src/game_api/thread_utils.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include // for ResumeThread, SuspendThread, HANDLE -#include // for size_t -#include // for int64_t - -HANDLE get_main_thread(); -size_t heap_base(); -size_t local_heap_base(); - -// Used for objects that are allocated with the game's custom allocator -template -class OnHeapPointer -{ - int64_t ptr_; - - public: - explicit OnHeapPointer(size_t ptr) - : ptr_(ptr) - { - } - - T* decode() const - { - return reinterpret_cast(ptr_ + heap_base()); - } - - T* decode_local() const - { - auto lhb = local_heap_base(); - if (lhb == 0) - return nullptr; - - return reinterpret_cast(ptr_ + lhb); - } - - T* operator->() const - { - return decode(); - } -}; - -struct CriticalSection -{ - HANDLE thread; - - CriticalSection() - { - thread = get_main_thread(); - SuspendThread(thread); - } - - ~CriticalSection() - { - ResumeThread(thread); - } -}; diff --git a/src/game_api/window_api.cpp b/src/game_api/window_api.cpp index e0a827d6e..56fe06d9f 100644 --- a/src/game_api/window_api.cpp +++ b/src/game_api/window_api.cpp @@ -12,8 +12,8 @@ #include "bucket.hpp" #include "logger.h" -#include "memory.hpp" #include "script/lua_backend.hpp" +#include "search.hpp" #include "state.hpp" bool detect_wine() @@ -132,7 +132,7 @@ LRESULT CALLBACK hkWndProc(HWND window, UINT message, WPARAM wParam, LPARAM lPar /*if (bucket->io->WantCaptureKeyboard.value_or(false) && (message == WM_KEYDOWN || message == WM_KEYUP)) consumed_input = true;*/ - if (get_forward_events() && bucket->io->WantCaptureMouse.value_or(false) && message >= WM_LBUTTONDOWN && message <= WM_MOUSEWHEEL) + if (API::get_forward_events() && bucket->io->WantCaptureMouse.value_or(false) && message >= WM_LBUTTONDOWN && message <= WM_MOUSEWHEEL) consumed_input = true; if (!consumed_input) @@ -267,7 +267,7 @@ HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterva if (bucket->count > 1) { - if (!get_forward_events()) + if (!API::get_forward_events()) { bucket->io->WantCaptureMouse = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && g.HoveredWindow && strcmp(g.HoveredWindow->Name, "Clickhandler"); bucket->io->WantCaptureKeyboard = ImGui::GetIO().WantCaptureKeyboard; @@ -338,7 +338,7 @@ HRESULT STDMETHODCALLTYPE hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterva g_PostDrawCallback(); } - if (get_forward_events() || bucket->count == 1) + if (API::get_forward_events() || bucket->count == 1) { bucket->io->WantCaptureKeyboard = std::nullopt; bucket->io->WantCaptureMouse = std::nullopt; diff --git a/src/game_api/window_api.hpp b/src/game_api/window_api.hpp index 249f2e8b7..36572c047 100644 --- a/src/game_api/window_api.hpp +++ b/src/game_api/window_api.hpp @@ -1,8 +1,7 @@ #pragma once #include - -#include +#include // for UINT, WPARAM, LPARAM bool detect_wine(); diff --git a/src/info_dump/main.cpp b/src/info_dump/main.cpp index 3dbcf7e40..5ddcb1b78 100644 --- a/src/info_dump/main.cpp +++ b/src/info_dump/main.cpp @@ -37,7 +37,6 @@ #include "search.hpp" // for get_address #include "settings_api.hpp" // for get_settings_names_and_... #include "sound_manager.hpp" // for SoundManager, SoundMana... -#include "state.hpp" // for StateMemory, State #include "texture.hpp" // for Texture, get_textures #include "virtual_table.hpp" // for VTABLE_OFFSET, VTABLE_O... @@ -1219,7 +1218,7 @@ void run() EntityDB* db = get_type(ent.id); if (!db) break; - if ((db->search_flags & search_flag) != 0) + if ((std::uint32_t)db->search_flags & search_flag) { entities.push_back(ent.name); } @@ -1271,7 +1270,7 @@ void run() if (auto file = std::ofstream("game_data/particle_emitters.txt")) { - auto particles = list_particles(); + auto& particles = list_particles(); for (const auto& particle : particles) { file << particle.id << ": " << particle.name << "\n"; @@ -1285,11 +1284,11 @@ void run() // file << "---@diagnostic disable: lowercase-global,deprecated" << std::endl; } - auto state = State::get().ptr_main(); + auto level_gen = HeapBase::get_main().level_gen(); if (auto file = std::ofstream("game_data/tile_codes.txt")) { - for (const auto& tile_code : state->level_gen->data->tile_codes) + for (const auto& tile_code : level_gen->data->tile_codes) { std::string clean_tile_code_name = tile_code.first.c_str(); std::transform( @@ -1303,7 +1302,7 @@ void run() if (auto file = std::ofstream("game_data/spawn_chances.txt")) { std::multimap ordered_chances; - for (auto* chances : {&state->level_gen->data->monster_chances, &state->level_gen->data->trap_chances}) + for (auto* chances : {&level_gen->data->monster_chances, &level_gen->data->trap_chances}) { for (const auto& spawn_chanc : *chances) { @@ -1321,7 +1320,7 @@ void run() if (auto file = std::ofstream("game_data/room_templates.txt")) { - auto templates = state->level_gen->data->room_templates; + auto templates = level_gen->data->room_templates; templates["empty_backlayer"] = {9}; templates["boss_arena"] = {22}; templates["shop_jail_backlayer"] = {44}; diff --git a/src/injected/main.cpp b/src/injected/main.cpp index 569a15f4e..6bc55e473 100644 --- a/src/injected/main.cpp +++ b/src/injected/main.cpp @@ -134,7 +134,7 @@ void run() } auto& api = RenderAPI::get(); - init_ui(); + register_imgui_pre_init(&init_ui); init_hooks((void*)api.swap_chain()); } diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index 58ad5c6f1..37a3c880c 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -45,6 +45,7 @@ #include "illumination.hpp" #include "items.hpp" #include "level_api.hpp" +#include "liquid_engine.hpp" #include "logger.h" #include "math.hpp" #include "savedata.hpp" @@ -54,6 +55,7 @@ #include "socket.hpp" #include "sound_manager.hpp" // TODO: remove from here? #include "state.hpp" +#include "state_structs.hpp" #include "steam_api.hpp" #include "version.hpp" #include "window_api.hpp" @@ -436,9 +438,9 @@ void render_version_warning() if (version_check_messages[(int)version_check_status.state].fade > 0) { if (version_check_status.start == 0) - version_check_status.start = get_global_frame_count(); + version_check_status.start = API::get_global_frame_count(); - auto duration = get_global_frame_count() - version_check_status.start; + auto duration = API::get_global_frame_count() - version_check_status.start; version_check_status.opacity = 1.0f - duration / version_check_messages[(int)version_check_status.state].fade; if (version_check_status.opacity <= 0.0f) { @@ -1323,7 +1325,7 @@ void smart_delete(Entity* ent, bool unsafe = false) static auto logical_door = to_id("ENT_TYPE_LOGICAL_DOOR"); if (!ent->is_player()) ent->flags = set_flag(ent->flags, 1); - if ((ent->type->search_flags & 0x80) == 0) + if (!(ent->type->search_flags & ENTITY_MASK::ACTIVEFLOOR)) { for (auto item : ent->items.entities()) { @@ -1338,7 +1340,7 @@ void smart_delete(Entity* ent, bool unsafe = false) auto layer = (LAYER)ent->layer; UI::cleanup_at(pos.x, pos.y, layer, ent->type->id); } - if (ent->type->search_flags & 0x180) + if (!!(ent->type->search_flags & (ENTITY_MASK::ACTIVEFLOOR | ENTITY_MASK::FLOOR))) { auto pos = ent->abs_position(); auto layer = (LAYER)ent->layer; @@ -1495,7 +1497,7 @@ int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) } uint32_t i_x = static_cast(floor->x + 0.5f); uint32_t i_y = static_cast(floor->y + 0.5f); - State::get().layer(floor->layer)->grid_entities[i_y][i_x] = floor; + HeapBase::get().state()->layer(floor->layer)->grid_entities[i_y][i_x] = floor; fix_decorations_at(floor->x, floor->y, (LAYER)floor->layer); } } @@ -1648,7 +1650,7 @@ void spawn_entity_over() auto who = overlay->as(); who->give_powerup(item.id); } - else if (item.name.find("ENT_TYPE_ITEM") != std::string::npos && overlay->type->search_flags & 0x100) + else if (item.name.find("ENT_TYPE_ITEM") != std::string::npos && (overlay->type->search_flags & ENTITY_MASK::FLOOR) == ENTITY_MASK::FLOOR) { int spawned = UI::spawn_entity_over(item.id, g_over_id, g_dx, g_dy); auto ent = get_entity_ptr(spawned); @@ -2100,7 +2102,8 @@ struct VoidEntity void clear_void() { - for (auto uid : UI::get_entities_by({}, 1422, LAYER::FRONT)) + constexpr auto clear_mask = ENTITY_MASK::ITEM | ENTITY_MASK::MOUNT | ENTITY_MASK::MONSTER | ENTITY_MASK::ACTIVEFLOOR | ENTITY_MASK::BG | ENTITY_MASK::FLOOR; + for (auto uid : UI::get_entities_by({}, clear_mask, LAYER::FRONT)) { auto ent = get_entity_ptr(uid); auto [x, y] = ent->abs_position(); @@ -2133,7 +2136,7 @@ void load_void(std::string data) { auto uid = UI::spawn_grid(e.id, (float)e.x, (float)e.y, 0); auto ent = get_entity_ptr(uid); - if (ent->type->search_flags & 0x100) + if ((ent->type->search_flags & ENTITY_MASK::FLOOR) == ENTITY_MASK::FLOOR) { fix_decorations_at((float)e.x, (float)e.y, LAYER::FRONT); Callback cb = {g_state->time_total + 2, [e] @@ -2142,7 +2145,7 @@ void load_void(std::string data) }}; callbacks.push_back(cb); } - else if (ent->type->search_flags & 0x8) + else if ((ent->type->search_flags & ENTITY_MASK::ITEM) == ENTITY_MASK::ITEM) { ent->y = e.y - 0.5f + ent->hitboxy - ent->offsety; } @@ -2189,7 +2192,7 @@ void import_void() std::string serialize_void() { - const int export_mask = 398; + constexpr auto export_mask = ENTITY_MASK::MOUNT | ENTITY_MASK::MONSTER | ENTITY_MASK::ITEM | ENTITY_MASK::ACTIVEFLOOR | ENTITY_MASK::FLOOR; auto [px, py] = g_players[0]->abs_position(); std::string v = fmt::format("V1{:02X}{:02X}", (uint8_t)(px + 0.5f), (uint8_t)(py + 0.5f)); auto uids = g_selected_ids; @@ -2360,7 +2363,7 @@ void warp_next_level(size_t num) targets.emplace_back(target_world, target_level, target_theme); } - for (auto doorid : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::EXITDOOR}, 0x100, LAYER::BOTH)) + for (auto doorid : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::EXITDOOR}, ENTITY_MASK::FLOOR, LAYER::BOTH)) { ExitDoor* doorent = get_entity_ptr(doorid)->as(); if (!doorent->special_door) @@ -2821,7 +2824,7 @@ void load_state(int slot) g_state->camera->focus_offset_y = 0; set_camera_bounds(true); } - UI::copy_state(slot, 5); + UI::load_state_as_main(slot); } void clear_script_messages() @@ -3556,19 +3559,19 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) } else if (pressed("save_state_1", wParam)) { - UI::copy_state(5, 1); + UI::save_main_state(1); } else if (pressed("save_state_2", wParam)) { - UI::copy_state(5, 2); + UI::save_main_state(2); } else if (pressed("save_state_3", wParam)) { - UI::copy_state(5, 3); + UI::save_main_state(3); } else if (pressed("save_state_4", wParam)) { - UI::copy_state(5, 4); + UI::save_main_state(4); } else if (pressed("load_state_1", wParam)) { @@ -3958,7 +3961,7 @@ void render_narnia() n++; } - for (auto doorid : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::EXITDOOR}, 0x100, LAYER::BOTH)) + for (auto doorid : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::EXITDOOR}, ENTITY_MASK::FLOOR, LAYER::BOTH)) { ExitDoor* target = get_entity_ptr(doorid)->as(); if (!target->special_door) @@ -4219,15 +4222,15 @@ void render_camera() tooltip("Focus the selected entity"); } - auto [cx, cy] = State::get_camera_position(); + auto [cx, cy] = Camera::get_position(); ImGui::PushItemWidth(120.0f); ImGui::InputFloat("##CameraPosX", &cx, 0.1f, 1.0f); if (ImGui::IsItemEdited()) - State::get().set_camera_position(cx, cy); + HeapBase::get().state()->camera->set_position(cx, cy); ImGui::SameLine(0, 4.0f); ImGui::InputFloat("Position##CameraPosY", &cy, 0.1f, 1.0f); if (ImGui::IsItemEdited()) - State::get().set_camera_position(cx, cy); + HeapBase::get().state()->camera->set_position(cx, cy); ImGui::InputFloat("##CameraFocusX", &g_state->camera->focus_x, 0.1f, 1.0f); ImGui::SameLine(0, 4.0f); @@ -4720,7 +4723,7 @@ std::string entity_tooltip(Entity* hovered) auto bomb = hovered->as(); coords += fmt::format(" ({} FUSE)", 150 - bomb->idle_counter); } - else if (hovered->type->search_flags & 7) + else if (!!(hovered->type->search_flags & (ENTITY_MASK::MOUNT | ENTITY_MASK::PLAYER | ENTITY_MASK::MONSTER))) { auto ent = hovered->as(); coords += fmt::format(" ({} HP)", ent->health); @@ -4729,7 +4732,7 @@ std::string entity_tooltip(Entity* hovered) { coords += fmt::format("\nON: {}, {} ({:.2f}, {:.2f})", hovered->overlay->uid, entity_names[hovered->overlay->type->id], hovered->overlay->abs_x == -FLT_MAX ? hovered->overlay->x : hovered->overlay->abs_x, hovered->overlay->abs_y == -FLT_MAX ? hovered->overlay->y : hovered->overlay->abs_y); } - if (hovered->type->search_flags & 15 && hovered->as()->last_owner_uid > -1) + if (!!(hovered->type->search_flags & (ENTITY_MASK::MOUNT | ENTITY_MASK::PLAYER | ENTITY_MASK::MONSTER | ENTITY_MASK::ITEM)) && hovered->as()->last_owner_uid > -1) { auto ent = hovered->as(); auto owner = get_entity_ptr(ent->last_owner_uid); @@ -4772,7 +4775,7 @@ void render_hitbox(Entity* ent, bool cross, ImColor color, bool filled = false, if (ent_spark->size >= 1.0) color = ImColor(255, 0, 0, 150); } - else if (ent->type->search_flags == 0x10) // Explosion + else if ((ent->type->search_flags & ENTITY_MASK::EXPLOSION) == ENTITY_MASK::EXPLOSION) { color = ImColor(255, 0, 0, 150); } @@ -5088,7 +5091,7 @@ void render_clickhandler() if (options["draw_hitboxes"] && g_state->screen != 5) { static const auto olmec = to_id("ENT_TYPE_ACTIVEFLOOR_OLMEC"); - for (auto entity : UI::get_entities_by({}, g_hitbox_mask, (LAYER)g_state->camera_layer)) + for (auto entity : UI::get_entities_by({}, (ENTITY_MASK)g_hitbox_mask, (LAYER)g_state->camera_layer)) { auto ent = get_entity_ptr(entity); if (!ent) @@ -5100,10 +5103,10 @@ void render_clickhandler() continue; } - if (!UI::has_active_render(ent) && (ent->type->search_flags & 0x7000) == 0) + if (!UI::has_active_render(ent) && !(ent->type->search_flags & (ENTITY_MASK::LOGICAL | ENTITY_MASK::LIQUID))) continue; - if ((ent->type->search_flags & 1) == 0 || ent->as()->ai) + if (!(ent->type->search_flags & ENTITY_MASK::PLAYER) || ent->as()->ai) render_hitbox(ent, false, ImColor(0, 255, 255, 150)); } if ((g_hitbox_mask & 0x1) != 0) @@ -5142,17 +5145,17 @@ void render_clickhandler() to_id("ENT_TYPE_FLOOR_SHOPKEEPER_GENERATOR"), to_id("ENT_TYPE_FLOOR_SUNCHALLENGE_GENERATOR"), }; - for (auto entity : UI::get_entities_by(additional_fixed_entities, 0x180, (LAYER)g_state->camera_layer)) // FLOOR | ACTIVEFLOOR + for (auto entity : UI::get_entities_by(additional_fixed_entities, ENTITY_MASK::FLOOR | ENTITY_MASK::ACTIVEFLOOR, (LAYER)g_state->camera_layer)) { auto ent = get_entity_ptr(entity); render_hitbox(ent, false, ImColor(0, 255, 255, 150)); } - for (auto entity : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::TRIGGER}, 0x1000, (LAYER)g_state->camera_layer)) // LOGICAL + for (auto entity : UI::get_entities_by({(ENT_TYPE)CUSTOM_TYPE::TRIGGER}, ENTITY_MASK::LOGICAL, (LAYER)g_state->camera_layer)) { auto ent = get_entity_ptr(entity); render_hitbox(ent, false, ImColor(255, 0, 0, 150)); } - for (auto entity : UI::get_entities_by({to_id("ENT_TYPE_LOGICAL_DOOR")}, 0x1000, (LAYER)g_state->camera_layer)) // DOOR + for (auto entity : UI::get_entities_by({to_id("ENT_TYPE_LOGICAL_DOOR")}, ENTITY_MASK::LOGICAL, (LAYER)g_state->camera_layer)) // DOOR { auto ent = get_entity_ptr(entity); render_hitbox(ent, false, ImColor(255, 180, 45, 150), false, true); @@ -5646,7 +5649,7 @@ void render_clickhandler() g_state->camera->focus_x -= (current_pos.first - oryginal_pos.first) * g_camera_speed; g_state->camera->focus_y -= (current_pos.second - oryginal_pos.second) * g_camera_speed; if (g_state->pause != 0 || g_bucket->pause_api->paused() || !options["smooth_camera"]) - State::get().set_camera_position(g_state->camera->focus_x, g_state->camera->focus_y); + HeapBase::get().state()->camera->set_position(g_state->camera->focus_x, g_state->camera->focus_y); startpos = normalize(mouse_pos()); enable_camera_bounds = false; set_camera_bounds(enable_camera_bounds); @@ -7267,7 +7270,7 @@ void render_entity_finder() static bool extra_filter = false; if (ImGui::Button("Search##SearchEntities") || run_finder) { - g_selected_ids = UI::get_entities_by({search_entity_type}, search_entity_mask, (LAYER)search_entity_layer); + g_selected_ids = UI::get_entities_by({search_entity_type}, (ENTITY_MASK)search_entity_mask, (LAYER)search_entity_layer); run_filter = true; run_finder = false; } @@ -7299,7 +7302,7 @@ void render_entity_finder() auto ent = get_entity_ptr(filter_uid); if (!ent) return true; - return (ent->type->search_flags & search_entity_mask) == 0; }), + return ((int)ent->type->search_flags & search_entity_mask) == 0; }), g_selected_ids.end()); } { @@ -7543,7 +7546,7 @@ void render_entity_props(int uid, bool detached = false) auto overlay = entity->overlay; if (overlay) { - if (overlay->type->search_flags & 0x2) // MOUNT + if ((overlay->type->search_flags & ENTITY_MASK::MOUNT) == ENTITY_MASK::MOUNT) { ImGui::Text("Riding:"); ImGui::SameLine(); @@ -7560,7 +7563,7 @@ void render_entity_props(int uid, bool detached = false) ImGui::SameLine(); if (ImGui::Button("Detach")) { - if (entity->type->search_flags & 0x1) // PLAYER + if ((entity->type->search_flags & ENTITY_MASK::PLAYER) == ENTITY_MASK::PLAYER) entity->as()->let_go(); else entity->detach(true); @@ -7699,7 +7702,7 @@ void render_entity_props(int uid, bool detached = false) auto movable = entity->as(); ImGui::DragScalar("Health##EntityHealth", ImGuiDataType_U8, (char*)&movable->health, 0.5f, &u8_one, &u8_max); ImGui::DragScalar("Price##Price", ImGuiDataType_S32, (char*)&movable->price, 0.5f, &s32_min, &s32_max); - if ((entity->type->search_flags & 0x1) && movable->inventory_ptr != 0) + if ((entity->type->search_flags & ENTITY_MASK::PLAYER) == ENTITY_MASK::PLAYER && movable->inventory_ptr != 0) { ImGui::DragScalar("Bombs##EntityBombs", ImGuiDataType_U8, (char*)&movable->inventory_ptr->bombs, 0.5f, &u8_one, &u8_max); ImGui::DragScalar("Ropes##EntityRopes", ImGuiDataType_U8, (char*)&movable->inventory_ptr->ropes, 0.5f, &u8_one, &u8_max); @@ -7714,13 +7717,25 @@ void render_entity_props(int uid, bool detached = false) static bool fx = false; ImGui::Checkbox("Show annoying FX items", &fx); ImGui::SeparatorText("Items"); - if (entity->type->search_flags & 0x7) + if (!(entity->type->search_flags & (ENTITY_MASK::PLAYER | ENTITY_MASK::MOUNT | ENTITY_MASK::MONSTER))) + { + int removed_uid = -1; + for (auto ent : entity->items.entities()) + { + if (fx || !(ent->type->search_flags & ENTITY_MASK::FX)) + if (render_uid(ent->uid, "EntityItems", true)) + removed_uid = ent->uid; + } + if (auto removed = get_entity_ptr(removed_uid)) + entity->remove_item(removed, true); + } + else { auto entity_pow = entity->as(); int removed_uid = -1; for (auto ent : entity->items.entities()) { - if ((fx || (ent->type->search_flags & 0x40) == 0) && !entity_pow->has_powerup(ent->type->id)) + if ((fx || !(ent->type->search_flags & ENTITY_MASK::FX)) && !entity_pow->has_powerup(ent->type->id)) if (render_uid(ent->uid, "EntityItems", true)) removed_uid = ent->uid; } @@ -7796,18 +7811,6 @@ void render_entity_props(int uid, bool detached = false) } ImGui::PopItemWidth(); } - else - { - int removed_uid = -1; - for (auto ent : entity->items.entities()) - { - if ((fx || (ent->type->search_flags & 0x40) == 0)) - if (render_uid(ent->uid, "EntityItems", true)) - removed_uid = ent->uid; - } - if (auto removed = get_entity_ptr(removed_uid)) - entity->remove_item(removed, true); - } endmenu(); } if (submenu("Global attributes") && entity->type) @@ -7823,7 +7826,7 @@ void render_entity_props(int uid, bool detached = false) ImGui::DragFloat("Jump power##GlobalJumpPower", &entity->type->jump, 0.01f, 0.0f, 10.0f, "%.5f"); ImGui::InputScalar("Mask:##SearchFlags", ImGuiDataType_U32, &entity->type->search_flags, 0, 0, "%08X", ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); - ImGui::TextUnformatted(mask_names[std::countr_zero(entity->type->search_flags)]); + ImGui::TextUnformatted(mask_names[std::countr_zero((uint32_t)entity->type->search_flags)]); if (submenu("Properties flags")) { render_flags(entity_type_properties_flags, &entity->type->properties_flags); @@ -7903,10 +7906,10 @@ void render_entity_props(int uid, bool detached = false) ImGui::Checkbox("Door spawned##LogicalDoorSpawned", &door->not_hidden); ImGui::Checkbox("Platform spawned##LogicalDoorPlatformSpawned", &door->platform_spawned); } - else if (entity->type->search_flags & 0x7) // PLYAER, MOUNT, MONSTER + else if (!!(entity->type->search_flags & (ENTITY_MASK::PLAYER | ENTITY_MASK::MOUNT | ENTITY_MASK::MONSTER))) { auto entity_player = entity->as(); - if ((entity->type->search_flags & 0x1) && entity_player->ai != 0) + if ((entity->type->search_flags & ENTITY_MASK::PLAYER) == ENTITY_MASK::PLAYER && entity_player->ai != 0) { ImGui::InputScalar("AI state##AiState", ImGuiDataType_S8, &entity_player->ai->state, &u8_min, &s8_max); ImGui::InputScalar("Trust##AiTrust", ImGuiDataType_S8, &entity_player->ai->trust, &u8_min, &s8_max); @@ -7991,7 +7994,7 @@ void render_entity_props(int uid, bool detached = false) } endmenu(); } - if ((entity->type->search_flags & 0x1) && submenu("Illumination")) + if ((entity->type->search_flags & ENTITY_MASK::PLAYER) == ENTITY_MASK::PLAYER && submenu("Illumination")) { auto entity_player = entity->as(); if (entity_player->emitted_light) @@ -8520,7 +8523,7 @@ void render_game_props() for (int i = 1; i <= 4; ++i) { if (ImGui::Button(fmt::format(" {} ##SaveState{}", i, i).c_str())) - UI::copy_state(5, i); + UI::save_main_state(i); tooltip("Save current level state", fmt::format("save_state_{}", i).c_str()); ImGui::SameLine(); } @@ -8785,7 +8788,7 @@ void render_game_props() else if (player->input_ptr->player_slot >= 0) active_players[player->input_ptr->player_slot] = true; } - for (auto uid : UI::get_entities_by({to_id("ENT_TYPE_ITEM_PLAYERGHOST")}, 0x8, LAYER::BOTH)) + for (auto uid : UI::get_entities_by({to_id("ENT_TYPE_ITEM_PLAYERGHOST")}, ENTITY_MASK::ITEM, LAYER::BOTH)) { auto ghost = get_entity_ptr(uid)->as(); if (ghost->player_inputs && ghost->player_inputs->player_slot == i && g_state->items->player_count < i + 1) @@ -8947,7 +8950,7 @@ void render_game_props() } auto ai_entity = get_entity_ptr(ai_target.ai_uid); auto target = ai_target.target_uid; - if (ai_entity == nullptr || (ai_entity->type->search_flags & 1) != 1) + if (ai_entity == nullptr || (ai_entity->type->search_flags & ENTITY_MASK::PLAYER) != ENTITY_MASK::PLAYER) { continue; } @@ -9369,7 +9372,7 @@ void render_prohud() auto topmargin = 0.0f; if (options["menu_ui"] && !hide_ui) topmargin = ImGui::GetTextLineHeight(); - std::string buf = fmt::format("TIMER:{}/{} GLOBAL:{:#06} FRAME:{:#06} START:{:#06} TOTAL:{:#06} LEVEL:{:#06} COUNT:{} SCREEN:{} SIZE:{}x{} PAUSE:{} FPS:{:.2f} ENGINE:{:.2f} TARGET:{:.2f}", format_time(g_state->time_level), format_time(g_state->time_total), get_global_frame_count(), UI::get_frame_count(), g_state->time_startup, g_state->time_total, g_state->time_level, g_state->level_count, g_state->screen, g_state->w, g_state->h, g_state->pause, io.Framerate, engine_fps, g_engine_fps); + std::string buf = fmt::format("TIMER:{}/{} GLOBAL:{:#06} FRAME:{:#06} START:{:#06} TOTAL:{:#06} LEVEL:{:#06} COUNT:{} SCREEN:{} SIZE:{}x{} PAUSE:{} FPS:{:.2f} ENGINE:{:.2f} TARGET:{:.2f}", format_time(g_state->time_level), format_time(g_state->time_total), API::get_global_frame_count(), UI::get_frame_count(), g_state->time_startup, g_state->time_total, g_state->time_level, g_state->level_count, g_state->screen, g_state->w, g_state->h, g_state->pause, io.Framerate, engine_fps, g_engine_fps); ImVec2 textsize = ImGui::CalcTextSize(buf.c_str()); dl->AddText({base->Pos.x + base->Size.x / 2 - textsize.x / 2, base->Pos.y + 2 + topmargin}, ImColor(1.0f, 1.0f, 1.0f, .5f), buf.c_str()); @@ -10011,14 +10014,14 @@ std::string make_save_path(std::string_view script_path, std::string_view script return save_path; } -void init_ui() +void init_ui(ImGuiContext* ctx) { g_SoundManager = std::make_unique(&LoadAudioFile); - State::init(g_SoundManager.get()); - State::post_init(); + API::init(g_SoundManager.get()); + API::post_init(); - g_state = State::get().ptr_main(); + g_state = HeapBase::get_main().state(); g_save = UI::savedata(); g_game_manager = get_game_manager(); g_bucket = Bucket::get(); @@ -10029,7 +10032,7 @@ void init_ui() g_Console->load_history("console_history.txt"); register_on_input(&process_keys); - register_imgui_pre_init(&imgui_pre_init); + imgui_pre_init(ctx); register_imgui_init(&imgui_init); register_imgui_draw(&imgui_draw); register_post_draw(&post_draw); diff --git a/src/injected/ui.hpp b/src/injected/ui.hpp index 7e92ff545..6310d9d92 100644 --- a/src/injected/ui.hpp +++ b/src/injected/ui.hpp @@ -52,5 +52,5 @@ const int OL_WHEEL_UP = 0x12; struct EntityItem; void create_box(std::vector items); -void init_ui(); +void init_ui(struct ImGuiContext* ctx); void reload_enabled_scripts(); diff --git a/src/injected/ui_util.cpp b/src/injected/ui_util.cpp index aee48aa72..561f21890 100644 --- a/src/injected/ui_util.cpp +++ b/src/injected/ui_util.cpp @@ -16,6 +16,7 @@ #include "entity_lookup.hpp" // #include "game_api.hpp" // #include "game_manager.hpp" // for get_game_manager, GameManager +#include "game_patches.hpp" // #include "illumination.hpp" // #include "items.hpp" // for Items #include "layer.hpp" // for Layer, EntityList::Range, Entit... @@ -27,17 +28,17 @@ #include "savestate.hpp" // for copy_save_slot #include "search.hpp" // #include "spawn_api.hpp" // for spawn_liquid, spawn_companion -#include "state.hpp" // for State, StateMemory +#include "state.hpp" // for StateMemory #include "state_structs.hpp" // for Camera, Illumination (ptr only) #include "steam_api.hpp" // for disable_steam_achievements, ena... void UI::godmode(bool g) { - State::get().godmode(g); + API::godmode(g); } void UI::godmode_companions(bool g) { - State::get().godmode_companions(g); + API::godmode_companions(g); } void UI::death_enabled(bool g) { @@ -45,35 +46,35 @@ void UI::death_enabled(bool g) } std::pair UI::click_position(float x, float y) { - return State::click_position(x, y); + return API::click_position(x, y); } void UI::zoom(float level) { - State::get().zoom(level); + API::zoom(level); } void UI::zoom_reset() { - State::get().zoom_reset(); + API::zoom_reset(); } uint32_t UI::get_frame_count() { - return State::get().get_frame_count(); + return HeapBase::get().frame_count(); } void UI::warp(uint8_t world, uint8_t level, uint8_t theme) { - static auto state = State::get().ptr(); + auto state = HeapBase::get().state(); if (state->items->player_inventories[0].health == 0) state->items->player_inventories[0].health = 4; - State::get().warp(world, level, theme); + HeapBase::get().state()->warp(world, level, theme); } void UI::transition(uint8_t world, uint8_t level, uint8_t theme) { - auto state = State::get().ptr_main(); + auto state = HeapBase::get().state(); if (state->screen != 12) { - State::get().warp(world, level, theme); + state->warp(world, level, theme); return; } state->world_next = world; @@ -115,8 +116,7 @@ void teleport_entity(Entity* ent, float dx, float dy, bool s, float vx, float vy { // screen coordinates -1..1 // log::debug!("Teleporting to screen {}, {}", x, y); - auto& state = State::get(); - auto [x_pos, y_pos] = state.click_position(dx, dy); + auto [x_pos, y_pos] = API::click_position(dx, dy); if (snap && abs(vx) + abs(vy) <= 0.04f) { x_pos = round(x_pos); @@ -150,27 +150,25 @@ void UI::teleport_entity_abs(Entity* ent, float dx, float dy, float vx, float vy } void UI::teleport(float x, float y, bool s, float vx, float vy, bool snap) { - auto state = State::get().ptr_main(); + auto state = HeapBase::get().state(); - auto player = state->items->player(0); + auto player = state->items->players[0]; if (player == nullptr) return; teleport_entity(player, x, y, s, vx, vy, snap); } std::pair UI::screen_position(float x, float y) { - return State::screen_position(x, y); + return API::screen_position(x, y); } float UI::screen_distance(float x) { - auto a = State::screen_position(0, 0); - auto b = State::screen_position(x, 0); + auto a = API::screen_position(0, 0); + auto b = API::screen_position(x, 0); return b.x - a.x; } Entity* UI::get_entity_at(float x, float y, bool s, float radius, uint32_t mask) { - auto& state = State::get(); - static const auto masks_order = { 0x1, // Player 0x2, // Mount @@ -190,7 +188,7 @@ Entity* UI::get_entity_at(float x, float y, bool s, float radius, uint32_t mask) }; if (s) { - std::tie(x, y) = state.click_position(x, y); + std::tie(x, y) = API::click_position(x, y); } Entity* current_entity = nullptr; float current_distance = radius; @@ -204,10 +202,10 @@ Entity* UI::get_entity_at(float x, float y, bool s, float radius, uint32_t mask) current_distance = distance; } }; - + auto state = HeapBase::get().state(); if (mask == 0) { - for (auto& item : state.layer(state.ptr_main()->camera_layer)->all_entities.entities()) + for (auto& item : state->layers[state->camera_layer]->all_entities.entities()) { check_distance(item); } @@ -219,8 +217,8 @@ Entity* UI::get_entity_at(float x, float y, bool s, float radius, uint32_t mask) if ((mask & current_mask) == 0) continue; - const auto& entities = state.layer(state.ptr_main()->camera_layer)->entities_by_mask.find(current_mask); - if (entities == state.layer(state.ptr_main()->camera_layer)->entities_by_mask.end()) + const auto& entities = state->layers[state->camera_layer]->entities_by_mask.find(current_mask); + if (entities == state->layers[state->camera_layer]->entities_by_mask.end()) continue; for (auto& item : entities->second.entities()) @@ -240,11 +238,11 @@ void UI::move_entity(uint32_t uid, float x, float y, bool s, float vx, float vy, } SaveData* UI::savedata() { - return State::get().savedata(); + return get_game_manager()->save_related->savedata.decode_local(); } int32_t UI::spawn_entity(ENT_TYPE entity_type, float x, float y, bool s, float vx, float vy, bool snap) { - auto state = State::get().ptr_local(); + auto state = HeapBase::get().state(); if (!s) { @@ -256,12 +254,12 @@ int32_t UI::spawn_entity(ENT_TYPE entity_type, float x, float y, bool s, float v } int32_t UI::spawn_grid(ENT_TYPE entity_type, float x, float y, uint8_t layer) { - auto state = State::get().ptr_local(); + auto state = HeapBase::get().state(); return state->layers[layer]->spawn_entity(entity_type, x, y, false, 0, 0, false)->uid; } int32_t UI::spawn_door(float x, float y, uint8_t w, uint8_t l, uint8_t t) { - auto state = State::get().ptr_local(); + auto state = HeapBase::get().state(); x += state->camera->focus_x; y += state->camera->focus_y; @@ -271,7 +269,7 @@ int32_t UI::spawn_door(float x, float y, uint8_t w, uint8_t l, uint8_t t) } void UI::spawn_backdoor(float x, float y) { - auto state = State::get().ptr_local(); + auto state = HeapBase::get().state(); x += state->camera->focus_x; y += state->camera->focus_y; @@ -344,7 +342,7 @@ ENT_TYPE UI::get_entity_type(int32_t uid) } std::vector UI::get_players() { - return ::get_players(State::get().ptr_main()); + return HeapBase::get().state()->get_players(); } int32_t UI::get_grid_entity_at(float x, float y, LAYER l) { @@ -358,7 +356,7 @@ void UI::set_camp_camera_bounds_enabled(bool b) { ::set_camp_camera_bounds_enabled(b); } -std::vector UI::get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer) +std::vector UI::get_entities_by(std::vector entity_types, ENTITY_MASK mask, LAYER layer) { return ::get_entities_by(entity_types, mask, layer); } @@ -388,13 +386,11 @@ std::pair UI::get_room_pos(uint32_t x, uint32_t y) } std::string_view UI::get_room_template_name(uint16_t room_template) { - const auto state = State::get().ptr_main(); - return state->level_gen->get_room_template_name(room_template); + return HeapBase::get().level_gen()->get_room_template_name(room_template); } std::optional UI::get_room_template(uint32_t x, uint32_t y, uint8_t l) { - const auto state = State::get().ptr_main(); - return state->level_gen->get_room_template(x, y, l); + return HeapBase::get().level_gen()->get_room_template(x, y, l); } void UI::steam_achievements(bool on) { @@ -405,9 +401,9 @@ void UI::steam_achievements(bool on) } int32_t UI::destroy_entity_items(Entity* ent) { - if (ent->type->search_flags & 0x80) + if ((ent->type->search_flags & ENTITY_MASK::ACTIVEFLOOR) == ENTITY_MASK::ACTIVEFLOOR) return 0; - auto items = entity_get_items_by(ent->uid, 0, 0); + auto items = entity_get_items_by(ent->uid, 0, ENTITY_MASK::ANY); // TODO: use ent->items if (items.size() == 0) return -1; std::vector::reverse_iterator it = items.rbegin(); @@ -415,17 +411,18 @@ int32_t UI::destroy_entity_items(Entity* ent) while (it != items.rend()) { auto item = get_entity_ptr(*it); - if (item->type->search_flags & 0x81) - continue; - UI::destroy_entity_items(item); - UI::safe_destroy(item, false, false); - it++; + if (!(item->type->search_flags & (ENTITY_MASK::ACTIVEFLOOR | ENTITY_MASK::PLAYER))) + { + UI::destroy_entity_items(item); + UI::safe_destroy(item, false, false); + it++; + } } return last_uid; } bool UI::destroy_entity_item_type(Entity* ent, ENT_TYPE type) { - auto items = entity_get_items_by(ent->uid, 0, 0); + auto items = entity_get_items_by(ent->uid, 0, ENTITY_MASK::ANY); // TODO: use ent->items if (items.size() == 0) return false; auto destroyed = false; @@ -472,13 +469,13 @@ void UI::update_floor_at(float x, float y, LAYER l) if (uid == -1) return; auto ent = get_entity_ptr(uid); - if ((ent->type->search_flags & 0x100) == 0 || !test_flag(ent->flags, 3)) + if (!(ent->type->search_flags & ENTITY_MASK::FLOOR) || !test_flag(ent->flags, 3)) return; auto floor = ent->as(); - auto state = State::get().ptr_main(); + auto state = HeapBase::get().state(); if (test_flag(state->special_visibility_flags, 1)) { - for (auto item : entity_get_items_by(floor->uid, 0, 0x8)) + for (auto item : entity_get_items_by(floor->uid, 0, ENTITY_MASK::ITEM)) { auto embed = get_entity_ptr(item); clr_flag(embed->flags, 1); @@ -504,7 +501,7 @@ void UI::update_floor_at(float x, float y, LAYER l) floor->decos[i] = -1; } } - for (auto deco : entity_get_items_by(floor->uid, destroy_deco, 0x200)) + for (auto deco : entity_get_items_by(floor->uid, destroy_deco, ENTITY_MASK::DECORATION)) { auto deco_ent = get_entity_ptr(deco); if (deco_ent) @@ -513,7 +510,7 @@ void UI::update_floor_at(float x, float y, LAYER l) deco_ent->destroy(); } } - for (auto deco : get_entities_at(destroy_deco, 0, x, y, l, 0.5f)) + for (auto deco : get_entities_at(destroy_deco, ENTITY_MASK::ANY, x, y, l, 0.5f)) { auto deco_ent = get_entity_ptr(deco); if (deco_ent) @@ -573,7 +570,7 @@ void UI::cleanup_at(float x, float y, LAYER l, ENT_TYPE type) to_id("ENT_TYPE_BG_DOOR_BACK_LAYER"), }; - for (auto bg : get_entities_at(cleanup_ents, 0, x, y, l, 0.1f)) + for (auto bg : get_entities_at(cleanup_ents, ENTITY_MASK::ANY, x, y, l, 0.1f)) { auto bg_ent = get_entity_ptr(bg); if (bg_ent) @@ -585,7 +582,7 @@ void UI::cleanup_at(float x, float y, LAYER l, ENT_TYPE type) while (true) { y -= 1.0f; - auto bgs = get_entities_at(platform_bg, 0x400, x, y, l, 0.1f); + auto bgs = get_entities_at(platform_bg, ENTITY_MASK::BG, x, y, l, 0.1f); if (bgs.size() == 0) return; for (auto bg : bgs) @@ -600,13 +597,13 @@ void UI::cleanup_at(float x, float y, LAYER l, ENT_TYPE type) { if (type == layer_door || type == logical_door) l = LAYER::BOTH; - auto door_parts = get_entities_at(door_crap, 0, x, y, l, 0.5f); + auto door_parts = get_entities_at(door_crap, ENTITY_MASK::ANY, x, y, l, 0.5f); for (auto part : door_parts) { auto ent = get_entity_ptr(part); ent->destroy(); } - for (auto uid : get_entities_at(door_platform, 0x100, x, y - 1.0f, l, 0.5f)) + for (auto uid : get_entities_at(door_platform, ENTITY_MASK::FLOOR, x, y - 1.0f, l, 0.5f)) { auto ent = get_entity_ptr(uid); ent->destroy(); @@ -687,7 +684,7 @@ void UI::safe_destroy(Entity* ent, bool unsafe, bool recurse) { if (check && in_array(check->type->id, olmecs)) { - auto state = State::get().ptr(); + auto state = HeapBase::get().state(); if (state->logic->olmec_cutscene) { // if cutscene is still running, perform the last frame of cutscene before killing olmec @@ -738,7 +735,7 @@ void UI::safe_destroy(Entity* ent, bool unsafe, bool recurse) check->destroy(); return; } - else if (in_array(check->type->id, kill_last_overlay) || (check->type->search_flags & 0x200 && check->draw_depth <= 11)) // normal floor decorations, missing those and killing floor is not good + else if (in_array(check->type->id, kill_last_overlay) || ((check->type->search_flags & ENTITY_MASK::DECORATION) == ENTITY_MASK::DECORATION && check->draw_depth <= 11)) // normal floor decorations, missing those and killing floor is not good { kill_entity_overlay(check); return; @@ -758,7 +755,7 @@ void UI::safe_destroy(Entity* ent, bool unsafe, bool recurse) } else if (in_array(check->type->id, backitems)) { - if (check->overlay && (check->overlay->type->search_flags & 0x5) > 0) + if (check->overlay && check->overlay->is_movable() && check->overlay->as()->is_powerup_capable()) { auto wearer = check->overlay->as(); for (const auto& [powerup_type, powerup_entity] : wearer->powerups) @@ -780,7 +777,7 @@ void UI::safe_destroy(Entity* ent, bool unsafe, bool recurse) const auto [x, y] = UI::get_position(ent); const auto sf = ent->type->search_flags; destroy_entity_items(ent); - if (sf & 0x100) + if ((sf & ENTITY_MASK::FLOOR) == ENTITY_MASK::FLOOR) { if (test_flag(ent->flags, 3)) // solid floor update_liquid_collision_at(x, y, false); @@ -790,7 +787,7 @@ void UI::safe_destroy(Entity* ent, bool unsafe, bool recurse) { ent->kill(true, ent); } - else if (sf & 0x2) + else if ((sf & ENTITY_MASK::MOUNT) == ENTITY_MASK::MOUNT) { auto mount = ent->as(); mount->remove_rider(); @@ -809,7 +806,7 @@ void UI::safe_destroy(Entity* ent, bool unsafe, bool recurse) } std::vector UI::get_entities_overlapping(uint32_t mask, AABB hitbox, LAYER layer) { - return get_entities_overlapping_hitbox(0, mask, hitbox, layer); + return get_entities_overlapping_hitbox(0, (ENTITY_MASK)mask, hitbox, layer); } bool UI::get_focus() @@ -838,7 +835,8 @@ void UI::spawn_player(uint8_t player_slot, std::optional x, std::optional std::pair UI::spawn_position() { - return {State::get().ptr()->level_gen->spawn_x, State::get().ptr()->level_gen->spawn_y}; + auto level_gen = HeapBase::get().level_gen(); + return {level_gen->spawn_x, level_gen->spawn_y}; } void UI::load_death_screen() @@ -886,9 +884,14 @@ void UI::set_adventure_seed(int64_t first, int64_t second) ::set_adventure_seed(first, second); } -void UI::copy_state(int from, int to) +void UI::load_state_as_main(int from) +{ + SaveState::restore_main(from); +} + +void UI::save_main_state(int to) { - ::copy_save_slot(from, to); + SaveState::backup_main(to); } StateMemory* UI::get_save_state(int slot) diff --git a/src/injected/ui_util.hpp b/src/injected/ui_util.hpp index 512ebaf16..c333bda07 100644 --- a/src/injected/ui_util.hpp +++ b/src/injected/ui_util.hpp @@ -66,7 +66,7 @@ class UI static int32_t get_grid_entity_at(float, float, LAYER); static Illumination* create_illumination(Color color, float size, float x, float y); static void set_camp_camera_bounds_enabled(bool b); - static std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer); + static std::vector get_entities_by(std::vector entity_types, ENTITY_MASK mask, LAYER layer); static int32_t spawn_companion(ENT_TYPE compatnion_type, float x, float y, LAYER l, float velocityx, float velocityy); static void spawn_liquid(ENT_TYPE entity_type, float x, float y, float velocityx, float velocityy, uint32_t liquid_flags, uint32_t amount, float blobs_separation = INFINITY); static void spawn_liquid(ENT_TYPE entity_type, float x, float y); @@ -98,7 +98,8 @@ class UI static void init_seeded(uint32_t seed); static std::pair get_adventure_seed(std::optional run_start); static void set_adventure_seed(int64_t first, int64_t second); - static void copy_state(int from, int to); + static void load_state_as_main(int from); + static void save_main_state(int to); static StateMemory* get_save_state(int slot); static void set_camera_layer_control_enabled(bool enable); static void teleport_entity_abs(Entity* ent, float dx, float dy, float vx, float vy); diff --git a/src/spel2_dll/spel2.cpp b/src/spel2_dll/spel2.cpp index d653e9d3f..69f2449dc 100644 --- a/src/spel2_dll/spel2.cpp +++ b/src/spel2_dll/spel2.cpp @@ -8,6 +8,7 @@ #include "render_api.hpp" #include "screen.hpp" #include "script.hpp" +#include "search.hpp" #include "sound_manager.hpp" #include "spawn_api.hpp" #include "state.hpp" @@ -22,19 +23,19 @@ SpelunkyConsole* g_Console{nullptr}; void Spelunky_SetDoHooks(bool do_hooks) { - State::set_do_hooks(do_hooks); + API::set_do_hooks(do_hooks); } void Spelunky_SetWriteLoadOptimization(bool write_load_opt) { - State::set_write_load_opt(write_load_opt); + API::set_write_load_opt(write_load_opt); } void Spelunky_InitState() { - State::init(); + API::init(); } void Spelunky_PostInitState() { - State::post_init(); + API::post_init(); } void Spelunky_RegisterApplicationVersion(const char* version) @@ -347,15 +348,10 @@ void SpelunkyConsole_LoadHistory(SpelunkyConsole* console, const char* path) console->load_history(path); } -StateMemory& get_state() -{ - static StateMemory* state = State::get().ptr(); - return *state; -} - SpelunkyScreen SpelunkyState_GetScreen() { - return static_cast(get_state().screen); + auto state = HeapBase::get().state(); + return static_cast(state->screen); } int32_t Spelunky_SpawnEntity(uint32_t entity_id, int32_t layer, float x, float y, float vel_x, float vel_y) @@ -455,11 +451,6 @@ void Spelunky_EnabledAdvancedHud() RenderAPI::get().set_advanced_hud(); } -void Spelunky_UpdateLiquidOutOfBoundsBugfix() -{ - fix_liquid_out_of_bounds(); -} - void Spelunky_ReloadShaders() { RenderAPI::get().reload_shaders(); diff --git a/src/spel2_dll/spel2.h b/src/spel2_dll/spel2.h index c0fa7684f..e54ec3eea 100644 --- a/src/spel2_dll/spel2.h +++ b/src/spel2_dll/spel2.h @@ -214,6 +214,4 @@ void Spelunky_DrawText(const char* text, float x, float y, float scale_x, float void Spelunky_EnabledAdvancedHud(); -void Spelunky_UpdateLiquidOutOfBoundsBugfix(); - void Spelunky_ReloadShaders();