Custom level generation#750
Conversation
|
Was going to test this out but it appears to be under heavy development still (I downloaded Very excited for it to be stable enough to do more stress testing with. |
| #include "Log.h" | ||
| #include "LuaDungeonGenerator.h" | ||
|
|
||
| DungeonGenerator* GetDungeonGenerator(lua_State* L) { |
There was a problem hiding this comment.
LUALIB_API DungeonGenerator* GetDungeonGenerator(lua_State* L)
I don't know exactly how important the LUALIB_API bit is, but its typically used for such functions. Might impact properly surfacing lua errors.
|
|
||
| std::vector<RoomCoords> forbidden_coords = GetForbiddenNeighbors(base_coords, room_shape, doors); | ||
|
|
||
| for (int i = 0; i < this->num_rooms; i++) { |
There was a problem hiding this comment.
I would suggest having the generator hold a map that ties gridIdx to roomIdx, in order to quickly find the neighboring rooms, and see if a room already occupies this room's slot, as going through the whole room list is not really efficient.
| LUA_FUNCTION(Lua_PlaceDefaultStartingRoom) { | ||
| DungeonGenerator* generator = GetDungeonGenerator(L); | ||
|
|
||
| int doors = (int)luaL_optinteger(L, 2, 15); |
There was a problem hiding this comment.
Since the start room has essentially a predefined config, and GetRandomRoom can only ever pick one room, due to the variant range being set to just the value 2, there is no need to pass the doors parameter.
There was a problem hiding this comment.
The doors parameter limits the allowed doors for the room. It could be useful to mimick the behaviour of the mega satan door/polaroid door
There was a problem hiding this comment.
It would be better to use a system similar to the blocked grid indices, rather than having the user having to deal with doors directly. In the original level gen the doors parameter is meant as a way to specify which doors are necessary, not those that are allowed. Of course, we should still allow control over the doors parameter, when doing very specific things, like how secret rooms and ultra secret room generation has to mark certain doors as allowed, post layout generation.
There was a problem hiding this comment.
I was basing the doors parameter on the LevelGeneratorEntry.doors parameter, since I'm later setting the LevelGeneratorEntry doors field to that. There it does specify the doors argument refers to the allowed doors.
https://repentogon.com/LevelGeneratorEntry.html
There was a problem hiding this comment.
LevelGeneratorEntry is a wrapper for a LevelGenerator_Room and LevelGenerator_Room only specifies the necessary rooms, not the allowed ones. Most likely the parameter was named like that because the necessary rooms end up being used as the allowed rooms when placing the room.
There was a problem hiding this comment.
Bump. IDK if I have input here but need to decide how we want to handle non-standard door layouts for the starting room.
Maybe DM guantol.
| this->shape = room->Shape; | ||
| } | ||
|
|
||
| RoomConfig_Room* DungeonGeneratorRoom::GetRoomConfig(uint32_t seed, int required_doors) { |
There was a problem hiding this comment.
I presume this function was originally meant to do something else due to the unused parameters and unnecessary check for nullptr, what was this supposed to do originally?
There was a problem hiding this comment.
bump.
Remove unused parameter, and simply return this->room;
| this->shape = room->Shape; | ||
| } | ||
|
|
||
| RoomConfig_Room* DungeonGeneratorRoom::GetRoomConfig(uint32_t seed, int required_doors) { |
There was a problem hiding this comment.
bump.
Remove unused parameter, and simply return this->room;
| #include "Log.h" | ||
| #include "LuaDungeonGenerator.h" | ||
|
|
||
| DungeonGenerator* GetDungeonGenerator(lua_State* L) { |
| } | ||
|
|
||
| g_Game->_lastBossRoomListIdx = this->final_boss_index; | ||
|
|
There was a problem hiding this comment.
The DungeonGenerator should support off-grid rooms.
Most likely by adding DungeonGeneratorRoom offGridRooms[21]; (just flip the index)
It should maybe be possible for lua to place RoomConfig_Rooms into these slots (but only by placing RoomConfig_Rooms).
Now, if they were not populated by the mod, we must initialize the following off-grid rooms for (mostly) ALL dungeons:
- Error room (-2)
- Crawlspace dungeon (-4)
- Black market (-6)
- Members card secret shop (-13) (except home & ascent)
- Stairway to heaven angel shop (-18) (except home)
- The Rep+ lil portal room (-20)
- The Beast's room in Home?? (-10)
For generate_dungeon specifically we need these as well, regardless of floor with some exceptions (yes, the game always populates boss rush / mega satan / etc):
- Boss Rush (-5)
- Mega Satan (-7)
- Blue Womb trapdoor (-8) (for all stages except blue womb)
- Void trapdoor (-9) (for blue womb only)
- The "secret exit" room SPECIFICALLY for returning to the main path from Repentance alt path floors (-10)
These will (mostly) boil down to GetRandomRoom calls, but please refer to the 1.7.9b logic (easier to read, tho doesnt help for lil portal) to make sure we do this correctly. Ask for help in the discord thread if needed.
There was a problem hiding this comment.
Added support for all of these except for the last one "The "secret exit" room SPECIFICALLY for returning to the main path from Repentance alt path floors (-10)", I couldn't really see when this room spawns exactly (or at all). Could you specify further?
| { | ||
| bool skip = ProcessGenerateDungeonCallback(this, *rng, DEFAULT); | ||
| if (skip) { | ||
| return; |
There was a problem hiding this comment.
We'll need to confirm if there's anything else we need to do here since we're skipping the vanilla code, like anything the game changes or sets that isnt directly tied to the LevelGenerator portion.
I'll need to defer to Guantol for that, though.
Sync branch
|
|
||
| bool CanRoomBePlaced(XY& base_coords, int shape, int allowed_doors, bool allow_unconnected); | ||
|
|
||
| void BlockPositionsFromAllowedDoords(XY& base_coords, int shape, int allowed_doors); |
There was a problem hiding this comment.
Typo.
Also this is unused.
|
|
||
| #pragma region Helpers | ||
|
|
||
| void PushCoordsIfValid(std::vector<XY>& list, XY& coords) { |
There was a problem hiding this comment.
This method should be static, as it is not exposed in the LevelGenUtils API
There was a problem hiding this comment.
All methods here should be in their a "LevelGenUtils" namespace.
| } | ||
|
|
||
| std::vector<XY> GetOccupiedCoords(XY& base_coords, int shape) { | ||
| std::vector<XY> occupied_coords = {}; |
There was a problem hiding this comment.
Minor optimization: since we know the max amount of occupied coords, we can safely reserve enough space for the vector and avoid reallocations.
| } | ||
|
|
||
| std::vector<XY> GetForbiddenNeighbors(XY& base_coords, int shape, int doors) { | ||
| std::vector<XY> forbidden_neighbors = {}; |
There was a problem hiding this comment.
You can preallocate enough memory for this vector as well.
| bool has_final_room = this->final_boss_index >= 0; | ||
|
|
||
| // Check if all rooms can fetch a room config | ||
| int initial_seed = this->rng->_seed; |
There was a problem hiding this comment.
It might be better to just create a copy of the RNG, that way it's guaranteed there are no side effects.
| } | ||
|
|
||
| int maxVariant = (int)luaL_optinteger(L, 8, -1); | ||
| if (maxVariant < minVariant && maxVariant >= 0) { |
There was a problem hiding this comment.
Not really a problem with this implementation, since this is a copy of the GetRandomRoom checks, but using int instead of unsigned int complicates the logic a bit. -1 is used as a way to quickly reach the maximum unsigned integer, making it "uncapped", rather than being a special value.
| this->ResetLevelGenerator(); | ||
|
|
||
| this->final_boss_index = -1; | ||
| for (size_t i = 0; i < 169; i++) |
There was a problem hiding this comment.
Nitpick: Given that rooms need to be synchronized with the LevelGenerator rooms I would place this reset in ResetLevelGenerator.
| this->level_generator._rooms.clear(); | ||
| } | ||
|
|
||
| void DungeonGenerator::Reset() { |
There was a problem hiding this comment.
Offgrid rooms are not reset.
| DungeonGeneratorRoom rooms[169]; | ||
| DungeonGeneratorRoom off_grid_rooms[20]; | ||
| RNG* rng; | ||
| Level* level; |
Added a new callback and a couple functions that allow for mods to generate custom floors.
MC_PRE_GENERATE_DUNGEON
Called before
level::generate_dungeonis called, which places all the rooms in a floor. Passes an RNG.Return true to cancel vanilla floor generation.
Level:ResetRoomList(bool)
Needs to be called before placing any rooms in the custom level.
The bool is the same as the one in
level::init. Testing it so far, setting it to true makes it work all the time.Level:SetLastBossRoomListIndex(int)
Also needs to be called after creating the custom floor, since not setting it crashes the game when continuing.
Doesn't actually need to be set to a boss room for the game to not crash, but I haven't tested any other side effects apart from boss rooms not spawning the trapdoor if they are not the last.
Sample
