Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8b17521
Reset room list function
thicco-catto Nov 2, 2025
7c047e1
Added bool parameter
thicco-catto Nov 2, 2025
ea8320c
Added callback enum
thicco-catto Nov 2, 2025
d2bf067
Merge branch 'TeamREPENTOGON:main' into level-gen
thicco-catto Nov 3, 2025
2490b78
Added RNG parameter to callback
thicco-catto Nov 3, 2025
8f8c157
Added dungeon generator controller
thicco-catto Nov 4, 2025
f9fb019
Removed test functions and fixed final boss not set
thicco-catto Nov 4, 2025
f65b5f5
Callback triggers for more dungeon types
thicco-catto Nov 4, 2025
d313242
Filter by generation type
thicco-catto Nov 4, 2025
e2f77b4
Moved custom callback to appropiate file
thicco-catto Nov 5, 2025
32e508d
Fixed return values for lua functions
thicco-catto Nov 5, 2025
1feba7f
Renamed lua functions
thicco-catto Nov 5, 2025
bfab985
PlaceRoom returns the room
thicco-catto Nov 6, 2025
845072c
Generator returns bool
thicco-catto Nov 6, 2025
c2cc975
Dungeon generator has own RNG
thicco-catto Nov 7, 2025
47dbdd9
Refactored floor generation
thicco-catto Nov 7, 2025
7d5ce08
Moved implementations to source file
thicco-catto Nov 7, 2025
57a718d
Added room shape check and swapped col/row
thicco-catto Nov 7, 2025
05c839a
Added door validation
thicco-catto Nov 7, 2025
9a7e37e
Added shape field to DungeonGeneratorRoom
thicco-catto Nov 7, 2025
40605fd
Added PlaceDefaultStartingRoom
thicco-catto Nov 7, 2025
797a52d
Fixed constructors
thicco-catto Nov 7, 2025
8ad7897
Added dungeon type param to callback
thicco-catto Nov 7, 2025
2e6f9f4
Changed some pointers to references
thicco-catto Nov 7, 2025
e1b7d98
Changed resetLilPortalRoom param
thicco-catto Nov 8, 2025
bd2415e
Removed unnecesary include
thicco-catto Nov 8, 2025
52c50b5
Use bitset to check room placement
thicco-catto Nov 8, 2025
c308a3e
Created utils file and generation type enum
thicco-catto Nov 8, 2025
0bbb155
Enum is passed as number to callback
thicco-catto Nov 8, 2025
d89ff98
DungeonGenerator now holds index for final boss
thicco-catto Nov 8, 2025
fc5098b
Removed some debug logs
thicco-catto Nov 8, 2025
6edd184
Refactored DungeonGenerator to use LevelGenerator
thicco-catto Nov 9, 2025
2c37191
Removed ununsed fields
thicco-catto Nov 10, 2025
86ff2bb
Added BlockIndex function
thicco-catto Nov 11, 2025
062d157
Changed optional return
thicco-catto Nov 11, 2025
4732779
Added custom callback logic
thicco-catto Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libzhl/LuaCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ namespace lua {
const char* LootListEntryMT = "LootListEntry";
const char* MinimapConfigMT = "MinimapConfig";
const char* EntityDescMT = "EntityDesc";
const char* DungeonGeneratorMT = "DungeonGenerator";
}

void TableAssoc(lua_State* L, std::string const& name, int value) {
Expand Down
1 change: 1 addition & 0 deletions libzhl/LuaCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ 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;
}

LIBZHL_API void UnloadMetatables();
Expand Down
18 changes: 18 additions & 0 deletions libzhl/functions/Level.zhl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ __thiscall void Level::Init(bool unkinitStartRoom);
"518b89????????e8":
__thiscall void Level::Update();

"558bec6aff68????????64a1????????5081ecc0000000a1????????33c58945??535657508d45??64a3????????8bc1":
__thiscall void Level::reset_room_list(bool unkinitStartRoom);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nitpick, change the name of the bool parameter to 'resetLilPortalRoom'


"558bec6aff68????????64a1????????5083ec2c535657a1????????33c5508d45??64a3????????8bd98b45":
__thiscall void Level::ChangeRoom(int targetRoomIDX, int dimension);

Expand Down Expand Up @@ -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);

Expand Down
62 changes: 62 additions & 0 deletions repentogon/LuaInterfaces/LuaDungeonGenerator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "HookSystem.h"
#include "IsaacRepentance.h"
#include "LuaCore.h"
#include "Exception.h"
#include "Log.h"
#include "LuaDungeonGenerator.h"

DungeonGenerator* GetDungeonGenerator(lua_State* L) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

