pmd-red/src/dungeon_generation.c
2025-11-25 00:36:29 -05:00

6129 lines
215 KiB
C

#include "global.h"
#include "globaldata.h"
#include "def_filearchives.h"
#include "dungeon_generation.h"
#include "file_system.h"
#include "dungeon_map_access.h"
#include "dungeon_message.h"
#include "dungeon_random.h"
#include "dungeon_util.h"
#include "items.h"
#include "pokemon.h"
#include "constants/direction.h"
#include "constants/fixed_rooms.h"
#include "constants/item.h"
#include "constants/monster.h"
#include "structs/str_dungeon.h"
#include "structs/map.h"
#include "dungeon_config.h"
#include "dungeon_generation_fixed.h"
#include "dungeon_pos_data.h"
#include "dungeon_data.h"
#include "dungeon_mon_spawn.h"
#include "run_dungeon.h"
enum CardinalDirection
{
CARDINAL_DIR_RIGHT,
CARDINAL_DIR_UP,
CARDINAL_DIR_LEFT,
CARDINAL_DIR_DOWN,
NUM_CARDINAL_DIRECTIONS
};
#define CARDINAL_DIRECTION_MASK 3
struct GridCell
{
DungeonPos start;
DungeonPos 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
static void ResetFloor(void);
static void sub_804C790(s32 gridSizeX, s32 gridSizeY, s32 fixedRoomSizeX, s32 fixedRoomSizeY, s32 fixedRoomNumber, FloorProperties *floorProps);
static void CreateRoomsAndAnchorsForFixedFloor(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, s32 a5, s32 fixedRoomSizeX, s32 fixedRoomSizeY);
static void sub_8051438(struct GridCell *gridCell, s32 fixedRoomNumber);
static void sub_8051288(s32 fixedRoomNumber);
static void GetGridPositions(s32 *listX, s32 *listY, s32 gridSizeX, s32 gridSizeY);
static void InitDungeonGrid(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY);
static void GenerateRoomImperfections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY);
static void GenerateSecondaryStructure(struct GridCell *gridCell);
static void GenerateSecondaryStructures(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY);
static void AssignRooms(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 roomsNumber);
static void CreateRoomsAndAnchors(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, u32 roomFlags);
static void CreateGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, bool8 disableRoomMerging);
static 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, FloorProperties *floorProps);
static void AssignGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 cursorX, s32 cursorY, FloorProperties *floorProps);
static void GenerateMazeRoom(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance);
static void GenerateMaze(struct GridCell *gridCell, bool8 useSecondaryTerrain);
static void GenerateMazeLine(s32 x0, s32 y0, s32 xMin, s32 yMin, s32 xMax, s32 yMax, bool8 useSecondaryTerrain, u32 roomIndex);
static void GenerateKecleonShop(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance);
static void GenerateMonsterHouse(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance);
static 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]);
static void CreateHallway(s32 startX, s32 startY, s32 endX, s32 endY, bool8 vertical, s32 turnX, s32 turnY);
static void EnsureImpassableTilesAreWalls(void);
static void sub_804FC74(void);
static void FinalizeJunctions(void);
static void sub_804B534(s32 a0, s32 a1, s32 a2, s32 a3);
static bool8 ProcessFixedRoom(s32 fixedRoomNumber, FloorProperties *floorProps);
static void GenerateStandardFloor(s32 a0, s32 a1, FloorProperties *a2);
static void GenerateOuterRingFloor(FloorProperties *a0);
static void GenerateCrossroadsFloor(FloorProperties *a0);
static void GenerateLineFloor(FloorProperties *a0);
static void GenerateCrossFloor(FloorProperties *a0);
static void GenerateBeetleFloor(FloorProperties *a0);
static void GenerateOuterRoomsFloor(s32 gridSizeX_, s32 gridSizeY_, FloorProperties *floorProps);
static void sub_8051654(FloorProperties *floorProps);
static void GenerateSecondaryTerrainFormations(u32 flag, FloorProperties *floorProps);
static void SpawnNonEnemies(FloorProperties *floorProps, bool8 isEmptyMonsterHouse);
static void SpawnEnemies(FloorProperties *floorProps, bool8 isEmptyMonsterHouse);
static void ResetInnerBoundaryTileRows(void);
static void GenerateOneRoomMonsterHouseFloor(void);
static void ResolveInvalidSpawns(void);
static void GenerateTwoRoomsWithMonsterHouseFloor(void);
EWRAM_DATA bool8 gUnknown_202F1A8 = FALSE;
static EWRAM_DATA bool8 sInvalidGeneration = FALSE;
static EWRAM_DATA bool8 sHasKecleonShop = FALSE;
static EWRAM_DATA bool8 sHasMonsterHouse = FALSE;
static EWRAM_DATA bool8 sHasMaze = FALSE;
static EWRAM_DATA bool8 sSecondSpawn = FALSE;
static EWRAM_DATA u8 sFloorSize = 0;
static EWRAM_DATA s16 sKecleonShopChance = 0;
static EWRAM_DATA s16 sMonsterHouseChance = 0;
static EWRAM_DATA u8 sStairsRoomIndex = 0;
static EWRAM_DATA struct MinMaxPosition sKecleonShopPosition = {0};
static EWRAM_DATA s32 sSecondaryStructuresBudget = 0;
static EWRAM_DATA s32 sNumRooms = 0;
static EWRAM_DATA s32 sFloorLayout = 0;
static EWRAM_DATA s32 sNumTilesReachableFromStairs = 0;
static EWRAM_DATA DungeonPos sKecleonShopMiddlePos = {0};
struct FixedRoomsData
{
u8 x;
u8 y;
u8 unk2;
u8 unk3[0]; // Not sure about the size;
};
/*
* GenerateFloor - The Master Function for generating a dungeon floor
*
* Runs based on 3 loop levels of safety
*
* Innermost Loop: 32 attempts for deciding the maximum rooms in each dimension for the floor
* - The dimensions are capped at 6x4 (but we can randomize outside this range and fail)
* - Must maintain a certain number of tiles per grid cell in both dimensions
* - If this loop fails 32 times, defaults to a 4x4 maximum
*
* Inner Main Loop: 10 attempts for the main layout of a floor
* - This loop can fail by being marked invalid during generation
* - Or, it can fail by having < 2 rooms or < 20 room tiles
* - This occurs prior to secondary terrain generation
* - If the loop fails 10 times, defaults to generating a One-Room Monster House
*
* Outermost Loop: 10 attempts for everything involved in generating a layout
* - This is where secondary terrain generation and junction additions takes place
* - This loop can fail during spawn location verification (can you get to the stairs?)
* - If the loop fails 10 times, defaults to generating a One-Room Monster House
*/
void GenerateFloor(void)
{
s32 x, y;
s32 spawnAttempts;
bool8 secondaryGen = FALSE;
FloorProperties *floorProps = &gDungeon->floorProperties;
gDungeon->unk13568 = OpenFileAndGetFileDataPtr("fixedmap", &gDungeonFileArchive);
sHasKecleonShop = FALSE;
sHasMonsterHouse = 0;
sHasMaze = FALSE;
gUnknown_202F1A8 = (gDungeonWaterType[gDungeon->tileset] == DUNGEON_WATER_TYPE_WATER);
sStairsRoomIndex = 0xFF;
sFloorSize = 0;
sKecleonShopChance = floorProps->kecleonShopChance;
sMonsterHouseChance = floorProps->monsterHouseChance;
sSecondSpawn = TRUE;
sKecleonShopPosition.minX = -1;
sKecleonShopPosition.maxX = -1;
sKecleonShopPosition.minY = -1;
sKecleonShopPosition.maxY = -1;
ResetFloor();
gDungeon->unk644.enemyDensity = abs(floorProps->enemyDensity);
gDungeon->unk3A09 = 0;
gDungeon->unk3A0A = 0;
sSecondaryStructuresBudget = floorProps->secondaryStructuresBudget;
for (spawnAttempts = 0; spawnAttempts < 10; spawnAttempts++) {
s32 genAttempts;
bool32 isEmptyMonsterHouse;
gDungeon->playerSpawn.x = -1;
gDungeon->playerSpawn.y = -1;
gDungeon->stairsSpawn.x = -1;
gDungeon->stairsSpawn.y = -1;
// Actual generation attempts, up to 10 times per entity
for (genAttempts = 0; genAttempts < 10; genAttempts++) {
gDungeon->unk3A16 = genAttempts;
if (genAttempts > 0) {
sSecondaryStructuresBudget = 0;
}
sInvalidGeneration = FALSE;
sKecleonShopMiddlePos.x = -1;
sKecleonShopMiddlePos.y = -1;
ResetFloor();
gDungeon->playerSpawn.x = -1;
gDungeon->playerSpawn.y = -1;
gDungeon->forceMonsterHouse = FALSE;
if (gDungeon->fixedRoomNumber != 0) {
// Check for a full-floor fixed room, if this is the case, generation is done.
if (ProcessFixedRoom(gDungeon->fixedRoomNumber, floorProps)) {
break;
}
}
else {
s32 gridSizeX, gridSizeY;
s32 attempts;
u8 layout = floorProps->layout;
// Attempt to generate random grid dimensions
attempts = 32;
while (TRUE) {
if (layout != LAYOUT_LARGE_0x8) {
gridSizeX = DungeonRandRange(2, 9);
gridSizeY = DungeonRandRange(2, 8);
}
else {
gridSizeX = DungeonRandRange(2, 5);
gridSizeY = DungeonRandRange(2, 4);
}
// Limit overall dimensions
if (gridSizeX <= 6 && gridSizeY <= 4) {
break;
}
if (--attempts == 0) {
// We failed to generate random grid dimensions, default to 4x4
gridSizeX = 4;
gridSizeY = 4;
break;
}
}
// Make sure there are at least 7 tiles per grid cell in both
// dimensions. Otherwise, the grid size is too big so default to 1
if (DUNGEON_MAX_SIZE_X / gridSizeX < 8) {
gridSizeX = 1;
}
if (DUNGEON_MAX_SIZE_Y / gridSizeY < 8) {
gridSizeY = 1;
}
gDungeon->forceMonsterHouse = FALSE;
gDungeon->monsterHouseRoom = 0xFF;
sFloorLayout = layout;
switch (layout % NUM_FLOOR_LAYOUTS) {
case LAYOUT_SMALL:
gridSizeX = 4;
gridSizeY = DungeonRandInt(2) + 2;
sFloorSize = FLOOR_SIZE_SMALL;
GenerateStandardFloor(gridSizeX, gridSizeY, floorProps);
secondaryGen = TRUE;
break;
case LAYOUT_MEDIUM:
gridSizeX = 4;
gridSizeY = DungeonRandInt(2) + 2;
sFloorSize = FLOOR_SIZE_MEDIUM;
GenerateStandardFloor(gridSizeX, gridSizeY, floorProps);
secondaryGen = TRUE;
break;
case LAYOUT_LARGE:
case LAYOUT_LARGE_0x8:
default:
GenerateStandardFloor(gridSizeX, gridSizeY, floorProps);
secondaryGen = TRUE;
break;
case LAYOUT_ONE_ROOM_MONSTER_HOUSE:
GenerateOneRoomMonsterHouseFloor();
gDungeon->forceMonsterHouse = TRUE;
break;
case LAYOUT_OUTER_RING:
GenerateOuterRingFloor(floorProps);
secondaryGen = TRUE;
break;
case LAYOUT_CROSSROADS:
GenerateCrossroadsFloor(floorProps);
secondaryGen = TRUE;
break;
case LAYOUT_TWO_ROOMS_WITH_MONSTER_HOUSE:
GenerateTwoRoomsWithMonsterHouseFloor();
gDungeon->forceMonsterHouse = TRUE;
break;
case LAYOUT_LINE:
GenerateLineFloor(floorProps);
secondaryGen = TRUE;
break;
case LAYOUT_CROSS:
GenerateCrossFloor(floorProps);
break;
case LAYOUT_BEETLE:
GenerateBeetleFloor(floorProps);
break;
case LAYOUT_OUTER_ROOMS:
GenerateOuterRoomsFloor(gridSizeX, gridSizeY, floorProps);
secondaryGen = TRUE;
break;
}
}
ResetInnerBoundaryTileRows();
EnsureImpassableTilesAreWalls();
// Nothing failed during generation. This variable is always set to FALSE, so the check always passes
if (!sInvalidGeneration) {
// We need to make sure there are at least 2 rooms with at least 20 total tiles
s32 numRooms = 0;
bool8 rooms[64];
s32 roomTiles = 0;
s32 i;
for (i = 0; i < 64; i++) {
rooms[i] = FALSE;
}
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
const Tile *tile = GetTile(x, y);
if ((tile->terrainFlags & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)) == TERRAIN_TYPE_NORMAL && tile->room <= 240) {
roomTiles++;
if (tile->room < 64) {
rooms[tile->room] = TRUE;
}
}
}
}
numRooms = 0;
// In order to match, 'y' had to be re-used as an iterator var
for (y = 0; y < 64; y++) {
if (rooms[y]) {
numRooms++;
}
}
if (roomTiles >= 30 && numRooms >= 2) {
break; // This layout is good!
}
}
}
// If we fail to generate a layout in 10 attempts, just abort and make a one-room Monster House
if (genAttempts == 10) {
sKecleonShopMiddlePos.x = -1;
sKecleonShopMiddlePos.y = -1;
GenerateOneRoomMonsterHouseFloor();
gDungeon->forceMonsterHouse = TRUE;
}
// We will be guaranteed to have a good layout by this point
FinalizeJunctions();
if (secondaryGen) {
GenerateSecondaryTerrainFormations(ROOM_FLAG_ALLOW_SECONDARY_TERRAIN, floorProps);
}
isEmptyMonsterHouse = (DungeonRandInt(100) < floorProps->itemlessMonsterHouseChance);
SpawnNonEnemies(floorProps, isEmptyMonsterHouse);
SpawnEnemies(floorProps, isEmptyMonsterHouse);
ResolveInvalidSpawns(); // Make sure multiple flags aren't set for one tile
if (gDungeon->playerSpawn.x != -1 && gDungeon->playerSpawn.y != -1) {
// This is for normal fixed rooms, we don't need to validate the stairs in this scenario
// Since it's fixed already
if (GetFloorType() == FLOOR_TYPE_FIXED)
break;
if (gDungeon->stairsSpawn.x != -1 && gDungeon->stairsSpawn.y != -1 && StairsAlwaysReachable(gDungeon->stairsSpawn.x, gDungeon->stairsSpawn.y, FALSE))
break; // We can reach the stairs, we're good!
}
// Something went bad with spawns, we'll need to retry on a new generation
}
// If we fail with spawns (or otherwise) 10 times, opt for a One-Room Monster House generation
if (spawnAttempts == 10) {
sKecleonShopMiddlePos.x = -1;
sKecleonShopMiddlePos.y = -1;
ResetFloor();
GenerateOneRoomMonsterHouseFloor();
gDungeon->forceMonsterHouse = TRUE;
FinalizeJunctions();
SpawnNonEnemies(floorProps, FALSE);
SpawnEnemies(floorProps, FALSE);
ResolveInvalidSpawns();
// We don't care about validating because this is our bailout, so we're done!
}
if (sKecleonShopMiddlePos.x >= 0 && sKecleonShopMiddlePos.y >= 0) {
sub_806C330(sKecleonShopMiddlePos.x, sKecleonShopMiddlePos.y, MONSTER_KECLEON, 0);
}
if (sKecleonShopPosition.minX >= 0) {
sub_8051654(floorProps);
gDungeon->unk3A0A = 1;
}
else {
gDungeon->unk3A0A = 0;
}
sub_804B534(0, 0, DUNGEON_MAX_SIZE_X, DUNGEON_MAX_SIZE_Y);
if (gUnknown_202F1A8) {
sub_804FC74();
}
CloseFile(gDungeon->unk13568);
}
static 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 = GetTileMut(x, y);
tile->terrainFlags &= ~(TERRAIN_TYPE_CORNER_CUTTABLE);
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->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
}
}
}
}
}
/*
* 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
*/
static void GenerateStandardFloor(s32 gridSizeX, s32 gridSizeY, FloorProperties *floorProps)
{
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, floorProps->roomDensity);
CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, floorProps->roomFlags);
AssignRandomGridCellConnections(grid, gridSizeX, gridSizeY, floorProps);
CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, FALSE);
EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY);
GenerateMazeRoom(grid, gridSizeX, gridSizeY, floorProps->mazeRoomChance);
GenerateKecleonShop(grid, gridSizeX, gridSizeY, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
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.
static void GenerateOuterRingFloor(FloorProperties *floorProps)
{
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(GetTileMut(curX, curY));
GetTileMut(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(GetTileMut(startX, startY));
GetTileMut(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, floorProps);
CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, FALSE);
EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY);
GenerateKecleonShop(grid, gridSizeX, gridSizeY, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
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.
*/
static void GenerateCrossroadsFloor(FloorProperties *floorProps)
{
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(GetTileMut(curX, curY));
GetTileMut(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(GetTileMut(startX, startY));
GetTileMut(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, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
GenerateRoomImperfections(grid, gridSizeX, gridSizeY);
}
// GenerateLineFloor - Generates a floor layout with 5 grid cells in a horizontal line.
static void GenerateLineFloor(FloorProperties *floorProps)
{
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, floorProps->roomDensity);
CreateRoomsAndAnchors(grid, gridSizeX, gridSizeY, listX, listY, floorProps->roomFlags);
AssignRandomGridCellConnections(grid, gridSizeX, gridSizeY, floorProps);
CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, disableRoomMerging);
EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY);
GenerateKecleonShop(grid, gridSizeX, gridSizeY, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
GenerateRoomImperfections(grid, gridSizeX, gridSizeY);
}
// GenerateCrossFloor - Generates a floor layout with 5 rooms arranged in a "plus" or "cross" configuration.
static void GenerateCrossFloor(FloorProperties *floorProps)
{
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, floorProps->roomFlags);
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, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
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
static void GenerateBeetleFloor(FloorProperties *floorProps)
{
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, floorProps->roomFlags);
// 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, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
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 = GetTileMut(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.
static void GenerateOuterRoomsFloor(s32 gridSizeX_, s32 gridSizeY_, FloorProperties *floorProps)
{
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, floorProps->roomFlags);
// 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, floorProps->mazeRoomChance);
GenerateKecleonShop(grid, gridSizeX, gridSizeY, sKecleonShopChance);
GenerateMonsterHouse(grid, gridSizeX, gridSizeY, sMonsterHouseChance);
GenerateExtraHallways(grid, gridSizeX, gridSizeY, floorProps->numExtraHallways);
GenerateRoomImperfections(grid, gridSizeX, gridSizeY);
GenerateSecondaryStructures(grid, gridSizeX, gridSizeY);
}
static bool8 ProcessFixedRoom(s32 fixedRoomNumber, FloorProperties *floorProps)
{
s32 fixedRoomSizeX = ((struct FixedRoomsData **)(gDungeon->unk13568->data))[fixedRoomNumber]->x;
s32 fixedRoomSizeY = ((struct FixedRoomsData **)(gDungeon->unk13568->data))[fixedRoomNumber]->y;
s32 gridSizeX, gridSizeY;
if (fixedRoomSizeX == 0 || fixedRoomSizeY == 0) {
GenerateOneRoomMonsterHouseFloor();
return FALSE;
}
else if (fixedRoomNumber < FIRST_NON_FLOORWIDE_FIXED_ROOM) {
sub_8051288(fixedRoomNumber);
return TRUE;
}
else {
gridSizeX = DUNGEON_MAX_SIZE_X / (fixedRoomSizeX + 4);
if (gridSizeX <= 1)
gridSizeX = 1;
gridSizeY = DUNGEON_MAX_SIZE_Y / (fixedRoomSizeY + 4);
if (gridSizeY <= 1)
gridSizeY = 1;
sub_804C790(gridSizeX, gridSizeY, fixedRoomSizeX, fixedRoomSizeY, fixedRoomNumber, floorProps);
return FALSE;
}
}
static void sub_804C790(s32 gridSizeX, s32 gridSizeY, s32 fixedRoomSizeX, s32 fixedRoomSizeY, s32 fixedRoomNumber, FloorProperties *floorProps)
{
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, floorProps->roomDensity);
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;
}
CreateRoomsAndAnchorsForFixedFloor(grid, gridSizeX, gridSizeY, listX, listY, r10, fixedRoomSizeX, fixedRoomSizeY);
if (gridSizeX != 1 || gridSizeY != 1) {
AssignGridCellConnections(grid, gridSizeX, gridSizeY, cursorX, cursorY, floorProps);
CreateGridCellConnections(grid, gridSizeX, gridSizeY, listX, listY, TRUE);
EnsureConnectedGrid(grid, gridSizeX, gridSizeY, listX, listY);
}
sub_8051438(&grid[cursorX][cursorY], fixedRoomNumber);
}
/*
* 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.
*/
static 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(GetTileMut(x, y));
GetTileMut(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.
static 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(GetTileMut(currX, currY));
GetTileMut(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.
*/
static 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 (TRUE) {
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 (TRUE) {
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)->terrainFlags & 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(GetTileMut(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 && sFloorSize == FLOOR_SIZE_SMALL && direction == DIRECTION_EAST) break;
if (currX >= 48 && sFloorSize == FLOOR_SIZE_MEDIUM && 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
static 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.
*/
static 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 (sFloorSize == 1 && x >= gridSizeX / 2) {
grid[x][y].isInvalid = TRUE;
}
else if (sFloorSize == 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
static 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;
sNumRooms = 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 (sNumRooms >= 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;
sNumRooms++;
// 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 (sNumRooms >= 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;
}
sSecondSpawn = FALSE;
}
/*
* 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.
*/
static 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(GetTileMut(roomX, roomY));
GetTileMut(roomX, roomY)->room = roomNumber;
}
}
flagImperfect = TRUE;
// Randomly flag the room for a secondary structure
flagSecondary = DungeonRandInt(100) < GENERATION_CONSTANT_SECONDARY_STRUCTURE_FLAG_CHANCE;
if (sSecondaryStructuresBudget == 0) {
flagSecondary = FALSE;
}
// Flag for imperfections if needed
if (!(roomFlags & ROOM_FLAG_ALLOW_IMPERFECTIONS)) {
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(GetTileMut(pt_x, pt_y));
// Set the room index to 0xFE for anchor
GetTileMut(pt_x, pt_y)->room = ROOM_0xFE;
}
}
}
}
/*
* GenerateSecondaryStructures - Attempt to generate secondary structures in flagged rooms.
*
* For a valid flagged room with no extra features, one of the following will attempt to generate:
* 0. No Secondary Structure
* 1. A maze (made of water/lava walls), or a "plus" sign fallback, or a single dot in the center fallback
* 2. Checkerboard pattern of water/lava
* 3. A central pool in the room made of water/lava
* 4. A central island with items and a warp tile, surrounded by water/lava
* 5. A horizontal or vertical line of water/lava splitting the room in two.
*
* If a room doesn't meet the conditions for the secondary structure chosen, it will be left unchanged.
*/
static 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, FloorProperties *floorProps)
{
s32 cursorX = DungeonRandInt(gridSizeX);
s32 cursorY = DungeonRandInt(gridSizeY);
AssignGridCellConnections(grid, gridSizeX, gridSizeY, cursorX, cursorY, floorProps);
}
/*
* 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.
*
*/
static void AssignGridCellConnections(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 cursorX, s32 cursorY, FloorProperties *floorProps)
{
s32 i;
s32 x = cursorX;
s32 y = cursorY;
s32 floorConnectivity = floorProps->floorConnectivity;
// 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 (TRUE) {
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 (floorProps->allowDeadEnds)
return;
// No dead ends, add some extra connections!
while (TRUE) {
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.
*
*/
static 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 = GetTileMut(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 = GetTileMut(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 = GetTileMut(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 = GetTileMut(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;
}
}
}
}
}
/*
* Used in GenerateRoomImperfections, table for determining
* which directions we expect to find open or closed (wall/secondary) tiles in
*
* Ex. If generating from the Top-Left corner, we should only expect to find
* open tiles below us or to our right, otherwise the room will look very strange.
*/
static const bool8 sCornerCardinalNeighborExpectOpen[][NUM_DIRECTIONS] = {
// Top-Left Corner
[0] = {
[DIRECTION_SOUTH] = TRUE,
[DIRECTION_SOUTHEAST] = FALSE,
[DIRECTION_EAST] = TRUE,
[DIRECTION_NORTHEAST] = FALSE,
[DIRECTION_NORTH] = FALSE,
[DIRECTION_NORTHWEST] = FALSE,
[DIRECTION_WEST] = FALSE,
[DIRECTION_SOUTHWEST] = FALSE,
},
// Top-Right Corner
[1] = {
[DIRECTION_SOUTH] = TRUE,
[DIRECTION_SOUTHEAST] = FALSE,
[DIRECTION_EAST] = FALSE,
[DIRECTION_NORTHEAST] = FALSE,
[DIRECTION_NORTH] = FALSE,
[DIRECTION_NORTHWEST] = FALSE,
[DIRECTION_WEST] = TRUE,
[DIRECTION_SOUTHWEST] = FALSE,
},
// Bottom-Right Corner
[2] = {
[DIRECTION_SOUTH] = FALSE,
[DIRECTION_SOUTHEAST] = FALSE,
[DIRECTION_EAST] = FALSE,
[DIRECTION_NORTHEAST] = FALSE,
[DIRECTION_NORTH] = TRUE,
[DIRECTION_NORTHWEST] = FALSE,
[DIRECTION_WEST] = TRUE,
[DIRECTION_SOUTHWEST] = FALSE,
},
// Bottom-Left Corner
[3] = {
[DIRECTION_SOUTH] = FALSE,
[DIRECTION_SOUTHEAST] = FALSE,
[DIRECTION_EAST] = TRUE,
[DIRECTION_NORTHEAST] = FALSE,
[DIRECTION_NORTH] = TRUE,
[DIRECTION_NORTHWEST] = FALSE,
[DIRECTION_WEST] = FALSE,
[DIRECTION_SOUTHWEST] = FALSE,
},
};
/*
* 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.
*/
static 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)
const 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 (sCornerCardinalNeighborExpectOpen[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(GetTileMut(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
*/
static 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(GetTileMut(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(GetTileMut(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(GetTileMut(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(GetTileMut(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(GetTileMut(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(GetTileMut(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.
*/
static 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 = GetTileMut(grid[x][y].start.x, grid[x][y].start.y);
SetTerrainWall(tile);
// Also remove any spawn flags
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_STAIRS);
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_ITEM);
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_TRAP);
}
}
}
// 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 = GetTileMut(curX, curY);
// Set it to wall terrain
SetTerrainWall(tile);
// Remove any spawn flags
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_ITEM);
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_STAIRS);
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_TRAP);
// 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.
*/
static void SetTerrainObstacleChecked(Tile *tile, bool8 useSecondaryTerrain, u8 roomIndex)
{
SetTerrainWall(tile);
if (useSecondaryTerrain && tile->room == roomIndex) {
SetTerrainSecondary(tile);
}
}
/*
* FinalizeJunctions - Finalizes junction tiles by setting the junction flag and verifying the tiles are open terrain
*
* Due to the nature of how this function iterates left-to-right / top-to-bottom, by identifying junctions as any
* open, non-hallway tile (roomIndex !== 0xFF) adjacent to an open, hallway tile (roomIndex == 0xFF), the function
* runs into issues handling hallway anchors (roomIndex == 0xFE). The room index of hallway anchors is set to 0xFF using
* the same loop, which means a hallway anchor may or may not be considered a junction depending on how the connected
* hallways are oriented.
*
* For example, in the configuration below, the "o" tile would be marked as a junction because the neighboring hallway tile
* to its left comes earlier in iteration, while "o" still has the room index 0xFE, with the algorithm mistaking it for a
* room tile.
*
* X X X X X
* - - - o X
* X X X | X
* X X X | X
*
* Alternatively, in the configuration below, the "o" tile would not be marked as a junction because it comes earlier in
* iteration than any of its neighboring hallway tiles, so its room index is set to 0xFF before it can be marked as a junction.
* This configuration is actually the only one where a hallway anchor will not be marked as a junction.
*
* X X X X X
* X o - - -
* X | X X X
* X | X X X
*/
static void FinalizeJunctions(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;
// Not in a room
if (GetTile(x, y)->room == CORRIDOR_ROOM) {
// Tile to the left is in a room (or anchor), mark junction
if (x > 0) {
Tile *tile = GetTileMut(x - 1, y);
if (tile->room != CORRIDOR_ROOM) {
tile->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
// If there's any water/lava on the junction tile, remove it
if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) {
SetTerrainType(tile, TERRAIN_TYPE_NORMAL);
}
}
}
// Tile above is in a room
if (y > 0) {
Tile *tile = GetTileMut(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->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
tile->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) {
SetTerrainType(tile, TERRAIN_TYPE_NORMAL);
}
}
}
// Tile below is in a room
if (y < DUNGEON_MAX_SIZE_Y - 1) {
Tile *tile = GetTileMut(x, y + 1);
if (tile->room != CORRIDOR_ROOM) {
tile->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
tile->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) {
SetTerrainType(tile, TERRAIN_TYPE_NORMAL);
}
}
}
//Tile to the right is in a room
if (x < DUNGEON_MAX_SIZE_X - 1) {
Tile *tile = GetTileMut(x + 1, y);
if (tile->room != CORRIDOR_ROOM) {
tile->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
tile->terrainFlags |= TERRAIN_TYPE_NATURAL_JUNCTION;
if (GetTerrainType(tile) == TERRAIN_TYPE_SECONDARY) {
SetTerrainType(tile, TERRAIN_TYPE_NORMAL);
}
}
}
}
// Hallway Anchor
else if (GetTile(x, y)->room == ROOM_0xFE) {
GetTileMut(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)->terrainFlags & 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.
*/
static 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;
sKecleonShopMiddlePos.x = -1;
sKecleonShopMiddlePos.y = -1;
if (sHasMonsterHouse || GetFloorType() == FLOOR_TYPE_RESCUE || 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
sHasKecleonShop = TRUE;
grid[x][y].isKecleonShop = TRUE;
// Make the shop span the whole room
sKecleonShopPosition.minX = grid[x][y].start.x + 1;
sKecleonShopPosition.maxX = grid[x][y].end.x - 1;
sKecleonShopPosition.minY = grid[x][y].start.y + 1;
sKecleonShopPosition.maxY = grid[x][y].end.y - 1;
if (sKecleonShopPosition.maxY - sKecleonShopPosition.minY < 3) {
// This should never happen?
sKecleonShopPosition.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 = sKecleonShopPosition.minX; curX < sKecleonShopPosition.maxX; curX++) {
for (curY = sKecleonShopPosition.minY; curY < sKecleonShopPosition.maxY; curY++) {
Tile *tile = GetTileMut(curX, curY);
tile->terrainFlags |= TERRAIN_TYPE_SHOP;
// Restrict monsters and stairs from spawning here
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_MONSTER);
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_STAIRS);
// 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++) {
GetTileMut(curX, curY)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_SPECIAL_TILE;
}
}
curX = (sKecleonShopPosition.minX + sKecleonShopPosition.maxX) / 2;
curY = (sKecleonShopPosition.minY + sKecleonShopPosition.maxY) / 2;
sKecleonShopMiddlePos.x = curX;
sKecleonShopMiddlePos.y = curY;
return;
}
}
}
/*
* GenerateMonsterHouse - Possibly generate a monster house on the floor.
*
* A Monster House will be generated with a probability determined by the Monster House spawn chance, and only
* if the floor supports one (no kecleon shop, no non-MH outlaw missions, no special floor types)
*
* A Monster House will be generated in a random room that's valid, connected, not merged, not a maze room, and
* is not a few unknown conditions.
*/
static void GenerateMonsterHouse(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance)
{
// To spawn a monster house on this floor:
// - It must meet the probability chance
// - not have a kecleon shop
// - Be an outlaw monster house floor or not be a special destination floor with a target
// - Be a Normal Floor Type
s32 i;
bool8 values[256];
s32 x, y;
s32 numValid;
Dungeon *dungeon = gDungeon;
if (chance == 0 || chance <= DungeonRandInt(100))
return;
if (sHasKecleonShop)
return;
if (gDungeon->unk644.unk44 != 0)
return;
if (GetFloorType() != FLOOR_TYPE_NORMAL)
return;
numValid = 0;
for (x = 0; x < gridSizeX; x++) {
for (y = 0; y < gridSizeY; y++) {
// A grid cell can have a monster house if it:
// - is valid
// - is not a merged room
// - is connected
// - is a room
// - is not a maze
// - and some other unknown condition
if (grid[x][y].isInvalid)
continue;
if (grid[x][y].hasBeenMerged)
continue;
if (!grid[x][y].isConnected)
continue;
if (!grid[x][y].isRoom)
continue;
if (grid[x][y].isKecleonShop)
continue;
if (grid[x][y].unk15)
continue;
if (grid[x][y].isMazeRoom)
continue;
if (grid[x][y].hasSecondaryStructure)
continue;
numValid++;
}
}
if (numValid == 0)
return;
// Have a single 1, the rest as 0's
for (i = 0; i < 256; i++) {
values[i] = FALSE;
}
values[0] = TRUE;
// Shuffle values
for (i = 0; i < 64; i++) {
s32 temp;
s32 a = DungeonRandInt(numValid);
s32 b = DungeonRandInt(numValid);
SWAP(values[a], values[b], temp);
}
// Counter
i = 0;
for (x = 0; x < gridSizeX; x++) {
for (y = 0; y < gridSizeY; y++) {
if (grid[x][y].isInvalid)
continue;
if (grid[x][y].hasBeenMerged)
continue;
if (!grid[x][y].isConnected)
continue;
if (!grid[x][y].isRoom)
continue;
if (grid[x][y].isKecleonShop)
continue;
if (grid[x][y].unk15)
continue;
if (grid[x][y].isMazeRoom)
continue;
if (grid[x][y].hasSecondaryStructure)
continue;
if (values[i]) {
s32 curX, curY;
// The selected room can support a monster house
// Generate one!
sHasMonsterHouse = TRUE;
grid[x][y].isMonsterHouse = TRUE;
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++) {
GetTileMut(curX, curY)->terrainFlags |= TERRAIN_TYPE_IN_MONSTER_HOUSE;
dungeon->monsterHouseRoom = GetTile(curX, curY)->room;
}
}
return;
}
i++;
}
}
}
/*
* GenerateMazeRoom - Determines and calls for whether to generate a maze room on the floor.
*
* A maze room can be generated by the probability specified in the maze_chance parameter, usually in vanilla
* this parameter is set to either 0 or 1, giving a 0-1% chance for trying to spawn a maze room.
*
* Then, a strange check occurs for whether the floor_generation_attempts is < 0, which will always fail, resulting
* in no maze rooms ever being generated under normal conditions.
*
* Candidate maze rooms have to be valid, connected, have odd dimensions, and not have any other features.
* If any candidates are found, the game will select one of these rooms and call GenerateMaze.
*/
static void GenerateMazeRoom(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 chance)
{
bool8 values[256];
s32 i;
s32 x, y;
s32 numValid;
Dungeon *dungeon = gDungeon;
if (chance == 0) // No chance for maze, stop
return;
if (chance <= DungeonRandInt(100)) // Need to roll the probability chance (which is usually 0-1%...)
return;
if (dungeon->unk3A16 >= 0) // This bizarre check prevents maze rooms from ever being created
return;
// Count the number of rooms that can be mazified:
// - Valid
// - Not a Merged Room
// - Connected
// - Not a Monster House
// - Both Dimensions are Odd
numValid = 0;
for (x = 0; x < gridSizeX; x++) {
for (y = 0; y < gridSizeY; y++) {
if (grid[x][y].isInvalid)
continue;
if (grid[x][y].hasBeenMerged)
continue;
if (!grid[x][y].isConnected)
continue;
if (!grid[x][y].isRoom)
continue;
if (grid[x][y].isKecleonShop)
continue;
if (grid[x][y].unk15)
continue;
if (grid[x][y].isMonsterHouse)
continue;
if (grid[x][y].hasSecondaryStructure)
continue;
if ((grid[x][y].end.x - grid[x][y].start.x) % 2 != 0 && (grid[x][y].end.y - grid[x][y].start.y) % 2 != 0) {
numValid++;
}
}
}
if (numValid == 0)
return;
// Have a single 1, the rest as 0's
for (i = 0; i < 256; i++) {
values[i] = FALSE;
}
values[0] = TRUE;
// Shuffle values
for (i = 0; i < 64; i++) {
s32 temp;
s32 a = DungeonRandInt(numValid);
s32 b = DungeonRandInt(numValid);
SWAP(values[a], values[b], temp);
}
// Counter
i = 0;
for (x = 0; x < gridSizeX; x++) {
for (y = 0; y < gridSizeY; y++) {
if (grid[x][y].isInvalid)
continue;
if (grid[x][y].hasBeenMerged)
continue;
if (!grid[x][y].isConnected)
continue;
if (!grid[x][y].isRoom)
continue;
if (grid[x][y].isKecleonShop)
continue;
if (grid[x][y].unk15)
continue;
if (grid[x][y].isMonsterHouse)
continue;
if (grid[x][y].hasSecondaryStructure)
continue;
if ((grid[x][y].end.x - grid[x][y].start.x) % 2 != 0 && (grid[x][y].end.y - grid[x][y].start.y) % 2 != 0) {
// This room can be mazified
if (values[i]) {
GenerateMaze(&grid[x][y], FALSE);
return;
}
i++;
}
}
}
}
/*
* GenerateMaze - Generate a maze room within the given grid cell.
*
* A "maze" is generated using a series of random walks whiich place obstacle terrain (walls / secondary terrain)
* in a maze-like arrangement. "Maze lines" (see: GenerateMazeLine) are generated using every other tile around the
* room's border, and every other interior tile as a starting point, ensuring there are stripes of walkable open terrain
* surrounded by striples of obstacles (maze walls).
*
*/
static void GenerateMaze(struct GridCell *gridCell, bool8 useSecondaryTerrain)
{
s32 curX, curY;
u8 roomIndex;
sHasMaze = TRUE;
gridCell->isMazeRoom = TRUE;
roomIndex = GetTile(gridCell->start.x, gridCell->start.y)->room;
// Random walks from upper border
for (curX = gridCell->start.x + 1; curX < gridCell->end.x - 1; curX += 2) {
if (GetTerrainType(GetTile(curX, gridCell->start.y - 1)) != TERRAIN_TYPE_NORMAL) {
GenerateMazeLine(curX,
gridCell->start.y - 1,
gridCell->start.x,
gridCell->start.y,
gridCell->end.x,
gridCell->end.y,
useSecondaryTerrain,
roomIndex
);
}
}
// Random walks from right border
for (curY = gridCell->start.y + 1; curY < gridCell->end.y - 1; curY += 2) {
if (GetTerrainType(GetTile(gridCell->end.x, curY)) != TERRAIN_TYPE_NORMAL) {
GenerateMazeLine(gridCell->end.x, curY, gridCell->start.x, gridCell->start.y, gridCell->end.x, gridCell->end.y, useSecondaryTerrain, roomIndex);
}
}
// Random walks from lower border
for (curX = gridCell->start.x + 1; curX < gridCell->end.x - 1; curX += 2) {
if (GetTerrainType(GetTile(curX, gridCell->end.y)) != TERRAIN_TYPE_NORMAL) {
GenerateMazeLine(curX, gridCell->end.y, gridCell->start.x, gridCell->start.y, gridCell->end.x, gridCell->end.y, useSecondaryTerrain, roomIndex);
}
}
// Random walks from left border
for (curY = gridCell->start.y + 1; curY < gridCell->end.y - 1; curY += 2) {
if (GetTerrainType(GetTile(gridCell->start.x - 1, curY)) != TERRAIN_TYPE_NORMAL) {
GenerateMazeLine(gridCell->start.x - 1,
curY,
gridCell->start.x,
gridCell->start.y,
gridCell->end.x,
gridCell->end.y,
useSecondaryTerrain,
roomIndex);
}
}
// Fill in all the inner tiles with a stride of 2
for (curX = gridCell->start.x + 3; curX < gridCell->end.x - 3; curX += 2) {
for (curY = gridCell->start.y + 3; curY < gridCell->end.y - 3; curY += 2) {
if (GetTerrainType(GetTile(curX, curY)) == TERRAIN_TYPE_NORMAL) {
if (useSecondaryTerrain) {
SetTerrainSecondary(GetTileMut(curX - 1, curY));
}
else {
SetTerrainWall(GetTileMut(curX - 1, curY));
}
// More random walks
GenerateMazeLine(curX, curY, gridCell->start.x, gridCell->start.y, gridCell->end.x, gridCell->end.y, useSecondaryTerrain, roomIndex);
}
}
}
}
/*
* GenerateMazeLine - Generates a "maze line" from the given start point, within the given bounds.
*
* A "maze line" is a random walk starting from (x0, y0). The random walk moves in strides of 2
* in a random direction, placing down obstacles as it goes.
*
* The walk will terminate when the random walk has no available open tiles it can walk to.
*
* [ ^ ] [ ] [ ] [ o ] [ - ] [ > ]
* [ | ] [ ] [ ] => [ W ] [ ] [ ] => etc.
* [ o ] [ ] [ W ] [ W ] [ ] [ W ]
*
* First, an obstacle is placed at the given DungeonPos (see: SetTerrainObstacleChecked)
*
* Then, a random direction is selected, searching for an open tile distance 2 away from (x0, y0).
* Each direction is attempted rotating counter-clockwise until an open tile is found or all directions are exhausted.
*
* If an open tile is found, an obstacle is placed between the two tiles, and (x0, y0) moves to the new open tile.
* This process continues until no valid open tile can be found.
*/
static void GenerateMazeLine(s32 x0, s32 y0, s32 xMin, s32 yMin, s32 xMax, s32 yMax, bool8 useSecondaryTerrain, u32 roomIndex)
{
while (TRUE) {
s32 direction = DungeonRandInt(NUM_CARDINAL_DIRECTIONS);
s32 i = 0;
SetTerrainObstacleChecked(GetTileMut(x0, y0), useSecondaryTerrain, roomIndex);
while (TRUE) {
s32 offsetX, offsetY;
s32 posX, posY;
// Offset from our current DungeonPos to look 2 tiles in a given direction
switch (direction & CARDINAL_DIRECTION_MASK) {
case CARDINAL_DIR_RIGHT:
offsetX = 2;
offsetY = 0;
break;
case CARDINAL_DIR_UP:
offsetX = 0;
offsetY = -2;
break;
case CARDINAL_DIR_LEFT:
offsetX = -2;
offsetY = 0;
break;
default:
case CARDINAL_DIR_DOWN:
offsetX = 0;
offsetY = 2;
break;
}
posX = x0 + offsetX;
// Check that this DungeonPos is in-bounds
if (xMin <= posX && xMax > posX) {
posY = y0 + offsetY;
if (yMin <= posY && yMax > posY) {
// Check that this tile is open ground
if (GetTerrainType(GetTile(posX, posY)) == TERRAIN_TYPE_NORMAL) {
// We found open ground, we're done!
break;
}
}
}
// We didn't find any, try a different direction
direction++;
if (++i >= 4)
return;
}
// If we found some open terrain, set an obstacle for the terrain in between those two,
// then move to the open terrain we found.
switch (direction & CARDINAL_DIRECTION_MASK) {
case CARDINAL_DIR_RIGHT:
SetTerrainObstacleChecked(GetTileMut(x0 + 1, y0), useSecondaryTerrain, roomIndex);
x0 += 2;
break;
case CARDINAL_DIR_UP:
SetTerrainObstacleChecked(GetTileMut(x0, y0 - 1), useSecondaryTerrain, roomIndex);
y0 -= 2;
break;
case CARDINAL_DIR_LEFT:
SetTerrainObstacleChecked(GetTileMut(x0 - 1, y0), useSecondaryTerrain, roomIndex);
x0 -= 2;
break;
case CARDINAL_DIR_DOWN:
SetTerrainObstacleChecked(GetTileMut(x0, y0 + 1), useSecondaryTerrain, roomIndex);
y0 += 2;
break;
}
}
}
static void SetTerrainSecondaryWithFlag(Tile *tile, u32 additionalFlag)
{
SetTerrainSecondary(tile);
tile->terrainFlags |= additionalFlag;
}
/*
* SetSpawnFlag5 - Sets unknown spawn flag 0x5 on all tiles in a room
*/
static void SetSpawnFlag5(struct GridCell *gridCell)
{
s32 x, y;
for (x = gridCell->start.x; x < gridCell->end.x; x++) {
for (y = gridCell->start.y; y < gridCell->end.y; y++) {
GetTileMut(x, y)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_UNK5;
}
}
}
/*
* IsNextToHallway - Checks if a tile DungeonPos is either in a hallway or next to one.
*/
static bool8 IsNextToHallway(s32 x, s32 y)
{
s32 offsetX, offsetY;
s32 posX, posY;
for (offsetX = -1; offsetX <= 1; offsetX++) {
posX = x + offsetX;
if (posX < 0)
continue;
if (posX >= DUNGEON_MAX_SIZE_X)
break;
for (offsetY = -1; offsetY <= 1; offsetY++) {
posY = y + offsetY;
if (posY < 0)
continue;
if (posY >= DUNGEON_MAX_SIZE_Y)
break;
if (offsetX != 0 && offsetY != 0)
continue;
if (GetTerrainType(GetTile(posX, posY)) == TERRAIN_TYPE_NORMAL && GetTile(posX, posY)->room == CORRIDOR_ROOM)
return TRUE;
}
}
return FALSE;
}
// See GenerateSecondaryStructures for more information.
static void GenerateSecondaryStructure(struct GridCell *gridCell)
{
switch (DungeonRandInt(6)) {
// Generate a "split room" with two sides separated by a line of water/lava
case SECONDARY_STRUCTURE_DIVIDER:
if (sSecondaryStructuresBudget != 0) {
s32 i;
sSecondaryStructuresBudget--;
SetSpawnFlag5(gridCell);
if (DungeonRandInt(2) != 0) {
// Split the room with a vertical line
s32 curX, curY;
bool8 invalid = FALSE;
s32 middleX = (gridCell->start.x + gridCell->end.x) / 2;
for (i = gridCell->start.y; i < gridCell->end.y; i++) {
if (IsNextToHallway(middleX, i)) {
invalid = TRUE;
break;
}
}
if (!invalid) {
for (i = gridCell->start.y; i < gridCell->end.y; i++) {
SetTerrainSecondaryWithFlag(GetTileMut(middleX, i), 0);
}
for (curX = gridCell->start.x; curX < middleX; curX++) {
for (curY = gridCell->start.y; curY < gridCell->end.y; curY++) {
GetTileMut(curX, curY)->terrainFlags |= TERRAIN_TYPE_UNK_7;
}
}
gridCell->hasSecondaryStructure = TRUE;
}
}
else {
// Split the room with a horizontal line
s32 curX, curY;
bool8 invalid = FALSE;
s32 middleY = (gridCell->start.y + gridCell->end.y) / 2;
for (i = gridCell->start.x; i < gridCell->end.x; i++) {
if (IsNextToHallway(i, middleY)) {
invalid = TRUE;
break;
}
}
if (!invalid) {
for (i = gridCell->start.x; i < gridCell->end.x; i++) {
SetTerrainSecondaryWithFlag(GetTileMut(i, middleY), 0);
}
for (curY = gridCell->start.y; curY < middleY; curY++) {
for (curX = gridCell->start.x; curX < gridCell->end.x; curX++) {
GetTileMut(curX, curY)->terrainFlags |= TERRAIN_TYPE_UNK_7;
}
}
gridCell->hasSecondaryStructure = TRUE;
}
}
}
break;
case SECONDARY_STRUCTURE_ISLAND:
if ((gridCell->end.x - gridCell->start.x) >= 6 && (gridCell->end.y - gridCell->start.y) >= 6) {
s32 middleX = (gridCell->start.x + gridCell->end.x) / 2;
s32 middleY = (gridCell->start.y + gridCell->end.y) / 2;
// Both dimensions are at least 6. Generate an "island" with lava, items, and a Warp Tile at the center
if (sSecondaryStructuresBudget != 0) {
sSecondaryStructuresBudget--;
SetSpawnFlag5(gridCell);
// Water "Moat"
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 2, middleY - 2), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 1, middleY - 2), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX, middleY - 2), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX + 1, middleY - 2), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 2, middleY - 1), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 2, middleY), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 2, middleY + 1), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 2, middleY + 1), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 1, middleY + 1), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX, middleY + 1), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX + 1, middleY - 2), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX + 1, middleY - 1), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX + 1, middleY), TERRAIN_TYPE_CORNER_CUTTABLE);
SetTerrainSecondaryWithFlag(GetTileMut(middleX + 1, middleY + 1), TERRAIN_TYPE_CORNER_CUTTABLE);
// Trap
GetTileMut(middleX - 1, middleY - 1)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_TRAP;
// Warp Tile ?
GetTileMut(middleX - 1, middleY - 1)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_UNK6;
// Items
GetTileMut(middleX, middleY - 1)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
GetTileMut(middleX - 1, middleY)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
GetTileMut(middleX, middleY)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
GetTileMut(middleX - 1, middleY - 1)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_SPECIAL_TILE;
GetTileMut(middleX, middleY - 1)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_SPECIAL_TILE;
GetTileMut(middleX - 1, middleY)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_SPECIAL_TILE;
GetTileMut(middleX, middleY)->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_SPECIAL_TILE;
gridCell->hasSecondaryStructure = TRUE;
}
}
break;
case SECONDARY_STRUCTURE_POOL:
if ((gridCell->end.x - gridCell->start.x) >= 5 && (gridCell->end.y - gridCell->start.y) >= 5) {
s32 curX, curY;
// Both dimensions are at least 5, generate a "pool" of water/lava
s32 randX1 = DungeonRandRange(gridCell->start.x + 2, gridCell->end.x - 3);
s32 randY1 = DungeonRandRange(gridCell->start.y + 2, gridCell->end.y - 3);
s32 randX2 = DungeonRandRange(gridCell->start.x + 2, gridCell->end.x - 3);
s32 randY2 = DungeonRandRange(gridCell->start.y + 2, gridCell->end.y - 3);
if (sSecondaryStructuresBudget != 0) {
sSecondaryStructuresBudget--;
SetSpawnFlag5(gridCell);
if (randX1 > randX2) {
s32 temp;
SWAP(randX1, randX2, temp);
}
if (randY1 > randY2) {
s32 temp;
SWAP(randY1, randY2, temp);
}
for (curX = randX1; curX <= randX2; curX++) {
for (curY = randY1; curY <= randY2; curY++) {
SetTerrainSecondaryWithFlag(GetTileMut(curX, curY), 0);
}
}
gridCell->hasSecondaryStructure = TRUE;
}
}
break;
case SECONDARY_STRUCTURE_CHECKERBOARD:
if ((gridCell->end.x - gridCell->start.x) % 2 != 0 && (gridCell->end.y - gridCell->start.y) % 2 != 0 && sSecondaryStructuresBudget != 0) {
s32 i;
// Dimensions are odd, generate diagonal stripes/checkerboard of water/lava
sSecondaryStructuresBudget--;
SetSpawnFlag5(gridCell);
for (i = 0; i < 64; i++) {
s32 randX = DungeonRandInt(gridCell->end.x - gridCell->start.x);
s32 randY = DungeonRandInt(gridCell->end.y - gridCell->start.y);
if ((randX + randY) % 2 != 0) {
SetTerrainSecondaryWithFlag(GetTileMut(gridCell->start.x + randX, gridCell->start.y + randY), 0);
}
}
gridCell->hasSecondaryStructure = TRUE;
}
break;
case SECONDARY_STRUCTURE_MAZE_PLUS_DOT:
if (sSecondaryStructuresBudget != 0) {
sSecondaryStructuresBudget--;
// If the dimensions are odd, generate a maze room
if ((gridCell->end.x - gridCell->start.x) % 2 == 0 || (gridCell->end.y - gridCell->start.y) % 2 == 0) {
s32 middleX = (gridCell->start.x + gridCell->end.x) / 2;
s32 middleY = (gridCell->start.y + gridCell->end.y) / 2;
if ((gridCell->end.x - gridCell->start.x) >= 5 && (gridCell->end.y - gridCell->start.y) >= 5) {
// Both dimensions are at least 5, generate a water/lava cross in the center
SetTerrainSecondaryWithFlag(GetTileMut(middleX + 1, middleY), 0);
SetTerrainSecondaryWithFlag(GetTileMut(middleX, middleY + 1), 0);
SetTerrainSecondaryWithFlag(GetTileMut(middleX - 1, middleY), 0);
SetTerrainSecondaryWithFlag(GetTileMut(middleX, middleY - 1), 0);
}
// Generate a single water/lava spot in the center
SetTerrainSecondaryWithFlag(GetTileMut(middleX, middleY), 0);
}
else {
// Both dimensions are odd. Generate a maze room
SetSpawnFlag5(gridCell);
GenerateMaze(gridCell, TRUE);
}
gridCell->hasSecondaryStructure = TRUE;
}
break;
}
}
/*
* ResolveInvalidSpawns - Resolve any potentially invalid spawns on tiles.
*
* Obstacles can't spawn traps, impassable obstacles can't spawn items (you aren't able to reach them)
*
* A tile marked for the stairs must not have a trap there
*
* A tile marked for an item also must not have a trap there
*/
static void ResolveInvalidSpawns(void)
{
s32 x, y;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
Tile *tile = GetTileMut(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL) {
if (tile->terrainFlags & (TERRAIN_TYPE_UNBREAKABLE | TERRAIN_TYPE_IMPASSABLE_WALL)) {
// This tile is an impassable obstacle, make sure no items spawn here
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_ITEM);
}
// This tile is an obstacle, make sure no traps spawn here
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_TRAP);
}
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_STAIRS) {
// This tile has the stairs, make sure the stairs bit is set and
// make sure no traps spawn here
tile->terrainFlags |= TERRAIN_TYPE_STAIRS;
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_TRAP);
}
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_ITEM) {
//This tile is an item spawn, make sure no traps spawn here
tile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_TRAP);
}
}
}
}
// Converts Secondary terrain to both secondary and normal. Possibly to note that it's a water tile?
static void sub_804FC74(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_SECONDARY) {
SetTerrainType(GetTileMut(x,y), TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY);
}
}
}
}
/*
* EnsureImpassableTilesAreWalls - Force all tiles with the impassable flag to be set as walls.
*/
static void EnsureImpassableTilesAreWalls(void)
{
s32 x, y;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
if (GetTile(x, y)->terrainFlags & TERRAIN_TYPE_IMPASSABLE_WALL) {
SetTerrainWall(GetTileMut(x,y));
}
}
}
}
static void ResetTile(Tile *tile)
{
tile->terrainFlags = 0;
tile->spawnOrVisibilityFlags.spawn = 0;
tile->room = -1;
tile->unk8 = 0;
tile->walkableNeighborFlags[CROSSABLE_TERRAIN_REGULAR] = 0;
tile->walkableNeighborFlags[CROSSABLE_TERRAIN_LIQUID] = 0;
tile->walkableNeighborFlags[CROSSABLE_TERRAIN_CREVICE] = 0;
tile->walkableNeighborFlags[CROSSABLE_TERRAIN_WALL] = 0;
tile->unkE = 0;
tile->monster = NULL;
tile->object = NULL;
}
// PosIsOutOfBounds - Checks if a DungeonPos is out of bounds on the map.
static inline bool8 PosIsOutOfBounds(s32 x, s32 y)
{
if (x < 0)
return TRUE;
if (y < 0)
return TRUE;
if (DUNGEON_MAX_SIZE_X <= x)
return TRUE;
if (DUNGEON_MAX_SIZE_Y <= y)
return TRUE;
return FALSE;
}
static void ResetFloor(void)
{
s32 x, y;
// Reset Room Tiles
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
ResetTile(GetTileMut(x,y));
if ((PosIsOutOfBounds(x, y - 1)) ||
(PosIsOutOfBounds(x + 1, y - 1)) ||
(PosIsOutOfBounds(x + 1, y - 1)) ||
(PosIsOutOfBounds(x + 1, y + 1)) ||
(PosIsOutOfBounds(x, y + 1)) ||
(PosIsOutOfBounds(x - 1, y + 1)) ||
(PosIsOutOfBounds(x - 1, y)) ||
(PosIsOutOfBounds(x - 1, y - 1)))
{
GetTileMut(x,y)->terrainFlags |= TERRAIN_TYPE_IMPASSABLE_WALL;
}
}
}
// Reset Stairs DungeonPos
gDungeon->stairsSpawn.x = -1;
gDungeon->stairsSpawn.y = -1;
// Reset Fixed Room Tiles
for (x = 0; x < 8; x++) {
for (y = 0; y < 8; y++) {
ResetTile(&gDungeon->unkE27C[x][y]);
}
}
gDungeon->numItems = 0;
// Reset Traps
for (x = 0; x < DUNGEON_MAX_TRAPS; x++) {
gDungeon->traps[x]->type = 0;
}
}
/*
* ShuffleSpawnPositions - Randomly shuffle an array of spawn positions
*/
static void ShuffleSpawnPositions(struct PositionU8 *spawns, s32 count)
{
s32 i;
// Do twice as many swaps as there are items in the array
for (i = 0; i < count * 2; i++) {
struct PositionU8 temp;
s32 a = DungeonRandInt(count);
s32 b = DungeonRandInt(count);
SWAP(spawns[a], spawns[b], temp);
}
}
/*
* SpawnNonEnemies - Spawns all non-enemy entities: Stairs, Items, Traps, and the player.
*
* Most entities spawn randomly on a subset of the valid tiles for their type.
*
* These spawns are categorized into:
* - Stairs (and hidden stairs in later gens)
* - Normal Items
* - Buried Items
* - Monster House Items/Traps
* - Normal Traps
* - Player Spawn
*
* See below for specific conditions on each type of spawn.
*/
static void SpawnNonEnemies(FloorProperties *floorProps, bool8 isEmptyMonsterHouse)
{
struct PositionU8 validSpawns[DUNGEON_MAX_NUM_TILES];
s32 count;
s32 randIndex;
s32 i;
s32 x, y;
Dungeon *dungeon = gDungeon;
// Spawn Stairs
if (dungeon->stairsSpawn.x == -1 || dungeon->stairsSpawn.y == -1) {
count = 0;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// The stairs can spawn on tiles that are:
// - Open Terrain
// - In a room
// - Not in a Kecleon Shop
// - Not an enemy spawn
// - Not a special tile (flagged by kecleon shops, traps, and items)
// - Not a junction tile (next to a hallway)
// - Not a special tile that can't be broken by Absolute Mover
const Tile *tile = GetTile(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL)
continue;
if (tile->room == CORRIDOR_ROOM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_MONSTER)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_SPECIAL_TILE)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
if (count != 0) {
// Randomly select one of the valid tiles to spawn the stairs on
s32 stairsIndex = DungeonRandInt(count);
Tile *stairsTile = GetTileMut(validSpawns[stairsIndex].x, validSpawns[stairsIndex].y);
stairsTile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_STAIRS;
stairsTile->spawnOrVisibilityFlags.spawn &= ~(SPAWN_FLAG_ITEM);
sStairsRoomIndex = stairsTile->room;
dungeon->stairsSpawn.x = validSpawns[stairsIndex].x;
dungeon->stairsSpawn.y = validSpawns[stairsIndex].y;
// If we're spawning normal stairs and this is a rescue floor, make the stairs room a Monster House
if (GetFloorType() == FLOOR_TYPE_RESCUE) {
u32 stairsRoomIndex = stairsTile->room;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
Tile *tile = GetTileMut(x, y);
if (GetTerrainType(tile) == TERRAIN_TYPE_NORMAL && tile->room == stairsRoomIndex) {
tile->terrainFlags |= TERRAIN_TYPE_IN_MONSTER_HOUSE;
dungeon->monsterHouseRoom = tile->room;
}
}
}
}
}
}
// Spawn normal items
count = 0;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// Normal items can spawn on tiles that are:
// - Open Terrain
// - In a room
// - Not in a Kecleon Shop
// - Not in a Monster House
// - Not a junction tile (next to a hallway)
// - Not a special tile that can't be broken by Absolute Mover
const Tile *tile = GetTile(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL)
continue;
if (tile->room == CORRIDOR_ROOM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_IN_MONSTER_HOUSE)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
if (count != 0) {
s32 numItems = floorProps->itemDensity;
if (numItems != 0) {
// Add variation to the item count
numItems = DungeonRandRange(numItems - 2, numItems + 2);
if (numItems <= 0) {
numItems = 1;
}
}
if (numItems != 0) {
// Randomly select among the valid item spawn spots
ShuffleSpawnPositions(validSpawns, count);
randIndex = DungeonRandInt(count);
for (i = 0; i < numItems; i++) {
Tile *tile = GetTileMut(validSpawns[randIndex].x, validSpawns[randIndex].y);
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
randIndex++;
if (randIndex == count) {
// Wrap around to the start
randIndex = 0;
}
}
}
}
// Spawn Buried Items (in walls)
count = 0;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// Any wall tile is all buried items need
if (GetTerrainType(GetTile(x, y)) == TERRAIN_TYPE_WALL) {
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
}
if (count != 0) {
s32 numItems = floorProps->buriedItemDensity;
if (numItems != 0) {
// Add variation to the item count
numItems = DungeonRandRange(numItems - 2, numItems + 2);
}
if (numItems > 0) {
// Randomly select among the valid item spawn spots
ShuffleSpawnPositions(validSpawns, count);
randIndex = DungeonRandInt(count);
for (i = 0; i < numItems; i++) {
Tile *tile = GetTileMut(validSpawns[randIndex].x, validSpawns[randIndex].y);
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
randIndex++;
if (randIndex == count) {
// Wrap around to the start
randIndex = 0;
}
}
}
}
// Spawn items/traps in a non-empty Monster House
count = 0;
if (!isEmptyMonsterHouse) {
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// Monster House items/traps can spawn on tiles that are:
// - not in a kecleon shop (how would they be?)
// - in a Monster House
// - not a junction (near a hallway)
const Tile *tile = GetTile(x, y);
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (!(tile->terrainFlags & TERRAIN_TYPE_IN_MONSTER_HOUSE))
continue;
if (tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
}
if (count != 0) {
// Choose a subset of the available tiles to spawn stuff on
s32 numItemsTraps = DungeonRandRange((count / 2), (count * 8) / 10);
if (numItemsTraps < 6) {
numItemsTraps = 6;
}
// Cap item spawns at 7 (normally)
if (numItemsTraps >= gMonsterHouseMaxItemsTraps) {
numItemsTraps = gMonsterHouseMaxItemsTraps;
}
// Randomly select among the valid item spawn spots
ShuffleSpawnPositions(validSpawns, count);
randIndex = DungeonRandInt(count);
for (i = 0; i < numItemsTraps; i++) {
Tile *tile = GetTileMut(validSpawns[randIndex].x, validSpawns[randIndex].y);
// 50/50 chance of spawning an item or a trap
if (DungeonRandInt(2) != 0) {
// Spawn an item
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
}
else if (gDungeon->unk644.canChangeLeader) {
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_TRAP;
}
randIndex++;
if (randIndex == count) {
// Wrap around to the start
randIndex = 0;
}
}
}
// Spawn Normal Traps
count = 0;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// Normal traps can spawn on tiles that are:
// - Open Terrain
// - In a room
// - Not in a Kecleon Shop
// - Don't already have an item spawn
// - Not a junction tile (next to a hallway)
// - Not a special tile that can't be broken by Absolute Mover
const Tile *tile = GetTile(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL)
continue;
if (tile->room == CORRIDOR_ROOM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_ITEM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
if (count != 0) {
s32 numTraps = DungeonRandRange(floorProps->trapDensity / 2, floorProps->trapDensity);
if (numTraps > 0) {
s32 trapIndex;
if (numTraps >= 56) {
// Cap the number of traps at 56
numTraps = 56;
}
// Randomly select among the valid trap spawn spots
ShuffleSpawnPositions(validSpawns, count);
trapIndex = DungeonRandInt(count);
for (i = 0; i < numTraps; i++) {
Tile *tile = GetTileMut(validSpawns[trapIndex].x, validSpawns[trapIndex].y);
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_TRAP;
trapIndex++;
if (trapIndex == count) {
// Wrap around to the start
trapIndex = 0;
}
}
}
}
// Spawn the player
if (dungeon->playerSpawn.x == -1 || dungeon->playerSpawn.y == -1) {
count = 0;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// The player can spawn on tiles that are:
// - Open Terrain
// - In a room
// - Not in a Kecleon Shop
// - Not a junction tile (next to a hallway)
// - Not a special tile that can't be broken by Absolute Mover
// - Not an item, enemy, or trap spawn
const Tile *tile = GetTile(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL)
continue;
if (tile->room == CORRIDOR_ROOM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_ITEM)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_MONSTER)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_TRAP)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
if (count != 0) {
// Randomly select one of the valid tiles to spawn the player on
s32 spawnIndex = DungeonRandInt(count);
dungeon->playerSpawn.x = validSpawns[spawnIndex].x;
dungeon->playerSpawn.y = validSpawns[spawnIndex].y;
}
}
}
/*
* SpawnEnemies - Spawns all enemies, including those in forced monster houses
*/
static void SpawnEnemies(FloorProperties *floorProps, bool8 isEmptyMonsterHouse)
{
struct PositionU8 validSpawns[DUNGEON_MAX_NUM_TILES];
s32 count;
s32 randIndex;
s32 i;
s32 x, y;
Dungeon *dungeon = gDungeon;
s32 numEnemies, numMonsterHouseEnemies;
s32 enemyDensity = floorProps->enemyDensity;
// BUG: Game assumes floorProps->enemyDensity is a signed byte, but in reality it's unsigned.
// Attempting to use a negative density will instead produce a very large positive density up to 255.
// This only matters for unused dungeons, as Deoxys has its own logic despite Meteor Cave having an effective enemy density of 255.
if (enemyDensity > 0) {
// Positive means value with variance
numEnemies = DungeonRandRange(enemyDensity / 2, enemyDensity);
if (numEnemies < 1) {
numEnemies = 1;
}
}
else {
// Negative means exact value.
numEnemies = abs(enemyDensity);
}
count = 0;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// Enemies can spawn on tiles that are:
// - Open Terrain
// - In a room
// - Not in a Kecleon Shop
// - Don't have stairs, an item
// - Not a special tile that can't be broken by Absolute Mover
// - Not where the player spawns
const Tile *tile = GetTile(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL)
continue;
if (tile->room == CORRIDOR_ROOM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_ITEM)
continue;
if (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_STAIRS)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)
continue;
if (x == dungeon->playerSpawn.x && y == dungeon->playerSpawn.y)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
if (count != 0) {
// ?
if (gDungeon->unk644.unk44) {
numEnemies++;
}
if (numEnemies != 0) {
// Randomly select among the valid enemy spawn spots
ShuffleSpawnPositions(validSpawns, count);
randIndex = DungeonRandInt(count);
for (i = 0; i < numEnemies; i++) {
Tile *tile = GetTileMut(validSpawns[randIndex].x, validSpawns[randIndex].y);
// Spawn an enemy here
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_MONSTER;
randIndex++;
if (randIndex == count) {
// Wrap around to the start
randIndex = 0;
}
}
}
}
if (!dungeon->forceMonsterHouse) {
return;
}
// This floor was marked to force a monster house
// Place extra enemy spawns in the Monster House room
numMonsterHouseEnemies = gMonsterHouseMaxMons;
count = 0;
if (isEmptyMonsterHouse) {
// An "empty" monster house only spawns 3 enemies
numMonsterHouseEnemies = 3;
}
numMonsterHouseEnemies = (numMonsterHouseEnemies * 3) / 2;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
s32 y;
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
// Monster House enemies can spawn on tiles that are:
// - Open Terrain
// - In a room
// - Not in a Kecleon Shop
// - Not a special tile that can't be broken by Absolute Mover
// - Not where the player spawns
// - In the monster house room
const Tile *tile = GetTile(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_NORMAL)
continue;
if (tile->room == CORRIDOR_ROOM)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_SHOP)
continue;
if (tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)
continue;
if (!(tile->terrainFlags & TERRAIN_TYPE_IN_MONSTER_HOUSE))
continue;
if (x == dungeon->playerSpawn.x && y == dungeon->playerSpawn.y)
continue;
validSpawns[count].x = x;
validSpawns[count].y = y;
count++;
}
}
if (count != 0) {
numEnemies = DungeonRandRange((7 * count) / 10, (8 * count) / 10);
if (numEnemies == 0) {
numEnemies = 1;
}
if (numEnemies >= numMonsterHouseEnemies) {
// Don't spawn more enemies than the designated limit
numEnemies = numMonsterHouseEnemies;
}
// Randomly select among the valid enemy spawn spots
ShuffleSpawnPositions(validSpawns, count);
randIndex = DungeonRandInt(count);
for (i = 0; i < numEnemies; i++) {
Tile *tile = GetTileMut(validSpawns[randIndex].x, validSpawns[randIndex].y);
// Spawn an enemy here
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_MONSTER;
randIndex++;
if (randIndex == count) {
// Wrap around to the start
randIndex = 0;
}
}
}
}
/*
* SetSecondaryTerrainOnWall - Set a specific tile to have secondary terrain if the tile
* is a passable wall.
*/
void SetSecondaryTerrainOnWall(Tile *tile)
{
bool8 isWall = TRUE;
if (GetTerrainType(tile) != TERRAIN_TYPE_WALL)
isWall = FALSE;
if (tile->terrainFlags & TERRAIN_TYPE_IMPASSABLE_WALL)
isWall = FALSE;
if (isWall) {
SetTerrainSecondary(tile);
}
}
static const s32 sNumToGenTable[8] = {1, 1, 1, 2, 2, 2, 3, 3};
/*
* GenerateSecondaryTerrainFormations - Generates secondary terrain (water/lava) formations
*
* This generation includes rivers, lakes along the river path, and standalone lakes.
*
* The river flows from top-to-bottom or bottom-to-top, using a random walk ending when the walk
* goes out of bounds or finds existing secondary terrain. Because of this, rivers can end prematurely
* when a lake is generated.
*
* Lakes are a large collection of secondary terrain generated around a central point.
* Standalone lakes are generated based on floorProps->standaloneLakeDensity
*
* The formations will never cut into room tiles, but can pass through to the other side.
*/
// This function IS IMPOSSIBLE TO MATCH! Tried with 3 different compiler outputs and got nothing. Functionally it is the same, for the gba it's just a stack difference.
// https://decomp.me/scratch/9E4Uj - Red
// https://decomp.me/scratch/sA4YH - Blue
// https://decomp.me/scratch/SNyV8 - Sky
#ifdef NONMATCHING
static void GenerateSecondaryTerrainFormations(u32 flag, FloorProperties *floorProps)
{
s32 densityN;
s32 x, y;
// Stack
s32 numToGen;
s32 numTilesFill;
if (!(floorProps->roomFlags & flag))
return;
// Generate 1-3 "river+lake" formations
for (numToGen = sNumToGenTable[DungeonRandInt(ARRAY_COUNT(sNumToGenTable))]; numToGen != 0; numToGen--) {
s32 dirX, dirY;
bool8 upwards;
s32 stepsUntilLake;
s32 j;
s32 offsetX;
s32 offsetY;
// Randomly pick between starting from the bottom going up, or from the top going down
if (DungeonRandInt(100) < 50) {
upwards = TRUE;
y = DUNGEON_MAX_SIZE_Y - 1;
dirY = -1;
}
else {
upwards = FALSE;
y = 0;
dirY = 1;
}
stepsUntilLake = DungeonRandInt(50) + 10;
// Pick a random column in the interior to start the river on
x = DungeonRandRange(2, DUNGEON_MAX_SIZE_X - 2);
dirX = 0;
while (TRUE) {
// Fill in tiles in chunks of size 2-7 before changing the flow direction
numTilesFill = DungeonRandInt(6) + 2;
while (numTilesFill != 0) {
if (x >= 0 && x < DUNGEON_MAX_SIZE_X) {
if (GetTerrainType(GetTile(x, y)) == TERRAIN_TYPE_SECONDARY) {
goto LABEL;
}
if (!PosIsOutOfBounds(x, y)) {
// Fill in secondary terrain as we go
SetSecondaryTerrainOnWall(GetTileMut(x, y));
}
}
numTilesFill--;
// Move to the next tile
x += dirX;
y += dirY;
// Vertically out of bounds, stop
if (y < 0 || y >= DUNGEON_MAX_SIZE_Y) {
break;
}
stepsUntilLake--;
if (stepsUntilLake != 0)
continue;
// After we go a certain number of steps, make a "lake"
// This loop will attempt to generate new lake tiles up to 64 times
// We select a random tile, check for space and nearby secondary terrain tiles,
// then if verified add a new lake tile.
for (j = 0; j < 64; j++) {
// Each tile is in a random location +-3 tiles from the current cursor in either direction
s32 offsetX = DungeonRandInt(7) - 3;
s32 offsetY = DungeonRandInt(7) - 3;
// Check that there's enough space for a lake within a 2 tile margin of the map bounds
if (offsetX + x < 2 || offsetX + x >= DUNGEON_MAX_SIZE_X - 2 || offsetY + y < 2 || offsetY + y >= DUNGEON_MAX_SIZE_Y - 2)
continue;
// Make secondary terrain here if it's within 2 tiles
// of a tile that's currently secondary terrain
// This results in a "cluster" akin to a lake
if (GetTerrainType(GetTile(offsetX + x + 1, offsetY + y + 1)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x + 1, offsetY + y + 0)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x + 1, offsetY + y - 1)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x + 0, offsetY + y + 1)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x + 0, offsetY + y - 1)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x - 1, offsetY + y + 1)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x - 1, offsetY + y + 0)) == TERRAIN_TYPE_SECONDARY ||
GetTerrainType(GetTile(offsetX + x - 1, offsetY + y - 1)) == TERRAIN_TYPE_SECONDARY)
{
if (!PosIsOutOfBounds(x + offsetX, y + offsetY)) {
SetSecondaryTerrainOnWall(GetTileMut(offsetX + x, offsetY + y));
}
}
}
// Finalization/gap-filling step because the random approach
// might leave weird gaps. Go through every tile and do an
// on line nearest-neighbor interpolation of secondary terrain
// tiles to smoothen out the "lake"
for (offsetX = -3; offsetX <= 3; offsetX++) {
for (offsetY = -3; offsetY <= 3; offsetY++) {
s32 numAdjacent = 0;
s32 xPlus1, yPlus1;
if (offsetX + x < 2 || offsetX + x >= DUNGEON_MAX_SIZE_X - 2 || offsetY + y < 2 || offsetY + y >= DUNGEON_MAX_SIZE_Y - 2)
continue;
// Count the number of secondary terrain tiles adjacent (all 8 directions)
xPlus1 = offsetX + x + 1;
yPlus1 = offsetY + y + 1;
if (GetTerrainType(GetTile(xPlus1, yPlus1)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x + 1, offsetY + y + 0)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x + 1, offsetY + y - 1)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x + 0, offsetY + y + 1)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x + 0, offsetY + y - 1)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x - 1, offsetY + y + 1)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x - 1, offsetY + y + 0)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
if (GetTerrainType(GetTile(offsetX + x - 1, offsetY + y - 1)) == TERRAIN_TYPE_SECONDARY) numAdjacent++;
// If at least half are secondary terrain, make this tile secondary terrain as well
if (numAdjacent >= 4 && !PosIsOutOfBounds(x + offsetX , y + offsetY)) {
SetSecondaryTerrainOnWall(GetTileMut(offsetX + x , offsetY + y));
}
}
}
}
// Creating a lake doesn't mean we are done yet
// but it's likely that the next iteration will hit the tile
// stopping condition for secondary terrain, if not the river continues
// Alternate between horizontal and vertical movement each iteration
if (dirX != 0) {
// The y direction never reverses, ensuring the river doesn't
// double back on itself and cuts across the map only once
if (upwards) {
dirY = -1;
}
else {
dirY = 1;
}
dirX = 0;
}
else {
//Randomly pick between left and right
if (DungeonRandInt(100) < 50) {
dirX = -1;
}
else {
dirX = 1;
}
dirY = 0;
}
if (y < 0 || y >= DUNGEON_MAX_SIZE_Y) {
LABEL:
// Vertically out of bounds, stop
break;
}
}
}
// Generate standalone lakes floorProps->standaloneLakeDensity # of times
for (densityN = 0; densityN < floorProps->standaloneLakeDensity; densityN++) {
s32 x, y;
bool8 table[10][10];
// Try to pick a random tile in the interior to seed the "lake"
// Incredibly unlikely to fail
s32 rndY = 0;
s32 rndX = 0;
s32 n = 0;
// Up to 200 attempts
while (n < 200) {
rndX = DungeonRandRange(0, DUNGEON_MAX_SIZE_X);
rndY = DungeonRandRange(0, DUNGEON_MAX_SIZE_Y);
if (rndX >= 1 && rndX < DUNGEON_MAX_SIZE_X - 1 && rndY >= 1 && rndY < DUNGEON_MAX_SIZE_Y - 1)
break;
n++;
}
if (n == 200)
continue;
// Make a 10x10 grid with TRUE on the boundary and FALSE on the interior
for (x = 0; x < 10; x++) {
for (y = 0; y < 10; y++) {
if (x == 0 || x == 9 || y == 0 || y == 9) {
table[x][y] = TRUE;
}
else {
table[x][y] = FALSE;
}
}
}
// Generate an "inverse lake" by spreading the TRUE values inwards
for (n = 0; n < 80; n++) {
// Pick a random interior point on the 10x10 grid
x = DungeonRandInt(8) + 1;
y = DungeonRandInt(8) + 1;
if (table[x - 1][y] || table[x + 1][y] || table[x][y - 1] || table[x][y + 1]) {
table[x][y] = TRUE;
}
}
// Iterate through the grid, any spaces which are still FALSE form the inverse-inverse lake
// or as some may prefer to call it, just a regular lake!
for (x = 0; x < 10; x++) {
for (y = 0; y < 10; y++) {
if (!table[x][y]) {
// Shift the 0-10 random offset Position into +- 5 to center around the lake seed tile
SetSecondaryTerrainOnWall(GetTileMut(x + rndX - 5, y + rndY - 5));
}
}
}
}
// Clean up secondary terrain that got in places it shouldn't
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
Tile *tile = GetTileMut(x, y);
if (GetTerrainType(tile) != TERRAIN_TYPE_SECONDARY)
continue;
// Revert tiles back to open terrain if:
// - in a kecleon shop
// - in a monster house
// - is an unbreakable tile
// - on a stairs spawn point
// This really shouldn't happen since we only place terrain on wall tiles to begin with,
// but it provides additional safety
if (tile->terrainFlags & (TERRAIN_TYPE_SHOP | TERRAIN_TYPE_IN_MONSTER_HOUSE | TERRAIN_TYPE_UNBREAKABLE) || (tile->spawnOrVisibilityFlags.spawn & SPAWN_FLAG_STAIRS)) {
SetTerrainNormal(tile);
}
else {
// Revert to wall tiles if they're on the soft/hard borders
if (x <= 1 || x >= DUNGEON_MAX_SIZE_X - 1 || y <= 1 || y >= DUNGEON_MAX_SIZE_Y - 1) {
SetTerrainWall(tile);
}
}
}
}
}
#else
NAKED
static void GenerateSecondaryTerrainFormations(u32 flag, FloorProperties *floorProps)
{
asm_unified("\n"
" push {r4-r7,lr}\n"
" mov r7, r10\n"
" mov r6, r9\n"
" mov r5, r8\n"
" push {r5-r7}\n"
" sub sp, 0xA4\n"
" str r1, [sp, 0x64]\n"
" ldrb r1, [r1, 0xD]\n"
" ands r1, r0\n"
" cmp r1, 0\n"
" bne _08050708\n"
" b _08050C20\n"
"_08050708:\n"
" ldr r4, _08050738\n"
" movs r0, 0x8\n"
" bl DungeonRandInt\n"
" lsls r0, 2\n"
" adds r0, r4\n"
" ldr r4, [r0]\n"
" cmp r4, 0\n"
" bne _0805071C\n"
" b _08050A7C\n"
"_0805071C:\n"
" movs r0, 0x64\n"
" bl DungeonRandInt\n"
" cmp r0, 0x31\n"
" bgt _0805073C\n"
" movs r0, 0x1\n"
" str r0, [sp, 0x74]\n"
" movs r1, 0x1F\n"
" mov r10, r1\n"
" movs r2, 0x1\n"
" negs r2, r2\n"
" str r2, [sp, 0x70]\n"
" b _08050746\n"
" .align 2, 0\n"
"_08050738: .4byte sNumToGenTable\n"
"_0805073C:\n"
" movs r0, 0\n"
" str r0, [sp, 0x74]\n"
" mov r10, r0\n"
" movs r1, 0x1\n"
" str r1, [sp, 0x70]\n"
"_08050746:\n"
" movs r0, 0x32\n"
" bl DungeonRandInt\n"
" adds r0, 0xA\n"
" str r0, [sp, 0x78]\n"
" movs r0, 0x2\n"
" movs r1, 0x36\n"
" bl DungeonRandRange\n"
" mov r9, r0\n"
" movs r2, 0\n"
" str r2, [sp, 0x6C]\n"
" subs r4, 0x1\n"
" str r4, [sp, 0x98]\n"
"_08050762:\n"
" movs r0, 0x6\n"
" bl DungeonRandInt\n"
" adds r0, 0x2\n"
" str r0, [sp, 0x68]\n"
" cmp r0, 0\n"
" bne _08050772\n"
" b _08050A38\n"
"_08050772:\n"
" mov r0, r9\n"
" cmp r0, 0x37\n"
" bhi _080507B8\n"
" mov r1, r10\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" movs r0, 0x3\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" bne _0805078A\n"
" b _08050A74\n"
"_0805078A:\n"
" mov r1, r9\n"
" cmp r1, 0\n"
" blt _080507A2\n"
" mov r2, r10\n"
" cmp r2, 0\n"
" blt _080507A2\n"
" mov r0, r9\n"
" cmp r0, 0x37\n"
" bgt _080507A2\n"
" mov r1, r10\n"
" cmp r1, 0x1F\n"
" ble _080507A6\n"
"_080507A2:\n"
" movs r0, 0x1\n"
" b _080507A8\n"
"_080507A6:\n"
" movs r0, 0\n"
"_080507A8:\n"
" cmp r0, 0\n"
" bne _080507B8\n"
" mov r0, r9\n"
" mov r1, r10\n"
" bl GetTileMut\n"
" bl SetSecondaryTerrainOnWall\n"
"_080507B8:\n"
" ldr r2, [sp, 0x68]\n"
" subs r2, 0x1\n"
" str r2, [sp, 0x68]\n"
" ldr r0, [sp, 0x6C]\n"
" add r9, r0\n"
" ldr r1, [sp, 0x70]\n"
" add r10, r1\n"
" mov r2, r10\n"
" cmp r2, 0x1F\n"
" bls _080507CE\n"
" b _08050A38\n"
"_080507CE:\n"
" ldr r0, [sp, 0x78]\n"
" subs r0, 0x1\n"
" str r0, [sp, 0x78]\n"
" cmp r0, 0\n"
" beq _080507DA\n"
" b _08050A30\n"
"_080507DA:\n"
" movs r1, 0x3F\n"
" str r1, [sp, 0x7C]\n"
"_080507DE:\n"
" movs r0, 0x7\n"
" bl DungeonRandInt\n"
" subs r0, 0x3\n"
" str r0, [sp, 0x84]\n"
" movs r0, 0x7\n"
" bl DungeonRandInt\n"
" subs r1, r0, 0x3\n"
" ldr r6, [sp, 0x84]\n"
" add r6, r9\n"
" subs r0, r6, 0x2\n"
" cmp r0, 0x33\n"
" bhi _080508DA\n"
" mov r2, r10\n"
" adds r5, r1, r2\n"
" cmp r5, 0x1\n"
" ble _080508DA\n"
" cmp r5, 0x1D\n"
" bgt _080508DA\n"
" adds r4, r6, 0x1\n"
" adds r0, r5, 0x1\n"
" mov r8, r0\n"
" adds r0, r4, 0\n"
" mov r1, r8\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" movs r2, 0x3\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" adds r0, r4, 0\n"
" adds r1, r5, 0\n"
" str r2, [sp, 0x9C]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" subs r7, r5, 0x1\n"
" adds r0, r4, 0\n"
" adds r1, r7, 0\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" adds r0, r6, 0\n"
" mov r1, r8\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" adds r0, r6, 0\n"
" adds r1, r7, 0\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" subs r4, r6, 0x1\n"
" adds r0, r4, 0\n"
" mov r1, r8\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" adds r0, r4, 0\n"
" adds r1, r5, 0\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" beq _080508B2\n"
" adds r0, r4, 0\n"
" adds r1, r7, 0\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" ldr r2, [sp, 0x9C]\n"
" adds r0, r2, 0\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" bne _080508DA\n"
"_080508B2:\n"
" ldr r0, [sp, 0x84]\n"
" add r0, r9\n"
" cmp r0, 0\n"
" blt _080508C6\n"
" cmp r5, 0\n"
" blt _080508C6\n"
" cmp r0, 0x37\n"
" bgt _080508C6\n"
" cmp r5, 0x1F\n"
" ble _080508CA\n"
"_080508C6:\n"
" movs r1, 0x1\n"
" b _080508CC\n"
"_080508CA:\n"
" movs r1, 0\n"
"_080508CC:\n"
" cmp r1, 0\n"
" bne _080508DA\n"
" adds r1, r5, 0\n"
" bl GetTileMut\n"
" bl SetSecondaryTerrainOnWall\n"
"_080508DA:\n"
" ldr r1, [sp, 0x7C]\n"
" subs r1, 0x1\n"
" str r1, [sp, 0x7C]\n"
" cmp r1, 0\n"
" blt _080508E6\n"
" b _080507DE\n"
"_080508E6:\n"
" movs r0, 0x3\n"
" negs r0, r0\n"
"_080508EA:\n"
" movs r2, 0x3\n"
" negs r2, r2\n"
" str r2, [sp, 0x80]\n"
" mov r1, r9\n"
" adds r1, r0, r1\n"
" str r1, [sp, 0x90]\n"
" adds r0, 0x1\n"
" str r0, [sp, 0x8C]\n"
" adds r7, r1, 0\n"
" mov r5, r10\n"
" subs r5, 0x3\n"
"_08050900:\n"
" movs r6, 0\n"
" subs r0, r7, 0x2\n"
" cmp r0, 0x33\n"
" bls _0805090A\n"
" b _08050A1A\n"
"_0805090A:\n"
" str r5, [sp, 0x88]\n"
" str r5, [sp, 0x94]\n"
" cmp r5, 0x1\n"
" bgt _08050914\n"
" b _08050A1A\n"
"_08050914:\n"
" cmp r5, 0x1D\n"
" ble _0805091A\n"
" b _08050A1A\n"
"_0805091A:\n"
" adds r4, r7, 0x1\n"
" adds r3, r5, 0x1\n"
" adds r0, r4, 0\n"
" adds r1, r3, 0\n"
" str r3, [sp, 0xA0]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" movs r2, 0x3\n"
" mov r8, r2\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r3, [sp, 0xA0]\n"
" cmp r0, 0x2\n"
" bne _0805093A\n"
" movs r6, 0x1\n"
"_0805093A:\n"
" adds r0, r4, 0\n"
" adds r1, r5, 0\n"
" str r3, [sp, 0xA0]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r3, [sp, 0xA0]\n"
" cmp r0, 0x2\n"
" bne _08050952\n"
" adds r6, 0x1\n"
"_08050952:\n"
" subs r2, r5, 0x1\n"
" adds r0, r4, 0\n"
" adds r1, r2, 0\n"
" str r2, [sp, 0x9C]\n"
" str r3, [sp, 0xA0]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r2, [sp, 0x9C]\n"
" ldr r3, [sp, 0xA0]\n"
" cmp r0, 0x2\n"
" bne _08050970\n"
" adds r6, 0x1\n"
"_08050970:\n"
" adds r0, r7, 0\n"
" adds r1, r3, 0\n"
" str r2, [sp, 0x9C]\n"
" str r3, [sp, 0xA0]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r2, [sp, 0x9C]\n"
" ldr r3, [sp, 0xA0]\n"
" cmp r0, 0x2\n"
" bne _0805098C\n"
" adds r6, 0x1\n"
"_0805098C:\n"
" adds r0, r7, 0\n"
" adds r1, r2, 0\n"
" str r2, [sp, 0x9C]\n"
" str r3, [sp, 0xA0]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r2, [sp, 0x9C]\n"
" ldr r3, [sp, 0xA0]\n"
" cmp r0, 0x2\n"
" bne _080509A8\n"
" adds r6, 0x1\n"
"_080509A8:\n"
" subs r4, r7, 0x1\n"
" adds r0, r4, 0\n"
" adds r1, r3, 0\n"
" str r2, [sp, 0x9C]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r2, [sp, 0x9C]\n"
" cmp r0, 0x2\n"
" bne _080509C2\n"
" adds r6, 0x1\n"
"_080509C2:\n"
" adds r0, r4, 0\n"
" adds r1, r5, 0\n"
" str r2, [sp, 0x9C]\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" ldr r2, [sp, 0x9C]\n"
" cmp r0, 0x2\n"
" bne _080509DA\n"
" adds r6, 0x1\n"
"_080509DA:\n"
" adds r0, r4, 0\n"
" adds r1, r2, 0\n"
" bl GetTile\n"
" ldrh r1, [r0]\n"
" mov r0, r8\n"
" ands r0, r1\n"
" cmp r0, 0x2\n"
" bne _080509EE\n"
" adds r6, 0x1\n"
"_080509EE:\n"
" cmp r6, 0x3\n"
" ble _08050A1A\n"
" cmp r7, 0\n"
" blt _08050A04\n"
" cmp r5, 0\n"
" blt _08050A04\n"
" cmp r7, 0x37\n"
" bgt _08050A04\n"
" ldr r0, [sp, 0x88]\n"
" cmp r0, 0x1F\n"
" ble _08050A08\n"
"_08050A04:\n"
" movs r0, 0x1\n"
" b _08050A0A\n"
"_08050A08:\n"
" movs r0, 0\n"
"_08050A0A:\n"
" cmp r0, 0\n"
" bne _08050A1A\n"
" ldr r0, [sp, 0x90]\n"
" ldr r1, [sp, 0x94]\n"
" bl GetTileMut\n"
" bl SetSecondaryTerrainOnWall\n"
"_08050A1A:\n"
" adds r5, 0x1\n"
" ldr r1, [sp, 0x80]\n"
" adds r1, 0x1\n"
" str r1, [sp, 0x80]\n"
" cmp r1, 0x3\n"
" bgt _08050A28\n"
" b _08050900\n"
"_08050A28:\n"
" ldr r0, [sp, 0x8C]\n"
" cmp r0, 0x3\n"
" bgt _08050A30\n"
" b _080508EA\n"
"_08050A30:\n"
" ldr r2, [sp, 0x68]\n"
" cmp r2, 0\n"
" beq _08050A38\n"
" b _08050772\n"
"_08050A38:\n"
" ldr r0, [sp, 0x6C]\n"
" cmp r0, 0\n"
" beq _08050A54\n"
" movs r1, 0x1\n"
" str r1, [sp, 0x70]\n"
" ldr r2, [sp, 0x74]\n"
" cmp r2, 0\n"
" beq _08050A4E\n"
" movs r0, 0x1\n"
" negs r0, r0\n"
" str r0, [sp, 0x70]\n"
"_08050A4E:\n"
" movs r1, 0\n"
" str r1, [sp, 0x6C]\n"
" b _08050A6C\n"
"_08050A54:\n"
" movs r0, 0x64\n"
" bl DungeonRandInt\n"
" movs r2, 0x1\n"
" str r2, [sp, 0x6C]\n"
" cmp r0, 0x31\n"
" bgt _08050A68\n"
" movs r0, 0x1\n"
" negs r0, r0\n"
" str r0, [sp, 0x6C]\n"
"_08050A68:\n"
" movs r1, 0\n"
" str r1, [sp, 0x70]\n"
"_08050A6C:\n"
" mov r2, r10\n"
" cmp r2, 0x1F\n"
" bhi _08050A74\n"
" b _08050762\n"
"_08050A74:\n"
" ldr r4, [sp, 0x98]\n"
" cmp r4, 0\n"
" beq _08050A7C\n"
" b _0805071C\n"
"_08050A7C:\n"
" movs r0, 0\n"
" ldr r1, [sp, 0x64]\n"
" ldrb r1, [r1, 0x15]\n"
" cmp r0, r1\n"
" blt _08050A88\n"
" b _08050BAE\n"
"_08050A88:\n"
" movs r2, 0\n"
" mov r8, r2\n"
" mov r9, r2\n"
" movs r5, 0\n"
" adds r0, 0x1\n"
" mov r10, r0\n"
" b _08050A98\n"
"_08050A96:\n"
" adds r5, 0x1\n"
"_08050A98:\n"
" cmp r5, 0xC7\n"
" bgt _08050AC2\n"
" movs r0, 0\n"
" movs r1, 0x38\n"
" bl DungeonRandRange\n"
" mov r9, r0\n"
" movs r0, 0\n"
" movs r1, 0x20\n"
" bl DungeonRandRange\n"
" mov r8, r0\n"
" mov r0, r9\n"
" subs r0, 0x1\n"
" cmp r0, 0x35\n"
" bhi _08050A96\n"
" mov r0, r8\n"
" cmp r0, 0\n"
" ble _08050A96\n"
" cmp r0, 0x1E\n"
" bgt _08050A96\n"
"_08050AC2:\n"
" cmp r5, 0xC8\n"
" beq _08050BA2\n"
" movs r7, 0\n"
" movs r3, 0x1\n"
" movs r1, 0\n"
"_08050ACC:\n"
" movs r2, 0\n"
" lsls r0, r7, 2\n"
" adds r4, r7, 0x1\n"
" adds r0, r7\n"
" lsls r0, 1\n"
" add r0, sp\n"
"_08050AD8:\n"
" cmp r7, 0\n"
" beq _08050AE8\n"
" cmp r7, 0x9\n"
" beq _08050AE8\n"
" cmp r2, 0\n"
" beq _08050AE8\n"
" cmp r2, 0x9\n"
" bne _08050AEC\n"
"_08050AE8:\n"
" strb r3, [r0]\n"
" b _08050AEE\n"
"_08050AEC:\n"
" strb r1, [r0]\n"
"_08050AEE:\n"
" adds r0, 0x1\n"
" adds r2, 0x1\n"
" cmp r2, 0x9\n"
" ble _08050AD8\n"
" adds r7, r4, 0\n"
" cmp r7, 0x9\n"
" ble _08050ACC\n"
" movs r5, 0x4F\n"
"_08050AFE:\n"
" movs r0, 0x8\n"
" bl DungeonRandInt\n"
" adds r4, r0, 0\n"
" adds r7, r4, 0x1\n"
" movs r0, 0x8\n"
" bl DungeonRandInt\n"
" adds r2, r0, 0x1\n"
" lsls r0, r4, 2\n"
" adds r0, r4\n"
" lsls r0, 1\n"
" adds r0, r2, r0\n"
" add r0, sp\n"
" ldrb r0, [r0]\n"
" cmp r0, 0\n"
" bne _08050B50\n"
" adds r1, r7, 0x1\n"
" lsls r0, r1, 2\n"
" adds r0, r1\n"
" lsls r0, 1\n"
" adds r0, r2, r0\n"
" add r0, sp\n"
" ldrb r0, [r0]\n"
" cmp r0, 0\n"
" bne _08050B50\n"
" lsls r0, r7, 2\n"
" adds r0, r7\n"
" lsls r1, r0, 1\n"
" subs r0, r1, 0x1\n"
" adds r0, r2, r0\n"
" add r0, sp\n"
" ldrb r0, [r0]\n"
" cmp r0, 0\n"
" bne _08050B50\n"
" adds r0, r1, 0x1\n"
" adds r0, r2, r0\n"
" add r0, sp\n"
" ldrb r0, [r0]\n"
" cmp r0, 0\n"
" beq _08050B60\n"
"_08050B50:\n"
" lsls r0, r7, 2\n"
" adds r0, r7\n"
" lsls r0, 1\n"
" adds r0, r2, r0\n"
" mov r2, sp\n"
" adds r1, r2, r0\n"
" movs r0, 0x1\n"
" strb r0, [r1]\n"
"_08050B60:\n"
" subs r5, 0x1\n"
" cmp r5, 0\n"
" bge _08050AFE\n"
" movs r7, 0\n"
"_08050B68:\n"
" lsls r0, r7, 2\n"
" adds r4, r7, 0x1\n"
" adds r0, r7\n"
" lsls r0, 1\n"
" mov r6, r8\n"
" subs r6, 0x5\n"
" mov r1, sp\n"
" adds r5, r0, r1\n"
" add r7, r9\n"
" movs r2, 0x9\n"
"_08050B7C:\n"
" ldrb r0, [r5]\n"
" cmp r0, 0\n"
" bne _08050B92\n"
" subs r0, r7, 0x5\n"
" adds r1, r6, 0\n"
" str r2, [sp, 0x9C]\n"
" bl GetTileMut\n"
" bl SetSecondaryTerrainOnWall\n"
" ldr r2, [sp, 0x9C]\n"
"_08050B92:\n"
" adds r6, 0x1\n"
" adds r5, 0x1\n"
" subs r2, 0x1\n"
" cmp r2, 0\n"
" bge _08050B7C\n"
" adds r7, r4, 0\n"
" cmp r7, 0x9\n"
" ble _08050B68\n"
"_08050BA2:\n"
" mov r0, r10\n"
" ldr r2, [sp, 0x64]\n"
" ldrb r2, [r2, 0x15]\n"
" cmp r0, r2\n"
" bge _08050BAE\n"
" b _08050A88\n"
"_08050BAE:\n"
" movs r0, 0\n"
" mov r9, r0\n"
" movs r6, 0x1\n"
" ldr r1, _08050BF4\n"
" adds r5, r1, 0\n"
"_08050BB8:\n"
" movs r2, 0\n"
" mov r10, r2\n"
" mov r4, r9\n"
" adds r4, 0x1\n"
"_08050BC0:\n"
" mov r0, r9\n"
" mov r1, r10\n"
" bl GetTileMut\n"
" adds r2, r0, 0\n"
" ldrh r3, [r2]\n"
" movs r0, 0x3\n"
" ands r0, r3\n"
" cmp r0, 0x2\n"
" bne _08050C10\n"
" movs r1, 0xB0\n"
" lsls r1, 1\n"
" adds r0, r1, 0\n"
" ands r0, r3\n"
" cmp r0, 0\n"
" bne _08050BEA\n"
" ldrh r1, [r2, 0x4]\n"
" adds r0, r6, 0\n"
" ands r0, r1\n"
" cmp r0, 0\n"
" beq _08050BF8\n"
"_08050BEA:\n"
" adds r0, r3, 0\n"
" ands r0, r5\n"
" orrs r0, r6\n"
" b _08050C0E\n"
" .align 2, 0\n"
"_08050BF4: .4byte 0x0000fffc\n"
"_08050BF8:\n"
" mov r0, r9\n"
" subs r0, 0x2\n"
" cmp r0, 0x34\n"
" bhi _08050C0A\n"
" mov r0, r10\n"
" cmp r0, 0x1\n"
" ble _08050C0A\n"
" cmp r0, 0x1E\n"
" ble _08050C10\n"
"_08050C0A:\n"
" ldrh r0, [r2]\n"
" ands r0, r5\n"
"_08050C0E:\n"
" strh r0, [r2]\n"
"_08050C10:\n"
" movs r1, 0x1\n"
" add r10, r1\n"
" mov r2, r10\n"
" cmp r2, 0x1F\n"
" ble _08050BC0\n"
" mov r9, r4\n"
" cmp r4, 0x37\n"
" ble _08050BB8\n"
"_08050C20:\n"
" add sp, 0xA4\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");
}
#endif // NONMATCHING
#define STAIRS_FLAG_CANNOT_CORNER_CUT 0x1 // Set to `true` for non-open terrain tiles which have `TERRAIN_TYPE_CORNER_CUTTABLE` in their [TerrainFlags]
#define STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT 0x2 // Set to `true` for secondary terrain tiles which have `TERRAIN_TYPE_CORNER_CUTTABLE` in their [TerrainFlags]
#define STAIRS_FLAG_UNKNOWN 0x4 // Not fully understood field. Is tested in relation to corners, but appears to never be set to `true`.
#define STAIRS_FLAG_STARTING_POINT 0x10 // Determines the starting point for graph traversal, set to `true` on the Stairs tile.
#define STAIRS_FLAG_IN_VISIT_QUEUE 0x40 // Set to `true` for tiles which are currently queued to be visited.
#define STAIRS_FLAG_VISITED 0x80 // Set to `true` for tiles which have been visit
/*
* StairsAlwaysReachable - Checks that the stairs are always reachable from every walkable tile on the floor
*
* Uses a graph-traversal similar to Breadth-First Search (but with slightly different order due to how
* iteration works))
*
* If any tile is walkable but wasn't reached, this function will return FALSE.
* If every tile was reached, this function will return TRUE.
*/
bool8 StairsAlwaysReachable(s32 stairsX, s32 stairsY, bool8 markUnreachable)
{
s32 x, y;
u8 test[DUNGEON_MAX_SIZE_X][DUNGEON_MAX_SIZE_Y];
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
Tile *tile = GetTileMut(x, y);
u16 terrain = GetTerrainType(tile);
test[x][y] = 0;
if (markUnreachable) {
// Reset all unreachable flags on tiles, they'll be recomputed from scratch
tile->terrainFlags &= ~(TERRAIN_TYPE_UNREACHABLE_FROM_STAIRS);
}
if (terrain != TERRAIN_TYPE_NORMAL) {
if (!(tile->terrainFlags & TERRAIN_TYPE_CORNER_CUTTABLE)) {
test[x][y] |= STAIRS_FLAG_CANNOT_CORNER_CUT;
}
}
if (terrain == TERRAIN_TYPE_SECONDARY) {
if (!(tile->terrainFlags & TERRAIN_TYPE_CORNER_CUTTABLE)) {
test[x][y] |= STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT;
}
}
}
}
test[stairsX][stairsY] |= STAIRS_FLAG_STARTING_POINT | STAIRS_FLAG_IN_VISIT_QUEUE;
if (gDungeon->stairsSpawn.x != stairsX || gDungeon->stairsSpawn.y != stairsY) {
return FALSE;
}
sNumTilesReachableFromStairs = 0;
// Uses a semi-BFS starting from the stairs until all reachable tiles
// have been visited
while (TRUE) {
s32 checked = 0;
sNumTilesReachableFromStairs += 1;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
if (!(test[x][y] & STAIRS_FLAG_VISITED) && test[x][y] & STAIRS_FLAG_IN_VISIT_QUEUE) {
checked++;
test[x][y] &= ~(STAIRS_FLAG_IN_VISIT_QUEUE);
test[x][y] |= STAIRS_FLAG_VISITED;
// Queue up in cardinal directions of this tile
// Left
if (x > 0 && !(test[x - 1][y] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED))) {
test[x - 1][y] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Up
if (y > 0 && !(test[x][y - 1] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED))) {
test[x][y - 1] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Right
if (x < DUNGEON_MAX_SIZE_X - 1 && !(test[x + 1][y] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED))) {
test[x + 1][y] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Down
if (y < DUNGEON_MAX_SIZE_Y - 1 && !(test[x][y + 1] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED))) {
test[x][y + 1] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Up-left
if (x > 0 && y > 0
&& !(test[x - 1][y - 1] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED | STAIRS_FLAG_UNKNOWN))
&& !(test[x - 1][y] & STAIRS_FLAG_CANNOT_CORNER_CUT)
&& !(test[x][y - 1] & STAIRS_FLAG_CANNOT_CORNER_CUT))
{
test[x - 1][y - 1] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Up-Right
if (x < DUNGEON_MAX_SIZE_X - 1 && y > 0
&& !(test[x + 1][y - 1] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED | STAIRS_FLAG_UNKNOWN))
&& !(test[x + 1][y] & STAIRS_FLAG_CANNOT_CORNER_CUT)
&& !(test[x][y - 1] & STAIRS_FLAG_CANNOT_CORNER_CUT))
{
test[x + 1][y - 1] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Down-left
if (x > 0 && y < DUNGEON_MAX_SIZE_Y - 1
&& !(test[x - 1][y + 1] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED | STAIRS_FLAG_UNKNOWN))
&& !(test[x - 1][y] & STAIRS_FLAG_CANNOT_CORNER_CUT)
&& !(test[x][y + 1] & STAIRS_FLAG_CANNOT_CORNER_CUT))
{
test[x - 1][y + 1] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
// Down-right
if (x < DUNGEON_MAX_SIZE_X - 1 && y < DUNGEON_MAX_SIZE_Y - 1
&& !(test[x + 1][y + 1] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_VISITED | STAIRS_FLAG_UNKNOWN))
&& !(test[x + 1][y] & STAIRS_FLAG_CANNOT_CORNER_CUT)
&& !(test[x][y + 1] & STAIRS_FLAG_CANNOT_CORNER_CUT))
{
test[x + 1][y + 1] |= STAIRS_FLAG_IN_VISIT_QUEUE;
}
}
}
}
if (checked == 0)
break;
}
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
Tile *tile = GetTileMut(x, y);
if (!(test[x][y] & (STAIRS_FLAG_CANNOT_CORNER_CUT | STAIRS_FLAG_SECONDARY_TERRAIN_CANNOT_CORNER_CUT | STAIRS_FLAG_UNKNOWN | STAIRS_FLAG_VISITED))) {
// This is an open tile that wasn't visited by BFS, which means it's unreachable from the starting stairs
if (markUnreachable) {
tile->terrainFlags |= TERRAIN_TYPE_UNREACHABLE_FROM_STAIRS;
}
else {
// unbreakable tiles can't really be navigated onto anyways, so if
// we can ignore the tile (otherwise it's a problem!)
if (!(tile->terrainFlags & TERRAIN_TYPE_UNBREAKABLE)) {
return FALSE;
}
}
}
}
}
return TRUE;
}
static void CreateRoomsAndAnchorsForFixedFloor(struct GridCell grid[GRID_CELL_LEN][GRID_CELL_LEN], s32 gridSizeX, s32 gridSizeY, s32 *listX, s32 *listY, s32 a5, s32 fixedRoomSizeX, s32 fixedRoomSizeY)
{
s32 roomNumber = 0;
s32 var_48 = 0;
s32 x, y;
for (y = 0; y < gridSizeY; y++) {
for (x = 0; x < gridSizeX; var_48++, 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] - 4;
s32 minRoomSizeX = 5;
s32 minRoomSizeY = 5;
if (gridSizeX <= 2) {
minRoomSizeX = 10;
rangeX = 14;
}
if (gridSizeY == 1) {
minRoomSizeY = 16;
rangeY = 24;
}
if (grid[x][y].isRoom) {
// This cell is a room!
s32 roomSizeX, roomSizeY;
s32 startX, endX;
s32 startY, endY;
s32 roomX, roomY;
if (var_48 != a5) {
roomSizeX = DungeonRandRange(minRoomSizeX, rangeX);
roomSizeY = DungeonRandRange(minRoomSizeY, 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;
}
else {
startX = minX;
startY = minY;
endX = startX + fixedRoomSizeX;
endY = startY + fixedRoomSizeY;
}
// 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(GetTileMut(roomX, roomY));
GetTileMut(roomX, roomY)->room = roomNumber;
}
}
if (var_48 != a5) {
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(GetTileMut(pt_x, pt_y));
// Set the room index to 0xFE for anchor
GetTileMut(pt_x, pt_y)->room = CORRIDOR_ROOM;
}
}
}
}
static EWRAM_DATA u8 *gUnknown_202F1DC = NULL;
static EWRAM_DATA u8 gUnknown_202F1E0 = 0;
static EWRAM_DATA u8 gUnknown_202F1E1 = 0;
static UNUSED EWRAM_DATA s32 sUnusedEwram = 0;
static u8 sub_80511F0(void)
{
if (gUnknown_202F1E1 != 0) {
gUnknown_202F1E1--;
return gUnknown_202F1E0;
}
gUnknown_202F1E0 = *gUnknown_202F1DC;
gUnknown_202F1DC++;
if (gUnknown_202F1E0 == 14) {
gUnknown_202F1E0 = *gUnknown_202F1DC;
gUnknown_202F1DC++;
}
else {
gUnknown_202F1E1 = gUnknown_202F1E0 & 0xF;
gUnknown_202F1E0 = (gUnknown_202F1E0 & 0xF0) >> 4;
}
return gUnknown_202F1E0;
}
static bool8 sub_805124C(Tile *tile, u8 a1, s32 x, s32 y, bool8 spawnTrapOrItem)
{
tile->terrainFlags |= TERRAIN_TYPE_UNBREAKABLE;
tile->unkE = 0;
return PlaceFixedRoomTile(tile, a1, x, y, spawnTrapOrItem);
}
static void sub_8051288(s32 fixedRoomNumber)
{
s32 x, y;
Dungeon *dungeon = gDungeon;
s32 fixedRoomSizeX = ((struct FixedRoomsData **)(dungeon->unk13568->data))[fixedRoomNumber]->x;
s32 fixedRoomSizeY = ((struct FixedRoomsData **)(dungeon->unk13568->data))[fixedRoomNumber]->y;
dungeon->unkE260.unk0 = fixedRoomSizeX;
dungeon->unkE260.unk2 = fixedRoomSizeY;
gUnknown_202F1DC = ((struct FixedRoomsData **)(dungeon->unk13568->data))[fixedRoomNumber]->unk3;
gUnknown_202F1E1 = 0;
for (y = 5; y < fixedRoomSizeY + 5; y++) {
for (x = 5; x < fixedRoomSizeX + 5; x++) {
u8 unk = sub_80511F0();
if (sub_805124C(GetTileMut(x, y), unk, x, y, TRUE)) {
dungeon->stairsSpawn.x = x;
dungeon->stairsSpawn.y = y;
}
}
}
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
if (x <= 4 || x >= fixedRoomSizeX + 5 || y <= 4 || y >= fixedRoomSizeY + 5) {
Tile *tile = GetTileMut(x, y);
tile->terrainFlags |= TERRAIN_TYPE_IMPASSABLE_WALL;
if (gUnknown_202F1A8) {
SetTerrainType(tile, TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY);
}
else {
SetTerrainWall(tile);
}
}
}
}
if (fixedRoomNumber == FIXED_ROOM_MT_BLAZE_PEAK_MOLTRES) {
for (y = 5; y < 17; y++) {
for (x = 2; x < 5; x++) {
Tile *tile = GetTileMut(x, y);
tile->terrainFlags |= TERRAIN_TYPE_IMPASSABLE_WALL;
SetTerrainWall(tile);
}
}
}
if (gDungeon->tileset >= 64) {
for (y = 0; y < DUNGEON_MAX_SIZE_Y; y++) {
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
Tile *tile = GetTileMut(x, y);
if (GetTerrainType(tile) == TERRAIN_TYPE_WALL) {
tile->terrainFlags |= TERRAIN_TYPE_IMPASSABLE_WALL;
}
}
}
}
FinalizeJunctions();
}
static void sub_8051438(struct GridCell *gridCell, s32 fixedRoomNumber)
{
s32 x, y;
Dungeon *dungeon = gDungeon;
gUnknown_202F1DC = ((struct FixedRoomsData **)(dungeon->unk13568->data))[fixedRoomNumber]->unk3;
gUnknown_202F1E1 = 0;
if (((struct FixedRoomsData **)(dungeon->unk13568->data))[fixedRoomNumber]->unk2 & 1) {
s32 yIndex;
dungeon->unkE250.minX = gridCell->start.x;
dungeon->unkE250.minY = gridCell->start.y;
dungeon->unkE250.maxX = gridCell->end.x;
dungeon->unkE250.maxY = gridCell->end.y;
yIndex = 0;
for (y = gridCell->start.y; y < gridCell->end.y; y++) {
s32 xIndex = 0;
for (x = gridCell->start.x; x < gridCell->end.x; x++) {
u8 roomId;
u8 unk = sub_80511F0();
Tile *tile = GetTileMut(x, y);
dungeon->unkE87C[xIndex][yIndex] = unk;
sub_805124C(&dungeon->unkE27C[xIndex][yIndex], unk, x, y, FALSE);
roomId = tile->room;
*tile = dungeon->unkE27C[xIndex][yIndex];
if (x >= gridCell->start.x + 2 && x < gridCell->end.x - 2 && y >= gridCell->start.y + 2 && y < gridCell->end.y - 2) {
tile->terrainFlags = TERRAIN_TYPE_IMPASSABLE_WALL | TERRAIN_TYPE_UNBREAKABLE;
tile->unkE = 0xE;
}
tile->room = roomId;
dungeon->unkE8BC = roomId;
xIndex++;
}
yIndex++;
}
}
else {
for (y = gridCell->start.y; y < gridCell->end.y; y++) {
for (x = gridCell->start.x; x < gridCell->end.x; x++) {
u8 unk = sub_80511F0();
Tile *tile = GetTileMut(x, y);
u8 roomId = tile->room;
sub_805124C(tile, unk, x, y, TRUE);
tile->room = roomId;
dungeon->unkE8BC = roomId;
}
}
}
}
static void sub_8051654(FloorProperties *floorProps)
{
s32 i, n;
s32 x, y;
s32 middleX, middleY;
s32 rangeX = 3, rangeY = 3;
s32 xIndex, yIndex;
s32 r10;
// Note: These loops make no sense as the range is always at least 3 - which means the loop always breaks after the first iteration.
// In fact, pmd red's compiler optimizes out these loops completely. These are however needed to match and were NOT optimized by blue's compiler which proves that's how it was written.
for (n = 0; n < 20; n++) {
rangeX = DungeonRandRange(3, (sKecleonShopPosition.maxX - sKecleonShopPosition.minX) - 2);
if (rangeX < 3)
rangeX = 3;
if (rangeX >= 3)
break;
}
for (n = 0; n < 20; n++) {
rangeY = DungeonRandRange(3, (sKecleonShopPosition.maxY - sKecleonShopPosition.minY) - 2);
if (rangeY < 3)
rangeY = 3;
if (rangeY >= 3)
break;
}
r10 = DungeonRandRange(2, 4);
for (i = 0; i < r10; i++) {
if (sKecleonShopPosition.maxX - sKecleonShopPosition.minX <= rangeX)
break;
if (DungeonRandInt(100) < 50) {
s32 y;
for (y = sKecleonShopPosition.minY; y < sKecleonShopPosition.maxY; y++) {
GetTileMut(sKecleonShopPosition.minX, y)->terrainFlags &= ~(TERRAIN_TYPE_SHOP);
}
sKecleonShopPosition.minX++;
}
else {
s32 y;
sKecleonShopPosition.maxX--;
for (y = sKecleonShopPosition.minY; y < sKecleonShopPosition.maxY; y++) {
GetTileMut(sKecleonShopPosition.maxX, y)->terrainFlags &= ~(TERRAIN_TYPE_SHOP);
}
}
}
for (i = 0; i < r10; i++) {
if (sKecleonShopPosition.maxY - sKecleonShopPosition.minY <= rangeY)
break;
if (DungeonRandInt(100) < 50) {
s32 x;
for (x = sKecleonShopPosition.minX; x < sKecleonShopPosition.maxX; x++) {
GetTileMut(x, sKecleonShopPosition.minY)->terrainFlags &= ~(TERRAIN_TYPE_SHOP);
}
sKecleonShopPosition.minY++;
}
else {
s32 x;
sKecleonShopPosition.maxY--;
for (x = sKecleonShopPosition.minX; x < sKecleonShopPosition.maxX; x++) {
GetTileMut(x, sKecleonShopPosition.maxY)->terrainFlags &= ~(TERRAIN_TYPE_SHOP);
}
}
}
for (x = sKecleonShopPosition.minX; x < sKecleonShopPosition.maxX; x++) {
for (y = sKecleonShopPosition.minY; y < sKecleonShopPosition.maxY; y++) {
Tile *tile = GetTileMut(x, y);
if (!(tile->terrainFlags & TERRAIN_TYPE_SHOP))
continue;
if (!(tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION))
continue;
tile->terrainFlags &= ~(TERRAIN_TYPE_SHOP);
}
}
middleX = (sKecleonShopPosition.minX + sKecleonShopPosition.maxX) / 2 - 1;
middleY = (sKecleonShopPosition.minY + sKecleonShopPosition.maxY) / 2 - 1;
xIndex = 0;
for (x = middleX; x < middleX + 3; x++, xIndex++) {
yIndex = 0;
for (y = middleY; y < middleY + 3; y++, yIndex++) {
Tile *tile = GetTileMut(x, y);
if (!(tile->terrainFlags & TERRAIN_TYPE_SHOP))
continue;
if ((tile->terrainFlags & TERRAIN_TYPE_IN_MONSTER_HOUSE))
continue;
if ((tile->terrainFlags & TERRAIN_TYPE_NATURAL_JUNCTION))
continue;
if (sKecleonShopItemSpawnChances[floorProps->kecleonShopLayout][yIndex][xIndex] > DungeonRandInt(100)) {
tile->spawnOrVisibilityFlags.spawn |= SPAWN_FLAG_ITEM;
}
}
}
}
/*
* ResetInnerBoundaryTileRows - Resets inner boundary tile rows (y == 1 and y == 30)
* to their initial state of all wall tiles, with impassable walls at the edges.
*
* This is needed because during generation these soft border walls may have been altered or breached.
*/
static void ResetInnerBoundaryTileRows(void)
{
s32 x;
for (x = 0; x < DUNGEON_MAX_SIZE_X; x++) {
Tile *tile = GetTileMut(x, 1);
ResetTile(tile);
if (x == 0 || x == DUNGEON_MAX_SIZE_X - 1) {
tile->terrainFlags |= TERRAIN_TYPE_IMPASSABLE_WALL;
}
tile = GetTileMut(x, 30);
ResetTile(tile);
if (x == 0 || x == DUNGEON_MAX_SIZE_X - 1) {
tile->terrainFlags |= TERRAIN_TYPE_IMPASSABLE_WALL;
}
}
}