diff --git a/libzhl/LuaCore.cpp b/libzhl/LuaCore.cpp index c746d3fe8..e6a175e3e 100644 --- a/libzhl/LuaCore.cpp +++ b/libzhl/LuaCore.cpp @@ -838,6 +838,8 @@ namespace lua { const char* LootListEntryMT = "LootListEntry"; const char* MinimapConfigMT = "MinimapConfig"; const char* EntityDescMT = "EntityDesc"; + const char* DungeonGeneratorMT = "DungeonGenerator"; + const char* DungeonGeneratorRoomMT = "DungeonGeneratorRoom"; } void TableAssoc(lua_State* L, std::string const& name, int value) { diff --git a/libzhl/LuaCore.h b/libzhl/LuaCore.h index 9d1ef9685..792cf6f09 100644 --- a/libzhl/LuaCore.h +++ b/libzhl/LuaCore.h @@ -257,6 +257,8 @@ namespace lua { extern LIBZHL_API const char* LootListEntryMT; extern LIBZHL_API const char* MinimapConfigMT; extern LIBZHL_API const char* EntityDescMT; + extern LIBZHL_API const char* DungeonGeneratorMT; + extern LIBZHL_API const char* DungeonGeneratorRoomMT; } LIBZHL_API void UnloadMetatables(); diff --git a/libzhl/functions/Level.zhl b/libzhl/functions/Level.zhl index 93bfc950a..407f79aea 100644 --- a/libzhl/functions/Level.zhl +++ b/libzhl/functions/Level.zhl @@ -2,11 +2,14 @@ __thiscall void Level::SetStage(int stageid,int alt); "538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????505383ec68a1????????33c58945??5657508d45??64a3????????8bf9897d??8b0d": -__thiscall void Level::Init(bool unkinitStartRoom); +__thiscall void Level::Init(bool resetLilPortalRoom); "518b89????????e8": __thiscall void Level::Update(); +"558bec6aff68????????64a1????????5081ecc0000000a1????????33c58945??535657508d45??64a3????????8bc1": +__thiscall void Level::reset_room_list(bool resetLilPortalRoom); + "558bec6aff68????????64a1????????5083ec2c535657a1????????33c5508d45??64a3????????8bd98b45": __thiscall void Level::ChangeRoom(int targetRoomIDX, int dimension); @@ -61,6 +64,21 @@ __thiscall int Level::GetRandomRoomIndex(bool IAmErrorRoom, unsigned int Seed); "538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????505381ec50040000": __thiscall void Level::generate_dungeon(RNG * RNG); +"558bec6aff68????????64a1????????5081ecd4030000a1????????33c58945??535657508d45??64a3????????8bd9": +__thiscall void Level::generate_home_dungeon(); + +"538bdc83ec0883e4f883c404558b6b??896c24??8bec6aff68????????64a1????????505381ec30040000": +__thiscall void Level::generate_backwards_dungeon(); + +"558bec6aff68????????64a1????????5081ecc4030000": +__thiscall void Level::generate_redkey_dungeon(); + +"558bec6aff68????????64a1????????5081ecdc030000": +__thiscall void Level::generate_blue_womb(); + +"558bec6aff68????????64a1????????5081ecec030000": +__thiscall void Level::generate_greed_dungeon(); + "558bec6aff68????????64a1????????5083ec68a1????????33c58945??535657508d45??64a3????????8bd98b55": __thiscall bool Level::place_rooms_backwards(LevelGenerator* levelGen, BackwardsStageDesc* backwardsStage, uint32_t stage, uint32_t minDifficulty, uint32_t maxDifficulty); diff --git a/repentogon/LuaInterfaces/CustomCallbacks.cpp b/repentogon/LuaInterfaces/CustomCallbacks.cpp index 8b9facead..6ba570f9a 100644 --- a/repentogon/LuaInterfaces/CustomCallbacks.cpp +++ b/repentogon/LuaInterfaces/CustomCallbacks.cpp @@ -15,6 +15,7 @@ #include "../Patches/MainMenuBlock.h" #include "../Patches/XMLData.h" #include "../Patches/EntityPlus.h" +#include "LuaDungeonGenerator.h" //Callback tracking for optimizations std::bitset<500> CallbackState; // For new REPENTOGON callbacks. I dont think we will add 500 callbacks but lets set it there for now @@ -5646,4 +5647,90 @@ HOOK_STATIC(LuaEngine, PostEntityKill, (Entity* ent) -> void, __stdcall) { .push(lastSource, lua::Metatables::ENTITY_REF) .call(1); } +} + + +//MC_PRE_GENERATE_DUNGEON(1340) +bool ProcessGenerateDungeonCallback(Level* level, RNG& rng, DungeonGenerationType dungeonType) { + const int callbackId = 1340; + if (!CallbackState.test(callbackId - 1000)) { + return false; + } + + lua_State* L = g_LuaEngine->_state; + lua::LuaStackProtector protector(L); + lua_rawgeti(L, LUA_REGISTRYINDEX, g_LuaEngine->runCallbackRegistry->key); + + DungeonGenerator generator(&rng, level); + lua::LuaResults results = lua::LuaCaller(L) + .push(callbackId) + .push((int)dungeonType) + .push(&generator, lua::metatables::DungeonGeneratorMT) + .push(&rng, lua::Metatables::RNG) + .push((int)dungeonType) + .call(1); + + if (results || !lua_isboolean(L, -1) || !lua_toboolean(L, -1)) + { + return false; + } + + bool correctGeneration = generator.Generate(); + + return correctGeneration; +} + +HOOK_METHOD(Level, generate_dungeon, (RNG* rng) -> void) +{ + bool skip = ProcessGenerateDungeonCallback(this, *rng, DEFAULT); + if (skip) { + return; + } + + super(rng); +} + +HOOK_METHOD(Level, generate_blue_womb, () -> void) { + bool skip = ProcessGenerateDungeonCallback(this, g_Game->_generationRNG, BLUE_WOMB); + if (skip) { + return; + } + + super(); +} + +HOOK_METHOD(Level, generate_backwards_dungeon, () -> void) { + bool skip = ProcessGenerateDungeonCallback(this, g_Game->_generationRNG, BACKWARDS); + if (skip) { + return; + } + + super(); +} + +HOOK_METHOD(Level, generate_home_dungeon, () -> void) { + bool skip = ProcessGenerateDungeonCallback(this, g_Game->_generationRNG, HOME); + if (skip) { + return; + } + + super(); +} + +HOOK_METHOD(Level, generate_redkey_dungeon, () -> void) { + bool skip = ProcessGenerateDungeonCallback(this, g_Game->_generationRNG, RED_REDEMPTION); + if (skip) { + return; + } + + super(); +} + +HOOK_METHOD(Level, generate_greed_dungeon, () -> void) { + bool skip = ProcessGenerateDungeonCallback(this, g_Game->_generationRNG, GREED); + if (skip) { + return; + } + + super(); } \ No newline at end of file diff --git a/repentogon/LuaInterfaces/LuaDungeonGenerator.cpp b/repentogon/LuaInterfaces/LuaDungeonGenerator.cpp new file mode 100644 index 000000000..3b19865c4 --- /dev/null +++ b/repentogon/LuaInterfaces/LuaDungeonGenerator.cpp @@ -0,0 +1,344 @@ +#include "HookSystem.h" +#include "IsaacRepentance.h" +#include "LuaCore.h" +#include "Exception.h" +#include "Log.h" +#include "LuaDungeonGenerator.h" +#include "vector" +#include "../Utils/LevelGenUtils.h" + +#pragma region DungeonGeneratorRoom Impl + +DungeonGeneratorRoom::DungeonGeneratorRoom() { + this->list_index = -1; + + this->room = nullptr; + this->col = -1; + this->row = -1; + + this->doors = -1; + this->shape = -1; +} + +DungeonGeneratorRoom::DungeonGeneratorRoom(int list_index, RoomConfig_Room* room, uint32_t col, uint32_t row, int doors) { + this->list_index = list_index; + + this->room = room; + this->col = col; + this->row = row; + this->doors = doors; + + this->shape = room->Shape; +} + +RoomConfig_Room* DungeonGeneratorRoom::GetRoomConfig(uint32_t seed, int required_doors) { + if (this->room != nullptr) { + return this->room; + } + + return nullptr; +} + +#pragma endregion + +#pragma region DungeonGenerator Impl + +DungeonGenerator::DungeonGenerator(RNG* rng, Level* level) { + this->rng = rng; + this->level = level; + + this->level_generator._rng = *rng; + this->level_generator._isChapter6 = false; + this->level_generator._isStageVoid = false; + this->level_generator._isXL = false; + this->ResetLevelGenerator(); +} + +bool DungeonGenerator::CanRoomBePlaced(XY& base_coords, int shape, int allowed_doors, bool allow_unconnected) { + int base_grid_index = base_coords.ToGridIdx(); + if (!this->level_generator.IsPositionInBounds(base_coords)) { + return false; + } + + if (!this->level_generator.is_pos_free(&base_coords, shape)) { + return false; + } + + if (!allow_unconnected) { + if (!this->level_generator.is_placement_valid((unsigned int*)&base_grid_index, shape)) { + return false; + } + } + + std::vector forbbidden_neighbors = GetForbiddenNeighbors(base_coords, shape, allowed_doors); + for (XY coords : forbbidden_neighbors) { + int grid_index = coords.ToGridIdx(); + if (this->level_generator._roomMap[grid_index] > -1) { + return false; + } + } + + return true; +} + +void DungeonGenerator::BlockPositionsFromAllowedDoords(XY& base_coords, int shape, int allowed_doors) { + std::vector forbbidden_neighbors = GetForbiddenNeighbors(base_coords, shape, allowed_doors); + for (XY coords : forbbidden_neighbors) { + this->level_generator.BlockPosition(coords); + } +} + +DungeonGeneratorRoom* DungeonGenerator::PlaceRoom(RoomConfig_Room* room_config, uint32_t col, uint32_t row, int doors) { + LevelGenerator_Room level_generator_room; + level_generator_room._gridColIdx = col; + level_generator_room._gridLineIdx = row; + level_generator_room._shape = room_config->Shape; + bool result = this->level_generator.place_room(&level_generator_room); + + LevelGenerator_Room placed_room = this->level_generator._rooms.at(this->level_generator._rooms.size() - 1); + + int new_room_list_index = placed_room._generationIndex; + + this->rooms[new_room_list_index] = DungeonGeneratorRoom(new_room_list_index, room_config, col, row, doors); + DungeonGeneratorRoom* generatorRoom = &this->rooms[new_room_list_index]; + + return generatorRoom; +} + +void DungeonGenerator::SetFinalBossRoom(DungeonGeneratorRoom* boss_room) { + this->final_boss_index = boss_room->list_index; +} + +bool DungeonGenerator::ValidateFloor() { + bool has_final_room = this->final_boss_index >= 0; + + // Check if all rooms can fetch a room config + int initial_seed = this->rng->_seed; + + for (LevelGenerator_Room room : this->level_generator._rooms) + { + DungeonGeneratorRoom generator_room = this->rooms[room._generationIndex]; + RoomConfig_Room* room_config = generator_room.GetRoomConfig(this->rng->Next(), room._doors); + + if (room_config == nullptr) { + return false; + } + + // When placing the rooms the rng is advanced here too. + this->rng->Next(); + } + + this->rng->_seed = initial_seed; + + return has_final_room; +} + +void DungeonGenerator::CleanFloor() { + this->level->reset_room_list(false); + + for (size_t i = 0; i < 507; i++) + { + g_Game->_roomOffset[i] = -1; + } + + g_Game->_nbRooms = 0; +} + +void DungeonGenerator::ResetLevelGenerator() { + std::fill_n(this->level_generator._roomMap, 169, -1); + std::fill_n(this->level_generator._blockedPositions, 169, false); + + this->level_generator._rooms.clear(); +} + +void DungeonGenerator::Reset() { + this->CleanFloor(); + + this->ResetLevelGenerator(); + + this->final_boss_index = -1; + for (size_t i = 0; i < 169; i++) + { + this->rooms[i] = DungeonGeneratorRoom(); + } +} + +bool DungeonGenerator::PlaceRoomsInFloor() { + this->level_generator.calc_required_doors(); + + for (LevelGenerator_Room room : this->level_generator._rooms) + { + DungeonGeneratorRoom generator_room = this->rooms[room._generationIndex]; + RoomConfig_Room* room_config = generator_room.GetRoomConfig(this->rng->Next(), room._doors); + + if (room_config == nullptr) { + return false; + } + + uint32_t seed = this->rng->Next(); + + g_Game->PlaceRoom(&room, room_config, seed, 0); + } + + g_Game->_lastBossRoomListIdx = this->final_boss_index; + + return true; +} + +bool DungeonGenerator::Generate() { + if (!this->ValidateFloor()) { + KAGE::_LogMessage(1, "[WARN] Failed to validate custom floor, not placing rooms.\n"); + Reset(); + + return false; + } + + CleanFloor(); + + bool could_place_rooms = PlaceRoomsInFloor(); + if (!could_place_rooms) { + KAGE::_LogMessage(1, "[WARN] Couldn't place the rooms in the level, clearing placed rooms...\n"); + Reset(); + } + + return could_place_rooms; +} + +#pragma endregion + +DungeonGenerator* GetDungeonGenerator(lua_State* L) { + return *lua::GetRawUserdata(L, 1, lua::metatables::DungeonGeneratorMT); +} + +LUA_FUNCTION(Lua_PlaceRoom) { + DungeonGenerator* generator = GetDungeonGenerator(L); + RoomConfig_Room* config = lua::GetLuabridgeUserdata(L, 2, lua::Metatables::CONST_ROOM_CONFIG_ROOM, "RoomConfig"); + uint32_t col = (uint32_t)luaL_checkinteger(L, 3); + uint32_t row = (uint32_t)luaL_checkinteger(L, 4); + int allowed_doors = (int)luaL_optinteger(L, 5, 255); + + // Can't have more doors than what the config allows. + allowed_doors = allowed_doors & config->Doors; + + XY coords(col, row); + + if (generator->CanRoomBePlaced(coords, config->Shape, allowed_doors, true)) { + DungeonGeneratorRoom* generator_room = generator->PlaceRoom(config, col, row, allowed_doors); + + DungeonGeneratorRoom** ud = (DungeonGeneratorRoom**)lua_newuserdata(L, sizeof(DungeonGeneratorRoom*)); + *ud = generator_room; + luaL_setmetatable(L, lua::metatables::DungeonGeneratorRoomMT); + + return 1; + } else { + return 0; + } +} + +LUA_FUNCTION(Lua_PlaceDefaultStartingRoom) { + DungeonGenerator* generator = GetDungeonGenerator(L); + + int doors = (int)luaL_optinteger(L, 2, 15); + + uint32_t col = 6; + uint32_t row = 6; + + XY coords(col, row); + + if (generator->CanRoomBePlaced(coords, ROOMSHAPE_1x1, doors, true)) { + unsigned int required_doors = 0; + + RoomConfig* room_config = g_Game->GetRoomConfig(); + RoomConfig_Room* config = room_config->GetRandomRoom( + generator->rng->Next(), + false, + STB_SPECIAL_ROOMS, + ROOM_DEFAULT, + ROOMSHAPE_1x1, + 2, + 2, + 0, + 10, + &required_doors, // If I don't do it like this it shits itself + 0, + -1 + ); + + DungeonGeneratorRoom* generator_room = generator->PlaceRoom(config, col, row, doors); + + DungeonGeneratorRoom** ud = (DungeonGeneratorRoom**)lua_newuserdata(L, sizeof(DungeonGeneratorRoom*)); + *ud = generator_room; + luaL_setmetatable(L, lua::metatables::DungeonGeneratorRoomMT); + } + else { + lua_pushnil(L); + } + + return 1; +} + +LUA_FUNCTION(Lua_SetFinalBossRoom) { + DungeonGenerator* generator = GetDungeonGenerator(L); + DungeonGeneratorRoom* generator_room = *lua::GetRawUserdata(L, 2, lua::metatables::DungeonGeneratorRoomMT); + + generator->SetFinalBossRoom(generator_room); + + return 0; +} + +LUA_FUNCTION(Lua_BlockIndex) { + DungeonGenerator* generator = GetDungeonGenerator(L); + int grid_index = (int)luaL_checkinteger(L, 2); + + generator->level_generator.BlockPosition(grid_index); + + return 0; +} + +LUA_FUNCTION(Lua_Validate) { + DungeonGenerator* generator = GetDungeonGenerator(L); + + bool result = generator->ValidateFloor(); + + lua_pushboolean(L, result); + + return 1; +} + +LUA_FUNCTION(Lua_Reset) { + DungeonGenerator* generator = GetDungeonGenerator(L); + + generator->Reset(); + + return 0; +} + + +static void RegisterDungeonGenerator(lua_State* L) { + luaL_Reg functions[] = { + {"PlaceRoom", Lua_PlaceRoom}, + {"SetFinalBossRoom", Lua_SetFinalBossRoom}, + {"PlaceDefaultStartingRoom", Lua_PlaceDefaultStartingRoom}, + {"BlockIndex", Lua_BlockIndex}, + {"Validate", Lua_Validate}, + {"Reset", Lua_Reset}, + { NULL, NULL } + }; + + lua::RegisterNewClass(L, lua::metatables::DungeonGeneratorMT, lua::metatables::DungeonGeneratorMT, functions); +} + +static void RegisterDungeonGeneratorRoom(lua_State* L) { + luaL_Reg functions[] = { + { NULL, NULL } + }; + + lua::RegisterNewClass(L, lua::metatables::DungeonGeneratorRoomMT, lua::metatables::DungeonGeneratorRoomMT, functions); +} + + +HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) { + super(); + RegisterDungeonGenerator(_state); + RegisterDungeonGeneratorRoom(_state); +} \ No newline at end of file diff --git a/repentogon/LuaInterfaces/LuaDungeonGenerator.h b/repentogon/LuaInterfaces/LuaDungeonGenerator.h new file mode 100644 index 000000000..f6a131aac --- /dev/null +++ b/repentogon/LuaInterfaces/LuaDungeonGenerator.h @@ -0,0 +1,57 @@ +#pragma once + +enum DungeonGenerationType { + DEFAULT, + BLUE_WOMB, + BACKWARDS, + HOME, + RED_REDEMPTION, + GREED, +}; + +struct DungeonGeneratorRoom { + RoomConfig_Room* room; + uint32_t col; + uint32_t row; + int doors; + int shape; + + int list_index; + + DungeonGeneratorRoom(); + + DungeonGeneratorRoom(int list_index, RoomConfig_Room* room, uint32_t row, uint32_t col, int doors); + + RoomConfig_Room* GetRoomConfig(uint32_t seed, int required_doors); +}; + +struct DungeonGenerator { + DungeonGeneratorRoom rooms[169]; + RNG* rng; + Level* level; + LevelGenerator level_generator; + + int final_boss_index = -1; + + DungeonGenerator(RNG* rng, Level* level); + + bool CanRoomBePlaced(XY& base_coords, int shape, int allowed_doors, bool allow_unconnected); + + void BlockPositionsFromAllowedDoords(XY& base_coords, int shape, int allowed_doors); + + DungeonGeneratorRoom* PlaceRoom(RoomConfig_Room* room_config, uint32_t row, uint32_t col, int doors); + + void SetFinalBossRoom(DungeonGeneratorRoom* boss_room); + + bool ValidateFloor(); + + void CleanFloor(); + + bool DungeonGenerator::PlaceRoomsInFloor(); + + bool Generate(); + + void Reset(); + + void ResetLevelGenerator(); +}; \ No newline at end of file diff --git a/repentogon/Utils/LevelGenUtils.cpp b/repentogon/Utils/LevelGenUtils.cpp new file mode 100644 index 000000000..7566fba4e --- /dev/null +++ b/repentogon/Utils/LevelGenUtils.cpp @@ -0,0 +1,294 @@ +#include "LevelGenUtils.h" +#include +#include + +#pragma region Helpers + +void PushCoordsIfValid(std::vector& list, XY& coords) { + if (coords.ToGridIdx() >= 0) { + list.push_back(coords); + } +} + +std::vector GetOccupiedCoords(XY& base_coords, int shape) { + std::vector occupied_coords = {}; + + switch (shape) + { + case ROOMSHAPE_1x1: + case ROOMSHAPE_IH: + case ROOMSHAPE_IV: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + break; + case ROOMSHAPE_1x2: + case ROOMSHAPE_IIV: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x, base_coords.y + 1)); + break; + case ROOMSHAPE_2x1: + case ROOMSHAPE_IIH: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y)); + break; + case ROOMSHAPE_2x2: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x, base_coords.y + 1)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y + 1)); + break; + case ROOMSHAPE_LTL: + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x, base_coords.y + 1)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y + 1)); + break; + case ROOMSHAPE_LTR: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x, base_coords.y + 1)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y + 1)); + break; + case ROOMSHAPE_LBL: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y + 1)); + break; + case ROOMSHAPE_LBR: + occupied_coords.push_back(XY(base_coords.x, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x + 1, base_coords.y)); + occupied_coords.push_back(XY(base_coords.x, base_coords.y + 1)); + break; + default: + break; + } + + return occupied_coords; +} + +std::vector GetForbiddenNeighbors(XY& base_coords, int shape, int doors) { + std::vector forbidden_neighbors = {}; + + switch (shape) + { + case ROOMSHAPE_1x1: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + } + break; + case ROOMSHAPE_IH: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + break; + case ROOMSHAPE_IV: + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y)); + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + } + break; + case ROOMSHAPE_1x2: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 2)); + } + if ((doors & (1 << DOOR_SLOT_LEFT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 1)); + } + break; + case ROOMSHAPE_IIV: + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y)); + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y + 1)); + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 1)); + break; + case ROOMSHAPE_2x1: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_UP1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_DOWN1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 1)); + } + break; + case ROOMSHAPE_IIH: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y)); + } + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y - 1)); + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 1)); + break; + case ROOMSHAPE_2x2: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 2)); + } + if ((doors & (1 << DOOR_SLOT_LEFT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_UP1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_DOWN1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 2)); + } + break; + case ROOMSHAPE_LTL: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0 || (doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 2)); + } + if ((doors & (1 << DOOR_SLOT_LEFT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_UP1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_DOWN1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 2)); + } + break; + case ROOMSHAPE_LTR: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0 || (doors & (1 << DOOR_SLOT_UP1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 2)); + } + if ((doors & (1 << DOOR_SLOT_LEFT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_DOWN1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 2)); + } + break; + case ROOMSHAPE_LBL: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0 || (doors & (1 << DOOR_SLOT_LEFT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_UP1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_DOWN1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 2)); + } + break; + case ROOMSHAPE_LBR: + if ((doors & (1 << DOOR_SLOT_LEFT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_UP0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 2, base_coords.y)); + } + if ((doors & (1 << DOOR_SLOT_DOWN0)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x, base_coords.y + 2)); + } + if ((doors & (1 << DOOR_SLOT_LEFT1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x - 1, base_coords.y + 1)); + } + if ((doors & (1 << DOOR_SLOT_UP1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y - 1)); + } + if ((doors & (1 << DOOR_SLOT_RIGHT1)) == 0 || (doors & (1 << DOOR_SLOT_DOWN1)) == 0) { + PushCoordsIfValid(forbidden_neighbors, XY(base_coords.x + 1, base_coords.y + 1)); + } + break; + default: + break; + } + + return forbidden_neighbors; +} + +#pragma endregion \ No newline at end of file diff --git a/repentogon/Utils/LevelGenUtils.h b/repentogon/Utils/LevelGenUtils.h new file mode 100644 index 000000000..8be4f0e6f --- /dev/null +++ b/repentogon/Utils/LevelGenUtils.h @@ -0,0 +1,8 @@ +#pragma once +#include +#include +#include + +std::vector GetOccupiedCoords(XY& base_coords, int shape); + +std::vector GetForbiddenNeighbors(XY& base_coords, int shape, int doors); \ No newline at end of file diff --git a/repentogon/resources/scripts/enums_ex.lua b/repentogon/resources/scripts/enums_ex.lua index c63e47ced..9ec3ab52c 100644 --- a/repentogon/resources/scripts/enums_ex.lua +++ b/repentogon/resources/scripts/enums_ex.lua @@ -266,6 +266,8 @@ ModCallbacks.MC_PRE_PICKUP_UPDATE_GHOST_PICKUPS = 1335 --ModCallbacks.MC_PRE_NPC_GET_LOOT_LIST = 1336 (Reserved for the future) --ModCallbacks.MC_PRE_NPC_UPDATE_GHOST_PICKUPS = 1337 (Reserved for the future) +ModCallbacks.MC_PRE_GENERATE_DUNGEON = 1340 + ModCallbacks.MC_PRE_PLAYER_ADD_CARD = 1350 ModCallbacks.MC_POST_PLAYER_ADD_CARD = 1351 ModCallbacks.MC_PRE_PLAYER_ADD_PILL = 1352 @@ -3080,6 +3082,14 @@ UseActiveItemResultFlag = { REMOVE = 1 << 8, } +GenerateDungeonType = { + NORMAL = 0, + BLUE_WOMB = 1, + BACKWARDS = 2, + HOME = 3, + RED_REDEMPTION = 4, + GREED = 5 +} --deprecated enums diff --git a/repentogon/resources/scripts/main_ex.lua b/repentogon/resources/scripts/main_ex.lua index acfbc6a0d..522fcd9ec 100644 --- a/repentogon/resources/scripts/main_ex.lua +++ b/repentogon/resources/scripts/main_ex.lua @@ -1687,6 +1687,21 @@ local function RunPreStatusEffectApplyCallback(callbackID, param, status, entity return recentRet end +local function RunPreGenerateDungeonCallback(callbackID, param, dungeonGenerator, rng, dungeonType) + for callback in GetCallbackIterator(callbackID, param) do + local ret = RunCallbackInternal(callbackID, callback, dungeonGenerator, rng, dungeonType) + + if type(ret) == "boolean" and ret then + local canGenerate = dungeonGenerator:Validate() + if canGenerate then + return true + else + dungeonGenerator:Reset() + end + end + end +end + -- I don't think we need these exposed anymore, but safer to just leave them alone since they were already exposed. rawset(Isaac, "RunPreRenderCallback", _RunPreRenderCallback) rawset(Isaac, "RunAdditiveCallback", _RunAdditiveCallback) @@ -1728,6 +1743,7 @@ local CustomRunCallbackLogic = { [ModCallbacks.MC_PRE_ADD_TRINKET] = RunPreAddTrinketCallback, [ModCallbacks.MC_POST_ADD_COLLECTIBLE] = RunNoReturnCallback, [ModCallbacks.MC_PLAYER_GET_HEART_LIMIT] = RunAdditiveSecondArgCallback, + [ModCallbacks.MC_PRE_GENERATE_DUNGEON] = RunPreGenerateDungeonCallback } for _, callback in ipairs({