return *lua::GetRawUserdata<DungeonGenerator**>(L, 1, lua::metatables::DungeonGeneratorMT);
}

LUA_FUNCTION(place_room) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Use names like Lua_PlaceRoom for the lua bindings, mostly just a consistency thing (not that our consistency is fantastic overall)

DungeonGenerator* generator = GetDungeonGenerator(L);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good for DungeonGenerator itself to contain all its functions such as PlaceRoom (ie, generator->PlaceRoom(config, row, col, seed), and the lua bindings just parse the inputs from lua and redirect to the generator's functions. This will separate the DungeonGenerator logic from the lua bindings, and allow DungeonGenerator code to call its own functions.

RoomConfig_Room* config = lua::GetLuabridgeUserdata<RoomConfig_Room*>(L, 2, lua::Metatables::CONST_ROOM_CONFIG_ROOM, "RoomConfig");
uint32_t row = (uint32_t)luaL_checkinteger(L, 3);
uint32_t col = (uint32_t)luaL_checkinteger(L, 4);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate the row/col (both bounds checking and verifying that there isn't already a room in that slot)

In theory we'll also need to do other stuff like handling overlapping large rooms and checking door slots, but you don't HAVE to add that right now, unless you want to

uint32_t seed = (uint32_t)luaL_checkinteger(L, 5);

DungeonGeneratorRoom* generatorRoom = new DungeonGeneratorRoom(config, row, col, seed);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This initializes a DungeonGeneratorRoom, and then saves a COPY of it into the array on the next line. The original is also a memory leak.

I think you can initialize it like this:

generator->rooms[generator->num_rooms] = DungeonGeneratorRoom(config, row, col seed);
generator->num_rooms++;
DungeonGeneratorRoom* generatorRoom = &generator->rooms[generator->num_rooms];

This should initialize a DungeonGeneratorRoom inline and assigns it into the array (no copying and no memory management required).

The three example line above would probably be good as a small helper function (ie, DungeonGeneratorRoom* generatorRoom = generator->CreateRoom();?

generator->rooms[generator->num_rooms] = *generatorRoom;

generator->num_rooms++;

return 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested in external discussion: Return DungeonGeneratorRoom

Example:

DungeonGeneratorRoom** ud = (DungeonGeneratorRoom**)lua_newuserdata(L, sizeof(DungeonGeneratorRoom*));
*ud = generatorRoom;
luaL_setmetatable(L, lua::metatables::DungeonGeneratorRoomMT);

return 1;

}

LUA_FUNCTION(set_final_boss_room) {
DungeonGenerator* generator = GetDungeonGenerator(L);
uint32_t row = (uint32_t)luaL_checkinteger(L, 2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested in external discussion: Take a DungeonGeneratorRoom instead of column+row

Should we enforce that this is only used on a room with a Boss Room config?

uint32_t col = (uint32_t)luaL_checkinteger(L, 3);

for (size_t i = 0; i < generator->num_rooms; i++) {
DungeonGeneratorRoom* generator_room = &(generator->rooms[i]);

if (generator_room->room != NULL) {
if (row == generator_room->row && col == generator_room->col) {
generator_room->is_final_boss = true;
} else {
generator_room->is_final_boss = false;
}
} else {
break;
}
}

return 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For lua bindings, return 1 means that the lua function returned something back to lua. Specifically, I believe the number is how many things were pushed to the lua stack.

If we dont return anything, we should return 0

Though, if we add any validation, this function could maybe return true on success (lua_pushboolean(L, value);)

}

static void RegisterDungeonGenerator(lua_State* L) {
luaL_Reg functions[] = {
{"PlaceRoom", place_room},
{"SetFinalBossRoom", set_final_boss_room},
{ NULL, NULL }
};

lua::RegisterNewClass(L, lua::metatables::DungeonGeneratorMT, lua::metatables::DungeonGeneratorMT, functions);
}

HOOK_METHOD(LuaEngine, RegisterClasses, () -> void) {
super();
RegisterDungeonGenerator(_state);
}
34 changes: 34 additions & 0 deletions repentogon/LuaInterfaces/LuaDungeonGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

struct DungeonGeneratorRoom {
RoomConfig_Room* room;
uint32_t row;
uint32_t col;
uint32_t seed;
bool is_final_boss = false;

DungeonGeneratorRoom() {
this->room = NULL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Prefer nullptr instead of NULL

this->row = -1;
this->col = -1;
this->seed = -1;
}

DungeonGeneratorRoom(RoomConfig_Room* room, uint32_t row, uint32_t col, uint32_t seed) {
this->room = room;
this->row = row;
this->col = col;
this->seed = seed;
}
};

struct DungeonGenerator {
int a;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a?

int num_rooms;
DungeonGeneratorRoom rooms[169];

DungeonGenerator() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice if we initialize DungeonGenerator using an RNG* (ie, the one the game provided)

That way it won't be necessary for the user to specify seeds, and allows the DungeonGenerator to do seeded rng at will

a = 0;
num_rooms = 0;
}
};
105 changes: 105 additions & 0 deletions repentogon/Patches/ASMPatches/ASMLevel.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code added here is actually in the wrong place, technically. This directory is primarily for Assembly (ASM) Patches

Since this is just function hooks, should probably move this stuff to repentogon/LuaInterfaces/CustomCallbacks.cpp

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "ASMLevel.h"
#include "../../LuaInterfaces/Level.h"
#include "../../LuaInterfaces/Room/Room.h"
#include "../../LuaInterfaces/LuaDungeonGenerator.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding this include is unused, so it should be removed.


std::bitset<36> generateLevels;

Expand Down Expand Up @@ -82,8 +83,67 @@ void ASMPatchVoidGeneration() {
sASMPatcher.PatchAt(addr, &patch);
}

//MC_PRE_GENERATE_DUNGEON(1340)
bool ProcessGenerateDungeonCallback(Level* level, RNG* rng, int 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 = new DungeonGenerator();
lua::LuaResults results = lua::LuaCaller(L)
.push(callbackId)
.push(dungeonType)
.push(generator, lua::metatables::DungeonGeneratorMT)
.push(rng, lua::Metatables::RNG)
.call(1);

if (results || !lua_isboolean(L, -1) || !lua_toboolean(L, -1))
{
return false;
}

level->reset_room_list(false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it might make sense for the logic to reset & build out the floor be a DungeonGenerator function (like generator->Generate())

May also be good for it to return a success boolean, in case we decide it failed somehow, we could maybe fall back to allowing the vanilla generation to happen again.


for (size_t i = 0; i < 507; i++)
{
g_Game->_roomOffset[i] = -1;
}

g_Game->_nbRooms = 0;

for (size_t i = 0; i < generator->num_rooms; i++)
{
DungeonGeneratorRoom generator_room = generator->rooms[i];

if (generator_room.room != NULL) {
LevelGenerator_Room* level_generator_room = new LevelGenerator_Room();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As pointed out by Guantol, this is a memory leak atm

You can initialize a local variable class/struct as just LevelGenerator_Room level_generator_room; and it will be cleaned up when it leaves scope as you'd expect. This also calls the default constructor (for classes at least) automatically where applicable.

Initializing classes with new requires explicit freeing later, though there's usually better options than new

level_generator_room->_gridColIdx = generator_room.col;
level_generator_room->_gridLineIdx = generator_room.row;
level_generator_room->_doors = 15;

g_Game->PlaceRoom(level_generator_room, generator_room.room, generator_room.seed, 0);

if (generator_room.is_final_boss) {
g_Game->_lastBossRoomListIdx = i;
}
}
}

return true;
}

HOOK_METHOD(Level, generate_dungeon, (RNG* rng) -> void)
{
bool skip = ProcessGenerateDungeonCallback(this, rng, 0);
if (skip) {
return;
}

if (this->_stage == 12)
{
if (generateLevels.any())
Expand Down Expand Up @@ -117,6 +177,51 @@ HOOK_METHOD(Level, generate_dungeon, (RNG* rng) -> void)
super(rng);
}

HOOK_METHOD(Level, generate_blue_womb, () -> void) {
bool skip = ProcessGenerateDungeonCallback(this, NULL, 1);
if (skip) {
return;
}

super();
}

HOOK_METHOD(Level, generate_backwards_dungeon, () -> void) {
bool skip = ProcessGenerateDungeonCallback(this, NULL, 2);
if (skip) {
return;
}

super();
}

HOOK_METHOD(Level, generate_home_dungeon, () -> void) {
bool skip = ProcessGenerateDungeonCallback(this, NULL, 3);
if (skip) {
return;
}

super();
}

HOOK_METHOD(Level, generate_redkey_dungeon, () -> void) {
bool skip = ProcessGenerateDungeonCallback(this, NULL, 4);
if (skip) {
return;
}

super();
}

HOOK_METHOD(Level, generate_greed_dungeon, () -> void) {
bool skip = ProcessGenerateDungeonCallback(this, NULL, 5);
if (skip) {
return;
}

super();
}

bool __stdcall SpawnSpecialQuestDoorValidStageTypeCheck() {
// Always return true if we forced it via Room:TrySpawnSpecialQuestDoor
bool ret = roomASM.ForceSpecialQuestDoor;
Expand Down
10 changes: 10 additions & 0 deletions repentogon/resources/scripts/enums_ex.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down