You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

609 lines
14 KiB

/**
* @file gendung.cpp
*
* Implementation of general dungeon generation code.
*/
#include "gendung.h"
#include "engine/load_file.hpp"
#include "engine/random.hpp"
#include "init.h"
#include "lighting.h"
#include "options.h"
namespace devilution {
uint8_t dungeon[DMAXX][DMAXY];
uint8_t pdungeon[DMAXX][DMAXY];
uint8_t dflags[DMAXX][DMAXY];
int setpc_x;
int setpc_y;
int setpc_w;
int setpc_h;
std::unique_ptr<uint16_t[]> pSetPiece;
bool setloadflag;
std::optional<CelSprite> pSpecialCels;
std::unique_ptr<MegaTile[]> pMegaTiles;
std::unique_ptr<uint16_t[]> pLevelPieces;
std::unique_ptr<byte[]> pDungeonCels;
std::array<uint8_t, MAXTILES + 1> block_lvid;
std::array<bool, MAXTILES + 1> nBlockTable;
std::array<bool, MAXTILES + 1> nSolidTable;
std::array<bool, MAXTILES + 1> nTransTable;
std::array<bool, MAXTILES + 1> nMissileTable;
std::array<bool, MAXTILES + 1> nTrapTable;
int dminx;
int dminy;
int dmaxx;
int dmaxy;
dungeon_type leveltype;
BYTE currlevel;
bool setlevel;
_setlevels setlvlnum;
dungeon_type setlvltype;
int ViewX;
int ViewY;
ScrollStruct ScrollInfo;
int MicroTileLen;
char TransVal;
bool TransList[256];
int dPiece[MAXDUNX][MAXDUNY];
MICROS dpiece_defs_map_2[MAXDUNX][MAXDUNY];
int8_t dTransVal[MAXDUNX][MAXDUNY];
char dLight[MAXDUNX][MAXDUNY];
char dPreLight[MAXDUNX][MAXDUNY];
int8_t dFlags[MAXDUNX][MAXDUNY];
int8_t dPlayer[MAXDUNX][MAXDUNY];
int16_t dMonster[MAXDUNX][MAXDUNY];
int8_t dDead[MAXDUNX][MAXDUNY];
char dObject[MAXDUNX][MAXDUNY];
int8_t dItem[MAXDUNX][MAXDUNY];
char dSpecial[MAXDUNX][MAXDUNY];
int themeCount;
THEME_LOC themeLoc[MAXTHEMES];
namespace {
std::unique_ptr<uint8_t[]> LoadLevelSOLData(size_t &tileCount)
{
switch (leveltype) {
case DTYPE_TOWN:
if (gbIsHellfire)
return LoadFileInMem<uint8_t>("NLevels\\TownData\\Town.SOL", &tileCount);
return LoadFileInMem<uint8_t>("Levels\\TownData\\Town.SOL", &tileCount);
case DTYPE_CATHEDRAL:
if (currlevel < 17)
return LoadFileInMem<uint8_t>("Levels\\L1Data\\L1.SOL", &tileCount);
return LoadFileInMem<uint8_t>("NLevels\\L5Data\\L5.SOL", &tileCount);
case DTYPE_CATACOMBS:
return LoadFileInMem<uint8_t>("Levels\\L2Data\\L2.SOL", &tileCount);
case DTYPE_CAVES:
if (currlevel < 17)
return LoadFileInMem<uint8_t>("Levels\\L3Data\\L3.SOL", &tileCount);
return LoadFileInMem<uint8_t>("NLevels\\L6Data\\L6.SOL", &tileCount);
case DTYPE_HELL:
return LoadFileInMem<uint8_t>("Levels\\L4Data\\L4.SOL", &tileCount);
default:
app_fatal("FillSolidBlockTbls");
}
}
bool WillThemeRoomFit(int floor, int x, int y, int minSize, int maxSize, int *width, int *height)
{
bool yFlag = true;
bool xFlag = true;
int xCount = 0;
int yCount = 0;
if (x + maxSize > DMAXX && y + maxSize > DMAXY) {
return false; // Original broken bounds check, avoids lower right corner
}
if (x + minSize > DMAXX || y + minSize > DMAXY) {
return false; // Skip definit OOB cases
}
if (!SkipThemeRoom(x, y)) {
return false;
}
int xArray[20] = {};
int yArray[20] = {};
for (int ii = 0; ii < maxSize; ii++) {
if (xFlag && y + ii < DMAXY) {
for (int xx = x; xx < x + maxSize && xx < DMAXX; xx++) {
if (dungeon[xx][y + ii] != floor) {
if (xx >= minSize) {
break;
}
xFlag = false;
} else {
xCount++;
}
}
if (xFlag) {
xArray[ii] = xCount;
xCount = 0;
}
}
if (yFlag && x + ii < DMAXX) {
for (int yy = y; yy < y + maxSize && yy < DMAXY; yy++) {
if (dungeon[x + ii][yy] != floor) {
if (yy >= minSize) {
break;
}
yFlag = false;
} else {
yCount++;
}
}
if (yFlag) {
yArray[ii] = yCount;
yCount = 0;
}
}
}
for (int ii = 0; ii < minSize; ii++) {
if (xArray[ii] < minSize || yArray[ii] < minSize) {
return false;
}
}
int xSmallest = xArray[0];
int ySmallest = yArray[0];
for (int ii = 0; ii < maxSize; ii++) {
if (xArray[ii] < minSize || yArray[ii] < minSize) {
break;
}
if (xArray[ii] < xSmallest) {
xSmallest = xArray[ii];
}
if (yArray[ii] < ySmallest) {
ySmallest = yArray[ii];
}
}
*width = xSmallest - 2;
*height = ySmallest - 2;
return true;
}
void CreateThemeRoom(int themeIndex)
{
const int lx = themeLoc[themeIndex].x;
const int ly = themeLoc[themeIndex].y;
const int hx = lx + themeLoc[themeIndex].width;
const int hy = ly + themeLoc[themeIndex].height;
for (int yy = ly; yy < hy; yy++) {
for (int xx = lx; xx < hx; xx++) {
if (leveltype == DTYPE_CATACOMBS) {
if (yy == ly || yy == hy - 1) {
dungeon[xx][yy] = 2;
} else if (xx == lx || xx == hx - 1) {
dungeon[xx][yy] = 1;
} else {
dungeon[xx][yy] = 3;
}
}
if (leveltype == DTYPE_CAVES) {
if (yy == ly || yy == hy - 1) {
dungeon[xx][yy] = 134;
} else if (xx == lx || xx == hx - 1) {
dungeon[xx][yy] = 137;
} else {
dungeon[xx][yy] = 7;
}
}
if (leveltype == DTYPE_HELL) {
if (yy == ly || yy == hy - 1) {
dungeon[xx][yy] = 2;
} else if (xx == lx || xx == hx - 1) {
dungeon[xx][yy] = 1;
} else {
dungeon[xx][yy] = 6;
}
}
}
}
if (leveltype == DTYPE_CATACOMBS) {
dungeon[lx][ly] = 8;
dungeon[hx - 1][ly] = 7;
dungeon[lx][hy - 1] = 9;
dungeon[hx - 1][hy - 1] = 6;
}
if (leveltype == DTYPE_CAVES) {
dungeon[lx][ly] = 150;
dungeon[hx - 1][ly] = 151;
dungeon[lx][hy - 1] = 152;
dungeon[hx - 1][hy - 1] = 138;
}
if (leveltype == DTYPE_HELL) {
dungeon[lx][ly] = 9;
dungeon[hx - 1][ly] = 16;
dungeon[lx][hy - 1] = 15;
dungeon[hx - 1][hy - 1] = 12;
}
if (leveltype == DTYPE_CATACOMBS) {
switch (GenerateRnd(2)) {
case 0:
dungeon[hx - 1][(ly + hy) / 2] = 4;
break;
case 1:
dungeon[(lx + hx) / 2][hy - 1] = 5;
break;
}
}
if (leveltype == DTYPE_CAVES) {
switch (GenerateRnd(2)) {
case 0:
dungeon[hx - 1][(ly + hy) / 2] = 147;
break;
case 1:
dungeon[(lx + hx) / 2][hy - 1] = 146;
break;
}
}
if (leveltype == DTYPE_HELL) {
switch (GenerateRnd(2)) {
case 0: {
int yy = (ly + hy) / 2;
dungeon[hx - 1][yy - 1] = 53;
dungeon[hx - 1][yy] = 6;
dungeon[hx - 1][yy + 1] = 52;
dungeon[hx - 2][yy - 1] = 54;
} break;
case 1: {
int xx = (lx + hx) / 2;
dungeon[xx - 1][hy - 1] = 57;
dungeon[xx][hy - 1] = 6;
dungeon[xx + 1][hy - 1] = 56;
dungeon[xx][hy - 2] = 59;
dungeon[xx - 1][hy - 2] = 58;
} break;
}
}
}
void FindTransparencyValues(int i, int j, int x, int y, int d, uint8_t floorID)
{
if (dTransVal[x][y] != 0 || dungeon[i][j] != floorID) {
if (d == 1) {
dTransVal[x][y] = TransVal;
dTransVal[x][y + 1] = TransVal;
}
if (d == 2) {
dTransVal[x + 1][y] = TransVal;
dTransVal[x + 1][y + 1] = TransVal;
}
if (d == 3) {
dTransVal[x][y] = TransVal;
dTransVal[x + 1][y] = TransVal;
}
if (d == 4) {
dTransVal[x][y + 1] = TransVal;
dTransVal[x + 1][y + 1] = TransVal;
}
if (d == 5) {
dTransVal[x + 1][y + 1] = TransVal;
}
if (d == 6) {
dTransVal[x][y + 1] = TransVal;
}
if (d == 7) {
dTransVal[x + 1][y] = TransVal;
}
if (d == 8) {
dTransVal[x][y] = TransVal;
}
return;
}
dTransVal[x][y] = TransVal;
dTransVal[x + 1][y] = TransVal;
dTransVal[x][y + 1] = TransVal;
dTransVal[x + 1][y + 1] = TransVal;
FindTransparencyValues(i + 1, j, x + 2, y, 1, floorID);
FindTransparencyValues(i - 1, j, x - 2, y, 2, floorID);
FindTransparencyValues(i, j + 1, x, y + 2, 3, floorID);
FindTransparencyValues(i, j - 1, x, y - 2, 4, floorID);
FindTransparencyValues(i - 1, j - 1, x - 2, y - 2, 5, floorID);
FindTransparencyValues(i + 1, j - 1, x + 2, y - 2, 6, floorID);
FindTransparencyValues(i - 1, j + 1, x - 2, y + 2, 7, floorID);
FindTransparencyValues(i + 1, j + 1, x + 2, y + 2, 8, floorID);
}
} // namespace
void FillSolidBlockTbls()
{
size_t tileCount;
auto pSBFile = LoadLevelSOLData(tileCount);
for (unsigned i = 0; i < tileCount; i++) {
uint8_t bv = pSBFile[i];
nSolidTable[i + 1] = (bv & 0x01) != 0;
nBlockTable[i + 1] = (bv & 0x02) != 0;
nMissileTable[i + 1] = (bv & 0x04) != 0;
nTransTable[i + 1] = (bv & 0x08) != 0;
nTrapTable[i + 1] = (bv & 0x80) != 0;
block_lvid[i + 1] = (bv & 0x70) >> 4;
}
}
void SetDungeonMicros()
{
MicroTileLen = 10;
int blocks = 10;
if (leveltype == DTYPE_TOWN) {
MicroTileLen = 16;
blocks = 16;
} else if (leveltype == DTYPE_HELL) {
MicroTileLen = 12;
blocks = 16;
}
for (int y = 0; y < MAXDUNY; y++) {
for (int x = 0; x < MAXDUNX; x++) {
int lv = dPiece[x][y];
MICROS &micros = dpiece_defs_map_2[x][y];
if (lv != 0) {
lv--;
uint16_t *pieces = &pLevelPieces[blocks * lv];
for (int i = 0; i < blocks; i++)
micros.mt[i] = SDL_SwapLE16(pieces[blocks - 2 + (i & 1) - (i & 0xE)]);
} else {
for (int i = 0; i < blocks; i++)
micros.mt[i] = 0;
}
}
}
}
void DRLG_InitTrans()
{
memset(dTransVal, 0, sizeof(dTransVal));
memset(TransList, 0, sizeof(TransList));
TransVal = 1;
}
void DRLG_MRectTrans(int x1, int y1, int x2, int y2)
{
x1 = 2 * x1 + 17;
y1 = 2 * y1 + 17;
x2 = 2 * x2 + 16;
y2 = 2 * y2 + 16;
for (int j = y1; j <= y2; j++) {
for (int i = x1; i <= x2; i++) {
dTransVal[i][j] = TransVal;
}
}
TransVal++;
}
void DRLG_RectTrans(int x1, int y1, int x2, int y2)
{
for (int j = y1; j <= y2; j++) {
for (int i = x1; i <= x2; i++) {
dTransVal[i][j] = TransVal;
}
}
TransVal++;
}
void DRLG_CopyTrans(int sx, int sy, int dx, int dy)
{
dTransVal[dx][dy] = dTransVal[sx][sy];
}
void DRLG_ListTrans(int num, BYTE *list)
{
for (int i = 0; i < num; i++) {
uint8_t x1 = *list++;
uint8_t y1 = *list++;
uint8_t x2 = *list++;
uint8_t y2 = *list++;
DRLG_RectTrans(x1, y1, x2, y2);
}
}
void DRLG_AreaTrans(int num, BYTE *list)
{
for (int i = 0; i < num; i++) {
uint8_t x1 = *list++;
uint8_t y1 = *list++;
uint8_t x2 = *list++;
uint8_t y2 = *list++;
DRLG_RectTrans(x1, y1, x2, y2);
TransVal--;
}
TransVal++;
}
void DRLG_InitSetPC()
{
setpc_x = 0;
setpc_y = 0;
setpc_w = 0;
setpc_h = 0;
}
void DRLG_SetPC()
{
int w = 2 * setpc_w;
int h = 2 * setpc_h;
int x = 2 * setpc_x + 16;
int y = 2 * setpc_y + 16;
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
dFlags[i + x][j + y] |= BFLAG_POPULATED;
}
}
}
void Make_SetPC(int x, int y, int w, int h)
{
int dw = 2 * w;
int dh = 2 * h;
int dx = 2 * x + 16;
int dy = 2 * y + 16;
for (int j = 0; j < dh; j++) {
for (int i = 0; i < dw; i++) {
dFlags[i + dx][j + dy] |= BFLAG_POPULATED;
}
}
}
void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize)
{
themeCount = 0;
memset(themeLoc, 0, sizeof(*themeLoc));
for (int j = 0; j < DMAXY; j++) {
for (int i = 0; i < DMAXX; i++) {
int themeW = 0;
int themeH = 0;
if (dungeon[i][j] == floor && GenerateRnd(freq) == 0 && WillThemeRoomFit(floor, i, j, minSize, maxSize, &themeW, &themeH)) {
if (rndSize) {
int min = minSize - 2;
int max = maxSize - 2;
themeW = min + GenerateRnd(GenerateRnd(themeW - min + 1));
if (themeW < min || themeW > max)
themeW = min;
themeH = min + GenerateRnd(GenerateRnd(themeH - min + 1));
if (themeH < min || themeH > max)
themeH = min;
}
themeLoc[themeCount].x = i + 1;
themeLoc[themeCount].y = j + 1;
themeLoc[themeCount].width = themeW;
themeLoc[themeCount].height = themeH;
if (leveltype == DTYPE_CAVES)
DRLG_RectTrans(2 * i + 20, 2 * j + 20, 2 * (i + themeW) + 15, 2 * (j + themeH) + 15);
else
DRLG_MRectTrans(i + 1, j + 1, i + themeW, j + themeH);
themeLoc[themeCount].ttval = TransVal - 1;
CreateThemeRoom(themeCount);
themeCount++;
}
}
}
}
void DRLG_HoldThemeRooms()
{
for (int i = 0; i < themeCount; i++) {
for (int y = themeLoc[i].y; y < themeLoc[i].y + themeLoc[i].height - 1; y++) {
for (int x = themeLoc[i].x; x < themeLoc[i].x + themeLoc[i].width - 1; x++) {
int xx = 2 * x + 16;
int yy = 2 * y + 16;
dFlags[xx][yy] |= BFLAG_POPULATED;
dFlags[xx + 1][yy] |= BFLAG_POPULATED;
dFlags[xx][yy + 1] |= BFLAG_POPULATED;
dFlags[xx + 1][yy + 1] |= BFLAG_POPULATED;
}
}
}
}
void DRLG_LPass3(int lv)
{
{
MegaTile mega = pMegaTiles[lv];
int v1 = SDL_SwapLE16(mega.micro1) + 1;
int v2 = SDL_SwapLE16(mega.micro2) + 1;
int v3 = SDL_SwapLE16(mega.micro3) + 1;
int v4 = SDL_SwapLE16(mega.micro4) + 1;
for (int j = 0; j < MAXDUNY; j += 2) {
for (int i = 0; i < MAXDUNX; i += 2) {
dPiece[i + 0][j + 0] = v1;
dPiece[i + 1][j + 0] = v2;
dPiece[i + 0][j + 1] = v3;
dPiece[i + 1][j + 1] = v4;
}
}
}
int yy = 16;
for (int j = 0; j < DMAXY; j++) {
int xx = 16;
for (int i = 0; i < DMAXX; i++) { // NOLINT(modernize-loop-convert)
int v1 = 0;
int v2 = 0;
int v3 = 0;
int v4 = 0;
int tileId = dungeon[i][j] - 1;
if (tileId >= 0) {
MegaTile mega = pMegaTiles[tileId];
v1 = SDL_SwapLE16(mega.micro1) + 1;
v2 = SDL_SwapLE16(mega.micro2) + 1;
v3 = SDL_SwapLE16(mega.micro3) + 1;
v4 = SDL_SwapLE16(mega.micro4) + 1;
}
dPiece[xx + 0][yy + 0] = v1;
dPiece[xx + 1][yy + 0] = v2;
dPiece[xx + 0][yy + 1] = v3;
dPiece[xx + 1][yy + 1] = v4;
xx += 2;
}
yy += 2;
}
}
void DRLG_Init_Globals()
{
memset(dFlags, 0, sizeof(dFlags));
memset(dPlayer, 0, sizeof(dPlayer));
memset(dMonster, 0, sizeof(dMonster));
memset(dDead, 0, sizeof(dDead));
memset(dObject, 0, sizeof(dObject));
memset(dItem, 0, sizeof(dItem));
memset(dSpecial, 0, sizeof(dSpecial));
int8_t c = DisableLighting ? 0 : 15;
memset(dLight, c, sizeof(dLight));
}
bool SkipThemeRoom(int x, int y)
{
for (int i = 0; i < themeCount; i++) {
if (x >= themeLoc[i].x - 2 && x <= themeLoc[i].x + themeLoc[i].width + 2
&& y >= themeLoc[i].y - 2 && y <= themeLoc[i].y + themeLoc[i].height + 2)
return false;
}
return true;
}
void InitLevels()
{
currlevel = 0;
leveltype = DTYPE_TOWN;
setlevel = false;
}
void FloodTransparencyValues(uint8_t floorID)
{
int yy = 16;
for (int j = 0; j < DMAXY; j++) {
int xx = 16;
for (int i = 0; i < DMAXX; i++) {
if (dungeon[i][j] == floorID && dTransVal[xx][yy] == 0) {
FindTransparencyValues(i, j, xx, yy, 0, floorID);
TransVal++;
}
xx += 2;
}
yy += 2;
}
}
} // namespace devilution