diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index ad4eb7d15..ffa664047 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -383,10 +383,11 @@ 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 ----@return nil -function spawn_player(player_slot, x, y) end +---@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 controll it ---or change it's `player_inputs` to the `input` of real player so he can control it directly ---@param char_type ENT_TYPE @@ -1134,14 +1135,15 @@ function update_liquid_collision_at(x, y, add) end ---@param disable boolean ---@return boolean function disable_floor_embeds(disable) end ----Get the address for a pattern name ----@param address_name string ----@return integer -function get_address(address_name) end ----Get the rva for a pattern name +---Get the rva for a pattern name, used for debugging. ---@param address_name string ----@return integer +---@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 ---Log to spelunky.log ---@param message string ---@return nil @@ -1233,39 +1235,34 @@ 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 ----@param types ENT_TYPE[] ----@return ENT_TYPE -function add_custom_type(types) 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 ----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 ----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. +---@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 +---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_level_logic_enabled(enable) 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 ---@return boolean function toast_visible() end ---@return boolean @@ -1592,8 +1589,6 @@ function set_lut(texture_id, layer) end ---@param layer LAYER ---@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 @@ -1906,9 +1901,8 @@ do ---@field is_pet_cursed boolean[] @size: 4 ---@field is_pet_poisoned boolean[] @size: 4 ---@field leader integer @Index of leader player in coop - ---@field player_select SelectPlayerSlot[] @size: MAX_PLAYERS ---@field player_inventory Inventory[] @size: MAX_PLAYERS - ---@field players Player[] @size: MAX_PLAYERS @Array of players, also keeps the dead body until they are destroyed (necromancer revive also destroys the old body) + ---@field player_select SelectPlayerSlot[] @size: MAX_PLAYERS ---@class LiquidPhysicsEngine ---@field pause boolean @@ -2154,7 +2148,6 @@ do ---@field pause_ui PauseUI ---@field journal_ui JournalUI ---@field save_related SaveRelated - ---@field main_menu_music BackgroundSound ---@class SaveRelated ---@field journal_popup_ui JournalPopupUI @@ -2384,31 +2377,6 @@ function Entity:overlaps_with(rect_left, rect_bottom, rect_right, rect_top) end ---@param other Entity ---@return boolean function Entity:overlaps_with(other) end ----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 a 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 funciton ----@param destroy_corpse boolean ----@param responsible Entity ----@param mask integer? ----@param ent_types ENT_TYPE[] ----@param rec_mode RECURSIVE_MODE ----@return nil -function Entity:kill_recursive(destroy_corpse, responsible, mask, ent_types, rec_mode) end ----Short for using RECURSIVE_MODE.NONE ----@param destroy_corpse boolean ----@param responsible Entity ----@return nil -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 a 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 ent_types ENT_TYPE[] ----@param rec_mode RECURSIVE_MODE ----@return nil -function Entity:destroy_recursive(mask, ent_types, rec_mode) end ----Short for using RECURSIVE_MODE.NONE ----@return nil -function Entity:destroy_recursive() end ---@class Movable : Entity ---@field move Vec2 @{movex, movey} @@ -2463,6 +2431,9 @@ function Entity:destroy_recursive() end ---@field set_gravity fun(self, gravity: number): nil @Force the gravity for this entity. Will override anything set by special states like swimming too, unless you reset it. Default 1.0 ---@field reset_gravity fun(self): nil @Remove the gravity hook and reset to defaults ---@field set_position fun(self, to_x: number, to_y: number): nil @Set the absolute position of an entity and offset all rendering related things accordingly to teleport without any interpolation or graphical glitches. If the camera is focused on the entity, it is also moved. + ---@field process_input fun(self): nil + ---@field cutscene CutsceneBehavior + ---@field clear_cutscene any @[](Movable&movable){deletemovable.cutscene_behavior ---@field get_base_behavior fun(self, state_id: integer): VanillaMovableBehavior @Gets a vanilla behavior from this movable, needs to be called before `clear_behaviors`
but the returned values are still valid after a call to `clear_behaviors` ---@field add_behavior fun(self, behavior: MovableBehavior): nil @Add a behavior to this movable, can be either a `VanillaMovableBehavior` or a
`CustomMovableBehavior` ---@field clear_behavior fun(self, behavior: MovableBehavior): nil @Clear a specific behavior of this movable, can be either a `VanillaMovableBehavior` or a
`CustomMovableBehavior`, a behavior with this behaviors `state_id` may be required to
run this movables statemachine without crashing, so add a new one if you are not sure @@ -2541,6 +2512,8 @@ function Movable:generic_update_world(disable_gravity) end ---@return nil function Movable:generic_update_world(move, sprint_factor, disable_gravity, on_rope) end +---@class CutsceneBehavior + ---@class PowerupCapable : Movable ---@field remove_powerup fun(self, powerup_type: ENT_TYPE): nil @Removes a currently applied powerup. Specify `ENT_TYPE.ITEM_POWERUP_xxx`, not `ENT_TYPE.ITEM_PICKUP_xxx`! Removing the Eggplant crown does not seem to undo the throwing of eggplants, the other powerups seem to work. ---@field give_powerup fun(self, powerup_type: ENT_TYPE): nil @Gives the player/monster the specified powerup. Specify `ENT_TYPE.ITEM_POWERUP_xxx`, not `ENT_TYPE.ITEM_PICKUP_xxx`! Giving true crown to a monster crashes the game. @@ -2851,7 +2824,7 @@ function Movable:generic_update_world(move, sprint_factor, disable_gravity, on_r ---@field sound1 SoundMeta ---@field sound2 SoundMeta ---@field top_chain_piece Entity - ---@field trigger fun(self, play_sound_effect: boolean?): nil + ---@field trigger fun(self): nil ---@class ThinIce : Movable ---@field strength integer @counts down when standing on, maximum is 134 as based of this value it changes animation_frame, and above that value it changes to wrong sprite @@ -4597,9 +4570,6 @@ function MovableBehavior:get_state_id() end ---@field spawn_room_y integer ---@field exit_doors custom_Array ---@field themes ThemeInfo[] @size: 18 - ---@field flags integer - ---@field flags2 integer - ---@field flags3 integer ---@class PostRoomGenerationContext ---@field set_room_template fun(self, x: integer, y: integer, layer: LAYER, room_template: ROOM_TEMPLATE): boolean @Set the room template at the given index and layer, returns `false` if the index is outside of the level. diff --git a/docs/generate.py b/docs/generate.py index 01bea7844..339d7945c 100644 --- a/docs/generate.py +++ b/docs/generate.py @@ -393,7 +393,7 @@ def print_lf(lf): cat = "Message functions" elif any( subs in func["name"] - for subs in ["get_address", "get_rva", "raise", "dump_network"] + for subs in ["get_rva", "get_virtual_rva", "raise", "dump_network"] ): cat = "Debug functions" elif any(subs in func["name"] for subs in ["_option"]): @@ -474,7 +474,7 @@ def print_lf(lf): cat = "Particle functions" elif any(subs in func["name"] for subs in ["string", "_name"]): cat = "String functions" - elif any(subs in func["name"] for subs in ["udp_"]): + elif any(subs in func["name"] for subs in ["udp_", "http_"]): cat = "Network functions" elif any(subs in func["name"] for subs in ["illuminati"]): cat = "Lighting functions" @@ -630,7 +630,7 @@ def print_lf(lf): "QuestsInfo", "PlayerSlot", "JournalProgressStickerSlot", - "JournalProgressStainSlot" + "JournalProgressStainSlot", ] ): cat = "State types" diff --git a/docs/index.html b/docs/index.html index aafd9064a..f68b9fc0e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -454,10 +454,10 @@ dump_network
  • - get_address + get_rva
  • - get_rva + get_virtual_rva
  • raise @@ -725,9 +725,21 @@
  • create_image_crop
  • +
  • + create_layer +
  • +
  • + create_level +
  • destroy_grid
  • +
  • + destroy_layer +
  • +
  • + destroy_level +
  • disable_floor_embeds
  • @@ -786,13 +798,10 @@ grow_vines
  • - http_get + import
  • - http_get_async -
  • -
  • - import + inputs_to_buttons
  • intersection @@ -863,6 +872,9 @@
  • set_level_config
  • +
  • + set_level_logic_enabled +
  • set_mask
  • @@ -904,6 +916,9 @@
  • Input functions +

    inputs_to_buttons

    +
    +

    Search script examples for inputs_to_buttons

    +
    +

    tuple<float, float, BUTTON> inputs_to_buttons(INPUTS inputs)

    +

    Converts INPUTS to (x, y, BUTTON)

    intersection

    Search script examples for intersection

    @@ -4943,6 +5008,12 @@

    set_level_config

    nil set_level_config(LEVEL_CONFIG config, int value)

    Set the value for the specified config

    +

    set_level_logic_enabled

    +
    +

    Search script examples for set_level_logic_enabled

    +
    +

    nil set_level_logic_enabled(bool enable)

    +

    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.

    set_mask

    Search script examples for set_mask

    @@ -5069,7 +5140,13 @@

    warp

    nil warp(int w, int l, int t)

    Warp to a level immediately.

    -

    Input functions

    get_io

    +

    Input functions

    buttons_to_inputs

    +
    +

    Search script examples for buttons_to_inputs

    +
    +

    INPUTS buttons_to_inputs(float x, float y, BUTTON buttons)

    +

    Converts (x, y, BUTTON) to INPUTS

    +

    get_io

    Search script examples for get_io

    @@ -5215,7 +5292,20 @@

    Network functions

    udp_listen

    +

    Network functions

    http_get

    +
    +

    Search script examples for http_get

    +
    +

    optional<string> http_get(string url)

    +

    Send a synchronous HTTP GET request and return response as a string or nil on an error

    +

    http_get_async

    +
    +

    Search script examples for http_get_async

    +
    +

    HttpRequest http_get_async(string url, function on_data)

    +

    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)

    +

    udp_listen

    Search script examples for udp_listen

    @@ -5838,7 +5928,7 @@

    spawn_player

    Search script examples for spawn_player

    -

    nil spawn_player(int player_slot, float x, float y)

    +

    int spawn_player(int player_slot, optional x, optional y, optional<LAYER> layer)

    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

    spawn_playerghost

    @@ -8771,6 +8861,15 @@

    Color

    uColor
     
     
    +

    CutsceneBehavior

    + + + + + + + +
    TypeNameDescription

    Hud

    set_callback(function(ctx, hud)
         -- draw on screen bottom but keep neat animations
         if hud.y > 0 then hud.y = -hud.y end
    @@ -26105,6 +26204,21 @@ 

    Movable

    Set the absolute position of an entity and offset all rendering related things accordingly to teleport without any interpolation or graphical glitches. If the camera is focused on the entity, it is also moved. +nil +process_input() + + + +CutsceneBehavior +cutscene + + + + +clear_cutscene + + + VanillaMovableBehavior get_base_behavior(int state_id) Gets a vanilla behavior from this movable, needs to be called before clear_behaviors
    but the returned values are still valid after a call to clear_behaviors @@ -27891,7 +28005,7 @@

    ON.PRE_LEVEL_GENERATION

    Search script examples for ON.PRE_LEVEL_GENERATION

    -

    Runs before any level generation, no entities should exist at this point

    +

    Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation.

    ON.PRE_LOAD_SCREEN

    Search script examples for ON.PRE_LOAD_SCREEN

    @@ -28112,6 +28226,54 @@

    ON.USER_DATA

    Params: Entity ent
    Runs on all changes to Entity.user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels.

    +

    ON.PRE_LEVEL_CREATION

    +
    +

    Search script examples for ON.PRE_LEVEL_CREATION

    +
    + +

    Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.POST_LEVEL_CREATION

    +
    +

    Search script examples for ON.POST_LEVEL_CREATION

    +
    + +

    Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.PRE_LAYER_CREATION

    +
    +

    Search script examples for ON.PRE_LAYER_CREATION

    +
    + +

    Params: LAYER layer
    Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.POST_LAYER_CREATION

    +
    +

    Search script examples for ON.POST_LAYER_CREATION

    +
    + +

    Params: LAYER layer
    Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.PRE_LEVEL_DESTRUCTION

    +
    +

    Search script examples for ON.PRE_LEVEL_DESTRUCTION

    +
    + +

    Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    +

    ON.POST_LEVEL_DESTRUCTION

    +
    +

    Search script examples for ON.POST_LEVEL_DESTRUCTION

    +
    + +

    Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    +

    ON.PRE_LAYER_DESTRUCTION

    +
    +

    Search script examples for ON.PRE_LAYER_DESTRUCTION

    +
    + +

    Params: LAYER layer
    Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    +

    ON.POST_LAYER_DESTRUCTION

    +
    +

    Search script examples for ON.POST_LAYER_DESTRUCTION

    +
    + +

    Params: LAYER layer
    Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    Enums

    Enums are like numbers but in text that's easier to remember.

    set_callback(function()
    @@ -30724,7 +30886,7 @@ 

    ON

    PRE_LEVEL_GENERATION ON::PRE_LEVEL_GENERATION -Runs before any level generation, no entities should exist at this point
    +Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation.
    PRE_LOAD_SCREEN @@ -30896,6 +31058,46 @@

    ON

    ON::USER_DATA Params: Entity ent
    Runs on all changes to Entity.user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels.
    + +PRE_LEVEL_CREATION +ON::PRE_LEVEL_CREATION +Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +POST_LEVEL_CREATION +ON::POST_LEVEL_CREATION +Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +PRE_LAYER_CREATION +ON::PRE_LAYER_CREATION +Params: LAYER layer
    Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +POST_LAYER_CREATION +ON::POST_LAYER_CREATION +Params: LAYER layer
    Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +PRE_LEVEL_DESTRUCTION +ON::PRE_LEVEL_DESTRUCTION +Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + + +POST_LEVEL_DESTRUCTION +ON::POST_LEVEL_DESTRUCTION +Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + + +PRE_LAYER_DESTRUCTION +ON::PRE_LAYER_DESTRUCTION +Params: LAYER layer
    Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + + +POST_LAYER_DESTRUCTION +ON::POST_LAYER_DESTRUCTION +Params: LAYER layer
    Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    +

    PARTICLEEMITTER

    diff --git a/docs/light.html b/docs/light.html index 01403b50e..386291e73 100644 --- a/docs/light.html +++ b/docs/light.html @@ -454,10 +454,10 @@ dump_network
  • - get_address + get_rva
  • - get_rva + get_virtual_rva
  • raise @@ -725,9 +725,21 @@
  • create_image_crop
  • +
  • + create_layer +
  • +
  • + create_level +
  • destroy_grid
  • +
  • + destroy_layer +
  • +
  • + destroy_level +
  • disable_floor_embeds
  • @@ -786,13 +798,10 @@ grow_vines
  • - http_get + import
  • - http_get_async -
  • -
  • - import + inputs_to_buttons
  • intersection @@ -863,6 +872,9 @@
  • set_level_config
  • +
  • + set_level_logic_enabled +
  • set_mask
  • @@ -904,6 +916,9 @@
  • Input functions
      +
    • + buttons_to_inputs +
    • get_io
    • @@ -993,6 +1008,12 @@
    • Network functions
    • @@ -4080,18 +4128,18 @@

      Debug functions

      dump_network<

      nil dump_network()

      Hook the sendto and recvfrom functions and start dumping network data to terminal

      -

      get_address

      -
      -

      Search script examples for get_address

      -
      -

      size_t get_address(string_view address_name)

      -

      Get the address for a pattern name

      get_rva

      Search script examples for get_rva

      -

      size_t get_rva(string_view address_name)

      -

      Get the rva for a pattern name

      +

      string get_rva(string_view address_name)

      +

      Get the rva for a pattern name, used for debugging.

      +

      get_virtual_rva

      +
      +

      Search script examples for get_virtual_rva

      +
      +

      string get_virtual_rva(VTABLE_OFFSET offset, int index)

      +

      Get the rva for a vtable offset and index, used for debugging.

      raise

      Search script examples for raise

      @@ -4642,6 +4690,18 @@

      create_image_crop

      tuple<IMAGE, int, int> create_image_crop(string path, int x, int y, int w, int h)

      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

      +

      create_layer

      +
      +

      Search script examples for create_layer

      +
      +

      nil create_layer(int layer)

      +

      Initializes an empty layer that doesn't currently exist.

      +

      create_level

      +
      +

      Search script examples for create_level

      +
      +

      nil create_level()

      +

      Initializes an empty front and back layer that don't currently exist. Does nothing(?) if layers already exist.

      destroy_grid

      Search script examples for destroy_grid

      @@ -4649,6 +4709,18 @@

      destroy_grid

      nil destroy_grid(int uid)

      nil destroy_grid(float x, float y, LAYER layer)

      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

      +

      destroy_layer

      +
      +

      Search script examples for destroy_layer

      +
      +

      nil destroy_layer(int layer)

      +

      Destroys a layer and all entities in it.

      +

      destroy_level

      +
      +

      Search script examples for destroy_level

      +
      +

      nil destroy_level()

      +

      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

      @@ -4764,19 +4836,6 @@

      grow_vines

      nil grow_vines(LAYER l, int max_lengh)

      nil grow_vines(LAYER l, int max_lengh, AABB area, bool destroy_broken)

      Grow vines from GROWABLE_VINE and VINE_TREE_TOP entities in a level, area default is whole level, destroy_broken default is false

      -

      http_get

      -
      -

      Search script examples for http_get

      -
      -

      optional<string> http_get(string url)

      -

      Send a synchronous HTTP GET request and return response as a string or nil on an error

      -

      http_get_async

      -
      -

      Search script examples for http_get_async

      -
      -

      HttpRequest http_get_async(string url, function on_data)

      -

      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)

      import

      Search script examples for import

      @@ -4790,6 +4849,12 @@

      tab
    • false if the script was not found but optional is set to true
    • an error if the script was not found and the optional argument was not set
    +

    inputs_to_buttons

    +
    +

    Search script examples for inputs_to_buttons

    +
    +

    tuple<float, float, BUTTON> inputs_to_buttons(INPUTS inputs)

    +

    Converts INPUTS to (x, y, BUTTON)

    intersection

    Search script examples for intersection

    @@ -4943,6 +5008,12 @@

    set_level_config

    nil set_level_config(LEVEL_CONFIG config, int value)

    Set the value for the specified config

    +

    set_level_logic_enabled

    +
    +

    Search script examples for set_level_logic_enabled

    +
    +

    nil set_level_logic_enabled(bool enable)

    +

    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.

    set_mask

    Search script examples for set_mask

    @@ -5069,7 +5140,13 @@

    warp

    nil warp(int w, int l, int t)

    Warp to a level immediately.

    -

    Input functions

    get_io

    +

    Input functions

    buttons_to_inputs

    +
    +

    Search script examples for buttons_to_inputs

    +
    +

    INPUTS buttons_to_inputs(float x, float y, BUTTON buttons)

    +

    Converts (x, y, BUTTON) to INPUTS

    +

    get_io

    Search script examples for get_io

    @@ -5215,7 +5292,20 @@

    Network functions

    udp_listen

    +

    Network functions

    http_get

    +
    +

    Search script examples for http_get

    +
    +

    optional<string> http_get(string url)

    +

    Send a synchronous HTTP GET request and return response as a string or nil on an error

    +

    http_get_async

    +
    +

    Search script examples for http_get_async

    +
    +

    HttpRequest http_get_async(string url, function on_data)

    +

    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)

    +

    udp_listen

    Search script examples for udp_listen

    @@ -5838,7 +5928,7 @@

    spawn_player

    Search script examples for spawn_player

    -

    nil spawn_player(int player_slot, float x, float y)

    +

    int spawn_player(int player_slot, optional x, optional y, optional<LAYER> layer)

    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

    spawn_playerghost

    @@ -8771,6 +8861,15 @@

    Color

    uColor
     
     
    +

    CutsceneBehavior

    + + + + + + + +
    TypeNameDescription

    Hud

    set_callback(function(ctx, hud)
         -- draw on screen bottom but keep neat animations
         if hud.y > 0 then hud.y = -hud.y end
    @@ -26105,6 +26204,21 @@ 

    Movable

    Set the absolute position of an entity and offset all rendering related things accordingly to teleport without any interpolation or graphical glitches. If the camera is focused on the entity, it is also moved. +nil +process_input() + + + +CutsceneBehavior +cutscene + + + + +clear_cutscene + + + VanillaMovableBehavior get_base_behavior(int state_id) Gets a vanilla behavior from this movable, needs to be called before clear_behaviors
    but the returned values are still valid after a call to clear_behaviors @@ -27891,7 +28005,7 @@

    ON.PRE_LEVEL_GENERATION

    Search script examples for ON.PRE_LEVEL_GENERATION

    -

    Runs before any level generation, no entities should exist at this point

    +

    Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation.

    ON.PRE_LOAD_SCREEN

    Search script examples for ON.PRE_LOAD_SCREEN

    @@ -28112,6 +28226,54 @@

    ON.USER_DATA

    Params: Entity ent
    Runs on all changes to Entity.user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels.

    +

    ON.PRE_LEVEL_CREATION

    +
    +

    Search script examples for ON.PRE_LEVEL_CREATION

    +
    + +

    Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.POST_LEVEL_CREATION

    +
    +

    Search script examples for ON.POST_LEVEL_CREATION

    +
    + +

    Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.PRE_LAYER_CREATION

    +
    +

    Search script examples for ON.PRE_LAYER_CREATION

    +
    + +

    Params: LAYER layer
    Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.POST_LAYER_CREATION

    +
    +

    Search script examples for ON.POST_LAYER_CREATION

    +
    + +

    Params: LAYER layer
    Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.

    +

    ON.PRE_LEVEL_DESTRUCTION

    +
    +

    Search script examples for ON.PRE_LEVEL_DESTRUCTION

    +
    + +

    Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    +

    ON.POST_LEVEL_DESTRUCTION

    +
    +

    Search script examples for ON.POST_LEVEL_DESTRUCTION

    +
    + +

    Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    +

    ON.PRE_LAYER_DESTRUCTION

    +
    +

    Search script examples for ON.PRE_LAYER_DESTRUCTION

    +
    + +

    Params: LAYER layer
    Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    +

    ON.POST_LAYER_DESTRUCTION

    +
    +

    Search script examples for ON.POST_LAYER_DESTRUCTION

    +
    + +

    Params: LAYER layer
    Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.

    Enums

    Enums are like numbers but in text that's easier to remember.

    set_callback(function()
    @@ -30724,7 +30886,7 @@ 

    ON

    PRE_LEVEL_GENERATION ON::PRE_LEVEL_GENERATION -Runs before any level generation, no entities should exist at this point
    +Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation.
    PRE_LOAD_SCREEN @@ -30896,6 +31058,46 @@

    ON

    ON::USER_DATA Params: Entity ent
    Runs on all changes to Entity.user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels.
    + +PRE_LEVEL_CREATION +ON::PRE_LEVEL_CREATION +Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +POST_LEVEL_CREATION +ON::POST_LEVEL_CREATION +Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +PRE_LAYER_CREATION +ON::PRE_LAYER_CREATION +Params: LAYER layer
    Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +POST_LAYER_CREATION +ON::POST_LAYER_CREATION +Params: LAYER layer
    Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    + + +PRE_LEVEL_DESTRUCTION +ON::PRE_LEVEL_DESTRUCTION +Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + + +POST_LEVEL_DESTRUCTION +ON::POST_LEVEL_DESTRUCTION +Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + + +PRE_LAYER_DESTRUCTION +ON::PRE_LAYER_DESTRUCTION +Params: LAYER layer
    Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + + +POST_LAYER_DESTRUCTION +ON::POST_LAYER_DESTRUCTION +Params: LAYER layer
    Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    +

    PARTICLEEMITTER

    diff --git a/docs/parse_source.py b/docs/parse_source.py index 9f2f8c57b..71085e855 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -126,6 +126,8 @@ cpp_type_exceptions = [ "Players", + "CutsceneBehavior", + "CustomCutsceneBehavior", ] not_functions = [ "players", @@ -893,7 +895,7 @@ def run_parse(): if not var: continue var = var.split(",") - if(len(var) > 1): + if len(var) > 1: vars.append({"name": var[0], "type": var[1]}) enums.append({"name": name, "vars": vars}) data = open(file, "r").read() diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index c12ecedca..78e066c82 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -783,7 +783,7 @@ Name | Data | Description [SAVE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.SAVE) | ON::SAVE | Params: [SaveContext](#SaveContext) save_ctx
    Runs at the same times as [ON](#ON).[SCREEN](#SCREEN), but receives the save_ctx
    [LOAD](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.LOAD) | ON::LOAD | Params: [LoadContext](#LoadContext) load_ctx
    Runs as soon as your script is loaded, including reloads, then never again
    [PRE_LOAD_LEVEL_FILES](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_LEVEL_FILES) | ON::PRE_LOAD_LEVEL_FILES | Params: [PreLoadLevelFilesContext](#PreLoadLevelFilesContext) load_level_ctx
    Runs right before level files would be loaded
    -[PRE_LEVEL_GENERATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_GENERATION) | ON::PRE_LEVEL_GENERATION | Runs before any level generation, no entities should exist at this point
    +[PRE_LEVEL_GENERATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_GENERATION) | ON::PRE_LEVEL_GENERATION | Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation.
    [PRE_LOAD_SCREEN](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LOAD_SCREEN) | ON::PRE_LOAD_SCREEN | Runs right before loading a new screen based on screen_next. Return true from callback to block the screen from loading.
    [POST_ROOM_GENERATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_ROOM_GENERATION) | ON::POST_ROOM_GENERATION | Params: [PostRoomGenerationContext](#PostRoomGenerationContext) room_gen_ctx
    Runs right after all rooms are generated before entities are spawned
    [POST_LEVEL_GENERATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LEVEL_GENERATION) | ON::POST_LEVEL_GENERATION | Runs right after level generation is done, before any entities are updated
    @@ -818,6 +818,14 @@ Name | Data | Description [PRE_UPDATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_UPDATE) | ON::PRE_UPDATE | Runs before the State is updated, runs always (menu, settings, camp, game, arena, online etc.) with the game engine, typically 60FPS
    Return behavior: return true to stop futher PRE_UPDATE callbacks from executing and don't update the state (this will essentially freeze the game engine)
    [POST_UPDATE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_UPDATE) | ON::POST_UPDATE | Runs right after the State is updated, runs always (menu, settings, camp, game, arena, online etc.) with the game engine, typically 60FPS
    [USER_DATA](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.USER_DATA) | ON::USER_DATA | Params: [Entity](#Entity) ent
    Runs on all changes to [Entity](#Entity).user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels.
    +[PRE_LEVEL_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_CREATION) | ON::PRE_LEVEL_CREATION | Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    +[POST_LEVEL_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LEVEL_CREATION) | ON::POST_LEVEL_CREATION | Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    +[PRE_LAYER_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LAYER_CREATION) | ON::PRE_LAYER_CREATION | Params: [LAYER](#LAYER) layer
    Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    +[POST_LAYER_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LAYER_CREATION) | ON::POST_LAYER_CREATION | Params: [LAYER](#LAYER) layer
    Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    +[PRE_LEVEL_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_DESTRUCTION) | ON::PRE_LEVEL_DESTRUCTION | Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    +[POST_LEVEL_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LEVEL_DESTRUCTION) | ON::POST_LEVEL_DESTRUCTION | Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    +[PRE_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LAYER_DESTRUCTION) | ON::PRE_LAYER_DESTRUCTION | Params: [LAYER](#LAYER) layer
    Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    +[POST_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LAYER_DESTRUCTION) | ON::POST_LAYER_DESTRUCTION | Params: [LAYER](#LAYER) layer
    Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    ## PARTICLEEMITTER diff --git a/docs/src/includes/_events.md b/docs/src/includes/_events.md index 1e9d26acf..309a790b1 100644 --- a/docs/src/includes/_events.md +++ b/docs/src/includes/_events.md @@ -290,7 +290,7 @@ Params: [PreLoadLevelFilesContext](#PreLoadLevelFilesContext) load_level_ctx
    Search script examples for [ON.PRE_LEVEL_GENERATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_GENERATION) -Runs before any level generation, no entities should exist at this point
    +Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation.
    ## ON.PRE_LOAD_SCREEN @@ -549,3 +549,59 @@ Runs right after the State is updated, runs always (menu, settings, camp, game, > Search script examples for [ON.USER_DATA](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.USER_DATA) Params: [Entity](#Entity) ent
    Runs on all changes to [Entity](#Entity).user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels.
    + +## ON.PRE_LEVEL_CREATION + + +> Search script examples for [ON.PRE_LEVEL_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_CREATION) + +Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    + +## ON.POST_LEVEL_CREATION + + +> Search script examples for [ON.POST_LEVEL_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LEVEL_CREATION) + +Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    + +## ON.PRE_LAYER_CREATION + + +> Search script examples for [ON.PRE_LAYER_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LAYER_CREATION) + +Params: [LAYER](#LAYER) layer
    Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually.
    + +## ON.POST_LAYER_CREATION + + +> Search script examples for [ON.POST_LAYER_CREATION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LAYER_CREATION) + +Params: [LAYER](#LAYER) layer
    Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually.
    + +## ON.PRE_LEVEL_DESTRUCTION + + +> Search script examples for [ON.PRE_LEVEL_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LEVEL_DESTRUCTION) + +Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + +## ON.POST_LEVEL_DESTRUCTION + + +> Search script examples for [ON.POST_LEVEL_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LEVEL_DESTRUCTION) + +Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + +## ON.PRE_LAYER_DESTRUCTION + + +> Search script examples for [ON.PRE_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.PRE_LAYER_DESTRUCTION) + +Params: [LAYER](#LAYER) layer
    Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    + +## ON.POST_LAYER_DESTRUCTION + + +> Search script examples for [ON.POST_LAYER_DESTRUCTION](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=ON.POST_LAYER_DESTRUCTION) + +Params: [LAYER](#LAYER) layer
    Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last.
    diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 6f7978268..d83184c82 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -351,23 +351,23 @@ If you set such a callback and then play the same sound yourself you have to wai Hook the sendto and recvfrom functions and start dumping network data to terminal -### get_address +### get_rva -> Search script examples for [get_address](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_address) +> Search script examples for [get_rva](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_rva) -#### size_t get_address(string_view address_name) +#### string get_rva(string_view address_name) -Get the address for a pattern name +Get the rva for a pattern name, used for debugging. -### get_rva +### get_virtual_rva -> Search script examples for [get_rva](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_rva) +> Search script examples for [get_virtual_rva](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_virtual_rva) -#### size_t get_rva(string_view address_name) +#### string get_virtual_rva(VTABLE_OFFSET offset, int index) -Get the rva for a pattern name +Get the rva for a vtable offset and index, used for debugging. ### raise @@ -1246,6 +1246,24 @@ Depending on the image size, this can take a moment, preferably don't create the 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 +### create_layer + + +> Search script examples for [create_layer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=create_layer) + +#### nil create_layer(int layer) + +Initializes an empty layer that doesn't currently exist. + +### create_level + + +> Search script examples for [create_level](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=create_level) + +#### nil create_level() + +Initializes an empty front and back layer that don't currently exist. Does nothing(?) if layers already exist. + ### destroy_grid @@ -1258,6 +1276,24 @@ Depending on the image size, this can take a moment, preferably don't create the 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](#MASK).PLAYER to prevent crashes +### destroy_layer + + +> Search script examples for [destroy_layer](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_layer) + +#### nil destroy_layer(int layer) + +Destroys a layer and all entities in it. + +### destroy_level + + +> Search script examples for [destroy_level](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_level) + +#### nil destroy_level() + +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 @@ -1454,25 +1490,6 @@ Grow pole from `GROWABLE_CLIMBING_POLE` entities in a level, `area` default is w Grow vines from `GROWABLE_VINE` and `VINE_TREE_TOP` entities in a level, `area` default is whole level, `destroy_broken` default is false -### http_get - - -> Search script examples for [http_get](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=http_get) - -#### optional<string> http_get(string url) - -Send a synchronous HTTP GET request and return response as a string or nil on an error - -### http_get_async - - -> Search script examples for [http_get_async](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=http_get_async) - -#### HttpRequest http_get_async(string url, function on_data) - -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) - ### import @@ -1487,6 +1504,15 @@ Load another script by id "author/name" and import its `exports` table. Returns: - `false` if the script was not found but optional is set to true - an error if the script was not found and the optional argument was not set +### inputs_to_buttons + + +> Search script examples for [inputs_to_buttons](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=inputs_to_buttons) + +#### tuple<float, float, [BUTTON](#BUTTON)> inputs_to_buttons([INPUTS](#INPUTS) inputs) + +Converts [INPUTS](#INPUTS) to (x, y, BUTTON) + ### intersection @@ -1714,6 +1740,15 @@ 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_mask @@ -1890,6 +1925,15 @@ Warp to a level immediately. ## Input functions +### buttons_to_inputs + + +> Search script examples for [buttons_to_inputs](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=buttons_to_inputs) + +#### [INPUTS](#INPUTS) buttons_to_inputs(float x, float y, [BUTTON](#BUTTON) buttons) + +Converts (x, y, BUTTON) to [INPUTS](#INPUTS) + ### get_io @@ -2125,6 +2169,25 @@ be returned instead. ## Network functions +### http_get + + +> Search script examples for [http_get](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=http_get) + +#### optional<string> http_get(string url) + +Send a synchronous HTTP GET request and return response as a string or nil on an error + +### http_get_async + + +> Search script examples for [http_get_async](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=http_get_async) + +#### HttpRequest http_get_async(string url, function on_data) + +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) + ### udp_listen @@ -3054,7 +3117,7 @@ Short for [spawn_entity_over](#spawn_entity_over) > Search script examples for [spawn_player](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawn_player) -#### nil spawn_player(int player_slot, float x, float y) +#### int spawn_player(int player_slot, optional x, optional y, optional<[LAYER](#LAYER)> layer) 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](#Inventory) `health` to above 0, and `time_of_death` to 0 and call this function, the ghost entity will be removed automatically diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index e5a07eea7..a693cdaf5 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -640,6 +640,12 @@ tuple<int, int, int, int> | [get_rgba()](https://github.com/spelunky-fyi/o [uColor](#Aliases) | [get_ucolor()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_ucolor) | Returns the `uColor` used in `GuiDrawContext` drawing functions [Color](#Color)& | [set_ucolor(const uColor color)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_ucolor) | Changes color based on given [uColor](#Aliases) +### CutsceneBehavior + + +Type | Name | Description +---- | ---- | ----------- + ### Hud @@ -6657,6 +6663,9 @@ int | [get_behavior()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q= nil | [set_gravity(float gravity)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_gravity) | Force the gravity for this entity. Will override anything set by special states like swimming too, unless you reset it. Default 1.0 nil | [reset_gravity()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=reset_gravity) | Remove the gravity hook and reset to defaults nil | [set_position(float to_x, float to_y)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_position) | Set the absolute position of an entity and offset all rendering related things accordingly to teleport without any interpolation or graphical glitches. If the camera is focused on the entity, it is also moved. +nil | [process_input()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=process_input) | +[CutsceneBehavior](#CutsceneBehavior) | [cutscene](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=cutscene) | + | [clear_cutscene](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear_cutscene) | [VanillaMovableBehavior](#VanillaMovableBehavior) | [get_base_behavior(int state_id)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_base_behavior) | Gets a vanilla behavior from this movable, needs to be called before `clear_behaviors`
    but the returned values are still valid after a call to `clear_behaviors` nil | [add_behavior(MovableBehavior behavior)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_behavior) | Add a behavior to this movable, can be either a `VanillaMovableBehavior` or a
    `CustomMovableBehavior` nil | [clear_behavior(MovableBehavior behavior)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear_behavior) | Clear a specific behavior of this movable, can be either a `VanillaMovableBehavior` or a
    `CustomMovableBehavior`, a behavior with this behaviors `state_id` may be required to
    run this movables statemachine without crashing, so add a new one if you are not sure diff --git a/src/game_api/flags.hpp b/src/game_api/flags.hpp index 79aaa2c53..60cbf73e2 100644 --- a/src/game_api/flags.hpp +++ b/src/game_api/flags.hpp @@ -756,3 +756,54 @@ const char* pause_types[]{ "32: Ankh (smooth camera, janky audio)", "Freeze on PRE_UPDATE", // this is not a real state.pause flag, it's only used by ui.cpp for magic }; + +const char* levelgen_flags[]{ + "1: Should generate path", + "2: Can spawn vault", + "3: Can spawn shops", + "4: Can have outpost?", + "5: Should spawn hard floor decorations", + "6: Apply ambient occlusion", + "7: Should spawn behind-floor and below-floorstyled decorations", + "8: unknown", +}; + +const char* levelgen_flags2[]{ + "1: Spawns background decorations on ground (ceiling if false)", + "2: Spawns fake ladder/chain midbg?", + "3: Spawn entrance door background (Ignored in 7-1 to 7-2 transition)", + "4: Procedural backlayer door midbg indicator related", + "5: Spawn backlayer border/background", + "6: Should spawn procedural backlayers", + "7: Should spawn backlayer torches", + "8: Has ghost", +}; + +const char* levelgen_flags3[]{ + "1: Can spawn angry NPCs", + "2: Can echo", + "3: Can spawn Dead are Restless", + "4: Can spawn procedural skeletons", + "5: Can have quests?", + "6: Can spawn player coffins", + "7: unknown", + "8: unknown", +}; + +const char* level_chances[]{ + "backroom", + "backroom interconnect", + "backroom hidden door", + "backroom hidden cache", + "mount", + "altar", + "idol", + "floor side spread", + "floor bottom spread", + "background", + "ground background", + "bigroom", + "wideroom", + "tallroom", + "rewardroom", +}; diff --git a/src/game_api/level_api.cpp b/src/game_api/level_api.cpp index eb4769d4a..6191fce84 100644 --- a/src/game_api/level_api.cpp +++ b/src/game_api/level_api.cpp @@ -827,7 +827,8 @@ void level_gen(LevelGenSystem* level_gen_sys, float param_2, size_t param_3) g_CustomShopTypes[0] = {}; g_CustomShopTypes[1] = {}; - pre_level_generation(); + if (pre_level_generation()) + return; g_level_gen_trampoline(level_gen_sys, param_2, param_3); post_level_generation(); @@ -847,6 +848,65 @@ void level_gen(LevelGenSystem* level_gen_sys, float param_2, size_t param_3) g_levels_to_load.clear(); } +using TransGenFun = void(ThemeInfo*); +TransGenFun* g_trans_gen_trampoline{nullptr}; +TransGenFun* g_trans_gen2_trampoline{nullptr}; +using TransGenFun3 = void(size_t, size_t, ThemeInfo*); +TransGenFun3* g_trans_gen3_trampoline{nullptr}; +using TransGenFun4 = void(size_t, size_t, size_t, size_t, size_t, size_t); +TransGenFun4* g_trans_gen4_trampoline{nullptr}; +// generic transition hook +void trans_gen(ThemeInfo* theme) +{ + push_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); + OnScopeExit pop{[] + { pop_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); }}; + + if (pre_level_generation()) + return; + g_trans_gen_trampoline(theme); + post_level_generation(); +} +// cosmic transition hook +void trans_gen2(ThemeInfo* theme) +{ + push_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); + OnScopeExit pop{[] + { pop_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); }}; + + if (pre_level_generation()) + return; + g_trans_gen2_trampoline(theme); + post_level_generation(); +} +// cog-duat transition hook +void trans_gen3(size_t a, size_t b, ThemeInfo* theme) +{ + push_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); + OnScopeExit pop{[] + { pop_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); }}; + + auto state = State::get().ptr(); + // trampoline will call the generic trans_gen if not going to duat + if (state->theme_next == 12 && pre_level_generation()) + return; + g_trans_gen3_trampoline(a, b, theme); + if (state->theme_next == 12) + post_level_generation(); +} +// olmecship transition hook +void trans_gen4(size_t a, size_t b, size_t c, size_t d, size_t e, size_t f) +{ + push_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); + OnScopeExit pop{[] + { pop_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_GENERAL); }}; + + if (pre_level_generation()) + return; + g_trans_gen4_trampoline(a, b, c, d, e, f); + post_level_generation(); +} + using LoadScreenFun = void(StateMemory*, size_t, size_t); LoadScreenFun* g_load_screen_trampoline{nullptr}; void load_screen(StateMemory* state, size_t param_2, size_t param_3) @@ -857,6 +917,33 @@ void load_screen(StateMemory* state, size_t param_2, size_t param_3) post_load_screen(); } +using UnloadLayerFun = void(Layer*); +UnloadLayerFun* g_unload_layer_trampoline{nullptr}; +void unload_layer(Layer* layer) +{ + if (!layer->is_back_layer && pre_unload_level()) + return; + if (pre_unload_layer((LAYER)layer->is_back_layer)) + return; + g_unload_layer_trampoline(layer); + post_unload_layer((LAYER)layer->is_back_layer); + if (layer->is_back_layer) + post_unload_level(); +} + +using InitLayerFun = void(Layer*); +InitLayerFun* g_init_layer_trampoline{nullptr}; +void load_layer(Layer* layer) +{ + if (!layer->is_back_layer) + pre_init_level(); + pre_init_layer((LAYER)layer->is_back_layer); + g_init_layer_trampoline(layer); + post_init_layer((LAYER)layer->is_back_layer); + if (layer->is_back_layer) + post_init_level(); +} + using HandleTileCodeFun = void(LevelGenSystem*, std::uint32_t, std::uint64_t, float, float, std::uint8_t); HandleTileCodeFun* g_handle_tile_code_trampoline{nullptr}; void handle_tile_code(LevelGenSystem* self, std::uint32_t tile_code, std::uint16_t room_template, float x, float y, std::uint8_t layer) @@ -1471,6 +1558,12 @@ void LevelGenData::init() g_spawn_room_from_tile_codes_trampoline = (SpawnRoomFromTileCodes*)get_address("level_gen_spawn_room_from_tile_codes"sv); g_load_screen_trampoline = (LoadScreenFun*)get_address("load_screen_func"sv); + g_unload_layer_trampoline = (UnloadLayerFun*)get_address("unload_layer"sv); + g_init_layer_trampoline = (InitLayerFun*)get_address("init_layer"sv); + g_trans_gen_trampoline = (TransGenFun*)get_address("spawn_transition"sv); + g_trans_gen2_trampoline = (TransGenFun*)get_address("spawn_transition_cosmic"sv); + g_trans_gen3_trampoline = (TransGenFun3*)get_address("spawn_transition_duat"sv); + g_trans_gen4_trampoline = (TransGenFun4*)get_address("spawn_transition_olmecship"sv); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); @@ -1486,6 +1579,12 @@ void LevelGenData::init() DetourAttach((void**)&g_spawn_room_from_tile_codes_trampoline, spawn_room_from_tile_codes); DetourAttach((void**)&g_load_screen_trampoline, load_screen); + DetourAttach((void**)&g_unload_layer_trampoline, unload_layer); + DetourAttach((void**)&g_init_layer_trampoline, load_layer); + DetourAttach((void**)&g_trans_gen_trampoline, trans_gen); + DetourAttach((void**)&g_trans_gen2_trampoline, trans_gen2); + DetourAttach((void**)&g_trans_gen3_trampoline, trans_gen3); + DetourAttach((void**)&g_trans_gen4_trampoline, trans_gen4); const LONG error = DetourTransactionCommit(); if (error != NO_ERROR) @@ -1798,6 +1897,12 @@ void LevelGenSystem::populate_level_hook(ThemeInfo* self, uint64_t param_2, uint original(self, param_2, param_3, param_4); } +void LevelGenSystem::populate_transition_hook(ThemeInfo* self, PopulateTransitionFun* original) +{ + pre_level_generation(); + original(self); + post_level_generation(); +} void LevelGenSystem::do_procedural_spawn_hook(ThemeInfo* self, SpawnInfo* spawn_info, DoProceduralSpawnFun* original) { push_spawn_type_flags(SPAWN_TYPE_LEVEL_GEN_PROCEDURAL); @@ -1950,6 +2055,15 @@ LevelChanceDef& get_or_emplace_level_chance(game_unordered_mapvalue.second; } +std::optional LevelGenSystem::get_procedural_spawn_chance_name(uint32_t chance_id) +{ + if (g_monster_chance_id_to_name.contains(chance_id)) + return g_monster_chance_id_to_name[chance_id]; + if (g_trap_chance_id_to_name.contains(chance_id)) + return g_trap_chance_id_to_name[chance_id]; + return std::nullopt; +} + uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) { if (g_monster_chance_id_to_name.contains(chance_id)) @@ -1958,14 +2072,18 @@ uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) if (!this_chances.chances.empty()) { auto* state = State::get().ptr(); - if (this_chances.chances.size() >= state->level) + if (this_chances.chances.size() >= state->level && state->level > 0) { - return this_chances.chances[state->level]; + return this_chances.chances[state->level - 1]; } - else + else if (this_chances.chances.size() == 1) { return this_chances.chances[0]; } + else + { + return 0; + } } } @@ -1975,14 +2093,18 @@ uint32_t LevelGenSystem::get_procedural_spawn_chance(uint32_t chance_id) if (!this_chances.chances.empty()) { auto* state = State::get().ptr(); - if (this_chances.chances.size() >= state->level) + if (this_chances.chances.size() >= state->level && state->level > 0) { - return this_chances.chances[state->level]; + return this_chances.chances[state->level - 1]; } - else + else if (this_chances.chances.size() == 1) { return this_chances.chances[0]; } + else + { + return 0; + } } } diff --git a/src/game_api/level_api.hpp b/src/game_api/level_api.hpp index 87d40f9e4..ab8108d36 100644 --- a/src/game_api/level_api.hpp +++ b/src/game_api/level_api.hpp @@ -423,12 +423,17 @@ struct LevelGenSystem { hook_impl.template hook(theme, &populate_level_hook); hook_impl.template hook(theme, &do_procedural_spawn_hook); + // this didn't work right + // hook_impl.template hook(theme, &populate_transition_hook); } } using PopulateLevelFun = void(ThemeInfo*, uint64_t, uint64_t, uint64_t); static void populate_level_hook(ThemeInfo*, uint64_t, uint64_t, uint64_t, PopulateLevelFun*); + using PopulateTransitionFun = void(ThemeInfo*); + static void populate_transition_hook(ThemeInfo*, PopulateTransitionFun*); + using DoProceduralSpawnFun = void(ThemeInfo*, SpawnInfo*); static void do_procedural_spawn_hook(ThemeInfo*, SpawnInfo*, DoProceduralSpawnFun*); @@ -531,7 +536,7 @@ struct LevelGenSystem bool set_shop_type(uint32_t x, uint32_t y, uint8_t l, ShopType shop_type); std::string_view get_room_template_name(uint16_t room_template); - + std::optional get_procedural_spawn_chance_name(uint32_t chance_id); uint32_t get_procedural_spawn_chance(uint32_t chance_id); bool set_procedural_spawn_chance(uint32_t chance_id, uint32_t inverse_chance); diff --git a/src/game_api/movable.hpp b/src/game_api/movable.hpp index 06b8d257a..be63d573b 100644 --- a/src/game_api/movable.hpp +++ b/src/game_api/movable.hpp @@ -14,7 +14,7 @@ class CutsceneBehavior { public: virtual ~CutsceneBehavior(){}; - virtual void update() = 0; + virtual void update(Movable* e) = 0; // no more virtuals, it's possible that different sub classes have some extra variables as well }; @@ -24,7 +24,7 @@ class Movable : public Entity custom_map behaviors_map; custom_set behaviors; MovableBehavior* current_behavior; - CutsceneBehavior* ic8; + CutsceneBehavior* cutscene_behavior; union { /// {movex, movey} diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 81f3cb34b..1aac6cecc 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1882,3 +1882,69 @@ int32_t add_money_slot(int32_t amount, uint8_t player_slot, std::optionalmoney.timer = display_time.value_or(0x3C); return get_current_money(); } + +void destroy_layer(uint8_t layer) +{ + static size_t offset = 0; + if (offset == 0) + { + offset = get_address("unload_layer"); + } + if (offset != 0) + { + auto state = State::get().ptr(); + for (auto i = 0; i < MAX_PLAYERS; ++i) + { + if (state->items->players[i] && state->items->players[i]->layer == layer) + state->items->players[i] = nullptr; + } + auto* layer_ptr = State::get().layer(layer); + typedef void destroy_func(Layer*); + static destroy_func* df = (destroy_func*)(offset); + df(layer_ptr); + } +} + +void destroy_level() +{ + destroy_layer(0); + destroy_layer(1); +} + +void create_layer(uint8_t layer) +{ + static size_t offset = 0; + if (offset == 0) + { + offset = get_address("init_layer"); + } + if (offset != 0) + { + auto* layer_ptr = State::get().layer(layer); + typedef void init_func(Layer*); + static init_func* ilf = (init_func*)(offset); + ilf(layer_ptr); + } +} + +void create_level() +{ + create_layer(0); + create_layer(1); +} + +void set_death_enabled(bool enable) +{ + static size_t offset = 0; + if (offset == 0) + { + offset = get_address("dead_players"); + } + if (offset != 0) + { + if (!enable) + write_mem_recoverable("death_disable", offset, "\xC3\x90"sv, true); + else + recover_mem("death_disable"); + } +} diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index 1be7ebbf0..106ea1df2 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -133,3 +133,8 @@ ENT_TYPE add_custom_type(std::vector types); int32_t get_current_money(); 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); +void destroy_layer(uint8_t layer); +void destroy_level(); +void create_layer(uint8_t layer); +void create_level(); +void set_death_enabled(bool enable); diff --git a/src/game_api/script/events.cpp b/src/game_api/script/events.cpp index ff474b3a5..13bc37131 100644 --- a/src/game_api/script/events.cpp +++ b/src/game_api/script/events.cpp @@ -16,6 +16,8 @@ class JournalPage; struct AABB; +auto g_level_loaded = false; + void pre_load_level_files() { LuaBackend::for_each_backend( @@ -25,14 +27,16 @@ void pre_load_level_files() return true; }); } -void pre_level_generation() +bool pre_level_generation() { + bool block{false}; LuaBackend::for_each_backend( [&](LuaBackend::LockedBackend backend) { - backend->pre_level_generation(); - return true; + block = backend->pre_level_generation(); + return !block; }); + return block; } bool pre_load_screen() { @@ -72,6 +76,55 @@ bool pre_load_screen() return block; } +bool pre_unload_level() +{ + bool block{false}; + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + block = backend->pre_unload_level(); + return !block; + }); + if (!block) + g_level_loaded = false; + return block; +} +bool pre_init_level() +{ + bool block{false}; + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + block = backend->pre_init_level(); + return !block; + }); + if (!block) + g_level_loaded = true; + return block; +} +bool pre_unload_layer(LAYER layer) +{ + bool block{false}; + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + block = backend->pre_unload_layer(layer); + return !block; + }); + return block; +} +bool pre_init_layer(LAYER layer) +{ + bool block{false}; + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + block = backend->pre_init_layer(layer); + return !block; + }); + return block; +} + void post_room_generation() { LuaBackend::for_each_backend( @@ -90,6 +143,24 @@ void post_level_generation() return true; }); } +void post_init_layer(LAYER layer) +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->post_init_layer(layer); + return true; + }); +} +void post_init_level() +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->post_init_level(); + return true; + }); +} void post_load_screen() { LuaBackend::for_each_backend( @@ -99,6 +170,24 @@ void post_load_screen() return true; }); } +void post_unload_level() +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->post_unload_level(); + return true; + }); +} +void post_unload_layer(LAYER layer) +{ + LuaBackend::for_each_backend( + [&](LuaBackend::LockedBackend backend) + { + backend->post_unload_layer(layer); + return true; + }); +} void on_death_message(STRINGID stringid) { LuaBackend::for_each_backend( diff --git a/src/game_api/script/events.hpp b/src/game_api/script/events.hpp index ad3e06581..fff659bcd 100644 --- a/src/game_api/script/events.hpp +++ b/src/game_api/script/events.hpp @@ -16,11 +16,21 @@ struct HudData; struct Hud; void pre_load_level_files(); -void pre_level_generation(); +bool pre_level_generation(); bool pre_load_screen(); +bool pre_init_level(); +bool pre_init_layer(LAYER layer); +bool pre_unload_level(); +bool pre_unload_layer(LAYER layer); + void post_room_generation(); void post_level_generation(); void post_load_screen(); +void post_init_level(); +void post_init_layer(LAYER layer); +void post_unload_level(); +void post_unload_layer(LAYER layer); + void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); bool pre_set_feat(FEAT feat); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index 1d4b432de..c783c3f13 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -752,10 +752,10 @@ void LuaBackend::pre_load_level_files() } } } -void LuaBackend::pre_level_generation() +bool LuaBackend::pre_level_generation() { if (!get_enabled()) - return; + return false; auto now = get_frame_count(); @@ -767,11 +767,62 @@ void LuaBackend::pre_level_generation() if (callback.screen == ON::PRE_LEVEL_GENERATION) { set_current_callback(-1, id, CallbackType::Normal); - handle_function(this, callback.func); + auto return_value = handle_function(this, callback.func).value_or(false); + clear_current_callback(); + callback.lastRan = now; + if (return_value) + return return_value; + } + } + return false; +} +bool LuaBackend::pre_init_level() +{ + if (!get_enabled()) + return false; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::PRE_LEVEL_CREATION) + { + set_current_callback(-1, id, CallbackType::Normal); + auto return_value = handle_function(this, callback.func).value_or(false); + clear_current_callback(); + callback.lastRan = now; + if (return_value) + return return_value; + } + } + return false; +} +bool LuaBackend::pre_init_layer(LAYER layer) +{ + if (!get_enabled()) + return false; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::PRE_LAYER_CREATION) + { + set_current_callback(-1, id, CallbackType::Normal); + auto return_value = handle_function(this, callback.func, layer).value_or(false); clear_current_callback(); callback.lastRan = now; + if (return_value) + return return_value; } } + return false; } bool LuaBackend::pre_load_screen() { @@ -866,6 +917,58 @@ bool LuaBackend::pre_load_screen() return false; } + +bool LuaBackend::pre_unload_level() +{ + if (!get_enabled()) + return false; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::PRE_LEVEL_DESTRUCTION) + { + set_current_callback(-1, id, CallbackType::Normal); + auto return_value = handle_function(this, callback.func).value_or(false); + clear_current_callback(); + callback.lastRan = now; + if (return_value) + return return_value; + } + } + + return false; +} +bool LuaBackend::pre_unload_layer(LAYER layer) +{ + if (!get_enabled()) + return false; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::PRE_LAYER_DESTRUCTION) + { + set_current_callback(-1, id, CallbackType::Normal); + auto return_value = handle_function(this, callback.func, layer).value_or(false); + clear_current_callback(); + callback.lastRan = now; + if (return_value) + return return_value; + } + } + + return false; +} + void LuaBackend::post_room_generation() { if (!get_enabled()) @@ -957,6 +1060,48 @@ void LuaBackend::post_level_generation() } } } +void LuaBackend::post_init_level() +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::POST_LEVEL_CREATION) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func); + clear_current_callback(); + callback.lastRan = now; + } + } +} +void LuaBackend::post_init_layer(LAYER layer) +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::POST_LAYER_CREATION) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func, layer); + clear_current_callback(); + callback.lastRan = now; + } + } +} void LuaBackend::post_load_screen() { if (!get_enabled()) @@ -984,6 +1129,49 @@ void LuaBackend::post_load_screen() } } } +void LuaBackend::post_unload_level() +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::POST_LEVEL_DESTRUCTION) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func); + clear_current_callback(); + callback.lastRan = now; + } + } +} +void LuaBackend::post_unload_layer(LAYER layer) +{ + if (!get_enabled()) + return; + + auto now = get_frame_count(); + + for (auto& [id, callback] : callbacks) + { + if (is_callback_cleared(id)) + continue; + + if (callback.screen == ON::POST_LAYER_DESTRUCTION) + { + set_current_callback(-1, id, CallbackType::Normal); + handle_function(this, callback.func, layer); + clear_current_callback(); + callback.lastRan = now; + } + } +} + void LuaBackend::on_death_message(STRINGID stringid) { if (!get_enabled()) diff --git a/src/game_api/script/lua_backend.hpp b/src/game_api/script/lua_backend.hpp index 15e5ed89e..835cd8168 100644 --- a/src/game_api/script/lua_backend.hpp +++ b/src/game_api/script/lua_backend.hpp @@ -119,7 +119,15 @@ enum class ON PRE_SET_FEAT, PRE_UPDATE, POST_UPDATE, - USER_DATA + USER_DATA, + PRE_LEVEL_CREATION, + POST_LEVEL_CREATION, + PRE_LAYER_CREATION, + POST_LAYER_CREATION, + PRE_LEVEL_DESTRUCTION, + POST_LEVEL_DESTRUCTION, + PRE_LAYER_DESTRUCTION, + POST_LAYER_DESTRUCTION, }; struct IntOption @@ -360,11 +368,21 @@ class LuaBackend void post_tile_code(std::string_view tile_code, float x, float y, int layer, uint16_t room_template); void pre_load_level_files(); - void pre_level_generation(); + bool pre_level_generation(); bool pre_load_screen(); + bool pre_init_level(); + bool pre_init_layer(LAYER layer); + bool pre_unload_level(); + bool pre_unload_layer(LAYER layer); + void post_room_generation(); void post_level_generation(); void post_load_screen(); + void post_init_level(); + void post_init_layer(LAYER layer); + void post_unload_level(); + void post_unload_layer(LAYER layer); + void on_death_message(STRINGID stringid); std::optional pre_get_feat(FEAT feat); bool pre_set_feat(FEAT feat); diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 75e73a937..4d339c862 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -96,6 +96,7 @@ #include "usertypes/texture_lua.hpp" // for register_usertypes #include "usertypes/vanilla_render_lua.hpp" // for VanillaRenderContext #include "usertypes/vtables_lua.hpp" // for register_usertypes +#include "virtual_table.hpp" struct Illumination; @@ -1893,13 +1894,16 @@ end /// Disable all crust item spawns, returns whether they were already disabled before the call lua["disable_floor_embeds"] = disable_floor_embeds; - /// Get the address for a pattern name - lua["get_address"] = get_address; + /// 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)); + }; - /// Get the rva for a pattern name - lua["get_rva"] = [](std::string_view address_name) -> size_t + /// 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 get_address(address_name) - Memory::get().at_exe(0); + return fmt::format("{:x}", get_virtual_function_address(offset, index)); }; /// Log to spelunky.log @@ -2120,6 +2124,53 @@ end /// Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction lua["add_money_slot"] = add_money_slot; + /// 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. + lua["destroy_level"] = destroy_level; + + /// Destroys a layer and all entities in it. + lua["destroy_layer"] = destroy_layer; + + /// Initializes an empty front and back layer that don't currently exist. Does nothing(?) if layers already exist. + lua["create_level"] = create_level; + + /// 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_death_enabled; + + /// Converts INPUTS to (x, y, BUTTON) + lua["inputs_to_buttons"] = [](INPUTS inputs) -> std::tuple + { + float x = 0; + float y = 0; + if (inputs & 0x100) + x = -1; + else if (inputs & 0x200) + x = 1; + if (inputs & 0x400) + y = 1; + else if (inputs & 0x800) + y = -1; + BUTTON buttons = (BUTTON)(inputs & 0x3f); + return std::make_tuple(x, y, buttons); + }; + + /// Converts (x, y, BUTTON) to INPUTS + lua["buttons_to_inputs"] = [](float x, float y, BUTTON buttons) -> INPUTS + { + INPUTS inputs = buttons; + if (x < 0) + inputs |= 0x100; + else if (x > 0) + inputs |= 0x200; + if (y > 0) + inputs |= 0x400; + else if (y < 0) + inputs |= 0x800; + return inputs; + }; + lua.create_named_table("INPUTS", "NONE", 0, "JUMP", 1, "WHIP", 2, "BOMB", 4, "ROPE", 8, "RUN", 16, "DOOR", 32, "MENU", 64, "JOURNAL", 128, "LEFT", 256, "RIGHT", 512, "UP", 1024, "DOWN", 2048); lua.create_named_table( @@ -2274,7 +2325,24 @@ end "POST_UPDATE", ON::POST_UPDATE, "USER_DATA", - ON::USER_DATA); + ON::USER_DATA, + "PRE_LEVEL_CREATION", + ON::PRE_LEVEL_CREATION, + "POST_LEVEL_CREATION", + ON::POST_LEVEL_CREATION, + "PRE_LAYER_CREATION", + ON::PRE_LAYER_CREATION, + "POST_LAYER_CREATION", + ON::POST_LAYER_CREATION, + "PRE_LEVEL_DESTRUCTION", + ON::PRE_LEVEL_DESTRUCTION, + "POST_LEVEL_DESTRUCTION", + ON::POST_LEVEL_DESTRUCTION, + "PRE_LAYER_DESTRUCTION", + ON::PRE_LAYER_DESTRUCTION, + "POST_LAYER_DESTRUCTION", + ON::POST_LAYER_DESTRUCTION); + /* ON // LOGO // Runs when entering the the mossmouth logo screen. @@ -2351,7 +2419,7 @@ end // Params: PreLoadLevelFilesContext load_level_ctx // Runs right before level files would be loaded // PRE_LEVEL_GENERATION - // Runs before any level generation, no entities should exist at this point + // Runs before any level generation, no entities should exist at this point. Does not work in all level-like screens. Return true to stop normal level generation. // POST_ROOM_GENERATION // Params: PostRoomGenerationContext room_gen_ctx // Runs right after all rooms are generated before entities are spawned @@ -2486,6 +2554,26 @@ end // USER_DATA // Params: Entity ent // Runs on all changes to Entity.user_data, including after loading saved user_data in the next level and transition. Also runs the first time user_data is set back to nil, but nil won't be saved to bother you on future levels. + // PRE_LEVEL_CREATION + // Runs right before the front layer is created. Runs in all screens that usually have entities, or when creating a layer manually. + // POST_LEVEL_CREATION + // Runs right after the back layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually. + // PRE_LAYER_CREATION + // Params: LAYER layer + // Runs right before a layer is created. Runs in all screens that usually have entities, or when creating a layer manually. + // POST_LAYER_CREATION + // Params: LAYER layer + // Runs right after a layer has been created and you can start spawning entities in it. Runs in all screens that usually have entities, or when creating a layer manually. + // PRE_LEVEL_DESTRUCTION + // Runs right before the current level is unloaded and any entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last. + // POST_LEVEL_DESTRUCTION + // Runs right after the current level has been unloaded and all entities destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last. + // PRE_LAYER_DESTRUCTION + // Params: LAYER layer + // Runs right before a layer is unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last. + // POST_LAYER_DESTRUCTION + // Params: LAYER layer + // Runs right after a layer has been unloaded and any entities there destroyed. Runs in pretty much all screens, even ones without entities. The screen has already changed at this point, meaning the screen being destoyed is in state.screen_last. */ lua.create_named_table( diff --git a/src/game_api/script/usertypes/entity_lua.cpp b/src/game_api/script/usertypes/entity_lua.cpp index eebc75d0c..a8aaec46e 100644 --- a/src/game_api/script/usertypes/entity_lua.cpp +++ b/src/game_api/script/usertypes/entity_lua.cpp @@ -12,15 +12,17 @@ #include // for min, max, swap, pair #include // for _Vector_iterator, vector, _Vector_... -#include "color.hpp" // for Color, Color::a, Color::b, Color::g -#include "custom_types.hpp" // for get_custom_types_map -#include "entities_chars.hpp" // for Player -#include "entity.hpp" // for Entity, EntityDB, Animation, Rect -#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 "script/lua_backend.hpp" // for LuaBackend +#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 "entities_chars.hpp" // for Player +#include "entity.hpp" // for Entity, EntityDB, Animation, Rect +#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 "script/lua_backend.hpp" // for LuaBackend +#include "script/safe_cb.hpp" // for make_safe_cb namespace NEntity { @@ -356,6 +358,15 @@ void register_usertypes(sol::state& lua) movable_type["set_gravity"] = &Movable::set_gravity; movable_type["reset_gravity"] = &Movable::reset_gravity; movable_type["set_position"] = &Movable::set_position; + movable_type["process_input"] = &Movable::process_input; + movable_type["cutscene"] = sol::readonly(&Movable::cutscene_behavior); + movable_type["clear_cutscene"] = [](Movable& movable) + { + delete movable.cutscene_behavior; + movable.cutscene_behavior = nullptr; + }; + + lua.new_usertype("CutsceneBehavior", sol::no_constructor); lua["Entity"]["as_entity"] = &Entity::as; lua["Entity"]["as_movable"] = &Entity::as; diff --git a/src/game_api/script/usertypes/vtables_lua.cpp b/src/game_api/script/usertypes/vtables_lua.cpp index 21fdb59c6..5cb5f67f7 100644 --- a/src/game_api/script/usertypes/vtables_lua.cpp +++ b/src/game_api/script/usertypes/vtables_lua.cpp @@ -114,6 +114,9 @@ void register_usertypes(sol::state& lua) { Entity* ent = get_entity_ptr(uid); entity_vtable.unhook(ent, callback_id); + movable_vtable.unhook(ent, callback_id); + floor_vtable.unhook(ent, callback_id); + door_vtable.unhook(ent, callback_id); }); HookHandler::set_hook_dtor_impl( @@ -150,6 +153,9 @@ void register_usertypes(sol::state& lua) [](Entity* ent, std::uint32_t callback_id) { entity_vtable.unhook(ent, callback_id); + movable_vtable.unhook(ent, callback_id); + floor_vtable.unhook(ent, callback_id); + door_vtable.unhook(ent, callback_id); }); } }; // namespace NVTables diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 25951906c..e95dcd194 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -2051,6 +2051,64 @@ std::unordered_map g_address_rules{ .offset(0x5) .at_exe(), }, + { + "unload_layer"sv, + // bp on destroy entity, leave level, it's third in stack or something + PatternCommandBuffer{} + .find_inst("49 89 cc 8b 41 18 85 c0 74 0b 49 8b 74 24 08"_gh) + .at_exe() + .function_start(), + }, + { + "init_layer"sv, + // called a lot in load_screen, for both layers in every screen that has layers + PatternCommandBuffer{} + .find_inst("48 8d 7e 40 c7 44 24 2c 00 01 00 00"_gh) + .at_exe() + .function_start(), + //.from_exe_base(0x228b58f0), + }, + { + "dead_players"sv, + // I guess it writes 14 to screen_next before the death screen pops up. Apparently it's a SCREEN_LEVEL virtual too. + PatternCommandBuffer{} + .find_inst("4c 8b b8 e8 12 00 00 48 8b 80 f0 12 00 00"_gh) + .at_exe() + .function_start(), + //.from_exe_base(0x22c061d0), + }, + { + "spawn_transition"sv, + // These functions are hooked separately cause hooking the vtable just didn't work right for POST_LEVEL_GENERATION + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_DWELLING, VIRT_FUNC::THEME_SPAWN_TRANSITION) + .at_exe(), + //.from_exe_base(0x22afe5c0), + }, + { + "spawn_transition_cosmic"sv, + // These functions are hooked separately cause hooking the vtable just didn't work right for POST_LEVEL_GENERATION + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_COSMICOCEAN, VIRT_FUNC::THEME_SPAWN_TRANSITION) + .at_exe(), + //.from_exe_base(0x22b373b0), + }, + { + "spawn_transition_duat"sv, + // These functions are hooked separately cause hooking the vtable just didn't work right for POST_LEVEL_GENERATION + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_CITY_OF_GOLD, VIRT_FUNC::THEME_SPAWN_TRANSITION) + .at_exe(), + //.from_exe_base(0x22b34940), + }, + { + "spawn_transition_olmecship"sv, + // These functions are hooked separately cause hooking the vtable just didn't work right for POST_LEVEL_GENERATION + PatternCommandBuffer{} + .get_virtual_function_address(VTABLE_OFFSET::THEME_BASECAMP, VIRT_FUNC::THEME_SPAWN_TRANSITION) + .at_exe(), + //.from_exe_base(0x22b2d350), + }, }; std::unordered_map g_cached_addresses; diff --git a/src/game_api/spawn_api.cpp b/src/game_api/spawn_api.cpp index 428f1d554..7cf829e97 100644 --- a/src/game_api/spawn_api.cpp +++ b/src/game_api/spawn_api.cpp @@ -662,18 +662,40 @@ void init_spawn_hooks() } } -void spawn_player(int8_t player_slot, float x, float y) +int32_t spawn_player(int8_t player_slot, std::optional x, std::optional y, std::optional layer) { if (player_slot < 1 || player_slot > 4) - return; + return -1; + auto state = State::get().ptr(); + 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; + if (state->items->player_count < player_slot) + state->items->player_count = player_slot; + slot.activated = true; push_spawn_type_flags(SPAWN_TYPE_SCRIPT); OnScopeExit pop{[] { pop_spawn_type_flags(SPAWN_TYPE_SCRIPT); }}; - using spawn_player_fun = void(Items*, uint8_t ps, float pos_x, float pos_y); + auto old_x = state->level_gen->spawn_x; + auto old_y = state->level_gen->spawn_y; + state->level_gen->spawn_x = x.value_or(old_x); + state->level_gen->spawn_y = y.value_or(old_y); + using spawn_player_fun = void(Items*, uint8_t ps); static auto spawn_player = (spawn_player_fun*)get_address("spawn_player"); - spawn_player(get_state_ptr()->items, player_slot - 1, x, y); + // move the back layer to front layer offset if spawning in back layer + if (layer.has_value() && layer.value() == LAYER::BACK) + std::swap(State::get().ptr()->layers[0], State::get().ptr()->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]); + state->level_gen->spawn_x = old_x; + state->level_gen->spawn_y = old_y; + auto player = state->items->player(player_slot - 1); + if (player) + return player->uid; + return -1; } int32_t spawn_companion(ENT_TYPE companion_type, float x, float y, LAYER layer) diff --git a/src/game_api/spawn_api.hpp b/src/game_api/spawn_api.hpp index 2fed2ba2c..b618399b0 100644 --- a/src/game_api/spawn_api.hpp +++ b/src/game_api/spawn_api.hpp @@ -49,7 +49,7 @@ void pop_spawn_type_flags(SPAWN_TYPE flags); void init_spawn_hooks(); -void spawn_player(int8_t player_slot, float x, float y); +int32_t spawn_player(int8_t player_slot, std::optional x, std::optional y, std::optional layer); 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 = 65); int32_t spawn_roomowner(ENT_TYPE owner_type, float x, float y, LAYER layer, int16_t room_template = -1); diff --git a/src/game_api/virtual_table.hpp b/src/game_api/virtual_table.hpp index 6d611fae1..8a4a3c728 100644 --- a/src/game_api/virtual_table.hpp +++ b/src/game_api/virtual_table.hpp @@ -25,6 +25,7 @@ enum class VIRT_FUNC ENTITY_COLLISION2 = 26, MOVABLE_DAMAGE = 48, LOGIC_PERFORM = 1, + THEME_SPAWN_TRANSITION = 21, }; enum class VTABLE_OFFSET diff --git a/src/injected/ui.cpp b/src/injected/ui.cpp index b5735dc31..f5cf8ef56 100644 --- a/src/injected/ui.cpp +++ b/src/injected/ui.cpp @@ -213,6 +213,7 @@ std::map default_keys{ {"speedhack_turbo", VK_PRIOR}, {"speedhack_slow", VK_NEXT}, {"toggle_uncapped_fps", OL_KEY_CTRL | OL_KEY_SHIFT | 'U'}, + {"respawn", OL_KEY_CTRL | 'R'}, //{ "", 0x }, }; @@ -267,7 +268,7 @@ std::vector g_selected_ids; bool set_focus_entity = false, set_focus_world = false, set_focus_zoom = false, set_focus_finder = false, set_focus_uid = false, scroll_to_entity = false, scroll_top = false, click_teleport = false, throw_held = false, paused = false, show_app_metrics = false, lock_entity = false, lock_player = false, freeze_last = false, freeze_level = false, freeze_total = false, hide_ui = false, - enable_noclip = false, load_script_dir = true, load_packs_dir = false, enable_camp_camera = true, enable_camera_bounds = true, freeze_quest_yang = false, freeze_quest_sisters = false, freeze_quest_horsing = false, freeze_quest_sparrow = false, freeze_quest_tusk = false, freeze_quest_beg = false, run_finder = false, in_menu = false, zooming = false, g_inv = false, edit_last_id = false, edit_achievements = false, peek_layer = false, pause_updates = true; + enable_noclip = false, load_script_dir = true, load_packs_dir = false, enable_camp_camera = true, enable_camera_bounds = true, freeze_quest_yang = false, freeze_quest_sisters = false, freeze_quest_horsing = false, freeze_quest_sparrow = false, freeze_quest_tusk = false, freeze_quest_beg = false, run_finder = false, in_menu = false, zooming = false, g_inv = false, edit_last_id = false, edit_achievements = false, peek_layer = false, pause_updates = true, death_disable = false; std::optional quest_yang_state, quest_sisters_state, quest_horsing_state, quest_sparrow_state, quest_tusk_state, quest_beg_state; Entity* g_entity = 0; Entity* g_held_entity = 0; @@ -1234,6 +1235,7 @@ void smart_delete(Entity* ent, bool unsafe = false) { static auto first_door = to_id("ENT_TYPE_FLOOR_DOOR_ENTRANCE"); static auto logical_door = to_id("ENT_TYPE_LOGICAL_DOOR"); + UI::safe_destroy(ent, unsafe); if ((ent->type->id >= first_door && ent->type->id <= first_door + 15) || ent->type->id == logical_door) { auto pos = ent->position(); @@ -1245,14 +1247,9 @@ void smart_delete(Entity* ent, bool unsafe = false) auto pos = ent->position(); auto layer = (LAYER)ent->layer; ENT_TYPE type = ent->type->id; - Callback cb = {g_state->time_total + 1, [pos, layer, type] - { - fix_decorations_at(std::round(pos.first), std::round(pos.second), layer); - UI::cleanup_at(std::round(pos.first), std::round(pos.second), layer, type); - }}; - callbacks.push_back(cb); + fix_decorations_at(std::round(pos.first), std::round(pos.second), layer); + UI::cleanup_at(std::round(pos.first), std::round(pos.second), layer, type); } - UI::safe_destroy(ent, unsafe); } void reset_windows() @@ -1334,7 +1331,7 @@ int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) if (to_spawn.name.find("ENT_TYPE_CHAR") != std::string::npos) { int spawned = UI::spawn_companion(to_spawn.id, cpos.first, cpos.second, LAYER::PLAYER, g_vx, g_vy); - if (!lock_entity && set_last) + if (!lock_entity && set_last && options["draw_hitboxes"]) g_last_id = spawned; return spawned; } @@ -1348,7 +1345,7 @@ int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) static const auto ana_spelunky = to_id("ENT_TYPE_CHAR_ANA_SPELUNKY"); auto spawned = UI::spawn_playerghost(ana_spelunky + (rand() % 19), cpos.first, cpos.second, LAYER::PLAYER, g_vx, g_vy); - if (!lock_entity && set_last) + if (!lock_entity && set_last && options["draw_hitboxes"]) g_last_id = spawned; return spawned; } @@ -1422,7 +1419,7 @@ int32_t spawn_entityitem(EntityItem to_spawn, bool s, bool set_last = true) ent->door_type = to_id("ENT_TYPE_FLOOR_DOOR_LAYER"); ent->platform_type = to_id("ENT_TYPE_FLOOR_DOOR_PLATFORM"); } - if (!lock_entity && set_last) + if (!lock_entity && set_last && options["draw_hitboxes"]) g_last_id = spawned; return spawned; } @@ -1890,8 +1887,6 @@ void quick_start(uint8_t screen, uint8_t world, uint8_t level, uint8_t theme) g_state->level_next = level; g_state->theme_next = theme; g_state->quest_flags = g_state->quest_flags | 1; - g_state->fadein = 1; - g_state->fadeout = 1; g_state->loading = 1; if (g_game_manager->main_menu_music) @@ -2296,6 +2291,44 @@ void warp_next_level(size_t num) } } +void respawn() +{ + if (g_state->screen != 11 && g_state->screen != 12) + { + if (g_state->screen > 11) + { + quick_start(12, g_state->world_start, g_state->level_start, g_state->theme_start); + } + else + { + quick_start(12, 1, 1, 1); + } + return; + } + for (int8_t i = 0; i < g_state->items->player_count; ++i) + { + auto found = false; + for (auto p : UI::get_players()) + { + if (p->inventory_ptr->player_slot == i) + { + found = true; + if (p->health == 0 || test_flag(p->flags, 29)) + { + p->health = 4; + p->flags = clr_flag(p->flags, 29); + p->set_behavior(1); + } + } + } + if (!found) + { + g_state->items->player_inventories[i].health = 4; + UI::spawn_player(i); + } + } +} + bool pressed(std::string keyname, WPARAM wParam) { if (keys.find(keyname) == keys.end() || (keys[keyname] & 0xff) == 0) @@ -2859,6 +2892,10 @@ bool process_keys(UINT nCode, WPARAM wParam, [[maybe_unused]] LPARAM lParam) g_engine_fps = 0; update_frametimes(); } + else if (pressed("respawn", wParam)) + { + respawn(); + } else if (pressed("toggle_godmode", wParam)) { options["god_mode"] = !options["god_mode"]; @@ -3519,6 +3556,66 @@ const char* theme_name(int theme) void render_narnia() { + if (submenu("Other game screens")) + { + int screen = -1; + ImGui::PushID("WarpSpecial"); + for (unsigned int i = 0; i < 21; ++i) + { + if ((i >= 5 && i <= 10)) + continue; + if (options["menu_ui"]) + { + if (ImGui::MenuItem(screen_names[i])) + screen = i; + } + else + { + if (i % 2) + ImGui::SameLine(ImGui::GetContentRegionAvail().x * 0.5f); + if (ImGui::Button(screen_names[i], ImVec2(ImGui::GetContentRegionMax().x * 0.5f, 0))) + screen = i; + } + } + endmenu(); + if (screen != -1) + { + if (screen == 14) + { + if (g_state->screen == 11 or g_state->screen == 12) + UI::load_death_screen(); + } + else if (g_state->screen != 12 && screen >= 11) + { + quick_start((uint8_t)screen, 1, 1, 1); + } + else + { + g_state->screen_next = screen; + g_state->loading = 1; + } + if (screen >= 16 && screen <= 18) + { + g_state->win_state = 1; + if (!g_state->end_spaceship_character) + g_state->end_spaceship_character = to_id("ENT_TYPE_CHAR_EGGPLANT_CHILD"); + } + if (screen == 19) + { + g_state->world_next = 8; + g_state->level_next = 99; + g_state->theme_next = 10; + if (!g_state->level_gen->theme_cosmicocean->sub_theme) + g_state->level_gen->theme_cosmicocean->sub_theme = g_state->level_gen->theme_dwelling; + g_state->current_theme = g_state->level_gen->theme_cosmicocean; + g_state->win_state = 3; + if (g_state->level_count < 1) + g_state->level_count = 1; + } + } + ImGui::PopID(); + } + ImGui::Text("Next level"); ImGui::SameLine(100.0f); @@ -4731,21 +4828,26 @@ void render_clickhandler() } } + static auto front_col = ImColor(0, 255, 51, 200); + static auto back_col = ImColor(255, 160, 31, 200); + static auto front_fill = ImColor(front_col); + front_fill.Value.w = 0.25f; + static auto back_fill = ImColor(back_col); + back_fill.Value.w = 0.25f; if (update_entity()) { - render_hitbox(g_entity, true, ImColor(0, 255, 0, 200)); + auto this_layer = (peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer) == g_entity->layer; + render_hitbox(g_entity, true, this_layer ? front_col : back_col); } - static auto front_col = ImColor(0, 255, 51, 100); - static auto back_col = ImColor(255, 160, 31, 100); for (auto entity : g_selected_ids) { auto ent = get_entity_ptr(entity); if (ent) { if (ent->layer == (peek_layer ? g_state->camera_layer ^ 1 : g_state->camera_layer)) - render_hitbox(ent, false, front_col, true); + render_hitbox(ent, false, front_fill, true); else - render_hitbox(ent, false, back_col, true); + render_hitbox(ent, false, back_fill, true); } } @@ -5465,6 +5567,11 @@ void render_options() UI::godmode_companions(options["god_mode_companions"]); } tooltip("Make the hired hands completely deathproof."); + if (ImGui::Checkbox("Disable death screen##NoDeath", &death_disable)) + { + UI::death_enabled(!death_disable); + } + tooltip("Disable the death screen from popping up for any reason."); if (ImGui::Checkbox("Noclip##Noclip", &options["noclip"])) { toggle_noclip(); @@ -7929,6 +8036,8 @@ void render_game_props() } if (submenu("Players")) { + if (ImGui::MenuItem("Respawn dead players")) + respawn(); ImGui::TextWrapped("New players spawned here can't be controlled, but can be used to test some things that require multiple players."); if (ImGui::SliderScalar("Number of players##SetNumPlayers", ImGuiDataType_U8, &g_state->items->player_count, &u8_one, &u8_four, "%d", ImGuiSliderFlags_AlwaysClamp)) { @@ -7962,7 +8071,7 @@ void render_game_props() { g_state->items->player_inventories[i].health = 4; auto uid = g_state->next_entity_uid; - UI::spawn_player(i, spawn_x, spawn_y); + UI::spawn_player(i); auto player = get_entity_ptr(uid)->as(); player->set_position(spawn_x, spawn_y); } @@ -8011,6 +8120,85 @@ void render_game_props() } endmenu(); } + if (submenu("Level generation flags")) + { + auto flags = (int)g_state->level_gen->flags; + auto flags2 = (int)g_state->level_gen->flags2; + auto flags3 = (int)g_state->level_gen->flags3; + ImGui::SeparatorText("Flags 1"); + for (int i = 0; i < 8; i++) + { + ImGui::CheckboxFlags(levelgen_flags[i], &flags, (int)std::pow(2, i)); + } + ImGui::SeparatorText("Flags 2"); + for (int i = 0; i < 8; i++) + { + ImGui::CheckboxFlags(levelgen_flags2[i], &flags2, (int)std::pow(2, i)); + } + ImGui::SeparatorText("Flags 3"); + for (int i = 0; i < 8; i++) + { + ImGui::CheckboxFlags(levelgen_flags3[i], &flags3, (int)std::pow(2, i)); + } + g_state->level_gen->flags = (uint8_t)flags; + g_state->level_gen->flags2 = (uint8_t)flags2; + g_state->level_gen->flags3 = (uint8_t)flags3; + if (g_state->current_theme) + { + ImGui::SeparatorText("Theme flags"); + ImGui::Checkbox("Allow beehives##ThemeBeeHive", &g_state->current_theme->allow_beehive); + ImGui::Checkbox("Allow leprechauns##ThemeLeprechaun", &g_state->current_theme->allow_leprechaun); + } + endmenu(); + } + if (submenu("Procedural chances")) + { + static auto hide_zero = true; + ImGui::Checkbox("Hide 0% chances", &hide_zero); + static auto render_procedural_chance = [](uint32_t id, LevelChanceDef& def) + { + int inverse_chance = g_state->level_gen->get_procedural_spawn_chance(id); + std::string name = std::string(g_state->level_gen->get_procedural_spawn_chance_name(id).value_or(fmt::format("{}", id))); + if (def.chances.empty() || (hide_zero && inverse_chance == 0)) + return; + float chance = inverse_chance > 0 ? 100.f / static_cast(inverse_chance) : 0; + std::string all = fmt::format("{}", def.chances[0]); + for (auto i = 1; i < def.chances.size(); ++i) + all += "," + fmt::format("{}", def.chances[i]); + std::string str = fmt::format("{:.3f}% ({})", chance, all); + ImGui::Text("%s", name.c_str()); + auto w = ImGui::GetItemRectSize().x; + ImGui::SameLine(std::max(0.5f * ImGui::GetContentRegionMax().x, w), 4.f); + ImGui::Text("%s", str.c_str()); + }; + + static auto render_chance = [](int inverse_chance, const char* name) + { + if (hide_zero && inverse_chance == 0) + return; + float chance = inverse_chance > 0 ? 100.f / static_cast(inverse_chance) : 0; + std::string str = fmt::format("{:.3f}%", chance); + ImGui::Text("%s", name); + auto w = ImGui::GetItemRectSize().x; + ImGui::SameLine(std::max(0.5f * ImGui::GetContentRegionMax().x, w), 4.f); + ImGui::Text("%s", str.c_str()); + }; + + ImGui::SeparatorText("Monster chances"); + for (auto [id, def] : g_state->level_gen->data->level_monster_chances) + render_procedural_chance(id, def); + + ImGui::SeparatorText("Trap chances"); + for (auto [id, def] : g_state->level_gen->data->level_trap_chances) + render_procedural_chance(id, def); + + ImGui::SeparatorText("Level chances"); + if (g_state->current_theme) + render_chance(g_state->current_theme->get_shop_chance(), "shop"); + for (auto i = 0; i < 15; ++i) + render_chance(g_state->level_gen->data->level_config[i], level_chances[i]); + endmenu(); + } if (submenu("AI targets")) { for (size_t x = 0; x < 8; ++x) diff --git a/src/injected/ui_util.cpp b/src/injected/ui_util.cpp index ed2ecbd98..2f073f953 100644 --- a/src/injected/ui_util.cpp +++ b/src/injected/ui_util.cpp @@ -36,6 +36,10 @@ void UI::godmode_companions(bool g) { State::get().godmode_companions(g); } +void UI::death_enabled(bool g) +{ + set_death_enabled(g); +} std::pair UI::click_position(float x, float y) { return State::click_position(x, y); @@ -700,10 +704,11 @@ 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 && test_flag(ent->flags, 3)) // solid floor + if (sf & 0x100) { - ent->destroy(); - update_liquid_collision_at(x, y, false); + if (test_flag(ent->flags, 3)) // solid floor + update_liquid_collision_at(x, y, false); + destroy_grid(ent->uid); } else if (ent->is_liquid()) { @@ -750,12 +755,17 @@ int32_t UI::spawn_playerghost(ENT_TYPE char_type, float x, float y, LAYER layer, return uid; } -void UI::spawn_player(uint8_t player_slot, float x, float y) +void UI::spawn_player(uint8_t player_slot, std::optional x, std::optional y, std::optional layer) { - ::spawn_player(player_slot + 1, x, y); + ::spawn_player(player_slot + 1, x, y, layer); } std::pair UI::spawn_position() { return {State::get().ptr()->level_gen->spawn_x, State::get().ptr()->level_gen->spawn_y}; } + +void UI::load_death_screen() +{ + call_death_screen(); +} diff --git a/src/injected/ui_util.hpp b/src/injected/ui_util.hpp index d5960d5d1..224cee8f2 100644 --- a/src/injected/ui_util.hpp +++ b/src/injected/ui_util.hpp @@ -37,6 +37,7 @@ class UI public: static void godmode(bool g); static void godmode_companions(bool g); + static void death_enabled(bool g); static std::pair click_position(float x, float y); static void zoom(float level); static uint32_t get_frame_count(); @@ -84,6 +85,7 @@ class UI static float get_spark_distance(SparkTrap* ent); static void save_progress(); static int32_t spawn_playerghost(ENT_TYPE char_type, float x, float y, LAYER layer, float vx, float vy); - static void spawn_player(uint8_t player_slot, float x, float y); + static void spawn_player(uint8_t player_slot, std::optional x = std::nullopt, std::optional y = std::nullopt, std::optional layer = std::nullopt); static std::pair spawn_position(); + static void load_death_screen(); };