Hook the sendto and recvfrom functions and start dumping network data to terminal
---Search script examples for get_address
-
Get the address for a pattern name
-Search script examples for get_rva
Get the rva for a pattern name
+Get the rva for a pattern name, used for debugging.
+++Search script examples for get_virtual_rva
+
Get the rva for a vtable offset and index, used for debugging.
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
andVINE_TREE_TOP
entities in a level,area
default is whole level,destroy_broken
default is falsehttp_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
++Search script examples for inputs_to_buttons
+
Converts INPUTS to (x, y, BUTTON)
Search script examples for intersection
@@ -4943,6 +5008,12 @@set_level_config
Set the value for the specified config
+++Search script examples for set_level_logic_enabled
+
Setting to false disables all player logic in SCREEN.LEVEL, mainly the death screen from popping up if all players are dead or missing, but also shop camera zoom and some other small things.
Search script examples for set_mask
@@ -5069,7 +5140,13 @@warp
Warp to a level immediately.
-++Search script examples for buttons_to_inputs
+
Converts (x, y, BUTTON) to INPUTS
+@@ -5215,7 +5292,20 @@Search script examples for get_io
++Search script examples for http_get
+
Send a synchronous HTTP GET request and return response as a string or nil on an error
+++Search script examples for http_get_async
+
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)
+@@ -5838,7 +5928,7 @@Search script examples for udp_listen
-Search script examples for spawn_player
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
uColor
+CutsceneBehavior
+
Type | +Name | +Description | +
---|
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
+ -
+ http_get
+
+ -
+ http_get_async
+
-
udp_listen
@@ -1684,6 +1705,9 @@
-
Color
+ -
+ CutsceneBehavior
+
-
Hud
@@ -3451,6 +3475,30 @@
-
ON.USER_DATA
+ -
+ ON.PRE_LEVEL_CREATION
+
+ -
+ ON.POST_LEVEL_CREATION
+
+ -
+ ON.PRE_LAYER_CREATION
+
+ -
+ ON.POST_LAYER_CREATION
+
+ -
+ ON.PRE_LEVEL_DESTRUCTION
+
+ -
+ ON.POST_LEVEL_DESTRUCTION
+
+ -
+ ON.PRE_LAYER_DESTRUCTION
+
+ -
+ ON.POST_LAYER_DESTRUCTION
+
-
@@ -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 functionsudp_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
+
+
+Type
+Name
+Description
+
+
+
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();
};