diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index 3e0f3894f..6bf3b1e20 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1240,6 +1240,44 @@ function set_frametime_unfocused(frametime) end ---Get engine target frametime when game is unfocused (1/framerate, default 1/33). ---@return double? function get_frametime_unfocused() end +---Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn +---Use empty array or no parameter to get new uniqe ENT_TYPE that can be used for custom EntityDB +---@param types ENT_TYPE[] +---@return ENT_TYPE +function add_custom_type(types) end +---Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn +---Use empty array or no parameter to get new uniqe ENT_TYPE that can be used for custom EntityDB +---@return ENT_TYPE +function add_custom_type() end +---Get uids of entities by draw_depth. Can also use table of draw_depths. +---You can later use [filter_entities](https://spelunky-fyi.github.io/overlunky/#filter_entities) if you want specific entity +---@param draw_depth integer +---@param l LAYER +---@return integer[] +function get_entities_by_draw_depth(draw_depth, l) end +---Get uids of entities by draw_depth. Can also use table of draw_depths. +---You can later use [filter_entities](https://spelunky-fyi.github.io/overlunky/#filter_entities) if you want specific entity +---@param draw_depths integer[] +---@param l LAYER +---@return integer[] +function get_entities_by_draw_depth(draw_depths, l) end +---Just convenient way of getting the current amount of money +---short for state->money_shop_total + loop[inventory.money + inventory.collected_money_total] +---@return integer +function get_current_money() end +---Adds money to the state.money_shop_total and displays the effect on the HUD for money change +---Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction +---@param amount integer +---@param display_time integer? +---@return integer +function add_money(amount, display_time) end +---Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change +---Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction +---@param amount integer +---@param player_slot integer +---@param display_time integer? +---@return integer +function add_money_slot(amount, player_slot, display_time) end ---Destroys all layers and all entities in the level. Usually a bad idea, unless you also call create_level and spawn the player back in. ---@return nil function destroy_level() end @@ -1598,6 +1636,8 @@ function set_lut(texture_id, layer) end ---@param layer LAYER ---@return nil function reset_lut(layer) end +---@return HudData +function get_hud() end ---Alters the drop chance for the provided monster-item combination (use e.g. set_drop_chance(DROPCHANCE.MOLE_MATTOCK, 10) for a 1 in 10 chance) ---Use `-1` as dropchance_id to reset all to default ---@param dropchance_id integer @@ -1910,8 +1950,9 @@ do ---@field is_pet_cursed boolean[] @size: 4 ---@field is_pet_poisoned boolean[] @size: 4 ---@field leader integer @Index of leader player in coop - ---@field player_inventory Inventory[] @size: MAX_PLAYERS ---@field player_select SelectPlayerSlot[] @size: MAX_PLAYERS + ---@field player_inventory Inventory[] @size: MAX_PLAYERS + ---@field players Player[] @size: MAX_PLAYERS @Table of players, also keeps the dead body until they are destroyed (necromancer revive also destroys the old body) ---@class LiquidPhysicsEngine ---@field pause boolean @@ -2157,6 +2198,7 @@ do ---@field pause_ui PauseUI ---@field journal_ui JournalUI ---@field save_related SaveRelated + ---@field main_menu_music BackgroundSound ---@class SaveRelated ---@field journal_popup_ui JournalPopupUI @@ -2386,6 +2428,31 @@ function Entity:overlaps_with(rect_left, rect_bottom, rect_right, rect_top) end ---@param other Entity ---@return boolean function Entity:overlaps_with(other) end +---Kill entity along with all entities attached to it. Be aware that for example killing push block with this function will also kill anything on top of it, any items, players, monsters etc. +---To a that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check mask, if the entity doesn't match, it will look in the provided ENT_TYPE's +---destroy_corpse and responsible are the standard parameters for the kill funciton +---@param destroy_corpse boolean +---@param responsible Entity +---@param mask integer? +---@param ent_types ENT_TYPE[] +---@param rec_mode RECURSIVE_MODE +---@return nil +function Entity:kill_recursive(destroy_corpse, responsible, mask, ent_types, rec_mode) end +---Short for using RECURSIVE_MODE.NONE +---@param destroy_corpse boolean +---@param responsible Entity +---@return nil +function Entity:kill_recursive(destroy_corpse, responsible) end +---Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc. +---To a that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check the mask, if the entity doesn't match, it will look in the provided ENT_TYPE's +---@param mask integer? +---@param ent_types ENT_TYPE[] +---@param rec_mode RECURSIVE_MODE +---@return nil +function Entity:destroy_recursive(mask, ent_types, rec_mode) end +---Short for using RECURSIVE_MODE.NONE +---@return nil +function Entity:destroy_recursive() end ---@class Movable : Entity ---@field move Vec2 @{movex, movey} @@ -2833,7 +2900,7 @@ function Movable:generic_update_world(move, sprint_factor, disable_gravity, on_r ---@field sound1 SoundMeta ---@field sound2 SoundMeta ---@field top_chain_piece Entity - ---@field trigger fun(self): nil + ---@field trigger fun(self, play_sound_effect: boolean?): nil ---@class ThinIce : Movable ---@field strength integer @counts down when standing on, maximum is 134 as based of this value it changes animation_frame, and above that value it changes to wrong sprite @@ -4579,6 +4646,9 @@ function MovableBehavior:get_state_id() end ---@field spawn_room_y integer ---@field exit_doors custom_Array ---@field themes ThemeInfo[] @size: 18 + ---@field flags integer + ---@field flags2 integer + ---@field flags3 integer ---@class PostRoomGenerationContext ---@field set_room_template fun(self, x: integer, y: integer, layer: LAYER, room_template: ROOM_TEMPLATE): boolean @Set the room template at the given index and layer, returns `false` if the index is outside of the level. @@ -4713,8 +4783,8 @@ function CustomSound:play(paused, sound_type) end ---@class SoundMeta ---@field x number ---@field y number - ---@field left_channel number[] @size: 38 - ---@field right_channel number[] @size: 38 + ---@field left_channel number[] @size: 38 @Use VANILLA_SOUND_PARAM as index, warning: special case with first index at 0, loop using tuples will get you all results but the key/index will be wrong, ituples will have correct key/index but will skip the first element + ---@field right_channel number[] @size: 38 @Use VANILLA_SOUND_PARAM as index warning: special case with first index at 0, loop using tuples will get you all results but the key/index will be wrong, ituples will have correct key/index but will skip the first element ---@field start_over boolean @when false, current track starts from the beginning, is immediately set back to true ---@field playing boolean @set to false to turn off diff --git a/docs/parse_source.py b/docs/parse_source.py index 4eadd652b..71085e855 100644 --- a/docs/parse_source.py +++ b/docs/parse_source.py @@ -14,6 +14,7 @@ header_files = [ "../src/game_api/math.hpp", "../src/game_api/rpc.hpp", + "../src/game_api/entity_lookup.hpp", "../src/game_api/drops.hpp", "../src/game_api/spawn_api.hpp", "../src/game_api/script.hpp", diff --git a/docs/src/includes/_enums.md b/docs/src/includes/_enums.md index 3ee27a974..78e066c82 100644 --- a/docs/src/includes/_enums.md +++ b/docs/src/includes/_enums.md @@ -970,6 +970,19 @@ Name | Data | Description [STAR_CHALLENGE_SPAWNED](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=QUEST_FLAG.STAR_CHALLENGE_SPAWNED) | 26 | [SUN_CHALLENGE_SPAWNED](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=QUEST_FLAG.SUN_CHALLENGE_SPAWNED) | 27 | +## RECURSIVE_MODE + + +> Search script examples for [RECURSIVE_MODE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=RECURSIVE_MODE) + + + +Name | Data | Description +---- | ---- | ----------- +[EXCLUSIVE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=RECURSIVE_MODE.EXCLUSIVE) | RECURSIVE_MODE::EXCLUSIVE | In this mode the provided [ENT_TYPE](#ENT_TYPE) and [MASK](#MASK) will not be affected nor will entities attached to them
+[INCLUSIVE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=RECURSIVE_MODE.INCLUSIVE) | RECURSIVE_MODE::INCLUSIVE | In this mode the provided [ENT_TYPE](#ENT_TYPE) and [MASK](#MASK) will be the only affected entities, anything outside of the specified mask or type will not be touched including entities attached to them
For this mode you have to specify at least one mask or [ENT_TYPE](#ENT_TYPE), otherwise nothing will be affected
+[NONE](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=RECURSIVE_MODE.NONE) | RECURSIVE_MODE::NONE | Ignores provided [ENT_TYPE](#ENT_TYPE) and [MASK](#MASK) and affects all the entities
+ ## RENDER_INFO_OVERRIDE diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index fc7123016..e1b4c9737 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -597,6 +597,18 @@ end Get uids of entities by some conditions ([ENT_TYPE](#ENT_TYPE), [MASK](#MASK)). Set `entity_type` or `mask` to `0` to ignore that, can also use table of entity_types. Recommended to always set the mask, even if you look for one entity type +### get_entities_by_draw_depth + + +> Search script examples for [get_entities_by_draw_depth](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_entities_by_draw_depth) + +#### array<int> get_entities_by_draw_depth(int draw_depth, [LAYER](#LAYER) l) + +#### array<int> get_entities_by_draw_depth(array draw_depths, [LAYER](#LAYER) l) + +Get uids of entities by draw_depth. Can also use table of draw_depths. +You can later use [filter_entities](#filter_entities) if you want specific entity + ### get_entities_by_type @@ -1167,6 +1179,38 @@ Activate custom variables for y coordinate limit for hundun and spawn of it's he note: because those variables are custom and game does not initiate them, you need to do it yourself for each [Hundun](#Hundun) entity, recommending set_post_entity_spawn default game value are: y_limit = 98.5, rising_speed_x = 0, rising_speed_y = 0.0125, bird_head_spawn_y = 55, snake_head_spawn_y = 71 +### add_custom_type + + +> Search script examples for [add_custom_type](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_custom_type) + +#### [ENT_TYPE](#ENT_TYPE) add_custom_type(array<[ENT_TYPE](#ENT_TYPE)> types) + +#### [ENT_TYPE](#ENT_TYPE) add_custom_type() + +Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn +Use empty array or no parameter to get new uniqe [ENT_TYPE](#ENT_TYPE) that can be used for custom [EntityDB](#EntityDB) + +### add_money + + +> Search script examples for [add_money](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_money) + +#### int add_money(int amount, optional display_time) + +Adds money to the state.money_shop_total and displays the effect on the HUD for money change +Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction + +### add_money_slot + + +> Search script examples for [add_money_slot](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=add_money_slot) + +#### int add_money_slot(int amount, int player_slot, optional display_time) + +Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change +Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction + ### change_poison_timer @@ -1307,6 +1351,16 @@ Get the current adventure seed pair Same as `Player.get_heart_color` +### get_current_money + + +> Search script examples for [get_current_money](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_current_money) + +#### int get_current_money() + +Just convenient way of getting the current amount of money +short for state->money_shop_total + loop[inventory.money + inventory.collected_money_total] + ### get_frame @@ -1334,6 +1388,14 @@ Get engine target frametime (1/framerate, default 1/60). Get engine target frametime when game is unfocused (1/framerate, default 1/33). +### get_hud + + +> Search script examples for [get_hud](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_hud) + +#### [HudData](#HudData) get_hud() + + ### get_id diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index b56e62e7b..de273e7a5 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -1234,7 +1234,10 @@ float | [spawn_y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawn int | [spawn_room_x](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawn_room_x) | int | [spawn_room_y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=spawn_room_y) | custom_array<[Vec2](#Vec2)> | [exit_doors](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=exit_doors) | -[ThemeInfo](#ThemeInfo) | [themes[18]](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=themes) | +array<[ThemeInfo](#ThemeInfo), 18> | [themes](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=themes) | +int | [flags](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flags) | +int | [flags2](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flags2) | +int | [flags3](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=flags3) | ## Lighting types @@ -2620,8 +2623,8 @@ Type | Name | Description ---- | ---- | ----------- float | [x](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=x) | float | [y](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=y) | -array<float, 38> | [left_channel](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=left_channel) | -array<float, 38> | [right_channel](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=right_channel) | +array<float, 38> | [left_channel](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=left_channel) | Use [VANILLA_SOUND_PARAM](#VANILLA_SOUND_PARAM) as index, warning: special case with first index at 0, loop using pairs will get you all results but the key/index will be wrong, ipairs will have correct key/index but will skip the first element +array<float, 38> | [right_channel](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=right_channel) | Use [VANILLA_SOUND_PARAM](#VANILLA_SOUND_PARAM) as index warning: special case with first index at 0, loop using pairs will get you all results but the key/index will be wrong, ipairs will have correct key/index but will skip the first element bool | [start_over](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=start_over) | when false, current track starts from the beginning, is immediately set back to true bool | [playing](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=playing) | set to false to turn off @@ -2679,6 +2682,7 @@ Type | Name | Description [PauseUI](#PauseUI) | [pause_ui](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=pause_ui) | [JournalUI](#JournalUI) | [journal_ui](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=journal_ui) | [SaveRelated](#SaveRelated) | [save_related](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=save_related) | +[BackgroundSound](#BackgroundSound) | [main_menu_music](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=main_menu_music) | ### GameProps @@ -2700,8 +2704,9 @@ array<[ENT_TYPE](#ENT_TYPE), 4> | [saved_pets](https://github.com/spelunky array<bool, 4> | [is_pet_cursed](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_pet_cursed) | array<bool, 4> | [is_pet_poisoned](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_pet_poisoned) | int | [leader](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=leader) | Index of leader player in coop -array<[Inventory](#Inventory), MAX_PLAYERS> | [player_inventory](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=player_inventory) | array<[SelectPlayerSlot](#SelectPlayerSlot), MAX_PLAYERS> | [player_select](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=player_select) | +array<[Inventory](#Inventory), MAX_PLAYERS> | [player_inventory](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=player_inventory) | +array<[Player](#Player), MAX_PLAYERS> | [players](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=players) | Table of players, also keeps the dead body until they are destroyed (necromancer revive also destroys the old body) ### JournalProgressStainSlot @@ -4001,6 +4006,10 @@ nil | [set_invisible(bool value)](https://github.com/spelunky-fyi/overlunky/sear array<int> | [get_items()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=get_items) | bool | [is_in_liquid()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_in_liquid) | Returns true if entity is in water/lava bool | [is_cursed()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_cursed) | +nil | [kill_recursive(bool destroy_corpse, Entity responsible, optional mask, const array ent_types, RECURSIVE_MODE rec_mode)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=kill_recursive) | Kill entity along with all entities attached to it. Be aware that for example killing push block with this function will also kill anything on top of it, any items, players, monsters etc.
To a that, you can inclusively or exclusively limit certain [MASK](#MASK) and [ENT_TYPE](#ENT_TYPE). Note: the function will first check mask, if the entity doesn't match, it will look in the provided [ENT_TYPE](#ENT_TYPE)'s
destroy_corpse and responsible are the standard parameters for the kill funciton +nil | [kill_recursive(bool destroy_corpse, Entity responsible)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=kill_recursive) | Short for using [RECURSIVE_MODE](#RECURSIVE_MODE).NONE +nil | [destroy_recursive(optional mask, const array ent_types, RECURSIVE_MODE rec_mode)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_recursive) | Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc.
To a that, you can inclusively or exclusively limit certain [MASK](#MASK) and [ENT_TYPE](#ENT_TYPE). Note: the function will first check the mask, if the entity doesn't match, it will look in the provided [ENT_TYPE](#ENT_TYPE)'s +nil | [destroy_recursive()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=destroy_recursive) | Short for using [RECURSIVE_MODE](#RECURSIVE_MODE).NONE [CallbackId](#Aliases) | [set_pre_virtual(ENTITY_OVERRIDE entry, function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_pre_virtual) | Hooks before the virtual function at index `entry`. [CallbackId](#Aliases) | [set_post_virtual(ENTITY_OVERRIDE entry, function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=set_post_virtual) | Hooks after the virtual function at index `entry`. nil | [clear_virtual(CallbackId callback_id)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=clear_virtual) | Clears the hook given by `callback_id`, alternatively use `clear_callback()` inside the hook. @@ -5882,7 +5891,7 @@ Type | Name | Description [SoundMeta](#SoundMeta) | [sound1](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=sound1) | [SoundMeta](#SoundMeta) | [sound2](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=sound2) | [Entity](#Entity) | [top_chain_piece](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=top_chain_piece) | -nil | [trigger()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=trigger) | +nil | [trigger(optional play_sound_effect)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=trigger) | ### DummyPurchasableEntity diff --git a/src/game_api/custom_types.cpp b/src/game_api/custom_types.cpp index 23fb7ff45..d698fd2ff 100644 --- a/src/game_api/custom_types.cpp +++ b/src/game_api/custom_types.cpp @@ -9,7 +9,7 @@ #include "entity.hpp" // for to_id -const std::map custom_type_names = { +const std::vector> custom_type_names = { {CUSTOM_TYPE::ACIDBUBBLE, "ACIDBUBBLE"}, {CUSTOM_TYPE::ALIEN, "ALIEN"}, {CUSTOM_TYPE::ALTAR, "ALTAR"}, @@ -355,6 +355,9 @@ std::span make_custom_entity_type_list(StrArgs... ent_type_ids) return {s_entity_types.begin(), s_entity_types.end()}; } +std::map> user_custom_types; +uint32_t g_last_custom_id = (uint32_t)custom_type_max; + std::span get_custom_entity_types(CUSTOM_TYPE type) { if (type < CUSTOM_TYPE::ACIDBUBBLE) @@ -1561,11 +1564,29 @@ std::span get_custom_entity_types(CUSTOM_TYPE type) return make_custom_entity_type_list("ENT_TYPE_MONS_YETIKING"); case CUSTOM_TYPE::YETIQUEEN: return make_custom_entity_type_list("ENT_TYPE_MONS_YETIQUEEN"); + default: + { + auto it = user_custom_types.find(type); + if (it != user_custom_types.end()) + { + return {it->second.begin(), it->second.end()}; + } + } } - return {}; } +CUSTOM_TYPE add_new_custom_type(std::vector types) +{ + ++g_last_custom_id; + if (types.empty()) + { + types.push_back(g_last_custom_id); + } + user_custom_types.emplace((CUSTOM_TYPE)g_last_custom_id, std::move(types)); + return (CUSTOM_TYPE)g_last_custom_id; +} + bool is_type_movable(ENT_TYPE type) { auto movable_types = get_custom_entity_types(CUSTOM_TYPE::MOVABLE); @@ -1575,7 +1596,7 @@ bool is_type_movable(ENT_TYPE type) return false; } -const std::map& get_custom_types_map() +const std::vector>& get_custom_types_vector() { return custom_type_names; } diff --git a/src/game_api/custom_types.hpp b/src/game_api/custom_types.hpp index b89561190..683fdd5da 100644 --- a/src/game_api/custom_types.hpp +++ b/src/game_api/custom_types.hpp @@ -345,6 +345,9 @@ enum class CUSTOM_TYPE : uint32_t YETIQUEEN, }; +constexpr CUSTOM_TYPE custom_type_max = CUSTOM_TYPE::YETIQUEEN; + std::span get_custom_entity_types(CUSTOM_TYPE type); +CUSTOM_TYPE add_new_custom_type(std::vector types); bool is_type_movable(ENT_TYPE type); -const std::map& get_custom_types_map(); +const std::vector>& get_custom_types_vector(); diff --git a/src/game_api/entities_activefloors.cpp b/src/game_api/entities_activefloors.cpp index d0db120c7..65bf65d92 100644 --- a/src/game_api/entities_activefloors.cpp +++ b/src/game_api/entities_activefloors.cpp @@ -23,7 +23,7 @@ uint8_t Olmec::broken_floaters() return broken; } -void Drill::trigger() +void Drill::trigger(std::optional play_sound_effect) { if (move_state != 0 || standing_on_uid != -1) { @@ -39,5 +39,9 @@ void Drill::trigger() flags = flags & ~(1U << (10 - 1)); sound1 = construct_soundmeta(0x159, false); + sound1->start(); sound2 = construct_soundmeta(0x153, false); + sound2->start(); + if (play_sound_effect.value_or(false)) + play_sound_by_id(0xA4, uid); } diff --git a/src/game_api/entities_activefloors.hpp b/src/game_api/entities_activefloors.hpp index 7be149e3a..975354d81 100644 --- a/src/game_api/entities_activefloors.hpp +++ b/src/game_api/entities_activefloors.hpp @@ -113,7 +113,7 @@ class Drill : public Movable Entity* top_chain_piece; uint8_t unknown1; // it's forced to 0, for whatever reason - void trigger(); + void trigger(std::optional play_sound_effect); }; class ThinIce : public Movable diff --git a/src/game_api/entities_floors.cpp b/src/game_api/entities_floors.cpp index 5627dd97b..b889524bb 100644 --- a/src/game_api/entities_floors.cpp +++ b/src/game_api/entities_floors.cpp @@ -728,7 +728,7 @@ void ForceField::activate_laserbeam(bool turn_on) void Door::unlock(bool unlock) { - // TODO: DOOR_EGGSHIP, DOOR_EGGSHIP_ATREZZO, DOOR_EGGSHIP_ROOM ? + // TODO: DOOR_EGGSHIP, DOOR_EGGSHIP_ATREZZO, DOOR_EGGSHIP_ROOM, HUNDUN ? static const ENT_TYPE entrence_door = to_id("ENT_TYPE_FLOOR_DOOR_ENTRANCE"); static const ENT_TYPE locked_door = to_id("ENT_TYPE_FLOOR_DOOR_LOCKED"); static const ENT_TYPE COG_door = to_id("ENT_TYPE_FLOOR_DOOR_COG"); diff --git a/src/game_api/entity.cpp b/src/game_api/entity.cpp index ffd860334..c8bc6fd11 100644 --- a/src/game_api/entity.cpp +++ b/src/game_api/entity.cpp @@ -15,7 +15,9 @@ #include "containers/custom_map.hpp" // for custom_map #include "entities_chars.hpp" // for Player +#include "entities_monsters.hpp" // #include "entity_hooks_info.hpp" // for EntityHooksInfo +#include "entity_lookup.hpp" // #include "memory.hpp" // for write_mem_prot #include "movable.hpp" // for Movable #include "movable_behavior.hpp" // for MovableBehavior @@ -476,3 +478,81 @@ void Movable::set_position(float to_x, float to_y) State::get().ptr()->camera->calculated_focus_y += dy; } } + +template +bool recursive(Entity* ent, std::optional mask, std::vector ent_types, RECURSIVE_MODE rec_mode, F func) +{ + auto acutal_mask = [](uint32_t m) -> uint32_t // for the MASK.ANY + { return m == 0 ? 0xFFFF : m; }; + + if (rec_mode == RECURSIVE_MODE::EXCLUSIVE) + { + if (mask.has_value() && (acutal_mask(mask.value()) & ent->type->search_flags) != 0) + return false; + + if (std::find(ent_types.begin(), ent_types.end(), ent->type->id) != ent_types.end()) + return false; + } + else if (rec_mode == RECURSIVE_MODE::INCLUSIVE) + { + if (mask.has_value() && (acutal_mask(mask.value()) & ent->type->search_flags) == 0) + { + if (std::find(ent_types.begin(), ent_types.end(), ent->type->id) == ent_types.end()) + return false; + } + else if (std::find(ent_types.begin(), ent_types.end(), ent->type->id) == ent_types.end()) + return false; + } + const std::vector items{ent->items.entities().begin(), ent->items.entities().end()}; + for (auto entity : items) + { + recursive(entity, mask, ent_types, rec_mode, func); + } + + { + static const ENT_TYPE jellys[] = { + to_id("ENT_TYPE_MONS_MEGAJELLYFISH"), + to_id("ENT_TYPE_MONS_MEGAJELLYFISH_BACKGROUND"), + }; + static const ENT_TYPE jellys_tails[] = { + to_id("ENT_TYPE_FX_MEGAJELLYFISH_TAIL"), + to_id("ENT_TYPE_FX_MEGAJELLYFISH_TAIL_BG"), + }; + + if (ent->type->id == jellys[0] || ent->type->id == jellys[1]) // special only for MEGAJELLYFISH + { + auto true_type = (MegaJellyfish*)ent; + auto currend_uid = true_type->tail_bg_uid; + for (int idx = 0; idx < 8; ++idx) + { + auto tail_ent = get_entity_ptr(currend_uid + idx); + if (tail_ent != nullptr && (tail_ent->type->id == jellys_tails[0] || tail_ent->type->id == jellys_tails[1])) // only kill the tail + { + recursive(tail_ent, mask, ent_types, rec_mode, func); + } + } + } + } + func(ent); + return true; +} + +void Entity::kill_recursive(bool destroy_corpse, Entity* responsible, std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode) +{ + auto kill_func = [destroy_corpse, &responsible](Entity* ent) -> void + { + ent->kill(destroy_corpse, responsible); + }; + if (!recursive(this, mask, get_proper_types(ent_types), rec_mode, kill_func)) + kill(destroy_corpse, responsible); +} + +void Entity::destroy_recursive(std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode) +{ + auto destroy_func = [](Entity* ent) -> void + { + ent->destroy(); + }; + if (!recursive(this, mask, get_proper_types(ent_types), rec_mode, destroy_func)) + destroy(); +} diff --git a/src/game_api/entity.hpp b/src/game_api/entity.hpp index 8d7f0dacb..312db1810 100644 --- a/src/game_api/entity.hpp +++ b/src/game_api/entity.hpp @@ -3,6 +3,7 @@ #include // for size_t #include // for uint8_t, uint32_t, int32_t, uint16_t, int64_t #include // for function, equal_to +#include // #include // for span #include // for allocator, string #include // for string_view @@ -30,6 +31,13 @@ struct EntityHooksInfo; using ENT_FLAG = uint32_t; using ENT_MORE_FLAG = uint32_t; +enum class RECURSIVE_MODE +{ + EXCLUSIVE, + INCLUSIVE, + NONE, +}; + class Entity { public: @@ -210,6 +218,24 @@ class Entity std::span get_items(); + /// Kill entity along with all entities attached to it. Be aware that for example killing push block with this function will also kill anything on top of it, any items, players, monsters etc. + /// To avoid that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check mask, if the entity doesn't match, it will look in the provided ENT_TYPE's + /// destroy_corpse and responsible are the standard parameters for the kill funciton + void kill_recursive(bool destroy_corpse, Entity* responsible, std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode); + /// Short for using RECURSIVE_MODE.NONE + void kill_recursive(bool destroy_corpse, Entity* responsible) + { + kill_recursive(destroy_corpse, responsible, std::nullopt, {}, RECURSIVE_MODE::NONE); + }; + /// Destroy entity along with all entities attached to it. Be aware that for example destroying push block with this function will also destroy anything on top of it, any items, players, monsters etc. + /// To avoid that, you can inclusively or exclusively limit certain MASK and ENT_TYPE. Note: the function will first check the mask, if the entity doesn't match, it will look in the provided ENT_TYPE's + void destroy_recursive(std::optional mask, const std::vector ent_types, RECURSIVE_MODE rec_mode); + /// Short for using RECURSIVE_MODE.NONE + void destroy_recursive() + { + destroy_recursive(std::nullopt, {}, RECURSIVE_MODE::NONE); + } + template T* as() { diff --git a/src/game_api/entity_lookup.cpp b/src/game_api/entity_lookup.cpp new file mode 100644 index 000000000..5b9273b0b --- /dev/null +++ b/src/game_api/entity_lookup.cpp @@ -0,0 +1,342 @@ +#include "entity_lookup.hpp" + +#include +#include + +#include "aliases.hpp" +#include "custom_types.hpp" +#include "entity.hpp" +#include "layer.hpp" +#include "math.hpp" +#include "state.hpp" + +bool entity_type_check(const std::vector& types_array, const ENT_TYPE find) +{ + if (types_array.empty() || types_array[0] == 0 || std::find(types_array.begin(), types_array.end(), find) != types_array.end()) + return true; + + return false; +} + +std::vector get_proper_types(std::vector ent_types) +{ + for (size_t i = 0; i < ent_types.size(); ++i) + { + if (ent_types[i] >= (uint32_t)CUSTOM_TYPE::ACIDBUBBLE) + { + auto extra_types = get_custom_entity_types(static_cast(ent_types[i])); + if (extra_types.size() == 1) + { + ent_types[i] = extra_types[0]; + } + else if (!extra_types.empty()) + { + auto it = ent_types.begin() + i; + *it = extra_types[0]; + ent_types.insert(++it, extra_types.begin() + 1, extra_types.end()); + i += extra_types.size() - 1; + } + } + } + return ent_types; +} + +int32_t get_grid_entity_at(float x, float y, LAYER layer) +{ + auto state = State::get(); + uint8_t actual_layer = enum_to_layer(layer); + + if (Entity* ent = state.layer(actual_layer)->get_grid_entity_at(x, y)) + return ent->uid; + + return -1; +} + +std::vector get_entities() +{ + return get_entities_by({}, 0, LAYER::BOTH); +} + +std::vector get_entities_by_layer(LAYER layer) +{ + return get_entities_by({}, 0, layer); +} + +std::vector get_entities_by_type(std::vector entity_types) +{ + return get_entities_by(std::move(entity_types), 0, LAYER::BOTH); +} +std::vector get_entities_by_type(ENT_TYPE entity_type) +{ + return get_entities_by(std::vector{entity_type}, 0, LAYER::BOTH); +} + +std::vector get_entities_by_mask(uint32_t mask) +{ + return get_entities_by({}, mask, LAYER::BOTH); +} + +template +requires std::is_invocable_v +void foreach_mask(uint32_t mask, Layer* l, FunT&& fun) +{ + if (mask == 0) + { + fun(l->all_entities); + } + else + { + for (uint32_t test_flag = 1U; test_flag < 0x8000; test_flag <<= 1U) + { + if (mask & test_flag) + { + const auto& it = l->entities_by_mask.find(test_flag); + if (it != l->entities_by_mask.end()) + { + fun(it->second); + } + } + } + } +} + +std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer) +{ + auto state = State::get(); + std::vector found; + const std::vector proper_types = get_proper_types(std::move(entity_types)); + + auto push_matching_types = [&proper_types, &found](const EntityList& entities) + { + for (auto& item : entities.entities()) + { + if (entity_type_check(proper_types, item->type->id)) + { + found.push_back(item->uid); + } + } + }; + auto insert_all_uids = [&found](const EntityList& entities) + { + const auto uids = entities.uids(); + found.insert(found.end(), uids.begin(), uids.end()); + }; + + if (layer == LAYER::BOTH) + { + if (proper_types.empty() || proper_types[0] == 0) + { + if (mask == 0) // all entities + { + // this exception for small improvments with calling reserve once + found.reserve(found.size() + (size_t)state.layer(0)->all_entities.size + (size_t)state.layer(1)->all_entities.size); + found.insert(found.end(), state.layer(0)->all_entities.uids().begin(), state.layer(0)->all_entities.uids().end()); + found.insert(found.end(), state.layer(1)->all_entities.uids().begin(), state.layer(1)->all_entities.uids().end()); + } + else // all types + { + foreach_mask(mask, state.layer(0), insert_all_uids); + foreach_mask(mask, state.layer(1), insert_all_uids); + } + } + else + { + foreach_mask(mask, state.layer(0), push_matching_types); + foreach_mask(mask, state.layer(1), push_matching_types); + } + } + else + { + uint8_t correct_layer = enum_to_layer(layer); + if (proper_types.empty() || proper_types[0] == 0) // all types + { + foreach_mask(mask, state.layer(correct_layer), insert_all_uids); + } + else + { + foreach_mask(mask, state.layer(correct_layer), push_matching_types); + } + } + return found; +} + +std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer) +{ + return get_entities_by(std::vector{entity_type}, mask, layer); +} + +std::vector get_entities_at(std::vector entity_types, uint32_t mask, float x, float y, LAYER layer, float radius) +{ + auto state = State::get(); + std::vector found; + const std::vector proper_types = get_proper_types(std::move(entity_types)); + auto push_entities_at = [&x, &y, &radius, &proper_types, &found](const EntityList& entities) + { + for (auto& item : entities.entities()) + { + auto [ix, iy] = item->position(); + float distance = sqrt(pow(x - ix, 2.0f) + pow(y - iy, 2.0f)); + if (distance < radius && entity_type_check(proper_types, item->type->id)) + { + found.push_back(item->uid); + } + } + }; + if (layer == LAYER::BOTH) + { + foreach_mask(mask, state.layer(0), push_entities_at); + foreach_mask(mask, state.layer(1), push_entities_at); + } + else + { + foreach_mask(mask, state.layer(enum_to_layer(layer)), push_entities_at); + } + return found; +} + +std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius) +{ + return get_entities_at(std::vector{entity_type}, mask, x, y, layer, radius); +} + +std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer) +{ + auto state = State::get(); + std::vector result; + const std::vector proper_types = get_proper_types(std::move(entity_types)); + if (layer == LAYER::BOTH) + { + std::vector result2; + result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(0)); + result2 = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(1)); + result.insert(result.end(), result2.begin(), result2.end()); + } + else + { + uint8_t actual_layer = enum_to_layer(layer); + result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(actual_layer)); + } + return result; +} +std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer) +{ + return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, hitbox, layer); +} + +std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) +{ + return get_entities_overlapping_hitbox(std::move(entity_types), mask, {sx, sy2, sx2, sy}, layer); +} +std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) +{ + return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, {sx, sy2, sx2, sy}, layer); +} + +std::vector get_entities_overlapping_by_pointer(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) +{ + std::vector found; + foreach_mask(mask, layer, [&entity_types, &found, &sx, &sy, &sx2, &sy2](const EntityList& entities) + { + for (auto& item : entities.entities()) + { + if (entity_type_check(std::move(entity_types), item->type->id) && item->overlaps_with(sx, sy, sx2, sy2)) + { + found.push_back(item->uid); + } + } }); + + return found; +} +std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) +{ + return get_entities_overlapping_by_pointer(std::vector{entity_type}, mask, sx, sy, sx2, sy2, layer); +} + +bool entity_has_item_uid(uint32_t uid, uint32_t item_uid) +{ + Entity* entity = get_entity_ptr(uid); + if (entity == nullptr) + return false; + + return entity->items.contains(item_uid); +}; + +bool entity_has_item_type(uint32_t uid, std::vector entity_types) +{ + Entity* entity = get_entity_ptr(uid); + if (entity == nullptr) + return false; + if (entity->items.size > 0) + { + const std::vector proper_types = get_proper_types(std::move(entity_types)); + for (auto item : entity->items.entities()) + { + if (entity_type_check(proper_types, item->type->id)) + return true; + } + } + return false; +} +bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type) +{ + return entity_has_item_type(uid, std::vector{entity_type}); +} + +std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, uint32_t mask) +{ + std::vector found; + Entity* entity = get_entity_ptr(uid); + if (entity == nullptr) + return found; + if (entity->items.size > 0) + { + const std::vector proper_types = get_proper_types(std::move(entity_types)); + if ((!proper_types.size() || !proper_types[0]) && !mask) // all items + { + const auto uids = entity->items.uids(); + found.insert(found.end(), uids.begin(), uids.end()); + } + else + { + for (auto item : entity->items.entities()) + { + if ((mask == 0 || (item->type->search_flags & mask)) && entity_type_check(proper_types, item->type->id)) + { + found.push_back(item->uid); + } + } + } + } + return found; +} +std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask) +{ + return entity_get_items_by(uid, std::vector{entity_type}, mask); +} + +std::vector get_entities_by_draw_depth(uint8_t draw_depth, LAYER l) +{ + return get_entities_by_draw_depth(std::vector{draw_depth}, l); +} + +std::vector get_entities_by_draw_depth(std::vector draw_depths, LAYER l) +{ + auto state = State::get().ptr_local(); + std::vector found; + auto actual_layer = enum_to_layer(l); + for (auto draw_depth : draw_depths) + { + if (draw_depth > 52) + continue; + + auto uids_layer1 = state->layers[actual_layer]->entities_by_draw_depth[draw_depth].uids(); + found.insert(found.end(), uids_layer1.begin(), uids_layer1.end()); + + if (l == LAYER::BOTH) // if it's both, then the actual_layer is 0 + { + auto uids_layer2 = state->layers[1]->entities_by_draw_depth[draw_depth].uids(); + found.insert(found.end(), uids_layer2.begin(), uids_layer2.end()); + } + } + return found; +} diff --git a/src/game_api/entity_lookup.hpp b/src/game_api/entity_lookup.hpp new file mode 100644 index 000000000..c774cbb0a --- /dev/null +++ b/src/game_api/entity_lookup.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "aliases.hpp" +#include "math.hpp" + +struct Layer; + +int32_t get_grid_entity_at(float x, float y, LAYER layer); +std::vector get_entities(); +std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer); +std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer); +std::vector get_entities_by_type(std::vector entity_types); +std::vector get_entities_by_type(ENT_TYPE entity_type); +std::vector get_entities_by_mask(uint32_t mask); +std::vector get_entities_by_layer(LAYER layer); +std::vector get_entities_at(std::vector entity_types, uint32_t mask, float x, float y, LAYER layer, float radius); +std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius); +std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer); +std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer); +std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer); +std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer); +std::vector get_entities_overlapping_by_pointer(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer); +std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer); +bool entity_has_item_uid(uint32_t uid, uint32_t item_uid); +bool entity_has_item_type(uint32_t uid, std::vector entity_types); +bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type); +std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, uint32_t mask); +std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask); +std::vector get_entities_by_draw_depth(uint8_t draw_depth, LAYER l); +std::vector get_entities_by_draw_depth(std::vector draw_depths, LAYER l); +std::vector get_proper_types(std::vector ent_types); diff --git a/src/game_api/game_patches.cpp b/src/game_api/game_patches.cpp index dc52d9a97..8ce032147 100644 --- a/src/game_api/game_patches.cpp +++ b/src/game_api/game_patches.cpp @@ -305,3 +305,37 @@ void patch_ushabti_error() write_mem_prot(offset, "\x90\x90\x90\x90\x90\x90"sv, true); once = true; } + +void patch_entering_closed_door_crash() +{ + static bool once = false; + if (once) + return; + + size_t addr = get_address("enter_closed_door_crash"); + size_t return_addr; + { + auto memory = Memory::get(); + auto rva = find_inst(memory.exe(), "\x49\x39\xD4", addr - memory.exe_ptr, addr - memory.exe_ptr + 0x3F5, "patch_entering_closed_door_crash"); + if (rva == 0) + return; + size_t jump_addr = memory.at_exe(rva + 3); + size_t offset = memory_read(jump_addr + 2); + return_addr = jump_addr + 6 + offset; + } + std::string_view new_code{ + "\x48\x85\xC0"sv // test rax,rax + "\x74\x0D"sv // je + "\x48\x8B\x48\x08"sv // mov rcx,QWORD PTR [rax+0x8] // game code + "\x41\x8B\x47\x28"sv // mov eax,DWORD PTR [r15+0x28] // game code + "\xE9\x00\x00\x00\x00"sv // jmp (offset needs to be updated after we know the address) + }; + + auto new_code_addr = patch_and_redirect(addr, 8, new_code, true, return_addr); + if (new_code_addr == 0) + return; + + int32_t rel = static_cast((addr + 8) - (new_code_addr + 18)); + write_mem_prot(new_code_addr + 14, rel, true); + once = true; +} diff --git a/src/game_api/game_patches.hpp b/src/game_api/game_patches.hpp index a09df57ac..a0aeaa2c5 100644 --- a/src/game_api/game_patches.hpp +++ b/src/game_api/game_patches.hpp @@ -7,3 +7,4 @@ void set_skip_olmec_cutscene(bool skip); void patch_tiamat_kill_crash(); void set_skip_tiamat_cutscene(bool skip); void patch_ushabti_error(); +void patch_entering_closed_door_crash(); diff --git a/src/game_api/items.hpp b/src/game_api/items.hpp index 4a3589fdf..e8e4c88d4 100644 --- a/src/game_api/items.hpp +++ b/src/game_api/items.hpp @@ -10,7 +10,7 @@ class Player; struct Inventory { /// Sum of the money collected in current level - uint32_t money; + int32_t money; uint8_t bombs; uint8_t ropes; /// Used to transfer information to transition/next level. Is not updated during a level @@ -92,7 +92,7 @@ struct Inventory /// You can use `ON.PRE_LEVEL_GENERATION` to access/edit this std::array acquired_powerups; /// Total money collected during previous levels (so excluding the current one) - uint32_t collected_money_total; + int32_t collected_money_total; }; struct SelectPlayerSlot @@ -113,6 +113,7 @@ struct Items uint8_t unknown2; uint8_t unknown3; uint32_t unknown; // padding probably + /// Table of players, also keeps the dead body until they are destroyed (necromancer revive also destroys the old body) std::array players; std::array player_inventories; std::array player_select_slots; diff --git a/src/game_api/layer.cpp b/src/game_api/layer.cpp index 39a0ca999..735f8cd2f 100644 --- a/src/game_api/layer.cpp +++ b/src/game_api/layer.cpp @@ -9,7 +9,7 @@ #include "entity.hpp" // for Entity, to_id, EntityDB, entity_factory #include "logger.h" // for DEBUG #include "movable.hpp" // for Movable -#include "rpc.hpp" // for entity_get_items_by +#include "rpc.hpp" // #include "search.hpp" // for get_address #include "state.hpp" // for State, StateMemory @@ -164,18 +164,17 @@ Entity* Layer::spawn_apep(float x, float y, bool right) int current_uid = apep_head->uid; do { - auto body_parts = entity_get_items_by(current_uid, {}, 0); + auto body_parts = apep_head->items; int temp = current_uid; - for (auto body_part_uid : body_parts) + for (auto body_part : body_parts.entities()) { - Entity* body_part = get_entity_ptr(body_part_uid); body_part->flags = right ? body_part->flags & ~facing_left_flag : body_part->flags | facing_left_flag; body_part->x *= -1.0f; if (body_part->type->id == body_id) { - current_uid = body_part_uid; + current_uid = body_part->uid; } } if (temp == current_uid) diff --git a/src/game_api/layer.hpp b/src/game_api/layer.hpp index 32dc5f40b..e77c86e0d 100644 --- a/src/game_api/layer.hpp +++ b/src/game_api/layer.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include // for size_t #include // for uint32_t, int32_t, uint64_t, uint8_t #include // for less @@ -151,7 +152,7 @@ struct Layer EntityList entities_overlaping_grid[g_level_max_y][g_level_max_x]; // static entities (like midbg, decorations) that overlap this grid position EntityList unknown_entities2; - EntityList entities_by_draw_depth[53]; + std::array entities_by_draw_depth; EntityList unknown_entities3; // debris, explosions, laserbeams etc. ? EntityList unknown_entities4; // explosions, laserbeams, BG_LEVEL_*_SOOT ? only for short time while there are spawned? std::vector unknown_vector; // add_to_layer uses this diff --git a/src/game_api/level_api.cpp b/src/game_api/level_api.cpp index 6f2af4b67..8791b6d22 100644 --- a/src/game_api/level_api.cpp +++ b/src/game_api/level_api.cpp @@ -22,6 +22,7 @@ #include "entities_items.hpp" // #include "entities_monsters.hpp" // for GHOST_BEHAVIOR, GHOST_BEHAVIOR::MED... #include "entity.hpp" // for to_id, Entity, get_entity_ptr, Enti... +#include "entity_lookup.hpp" // #include "layer.hpp" // for Layer, g_level_max_y, g_level_max_x #include "logger.h" // for DEBUG #include "memory.hpp" // for to_le_bytes, write_mem_prot, Execut... @@ -223,8 +224,26 @@ std::array g_community_tile_codes{ CommunityTileCode{"monkey", "ENT_TYPE_MONS_MONKEY"}, CommunityTileCode{"firebug", "ENT_TYPE_MONS_FIREBUG"}, CommunityTileCode{"vampire", "ENT_TYPE_MONS_VAMPIRE", g_spawn_not_snapped_to_floor}, - CommunityTileCode{"vampire_flying", "ENT_TYPE_MONS_VAMPIRE"}, - CommunityTileCode{"vlad_flying", "ENT_TYPE_MONS_VLAD"}, + CommunityTileCode{ + "vampire_flying", + "ENT_TYPE_MONS_VAMPIRE", + [](const CommunityTileCode& self, float x, float y, Layer* layer) + { + if (Movable* vampire = (Movable*)layer->spawn_entity(self.entity_id, x, y, false, 0.0f, 0.0f, true)) + { + vampire->move_state = 9; + } + }}, + CommunityTileCode{ + "vlad_flying", + "ENT_TYPE_MONS_VLAD", + [](const CommunityTileCode& self, float x, float y, Layer* layer) + { + if (Movable* vlad = (Movable*)layer->spawn_entity(self.entity_id, x, y, false, 0.0f, 0.0f, true)) + { + vlad->move_state = 9; + } + }}, CommunityTileCode{"osiris", "ENT_TYPE_MONS_OSIRIS_HEAD"}, CommunityTileCode{"anubis2", "ENT_TYPE_MONS_ANUBIS2"}, CommunityTileCode{"assassin", "ENT_TYPE_MONS_FEMALE_JIANGSHI"}, @@ -419,20 +438,14 @@ std::array g_community_tile_codes{ }, CommunityTileCode{"boulder", "ENT_TYPE_ACTIVEFLOOR_BOULDER"}, CommunityTileCode{"apep", "ENT_TYPE_MONS_APEP_HEAD"}, - CommunityTileCode{ - "apep_left", - "ENT_TYPE_MONS_APEP_HEAD", - []([[maybe_unused]] const CommunityTileCode& self, float x, float y, Layer* layer) - { - layer->spawn_apep(x, y, is_room_flipped(x, y)); - }}, - CommunityTileCode{ - "apep_right", - "ENT_TYPE_MONS_APEP_HEAD", - []([[maybe_unused]] const CommunityTileCode& self, float x, float y, Layer* layer) - { - layer->spawn_apep(x, y, !is_room_flipped(x, y)); - }}, + CommunityTileCode{"apep_left", "ENT_TYPE_MONS_APEP_HEAD", []([[maybe_unused]] const CommunityTileCode& self, float x, float y, Layer* layer) + { + layer->spawn_apep(x, y, is_room_flipped(x, y)); + }}, + CommunityTileCode{"apep_right", "ENT_TYPE_MONS_APEP_HEAD", []([[maybe_unused]] const CommunityTileCode& self, float x, float y, Layer* layer) + { + layer->spawn_apep(x, y, !is_room_flipped(x, y)); + }}, CommunityTileCode{"olmite_naked", "ENT_TYPE_MONS_OLMITE_NAKED"}, CommunityTileCode{"olmite_helmet", "ENT_TYPE_MONS_OLMITE_HELMET"}, CommunityTileCode{ @@ -470,46 +483,34 @@ std::array g_community_tile_codes{ CommunityTileCode{"punishball_attach_bottom", "ENT_TYPE_ITEM_PUNISHBALL", g_spawn_punishball_attach<0, -1>}, CommunityTileCode{"critter_slime", "ENT_TYPE_MONS_CRITTERSLIME"}, CommunityTileCode{"skull", "ENT_TYPE_ITEM_SKULL"}, - CommunityTileCode{ - "venom", - "ENT_TYPE_ITEM_ACIDSPIT", - [](const CommunityTileCode& self, float x, float y, Layer* layer) - { - layer->spawn_entity(self.entity_id, x, y - 1, false, 0, 0, false); - }}, + CommunityTileCode{"venom", "ENT_TYPE_ITEM_ACIDSPIT", [](const CommunityTileCode& self, float x, float y, Layer* layer) + { + layer->spawn_entity(self.entity_id, x, y - 1, false, 0, 0, false); + }}, CommunityTileCode{"arrow_wooden", "ENT_TYPE_ITEM_WOODEN_ARROW"}, CommunityTileCode{"arrow_metal", "ENT_TYPE_ITEM_METAL_ARROW"}, - CommunityTileCode{ - "arrow_wooden_poison", - "ENT_TYPE_ITEM_WOODEN_ARROW", - [](const CommunityTileCode& self, float x, float y, Layer* layer) - { - Arrow* arrow = layer->spawn_entity_snap_to_floor(self.entity_id, x, y)->as(); - arrow->poison_arrow(true); - }}, - CommunityTileCode{ - "arrow_metal_poison", - "ENT_TYPE_ITEM_METAL_ARROW", - [](const CommunityTileCode& self, float x, float y, Layer* layer) - { - Arrow* arrow = layer->spawn_entity_snap_to_floor(self.entity_id, x, y)->as(); - arrow->poison_arrow(true); - }}, - CommunityTileCode{ - "movable_spikes", - "ENT_TYPE_ITEM_SPIKES", - [](const CommunityTileCode& self, float x, float y, Layer* layer) - { - auto do_spawn = [=]() - { - std::vector entities_neighbour = get_entities_overlapping_by_pointer({}, 0, x - 0.5f, y - 1.5f, x + 0.5f, y - 0.5f, layer); - if (!entities_neighbour.empty()) - { - layer->spawn_entity_over(self.entity_id, get_entity_ptr(entities_neighbour.front()), 0.0f, 1.0f); - } - }; - g_attachee_requiring_entities.push_back({{{x, y - 1}}, do_spawn}); - }}, + CommunityTileCode{"arrow_wooden_poison", "ENT_TYPE_ITEM_WOODEN_ARROW", [](const CommunityTileCode& self, float x, float y, Layer* layer) + { + Arrow* arrow = layer->spawn_entity_snap_to_floor(self.entity_id, x, y)->as(); + arrow->poison_arrow(true); + }}, + CommunityTileCode{"arrow_metal_poison", "ENT_TYPE_ITEM_METAL_ARROW", [](const CommunityTileCode& self, float x, float y, Layer* layer) + { + Arrow* arrow = layer->spawn_entity_snap_to_floor(self.entity_id, x, y)->as(); + arrow->poison_arrow(true); + }}, + CommunityTileCode{"movable_spikes", "ENT_TYPE_ITEM_SPIKES", [](const CommunityTileCode& self, float x, float y, Layer* layer) + { + auto do_spawn = [=]() + { + std::vector entities_neighbour = get_entities_overlapping_by_pointer({}, 0, x - 0.5f, y - 1.5f, x + 0.5f, y - 0.5f, layer); + if (!entities_neighbour.empty()) + { + layer->spawn_entity_over(self.entity_id, get_entity_ptr(entities_neighbour.front()), 0.0f, 1.0f); + } + }; + g_attachee_requiring_entities.push_back({{{x, y - 1}}, do_spawn}); + }}, CommunityTileCode{"boombox", "ENT_TYPE_ITEM_BOOMBOX"}, // CommunityTileCode{ // "lake_imposter", diff --git a/src/game_api/level_api.hpp b/src/game_api/level_api.hpp index 65988f426..ab8108d36 100644 --- a/src/game_api/level_api.hpp +++ b/src/game_api/level_api.hpp @@ -441,7 +441,7 @@ struct LevelGenSystem uint64_t unknown2; union { - ThemeInfo* themes[18]; + std::array themes; struct { ThemeInfo* theme_dwelling; diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index 482e6ac80..4215e8f8c 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -195,7 +195,59 @@ size_t patch_and_redirect(size_t addr, size_t replace_size, const std::string_vi VirtualProtect(new_code, new_memory_size, PAGE_EXECUTE_READ, &dummy); int32_t rel = static_cast((size_t)new_code - (addr + jump_size)); - const std::string redirect_code = fmt::format("\xE9{}{}"sv, to_le_bytes(rel), std::string(replace_size - jump_size, '\x90')); + const std::string redirect_code = fmt::format("\xE9{}{}"sv, to_le_bytes(rel), get_nop(replace_size - jump_size)); write_mem_prot(addr, redirect_code, true); return (size_t)new_code; } + +std::string get_nop(size_t size, bool true_nop) +{ + if (true_nop) + return std::string(size, '\x90'); + + switch (size) + { + case 0: + return ""; + case 1: + return "\x90"s; + case 2: + return "\x66\x90"s; + case 3: + return "\x0F\x1F\x00"s; + case 4: + return "\x0F\x1F\x40\x00"s; + case 5: + return "\x0F\x1F\x44\x00\x00"s; + case 6: + return "\x66\x0F\x1F\x44\x00\x00"s; + case 7: + return "\x0F\x1F\x80\x00\x00\x00\x00"s; + case 8: + return "\x0F\x1F\x84\x00\x00\x00\x00\x00"s; + case 9: + return "\x66\x0F\x1F\x84\x00\x00\x00\x00\x00"s; + case 10: + return "\x66\x2E\x0F\x1F\x84\x00\x00\x00\x00\x00"s; + default: + { + std::string ret_str; + size_t remaning = size; + + for (uint8_t idx = 10; idx > 0; --idx) + { + size_t d_t = remaning / idx; + if (d_t > 0) + { + std::string c_nop = get_nop(idx); + for (; d_t > 0; --d_t) + { + ret_str += c_nop; + remaning -= idx; + } + } + } + return ret_str; + } + } +} diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index 27eaab119..a22248f57 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -95,6 +95,7 @@ void write_mem(size_t addr, std::string payload); size_t function_start(size_t off, uint8_t outside_byte = '\xcc'); void write_mem_recoverable(std::string name, size_t addr, std::string_view payload, bool prot); void recover_mem(std::string name, size_t addr = NULL); +std::string get_nop(size_t size, bool true_nop = false); // similar to ExecutableMemory but writes automatic jump from and back, moves the code it replaces etc. // it needs at least 5 bytes to move, use just_nop = true to nuke the oryginal code diff --git a/src/game_api/render_api.cpp b/src/game_api/render_api.cpp index 95b7aefd8..999632af5 100644 --- a/src/game_api/render_api.cpp +++ b/src/game_api/render_api.cpp @@ -867,3 +867,9 @@ void TextRenderingInfo::rotate(float angle, std::optional px, std::option letter->top.C.x *= inverse_ratio; } } + +HudData* get_hud() +{ + static auto hud = (HudData*)get_address("hud"); + return hud; +} diff --git a/src/game_api/render_api.hpp b/src/game_api/render_api.hpp index 39978ca19..e6c19c5f9 100644 --- a/src/game_api/render_api.hpp +++ b/src/game_api/render_api.hpp @@ -408,7 +408,8 @@ struct HudMoney : HudElement { int32_t total; int32_t counter; - int32_t timer; + uint8_t timer; + // padding? }; struct HudData @@ -439,3 +440,5 @@ struct Hud float opacity; HudData* data; }; + +HudData* get_hud(); diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index bd3283623..3e39554dc 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -27,6 +27,7 @@ #include "entities_liquids.hpp" // for Liquid #include "entities_mounts.hpp" // for Mount #include "entity.hpp" // for get_entity_ptr, to_id, Entity, EntityDB +#include "entity_lookup.hpp" // #include "game_manager.hpp" // #include "game_patches.hpp" // #include "items.hpp" // for Items @@ -142,17 +143,6 @@ void stack_entities(uint32_t bottom_uid, uint32_t top_uid, const float (&offset) } } -int32_t get_grid_entity_at(float x, float y, LAYER layer) -{ - auto state = State::get(); - uint8_t actual_layer = enum_to_layer(layer); - - if (Entity* ent = state.layer(actual_layer)->get_grid_entity_at(x, y)) - return ent->uid; - - return -1; -} - void move_entity_abs(uint32_t uid, float x, float y, float vx, float vy) { auto ent = get_entity_ptr(uid); @@ -306,204 +296,6 @@ std::vector filter_entities(std::vector entities, std::funct return filtered_entities; } -std::vector get_entities() -{ - return get_entities_by({}, 0, LAYER::BOTH); -} - -std::vector get_entities_by_layer(LAYER layer) -{ - return get_entities_by({}, 0, layer); -} - -std::vector get_entities_by_type(std::vector entity_types) -{ - return get_entities_by(std::move(entity_types), 0, LAYER::BOTH); -} -std::vector get_entities_by_type(ENT_TYPE entity_type) -{ - return get_entities_by(std::vector{entity_type}, 0, LAYER::BOTH); -} - -std::vector get_entities_by_mask(uint32_t mask) -{ - return get_entities_by({}, mask, LAYER::BOTH); -} - -template -requires std::is_invocable_v -void foreach_mask(uint32_t mask, Layer* l, FunT&& fun) -{ - if (mask == 0) - { - fun(l->all_entities); - } - else - { - for (uint32_t test_flag = 1U; test_flag < 0x8000; test_flag <<= 1U) - { - if (mask & test_flag) - { - const auto& it = l->entities_by_mask.find(test_flag); - if (it != l->entities_by_mask.end()) - { - fun(it->second); - } - } - } - } -} - -std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer) -{ - auto state = State::get(); - std::vector found; - const std::vector proper_types = get_proper_types(std::move(entity_types)); - - auto push_matching_types = [&proper_types, &found](const EntityList& entities) - { - for (auto& item : entities.entities()) - { - if (entity_type_check(proper_types, item->type->id)) - { - found.push_back(item->uid); - } - } - }; - auto insert_all_uids = [&found](const EntityList& entities) - { - const auto uids = entities.uids(); - found.insert(found.end(), uids.begin(), uids.end()); - }; - - if (layer == LAYER::BOTH) - { - if (proper_types.empty() || proper_types[0] == 0) - { - if (mask == 0) // all entities - { - // this exception for small improvments with calling reserve once - found.reserve(found.size() + (size_t)state.layer(0)->all_entities.size + (size_t)state.layer(1)->all_entities.size); - found.insert(found.end(), state.layer(0)->all_entities.uids().begin(), state.layer(0)->all_entities.uids().end()); - found.insert(found.end(), state.layer(1)->all_entities.uids().begin(), state.layer(1)->all_entities.uids().end()); - } - else // all types - { - foreach_mask(mask, state.layer(0), insert_all_uids); - foreach_mask(mask, state.layer(1), insert_all_uids); - } - } - else - { - foreach_mask(mask, state.layer(0), push_matching_types); - foreach_mask(mask, state.layer(1), push_matching_types); - } - } - else - { - uint8_t correct_layer = enum_to_layer(layer); - if (proper_types.empty() || proper_types[0] == 0) // all types - { - foreach_mask(mask, state.layer(correct_layer), insert_all_uids); - } - else - { - foreach_mask(mask, state.layer(correct_layer), push_matching_types); - } - } - return found; -} -std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer) -{ - return get_entities_by(std::vector{entity_type}, mask, layer); -} - -std::vector get_entities_at(std::vector entity_types, uint32_t mask, float x, float y, LAYER layer, float radius) -{ - auto state = State::get(); - std::vector found; - const std::vector proper_types = get_proper_types(std::move(entity_types)); - auto push_entities_at = [&x, &y, &radius, &proper_types, &found](const EntityList& entities) - { - for (auto& item : entities.entities()) - { - auto [ix, iy] = item->position(); - float distance = sqrt(pow(x - ix, 2.0f) + pow(y - iy, 2.0f)); - if (distance < radius && entity_type_check(proper_types, item->type->id)) - { - found.push_back(item->uid); - } - } - }; - if (layer == LAYER::BOTH) - { - foreach_mask(mask, state.layer(0), push_entities_at); - foreach_mask(mask, state.layer(1), push_entities_at); - } - else - { - foreach_mask(mask, state.layer(enum_to_layer(layer)), push_entities_at); - } - return found; -} -std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius) -{ - return get_entities_at(std::vector{entity_type}, mask, x, y, layer, radius); -} - -std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer) -{ - auto state = State::get(); - std::vector result; - const std::vector proper_types = get_proper_types(std::move(entity_types)); - if (layer == LAYER::BOTH) - { - std::vector result2; - result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(0)); - result2 = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(1)); - result.insert(result.end(), result2.begin(), result2.end()); - } - else - { - uint8_t actual_layer = enum_to_layer(layer); - result = get_entities_overlapping_by_pointer(proper_types, mask, hitbox.left, hitbox.bottom, hitbox.right, hitbox.top, state.layer(actual_layer)); - } - return result; -} -std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer) -{ - return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, hitbox, layer); -} - -std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) -{ - return get_entities_overlapping_hitbox(std::move(entity_types), mask, {sx, sy2, sx2, sy}, layer); -} -std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer) -{ - return get_entities_overlapping_hitbox(std::vector{entity_type}, mask, {sx, sy2, sx2, sy}, layer); -} - -std::vector get_entities_overlapping_by_pointer(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) -{ - std::vector found; - foreach_mask(mask, layer, [&entity_types, &found, &sx, &sy, &sx2, &sy2](const EntityList& entities) - { - for (auto& item : entities.entities()) - { - if (entity_type_check(std::move(entity_types), item->type->id) && item->overlaps_with(sx, sy, sx2, sy2)) - { - found.push_back(item->uid); - } - } }); - - return found; -} -std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer) -{ - return get_entities_overlapping_by_pointer(std::vector{entity_type}, mask, sx, sy, sx2, sy2, layer); -} - void set_door_target(uint32_t uid, uint8_t w, uint8_t l, uint8_t t) { if (auto door = get_entity_ptr(uid)->as()) @@ -553,68 +345,6 @@ void entity_remove_item(uint32_t uid, uint32_t item_uid) entity->remove_item(item_uid); } -bool entity_has_item_uid(uint32_t uid, uint32_t item_uid) -{ - Entity* entity = get_entity_ptr(uid); - if (entity == nullptr) - return false; - - return entity->items.contains(item_uid); -}; - -bool entity_has_item_type(uint32_t uid, std::vector entity_types) -{ - Entity* entity = get_entity_ptr(uid); - if (entity == nullptr) - return false; - if (entity->items.size > 0) - { - const std::vector proper_types = get_proper_types(std::move(entity_types)); - for (auto item : entity->items.entities()) - { - if (entity_type_check(proper_types, item->type->id)) - return true; - } - } - return false; -} -bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type) -{ - return entity_has_item_type(uid, std::vector{entity_type}); -} - -std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, uint32_t mask) -{ - std::vector found; - Entity* entity = get_entity_ptr(uid); - if (entity == nullptr) - return found; - if (entity->items.size > 0) - { - const std::vector proper_types = get_proper_types(std::move(entity_types)); - if ((!proper_types.size() || !proper_types[0]) && !mask) // all items - { - const auto uids = entity->items.uids(); - found.insert(found.end(), uids.begin(), uids.end()); - } - else - { - for (auto item : entity->items.entities()) - { - if ((mask == 0 || (item->type->search_flags & mask)) && entity_type_check(proper_types, item->type->id)) - { - found.push_back(item->uid); - } - } - } - } - return found; -} -std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask) -{ - return entity_get_items_by(uid, std::vector{entity_type}, mask); -} - void lock_door_at(float x, float y) { std::vector items = get_entities_at({}, 0, x, y, LAYER::FRONT, 1); @@ -1331,37 +1061,6 @@ uint32_t waddler_entity_type_in_slot(uint8_t slot) return 0; } -bool entity_type_check(const std::vector& types_array, const ENT_TYPE find) -{ - if (types_array.empty() || types_array[0] == 0 || std::find(types_array.begin(), types_array.end(), find) != types_array.end()) - return true; - - return false; -} - -std::vector get_proper_types(std::vector ent_types) -{ - for (size_t i = 0; i < ent_types.size(); i++) - { - if (ent_types[i] >= (uint32_t)CUSTOM_TYPE::ACIDBUBBLE) - { - auto extra_types = get_custom_entity_types(static_cast(ent_types[i])); - if (extra_types.size() == 1) - { - ent_types[i] = extra_types[0]; - } - else if (!extra_types.empty()) - { - auto it = ent_types.begin() + i; - it = ent_types.erase(it); - ent_types.insert(it, extra_types.begin(), extra_types.end()); - i += extra_types.size() - 1; - } - } - } - return ent_types; -} - void enter_door(int32_t player_uid, int32_t door_uid) { auto player = get_entity_ptr(player_uid); @@ -2143,6 +1842,52 @@ std::optional get_frametime_inactive() return std::nullopt; } +ENT_TYPE add_custom_type(std::vector types) +{ + return (ENT_TYPE)add_new_custom_type(std::move(types)); +} + +ENT_TYPE add_custom_type() +{ + return (ENT_TYPE)add_new_custom_type({}); +} + +int32_t get_current_money() +{ + auto state = State::get().ptr(); + int32_t money = state->money_shop_total; + for (auto& inventory : state->items->player_inventories) + { + money += inventory.money; + money += inventory.collected_money_total; + } + return money; +} + +int32_t add_money(int32_t amount, std::optional display_time) +{ + auto state = State::get().ptr(); + auto hud = get_hud(); + state->money_shop_total += amount; + hud->money.counter += amount; + hud->money.timer = display_time.value_or(0x3C); + return get_current_money(); +} + +int32_t add_money_slot(int32_t amount, uint8_t player_slot, std::optional display_time) +{ + auto state = State::get().ptr(); + auto hud = get_hud(); + uint8_t slot = player_slot - 1; + if (slot > 3) + return get_current_money(); + + state->items->player_inventories[slot].money += amount; + hud->money.counter += amount; + hud->money.timer = display_time.value_or(0x3C); + return get_current_money(); +} + void destroy_layer(uint8_t layer) { static size_t offset = 0; diff --git a/src/game_api/rpc.hpp b/src/game_api/rpc.hpp index 92d9690d5..3b366f459 100644 --- a/src/game_api/rpc.hpp +++ b/src/game_api/rpc.hpp @@ -23,7 +23,6 @@ void attach_entity(Entity* overlay, Entity* attachee); void attach_entity_by_uid(uint32_t overlay_uid, uint32_t attachee_uid); int32_t attach_ball_and_chain(uint32_t uid, float off_x, float off_y); void stack_entities(uint32_t bottom_uid, uint32_t top_uid, const float (&offset)[2]); -int32_t get_grid_entity_at(float x, float y, LAYER layer); void move_entity_abs(uint32_t uid, float x, float y, float vx, float vy); void move_entity_abs(uint32_t uid, float x, float y, float vx, float vy, LAYER layer); void move_liquid_abs(uint32_t uid, float x, float y, float vx, float vy); @@ -39,30 +38,10 @@ std::vector get_players(StateMemory* state); std::tuple screen_aabb(float x1, float y1, float x2, float y2); float screen_distance(float x); std::vector filter_entities(std::vector entities, std::function predicate); -std::vector get_entities(); -std::vector get_entities_by(std::vector entity_types, uint32_t mask, LAYER layer); -std::vector get_entities_by(ENT_TYPE entity_type, uint32_t mask, LAYER layer); -std::vector get_entities_by_type(std::vector entity_types); -std::vector get_entities_by_type(ENT_TYPE entity_type); -std::vector get_entities_by_mask(uint32_t mask); -std::vector get_entities_by_layer(LAYER layer); -std::vector get_entities_at(std::vector entity_types, uint32_t mask, float x, float y, LAYER layer, float radius); -std::vector get_entities_at(ENT_TYPE entity_type, uint32_t mask, float x, float y, LAYER layer, float radius); -std::vector get_entities_overlapping_hitbox(std::vector entity_types, uint32_t mask, AABB hitbox, LAYER layer); -std::vector get_entities_overlapping_hitbox(ENT_TYPE entity_type, uint32_t mask, AABB hitbox, LAYER layer); -std::vector get_entities_overlapping(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer); -std::vector get_entities_overlapping(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, LAYER layer); -std::vector get_entities_overlapping_by_pointer(std::vector entity_types, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer); -std::vector get_entities_overlapping_by_pointer(ENT_TYPE entity_type, uint32_t mask, float sx, float sy, float sx2, float sy2, Layer* layer); void set_door_target(uint32_t uid, uint8_t w, uint8_t l, uint8_t t); std::tuple get_door_target(uint32_t uid); void set_contents(uint32_t uid, ENT_TYPE item_entity_type); void entity_remove_item(uint32_t uid, uint32_t item_uid); -bool entity_has_item_uid(uint32_t uid, uint32_t item_uid); -bool entity_has_item_type(uint32_t uid, std::vector entity_types); -bool entity_has_item_type(uint32_t uid, ENT_TYPE entity_type); -std::vector entity_get_items_by(uint32_t uid, std::vector entity_types, uint32_t mask); -std::vector entity_get_items_by(uint32_t uid, ENT_TYPE entity_type, uint32_t mask); void lock_door_at(float x, float y); void unlock_door_at(float x, float y); uint32_t get_frame_count_main(); @@ -150,6 +129,11 @@ void set_frametime(std::optional frametime); std::optional get_frametime(); void set_frametime_inactive(std::optional frametime); std::optional get_frametime_inactive(); +ENT_TYPE add_custom_type(std::vector types); +ENT_TYPE add_custom_type(); +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); diff --git a/src/game_api/screen.hpp b/src/game_api/screen.hpp index 2e588417e..0c4eb878a 100644 --- a/src/game_api/screen.hpp +++ b/src/game_api/screen.hpp @@ -23,12 +23,10 @@ class Screen float render_timer; uint32_t unknown_zero; - // this first virtual does not appear to be the destructor in the game - // but is made one here to appease Clang + virtual void init() = 0; + virtual void handle_player() = 0; // for normal level: death, camera zoom, camera bounds, some save data stuff virtual ~Screen() = 0; - virtual void v1() = 0; - virtual void v2() = 0; - virtual void render() = 0; + virtual void render() = 0; // mostly used by the non gameplay screens to draw textures and text std::uint32_t reserve_callback_id(); void unhook(std::uint32_t id); diff --git a/src/game_api/script/lua_backend.cpp b/src/game_api/script/lua_backend.cpp index b559c79b8..31edf5209 100644 --- a/src/game_api/script/lua_backend.cpp +++ b/src/game_api/script/lua_backend.cpp @@ -14,6 +14,7 @@ #include "constants.hpp" // for no_return_str #include "entities_chars.hpp" // for Player #include "entity.hpp" // for Entity, get_entity_ptr +#include "entity_lookup.hpp" // #include "handle_lua_function.hpp" // for handle_function #include "items.hpp" // for Inventory #include "level_api.hpp" // for LevelGenData, LevelGenSy... diff --git a/src/game_api/script/lua_vm.cpp b/src/game_api/script/lua_vm.cpp index 663eb5df1..3a79003b7 100644 --- a/src/game_api/script/lua_vm.cpp +++ b/src/game_api/script/lua_vm.cpp @@ -35,6 +35,7 @@ #include "entities_chars.hpp" // for Player #include "entities_items.hpp" // for Container, Player... #include "entity.hpp" // for get_entity_ptr +#include "entity_lookup.hpp" // #include "game_manager.hpp" // for get_game_manager #include "handle_lua_function.hpp" // for handle_function #include "items.hpp" // for Inventory @@ -2113,6 +2114,34 @@ end /// Get engine target frametime when game is unfocused (1/framerate, default 1/33). lua["get_frametime_unfocused"] = get_frametime_inactive; + auto add_custom_type = sol::overload( + static_cast)>(::add_custom_type), + static_cast(::add_custom_type)); + + /// Adds new custom type (group of ENT_TYPE) that can be later used in functions like get_entities_by or set_(pre/post)_entity_spawn + /// Use empty array or no parameter to get new uniqe ENT_TYPE that can be used for custom EntityDB + lua["add_custom_type"] = add_custom_type; + + auto get_entities_by_draw_depth = sol::overload( + static_cast (*)(uint8_t, LAYER)>(::get_entities_by_draw_depth), + static_cast (*)(std::vector, LAYER)>(::get_entities_by_draw_depth)); + + /// Get uids of entities by draw_depth. Can also use table of draw_depths. + /// You can later use [filter_entities](#filter_entities) if you want specific entity + lua["get_entities_by_draw_depth"] = get_entities_by_draw_depth; + + /// Just convenient way of getting the current amount of money + /// short for state->money_shop_total + loop[inventory.money + inventory.collected_money_total] + lua["get_current_money"] = get_current_money; + + /// Adds money to the state.money_shop_total and displays the effect on the HUD for money change + /// Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction + lua["add_money"] = add_money; + + /// Adds money to the state.items.player_inventory[player_slot].money and displays the effect on the HUD for money change + /// Can be negative, default display_time = 60 (about 2s). Returns the current money after the transaction + 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; diff --git a/src/game_api/script/sol_helper.hpp b/src/game_api/script/sol_helper.hpp new file mode 100644 index 000000000..d50334f27 --- /dev/null +++ b/src/game_api/script/sol_helper.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include + +template +struct ZeroIndexArray +{ + using value_type = T; + using iterator_category = std::contiguous_iterator_tag; + using difference_type = size_t; + using pointer = T*; + using reference = T&; + using iterator = T*; + + ZeroIndexArray(T* data, size_t size) + : data(data), data_size(size){}; + + ZeroIndexArray(std::span arr) + { + data = arr.data(); + data_size = arr.size(); + }; + ~ZeroIndexArray() + { + data_size = 0; + }; + + T& operator[](int index) + { + return data[index]; + } + T& operator=(T&& other) noexcept + { + // Guard self assignment + if (this == &other) + return *this; + + data = other.data; + data_size = other.data_size; + return *this; + } + iterator begin() const + { + return iterator(data); + } + iterator end() const + { + return iterator(data + data_size); + } + bool empty() const + { + return data_size == 0; + } + size_t size() const + { + return data_size; + } + T* data; + size_t data_size{0}; +}; + +namespace sol +{ +template +struct is_container> : std::true_type +{ +}; + +template +struct usertype_container> +{ + static int size(lua_State* L) + { + ZeroIndexArray& v = sol::stack::get&>(L, 1); + return sol::stack::push(L, v.size()); + } + // Used by default implementation + static auto begin(lua_State*, ZeroIndexArray& self) + { + return self.begin(); + } + static auto end(lua_State*, ZeroIndexArray& self) + { + return self.end(); + } + static std::ptrdiff_t index_adjustment(lua_State*, ZeroIndexArray&) + { + return 0; + } +}; +} // namespace sol diff --git a/src/game_api/script/usertypes/entity_lua.cpp b/src/game_api/script/usertypes/entity_lua.cpp index b693abe30..a8aaec46e 100644 --- a/src/game_api/script/usertypes/entity_lua.cpp +++ b/src/game_api/script/usertypes/entity_lua.cpp @@ -211,6 +211,13 @@ void register_usertypes(sol::state& lua) static_cast(&Entity::overlaps_with), static_cast(&Entity::overlaps_with)); + auto kill_recursive = sol::overload( + static_cast(&Entity::kill_recursive), + static_cast, std::vector, RECURSIVE_MODE)>(&Entity::kill_recursive)); + auto destroy_recursive = sol::overload( + static_cast(&Entity::destroy_recursive), + static_cast, std::vector, RECURSIVE_MODE)>(&Entity::destroy_recursive)); + auto entity_type = lua.new_usertype("Entity"); entity_type["type"] = &Entity::type; entity_type["overlay"] = std::move(overlay); @@ -275,6 +282,8 @@ void register_usertypes(sol::state& lua) entity_type["get_items"] = &Entity::get_items; entity_type["is_in_liquid"] = &Entity::is_in_liquid; entity_type["is_cursed"] = &Entity::is_cursed; + entity_type["kill_recursive"] = kill_recursive; + entity_type["destroy_recursive"] = destroy_recursive; /* Entity // user_data // You can put any arbitrary lua object here for custom entities or player stats, which is then saved across level transitions for players and carried items, mounts etc... This field is local to the script and multiple scripts can write different things in the same entity. The data is saved right before ON.PRE_LOAD_SCREEN from a level and loaded right before ON.POST_LOAD_SCREEN to a level or transition. It is not available yet in post_entity_spawn, but that is a good place to initialize it for new custom entities. See example for more. @@ -372,11 +381,22 @@ void register_usertypes(sol::state& lua) auto name = item.name.substr(9, item.name.size()); lua["ENT_TYPE"][name] = item.id; } - for (auto& elm : get_custom_types_map()) + for (auto& elm : get_custom_types_vector()) { - lua["ENT_TYPE"][elm.second] = elm.first; + lua["ENT_TYPE"][elm.second] = (uint32_t)elm.first; } + lua.create_named_table("RECURSIVE_MODE", "EXCLUSIVE", RECURSIVE_MODE::EXCLUSIVE, "INCLUSIVE", RECURSIVE_MODE::INCLUSIVE, "NONE", RECURSIVE_MODE::NONE); + /* RECURSIVE_MODE + // EXCLUSIVE + // In this mode the provided ENT_TYPE and MASK will not be affected nor will entities attached to them + // INCLUSIVE + // In this mode the provided ENT_TYPE and MASK will be the only affected entities, anything outside of the specified mask or type will not be touched including entities attached to them + // For this mode you have to specify at least one mask or ENT_TYPE, otherwise nothing will be affected + // NONE + // Ignores provided ENT_TYPE and MASK and affects all the entities + */ + lua.create_named_table("REPEAT_TYPE", "NO_REPEAT", REPEAT_TYPE::NoRepeat, "LINEAR", REPEAT_TYPE::Linear, "BACK_AND_FORTH", REPEAT_TYPE::BackAndForth); lua.create_named_table("SHAPE", "RECTANGLE", SHAPE::RECTANGLE, "CIRCLE", SHAPE::CIRCLE); lua.create_named_table("BUTTON", "JUMP", 1, "WHIP", 2, "BOMB", 4, "ROPE", 8, "RUN", 16, "DOOR", 32); diff --git a/src/game_api/script/usertypes/game_manager_lua.cpp b/src/game_api/script/usertypes/game_manager_lua.cpp index 2ab569002..f16cbee0a 100644 --- a/src/game_api/script/usertypes/game_manager_lua.cpp +++ b/src/game_api/script/usertypes/game_manager_lua.cpp @@ -69,6 +69,7 @@ void register_usertypes(sol::state& lua) gamemanager_type["pause_ui"] = &GameManager::pause_ui; gamemanager_type["journal_ui"] = &GameManager::journal_ui; gamemanager_type["save_related"] = &GameManager::save_related; + gamemanager_type["main_menu_music"] = &GameManager::main_menu_music; lua.new_usertype( "SaveRelated", diff --git a/src/game_api/script/usertypes/level_lua.cpp b/src/game_api/script/usertypes/level_lua.cpp index 40eb8226b..c213c903e 100644 --- a/src/game_api/script/usertypes/level_lua.cpp +++ b/src/game_api/script/usertypes/level_lua.cpp @@ -1397,8 +1397,7 @@ void register_usertypes(sol::state& lua) "exit_doors", &LevelGenSystem::exit_doors, "themes", - sol::property([](LevelGenSystem& lgs) - { return std::ref(lgs.themes); }), + &LevelGenSystem::themes, "flags", &LevelGenSystem::flags, "flags2", diff --git a/src/game_api/script/usertypes/sound_lua.cpp b/src/game_api/script/usertypes/sound_lua.cpp index e31b2ae01..29976615a 100644 --- a/src/game_api/script/usertypes/sound_lua.cpp +++ b/src/game_api/script/usertypes/sound_lua.cpp @@ -27,6 +27,7 @@ #include "logger.h" // for DEBUG #include "script/lua_backend.hpp" // for LuaBackend #include "script/safe_cb.hpp" // for make_safe_cb +#include "script/sol_helper.hpp" // #include "sound_manager.hpp" // for CustomSound, PlayingSound, SoundMa... #include "string_aliases.hpp" // for VANILLA_SOUND @@ -167,10 +168,14 @@ void register_usertypes(sol::state& lua, SoundManager* sound_manager) &SoundMeta::x, "y", &SoundMeta::y, - //"left_channel", - //&SoundMeta::left_channel, // TODO: index 0-37 instead of 1-38 - //"right_channel", + "left_channel", + //&SoundMeta::left_channel, + sol::property([](SoundMeta* sm) + { return ZeroIndexArray(sm->left_channel) /**/; }), + "right_channel", //&SoundMeta::right_channel, + sol::property([](SoundMeta* sm) + { return ZeroIndexArray(sm->right_channel) /**/; }), "start_over", &SoundMeta::start_over, "playing", diff --git a/src/game_api/script/usertypes/state_lua.cpp b/src/game_api/script/usertypes/state_lua.cpp index 0e770ea02..87751a4b0 100644 --- a/src/game_api/script/usertypes/state_lua.cpp +++ b/src/game_api/script/usertypes/state_lua.cpp @@ -12,14 +12,15 @@ #include // for move, declval, decay_t, reference_... #include // for min, max -#include "entity.hpp" // IWYU pragma: keep -#include "items.hpp" // for Items, SelectPlayerSlot, Items::is... -#include "level_api.hpp" // IWYU pragma: keep -#include "online.hpp" // for OnlinePlayer, OnlineLobby, Online -#include "screen.hpp" // IWYU pragma: keep -#include "screen_arena.hpp" // IWYU pragma: keep -#include "state.hpp" // for StateMemory, State, StateMemory::a... -#include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems +#include "entities_chars.hpp" // IWYU pragma: keep +#include "entity.hpp" // IWYU pragma: keep +#include "items.hpp" // for Items, SelectPlayerSlot, Items::is... +#include "level_api.hpp" // IWYU pragma: keep +#include "online.hpp" // for OnlinePlayer, OnlineLobby, Online +#include "screen.hpp" // IWYU pragma: keep +#include "screen_arena.hpp" // IWYU pragma: keep +#include "state.hpp" // for StateMemory, State, StateMemory::a... +#include "state_structs.hpp" // for ArenaConfigArenas, ArenaConfigItems namespace NState { @@ -199,6 +200,7 @@ void register_usertypes(sol::state& lua) &SelectPlayerSlot::character, "texture", &SelectPlayerSlot::texture_id); + /// Used in StateMemory lua.new_usertype( "Items", @@ -212,27 +214,17 @@ void register_usertypes(sol::state& lua) &Items::is_pet_cursed, "is_pet_poisoned", &Items::is_pet_poisoned, - - // had to be done this way as autodoc doesn't like sol::property stuff - /*"leader", - &Items::leader,*/ - /*"player_inventory", - &Items::player_inventories,*/ - /*"player_select", - &Items::player_select_slots,*/ - //); stop autodoc here - "leader", sol::property([](Items& s) -> uint8_t { return s.leader + 1; }, [](Items& s, uint8_t leader) { s.leader = leader - 1; }), "player_select", - sol::property([](Items& s) - { return std::ref(s.player_select_slots); }), + &Items::player_select_slots, "player_inventory", - sol::property([](Items& s) - { return std::ref(s.player_inventories); })); + &Items::player_inventories, + "players", + &Items::players); /// Used in LiquidPool lua.new_usertype( @@ -275,8 +267,7 @@ void register_usertypes(sol::state& lua) lua.new_usertype( "LiquidPhysics", "pools", - sol::property([](LiquidPhysics& lp) - { return std::ref(lp.pools) /**/; })); + &LiquidPhysics::pools); lua.create_named_table( "LIQUID_POOL", diff --git a/src/game_api/script/usertypes/vanilla_render_lua.cpp b/src/game_api/script/usertypes/vanilla_render_lua.cpp index 7a569086b..3e3fcf425 100644 --- a/src/game_api/script/usertypes/vanilla_render_lua.cpp +++ b/src/game_api/script/usertypes/vanilla_render_lua.cpp @@ -943,6 +943,6 @@ void register_usertypes(sol::state& lua) hud_type["opacity"] = &Hud::opacity; hud_type["data"] = &Hud::data; - // TODO: maybe add a getter for HudData outside render_hud, I think it's always at 0x22e18020 + lua["get_hud"] = get_hud; }; } // namespace NVanillaRender diff --git a/src/game_api/search.cpp b/src/game_api/search.cpp index 074c11b27..a566dcbd1 100644 --- a/src/game_api/search.cpp +++ b/src/game_api/search.cpp @@ -652,7 +652,7 @@ std::unordered_map g_address_rules{ "spawn_liquid"sv, // See tile code for water (0xea for 1.23.3) in handle_tile_code, last call before returning PatternCommandBuffer{} - .find_inst("\xE8****\xE9****\x48\x81\xC6"sv) + .find_inst("\xE8****\xE9****\x48\x81\xC6"sv) // alternative find_after_inst("41 0F 28 D1 41 B9 90 03 00 00"_gh) .decode_call() .at_exe(), }, @@ -2034,6 +2034,23 @@ std::unordered_map g_address_rules{ .offset(0x21) .at_exe(), }, + { + "hud"sv, + // you can get the address from the render_hud (first parameter), it's global/static, so just find good refrence to it + PatternCommandBuffer{} + .find_after_inst("41 C6 47 6B 01"_gh) + .find_inst("48 8D 0D"_gh) + .decode_pc() + .at_exe(), + }, + { + "enter_closed_door_crash"sv, + // third virtual in behavior of the dog in walking state, the exact line crashing the game when pet tries to enter closed door (tiamat/hundun) + PatternCommandBuffer{} + .find_after_inst("FF 90 A8 00 00 00 48 89 F1"_gh) + .offset(0x5) + .at_exe(), + }, { "unload_layer"sv, // bp on destroy entity, leave level, it's third in stack or something diff --git a/src/game_api/sound_manager.cpp b/src/game_api/sound_manager.cpp index 33c23252b..27a621b20 100644 --- a/src/game_api/sound_manager.cpp +++ b/src/game_api/sound_manager.cpp @@ -894,17 +894,16 @@ int32_t sound_name_to_id(const VANILLA_SOUND s_name) return -1; } -SoundMeta* play_sound(VANILLA_SOUND sound, uint32_t source_uid) +SoundMeta* play_sound_by_id(uint32_t sound_id, uint32_t source_uid) { + if (sound_id == -1) + return nullptr; + using play_sound = SoundMeta*(int32_t); static auto play_sound_func = (play_sound*)get_address("play_sound"); Entity* source = get_entity_ptr(source_uid); SoundMeta* sound_info{nullptr}; - auto sound_id = sound_name_to_id(sound); - - if (sound_id == -1) - return nullptr; if (source_uid == ~0 || source != nullptr) // don't play the sound if the entity is not valid { @@ -915,6 +914,12 @@ SoundMeta* play_sound(VANILLA_SOUND sound, uint32_t source_uid) return sound_info; } +SoundMeta* play_sound(VANILLA_SOUND sound, uint32_t source_uid) +{ + auto sound_id = sound_name_to_id(sound); + return play_sound_by_id(sound_id, source_uid); +} + SoundMeta* construct_soundmeta(VANILLA_SOUND sound, bool background_sound) { return construct_soundmeta(sound_name_to_id(sound), background_sound); diff --git a/src/game_api/sound_manager.hpp b/src/game_api/sound_manager.hpp index 14e5de664..c23011705 100644 --- a/src/game_api/sound_manager.hpp +++ b/src/game_api/sound_manager.hpp @@ -264,10 +264,13 @@ struct SoundMeta { float x; float y; - SoundInfo* sound_info; // param to FMOD::Studio::EventInstance::SetParameterByID (this ptr + 0x30) - uint64_t fmod_param_id; // param to FMOD::Studio::EventInstance::SetParameterByID - std::array left_channel; // VANILLA_SOUND_PARAM - std::array right_channel; // VANILLA_SOUND_PARAM + SoundInfo* sound_info; // param to FMOD::Studio::EventInstance::SetParameterByID (this ptr + 0x30) + uint64_t fmod_param_id; // param to FMOD::Studio::EventInstance::SetParameterByID + + /// Use VANILLA_SOUND_PARAM as index, warning: special case with first index at 0, loop using pairs will get you all results but the key/index will be wrong, ipairs will have correct key/index but will skip the first element + std::array left_channel; + /// Use VANILLA_SOUND_PARAM as index warning: special case with first index at 0, loop using pairs will get you all results but the key/index will be wrong, ipairs will have correct key/index but will skip the first element + std::array right_channel; /// when false, current track starts from the beginning, is immediately set back to true bool start_over; @@ -294,6 +297,7 @@ struct BackgroundSound : public SoundMeta /// Use source_uid to make the sound be played at the location of that entity, set it -1 to just play it "everywhere" /// Returns SoundMeta (read only), beware that after the sound starts, that memory is no longer valid SoundMeta* play_sound(VANILLA_SOUND sound, uint32_t source_uid); +SoundMeta* play_sound_by_id(uint32_t sound_id, uint32_t source_uid); // could probably be exposed if someone can actually figure out how to properly "register it"? // it also needs to make sure the lua owns the returned object and it will properly delete it diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index d3159516e..668e7737a 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -303,6 +303,7 @@ State& State::get() patch_olmec_kill_crash(); patch_liquid_OOB(); patch_ushabti_error(); + patch_entering_closed_door_crash(); } else { diff --git a/src/game_api/state.hpp b/src/game_api/state.hpp index aa5c4d2d6..d74a015b1 100644 --- a/src/game_api/state.hpp +++ b/src/game_api/state.hpp @@ -242,7 +242,7 @@ struct StateMemory Items* items; /// Entrance and exit coordinates, shop types and all themes LevelGenSystem* level_gen; - Layer* layers[2]; + std::array layers; /// Level logic like dice game and cutscenes LogicList* logic; /// NPC quest states diff --git a/src/injected/ui_util.cpp b/src/injected/ui_util.cpp index 69877d960..2f073f953 100644 --- a/src/injected/ui_util.cpp +++ b/src/injected/ui_util.cpp @@ -13,6 +13,7 @@ #include "entities_items.hpp" // for Torch #include "entities_mounts.hpp" // for Mount #include "entity.hpp" // for to_id, Entity, get_entity_ptr +#include "entity_lookup.hpp" // #include "game_manager.hpp" // for get_game_manager, GameManager #include "items.hpp" // for Items #include "layer.hpp" // for Layer, EntityList::Range, Entit...