/** * @file gendung.h * * Interface of general dungeon generation code. */ #pragma once #include #include #include "engine.h" #include "engine/cel_sprite.hpp" #include "engine/point.hpp" #include "engine/rectangle.hpp" #include "engine/render/scrollrt.h" #include "utils/attributes.h" #include "utils/bitset2d.hpp" #include "utils/enum_traits.h" #include "utils/stdcompat/optional.hpp" namespace devilution { #define DMAXX 40 #define DMAXY 40 #define MAXDUNX (16 + DMAXX * 2 + 16) #define MAXDUNY (16 + DMAXY * 2 + 16) #define MAXTHEMES 50 #define MAXTILES 1379 enum _setlevels : int8_t { SL_NONE, SL_SKELKING, SL_BONECHAMB, SL_MAZE, SL_POISONWATER, SL_VILEBETRAYER, SL_LAST = SL_VILEBETRAYER, }; enum dungeon_type : int8_t { DTYPE_TOWN, DTYPE_CATHEDRAL, DTYPE_CATACOMBS, DTYPE_CAVES, DTYPE_HELL, DTYPE_NEST, DTYPE_CRYPT, DTYPE_LAST = DTYPE_CRYPT, DTYPE_NONE = -1, }; enum lvl_entry : uint8_t { ENTRY_MAIN, ENTRY_PREV, ENTRY_SETLVL, ENTRY_RTNLVL, ENTRY_LOAD, ENTRY_WARPLVL, ENTRY_TWARPDN, ENTRY_TWARPUP, }; enum class DungeonFlag : uint8_t { // clang-format off None = 0, // Only used by lighting/automap Missile = 1 << 0, Visible = 1 << 1, DeadPlayer = 1 << 2, Populated = 1 << 3, // 1 << 4 and 1 << 5 were used as workarounds for a bug with horizontal movement (relative to the screen) for monsters and players respectively Lit = 1 << 6, Explored = 1 << 7, SavedFlags = (Populated | Lit | Explored), // ~(Missile | Visible | DeadPlayer) LoadedFlags = (Missile | Visible | DeadPlayer | Populated | Lit | Explored) // clang-format on }; use_enum_as_flags(DungeonFlag); enum class TileProperties : uint8_t { // clang-format off None = 0, Solid = 1 << 0, BlockLight = 1 << 1, BlockMissile = 1 << 2, Transparent = 1 << 3, TransparentLeft = 1 << 4, TransparentRight = 1 << 5, Trap = 1 << 7, // clang-format on }; use_enum_as_flags(TileProperties); enum _difficulty : uint8_t { DIFF_NORMAL, DIFF_NIGHTMARE, DIFF_HELL, DIFF_LAST = DIFF_HELL, }; struct ScrollStruct { /** @brief Tile offset of camera. */ Point tile; /** @brief Pixel offset of camera. */ Displacement offset; /** @brief Move direction of camera. */ ScrollDirection _sdir; }; struct THEME_LOC { Rectangle room; int16_t ttval; }; struct MegaTile { uint16_t micro1; uint16_t micro2; uint16_t micro3; uint16_t micro4; }; struct MICROS { uint16_t mt[16]; }; struct ShadowStruct { uint8_t strig; uint8_t s1; uint8_t s2; uint8_t s3; uint8_t nv1; uint8_t nv2; uint8_t nv3; }; /** Contains the tile IDs of the map. */ extern DVL_API_FOR_TEST uint8_t dungeon[DMAXX][DMAXY]; /** Contains a backup of the tile IDs of the map. */ extern uint8_t pdungeon[DMAXX][DMAXY]; /** Tile that may not be overwritten by the level generator */ extern Bitset2d Protected; extern Rectangle SetPieceRoom; /** Specifies the active set quest piece in coordinate. */ extern Rectangle SetPiece; /** Contains the contents of the single player quest DUN file. */ extern std::unique_ptr pSetPiece; extern std::optional pSpecialCels; /** Specifies the tile definitions of the active dungeon type; (e.g. levels/l1data/l1.til). */ extern DVL_API_FOR_TEST std::unique_ptr pMegaTiles; extern std::unique_ptr pDungeonCels; /** * List tile properties */ extern DVL_API_FOR_TEST std::array SOLData; /** Specifies the minimum X,Y-coordinates of the map. */ extern Point dminPosition; /** Specifies the maximum X,Y-coordinates of the map. */ extern Point dmaxPosition; /** Specifies the active dungeon type of the current game. */ extern DVL_API_FOR_TEST dungeon_type leveltype; /** Specifies the active dungeon level of the current game. */ extern DVL_API_FOR_TEST uint8_t currlevel; extern bool setlevel; /** Specifies the active quest level of the current game. */ extern _setlevels setlvlnum; /** Specifies the player viewpoint X-coordinate of the map. */ extern dungeon_type setlvltype; /** Specifies the player viewpoint X,Y-coordinates of the map. */ extern DVL_API_FOR_TEST Point ViewPosition; extern ScrollStruct ScrollInfo; extern int MicroTileLen; extern char TransVal; /** Specifies the active transparency indices. */ extern bool TransList[256]; /** Contains the piece IDs of each tile on the map. */ extern DVL_API_FOR_TEST uint16_t dPiece[MAXDUNX][MAXDUNY]; /** Map of micros that comprises a full tile for any given dungeon piece. */ extern MICROS DPieceMicros[MAXTILES]; /** Specifies the transparency at each coordinate of the map. */ extern DVL_API_FOR_TEST int8_t dTransVal[MAXDUNX][MAXDUNY]; extern char dLight[MAXDUNX][MAXDUNY]; extern char dPreLight[MAXDUNX][MAXDUNY]; /** Holds various information about dungeon tiles, @see DungeonFlag */ extern DungeonFlag dFlags[MAXDUNX][MAXDUNY]; /** Contains the player numbers (players array indices) of the map. */ extern int8_t dPlayer[MAXDUNX][MAXDUNY]; /** * Contains the NPC numbers of the map. The NPC number represents a * towner number (towners array index) in Tristram and a monster number * (monsters array index) in the dungeon. */ extern int16_t dMonster[MAXDUNX][MAXDUNY]; /** * Contains the dead numbers (deads array indices) and dead direction of * the map, encoded as specified by the pseudo-code below. * dDead[x][y] & 0x1F - index of dead * dDead[x][y] >> 0x5 - direction */ extern DVL_API_FOR_TEST int8_t dCorpse[MAXDUNX][MAXDUNY]; /** Contains the object numbers (objects array indices) of the map. */ extern DVL_API_FOR_TEST int8_t dObject[MAXDUNX][MAXDUNY]; /** * Contains the arch frame numbers of the map from the special tileset * (e.g. "levels/l1data/l1s.cel"). Note, the special tileset of Tristram (i.e. * "levels/towndata/towns.cel") contains trees rather than arches. */ extern char dSpecial[MAXDUNX][MAXDUNY]; extern int themeCount; extern THEME_LOC themeLoc[MAXTHEMES]; dungeon_type GetLevelType(int level); void CreateDungeon(uint32_t rseed, lvl_entry entry); constexpr bool InDungeonBounds(Point position) { return position.x >= 0 && position.x < MAXDUNX && position.y >= 0 && position.y < MAXDUNY; } /** * @brief Checks if a given tile contains at least one missile * @param position Coordinates of the dungeon tile to check * @return true if a missile exists at this position */ constexpr bool TileContainsMissile(Point position) { return InDungeonBounds(position) && HasAnyOf(dFlags[position.x][position.y], DungeonFlag::Missile); } /** * @brief Checks if a given tile contains a player corpse * @param position Coordinates of the dungeon tile to check * @return true if a dead player exists at this position */ constexpr bool TileContainsDeadPlayer(Point position) { return InDungeonBounds(position) && HasAnyOf(dFlags[position.x][position.y], DungeonFlag::DeadPlayer); } /** * @brief Check if a given tile contains a decorative object (or similar non-pathable set piece) * * This appears to include stairs so that monsters do not spawn or path onto them, but players can path to them to navigate between layers * * @param position Coordinates of the dungeon tile to check * @return true if a set piece was spawned at this position */ constexpr bool TileContainsSetPiece(Point position) { return InDungeonBounds(position) && HasAnyOf(dFlags[position.x][position.y], DungeonFlag::Populated); } /** * @brief Checks if any player can currently see this tile * * Currently only used by monster AI routines so basic monsters out of sight can be ignored until they're likely to interact with the player * * @param position Coordinates of the dungeon tile to check * @return true if the tile is within at least one players vision */ constexpr bool IsTileVisible(Point position) { return InDungeonBounds(position) && HasAnyOf(dFlags[position.x][position.y], DungeonFlag::Visible); } /** * @brief Checks if a light source is illuminating this tile * @param position Coordinates of the dungeon tile to check * @return true if the tile is within the radius of at least one light source */ constexpr bool IsTileLit(Point position) { return InDungeonBounds(position) && HasAnyOf(dFlags[position.x][position.y], DungeonFlag::Lit); } struct Miniset { Size size; /* these are indexed as [y][x] */ uint8_t search[6][6]; uint8_t replace[6][6]; /** * @param respectProtected Match bug from Crypt levels */ bool matches(Point position, bool respectProtected = true) const { for (int yy = 0; yy < size.height; yy++) { for (int xx = 0; xx < size.width; xx++) { if (search[yy][xx] != 0 && dungeon[xx + position.x][yy + position.y] != search[yy][xx]) return false; if (respectProtected && Protected.test(xx + position.x, yy + position.y)) return false; } } return true; } void place(Point position, bool protect = false) const { for (int y = 0; y < size.height; y++) { for (int x = 0; x < size.width; x++) { if (replace[y][x] == 0) continue; dungeon[x + position.x][y + position.y] = replace[y][x]; if (protect) Protected.set(x + position.x, y + position.y); } } } }; bool TileHasAny(int tileId, TileProperties property); void LoadLevelSOLData(); void SetDungeonMicros(); void DRLG_InitTrans(); void DRLG_MRectTrans(Point origin, Point extent); void DRLG_MRectTrans(Rectangle area); void DRLG_RectTrans(Rectangle area); void DRLG_CopyTrans(int sx, int sy, int dx, int dy); void LoadTransparency(const uint16_t *dunData); void LoadDungeonBase(const char *path, Point spawn, int floorId, int dirtId); void Make_SetPC(Rectangle area); /** * @param tries Tiles to try, 1600 will scan the full map * @param drlg1Quirk Match buggy behaviour of Diablo's Cathedral */ std::optional PlaceMiniSet(const Miniset &miniset, int tries = 199, bool drlg1Quirk = false); void PlaceDunTiles(const uint16_t *dunData, Point position, int floorId = 0); void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize); void DRLG_HoldThemeRooms(); void SetSetPieceRoom(Point position, int floorId); void FreeQuestSetPieces(); void DRLG_LPass3(int lv); /** * @brief Checks if a theme room is located near the target point * @param position Target location in dungeon coordinates * @return True if a theme room is near (within 2 tiles of) this point, false if it is free. */ bool IsNearThemeRoom(Point position); void InitLevels(); void FloodTransparencyValues(uint8_t floorID); } // namespace devilution