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.
607 lines
14 KiB
607 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; |
|
Point dminPosition; |
|
int dmaxx; |
|
int dmaxy; |
|
dungeon_type leveltype; |
|
BYTE currlevel; |
|
bool setlevel; |
|
_setlevels setlvlnum; |
|
dungeon_type setlvltype; |
|
Point ViewPosition; |
|
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 µs = 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
|
|
|