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.

375 lines
12 KiB

/**
* @file gendung.h
*
* Interface of general dungeon generation code.
*/
#pragma once
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <SDL_endian.h>
#include <expected.hpp>
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/scrollrt.h"
#include "engine/world_tile.hpp"
#include "levels/dun_tile.hpp"
#include "utils/attributes.h"
#include "utils/bitset2d.hpp"
#include "utils/enum_traits.h"
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_ARENA_CHURCH,
SL_ARENA_HELL,
SL_ARENA_CIRCLE_OF_LIFE,
SL_FIRST_ARENA = SL_ARENA_CHURCH,
SL_LAST = SL_ARENA_CIRCLE_OF_LIFE,
};
inline bool IsArenaLevel(_setlevels setLevel)
{
switch (setLevel) {
case SL_ARENA_CHURCH:
case SL_ARENA_HELL:
case SL_ARENA_CIRCLE_OF_LIFE:
return true;
default:
return false;
}
}
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,
};
tl::expected<dungeon_type, std::string> ParseDungeonType(std::string_view value);
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,
MissileFireWall = 1 << 4,
MissileLightningWall = 1 << 5,
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 _difficulty : uint8_t {
DIFF_NORMAL,
DIFF_NIGHTMARE,
DIFF_HELL,
DIFF_LAST = DIFF_HELL,
};
struct THEME_LOC {
RectangleOf<uint8_t> room;
int8_t ttval;
};
struct MegaTile {
uint16_t micro1;
uint16_t micro2;
uint16_t micro3;
uint16_t micro4;
};
struct ShadowStruct {
uint8_t strig;
uint8_t s1;
uint8_t s2;
uint8_t s3;
uint8_t nv1;
uint8_t nv2;
uint8_t nv3;
};
/** Reprecents what tiles are being utilized in the generated map. */
extern Bitset2d<DMAXX, DMAXY> DungeonMask;
/** 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<DMAXX, DMAXY> Protected;
extern WorldTileRectangle SetPieceRoom;
/** Specifies the active set quest piece in coordinate. */
extern WorldTileRectangle SetPiece;
extern OptionalOwnedClxSpriteList pSpecialCels;
/** Specifies the tile definitions of the active dungeon type; (e.g. levels/l1data/l1.til). */
extern DVL_API_FOR_TEST std::unique_ptr<MegaTile[]> pMegaTiles;
Add dun_render_benchmark Results from a single run (a bit noisy) on my machine: ``` tools/build_and_run_benchmark.py dun_render_benchmark ``` ``` ------------------------------------------------------------------------------------------------------------------------ Benchmark Time CPU Iterations UserCounters... ------------------------------------------------------------------------------------------------------------------------ DunRenderBenchmark/LeftTriangle_Solid_FullyLit 98297 ns 98282 ns 8840 items_per_second=15.1096M/s DunRenderBenchmark/LeftTriangle_Solid_FullyDark 124727 ns 124701 ns 6973 items_per_second=11.9085M/s DunRenderBenchmark/LeftTriangle_Solid_PartiallyLit 514869 ns 514747 ns 1700 items_per_second=2.88491M/s DunRenderBenchmark/LeftTriangle_Transparent_FullyLit 520312 ns 520216 ns 1682 items_per_second=2.85458M/s DunRenderBenchmark/LeftTriangle_Transparent_FullyDark 524440 ns 524331 ns 1664 items_per_second=2.83218M/s DunRenderBenchmark/LeftTriangle_Transparent_PartiallyLit 532300 ns 532162 ns 1647 items_per_second=2.7905M/s DunRenderBenchmark/RightTriangle_Solid_FullyLit 92387 ns 92363 ns 8840 items_per_second=16.7275M/s DunRenderBenchmark/RightTriangle_Solid_FullyDark 85680 ns 85662 ns 9884 items_per_second=18.0361M/s DunRenderBenchmark/RightTriangle_Solid_PartiallyLit 538347 ns 538250 ns 1626 items_per_second=2.87041M/s DunRenderBenchmark/RightTriangle_Transparent_FullyLit 548800 ns 548760 ns 1598 items_per_second=2.81544M/s DunRenderBenchmark/RightTriangle_Transparent_FullyDark 540450 ns 540369 ns 1620 items_per_second=2.85916M/s DunRenderBenchmark/RightTriangle_Transparent_PartiallyLit 555061 ns 555003 ns 1575 items_per_second=2.78377M/s DunRenderBenchmark/TransparentSquare_Solid_FullyLit 700849 ns 700751 ns 1320 items_per_second=3.68176M/s DunRenderBenchmark/TransparentSquare_Solid_FullyDark 664927 ns 664872 ns 1389 items_per_second=3.88045M/s DunRenderBenchmark/TransparentSquare_Solid_PartiallyLit 1131702 ns 1131559 ns 822 items_per_second=2.28004M/s DunRenderBenchmark/TransparentSquare_Transparent_FullyLit 1022384 ns 1022267 ns 916 items_per_second=2.5238M/s DunRenderBenchmark/TransparentSquare_Transparent_FullyDark 1023193 ns 1023057 ns 900 items_per_second=2.52185M/s DunRenderBenchmark/TransparentSquare_Transparent_PartiallyLit 1033573 ns 1033496 ns 895 items_per_second=2.49638M/s DunRenderBenchmark/Square_Solid_FullyLit 53532 ns 53524 ns 10000 items_per_second=30.8272M/s DunRenderBenchmark/Square_Solid_FullyDark 41993 ns 41987 ns 19794 items_per_second=47.1573M/s DunRenderBenchmark/Square_Solid_PartiallyLit 842772 ns 842615 ns 1108 items_per_second=1.56655M/s DunRenderBenchmark/Square_Transparent_FullyLit 834105 ns 834026 ns 1119 items_per_second=1.58269M/s DunRenderBenchmark/Square_Transparent_FullyDark 831912 ns 831823 ns 1122 items_per_second=1.58688M/s DunRenderBenchmark/Square_Transparent_PartiallyLit 924638 ns 924536 ns 1010 items_per_second=1.42774M/s DunRenderBenchmark/LeftTrapezoid_Solid_FullyLit 33728 ns 33725 ns 24962 items_per_second=18.8583M/s DunRenderBenchmark/LeftTrapezoid_Solid_FullyDark 31088 ns 31085 ns 27444 items_per_second=20.4601M/s DunRenderBenchmark/LeftTrapezoid_Solid_PartiallyLit 268792 ns 268768 ns 3254 items_per_second=1.97196M/s DunRenderBenchmark/LeftTrapezoid_Transparent_FullyLit 277990 ns 277965 ns 3140 items_per_second=1.90672M/s DunRenderBenchmark/LeftTrapezoid_Transparent_FullyDark 268952 ns 268912 ns 3250 items_per_second=1.9709M/s DunRenderBenchmark/LeftTrapezoid_Transparent_PartiallyLit 288869 ns 288826 ns 3056 items_per_second=1.83501M/s DunRenderBenchmark/RightTrapezoid_Solid_FullyLit 29581 ns 29576 ns 28347 items_per_second=21.0984M/s DunRenderBenchmark/RightTrapezoid_Solid_FullyDark 25315 ns 25312 ns 32250 items_per_second=24.6523M/s DunRenderBenchmark/RightTrapezoid_Solid_PartiallyLit 259977 ns 259960 ns 3312 items_per_second=2.0003M/s DunRenderBenchmark/RightTrapezoid_Transparent_FullyLit 263079 ns 263056 ns 3322 items_per_second=1.97677M/s DunRenderBenchmark/RightTrapezoid_Transparent_FullyDark 259849 ns 259824 ns 3364 items_per_second=2.00136M/s DunRenderBenchmark/RightTrapezoid_Transparent_PartiallyLit 279623 ns 279594 ns 3127 items_per_second=1.85984M/s ```
2 years ago
extern DVL_API_FOR_TEST std::unique_ptr<std::byte[]> pDungeonCels;
/**
* List tile properties
*/
extern DVL_API_FOR_TEST TileProperties SOLData[MAXTILES];
/** Specifies the minimum X,Y-coordinates of the map. */
extern WorldTilePosition dminPosition;
/** Specifies the maximum X,Y-coordinates of the map. */
extern WorldTilePosition 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 uint_fast8_t MicroTileLen;
extern int8_t TransVal;
/** Specifies the active transparency indices. */
extern std::array<bool, 256> TransList;
/** 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. */
Add dun_render_benchmark Results from a single run (a bit noisy) on my machine: ``` tools/build_and_run_benchmark.py dun_render_benchmark ``` ``` ------------------------------------------------------------------------------------------------------------------------ Benchmark Time CPU Iterations UserCounters... ------------------------------------------------------------------------------------------------------------------------ DunRenderBenchmark/LeftTriangle_Solid_FullyLit 98297 ns 98282 ns 8840 items_per_second=15.1096M/s DunRenderBenchmark/LeftTriangle_Solid_FullyDark 124727 ns 124701 ns 6973 items_per_second=11.9085M/s DunRenderBenchmark/LeftTriangle_Solid_PartiallyLit 514869 ns 514747 ns 1700 items_per_second=2.88491M/s DunRenderBenchmark/LeftTriangle_Transparent_FullyLit 520312 ns 520216 ns 1682 items_per_second=2.85458M/s DunRenderBenchmark/LeftTriangle_Transparent_FullyDark 524440 ns 524331 ns 1664 items_per_second=2.83218M/s DunRenderBenchmark/LeftTriangle_Transparent_PartiallyLit 532300 ns 532162 ns 1647 items_per_second=2.7905M/s DunRenderBenchmark/RightTriangle_Solid_FullyLit 92387 ns 92363 ns 8840 items_per_second=16.7275M/s DunRenderBenchmark/RightTriangle_Solid_FullyDark 85680 ns 85662 ns 9884 items_per_second=18.0361M/s DunRenderBenchmark/RightTriangle_Solid_PartiallyLit 538347 ns 538250 ns 1626 items_per_second=2.87041M/s DunRenderBenchmark/RightTriangle_Transparent_FullyLit 548800 ns 548760 ns 1598 items_per_second=2.81544M/s DunRenderBenchmark/RightTriangle_Transparent_FullyDark 540450 ns 540369 ns 1620 items_per_second=2.85916M/s DunRenderBenchmark/RightTriangle_Transparent_PartiallyLit 555061 ns 555003 ns 1575 items_per_second=2.78377M/s DunRenderBenchmark/TransparentSquare_Solid_FullyLit 700849 ns 700751 ns 1320 items_per_second=3.68176M/s DunRenderBenchmark/TransparentSquare_Solid_FullyDark 664927 ns 664872 ns 1389 items_per_second=3.88045M/s DunRenderBenchmark/TransparentSquare_Solid_PartiallyLit 1131702 ns 1131559 ns 822 items_per_second=2.28004M/s DunRenderBenchmark/TransparentSquare_Transparent_FullyLit 1022384 ns 1022267 ns 916 items_per_second=2.5238M/s DunRenderBenchmark/TransparentSquare_Transparent_FullyDark 1023193 ns 1023057 ns 900 items_per_second=2.52185M/s DunRenderBenchmark/TransparentSquare_Transparent_PartiallyLit 1033573 ns 1033496 ns 895 items_per_second=2.49638M/s DunRenderBenchmark/Square_Solid_FullyLit 53532 ns 53524 ns 10000 items_per_second=30.8272M/s DunRenderBenchmark/Square_Solid_FullyDark 41993 ns 41987 ns 19794 items_per_second=47.1573M/s DunRenderBenchmark/Square_Solid_PartiallyLit 842772 ns 842615 ns 1108 items_per_second=1.56655M/s DunRenderBenchmark/Square_Transparent_FullyLit 834105 ns 834026 ns 1119 items_per_second=1.58269M/s DunRenderBenchmark/Square_Transparent_FullyDark 831912 ns 831823 ns 1122 items_per_second=1.58688M/s DunRenderBenchmark/Square_Transparent_PartiallyLit 924638 ns 924536 ns 1010 items_per_second=1.42774M/s DunRenderBenchmark/LeftTrapezoid_Solid_FullyLit 33728 ns 33725 ns 24962 items_per_second=18.8583M/s DunRenderBenchmark/LeftTrapezoid_Solid_FullyDark 31088 ns 31085 ns 27444 items_per_second=20.4601M/s DunRenderBenchmark/LeftTrapezoid_Solid_PartiallyLit 268792 ns 268768 ns 3254 items_per_second=1.97196M/s DunRenderBenchmark/LeftTrapezoid_Transparent_FullyLit 277990 ns 277965 ns 3140 items_per_second=1.90672M/s DunRenderBenchmark/LeftTrapezoid_Transparent_FullyDark 268952 ns 268912 ns 3250 items_per_second=1.9709M/s DunRenderBenchmark/LeftTrapezoid_Transparent_PartiallyLit 288869 ns 288826 ns 3056 items_per_second=1.83501M/s DunRenderBenchmark/RightTrapezoid_Solid_FullyLit 29581 ns 29576 ns 28347 items_per_second=21.0984M/s DunRenderBenchmark/RightTrapezoid_Solid_FullyDark 25315 ns 25312 ns 32250 items_per_second=24.6523M/s DunRenderBenchmark/RightTrapezoid_Solid_PartiallyLit 259977 ns 259960 ns 3312 items_per_second=2.0003M/s DunRenderBenchmark/RightTrapezoid_Transparent_FullyLit 263079 ns 263056 ns 3322 items_per_second=1.97677M/s DunRenderBenchmark/RightTrapezoid_Transparent_FullyDark 259849 ns 259824 ns 3364 items_per_second=2.00136M/s DunRenderBenchmark/RightTrapezoid_Transparent_PartiallyLit 279623 ns 279594 ns 3127 items_per_second=1.85984M/s ```
2 years ago
extern DVL_API_FOR_TEST MICROS DPieceMicros[MAXTILES];
/** Specifies the transparency at each coordinate of the map. */
extern DVL_API_FOR_TEST int8_t dTransVal[MAXDUNX][MAXDUNY];
/** Current realtime lighting. Per tile. */
extern uint8_t dLight[MAXDUNX][MAXDUNY];
/** Precalculated static lights. dLight uses this as a base before applying lights. Per tile. */
extern uint8_t 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. negative id indicates player moving. */
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.
* Negative id indicates monsters moving.
*/
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.
* Large objects have negative id for their extended area.
*/
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"). Note, the special tileset of Tristram (i.e.
* "levels/towndata/towns") contains trees rather than arches.
*/
extern int8_t dSpecial[MAXDUNX][MAXDUNY];
extern int themeCount;
extern THEME_LOC themeLoc[MAXTHEMES];
#ifdef BUILD_TESTING
std::optional<WorldTileSize> GetSizeForThemeRoom();
#endif
dungeon_type GetLevelType(int level);
void CreateDungeon(uint32_t rseed, lvl_entry entry);
DVL_ALWAYS_INLINE 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 {
WorldTileSize size;
/* these are indexed as [y][x] */
uint8_t search[6][6];
uint8_t replace[6][6];
/**
* @param position Coordinates of the dungeon tile to check
* @param respectProtected Match bug from Crypt levels if false
*/
bool matches(WorldTilePosition position, bool respectProtected = true) const
{
for (WorldTileCoord yy = 0; yy < size.height; yy++) {
for (WorldTileCoord 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(WorldTilePosition position, bool protect = false) const
{
for (WorldTileCoord y = 0; y < size.height; y++) {
for (WorldTileCoord 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);
}
}
}
};
[[nodiscard]] DVL_ALWAYS_INLINE bool TileHasAny(Point coords, TileProperties property)
{
return HasAnyOf(SOLData[dPiece[coords.x][coords.y]], property);
}
tl::expected<void, std::string> LoadLevelSOLData();
void SetDungeonMicros();
void DRLG_InitTrans();
void DRLG_MRectTrans(WorldTilePosition origin, WorldTilePosition extent);
void DRLG_MRectTrans(WorldTileRectangle area);
void DRLG_RectTrans(WorldTileRectangle 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(WorldTileRectangle area);
/**
* @param miniset The miniset to place
* @param tries Tiles to try, 1600 will scan the full map
* @param drlg1Quirk Match buggy behaviour of Diablo's Cathedral
*/
std::optional<Point> 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();
/**
* @brief Returns the size in tiles of the specified ".dun" Data
*/
WorldTileSize GetDunSize(const uint16_t *dunData);
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(WorldTilePosition position);
void InitLevels();
void FloodTransparencyValues(uint8_t floorID);
DVL_ALWAYS_INLINE const uint8_t *GetDunFrame(uint32_t frame)
{
const auto *pFrameTable = reinterpret_cast<const uint32_t *>(pDungeonCels.get());
return reinterpret_cast<const uint8_t *>(&pDungeonCels[SDL_SwapLE32(pFrameTable[frame])]);
}
DVL_ALWAYS_INLINE const uint8_t *GetDunFrameFoliage(uint32_t frame)
{
return GetDunFrame(frame) + ReencodedTriangleFrameSize;
}
} // namespace devilution