#include "global.h" #include "file_system.h" #include "tile_types.h" #include "dungeon_map_access.h" #include "dungeon_random.h" #include "constants/direction.h" #include "structs/str_dungeon.h" #include "structs/map.h" extern const u8 gUnknown_80F6DCC[]; extern struct FileArchive gDungeonFileArchive; extern bool8 gUnknown_202F1AA; extern u8 gUnknown_202F1AB; extern u8 gUnknown_202F1AC; extern u8 gUnknown_202F1A8; extern u8 gUnknown_202F1B4; extern u8 gUnknown_202F1AE; extern u8 gUnknown_202F1AD; extern u8 gUnknown_202F1A9; extern s16 gUnknown_202F1B0; extern s16 gUnknown_202F1B2; extern s32 gUnknown_202F1C8; extern s32 gUnknown_202F1D0; extern s32 gUnknown_202F1CC; extern Position gUnknown_202F1D8; extern struct MinMaxPosition gUnknown_202F1B8; extern void sub_804FD30(void); extern void sub_804FD30(void); extern void sub_80518F0(void); extern void sub_804FCCC(void); extern void GenerateOneRoomMonsterHouseFloor(void); extern void sub_804FBE8(void); extern void sub_804FC74(void); void sub_804EB30(void); void sub_804E9DC(void); extern void GenerateTwoRoomsWithMonsterHouseFloor(void); extern u8 sub_8043D10(); extern bool8 sub_8050C30(s32 a0, s32 a1, u8 a2); extern void sub_806C330(s32 a0, s32 a1, s16 a2, u8 a3); extern const Position gAdjacentTileOffsets[]; void sub_804B534(s32 a0, s32 a1, s32 a2, s32 a3); bool8 sub_804C70C(s32, UnkDungeonGlobal_unk1C574 *); void GenerateStandardFloor(s32 a0, s32 a1, UnkDungeonGlobal_unk1C574 *a2); void GenerateOuterRingFloor(UnkDungeonGlobal_unk1C574 *a0); void GenerateCrossroadsFloor(UnkDungeonGlobal_unk1C574 *a0); void GenerateLineFloor(UnkDungeonGlobal_unk1C574 *a0); void GenerateCrossFloor(UnkDungeonGlobal_unk1C574 *a0); void GenerateBeetleFloor(UnkDungeonGlobal_unk1C574 *a0); void GenerateOuterRoomsFloor(s32 gridSizeX_, s32 gridSizeY_, UnkDungeonGlobal_unk1C574 *unkPtr); void sub_8051654(UnkDungeonGlobal_unk1C574 *a0); void sub_80506F0(s32 a0, UnkDungeonGlobal_unk1C574 *a1); void sub_804FF08(UnkDungeonGlobal_unk1C574 *a0, bool8 a1); void sub_8050438(UnkDungeonGlobal_unk1C574 *a0, bool8 a1); struct FileFixedmapPosStruct { u8 x; u8 y; }; // Helper functions for terrain flags static inline void SetTerrainType(Tile *tile, u32 terrainType) { tile->terrainType &= ~(TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY); tile->terrainType |= terrainType; } static inline void SetTerrainNormal(Tile *tile) { SetTerrainType(tile, TERRAIN_TYPE_NORMAL); } static inline void SetTerrainSecondary(Tile *tile) { SetTerrainType(tile, TERRAIN_TYPE_SECONDARY); } static inline void SetTerrainWall(Tile *tile) { SetTerrainType(tile, TERRAIN_TYPE_WALL); } static inline u32 GetTerrainType(Tile *tile) { return tile->terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY); } // Some weird-ass regswap prevents the function from being matched - https://decomp.me/scratch/9SUV3 #ifdef NONMATCHING void sub_804AFAC(void) { s32 x, y; s32 i, j, k; bool8 r10 = FALSE; UnkDungeonGlobal_unk1C574 *unkPtr = &gDungeon->unk1C574; gDungeon->unk13568 = OpenFileAndGetFileDataPtr(gUnknown_80F6DCC, &gDungeonFileArchive); gUnknown_202F1AA = FALSE; gUnknown_202F1AB = 0; gUnknown_202F1AC = 0; gUnknown_202F1A8 = (gDungeonWaterType[gDungeon->tileset] == DUNGEON_WATER_TYPE_WATER); gUnknown_202F1B4 = 0xFF; gUnknown_202F1AE = 0; gUnknown_202F1B0 = unkPtr->unk7; gUnknown_202F1B2 = unkPtr->unk8; gUnknown_202F1AD = 1; gUnknown_202F1B8.unk0 = -1; gUnknown_202F1B8.unk8 = -1; gUnknown_202F1B8.unk4 = -1; gUnknown_202F1B8.unkC = -1; sub_804FD30(); gDungeon->unk664 = unkPtr->unk6; gDungeon->unk3A09 = 0; gDungeon->unk3A0A = 0; gUnknown_202F1C8 = unkPtr->unkC; for (i = 0; i < 10; i++) { bool32 r4 = 0; gDungeon->unkE218.x = -1; gDungeon->unkE218.y = -1; gDungeon->unkE21C.x = -1; gDungeon->unkE21C.y = -1; for (j = 0; j < 10; j++) { gDungeon->unk3A16 = j; if (j > 0) { gUnknown_202F1C8 = 0; } gUnknown_202F1A9 = 0; gUnknown_202F1D8.x = -1; gUnknown_202F1D8.y = -1; sub_804FD30(); gDungeon->unkE218.x = -1; gDungeon->unkE218.y = -1; gDungeon->unk3A08 = 0; if (gDungeon->bossBattleIndex != 0) { if (sub_804C70C(gDungeon->bossBattleIndex, unkPtr)) { break; } } else { u8 r7 = unkPtr->unk0; k = 32; while (1) { if (r7 != 8) { x = DungeonRandRange(2, 9); y = DungeonRandRange(2, 8); } else { x = DungeonRandRange(2, 5); y = DungeonRandRange(2, 4); } if (x <= 6 && y <= 4) { break; } if (--k == 0) { x = 4; y = 4; break; } } if (DUNGEON_MAX_SIZE_X / x < 8) { x = 1; } if (DUNGEON_MAX_SIZE_Y / y < 8) { y = 1; } gDungeon->unk3A08 = 0; gDungeon->unk3A0C = 0xFF; gUnknown_202F1D0 = r7; switch (r7 & 0xF) { case 1: y = DungeonRandInt(2) + 2; gUnknown_202F1AE = 1; GenerateStandardFloor(4, y, unkPtr); r10 = TRUE; break; case 11: y = DungeonRandInt(2) + 2; gUnknown_202F1AE = 2; GenerateStandardFloor(4, y, unkPtr); r10 = TRUE; break; case 0: default: GenerateStandardFloor(x, y, unkPtr); r10 = TRUE; break; case 2: GenerateOneRoomMonsterHouseFloor(); gDungeon->unk3A08 = 1; break; case 3: GenerateOuterRingFloor(unkPtr); r10 = TRUE; break; case 4: GenerateCrossroadsFloor(unkPtr); r10 = TRUE; break; case 5: GenerateTwoRoomsWithMonsterHouseFloor(); gDungeon->unk3A08 = 1; break; case 6: GenerateLineFloor(unkPtr); r10 = TRUE; break; case 7: GenerateCrossFloor(unkPtr); break; case 9: GenerateBeetleFloor(unkPtr); break; case 10: GenerateOuterRoomsFloor(x, y, unkPtr); r10 = TRUE; break; } } sub_80518F0(); sub_804FCCC(); if (gUnknown_202F1A9 == 0) { s32 countSp = 0; u8 spArray[64]; s32 countRooms = 0; for (k = 0; k < 64; k++) { spArray[k] = 0; } for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) { for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) { Tile *tile = GetTile(x, y); if ((tile->terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)) == TERRAIN_TYPE_NORMAL && tile->room <= 240) { countRooms++; if (tile->room < 64) { spArray[tile->room] = 1; } } } } countSp = 0; // In order to match, 'y' had to be re-used as an iterator var for (y = 0; y < 64; y++) { if (spArray[y]) { countSp++; } } if (countRooms >= 30 && countSp > 1) { break; } } } if (j == 10) { gUnknown_202F1D8.x = -1; gUnknown_202F1D8.y = -1; GenerateOneRoomMonsterHouseFloor(); gDungeon->unk3A08 = 1; } sub_804E9DC(); if (r10) { sub_80506F0(1, unkPtr); } r4 = (DungeonRandInt(100) < unkPtr->connectedToTop); sub_804FF08(unkPtr, r4); sub_8050438(unkPtr, r4); sub_804FBE8(); if (gDungeon->unkE218.x != -1 && gDungeon->unkE218.y != -1) { if (sub_8043D10() == 1) break; if (gDungeon->unkE21C.x != -1 && gDungeon->unkE21C.y != -1 && sub_8050C30(gDungeon->unkE21C.x, gDungeon->unkE21C.y, 0)) break; } } if (i == 10) { gUnknown_202F1D8.x = -1; gUnknown_202F1D8.y = -1; sub_804FD30(); GenerateOneRoomMonsterHouseFloor(); gDungeon->unk3A08 = 1; sub_804E9DC(); sub_804FF08(unkPtr, FALSE); sub_8050438(unkPtr, FALSE); sub_804FBE8(); } if (gUnknown_202F1D8.x >= 0 && gUnknown_202F1D8.y >= 0) { sub_806C330(gUnknown_202F1D8.x, gUnknown_202F1D8.y, 380, 0); } if (gUnknown_202F1B8.unk0 >= 0) { sub_8051654(unkPtr); gDungeon->unk3A0A = 1; } else { gDungeon->unk3A0A = 0; } sub_804B534(0, 0, DUNGEON_MAX_SIZE_X, DUNGEON_MAX_SIZE_Y); if (gUnknown_202F1A8 != 0) { sub_804FC74(); } CloseFile(gDungeon->unk13568); } #else void NAKED sub_804AFAC(void) { asm_unified(" push {r4-r7,lr}\n" " mov r7, r10\n" " mov r6, r9\n" " mov r5, r8\n" " push {r5-r7}\n" " sub sp, 0x44\n" " movs r0, 0\n" " mov r10, r0\n" " ldr r4, _0804B0E8\n" " ldr r0, [r4]\n" " ldr r1, _0804B0EC\n" " adds r1, r0\n" " mov r8, r1\n" " ldr r0, _0804B0F0\n" " ldr r1, _0804B0F4\n" " bl OpenFileAndGetFileDataPtr\n" " ldr r2, [r4]\n" " ldr r3, _0804B0F8\n" " adds r1, r2, r3\n" " str r0, [r1]\n" " ldr r0, _0804B0FC\n" " mov r6, r10\n" " strb r6, [r0]\n" " ldr r0, _0804B100\n" " strb r6, [r0]\n" " ldr r0, _0804B104\n" " strb r6, [r0]\n" " ldr r5, _0804B108\n" " movs r3, 0\n" " ldr r1, _0804B10C\n" " ldr r0, _0804B110\n" " adds r2, r0\n" " movs r6, 0\n" " ldrsh r0, [r2, r6]\n" " adds r0, r1\n" " ldrb r0, [r0]\n" " cmp r0, 0x2\n" " bne _0804AFFC\n" " movs r3, 0x1\n" "_0804AFFC:\n" " strb r3, [r5]\n" " ldr r1, _0804B114\n" " movs r0, 0xFF\n" " strb r0, [r1]\n" " ldr r0, _0804B118\n" " mov r1, r10\n" " strb r1, [r0]\n" " ldr r1, _0804B11C\n" " mov r2, r8\n" " ldrb r0, [r2, 0x7]\n" " strh r0, [r1]\n" " ldr r1, _0804B120\n" " ldrb r0, [r2, 0x8]\n" " strh r0, [r1]\n" " ldr r1, _0804B124\n" " movs r0, 0x1\n" " strb r0, [r1]\n" " ldr r1, _0804B128\n" " movs r0, 0x1\n" " negs r0, r0\n" " str r0, [r1]\n" " str r0, [r1, 0x8]\n" " str r0, [r1, 0x4]\n" " str r0, [r1, 0xC]\n" " bl sub_804FD30\n" " ldr r3, [r4]\n" " mov r6, r8\n" " ldrb r2, [r6, 0x6]\n" " ldr r1, _0804B12C\n" " adds r0, r3, r1\n" " movs r1, 0\n" " strh r2, [r0]\n" " ldr r2, _0804B130\n" " adds r0, r3, r2\n" " strb r1, [r0]\n" " ldr r0, [r4]\n" " ldr r3, _0804B134\n" " adds r0, r3\n" " strb r1, [r0]\n" " ldr r1, _0804B138\n" " mov r6, r8\n" " ldrb r0, [r6, 0xC]\n" " str r0, [r1]\n" " movs r0, 0\n" " str r0, [sp, 0x40]\n" "_0804B058:\n" " ldr r0, _0804B0E8\n" " ldr r2, [r0]\n" " ldr r3, _0804B13C\n" " adds r1, r2, r3\n" " ldr r0, _0804B140\n" " strh r0, [r1]\n" " ldr r6, _0804B144\n" " adds r1, r2, r6\n" " movs r0, 0x1\n" " negs r0, r0\n" " strh r0, [r1]\n" " ldr r0, _0804B148\n" " adds r1, r2, r0\n" " movs r0, 0x1\n" " negs r0, r0\n" " strh r0, [r1]\n" " ldr r1, _0804B14C\n" " adds r2, r1\n" " strh r0, [r2]\n" " movs r2, 0\n" " mov r9, r2\n" "_0804B082:\n" " ldr r4, _0804B0E8\n" " ldr r0, [r4]\n" " ldr r3, _0804B150\n" " adds r0, r3\n" " movs r5, 0\n" " movs r1, 0\n" " mov r6, r9\n" " strh r6, [r0]\n" " mov r0, r9\n" " cmp r0, 0\n" " ble _0804B09C\n" " ldr r0, _0804B138\n" " str r1, [r0]\n" "_0804B09C:\n" " ldr r0, _0804B154\n" " strb r5, [r0]\n" " ldr r1, _0804B158\n" " ldr r0, _0804B140\n" " strh r0, [r1]\n" " movs r0, 0x1\n" " negs r0, r0\n" " strh r0, [r1, 0x2]\n" " bl sub_804FD30\n" " ldr r1, [r4]\n" " ldr r3, _0804B13C\n" " adds r2, r1, r3\n" " movs r0, 0x1\n" " negs r0, r0\n" " strh r0, [r2]\n" " ldr r6, _0804B144\n" " adds r2, r1, r6\n" " strh r0, [r2]\n" " ldr r0, _0804B15C\n" " adds r1, r0\n" " strb r5, [r1]\n" " ldr r0, [r4]\n" " ldr r2, _0804B160\n" " adds r1, r0, r2\n" " movs r3, 0\n" " ldrsh r0, [r1, r3]\n" " cmp r0, 0\n" " beq _0804B164\n" " mov r1, r8\n" " bl sub_804C70C\n" " lsls r0, 24\n" " cmp r0, 0\n" " beq _0804B0E4\n" " b _0804B376\n" "_0804B0E4:\n" " b _0804B2F6\n" " .align 2, 0\n" "_0804B0E8: .4byte gDungeon\n" "_0804B0EC: .4byte 0x0001c574\n" "_0804B0F0: .4byte gUnknown_80F6DCC\n" "_0804B0F4: .4byte gDungeonFileArchive\n" "_0804B0F8: .4byte 0x00013568\n" "_0804B0FC: .4byte gUnknown_202F1AA\n" "_0804B100: .4byte gUnknown_202F1AB\n" "_0804B104: .4byte gUnknown_202F1AC\n" "_0804B108: .4byte gUnknown_202F1A8\n" "_0804B10C: .4byte gDungeonWaterType\n" "_0804B110: .4byte 0x00003a0e\n" "_0804B114: .4byte gUnknown_202F1B4\n" "_0804B118: .4byte gUnknown_202F1AE\n" "_0804B11C: .4byte gUnknown_202F1B0\n" "_0804B120: .4byte gUnknown_202F1B2\n" "_0804B124: .4byte gUnknown_202F1AD\n" "_0804B128: .4byte gUnknown_202F1B8\n" "_0804B12C: .4byte 0x00000664\n" "_0804B130: .4byte 0x00003a09\n" "_0804B134: .4byte 0x00003a0a\n" "_0804B138: .4byte gUnknown_202F1C8\n" "_0804B13C: .4byte 0x0000e218\n" "_0804B140: .4byte 0x0000ffff\n" "_0804B144: .4byte 0x0000e21a\n" "_0804B148: .4byte 0x0000e21c\n" "_0804B14C: .4byte 0x0000e21e\n" "_0804B150: .4byte 0x00003a16\n" "_0804B154: .4byte gUnknown_202F1A9\n" "_0804B158: .4byte gUnknown_202F1D8\n" "_0804B15C: .4byte 0x00003a08\n" "_0804B160: .4byte 0x00003a14\n" "_0804B164:\n" " mov r0, r8\n" " ldrb r7, [r0]\n" " movs r6, 0x20\n" "_0804B16A:\n" " cmp r7, 0x8\n" " beq _0804B17E\n" " movs r0, 0x2\n" " movs r1, 0x9\n" " bl DungeonRandRange\n" " adds r5, r0, 0\n" " movs r0, 0x2\n" " movs r1, 0x8\n" " b _0804B18C\n" "_0804B17E:\n" " movs r0, 0x2\n" " movs r1, 0x5\n" " bl DungeonRandRange\n" " adds r5, r0, 0\n" " movs r0, 0x2\n" " movs r1, 0x4\n" "_0804B18C:\n" " bl DungeonRandRange\n" " adds r4, r0, 0\n" " cmp r5, 0x6\n" " bgt _0804B19A\n" " cmp r4, 0x4\n" " ble _0804B1A4\n" "_0804B19A:\n" " subs r6, 0x1\n" " cmp r6, 0\n" " bne _0804B16A\n" " movs r5, 0x4\n" " movs r4, 0x4\n" "_0804B1A4:\n" " movs r0, 0x38\n" " adds r1, r5, 0\n" " bl __divsi3\n" " cmp r0, 0x7\n" " bgt _0804B1B2\n" " movs r5, 0x1\n" "_0804B1B2:\n" " movs r0, 0x20\n" " adds r1, r4, 0\n" " bl __divsi3\n" " cmp r0, 0x7\n" " bgt _0804B1C0\n" " movs r4, 0x1\n" "_0804B1C0:\n" " ldr r2, _0804B1EC\n" " ldr r0, [r2]\n" " ldr r1, _0804B1F0\n" " adds r0, r1\n" " movs r1, 0\n" " strb r1, [r0]\n" " ldr r0, [r2]\n" " ldr r2, _0804B1F4\n" " adds r0, r2\n" " movs r1, 0xFF\n" " strb r1, [r0]\n" " ldr r0, _0804B1F8\n" " str r7, [r0]\n" " movs r0, 0xF\n" " ands r0, r7\n" " cmp r0, 0xB\n" " bhi _0804B274\n" " lsls r0, 2\n" " ldr r1, _0804B1FC\n" " adds r0, r1\n" " ldr r0, [r0]\n" " mov pc, r0\n" " .align 2, 0\n" "_0804B1EC: .4byte gDungeon\n" "_0804B1F0: .4byte 0x00003a08\n" "_0804B1F4: .4byte 0x00003a0c\n" "_0804B1F8: .4byte gUnknown_202F1D0\n" "_0804B1FC: .4byte _0804B200\n" " .align 2, 0\n" "_0804B200:\n" " .4byte _0804B274\n" " .4byte _0804B230\n" " .4byte _0804B280\n" " .4byte _0804B298\n" " .4byte _0804B2A4\n" " .4byte _0804B2B0\n" " .4byte _0804B2CC\n" " .4byte _0804B2D8\n" " .4byte _0804B274\n" " .4byte _0804B2E0\n" " .4byte _0804B2E8\n" " .4byte _0804B250\n" "_0804B230:\n" " movs r0, 0x2\n" " bl DungeonRandInt\n" " adds r4, r0, 0x2\n" " ldr r1, _0804B24C\n" " movs r0, 0x1\n" " strb r0, [r1]\n" " movs r0, 0x4\n" " adds r1, r4, 0\n" " mov r2, r8\n" " bl GenerateStandardFloor\n" " b _0804B2AA\n" " .align 2, 0\n" "_0804B24C: .4byte gUnknown_202F1AE\n" "_0804B250:\n" " movs r0, 0x2\n" " bl DungeonRandInt\n" " adds r4, r0, 0x2\n" " ldr r1, _0804B270\n" " movs r0, 0x2\n" " strb r0, [r1]\n" " movs r0, 0x4\n" " adds r1, r4, 0\n" " mov r2, r8\n" " bl GenerateStandardFloor\n" " movs r6, 0x1\n" " mov r10, r6\n" " b _0804B2F6\n" " .align 2, 0\n" "_0804B270: .4byte gUnknown_202F1AE\n" "_0804B274:\n" " adds r0, r5, 0\n" " adds r1, r4, 0\n" " mov r2, r8\n" " bl GenerateStandardFloor\n" " b _0804B2D2\n" "_0804B280:\n" " bl GenerateOneRoomMonsterHouseFloor\n" " ldr r0, _0804B290\n" " ldr r0, [r0]\n" " ldr r1, _0804B294\n" " adds r0, r1\n" " b _0804B2BC\n" " .align 2, 0\n" "_0804B290: .4byte gDungeon\n" "_0804B294: .4byte 0x00003a08\n" "_0804B298:\n" " mov r0, r8\n" " bl GenerateOuterRingFloor\n" " movs r2, 0x1\n" " mov r10, r2\n" " b _0804B2F6\n" "_0804B2A4:\n" " mov r0, r8\n" " bl GenerateCrossroadsFloor\n" "_0804B2AA:\n" " movs r3, 0x1\n" " mov r10, r3\n" " b _0804B2F6\n" "_0804B2B0:\n" " bl GenerateTwoRoomsWithMonsterHouseFloor\n" " ldr r0, _0804B2C4\n" " ldr r0, [r0]\n" " ldr r6, _0804B2C8\n" " adds r0, r6\n" "_0804B2BC:\n" " movs r1, 0x1\n" " strb r1, [r0]\n" " b _0804B2F6\n" " .align 2, 0\n" "_0804B2C4: .4byte gDungeon\n" "_0804B2C8: .4byte 0x00003a08\n" "_0804B2CC:\n" " mov r0, r8\n" " bl GenerateLineFloor\n" "_0804B2D2:\n" " movs r0, 0x1\n" " mov r10, r0\n" " b _0804B2F6\n" "_0804B2D8:\n" " mov r0, r8\n" " bl GenerateCrossFloor\n" " b _0804B2F6\n" "_0804B2E0:\n" " mov r0, r8\n" " bl GenerateBeetleFloor\n" " b _0804B2F6\n" "_0804B2E8:\n" " adds r0, r5, 0\n" " adds r1, r4, 0\n" " mov r2, r8\n" " bl GenerateOuterRoomsFloor\n" " movs r1, 0x1\n" " mov r10, r1\n" "_0804B2F6:\n" " bl sub_80518F0\n" " bl sub_804FCCC\n" " ldr r0, _0804B4B4\n" " ldrb r0, [r0]\n" " cmp r0, 0\n" " bne _0804B36A\n" " movs r7, 0\n" " movs r1, 0\n" " mov r0, sp\n" " adds r0, 0x3F\n" "_0804B30E:\n" " strb r1, [r0]\n" " subs r0, 0x1\n" " cmp r0, sp\n" " bge _0804B30E\n" " movs r5, 0\n" "_0804B318:\n" " movs r4, 0\n" " adds r6, r5, 0x1\n" "_0804B31C:\n" " adds r0, r5, 0\n" " adds r1, r4, 0\n" " bl GetTile\n" " adds r1, r0, 0\n" " ldrh r0, [r1]\n" " movs r2, 0x3\n" " ands r2, r0\n" " cmp r2, 0x1\n" " bne _0804B340\n" " ldrb r0, [r1, 0x9]\n" " cmp r0, 0xF0\n" " bhi _0804B340\n" " adds r7, 0x1\n" " cmp r0, 0x3F\n" " bhi _0804B340\n" " add r0, sp\n" " strb r2, [r0]\n" "_0804B340:\n" " adds r4, 0x1\n" " cmp r4, 0x1F\n" " ble _0804B31C\n" " adds r5, r6, 0\n" " cmp r5, 0x37\n" " ble _0804B318\n" " movs r1, 0\n" " movs r4, 0\n" "_0804B350:\n" " mov r2, sp\n" " adds r0, r2, r4\n" " ldrb r0, [r0]\n" " cmp r0, 0\n" " beq _0804B35C\n" " adds r1, 0x1\n" "_0804B35C:\n" " adds r4, 0x1\n" " cmp r4, 0x3F\n" " ble _0804B350\n" " cmp r7, 0x1D\n" " ble _0804B36A\n" " cmp r1, 0x1\n" " bgt _0804B376\n" "_0804B36A:\n" " movs r3, 0x1\n" " add r9, r3\n" " mov r6, r9\n" " cmp r6, 0x9\n" " bgt _0804B376\n" " b _0804B082\n" "_0804B376:\n" " mov r0, r9\n" " cmp r0, 0xA\n" " bne _0804B398\n" " ldr r1, _0804B4B8\n" " ldr r0, _0804B4BC\n" " strh r0, [r1]\n" " movs r0, 0x1\n" " negs r0, r0\n" " strh r0, [r1, 0x2]\n" " bl GenerateOneRoomMonsterHouseFloor\n" " ldr r0, _0804B4C0\n" " ldr r0, [r0]\n" " ldr r1, _0804B4C4\n" " adds r0, r1\n" " movs r1, 0x1\n" " strb r1, [r0]\n" "_0804B398:\n" " bl sub_804E9DC\n" " mov r2, r10\n" " cmp r2, 0\n" " beq _0804B3AA\n" " movs r0, 0x1\n" " mov r1, r8\n" " bl sub_80506F0\n" "_0804B3AA:\n" " movs r0, 0x64\n" " bl DungeonRandInt\n" " movs r4, 0\n" " mov r3, r8\n" " ldrb r3, [r3, 0x19]\n" " cmp r0, r3\n" " bge _0804B3BC\n" " movs r4, 0x1\n" "_0804B3BC:\n" " mov r0, r8\n" " adds r1, r4, 0\n" " bl sub_804FF08\n" " mov r0, r8\n" " adds r1, r4, 0\n" " bl sub_8050438\n" " bl sub_804FBE8\n" " ldr r5, _0804B4C0\n" " ldr r1, [r5]\n" " ldr r6, _0804B4C8\n" " adds r0, r1, r6\n" " movs r2, 0\n" " ldrsh r0, [r0, r2]\n" " movs r4, 0x1\n" " negs r4, r4\n" " cmp r0, r4\n" " beq _0804B42A\n" " ldr r3, _0804B4CC\n" " adds r0, r1, r3\n" " movs r6, 0\n" " ldrsh r0, [r0, r6]\n" " cmp r0, r4\n" " beq _0804B42A\n" " bl sub_8043D10\n" " lsls r0, 24\n" " lsrs r0, 24\n" " cmp r0, 0x1\n" " beq _0804B436\n" " ldr r1, [r5]\n" " ldr r0, _0804B4D0\n" " adds r2, r1, r0\n" " movs r3, 0\n" " ldrsh r0, [r2, r3]\n" " cmp r0, r4\n" " beq _0804B42A\n" " ldr r6, _0804B4D4\n" " adds r1, r6\n" " movs r3, 0\n" " ldrsh r0, [r1, r3]\n" " cmp r0, r4\n" " beq _0804B42A\n" " movs r6, 0\n" " ldrsh r0, [r2, r6]\n" " movs r2, 0\n" " ldrsh r1, [r1, r2]\n" " movs r2, 0\n" " bl sub_8050C30\n" " lsls r0, 24\n" " cmp r0, 0\n" " bne _0804B436\n" "_0804B42A:\n" " ldr r3, [sp, 0x40]\n" " adds r3, 0x1\n" " str r3, [sp, 0x40]\n" " cmp r3, 0x9\n" " bgt _0804B436\n" " b _0804B058\n" "_0804B436:\n" " ldr r6, [sp, 0x40]\n" " cmp r6, 0xA\n" " bne _0804B474\n" " ldr r1, _0804B4B8\n" " ldr r0, _0804B4BC\n" " strh r0, [r1]\n" " movs r0, 0x1\n" " negs r0, r0\n" " strh r0, [r1, 0x2]\n" " bl sub_804FD30\n" " bl GenerateOneRoomMonsterHouseFloor\n" " ldr r0, _0804B4C0\n" " ldr r0, [r0]\n" " ldr r1, _0804B4C4\n" " adds r0, r1\n" " movs r1, 0x1\n" " strb r1, [r0]\n" " bl sub_804E9DC\n" " mov r0, r8\n" " movs r1, 0\n" " bl sub_804FF08\n" " mov r0, r8\n" " movs r1, 0\n" " bl sub_8050438\n" " bl sub_804FBE8\n" "_0804B474:\n" " ldr r1, _0804B4B8\n" " movs r2, 0\n" " ldrsh r0, [r1, r2]\n" " cmp r0, 0\n" " blt _0804B498\n" " movs r3, 0x2\n" " ldrsh r0, [r1, r3]\n" " cmp r0, 0\n" " blt _0804B498\n" " movs r6, 0\n" " ldrsh r0, [r1, r6]\n" " movs r2, 0x2\n" " ldrsh r1, [r1, r2]\n" " movs r2, 0xBE\n" " lsls r2, 1\n" " movs r3, 0\n" " bl sub_806C330\n" "_0804B498:\n" " ldr r0, _0804B4D8\n" " ldr r0, [r0]\n" " cmp r0, 0\n" " blt _0804B4E0\n" " mov r0, r8\n" " bl sub_8051654\n" " ldr r0, _0804B4C0\n" " ldr r0, [r0]\n" " ldr r3, _0804B4DC\n" " adds r0, r3\n" " movs r1, 0x1\n" " b _0804B4EA\n" " .align 2, 0\n" "_0804B4B4: .4byte gUnknown_202F1A9\n" "_0804B4B8: .4byte gUnknown_202F1D8\n" "_0804B4BC: .4byte 0x0000ffff\n" "_0804B4C0: .4byte gDungeon\n" "_0804B4C4: .4byte 0x00003a08\n" "_0804B4C8: .4byte 0x0000e218\n" "_0804B4CC: .4byte 0x0000e21a\n" "_0804B4D0: .4byte 0x0000e21c\n" "_0804B4D4: .4byte 0x0000e21e\n" "_0804B4D8: .4byte gUnknown_202F1B8\n" "_0804B4DC: .4byte 0x00003a0a\n" "_0804B4E0:\n" " ldr r0, _0804B524\n" " ldr r0, [r0]\n" " ldr r6, _0804B528\n" " adds r0, r6\n" " movs r1, 0\n" "_0804B4EA:\n" " strb r1, [r0]\n" " movs r0, 0\n" " movs r1, 0\n" " movs r2, 0x38\n" " movs r3, 0x20\n" " bl sub_804B534\n" " ldr r0, _0804B52C\n" " ldrb r0, [r0]\n" " cmp r0, 0\n" " beq _0804B504\n" " bl sub_804FC74\n" "_0804B504:\n" " ldr r0, _0804B524\n" " ldr r0, [r0]\n" " ldr r1, _0804B530\n" " adds r0, r1\n" " ldr r0, [r0]\n" " bl CloseFile\n" " add sp, 0x44\n" " pop {r3-r5}\n" " mov r8, r3\n" " mov r9, r4\n" " mov r10, r5\n" " pop {r4-r7}\n" " pop {r0}\n" " bx r0\n" " .align 2, 0\n" "_0804B524: .4byte gDungeon\n" "_0804B528: .4byte 0x00003a0a\n" "_0804B52C: .4byte gUnknown_202F1A8\n" "_0804B530: .4byte 0x00013568\n"); } #endif // NONMATCHING void sub_804B534(s32 xStart, s32 yStart, s32 maxX, s32 maxY) { s32 x, y; for (x = xStart; x < maxX; x++) { for (y = yStart; y < maxY; y++) { s32 unkCount = 0; Tile *tile = GetTileSafe(x, y); tile->terrainType &= ~(TERRAIN_TYPE_UNK_2); if (tile->room == CORRIDOR_ROOM && (GetTerrainType(tile) == TERRAIN_TYPE_NORMAL)) { if (x > 0 && (GetTerrainType(GetTile(x - 1, y)) == TERRAIN_TYPE_NORMAL)) unkCount++; if (y > 0 && (GetTerrainType(GetTile(x, y - 1)) == TERRAIN_TYPE_NORMAL)) unkCount++; if (x < DUNGEON_MAX_SIZE_X - 2 && (GetTerrainType(GetTile(x + 1, y)) == TERRAIN_TYPE_NORMAL)) unkCount++; // BUG: It should check for y and not x. Not sure if it has any effect, because this function is called only once with maxY equal to DUNGEON_MAX_SIZE_Y if (x < DUNGEON_MAX_SIZE_Y - 2 && (GetTerrainType(GetTile(x, y + 1)) == TERRAIN_TYPE_NORMAL)) unkCount++; if (unkCount > 2) { tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; } } } } } struct GridCell { Position start; Position end; bool8 isInvalid; bool8 hasSecondaryStructure; bool8 isRoom; bool8 isConnected; bool8 isKecleonShop; bool8 unk13; bool8 isMonsterHouse; bool8 unk15; bool8 isMazeRoom; bool8 hasBeenMerged; bool8 isMerged; bool8 connectedToTop; bool8 connectedToBottom; bool8 connectedToLeft; bool8 connectedToRight; bool8 shouldConnectToTop; bool8 shouldConnectToBottom; bool8 shouldConnectToLeft; bool8 shouldConnectToRight; bool8 unk27; bool8 flagImperfect; bool8 flagSecondaryStructure; bool8 unk30; bool8 unk31; }; #define GRID_CELL_LEN 15 void sub_804C790(s32 gridSizeX, s32 gridSizeY, s32 x2, s32 y2, s32 a4, UnkDungeonGlobal_unk1C574 *unkPtr); void sub_8050F90(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, s32 a5, s32 x2, s32 y2); void sub_8051438(struct GridCell *cell, s32 a1); extern void sub_8051288(s32 a0); void GetGridPositions(s32 *listX, s32 *listY, s32 gridSizeX, s32 gridSizeY); void InitDungeonGrid(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY); void GenerateRoomImperfections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY); void GenerateSecondaryStructures(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY); void AssignRooms(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 roomsNumber); void CreateRoomsAndAnchors(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, u32 roomFlags); void CreateGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, bool8 disableRoomMerging); void EnsureConnectedGrid(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY); static void AssignRandomGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, UnkDungeonGlobal_unk1C574 *unkPtr); void AssignGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 cursorX, s32 cursorY, UnkDungeonGlobal_unk1C574 *unkPtr); void GenerateMazeRoom(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance); void GenerateKecleonShop(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance); void GenerateMonsterHouse(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance); void GenerateExtraHallways(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 numExtraHallways); static void MergeRoomsVertically(s32 roomX, s32 roomY1, s32 room_dy, struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]); void CreateHallway(s32 startX, s32 startY, s32 endX, s32 endY, bool8 vertical, s32 turnX, s32 turnY); /* * GenerateStandardFloor - Generates a standard, typical floor layout. * * Overview: * 1. Determine the grid based on gridSizeX, gridSizeY * 2. Assign and create rooms and hallway anchors to each grid cell * 3. Assign and create connections between grid cells (these are traditional hallways connecting the map together) * 4. Fix any unconnected grid cells by adding more connections or removing their rooms/hallway anchors * 5. Generate special rooms like a Maze Room (unused in vanilla?), Kecleon Shop, or Monster House * 6. Create additional "extra hallways" with random walks outside of existing rooms * 7. Finalize extra room details with imperfections (unused in vanilla?), and structures with secondary terrain */ void GenerateStandardFloor(s32 gridSizeX, s32 gridSizeY, UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; GetGridPositions(listX, listY, gridSizeX, gridSizeY); InitDungeonGrid(grid, gridSizeX, gridSizeY); AssignRooms(grid, gridSizeX, gridSizeY, unkPtr->unk1); CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, unkPtr->unkD); AssignRandomGridCellConnections(grid, gridSizeX, gridSizeY, unkPtr); CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, FALSE); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateMazeRoom(grid, gridSizeX, gridSizeY, unkPtr->unk9); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); GenerateSecondaryStructures(grid, gridSizeX, gridSizeY); } // GenerateOuterRingFloor - Generates on a 6x4 grid, with the outer border of grid cells being hallways and the inner 4x2 grid being rooms. void GenerateOuterRingFloor(UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 x, y; s32 currRoomIndex; s32 gridSizeX = 6; s32 gridSizeY = 4; // These are needed to match. Perhaps they wanted the code to be clear that inside = rooms, and outside = hallways. bool8 outerIsRoom = FALSE; bool8 innerIsRoom = TRUE; listX[0] = 0; listX[1] = 5; listX[3] = 28; listX[5] = 51; listX[6] = 56; listX[2] = 16; listX[4] = 39; listY[0] = 2; listY[1] = 7; listY[2] = 16; listY[3] = 25; listY[4] = 30; InitDungeonGrid(grid, gridSizeX, gridSizeY); // Mark the outer ring as not being rooms for (x = 0; x < gridSizeX; x++) { grid[x][0].isRoom = outerIsRoom; grid[x][gridSizeY - 1].isRoom = outerIsRoom; } for (y = 0; y < gridSizeY; y++) { grid[0][y].isRoom = outerIsRoom; grid[gridSizeX - 1][y].isRoom = outerIsRoom; } // Mark the inner tiles as rooms for (x = 1; x < gridSizeX - 1; x++) { for (y = 1; y < gridSizeY - 1; y++) { grid[x][y].isRoom = innerIsRoom; } } currRoomIndex = 0; for (y = 0; y < gridSizeY; y++) { for (x = 0; x < gridSizeX; x++) { if (grid[x][y].isRoom) { // Room s32 curX, curY; s32 minX = listX[x] + 2; s32 minY = listY[y] + 2; s32 rangeX = listX[x + 1] - listX[x] - 3; s32 rangeY = listY[y + 1] - listY[y] - 3; s32 roomSizeX = DungeonRandRange(5, rangeX); s32 roomSizeY = DungeonRandRange(4, rangeY); s32 startX = DungeonRandInt(rangeX - roomSizeX) + minX; s32 startY = DungeonRandInt(rangeY - roomSizeY) + minY; s32 endX = startX + roomSizeX; s32 endY = startY + roomSizeY; grid[x][y].start.x = startX; grid[x][y].end.x = endX; grid[x][y].start.y = startY; grid[x][y].end.y = startY + roomSizeY; for (curX = startX; curX < endX; curX++) { for (curY = startY; curY < endY; curY++) { SetTerrainNormal(GetTileSafe(curX, curY)); GetTileSafe(curX, curY)->room = currRoomIndex; } } currRoomIndex++; } else { // Hallway Anchor s32 minX = listX[x] + 1; s32 minY = listY[y] + 1; s32 rangeX = listX[x + 1] - listX[x] - 3; s32 rangeY = listY[y + 1] - listY[y] - 3; s32 startX = DungeonRandRange(minX, minX + rangeX); s32 startY = DungeonRandRange(minY, minY + rangeY); grid[x][y].start.x = startX; grid[x][y].end.x = startX + 1; grid[x][y].start.y = startY; grid[x][y].end.y = startY + 1; SetTerrainNormal(GetTileSafe(startX, startY)); GetTileSafe(startX, startY)->room = CORRIDOR_ROOM; } } } grid[0][0].connectedToRight = TRUE; grid[1][0].connectedToLeft = TRUE; grid[1][0].connectedToRight = TRUE; grid[2][0].connectedToLeft = TRUE; grid[2][0].connectedToRight = TRUE; grid[3][0].connectedToLeft = TRUE; grid[3][0].connectedToRight = TRUE; grid[4][0].connectedToLeft = TRUE; grid[4][0].connectedToRight = TRUE; grid[5][0].connectedToLeft = TRUE; grid[0][0].connectedToBottom = TRUE; grid[0][1].connectedToTop = TRUE; grid[0][1].connectedToBottom = TRUE; grid[0][2].connectedToTop = TRUE; grid[0][2].connectedToBottom = TRUE; grid[0][3].connectedToTop = TRUE; grid[0][3].connectedToRight = TRUE; grid[1][3].connectedToLeft = TRUE; grid[1][3].connectedToRight = TRUE; grid[2][3].connectedToLeft = TRUE; grid[2][3].connectedToRight = TRUE; grid[3][3].connectedToLeft = TRUE; grid[3][3].connectedToRight = TRUE; grid[4][3].connectedToLeft = TRUE; grid[4][3].connectedToRight = TRUE; grid[5][3].connectedToLeft = TRUE; grid[5][0].connectedToBottom = TRUE; grid[5][1].connectedToTop = TRUE; grid[5][1].connectedToBottom = TRUE; grid[5][2].connectedToTop = TRUE; grid[5][2].connectedToBottom = TRUE; grid[5][3].connectedToTop = TRUE; AssignRandomGridCellConnections(grid, gridSizeX, gridSizeY, unkPtr); CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, FALSE); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); } /* * GenerateCrossroadsFloor - Generates a floor layout with hallways on the inside and rooms on the outside, with empty corners. * Also nicknamed "Ladder Layout" by some. */ void GenerateCrossroadsFloor(UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 x, y; s32 currRoomIndex; s32 gridSizeX = 5; s32 gridSizeY = 4; // These are needed to match. Perhaps they wanted the code to be clear that outside = rooms, and inside = hallways. bool8 outerIsRoom = TRUE; bool8 innerIsRoom = FALSE; listX[0] = 0; listX[1] = 11; listX[2] = 22; listX[3] = 33; listX[4] = 44; listX[5] = 56; listY[0] = 1; listY[1] = 9; listY[2] = 16; listY[3] = 23; listY[4] = 31; InitDungeonGrid(grid, gridSizeX, gridSizeY); // Mark the outer ring as rooms for (x = 0; x < gridSizeX; x++) { grid[x][0].isRoom = outerIsRoom; grid[x][gridSizeY - 1].isRoom = outerIsRoom; } for (y = 0; y < gridSizeY; y++) { grid[0][y].isRoom = outerIsRoom; grid[gridSizeX - 1][y].isRoom = outerIsRoom; } // Mark the inner cells as hallways for (x = 1; x < gridSizeX - 1; x++) { for (y = 1; y < gridSizeY - 1; y++) { grid[x][y].isRoom = innerIsRoom; } } // Invalidate the corners grid[0][0].isInvalid = TRUE; grid[4][0].isInvalid = TRUE; grid[0][3].isInvalid = TRUE; grid[4][3].isInvalid = TRUE; currRoomIndex = 0; for (y = 0; y < gridSizeY; y++) { for (x = 0; x < gridSizeX; x++) { if (grid[x][y].isInvalid) continue; if (grid[x][y].isRoom) { // Room s32 curX, curY; s32 minX = listX[x] + 2; s32 minY = listY[y] + 2; s32 rangeX = listX[x + 1] - listX[x] - 3; s32 rangeY = listY[y + 1] - listY[y] - 3; s32 roomSizeX = DungeonRandRange(5, rangeX); s32 roomSizeY = DungeonRandRange(4, rangeY); s32 startX = DungeonRandInt(rangeX - roomSizeX) + minX; s32 startY = DungeonRandInt(rangeY - roomSizeY) + minY; s32 endX = startX + roomSizeX; s32 endY = startY + roomSizeY; grid[x][y].start.x = startX; grid[x][y].end.x = endX; grid[x][y].start.y = startY; grid[x][y].end.y = endY; for (curX = startX; curX < endX; curX++) { for (curY = startY; curY < endY; curY++) { SetTerrainNormal(GetTileSafe(curX, curY)); GetTileSafe(curX, curY)->room = currRoomIndex; } } currRoomIndex += 1; } else { // Hallway Anchor s32 minX = listX[x] + 1; s32 minY = listY[y] + 1; s32 rangeX = listX[x + 1] - listX[x] - 3; s32 rangeY = listY[y + 1] - listY[y] - 3; s32 startX = DungeonRandRange(minX, minX + rangeX); s32 startY = DungeonRandRange(minY, minY + rangeY); grid[x][y].start.x = startX; grid[x][y].end.x = startX + 1; grid[x][y].start.y = startY; grid[x][y].end.y = startY + 1; SetTerrainNormal(GetTileSafe(startX, startY)); GetTileSafe(startX, startY)->room = CORRIDOR_ROOM; } } } for (x = 1; x < 5 - 1; x++) { for (y = 0; y < 4 - 1; y++) { grid[x][y].connectedToBottom = TRUE; grid[x][y + 1].connectedToTop = TRUE; } } for (y = 1; y < 4 - 1; y++) { for (x = 0; x < 5 - 1; x++) { grid[x][y].connectedToRight = TRUE; grid[x + 1][y].connectedToLeft = TRUE; } } CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, TRUE); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); } // GenerateLineFloor - Generates a floor layout with 5 grid cells in a horizontal line. void GenerateLineFloor(UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 gridSizeX, gridSizeY; bool8 disableRoomMerging; listX[0] = 0; listX[1] = 11; listX[2] = 22; listX[3] = 33; listX[4] = 44; listX[5] = 56; listY[0] = 4; listY[1] = 15; disableRoomMerging = 1; gridSizeX = 5, gridSizeY = 1; InitDungeonGrid(grid, gridSizeX, gridSizeY); AssignRooms(grid, gridSizeX, gridSizeY, unkPtr->unk1); CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, unkPtr->unkD); AssignRandomGridCellConnections(grid, gridSizeX, gridSizeY, unkPtr); CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, disableRoomMerging); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); } // GenerateCrossFloor - Generates a floor layout with 5 rooms arranged in a "plus" or "cross" configuration. void GenerateCrossFloor(UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 x, y, gridSizeX, gridSizeY; listX[0] = 11; listX[1] = 22; listX[2] = 33; listX[3] = 44; listY[0] = 2; listY[1] = 11; listY[2] = 20; listY[3] = 30; gridSizeX = 3, gridSizeY = 3; InitDungeonGrid(grid, gridSizeX, gridSizeY); // Set all cells as rooms for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { grid[x][y].isRoom = TRUE; } } // Invalidate the corners grid[0][0].isInvalid = TRUE; grid[2][0].isInvalid = TRUE; grid[0][2].isInvalid = TRUE; grid[2][2].isInvalid = TRUE; CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, unkPtr->unkD); grid[0][1].connectedToRight = TRUE; grid[1][1].connectedToLeft = TRUE; grid[1][1].connectedToRight = TRUE; grid[2][1].connectedToLeft = TRUE; grid[1][0].connectedToBottom = TRUE; grid[1][1].connectedToTop = TRUE; grid[1][1].connectedToBottom = TRUE; grid[1][2].connectedToTop = TRUE; CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, TRUE); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); } // GenerateBeetleFloor - Generates a floor layout in a "beetle" shape, with a // 3x3 grid of rooms, a merged center column, and hallways along each row void GenerateBeetleFloor(UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 x, y, gridSizeX, gridSizeY; listX[0] = 5; listX[1] = 15; listX[2] = 35; listX[3] = 50; listY[0] = 2; listY[1] = 11; listY[2] = 20; listY[3] = 30; gridSizeX = 3, gridSizeY = 3; InitDungeonGrid(grid, gridSizeX, gridSizeY); // Set all cells as rooms for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { grid[x][y].isRoom = TRUE; } } CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, unkPtr->unkD); // Connect rooms in the same row together for (y = 0; y < 3; y++) { grid[0][y].connectedToRight = TRUE; grid[1][y].connectedToLeft = TRUE; grid[1][y].connectedToRight = TRUE; grid[2][y].connectedToLeft = TRUE; } CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, TRUE); // Merge the center column into one large room MergeRoomsVertically(1, 0, 1, grid); MergeRoomsVertically(1, 0, 2, grid); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); } // MergeRoomsVertically - Merges two vertically stacked rooms into one larger room. static void MergeRoomsVertically(s32 roomX, s32 roomY1, s32 room_dy, struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]) { s32 x, y; s32 xStart = min(grid[roomX][roomY1].start.x, grid[roomX][roomY1+room_dy].start.x); s32 yStart = grid[roomX][roomY1].start.y; s32 xEnd = max(grid[roomX][roomY1].end.x, grid[roomX][roomY1+room_dy].end.x); s32 yEnd = grid[roomX][roomY1 + room_dy].end.y; // Carve out the new larger room, retaining the index of the first room u8 roomId = GetTile(grid[roomX][roomY1].start.x, grid[roomX][roomY1].start.y)->room; for (x = xStart; x < xEnd; x++) { for (y = yStart; y < yEnd; y++) { Tile *tile = GetTileSafe(x, y); SetTerrainNormal(tile); tile->room = roomId; } } grid[roomX][roomY1].start.x = xStart; grid[roomX][roomY1].end.x = xEnd; grid[roomX][roomY1].start.y = yStart; grid[roomX][roomY1].end.y = yEnd; grid[roomX][roomY1 + room_dy].isMerged = TRUE; grid[roomX][roomY1].isMerged = TRUE; grid[roomX][roomY1 + room_dy].isConnected = FALSE; grid[roomX][roomY1 + room_dy].hasBeenMerged = TRUE; } // GenerateOuterRoomsFloor - Generates a floor layout with a ring of rooms and nothing on the interior. // This layout is bugged and will not properly connect rooms for gridSizeX < 3. void GenerateOuterRoomsFloor(s32 gridSizeX_, s32 gridSizeY_, UnkDungeonGlobal_unk1C574 *unkPtr) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 x, y; s32 gridSizeX = gridSizeX_; s32 gridSizeY = gridSizeY_; GetGridPositions(listX, listY, gridSizeX, gridSizeY); InitDungeonGrid(grid, gridSizeX, gridSizeY); // Make all cells rooms for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { grid[x][y].isRoom = TRUE; } } // Invalidate all interior cells for (x = 1; x < gridSizeX - 1; x++) { for (y = 1; y < gridSizeY - 1; y++) { grid[x][y].isInvalid = TRUE; } } CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, unkPtr->unkD); // Maybe Todo: Add EpicYoshiMaster's fixed implementation of this function // The original implementation fails for gridSizeX <= 2, as one of the branches // is never taken, and the other branch does not provide a backup connection, leaving the two sides unconnected. // Additionally, there is a minor issue for top/bottom connections which results in hallways being connected from the bottom // instead of from the top, but this does not affect the connectivity of the map. for (x = 0; x < gridSizeX - 1; x++) { if (x != 0) { grid[x][0].connectedToRight = TRUE; grid[x][gridSizeY-1].connectedToRight = TRUE; } // Bug: if gridSizeX <= 2, this branch will never be run. // Additionally, because the branch above this has no meaningful hallways produced for // gridSizeX == 1, no connections will be made between columns here. // This results in an unconnected map for gridSizeX <= 2. if (x < gridSizeX - 2) { grid[x+1][0].connectedToLeft = TRUE; grid[x+1][gridSizeY-1].connectedToLeft = TRUE; } } for (y = 0; y < gridSizeY - 1; y++) { if (y != 0) { grid[0][y].connectedToTop = TRUE; grid[gridSizeX-1][y].connectedToTop = TRUE; } // This connection ends up not being set for the bottom row, but this is fine because the other // connection to this room is still correct. The result is that hallways here will be using the opposing end // of the grid cell boundary for their turns compared to top/bottom hallways between other rows. if (y < gridSizeY - 2) { grid[0][y].connectedToBottom = TRUE; grid[gridSizeX-1][y].connectedToBottom = TRUE; } } CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, FALSE); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); GenerateMazeRoom(grid, gridSizeX, gridSizeY, unkPtr->unk9); GenerateKecleonShop(grid, gridSizeX, gridSizeY, gUnknown_202F1B0); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, gUnknown_202F1B2); GenerateExtraHallways(grid, gridSizeX, gridSizeY, unkPtr->unk13); GenerateRoomImperfections(grid, gridSizeX, gridSizeY); GenerateSecondaryStructures(grid, gridSizeX, gridSizeY); } bool8 sub_804C70C(s32 a0, UnkDungeonGlobal_unk1C574 *unkPtr) { struct FileFixedmapPosStruct **fileData = (void *) gDungeon->unk13568->data; s32 x1 = fileData[a0]->x; s32 y1 = fileData[a0]->y; s32 gridSizeX, gridSizeY; if (x1 == 0 || y1 == 0) { GenerateOneRoomMonsterHouseFloor(); return FALSE; } else if (a0 < 50) { sub_8051288(a0); return TRUE; } else { gridSizeX = DUNGEON_MAX_SIZE_X / (x1 + 4); if (gridSizeX <= 1) gridSizeX = 1; gridSizeY = DUNGEON_MAX_SIZE_Y / (y1 + 4); if (gridSizeY <= 1) gridSizeY = 1; sub_804C790(gridSizeX, gridSizeY, x1, y1, a0, unkPtr); return FALSE; } } void sub_804C790(s32 gridSizeX, s32 gridSizeY, s32 x2, s32 y2, s32 a4, UnkDungeonGlobal_unk1C574 *unkPtr) { s32 tries; struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; s32 r10 = 0; s32 cursorX = 0, cursorY = 0; GetGridPositions(listX, listY, gridSizeX, gridSizeY); InitDungeonGrid(grid, gridSizeX, gridSizeY); AssignRooms(grid, gridSizeX, gridSizeY, unkPtr->unk1); for (cursorX = 0; cursorX < gridSizeX; cursorX++) { for (cursorY = 0; cursorY < gridSizeY; cursorY++) { grid[cursorX][cursorY].unk27 = 1; } } for (tries = 0; tries < 64; tries++) { cursorX = DungeonRandInt(gridSizeX); cursorY = DungeonRandInt(gridSizeY); r10 = cursorY * gridSizeX + cursorX; if (grid[cursorX][cursorY].isRoom) break; } sub_8050F90(grid, gridSizeX, gridSizeY, listX, listY, r10, x2, y2); if (gridSizeX != 1 || gridSizeY != 1) { AssignGridCellConnections(grid, gridSizeX, gridSizeY, cursorX, cursorY, unkPtr); CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, TRUE); EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY); } sub_8051438(&grid[cursorX][cursorY], a4); } /* * GenerateOneRoomMonsterHouseFloor - Generates a floor layout with just one large room which is a Monster House. * This generator is used as a fallback if the event generation fails too many times. */ void GenerateOneRoomMonsterHouseFloor(void) { s32 x, y; struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; InitDungeonGrid(grid, 1, 1); grid[0][0].start.x = 2; grid[0][0].end.x = DUNGEON_MAX_SIZE_X - 2; grid[0][0].start.y = 2; grid[0][0].end.y = DUNGEON_MAX_SIZE_Y - 2; grid[0][0].isRoom = TRUE; grid[0][0].isConnected = TRUE; grid[0][0].isInvalid = FALSE; for (x = grid[0][0].start.x; x < grid[0][0].end.x; x++) { for (y = grid[0][0].start.y; y < grid[0][0].end.y; y++) { SetTerrainNormal(GetTileSafe(x, y)); GetTileSafe(x, y)->room = 0; } } GenerateMonsterHouse(grid, 1, 1, 999); } // GenerateTwoRoomsWithMonsterHouseFloor - Generates a floor layout with two rooms (left and right), with one being a Monster House. void GenerateTwoRoomsWithMonsterHouseFloor(void) { struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN]; s32 listX[GRID_CELL_LEN]; s32 listY[GRID_CELL_LEN]; const s32 gridSizeX = 2; const s32 gridSizeY = 1; s32 currRoomId = 0; s32 x, y; listX[0] = 2; listX[1] = 28; listX[2] = 54; listY[0] = 2; listY[1] = 30; InitDungeonGrid(grid, gridSizeX, gridSizeY); for (y = 0; y < gridSizeY; y++) { for (x = 0; x < gridSizeX; x++) { s32 currX, currY; s32 minX = listX[x] + 1; s32 minY = listY[y] + 1; s32 rangeX = listX[x + 1] - listX[x] - 3; s32 rangeY = listY[y + 1] - listY[y] - 3; s32 roomSizeX = DungeonRandRange(10, rangeX); s32 roomSizeY = DungeonRandRange(16, rangeY); s32 startX = DungeonRandInt(rangeX - roomSizeX) + minX; s32 startY = DungeonRandInt(rangeY - roomSizeY) + minY; s32 endX = startX + roomSizeX; s32 endY = startY + roomSizeY; grid[x][y].isRoom = TRUE; grid[x][y].start.x = startX; grid[x][y].end.x = endX; grid[x][y].start.y = startY; grid[x][y].end.y = endY; for (currX = startX; currX < endX; currX++) { for (currY = startY; currY < endY; currY++) { SetTerrainNormal(GetTileSafe(currX, currY)); GetTileSafe(currX, currY)->room = currRoomId; } } currRoomId++; } } grid[0][0].connectedToRight = TRUE; grid[1][0].connectedToLeft = TRUE; CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, FALSE); GenerateMonsterHouse(grid, gridSizeX, gridSizeY, 999); } /* * GenerateExtraHallways - Generate extra hallways on the floor via a series of random walks. * * These paths are often visibly dead-end hallways, or hallways which loop on themselves. * * Each walk begin at a random tile in a random room, leaving in a random cardinal direction, tunneling * through obstacles until it reaches open terrain, is out of bounds, or reaches an impassable obstruction. * * For each hallway the following steps are done: * * 1. Select a room, tile, and cardinal direction (specific conditions documented below) * * 2. Walk from the tile in that direction until we are out of the room, and reach an obstacle (could traverse hallways on the way) * * 3. Check we're safe to proceed (not at map borders, counterclockwise/clockwise tiles are not open) * * Begin our random-length walk strides: * * 4. Check we're safe to proceed (not at borders, not open tile, not impassable wall, will not make a 2x2 open square) * * 5. Place Open Terrain at this tile * * 6. Check we're safe to proceed (counterclockwise/clockwise tiles are not open) * * 7. Check if we've reached the end of the current stride (steps at 0), if so, turn left or right at random and start a new stride. * * 8. Move in the current direction. * * Repeat 4-8 until a check fails. */ void GenerateExtraHallways(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 numExtraHallways) { s32 i, j; if (numExtraHallways == 0) return; for (i = 0; i < numExtraHallways; i++) { s32 currX, currY; s32 direction; u8 roomId; bool8 invalid; s32 checkX, checkY, checkX2, checkY2; s32 xLoop, yLoop; // Select a random grid cell s32 x = DungeonRandInt(gridSizeX); s32 y = DungeonRandInt(gridSizeY); // To generate extra hallways the cell must be: // - a room // - connected // - valid // - not a maze room if (!grid[x][y].isRoom || !grid[x][y].isConnected || grid[x][y].isInvalid || grid[x][y].isMazeRoom) continue; // Choose a random tile in the room currX = DungeonRandRange(grid[x][y].start.x, grid[x][y].end.x); currY = DungeonRandRange(grid[x][y].start.y, grid[x][y].end.y); // Choose a random cardinal direction direction = DungeonRandInt(4) * 2; // If invalid, rotate counter-clockwise until one works for (j = 0; j < 3; j++) { if (direction == DIRECTION_SOUTH && y >= gridSizeY - 1) { direction = DIRECTION_EAST; } if (direction == DIRECTION_EAST && x >= gridSizeX - 1) { direction = DIRECTION_NORTH; } if (direction == DIRECTION_NORTH && y <= 0) { direction = DIRECTION_WEST; } if (direction == DIRECTION_WEST && x <= 0) { direction = DIRECTION_SOUTH; } } roomId = GetTile(currX, currY)->room; // Walk in the random direction until out of the room while (1) { if (roomId != GetTile(currX, currY)->room) break; // gAdjacentTileOffsets gives us the proper (x,y) offset to move one tile in the given direction. currX += gAdjacentTileOffsets[direction].x; currY += gAdjacentTileOffsets[direction].y; } // Keep walking until an obstacle is encountered while (1) { if (GetTerrainType(GetTile(currX, currY)) != TERRAIN_TYPE_NORMAL) break; currX += gAdjacentTileOffsets[direction].x; currY += gAdjacentTileOffsets[direction].y; } // Abort if we reached secondary terrain if (GetTerrainType(GetTile(currX, currY)) == TERRAIN_TYPE_SECONDARY) continue; // Check that the current tile is at least 2 away from the map border invalid = FALSE; for (xLoop = currX - 2; xLoop <= currX + 2; xLoop++) { for (yLoop = currY - 2; yLoop <= currY + 2; yLoop++) { if (xLoop < 0 || xLoop >= DUNGEON_MAX_SIZE_X || yLoop < 0 || yLoop >= DUNGEON_MAX_SIZE_Y) { invalid = TRUE; break; } } if (invalid) break; } if (invalid) continue; // Make sure the direction 90 degrees counterclockwise isn't an open tile checkX = gAdjacentTileOffsets[(direction + 2) & DIRECTION_MASK_CARDINAL].x; checkY = gAdjacentTileOffsets[(direction + 2) & DIRECTION_MASK_CARDINAL].y; if (GetTerrainType(GetTile(currX + checkX, currY + checkY)) == TERRAIN_TYPE_NORMAL) continue; // Do the same for 90 degrees clockwise (or 270 counterclockwise) and make sure it's not an open tile checkX2 = gAdjacentTileOffsets[(direction - 2) & DIRECTION_MASK_CARDINAL].x; checkY2 = gAdjacentTileOffsets[(direction - 2) & DIRECTION_MASK_CARDINAL].y; if (GetTerrainType(GetTile(currX + checkX2, currY + checkY2)) == TERRAIN_TYPE_NORMAL) continue; // Number of steps to walk in one direction before turning j = DungeonRandInt(3) + 3; while (TRUE) { s32 checkX, checkY, checkX2, checkY2; bool8 willNotMakeSquare; // Check for stopping conditions: // - Out of bounds or on the 1-tile border of impassable walls // - Reached an open tile // - Reached an impassable wall // - Would result in carving out a 2x2 square (not a hallway at that point) if (currX <= 1 || currY <= 1 || currX >= DUNGEON_MAX_SIZE_X - 1 || currY >= DUNGEON_MAX_SIZE_Y - 1) break; if (GetTerrainType(GetTile(currX, currY)) == TERRAIN_TYPE_NORMAL) break; if (GetTile(currX, currY)->terrainType & TERRAIN_TYPE_IMPASSABLE_WALL) break; willNotMakeSquare = TRUE; // Check Bottom to Right if ((GetTerrainType(GetTile(currX + 1, currY)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX + 1, currY + 1)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX, currY + 1)) == TERRAIN_TYPE_NORMAL)) { willNotMakeSquare = FALSE; } // Check Top to Right if ((GetTerrainType(GetTile(currX + 1, currY)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX + 1, currY - 1)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX, currY - 1)) == TERRAIN_TYPE_NORMAL)) { willNotMakeSquare = FALSE; } // Check Bottom to Left if ((GetTerrainType(GetTile(currX - 1, currY)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX - 1, currY + 1)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX, currY + 1)) == TERRAIN_TYPE_NORMAL)) { willNotMakeSquare = FALSE; } // Check Top to Left if ((GetTerrainType(GetTile(currX - 1, currY)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX - 1, currY - 1)) == TERRAIN_TYPE_NORMAL) && (GetTerrainType(GetTile(currX, currY - 1)) == TERRAIN_TYPE_NORMAL)) { willNotMakeSquare = FALSE; } // If TRUE, make the tile open, it will not produce a 2x2 opening // If FALSE, it will abort from neighbor checks so we don't break here if (willNotMakeSquare) { SetTerrainNormal(GetTileSafe(currX, currY)); } // Make sure the direction 90 degrees counterclockwise isn't an open tile checkX = gAdjacentTileOffsets[(direction + 2) & DIRECTION_MASK_CARDINAL].x; checkY = gAdjacentTileOffsets[(direction + 2) & DIRECTION_MASK_CARDINAL].y; if (GetTerrainType(GetTile(currX + checkX, currY + checkY)) == TERRAIN_TYPE_NORMAL) break; // Do the same for 90 degrees clockwise (or 270 counterclockwise) and make sure it's not an open tile checkX2 = gAdjacentTileOffsets[(direction - 2) & DIRECTION_MASK_CARDINAL].x; checkY2 = gAdjacentTileOffsets[(direction - 2) & DIRECTION_MASK_CARDINAL].y; if (GetTerrainType(GetTile(currX + checkX2, currY + checkY2)) == TERRAIN_TYPE_NORMAL) break; j--; if (j == 0) { j = DungeonRandInt(3) + 3; // Turn left or right with an equal chance if (DungeonRandInt(100) < 50) { direction += 2; } else { direction -= 2; } // If we'd step into an invalid grid cell, stop // (We don't always utilize the entire floor space) direction &= DIRECTION_MASK_CARDINAL; if (currX >= 32 && gUnknown_202F1AE == 1 && direction == DIRECTION_EAST) break; if (currX >= 48 && gUnknown_202F1AE == 2 && direction == DIRECTION_EAST) break; } // Move in the current direction currX += gAdjacentTileOffsets[direction].x; currY += gAdjacentTileOffsets[direction].y; } } } // GetGridPositions - Determines the starting positions of grid cells based on the given floor grid dimensions void GetGridPositions(s32 *listX, s32 *listY, s32 gridSizeX, s32 gridSizeY) { s32 i, sum; for (i = 0, sum = 0; i < gridSizeX; i++) { listX[i] = sum; sum += DUNGEON_MAX_SIZE_X / gridSizeX; } listX[gridSizeX] = sum; for (i = 0, sum = 0; i < gridSizeY; i++) { listY[i] = sum; sum += DUNGEON_MAX_SIZE_Y / gridSizeY; } listY[gridSizeY] = sum; } /* * InitDungeonGrid - Initializes the default state of the dungeon grid * * The dungeon grid is an array of grid cells stored in column-major order * (to give contiguous storage to cells with the same x value), with a fixed column size of 15. */ void InitDungeonGrid(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY) { s32 x, y; for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { if (gUnknown_202F1AE == 1 && x >= gridSizeX / 2) { grid[x][y].isInvalid = TRUE; } else if (gUnknown_202F1AE == 2 && x >= (gridSizeX * 3) / 4) { grid[x][y].isInvalid = TRUE; } else { grid[x][y].isInvalid = FALSE; } grid[x][y].isRoom = TRUE; grid[x][y].isConnected = FALSE; grid[x][y].unk15 = FALSE; grid[x][y].isMonsterHouse = FALSE; grid[x][y].isKecleonShop = FALSE; grid[x][y].connectedToRight = FALSE; grid[x][y].connectedToLeft = FALSE; grid[x][y].connectedToBottom = FALSE; grid[x][y].connectedToTop = FALSE; grid[x][y].shouldConnectToRight = FALSE; grid[x][y].shouldConnectToLeft = FALSE; grid[x][y].shouldConnectToBottom = FALSE; grid[x][y].shouldConnectToTop = FALSE; grid[x][y].hasSecondaryStructure = FALSE; grid[x][y].hasBeenMerged = FALSE; grid[x][y].isMazeRoom = FALSE; grid[x][y].isMerged = FALSE; grid[x][y].flagImperfect = FALSE; grid[x][y].flagSecondaryStructure = FALSE; } } } /* * AssignRooms - Randomly selects a subset of grid cells to become rooms * * If number_of_rooms is positive, number_of_rooms + [0..2] will become rooms * If the selected cells for rooms are invalid, less rooms will be generated. * The number of rooms assigned will always be at least 2 and always <= MAX_ROOM_COUNT (32). * * Any cells which aren't marked as rooms will become hallway anchors (those single 1x1 "rooms") * which will be connected as hallways later, to "anchor" hallway generation * * Primarily Modifies: grid to assign certain grid cells to have isRoom TRUE. */ #define ROOM_BITS_COUNT 256 // Despite max rooms being 32, randomRoomBits can hold up to 256 possible rooms void AssignRooms(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 roomsNumber) { bool8 randomRoomBits[ROOM_BITS_COUNT]; s32 i, nShuffles; s32 x, y; s32 attempts; s32 maxRooms; // Extra Rooms i = DungeonRandInt(3); // A negative # for room count requests an exact number of rooms if (roomsNumber < 0) { roomsNumber = -roomsNumber; } else { roomsNumber += i; } for (i = 0; i < roomsNumber; i++) { randomRoomBits[i] = TRUE; } for (; i < ROOM_BITS_COUNT; i++) { randomRoomBits[i] = FALSE; } // Shuffle around the acceptable rooms maxRooms = gridSizeX * gridSizeY; for (nShuffles = 0; nShuffles < 64; nShuffles++) { bool8 temp; s32 a = DungeonRandInt(maxRooms); s32 b = DungeonRandInt(maxRooms); SWAP(randomRoomBits[a], randomRoomBits[b], temp); } // Counter for randomRoomBits i = 0; gUnknown_202F1CC = 0; for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { if (grid[x][y].isInvalid) continue; // There are too many rooms, remove if (gUnknown_202F1CC >= MAX_ROOM_COUNT) { grid[x][y].isRoom = FALSE; } // Using the randomly shuffled bits, create or remove the room if (randomRoomBits[i]) { grid[x][y].isRoom = TRUE; gUnknown_202F1CC++; // Don't make a room at (x_mid, 1) if (gridSizeX % 2 != 0 && x == (gridSizeX - 1) / 2 && y == 1 ) { grid[x][y].isRoom = FALSE; } } else { grid[x][y].isRoom = FALSE; } i++; } } // We have at least 2 rooms, we're done. if (gUnknown_202F1CC >= 2) return; for (attempts = 0; attempts < 200; attempts++) { bool8 enoughRooms = FALSE; for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { if (grid[x][y].isInvalid) continue; if (DungeonRandInt(100) < 60) { grid[x][y].isRoom = TRUE; enoughRooms = TRUE; // This goto is needed to match, it's used to break from two nested loops. goto LOOP_BREAK; } } } LOOP_BREAK: if (enoughRooms) break; } gUnknown_202F1AD = FALSE; } // Todo: move to header #define GENERATION_CONSTANT_MERGE_ROOMS_CHANCE 5 // (0 to 100) probability to merge two rooms together #define GENERATION_CONSTANT_NO_IMPERFECTIONS_CHANCE 60 // (0 to 100) probability that a room will not have imperfections, if it was already flagged for them #define GENERATION_CONSTANT_SECONDARY_STRUCTURE_FLAG_CHANCE 80 // (0 to 100) probability that a room will be flagged to have a secondary structure. /* * CreateRoomsAndAnchors - Creates the rectangle regions of open terrain for each room * leaving a margin relative to the border * * If the room is an anchor, a single tile is placed with a hallway indicator for later. */ void CreateRoomsAndAnchors(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, u32 roomFlags) { s32 roomNumber = 0; s32 x, y; for (y = 0; y < gridSizeY; y++) { for (x = 0; x < gridSizeX; x++) { s32 minX = listX[x] + 2; s32 minY = listY[y] + 2; s32 rangeX = listX[x + 1] - listX[x] - 4; s32 rangeY = listY[y + 1] - listY[y] - 3; if (grid[x][y].isInvalid) continue; if (grid[x][y].isRoom) { // This cell is a room! s32 roomSizeX, roomSizeY; s32 startX, endX; s32 startY, endY; s32 roomX, roomY; bool8 flagSecondary, flagImperfect; roomSizeX = DungeonRandRange(5, rangeX); roomSizeY = DungeonRandRange(4, rangeY); // Force small rooms to have odd-numbered dimensions (?) if ((roomSizeX | 1) < rangeX) { roomSizeX |= 1; } if ((roomSizeY | 1) < rangeY) { roomSizeY |= 1; } // Aspect ratio 2/3 < x/y < 3/2 if (roomSizeX > (roomSizeY * 3) / 2) { roomSizeX = (roomSizeY * 3) / 2; } if (roomSizeY > (roomSizeX * 3) / 2) { roomSizeY = (roomSizeX * 3) / 2; } startX = DungeonRandInt(rangeX - roomSizeX) + minX; startY = DungeonRandInt(rangeY - roomSizeY) + minY; endX = startX + roomSizeX; endY = startY + roomSizeY; // Create the room! grid[x][y].start.x = startX; grid[x][y].end.x = endX; grid[x][y].start.y = startY; grid[x][y].end.y = endY; for (roomX = startX; roomX < endX; roomX++) { for (roomY = startY; roomY < endY; roomY++) { SetTerrainNormal(GetTileSafe(roomX, roomY)); GetTileSafe(roomX, roomY)->room = roomNumber; } } flagImperfect = TRUE; // Randomly flag the room for a secondary structure flagSecondary = DungeonRandInt(100) < GENERATION_CONSTANT_SECONDARY_STRUCTURE_FLAG_CHANCE; if (gUnknown_202F1C8 == 0) { flagSecondary = FALSE; } // Flag for imperfections if needed if (!(roomFlags & 4)) { flagImperfect = FALSE; } if (flagImperfect && flagSecondary) { // If a room gets both, pick one at random if (DungeonRandInt(100) < 50) { flagImperfect = FALSE; } else { flagSecondary = FALSE; } } if (flagImperfect) { grid[x][y].flagImperfect = TRUE; } if (flagSecondary) { grid[x][y].flagSecondaryStructure = TRUE; } roomNumber++; } else { // This cell is not a room, create a 1x1 hallway anchor s32 pt_x, pt_y; s32 unk_x1 = 2; s32 unk_x2 = 4; s32 unk_y1 = 2; s32 unk_y2 = 4; if (x == 0) { unk_x1 = 1; } if (y == 0) { unk_y1 = 1; } if (x == gridSizeX - 1) { unk_x2 = 2; } if (y == gridSizeY - 1) { unk_y2 = 2; } pt_x = DungeonRandRange(minX + unk_x1, minX + rangeX - unk_x2); pt_y = DungeonRandRange(minY + unk_y1, minY + rangeY - unk_y2); grid[x][y].start.x = pt_x; grid[x][y].end.x = pt_x + 1; grid[x][y].start.y = pt_y; grid[x][y].end.y = pt_y + 1; // Flag the tile as open to serve as a hallway anchor SetTerrainNormal(GetTileSafe(pt_x, pt_y)); // Set the room index to 0xFE for anchor GetTileSafe(pt_x, pt_y)->room = ROOM_0xFE; } } } } void GenerateSecondaryStructure(struct GridCell *cell); void GenerateSecondaryStructures(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY) { s32 x, y; for (y = 0; y < gridSizeY; y++) { for (x = 0; x < gridSizeX; x++) { // To have a secondary structure a room must be: // - valid // - not a monster house, merged, or have imperfections // - be a room // - be flagged for a secondary structure if (!grid[x][y].isInvalid && !grid[x][y].isMonsterHouse && !grid[x][y].isMerged && grid[x][y].isRoom && !grid[x][y].flagImperfect && grid[x][y].flagSecondaryStructure) { GenerateSecondaryStructure(&grid[x][y]); } } } } static void AssignRandomGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, UnkDungeonGlobal_unk1C574 *unkPtr) { s32 cursorX = DungeonRandInt(gridSizeX); s32 cursorY = DungeonRandInt(gridSizeY); AssignGridCellConnections(grid, gridSizeX, gridSizeY, cursorX, cursorY, unkPtr); } /* * AssignGridCellConnections - Responsible for assigning connections to randomly adjacent grid cells * * Connections begin from the grid cell at (cursorX, cursorY), and are created using a * random walk with momentum. * * There's a 50% chance it will continue in the same direction, otherwise it will be assigned a new random direction. * If the direction traveled runs into the border of the map, the direction turns counterclockwise. * If the direction walks towards an invalid grid tile, nothing happens and iteration continues. * * The random walk will be repeated floorConnectivity number of times (specified in FloorProperties) * * Once finished, if dead ends are disabled, an additional phase occurs to remove dead end hallway anchors (not rooms) * The original implementation contains a bug when applying new connections to these rooms, where the incorrect * grid cell index will be checked for validity (always the grid cell to the right), so some connections may go to * invalid tiles or not be applied to valid ones. * */ enum CardinalDirection { CARDINAL_DIR_RIGHT, CARDINAL_DIR_UP, CARDINAL_DIR_LEFT, CARDINAL_DIR_DOWN, NUM_CARDINAL_DIRECTIONS }; #define CARDINAL_DIRECTION_MASK 3 void AssignGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 cursorX, s32 cursorY, UnkDungeonGlobal_unk1C574 *unkPtr) { s32 i; s32 x = cursorX; s32 y = cursorY; s32 floorConnectivity = unkPtr->unk5; // Draw a random connection direction. // Connect the current cell with the cell to the: // 0: east // 1: north // 2: west // 3: south s32 cardinalDirection = DungeonRandInt(NUM_CARDINAL_DIRECTIONS); // Try to connect the current cell to another grid cell, repeat based on floorConnectivity for (i = 0; i < floorConnectivity; i++) { // Keep moving in the same cardinalDirection with probability 1/2 ("momentum" to connect in a straight line) // Less forks and less doubling back s32 test = DungeonRandInt(NUM_CARDINAL_DIRECTIONS * 2); s32 newDirection = DungeonRandInt(NUM_CARDINAL_DIRECTIONS); if (test < NUM_CARDINAL_DIRECTIONS) { // Shuffle to a new cardinalDirection cardinalDirection = newDirection; } // Make sure our cardinalDirection isn't going into a border // If so, rotate counterclockwise while (1) { bool8 notOk = FALSE; switch (cardinalDirection & CARDINAL_DIRECTION_MASK) { case CARDINAL_DIR_RIGHT: if (x < gridSizeX - 1) notOk = TRUE; else cardinalDirection++; break; case CARDINAL_DIR_UP: if (y > 0) notOk = TRUE; else cardinalDirection++; break; case CARDINAL_DIR_LEFT: if (x > 0) notOk = TRUE; else cardinalDirection++; break; case CARDINAL_DIR_DOWN: if (y < gridSizeY - 1) notOk = TRUE; else cardinalDirection++; break; } if (notOk) break; } // Set the connection, then move in that cardinalDirection switch (cardinalDirection & CARDINAL_DIRECTION_MASK) { case CARDINAL_DIR_RIGHT: if (!grid[x + 1][y].isInvalid) { grid[x][y].connectedToRight = TRUE; grid[x + 1][y].connectedToLeft = TRUE; x++; } break; case CARDINAL_DIR_UP: if (!grid[x][y - 1].isInvalid) { grid[x][y].connectedToTop = TRUE; grid[x][y - 1].connectedToBottom = TRUE; y--; } break; case CARDINAL_DIR_LEFT: if (!grid[x - 1][y].isInvalid) { grid[x][y].connectedToLeft = TRUE; grid[x - 1][y].connectedToRight = TRUE; x--; } break; case CARDINAL_DIR_DOWN: if (!grid[x][y + 1].isInvalid) { grid[x][y].connectedToBottom = TRUE; grid[x][y + 1].connectedToTop = TRUE; y++; } break; } } if (unkPtr->unkB) return; // No dead ends, add some extra connections! while (1) { bool8 more = FALSE; // Locate potential dead ends for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { if (grid[x][y].isInvalid) continue; if (!grid[x][y].isRoom) { // Find which cardinalDirections this tile is connected in s32 countConnect = 0; if (grid[x][y].connectedToTop) countConnect++; if (grid[x][y].connectedToBottom) countConnect++; if (grid[x][y].connectedToLeft) countConnect++; if (grid[x][y].connectedToRight) countConnect++; if (countConnect == 1) { s32 i; bool8 ok; // This tile has only one connection, it's a dead end // Connect it to a random other cell to remove the dead end cardinalDirection = DungeonRandInt(NUM_CARDINAL_DIRECTIONS); for (i = 0, ok = FALSE; i < 8; i++) { switch (cardinalDirection & CARDINAL_DIRECTION_MASK) { case CARDINAL_DIR_RIGHT: if (x < gridSizeX - 1 && !grid[x][y].connectedToRight) ok = TRUE; else cardinalDirection++; break; case CARDINAL_DIR_UP: if (y > 0 && !grid[x][y].connectedToTop) ok = TRUE; else cardinalDirection++; break; case CARDINAL_DIR_LEFT: if (x > 0 && !grid[x][y].connectedToLeft) ok = TRUE; else cardinalDirection++; break; case CARDINAL_DIR_DOWN: if (y < gridSizeY - 1 && !grid[x][y].connectedToBottom) ok = TRUE; else cardinalDirection++; break; } // Once we find a successful cardinalDirection, stop if (ok) break; } // We couldn't find any successful cardinalDirection, give up. if (!ok) continue; // This section retains the original functionality switch (cardinalDirection & CARDINAL_DIRECTION_MASK) { case CARDINAL_DIR_RIGHT: if (!grid[x + 1][y].isInvalid) { grid[x][y].connectedToRight = TRUE; grid[x + 1][y].connectedToLeft = TRUE; more = TRUE; } break; // BUG: the wrong grid index is used for the validity check case CARDINAL_DIR_UP: if (!grid[x + 1][y].isInvalid) { grid[x][y].connectedToTop = TRUE; grid[x][y - 1].connectedToBottom = TRUE; more = TRUE; } break; // BUG: the wrong grid index is used for the validity check case CARDINAL_DIR_LEFT: if (!grid[x + 1][y].isInvalid) { grid[x][y].connectedToLeft = TRUE; grid[x - 1][y].connectedToRight = TRUE; more = TRUE; } break; // BUG: the wrong grid index is used for the validity check case CARDINAL_DIR_DOWN: if (!grid[x + 1][y].isInvalid) { grid[x][y].connectedToBottom = TRUE; grid[x][y + 1].connectedToTop = TRUE; more = TRUE; } break; } } } } } if (!more) break; } } /* * CreateGridCellConnections - Creates connections through generating hallways and merging rooms * * First, connection links are copied over to a work array for managing hallway generation. * * Then, for each connection specified between two cells, a hallway is generated based on the following: * - If the cell is a hallway anchor, the hallway is generated based on the exact point of the anchor tile * - If the cell is a room, the hallway is generated based on a random interior point inside the room * * See: CreateHallway for how these points generate the hallway path * * Finally, if room merging is enabled there is a 9.75% chance that two connected rooms will be merged * into a single larger room. (9.75% comes from two 5% rolls, one for each of the two rooms being merged) * A room can only participate in a merge once. * * Merged rooms take up the full tile space occupied between the two rooms. * */ void CreateGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, bool8 disableRoomMerging) { s32 x, y; // Validate and copy grid connections over to a work array for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { if (grid[x][y].isInvalid) { // For invalid cells, assign no connections grid[x][y].shouldConnectToTop = FALSE; grid[x][y].shouldConnectToBottom = FALSE; grid[x][y].shouldConnectToLeft = FALSE; grid[x][y].shouldConnectToRight = FALSE; } else { // For valid cells, remove cell connections beyond the grid bounds if (x <= 0) { grid[x][y].connectedToLeft = FALSE; } if (y <= 0) { grid[x][y].connectedToTop = FALSE; } if (x >= gridSizeX - 1) { grid[x][y].connectedToRight = FALSE; } if (y >= gridSizeY - 1) { grid[x][y].connectedToBottom = FALSE; } // Assign the connections grid[x][y].shouldConnectToTop = grid[x][y].connectedToTop; grid[x][y].shouldConnectToBottom = grid[x][y].connectedToBottom; grid[x][y].shouldConnectToLeft = grid[x][y].connectedToLeft; grid[x][y].shouldConnectToRight = grid[x][y].connectedToRight; } } } for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { s32 pt_x, pt_y, pt2_x, pt2_y; if (grid[x][y].isInvalid) continue; if (grid[x][y].isRoom) { // Room, pick a random point in the interior of the room pt_x = DungeonRandRange(grid[x][y].start.x + 1, grid[x][y].end.x - 1); pt_y = DungeonRandRange(grid[x][y].start.y + 1, grid[x][y].end.y - 1); } else { // Hallway anchor, point is the 1x1 we've placed pt_x = grid[x][y].start.x; pt_y = grid[x][y].start.y; } if (grid[x][y].shouldConnectToTop) { // Connect to the cell above if (!grid[x][y - 1].isInvalid) { if (grid[x][y - 1].isRoom) { // Room, pick a random interior x coordinate pt2_x = DungeonRandRange(grid[x][y - 1].start.x + 1, grid[x][y - 1].end.x - 1); } else { // Anchor, use the central x coordinate pt2_x = grid[x][y - 1].start.x; } // Create the hallway CreateHallway(pt_x, grid[x][y].start.y, pt2_x, grid[x][y - 1].end.y - 1, TRUE, listX[x], listY[y]); } // Mark the connection and unassign it so we don't try to draw // a second connection from the other way grid[x][y].shouldConnectToTop = FALSE; grid[x][y - 1].shouldConnectToBottom = FALSE; grid[x][y].isConnected = TRUE; grid[x][y - 1].isConnected = TRUE; } if (grid[x][y].shouldConnectToBottom) { // Connect to the cell below if (!grid[x][y + 1].isInvalid) { if (grid[x][y + 1].isRoom) { // Room, pick a random interior x coordinate pt2_x = DungeonRandRange(grid[x][y + 1].start.x + 1, grid[x][y + 1].end.x - 1); } else { // Anchor, use the central x coordinate pt2_x = grid[x][y + 1].start.x; } // Create the hallway CreateHallway(pt_x, grid[x][y].end.y - 1, pt2_x, grid[x][y + 1].start.y, TRUE, listX[x], listY[y + 1] - 1); } // Mark the connection and unassign it so we don't try to draw // a second connection from the other way grid[x][y].shouldConnectToBottom = FALSE; grid[x][y + 1].shouldConnectToTop = FALSE; grid[x][y].isConnected = TRUE; grid[x][y + 1].isConnected = TRUE; } if (grid[x][y].shouldConnectToLeft) { // Connect to the cell on the left if (!grid[x - 1][y].isInvalid) { if (grid[x - 1][y].isRoom) { // Room, pick a random interior y coordinate pt2_y = DungeonRandRange(grid[x - 1][y].start.y + 1, grid[x - 1][y].end.y - 1); } else { // Anchor, use the central y coordinate pt2_y = grid[x - 1][y].start.y; } // Create the hallway // Using (grid[x-1][y].start.x - 1) is a bug, it should be (grid[x-1][y].end.x - 1) // But CreateHallway has safety checks making the end result the same anyways. CreateHallway(grid[x][y].start.x, pt_y, grid[x - 1][y].start.x - 1, pt2_y, FALSE, listX[x], listY[y]); } // Mark the connection and unassign it so we don't try to draw // a second connection from the other way grid[x][y].shouldConnectToLeft = FALSE; grid[x - 1][y].shouldConnectToRight = FALSE; grid[x][y].isConnected = TRUE; grid[x - 1][y].isConnected = TRUE; } if (grid[x][y].shouldConnectToRight) { // Connect to the cell on the right if (!grid[x + 1][y].isInvalid) { if (grid[x + 1][y].isRoom) { // Room, pick a random interior y coordinate pt2_y = DungeonRandRange(grid[x + 1][y].start.y + 1, grid[x + 1][y].end.y - 1); } else { // Anchor, use the central y coordinate pt2_y = grid[x + 1][y].start.y; } // Create the hallway CreateHallway(grid[x][y].end.x - 1, pt_y, grid[x + 1][y].start.x, pt2_y, FALSE, listX[x + 1] - 1, listY[y]); } // Mark the connection and unassign it so we don't try to draw // a second connection from the other way grid[x][y].shouldConnectToRight = FALSE; grid[x + 1][y].shouldConnectToLeft = FALSE; grid[x][y].isConnected = TRUE; grid[x + 1][y].isConnected = TRUE; } } } // If we don't want to merge rooms, we're done if (disableRoomMerging) { return; } // If we do, we can try to merge some! for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { s32 chance = DungeonRandInt(100); // Conditions for merging a room: // - rolls for merge chance // - valid // - connected to another room // - not already merged // - not have a secondary structure // - is a room, not an anchor if (chance < GENERATION_CONSTANT_MERGE_ROOMS_CHANCE && !grid[x][y].isInvalid && grid[x][y].isConnected && !grid[x][y].isMerged && !grid[x][y].hasSecondaryStructure && grid[x][y].isRoom) { s32 chanceTwo = DungeonRandInt(4); // Verify the same for the target room switch (chanceTwo) { case 0: if (x > 0 && grid[x - 1][y].isConnected && !grid[x - 1][y].isInvalid && grid[x - 1][y].isRoom && !grid[x - 1][y].hasSecondaryStructure && !grid[x - 1][y].isMerged) { // Merge with the room to the left s32 curX, curY; s32 srcX = grid[x - 1][y].start.x; s32 srcY = min(grid[x - 1][y].start.y, grid[x][y].start.y); s32 dstX = grid[x][y].end.x; s32 dstY = max(grid[x - 1][y].end.y, grid[x][y].end.y); // Use the original room's index s32 mergeRoomIndex = GetTile(grid[x][y].start.x, grid[x][y].start.y)->room; // Carve out the merged room for (curX = srcX; curX < dstX; curX++) { for (curY = srcY; curY < dstY; curY++) { Tile *tile = GetTileSafe(curX, curY); SetTerrainNormal(tile); tile->room = mergeRoomIndex; } } // Update room boundaries grid[x - 1][y].start.x = srcX; grid[x - 1][y].end.x = dstX; grid[x - 1][y].start.y = srcY; grid[x - 1][y].end.y = dstY; // Mark merge flags on both rooms grid[x][y].isMerged = TRUE; grid[x - 1][y].isMerged = TRUE; grid[x][y].isConnected = FALSE; grid[x][y].hasBeenMerged = TRUE; } break; case 1: if (y >= 1 && grid[x][y - 1].isConnected && !grid[x][y - 1].isInvalid && grid[x][y - 1].isRoom && !grid[x][y - 1].hasSecondaryStructure && !grid[x][y - 1].isMerged) { // Merge with the room above s32 curX, curY; s32 srcX = min(grid[x][y - 1].start.x, grid[x][y].start.x); s32 srcY = grid[x][y - 1].start.y; s32 dstX = max(grid[x][y - 1].end.x, grid[x][y].end.x); s32 dstY = grid[x][y].end.y; // Use the original room's index s32 mergeRoomIndex = GetTile(grid[x][y].start.x, grid[x][y].start.y)->room; // Carve out the merged room for (curX = srcX; curX < dstX; curX++) { for (curY = srcY; curY < dstY; curY++) { Tile *tile = GetTileSafe(curX, curY); SetTerrainNormal(tile); tile->room = mergeRoomIndex; } } // Update room boundaries grid[x][y - 1].start.x = srcX; grid[x][y - 1].end.x = dstX; grid[x][y - 1].start.y = srcY; grid[x][y - 1].end.y = dstY; // Mark merge flags on both rooms grid[x][y].isMerged = TRUE; grid[x][y - 1].isMerged = TRUE; grid[x][y].isConnected = FALSE; grid[x][y].hasBeenMerged = TRUE; } break; case 2: if (x <= gridSizeX - 2 && grid[x + 1][y].isConnected && !grid[x + 1][y].isInvalid && grid[x + 1][y].isRoom && !grid[x + 1][y].hasSecondaryStructure && !grid[x + 1][y].isMerged) { // Merge with the room to the right s32 curX, curY; s32 srcX = grid[x][y].start.x; s32 srcY = min(grid[x][y].start.y, grid[x + 1][y].start.y); s32 dstX = grid[x + 1][y].end.x; s32 dstY = max(grid[x][y].end.y, grid[x + 1][y].end.y); // Use the original room's index s32 mergeRoomIndex = GetTile(grid[x][y].start.x, grid[x][y].start.y)->room; // Carve out the merged room for (curX = srcX; curX < dstX; curX++) { for (curY = srcY; curY < dstY; curY++) { Tile *tile = GetTileSafe(curX, curY); SetTerrainNormal(tile); tile->room = mergeRoomIndex; } } // Update room boundaries grid[x][y].start.x = srcX; grid[x][y].end.x = dstX; grid[x][y].start.y = srcY; grid[x][y].end.y = dstY; // Mark merge flags on both rooms grid[x + 1][y].isMerged = TRUE; grid[x][y].isMerged = TRUE; grid[x + 1][y].isConnected = FALSE; grid[x + 1][y].hasBeenMerged = TRUE; } break; case 3: if (y <= gridSizeY - 2 && grid[x][y + 1].isConnected && !grid[x][y + 1].isInvalid && grid[x][y + 1].isRoom && !grid[x][y + 1].hasSecondaryStructure && !grid[x][y + 1].isMerged) { // Merge with the room below s32 curX, curY; s32 srcX = min(grid[x][y].start.x, grid[x][y + 1].start.x); s32 srcY = grid[x][y].start.y; s32 dstX = max(grid[x][y].end.x, grid[x][y + 1].end.x); s32 dstY = grid[x][y + 1].end.y; // Use the original room's index s32 mergeRoomIndex = GetTile(grid[x][y].start.x, grid[x][y].start.y)->room; // Carve out the merged room for (curX = srcX; curX < dstX; curX++) { for (curY = srcY; curY < dstY; curY++) { Tile *tile = GetTileSafe(curX, curY); SetTerrainNormal(tile); tile->room = mergeRoomIndex; } } // Update room boundaries grid[x][y].start.x = srcX; grid[x][y].end.x = dstX; grid[x][y].start.y = srcY; grid[x][y].end.y = dstY; // Mark merge flags on both rooms grid[x][y + 1].isMerged = TRUE; grid[x][y].isMerged = TRUE; grid[x][y + 1].isConnected = FALSE; grid[x][y + 1].hasBeenMerged = TRUE; } break; } } } } } extern const bool8 gUnknown_80F6DD5[][NUM_DIRECTIONS]; /* * GenerateRoomImperfections - Attempt to generate room imperfections for each room, if flagged to do so. * * Rooms are flagged for whether to allow imperfections in CreateRoomsAndAnchors. * Each qualifying flagged room has a 40% chance to generate imperfections in the room. * * Imperfections are generated by randomly growing walls of the room inwards * for a certain number of iterations, depending on the average length of the room. * * Each iteration will go in both a counterclockwise and clockwise generation movement (not necessarily for the same corner though) * * We pick a random corner, derive our direction from the movement being used, then seek up to 10 tiles for one to replace. * We avoid getting too close to hallways and ensure our cardinal neighbor tiles match what we expect for open terrain. */ void GenerateRoomImperfections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY) { s32 x, y; for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { s32 counter, length; // To have imperfections a room must: // - be valid // - not have been merged // - be a room // - be connected // - not have a secondary structure // - not be a maze room // - be flagged to be made imperfect if (grid[x][y].isInvalid) continue; if (grid[x][y].hasBeenMerged) continue; if (grid[x][y].isMerged) continue; if (!grid[x][y].isConnected) continue; if (!grid[x][y].isRoom) continue; if (grid[x][y].hasSecondaryStructure) continue; if (grid[x][y].isMazeRoom) continue; if (!grid[x][y].flagImperfect) continue; // Roll for imperfections // By default, is a 40% chance that the room will still have imperfections if (DungeonRandInt(100) < GENERATION_CONSTANT_NO_IMPERFECTIONS_CHANCE) continue; length = grid[x][y].end.x - grid[x][y].start.x + (grid[x][y].end.y - grid[x][y].start.y); length /= 4; if (length == 0) length = 1; // Shrink the room from its corners either in the x or y direction // Repeat the number of times equal to the average room length for (counter = 0; counter < length; counter++) { s32 i, v; for (i = 0; i < 2; i++) { // Start from one of four corners // i == 0 => fill in walls counterclockwise // i == 1 => fill in walls clockwise s32 pt_x, pt_y; s32 moveX, moveY; s32 startingCorner = DungeonRandInt(4); switch (startingCorner) { // Top-left corner case 0: default: pt_x = grid[x][y].start.x; pt_y = grid[x][y].start.y; if (i != 0) { moveX = 1; moveY = 0; } else { moveX = 0; moveY = 1; } break; // Top-right corner case 1: pt_x = grid[x][y].end.x - 1; pt_y = grid[x][y].start.y; if (i != 0) { moveX = 0; moveY = 1; } else { moveX = -1; moveY = 0; } break; // Bottom-right corner case 2: pt_x = grid[x][y].end.x - 1; pt_y = grid[x][y].end.y - 1; if (i != 0) { moveX = -1; moveY = 0; } else { moveX = 0; moveY = -1; } break; // Bottom-left corner case 3: pt_x = grid[x][y].start.x; pt_y = grid[x][y].end.y - 1; if (i != 0) { moveX = 0; moveY = -1; } else { moveX = 1; moveY = 0; } break; } // Search up to 10 tiles for a new tile to replace // from the selected starting corner and direction for (v = 0; v < 10; v++) { // Make sure we're still in bounds if (pt_x < grid[x][y].start.x || pt_x >= grid[x][y].end.x) break; if (pt_y < grid[x][y].start.y || pt_y >= grid[x][y].end.y) break; if (GetTerrainType(GetTile(pt_x, pt_y)) == TERRAIN_TYPE_NORMAL) { // Make sure there aren't any hallways within 2 spaces from the current tile // If there are, skip filling it in s32 direction = DIRECTION_SOUTH; while (direction < NUM_DIRECTIONS) { s32 offsetX, offsetY; s32 nextX = pt_x + gAdjacentTileOffsets[direction].x; s32 nextY = pt_y + gAdjacentTileOffsets[direction].y; bool8 found = FALSE; for (offsetY = -1; offsetY <= 1; offsetY++) { for (offsetX = -1; offsetX <= 1; offsetX++) { // Search for open terrain which is not a part of a room (a hallway) Tile *tile = GetTile(nextX + offsetX, nextY + offsetY); if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL) continue; if (tile->room == CORRIDOR_ROOM) { found = TRUE; } if (found) break; } if (found) break; } if (found) break; direction++; } // If direction == NUM_DIRECTIONS, we didn't find any hallways and are good to proceed if (direction == NUM_DIRECTIONS) { // Check that our cardinal neighbors' terrain types match what we expect for generating new tiles in this direction // For example, if we're generating from the top-left corner, we should only expect tiles // below us or to our right to have open terrain. If another tile does, we should stop // because the resulting room may look strange otherwise direction = DIRECTION_SOUTH; while (direction < NUM_DIRECTIONS) { s32 nextX = gAdjacentTileOffsets[direction].x; s32 nextY = gAdjacentTileOffsets[direction].y; bool8 isOpen = (GetTerrainType(GetTile(pt_x + nextX, pt_y + nextY)) == TERRAIN_TYPE_NORMAL); if (gUnknown_80F6DD5[startingCorner][direction] != isOpen) break; // Advance by 2 to only check cardinal directions direction += 2; } // If direction == NUM_DIRECTIONS, the neighbors match what we expect if (direction == NUM_DIRECTIONS) { // Fill in the current open floor tile with a wall SetTerrainWall(GetTileSafe(pt_x, pt_y)); } } break; } else { // The terrain is filled or already a wall, move to the next tile pt_x += moveX; pt_y += moveY; } } } } } } } /* * CreateHallway - Creates a hallway between two points. * * |---------B * | * A------| * * The hallway generated consists of two parallel paths connected by a perpendicular "kink" in the path * The "kink" in a path occurs along (turn_x, turnY), which in practice is the grid cell boundary * If the paths trace same line, no "kink" will be generated * * If generation runs into an existing open tile, creation stops prematurely (such as another hallway). * * The vertical flag specifies whether the hallway is being generated horizontally or vertically */ void CreateHallway(s32 x, s32 y, s32 endX, s32 endY, bool8 vertical, s32 turnX_, s32 turnY_) { s32 startX = x; s32 startY = y; s32 turnX = turnX_; s32 turnY = turnY_; s32 counter; if (vertical) { // Vertical hallway counter = 0; // Create the vertical line between the starting point and the grid cell boundary while (y != turnY) { if (counter++ >= DUNGEON_MAX_SIZE_X) return; // Sanity check! if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) { SetTerrainNormal(GetTileSafe(x, y)); } else { // If we find open floor, stop here // The hall has connected up to an existing hall if (x != startX || y != startY) return; } if (y < turnY) { y++; } else { y--; } } counter = 0; // Create the horizontal line to connect the horizontal lines at two different x values while (x != endX) { if (counter++ >= DUNGEON_MAX_SIZE_X) return; // Sanity check! if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) { SetTerrainNormal(GetTileSafe(x, y)); } else { if (x != startX || y != startY) return; } if (x < endX) { x++; } else { x--; } } counter = 0; // Create the vertical line between the end point and the grid cell while (y != endY) { if (counter++ >= DUNGEON_MAX_SIZE_X) return; if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) { SetTerrainNormal(GetTileSafe(x, y)); } else { if (x != startX || y != startY) return; } if (y < endY) { y++; } else { y--; } } } else { // Horizontal hallway counter = 0; // Create the horizontal line between the starting point and the grid cell boundary while (x != turnX) { if (counter++ >= DUNGEON_MAX_SIZE_X) return; // Sanity check! if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) { SetTerrainNormal(GetTileSafe(x, y)); } else { // If we find open floor, stop here // The hall has connected up to an existing hall if (x != startX || y != startY) return; } if (x < turnX) { x++; } else { x--; } } counter = 0; // Create the vertical line to connect the horizontal lines at two different y values while (y != endY) { if (counter++ >= DUNGEON_MAX_SIZE_X) return; if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) { SetTerrainNormal(GetTileSafe(x, y)); } else { if (x != startX || y != startY) return; } if (y < endY) { y++; } else { y--; } } counter = 0; // Create the horizontal line between the end point and the grid cell while (x != endX) { if (counter++ >= DUNGEON_MAX_SIZE_X) return; if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) { SetTerrainNormal(GetTileSafe(x, y)); } else { if (x != startX || y != startY) return; } if (x < endX) { x++; } else { x--; } } } } /* * EnsureConnectedGrid - Ensure the grid forms a connected graph (all valid cells are reachable) by adding hallways to unreachable cells * * If the unconnected cell is a hallway anchor, it will be ignored and filled in. * If the unconnected cell is a room, it will check all adjacent directions for a connected room, then add a hallway between if found. * * If no eligible room is found, the room will be removed and filled back in. */ void EnsureConnectedGrid(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY) { s32 x, y; for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { // If any of these is TRUE, this cell is fine and we don't need to worry about it if (grid[x][y].isInvalid) continue; if (grid[x][y].hasBeenMerged) continue; if (grid[x][y].isConnected) continue; if (grid[x][y].isRoom && !grid[x][y].hasSecondaryStructure) { // Unconnected room s32 pt_x, pt_y; s32 rnd_x = DungeonRandRange(grid[x][y].start.x + 1, grid[x][y].end.x - 1); s32 rnd_y = DungeonRandRange(grid[x][y].start.y + 1, grid[x][y].end.y - 1); if (y > 0 && !grid[x][y - 1].isInvalid && !grid[x][y - 1].isMerged && grid[x][y - 1].isConnected) { // Attempt to connect to the grid cell above if it's connected if (grid[x][y - 1].isRoom) { // Room, take random interior x coordinate pt_x = DungeonRandRange(grid[x][y - 1].start.x + 1, grid[x][y - 1].end.x - 1); pt_y = DungeonRandRange(grid[x][y - 1].start.y + 1, grid[x][y - 1].end.y - 1); // Unused } else { // Anchor, take center x pt_x = grid[x][y - 1].start.x; pt_y = grid[x][y - 1].start.y; // Unused } CreateHallway(rnd_x, grid[x][y].start.y, pt_x, grid[x][y - 1].end.y - 1, TRUE, listX[x], listY[y]); grid[x][y].isConnected = TRUE; grid[x][y].connectedToTop = TRUE; grid[x][y - 1].connectedToBottom = TRUE; } else if (y < gridSizeY - 1 && !grid[x][y + 1].isInvalid && !grid[x][y + 1].isMerged && grid[x][y + 1].isConnected) { // Attempt to connect to the grid cell below if it's connected if (grid[x][y + 1].isRoom) { // Room, take random interior x coordinate pt_x = DungeonRandRange(grid[x][y + 1].start.x + 1, grid[x][y + 1].end.x - 1); pt_y = DungeonRandRange(grid[x][y + 1].start.y + 1, grid[x][y + 1].end.y - 1); // Unused } else { // Anchor, take center x pt_x = grid[x][y + 1].start.x; pt_y = grid[x][y + 1].start.y; // Unused } CreateHallway(rnd_x, grid[x][y].end.y - 1, pt_x, grid[x][y + 1].start.y, TRUE, listX[x], listY[y + 1] - 1); grid[x][y].isConnected = TRUE; grid[x][y].connectedToBottom = TRUE; grid[x][y + 1].connectedToTop = TRUE; } else if (x > 0 && !grid[x - 1][y].isInvalid && !grid[x - 1][y].isMerged && grid[x - 1][y].isConnected) { // Attempt to connect to the grid cell left if it's connected if (grid[x - 1][y].isRoom) { // Room, take random interior y coordinate pt_x = DungeonRandRange(grid[x - 1][y].start.x + 1, grid[x - 1][y].end.x - 1); //Unused pt_y = DungeonRandRange(grid[x - 1][y].start.y + 1, grid[x - 1][y].end.y - 1); } else { // Anchor, take center y pt_x = grid[x - 1][y].start.x; // Unused pt_y = grid[x - 1][y].start.y; } // Typo? Would expect grid[x - 1][y].end.x - 1 for 3rd parameter CreateHallway(grid[x][y].start.x, rnd_y, grid[x - 1][y].start.x - 1, pt_y, FALSE, listX[x], listY[y]); grid[x][y].isConnected = TRUE; grid[x][y].connectedToLeft = TRUE; grid[x - 1][y].connectedToRight = TRUE; } else if (x < gridSizeX - 1 && !grid[x + 1][y].isInvalid && !grid[x + 1][y].isMerged && grid[x + 1][y].isConnected) { // Attempt to connect to the grid cell right if it's connected if (grid[x + 1][y].isRoom) { // Room, take random interior y coordinate pt_x = DungeonRandRange(grid[x + 1][y].start.x + 1, grid[x + 1][y].end.x - 1); // Unused pt_y = DungeonRandRange(grid[x + 1][y].start.y + 1, grid[x + 1][y].end.y - 1); } else { // Anchor, take center y pt_x = grid[x + 1][y].start.x; // Unused pt_y = grid[x + 1][y].start.y; } CreateHallway(grid[x][y].end.x - 1, rnd_y, grid[x + 1][y].start.x, pt_y, FALSE, listX[x + 1] - 1, listY[y]); grid[x][y].isConnected = TRUE; grid[x][y].connectedToRight = TRUE; grid[x + 1][y].connectedToLeft = TRUE; } } else { // Unconnected anchor, don't bother trying. // Just fill it in with wall tiles Tile *tile = GetTileSafe(grid[x][y].start.x, grid[x][y].start.y); SetTerrainWall(tile); // Also remove any spawn flags // TODO: rename to spawn_or_visibility_flags stairs/item/trap tile->unk4 &= ~(0x1); tile->unk4 &= ~(0x2); tile->unk4 &= ~(0x4); } } } // If any rooms are still unconnected (meaning attempts to connect failed) // Fill in the rooms for (x = 0; x < gridSizeX; x++) { for (y = 0; y < gridSizeY; y++) { s32 curX, curY; if (grid[x][y].isInvalid || grid[x][y].hasBeenMerged || grid[x][y].isConnected || grid[x][y].unk15) continue; for (curX = grid[x][y].start.x; curX < grid[x][y].end.x; curX++) { for (curY = grid[x][y].start.y; curY < grid[x][y].end.y; curY++) { Tile *tile = GetTileSafe(curX, curY); // Set it to wall terrain SetTerrainWall(tile); // Remove any spawn flags tile->unk4 &= ~(0x2); tile->unk4 &= ~(0x1); tile->unk4 &= ~(0x4); // Set room index to 0xFF (not a room) tile->room = CORRIDOR_ROOM; } } } } } /* * SetTerrainObstacleChecked - Sets terrain on a specific tile as an obstacle (either a wall or secondary terrain) * * If secondary terrain is requested and the room indices match, secondary terrain (water/lava) will be placed for the tile. * * Otherwise, the tile will be a wall. */ void SetTerrainObstacleChecked(Tile *tile, bool8 useSecondaryTerrain, u8 roomIndex) { SetTerrainWall(tile); if (useSecondaryTerrain && tile->room == roomIndex) { SetTerrainSecondary(tile); } } void sub_804E9DC(void) { s32 x, y; for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) { for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) { if (GetTerrainType(GetTile(x, y)) != TERRAIN_TYPE_NORMAL) continue; if (GetTile(x, y)->room == CORRIDOR_ROOM) { if (x > 0) { Tile *tile = GetTileSafe(x - 1, y); if (tile->room != CORRIDOR_ROOM) { tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) { SetTerrainType(tile, TERRAIN_TYPE_NORMAL); } } } if (y > 0) { Tile *tile = GetTileSafe(x, y - 1); if (tile->room != CORRIDOR_ROOM) { // Yes, these |= have to be duplicated in order to match. Either it's an error and they wanted to use a different flag, or just copy-pasted it twice. tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) { SetTerrainType(tile, TERRAIN_TYPE_NORMAL); } } } if (y < DUNGEON_MAX_SIZE_Y - 1) { Tile *tile = GetTileSafe(x, y + 1); if (tile->room != CORRIDOR_ROOM) { tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) { SetTerrainType(tile, TERRAIN_TYPE_NORMAL); } } } if (x < DUNGEON_MAX_SIZE_X - 1) { Tile *tile = GetTileSafe(x + 1, y); if (tile->room != CORRIDOR_ROOM) { tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; tile->terrainType |= TERRAIN_TYPE_NATURAL_JUNCTION; if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) { SetTerrainType(tile, TERRAIN_TYPE_NORMAL); } } } } else if (GetTile(x, y)->room == ROOM_0xFE) { GetTileSafe(x, y)->room = CORRIDOR_ROOM; } } } sub_804EB30(); } void sub_804EB30(void) { s32 i; s32 x, y; Dungeon *dungeon = gDungeon; for (i = 0; i < MAX_ROOM_COUNT; i++) { dungeon->naturalJunctionListCounts[i] = 0; } for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) { for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) { if (GetTile(x, y)->terrainType & TERRAIN_TYPE_NATURAL_JUNCTION) { u32 roomIndex = GetTile(x, y)->room; if (roomIndex < MAX_ROOM_COUNT && dungeon->naturalJunctionListCounts[roomIndex] < MAX_ROOM_COUNT) { dungeon->naturalJunctionList[roomIndex][dungeon->naturalJunctionListCounts[roomIndex]].x = x; dungeon->naturalJunctionList[roomIndex][dungeon->naturalJunctionListCounts[roomIndex]].y = y; dungeon->naturalJunctionListCounts[roomIndex]++; } } } } } /* * GenerateKecleonShop - Potentially generate a kecleon shop on the floor. * * To spawn a kecleon shop, the floor cannot have a monster house, must not be a rescue floor, and must roll * the percentage chance of kecleon_chance. * * Grid cells indices are shuffled, then each is checked to meet the conditions to spawn a kecleon shop in that cell: * The room must be valid, connected, have no other special features, and have dimensions of at least 5x4. * * Once the first room (if any) is found that meets these conditions, the room is assigned as a kecleon shop. * The kecleon shop will occupy the whole room interior, with a one tile margin from the room walls. * Kecleon shop tiles restrict monster and stair spawns. */ void GenerateKecleonShop(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance) { s16 listX[GRID_CELL_LEN]; s16 listY[GRID_CELL_LEN]; s32 x, y; s32 i, j; Dungeon *dungeon = gDungeon; gUnknown_202F1D8.x = -1; gUnknown_202F1D8.y = -1; if (gUnknown_202F1AB || sub_8043D10() == 2 || chance == 0) return; if (chance <= DungeonRandInt(100)) return; // All possible grid cells for (i = 0; i < GRID_CELL_LEN; i++) { listX[i] = i; listY[i] = i; } // Shuffle x indices for (i = 0; i < 200; i++) { s32 temp; s32 a = DungeonRandInt(GRID_CELL_LEN); s32 b = DungeonRandInt(GRID_CELL_LEN); SWAP(listX[a], listX[b], temp); } // Shuffle y indices for (i = 0; i < 200; i++) { s32 temp; s32 a = DungeonRandInt(GRID_CELL_LEN); s32 b = DungeonRandInt(GRID_CELL_LEN); SWAP(listY[a], listY[b], temp); } for (i = 0; i < GRID_CELL_LEN; i++) { x = listX[i]; if (x >= gridSizeX) continue; for (j = 0; j < GRID_CELL_LEN; j++) { s32 curX, curY; y = listY[j]; if (y >= gridSizeY) continue; // We've identified a random in-bounds grid cell // To support a kecleon shop it must be: // - valid // - not a merged room // - connected // - a room // - have no other special features (mazes/secondary structures) // - have dimensions of at least 5x4 if (grid[x][y].isInvalid) continue; if (grid[x][y].hasBeenMerged) continue; if (grid[x][y].isMerged) continue; if (!grid[x][y].isConnected) continue; if (!grid[x][y].isRoom) continue; if (grid[x][y].hasSecondaryStructure) continue; if (grid[x][y].isMazeRoom) continue; if (grid[x][y].flagSecondaryStructure) continue; if (abs(grid[x][y].end.x - grid[x][y].start.x) < 5 || abs(grid[x][y].end.y - grid[x][y].start.y) < 4) continue; // This room can be a kecleon shop gUnknown_202F1AA = TRUE; grid[x][y].isKecleonShop = TRUE; // Make the shop span the whole room gUnknown_202F1B8.minX = grid[x][y].start.x + 1; gUnknown_202F1B8.maxX = grid[x][y].end.x - 1; gUnknown_202F1B8.minY = grid[x][y].start.y + 1; gUnknown_202F1B8.maxY = grid[x][y].end.y - 1; if (gUnknown_202F1B8.maxY - gUnknown_202F1B8.minY < 3) { // This should never happen? gUnknown_202F1B8.maxY++; } // Set to values that guarantee they'll be replaced later dungeon->kecleonShopPos.minX = DEFAULT_MAX_POSITION; dungeon->kecleonShopPos.minY = DEFAULT_MAX_POSITION; dungeon->kecleonShopPos.maxX = -DEFAULT_MAX_POSITION; dungeon->kecleonShopPos.maxY = -DEFAULT_MAX_POSITION; // Generate the actual shop on the interior, leaving // a 1-tile border from the room walls for (curX = gUnknown_202F1B8.minX; curX < gUnknown_202F1B8.maxX; curX++) { for (curY = gUnknown_202F1B8.minY; curY < gUnknown_202F1B8.maxY; curY++) { Tile *tile = GetTileSafe(curX, curY); tile->terrainType |= TERRAIN_TYPE_SHOP; // Restrict monsters and stairs from spawning here tile->unk4 &= ~(0x8); tile->unk4 &= ~(0x1); // Ensure the borders are assigned properly if (dungeon->kecleonShopPos.minX > curX) { dungeon->kecleonShopPos.minX = curX; } if (dungeon->kecleonShopPos.minY > curY) { dungeon->kecleonShopPos.minY = curY; } if (dungeon->kecleonShopPos.maxX < curX) { dungeon->kecleonShopPos.maxX = curX; } if (dungeon->kecleonShopPos.maxY < curY) { dungeon->kecleonShopPos.maxY = curY; } } } // Sets an unknown spawn flag for all tiles in the room for (curX = grid[x][y].start.x; curX < grid[x][y].end.x; curX++) { for (curY = grid[x][y].start.y; curY < grid[x][y].end.y; curY++) { GetTileSafe(curX, curY)->unk4 |= 0x10; } } curX = (gUnknown_202F1B8.minX + gUnknown_202F1B8.maxX) / 2; curY = (gUnknown_202F1B8.minY + gUnknown_202F1B8.maxY) / 2; gUnknown_202F1D8.x = curX; gUnknown_202F1D8.y = curY; return; } } } //