Files
pmd-red/src/code_804AFAC_1.c
T
2024-11-11 21:01:00 +01:00

3769 lines
120 KiB
C

#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;
}
}
}
//