/** * @file themes.cpp * * Implementation of the theme room placing algorithms. */ #include "themes.h" #include "engine/points_in_rectangle_range.hpp" #include "engine/random.hpp" #include "items.h" #include "monster.h" #include "objects.h" #include "path.h" #include "quests.h" #include "trigs.h" namespace devilution { int numthemes; bool armorFlag; bool weaponFlag; bool treasureFlag; bool mFountainFlag; bool cauldronFlag; bool tFountainFlag; int zharlib; int themex; int themey; int themeVar1; ThemeStruct themes[MAXTHEMES]; bool pFountainFlag; bool bFountainFlag; /** Specifies the set of special theme IDs from which one will be selected at random. */ theme_id ThemeGood[4] = { THEME_GOATSHRINE, THEME_SHRINE, THEME_SKELROOM, THEME_LIBRARY }; /** Specifies a 5x5 area to fit theme objects. */ int trm5x[] = { -2, -1, 0, 1, 2, -2, -1, 0, 1, 2, -2, -1, 0, 1, 2, -2, -1, 0, 1, 2, -2, -1, 0, 1, 2 }; /** Specifies a 5x5 area to fit theme objects. */ int trm5y[] = { -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; bool TFit_Shrine(int i) { int xp = 0; int yp = 0; int found = 0; while (found == 0) { Point testPosition { xp, yp }; if (dTransVal[xp][yp] == themes[i].ttval) { if (TileHasAny(dPiece[xp][yp - 1], TileProperties::Trap) && IsTileNotSolid(testPosition + Direction::NorthWest) && IsTileNotSolid(testPosition + Direction::SouthEast) && dTransVal[xp - 1][yp] == themes[i].ttval && dTransVal[xp + 1][yp] == themes[i].ttval && !IsObjectAtPosition(testPosition + Direction::North) && !IsObjectAtPosition(testPosition + Direction::East)) { found = 1; } if (found == 0 && TileHasAny(dPiece[xp - 1][yp], TileProperties::Trap) && IsTileNotSolid(testPosition + Direction::NorthEast) && IsTileNotSolid(testPosition + Direction::SouthWest) && dTransVal[xp][yp - 1] == themes[i].ttval && dTransVal[xp][yp + 1] == themes[i].ttval && !IsObjectAtPosition(testPosition + Direction::North) && !IsObjectAtPosition(testPosition + Direction::West)) { found = 2; } } if (found == 0) { xp++; if (xp == MAXDUNX) { xp = 0; yp++; if (yp == MAXDUNY) return false; } } } themex = xp; themey = yp; themeVar1 = found; return true; } bool TFit_Obj5(int t) { int xp = 0; int yp = 0; int r = GenerateRnd(5) + 1; int rs = r; while (r > 0) { bool found = false; if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { found = true; for (int i = 0; found && i < 25; i++) { if (TileHasAny(dPiece[xp + trm5x[i]][yp + trm5y[i]], TileProperties::Solid)) { found = false; } if (dTransVal[xp + trm5x[i]][yp + trm5y[i]] != themes[t].ttval) { found = false; } } } if (!found) { xp++; if (xp == MAXDUNX) { xp = 0; yp++; if (yp == MAXDUNY) { if (r == rs) { return false; } yp = 0; } } continue; } r--; } themex = xp; themey = yp; return true; } bool TFit_SkelRoom(int t) { if (IsNoneOf(leveltype, DTYPE_CATHEDRAL, DTYPE_CATACOMBS)) { return false; } for (int i = 0; i < LevelMonsterTypeCount; i++) { if (IsSkel(LevelMonsterTypes[i].mtype)) { themeVar1 = i; return TFit_Obj5(t); } } return false; } bool TFit_GoatShrine(int t) { for (int i = 0; i < LevelMonsterTypeCount; i++) { if (IsGoat(LevelMonsterTypes[i].mtype)) { themeVar1 = i; return TFit_Obj5(t); } } return false; } bool CheckThemeObj3(Point origin, int8_t regionId, int frequency) { const PointsInRectangleRange searchArea { Rectangle { origin, 1 } }; return std::all_of(searchArea.cbegin(), searchArea.cend(), [regionId, frequency](Point testPosition) { if (!InDungeonBounds(testPosition)) { return false; } if (IsTileSolid(testPosition)) { return false; } // If the theme object would extend into a different region then it doesn't fit. if (dTransVal[testPosition.x][testPosition.y] != regionId) { return false; } if (IsObjectAtPosition(testPosition)) { return false; } if (frequency != -1 && GenerateRnd(frequency) == 0) { return false; } return true; }); } bool TFit_Obj3(int8_t regionId) { int objrnd[4] = { 4, 4, 3, 5 }; for (int yp = 1; yp < MAXDUNY - 1; yp++) { for (int xp = 1; xp < MAXDUNX - 1; xp++) { if (CheckThemeObj3({ xp, yp }, regionId, objrnd[leveltype - 1])) { themex = xp; themey = yp; return true; } } } return false; } bool CheckThemeReqs(theme_id t) { switch (t) { case THEME_SHRINE: case THEME_SKELROOM: case THEME_LIBRARY: if (leveltype == DTYPE_CAVES || leveltype == DTYPE_HELL) { return false; } break; case THEME_BLOODFOUNTAIN: if (!bFountainFlag) { return false; } break; case THEME_PURIFYINGFOUNTAIN: if (!pFountainFlag) { return false; } break; case THEME_ARMORSTAND: if (leveltype == DTYPE_CATHEDRAL) { return false; } break; case THEME_CAULDRON: if (leveltype != DTYPE_HELL || !cauldronFlag) { return false; } break; case THEME_MURKYFOUNTAIN: if (!mFountainFlag) { return false; } break; case THEME_TEARFOUNTAIN: if (!tFountainFlag) { return false; } break; case THEME_WEAPONRACK: if (leveltype == DTYPE_CATHEDRAL) { return false; } break; default: break; } return true; } static bool SpecialThemeFit(int i, theme_id t) { bool rv; rv = CheckThemeReqs(t); switch (t) { case THEME_SHRINE: case THEME_LIBRARY: if (rv) { rv = TFit_Shrine(i); } break; case THEME_SKELROOM: if (rv) { rv = TFit_SkelRoom(i); } break; case THEME_BLOODFOUNTAIN: if (rv) { rv = TFit_Obj5(i); } if (rv) { bFountainFlag = false; } break; case THEME_PURIFYINGFOUNTAIN: if (rv) { rv = TFit_Obj5(i); } if (rv) { pFountainFlag = false; } break; case THEME_MURKYFOUNTAIN: if (rv) { rv = TFit_Obj5(i); } if (rv) { mFountainFlag = false; } break; case THEME_TEARFOUNTAIN: if (rv) { rv = TFit_Obj5(i); } if (rv) { tFountainFlag = false; } break; case THEME_CAULDRON: if (rv) { rv = TFit_Obj5(i); } if (rv) { cauldronFlag = false; } break; case THEME_GOATSHRINE: if (rv) { rv = TFit_GoatShrine(i); } break; case THEME_TORTURE: case THEME_DECAPITATED: case THEME_ARMORSTAND: case THEME_BRNCROSS: case THEME_WEAPONRACK: if (rv) { rv = TFit_Obj3(themes[i].ttval); } break; case THEME_TREASURE: rv = treasureFlag; if (rv) { treasureFlag = false; } break; default: break; } return rv; } bool CheckThemeRoom(int tv) { for (int i = 0; i < numtrigs; i++) { if (dTransVal[trigs[i].position.x][trigs[i].position.y] == tv) return false; } int tarea = 0; for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) { if (dTransVal[i][j] != tv) continue; if (TileContainsSetPiece({ i, j })) return false; tarea++; } } if (leveltype == DTYPE_CATHEDRAL && (tarea < 9 || tarea > 100)) return false; for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) { if (dTransVal[i][j] != tv || TileHasAny(dPiece[i][j], TileProperties::Solid)) continue; if (dTransVal[i - 1][j] != tv && IsTileNotSolid({ i - 1, j })) return false; if (dTransVal[i + 1][j] != tv && IsTileNotSolid({ i + 1, j })) return false; if (dTransVal[i][j - 1] != tv && IsTileNotSolid({ i, j - 1 })) return false; if (dTransVal[i][j + 1] != tv && IsTileNotSolid({ i, j + 1 })) return false; } } return true; } void InitThemes() { zharlib = -1; numthemes = 0; armorFlag = true; bFountainFlag = true; cauldronFlag = true; mFountainFlag = true; pFountainFlag = true; tFountainFlag = true; treasureFlag = true; weaponFlag = true; if (currlevel == 16 || IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) { return; } if (leveltype == DTYPE_CATHEDRAL) { for (size_t i = 0; i < 256 && numthemes < MAXTHEMES; i++) { if (CheckThemeRoom(i)) { themes[numthemes].ttval = i; theme_id j = ThemeGood[GenerateRnd(4)]; while (!SpecialThemeFit(numthemes, j)) { j = (theme_id)GenerateRnd(17); } themes[numthemes].ttype = j; numthemes++; } } return; } for (int i = 0; i < themeCount; i++) { themes[i].ttype = THEME_NONE; } if (Quests[Q_ZHAR].IsAvailable()) { for (int j = 0; j < themeCount; j++) { themes[j].ttval = themeLoc[j].ttval; if (SpecialThemeFit(j, THEME_LIBRARY)) { themes[j].ttype = THEME_LIBRARY; zharlib = j; break; } } } for (int i = 0; i < themeCount; i++) { if (themes[i].ttype == THEME_NONE) { themes[i].ttval = themeLoc[i].ttval; theme_id j = ThemeGood[GenerateRnd(4)]; while (!SpecialThemeFit(i, j)) { j = (theme_id)GenerateRnd(17); } themes[i].ttype = j; } } numthemes += themeCount; } void HoldThemeRooms() { if (currlevel == 16 || IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) { return; } if (leveltype != DTYPE_CATHEDRAL) { DRLG_HoldThemeRooms(); return; } for (int i = 0; i < numthemes; i++) { int8_t v = themes[i].ttval; for (int y = 0; y < MAXDUNY; y++) { for (int x = 0; x < MAXDUNX; x++) { if (dTransVal[x][y] == v) { dFlags[x][y] |= DungeonFlag::Populated; } } } } } /** * PlaceThemeMonsts places theme monsters with the specified frequency. * * @param t theme number (index into themes array). * @param f frequency (1/f likelihood of adding monster). */ void PlaceThemeMonsts(int t, int f) { int scattertypes[138]; int numscattypes = 0; for (int i = 0; i < LevelMonsterTypeCount; i++) { if ((LevelMonsterTypes[i].mPlaceFlags & PLACE_SCATTER) != 0) { scattertypes[numscattypes] = i; numscattypes++; } } int mtype = scattertypes[GenerateRnd(numscattypes)]; for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp }) && dItem[xp][yp] == 0 && !IsObjectAtPosition({ xp, yp })) { if (GenerateRnd(f) == 0) { AddMonster({ xp, yp }, static_cast(GenerateRnd(8)), mtype, true); } } } } } /** * Theme_Barrel initializes the barrel theme. * * @param t theme number (index into themes array). */ void Theme_Barrel(int t) { int barrnd[4] = { 2, 6, 4, 8 }; int monstrnd[4] = { 5, 7, 3, 9 }; for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { if (GenerateRnd(barrnd[leveltype - 1]) == 0) { _object_id r = OBJ_BARREL; if (GenerateRnd(barrnd[leveltype - 1]) != 0) { r = OBJ_BARRELEX; } AddObject(r, { xp, yp }); } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_Shrine initializes the shrine theme. * * @param t theme number (index into themes array). */ void Theme_Shrine(int t) { int monstrnd[4] = { 6, 6, 3, 9 }; TFit_Shrine(t); if (themeVar1 == 1) { AddObject(OBJ_CANDLE2, { themex - 1, themey }); AddObject(OBJ_SHRINER, { themex, themey }); AddObject(OBJ_CANDLE2, { themex + 1, themey }); } else { AddObject(OBJ_CANDLE2, { themex, themey - 1 }); AddObject(OBJ_SHRINEL, { themex, themey }); AddObject(OBJ_CANDLE2, { themex, themey + 1 }); } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_MonstPit initializes the monster pit theme. * * @param t theme number (index into themes array). */ void Theme_MonstPit(int t) { int monstrnd[4] = { 6, 7, 3, 9 }; int r = GenerateRnd(100) + 1; int ixp = 0; int iyp = 0; while (r > 0) { if (dTransVal[ixp][iyp] == themes[t].ttval && IsTileNotSolid({ ixp, iyp })) { --r; } if (r <= 0) continue; ixp++; if (ixp == MAXDUNX) { ixp = 0; iyp++; if (iyp == MAXDUNY) { iyp = 0; } } } CreateRndItem({ ixp, iyp }, true, false, true); ItemNoFlippy(); PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_SkelRoom initializes the skeleton room theme. * * @param t theme number (index into themes array). */ void Theme_SkelRoom(int t) { int monstrnd[4] = { 6, 7, 3, 9 }; TFit_SkelRoom(t); int xp = themex; int yp = themey; AddObject(OBJ_SKFIRE, { xp, yp }); if (GenerateRnd(monstrnd[leveltype - 1]) != 0) { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp - 1, yp - 1 }); } else { AddObject(OBJ_BANNERL, { xp - 1, yp - 1 }); } { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp, yp - 1 }); } if (GenerateRnd(monstrnd[leveltype - 1]) != 0) { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp + 1, yp - 1 }); } else { AddObject(OBJ_BANNERR, { xp + 1, yp - 1 }); } if (GenerateRnd(monstrnd[leveltype - 1]) != 0) { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp - 1, yp }); } else { AddObject(OBJ_BANNERM, { xp - 1, yp }); } if (GenerateRnd(monstrnd[leveltype - 1]) != 0) { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp + 1, yp }); } else { AddObject(OBJ_BANNERM, { xp + 1, yp }); } if (GenerateRnd(monstrnd[leveltype - 1]) != 0) { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp - 1, yp + 1 }); } else { AddObject(OBJ_BANNERR, { xp - 1, yp + 1 }); } { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp, yp + 1 }); } if (GenerateRnd(monstrnd[leveltype - 1]) != 0) { int i = PreSpawnSkeleton(); SpawnSkeleton(i, { xp + 1, yp + 1 }); } else { AddObject(OBJ_BANNERL, { xp + 1, yp + 1 }); } if (!IsObjectAtPosition({ xp, yp - 3 })) { AddObject(OBJ_SKELBOOK, { xp, yp - 2 }); } if (!IsObjectAtPosition({ xp, yp + 3 })) { AddObject(OBJ_SKELBOOK, { xp, yp + 2 }); } } /** * Theme_Treasure initializes the treasure theme. * * @param t theme number (index into themes array). */ void Theme_Treasure(int t) { int treasrnd[4] = { 4, 9, 7, 10 }; int monstrnd[4] = { 6, 8, 3, 7 }; AdvanceRndSeed(); for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { int8_t treasureType = treasrnd[leveltype - 1]; int rv = GenerateRnd(treasureType); // BUGFIX: the `2*` in `2*GenerateRnd(treasrnd...) == 0` has no effect, should probably be `GenerateRnd(2*treasrnd...) == 0` if ((2 * GenerateRnd(treasureType)) == 0) { CreateTypeItem({ xp, yp }, false, ItemType::Gold, IMISC_NONE, false, true); ItemNoFlippy(); } if (rv == 0) { CreateRndItem({ xp, yp }, false, false, true); ItemNoFlippy(); } if (rv >= treasureType - 2 && leveltype != DTYPE_CATHEDRAL) { Item &item = Items[ActiveItems[ActiveItemCount - 1]]; if (item.IDidx == IDI_GOLD) { item._ivalue = std::max(item._ivalue / 2, 1); } } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_Library initializes the library theme. * * @param t theme number (index into themes array). */ void Theme_Library(int t) { int librnd[4] = { 1, 2, 2, 5 }; int monstrnd[4] = { 5, 7, 3, 9 }; TFit_Shrine(t); if (themeVar1 == 1) { AddObject(OBJ_BOOKCANDLE, { themex - 1, themey }); AddObject(OBJ_BOOKCASER, { themex, themey }); AddObject(OBJ_BOOKCANDLE, { themex + 1, themey }); } else { AddObject(OBJ_BOOKCANDLE, { themex, themey - 1 }); AddObject(OBJ_BOOKCASEL, { themex, themey }); AddObject(OBJ_BOOKCANDLE, { themex, themey + 1 }); } for (int yp = 1; yp < MAXDUNY - 1; yp++) { for (int xp = 1; xp < MAXDUNX - 1; xp++) { if (CheckThemeObj3({ xp, yp }, themes[t].ttval, -1) && dMonster[xp][yp] == 0 && GenerateRnd(librnd[leveltype - 1]) == 0) { AddObject(OBJ_BOOKSTAND, { xp, yp }); if (GenerateRnd(2 * librnd[leveltype - 1]) != 0) { Object *bookstand = ObjectAtPosition({ xp, yp }); if (bookstand != nullptr) { bookstand->_oSelFlag = 0; bookstand->_oAnimFrame += 2; } } } } } if (Quests[Q_ZHAR].IsAvailable() && t == zharlib) { return; } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_Torture initializes the torture theme. * * @param t theme number (index into themes array). */ void Theme_Torture(int t) { int tortrnd[4] = { 6, 8, 3, 8 }; int monstrnd[4] = { 6, 8, 3, 9 }; for (int yp = 1; yp < MAXDUNY - 1; yp++) { for (int xp = 1; xp < MAXDUNX - 1; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { if (CheckThemeObj3({ xp, yp }, themes[t].ttval, -1)) { if (GenerateRnd(tortrnd[leveltype - 1]) == 0) { AddObject(OBJ_TNUDEM2, { xp, yp }); } } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_BloodFountain initializes the blood fountain theme. * @param t Theme number (index into themes array). */ void Theme_BloodFountain(int t) { int monstrnd[4] = { 6, 8, 3, 9 }; TFit_Obj5(t); AddObject(OBJ_BLOODFTN, { themex, themey }); PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_Decap initializes the decapitated theme. * * @param t theme number (index into themes array). */ void Theme_Decap(int t) { int decaprnd[4] = { 6, 8, 3, 8 }; int monstrnd[4] = { 6, 8, 3, 9 }; for (int yp = 1; yp < MAXDUNY - 1; yp++) { for (int xp = 1; xp < MAXDUNX - 1; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { if (CheckThemeObj3({ xp, yp }, themes[t].ttval, -1)) { if (GenerateRnd(decaprnd[leveltype - 1]) == 0) { AddObject(OBJ_DECAP, { xp, yp }); } } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_PurifyingFountain initializes the purifying fountain theme. * * @param t theme number (index into themes array). */ void Theme_PurifyingFountain(int t) { int monstrnd[4] = { 6, 7, 3, 9 }; TFit_Obj5(t); AddObject(OBJ_PURIFYINGFTN, { themex, themey }); PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_ArmorStand initializes the armor stand theme. * * @param t theme number (index into themes array). */ void Theme_ArmorStand(int t) { int armorrnd[4] = { 6, 8, 3, 8 }; int monstrnd[4] = { 6, 7, 3, 9 }; if (armorFlag) { TFit_Obj3(themes[t].ttval); AddObject(OBJ_ARMORSTAND, { themex, themey }); } for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == themes[t].ttval && IsTileNotSolid({ xp, yp })) { if (CheckThemeObj3({ xp, yp }, themes[t].ttval, -1)) { if (GenerateRnd(armorrnd[leveltype - 1]) == 0) { AddObject(OBJ_ARMORSTANDN, { xp, yp }); } } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); armorFlag = false; } /** * Theme_GoatShrine initializes the goat shrine theme. * * @param t theme number (index into themes array). */ void Theme_GoatShrine(int t) { TFit_GoatShrine(t); AddObject(OBJ_GOATSHRINE, { themex, themey }); for (int yy = themey - 1; yy <= themey + 1; yy++) { for (int xx = themex - 1; xx <= themex + 1; xx++) { if (dTransVal[xx][yy] == themes[t].ttval && IsTileNotSolid({ xx, yy }) && (xx != themex || yy != themey)) { AddMonster({ xx, yy }, Direction::SouthWest, themeVar1, true); } } } } /** * Theme_Cauldron initializes the cauldron theme. * * @param t theme number (index into themes array). */ void Theme_Cauldron(int t) { int monstrnd[4] = { 6, 7, 3, 9 }; TFit_Obj5(t); AddObject(OBJ_CAULDRON, { themex, themey }); PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_MurkyFountain initializes the murky fountain theme. * * @param t theme number (index into themes array). */ void Theme_MurkyFountain(int t) { int monstrnd[4] = { 6, 7, 3, 9 }; TFit_Obj5(t); AddObject(OBJ_MURKYFTN, { themex, themey }); PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_TearFountain initializes the tear fountain theme. * * @param t theme number (index into themes array). */ void Theme_TearFountain(int t) { int monstrnd[4] = { 6, 7, 3, 9 }; TFit_Obj5(t); AddObject(OBJ_TEARFTN, { themex, themey }); PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_BrnCross initializes the burning cross theme. * * @param t theme number (index into themes array). */ void Theme_BrnCross(int t) { int8_t regionId = themes[t].ttval; int monstrnd[4] = { 6, 8, 3, 9 }; int bcrossrnd[4] = { 5, 7, 3, 8 }; for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == regionId && IsTileNotSolid({ xp, yp })) { if (CheckThemeObj3({ xp, yp }, regionId, -1)) { if (GenerateRnd(bcrossrnd[leveltype - 1]) == 0) { AddObject(OBJ_TBCROSS, { xp, yp }); } } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); } /** * Theme_WeaponRack initializes the weapon rack theme. * * @param t theme number (index into themes array). */ void Theme_WeaponRack(int t) { int8_t regionId = themes[t].ttval; int weaponrnd[4] = { 6, 8, 5, 8 }; int monstrnd[4] = { 6, 7, 3, 9 }; if (weaponFlag) { TFit_Obj3(regionId); AddObject(OBJ_WEAPONRACK, { themex, themey }); } for (int yp = 0; yp < MAXDUNY; yp++) { for (int xp = 0; xp < MAXDUNX; xp++) { if (dTransVal[xp][yp] == regionId && IsTileNotSolid({ xp, yp })) { if (CheckThemeObj3({ xp, yp }, regionId, -1)) { if (GenerateRnd(weaponrnd[leveltype - 1]) == 0) { AddObject(OBJ_WEAPONRACKN, { xp, yp }); } } } } } PlaceThemeMonsts(t, monstrnd[leveltype - 1]); weaponFlag = false; } /** * UpdateL4Trans sets each value of the transparency map to 1. */ void UpdateL4Trans() { for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) { // NOLINT(modernize-loop-convert) if (dTransVal[i][j] != 0) { dTransVal[i][j] = 1; } } } } void CreateThemeRooms() { if (currlevel == 16 || IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) { return; } ApplyObjectLighting = true; for (int i = 0; i < numthemes; i++) { themex = 0; themey = 0; switch (themes[i].ttype) { case THEME_BARREL: Theme_Barrel(i); break; case THEME_SHRINE: Theme_Shrine(i); break; case THEME_MONSTPIT: Theme_MonstPit(i); break; case THEME_SKELROOM: Theme_SkelRoom(i); break; case THEME_TREASURE: Theme_Treasure(i); break; case THEME_LIBRARY: Theme_Library(i); break; case THEME_TORTURE: Theme_Torture(i); break; case THEME_BLOODFOUNTAIN: Theme_BloodFountain(i); break; case THEME_DECAPITATED: Theme_Decap(i); break; case THEME_PURIFYINGFOUNTAIN: Theme_PurifyingFountain(i); break; case THEME_ARMORSTAND: Theme_ArmorStand(i); break; case THEME_GOATSHRINE: Theme_GoatShrine(i); break; case THEME_CAULDRON: Theme_Cauldron(i); break; case THEME_MURKYFOUNTAIN: Theme_MurkyFountain(i); break; case THEME_TEARFOUNTAIN: Theme_TearFountain(i); break; case THEME_BRNCROSS: Theme_BrnCross(i); break; case THEME_WEAPONRACK: Theme_WeaponRack(i); break; case THEME_NONE: app_fatal("Unknown theme type: %i", themes[i].ttype); } } ApplyObjectLighting = false; if (leveltype == DTYPE_HELL && themeCount > 0) { UpdateL4Trans(); } } } // namespace devilution