/** * @file automap.cpp * * Implementation of the in-game map overlay. */ #include "automap.h" #include #include #include #include "control/control.hpp" #include "engine/load_file.hpp" #include "engine/palette.h" #include "engine/render/automap_render.hpp" #include "engine/render/primitive_render.hpp" #include "levels/gendung.h" #include "levels/setmaps.h" #include "options.h" #include "player.h" #include "utils/attributes.h" #include "utils/enum_traits.h" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/ui_fwd.h" #include "utils/utf8.hpp" #ifdef _DEBUG #include "debug.h" #include "lighting.h" #endif namespace devilution { namespace { Point Automap; enum MapColors : uint8_t { /** color used to draw the player's arrow */ MapColorsPlayer1 = (PAL8_ORANGE + 1), MapColorsPlayer2 = (PAL8_YELLOW + 1), MapColorsPlayer3 = (PAL8_RED + 1), MapColorsPlayer4 = (PAL8_BLUE + 1), /** color for bright map lines (doors, stairs etc.) */ MapColorsBright = PAL8_YELLOW, /** color for dim map lines/dots */ MapColorsDim = (PAL16_YELLOW + 8), /** color for items on automap */ MapColorsItem = (PAL8_BLUE + 1), /** color for activated pentragram on automap */ MapColorsPentagramOpen = (PAL8_RED + 2), /** color for cave lava on automap */ MapColorsLava = (PAL8_ORANGE + 2), /** color for cave water on automap */ MapColorsWater = (PAL8_BLUE + 2), /** color for hive acid on automap */ MapColorsAcid = (PAL8_YELLOW + 4), }; struct AutomapTile { /** The general shape of the tile */ enum class Types : uint8_t { None, Diamond, Vertical, Horizontal, Cross, FenceVertical, FenceHorizontal, Corner, CaveHorizontalCross, CaveVerticalCross, CaveHorizontal, CaveVertical, CaveCross, Bridge, River, RiverCornerEast, RiverCornerNorth, RiverCornerSouth, RiverCornerWest, RiverForkIn, RiverForkOut, RiverLeftIn, RiverLeftOut, RiverRightIn, RiverRightOut, CaveHorizontalWoodCross, CaveVerticalWoodCross, CaveLeftCorner, CaveRightCorner, CaveBottomCorner, CaveHorizontalWood, CaveVerticalWood, CaveWoodCross, CaveRightWoodCross, CaveLeftWoodCross, HorizontalLavaThin, VerticalLavaThin, BendSouthLavaThin, BendWestLavaThin, BendEastLavaThin, BendNorthLavaThin, VerticalWallLava, HorizontalWallLava, SELava, SWLava, NELava, NWLava, SLava, WLava, ELava, NLava, Lava, CaveHorizontalWallLava, CaveVerticalWallLava, HorizontalBridgeLava, VerticalBridgeLava, VerticalDiamond, HorizontalDiamond, PentagramClosed, PentagramOpen, }; Types type; /** Additional details about the given tile */ enum class Flags : uint8_t { // clang-format off VerticalDoor = 1 << 0, HorizontalDoor = 1 << 1, VerticalArch = 1 << 2, HorizontalArch = 1 << 3, VerticalGrate = 1 << 4, HorizontalGrate = 1 << 5, VerticalPassage = VerticalDoor | VerticalArch | VerticalGrate, HorizontalPassage = HorizontalDoor | HorizontalArch | HorizontalGrate, Dirt = 1 << 6, Stairs = 1 << 7, // clang-format on }; Flags flags = {}; [[nodiscard]] DVL_ALWAYS_INLINE constexpr bool hasFlag(Flags test) const { return (static_cast(flags) & static_cast(test)) != 0; } template [[nodiscard]] DVL_ALWAYS_INLINE constexpr bool hasAnyFlag(Flags flag, Args... testFlags) { return (static_cast(this->flags) & (static_cast(flag) | ... | static_cast(testFlags))) != 0; } }; /** * Maps from tile_id to automap type. */ std::array AutomapTypeTiles; /** * @brief Draw a diamond on top tile. */ void DrawDiamond(const Surface &out, Point center, uint8_t color) { DrawMapLineNE(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), color); DrawMapLineSE(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), color); DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp), AmLine(AmLineLength::FullTile), color); DrawMapLineNE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::None), AmLine(AmLineLength::FullTile), color); } /** * @brief Draws a bright diamond and a line, orientation depending on the tileset. */ void DrawMapVerticalDoor(const Surface &out, Point center, AutomapTile neTile, uint8_t colorBright, uint8_t colorDim) { AmWidthOffset lWidthOffset; AmHeightOffset lHeightOffset; AmWidthOffset dWidthOffset; AmHeightOffset dHeightOffset; AmLineLength length; switch (leveltype) { case DTYPE_CATHEDRAL: case DTYPE_CRYPT: lWidthOffset = AmWidthOffset::QuarterTileLeft; lHeightOffset = AmHeightOffset::QuarterTileUp; dWidthOffset = AmWidthOffset::HalfTileLeft; dHeightOffset = AmHeightOffset::HalfTileDown; length = AmLineLength::HalfTile; break; case DTYPE_CATACOMBS: lWidthOffset = AmWidthOffset::ThreeQuartersTileLeft; lHeightOffset = AmHeightOffset::QuarterTileDown; dWidthOffset = AmWidthOffset::None; dHeightOffset = AmHeightOffset::None; length = AmLineLength::FullTile; break; case DTYPE_CAVES: lWidthOffset = AmWidthOffset::QuarterTileLeft; lHeightOffset = AmHeightOffset::ThreeQuartersTileDown; dWidthOffset = AmWidthOffset::HalfTileRight; dHeightOffset = AmHeightOffset::HalfTileDown; length = AmLineLength::FullTile; break; default: app_fatal("Invalid leveltype"); } if (!(neTile.hasFlag(AutomapTile::Flags::VerticalPassage) && leveltype == DTYPE_CATHEDRAL)) DrawMapLineNE(out, center + AmOffset(lWidthOffset, lHeightOffset), AmLine(length), colorDim); DrawDiamond(out, center + AmOffset(dWidthOffset, dHeightOffset), colorBright); } /** * @brief Draws a bright diamond and a line, orientation depending on the tileset. */ void DrawMapHorizontalDoor(const Surface &out, Point center, AutomapTile nwTile, uint8_t colorBright, uint8_t colorDim) { AmWidthOffset lWidthOffset; AmHeightOffset lHeightOffset; AmWidthOffset dWidthOffset; AmHeightOffset dHeightOffset; AmLineLength length; switch (leveltype) { case DTYPE_CATHEDRAL: case DTYPE_CRYPT: lWidthOffset = AmWidthOffset::None; lHeightOffset = AmHeightOffset::HalfTileUp; dWidthOffset = AmWidthOffset::HalfTileRight; dHeightOffset = AmHeightOffset::HalfTileDown; length = AmLineLength::HalfTile; break; case DTYPE_CATACOMBS: lWidthOffset = AmWidthOffset::QuarterTileRight; lHeightOffset = AmHeightOffset::QuarterTileUp; dWidthOffset = AmWidthOffset::None; dHeightOffset = AmHeightOffset::None; length = AmLineLength::FullTile; break; case DTYPE_CAVES: lWidthOffset = AmWidthOffset::QuarterTileLeft; lHeightOffset = AmHeightOffset::QuarterTileDown; dWidthOffset = AmWidthOffset::HalfTileLeft; dHeightOffset = AmHeightOffset::HalfTileDown; length = AmLineLength::FullTile; break; break; default: app_fatal("Invalid leveltype"); } if (!(nwTile.hasFlag(AutomapTile::Flags::HorizontalPassage) && leveltype == DTYPE_CATHEDRAL)) DrawMapLineSE(out, center + AmOffset(lWidthOffset, lHeightOffset), AmLine(length), colorDim); DrawDiamond(out, center + AmOffset(dWidthOffset, dHeightOffset), colorBright); } /** * @brief Draw 16 individual pixels equally spaced apart, used to communicate OOB area to the player. */ void DrawDirt(const Surface &out, Point center, AutomapTile nwTile, AutomapTile neTile, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); // Prevent the top dirt pixel from appearing inside arch diamonds if (!nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) && !neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate)) SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawBridge(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverRightIn(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverCornerSouth(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); } void DrawRiverCornerNorth(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); } void DrawRiverLeftOut(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverLeftIn(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); } void DrawRiverCornerWest(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); } void DrawRiverCornerEast(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverRightOut(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiver(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverForkIn(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); SetMapPixel(out, center, color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); } void DrawRiverForkOut(const Surface &out, Point center, uint8_t color) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); } template void DrawLavaRiver(const Surface &out, Point center, uint8_t color, bool hasBridge) { // First row (y = 0) if constexpr (IsAnyOf(Direction::NorthWest, TDir1, TDir2)) { if (!(hasBridge && IsAnyOf(TDir1, Direction::NorthWest))) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); } } // Second row (y = 1) if constexpr (IsAnyOf(Direction::NorthEast, TDir1, TDir2)) { if (!(hasBridge && IsAnyOf(Direction::NorthEast, TDir1, TDir2))) SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); } if constexpr (IsAnyOf(Direction::NorthWest, TDir1, TDir2) || IsAnyOf(Direction::NorthEast, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::None), color); } if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2) || IsAnyOf(Direction::NorthWest, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); } if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); } // Third row (y = 2) if constexpr (IsAnyOf(Direction::NorthEast, TDir1, TDir2)) { if (!(hasBridge && IsAnyOf(Direction::NorthEast, TDir1, TDir2))) SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); } if constexpr (IsAnyOf(Direction::NorthEast, TDir1, TDir2) || IsAnyOf(Direction::SouthEast, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); } if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2) || IsAnyOf(Direction::SouthEast, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); } if constexpr (IsAnyOf(Direction::SouthWest, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); } // Fourth row (y = 3) if constexpr (IsAnyOf(Direction::SouthEast, TDir1, TDir2)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); } } template void DrawLava(const Surface &out, Point center, uint8_t color) { if constexpr (IsAnyOf(TDir, Direction::NorthWest, Direction::North, Direction::NorthEast, Direction::NoDirection)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), color); // north corner } if constexpr (IsNoneOf(TDir, Direction::South, Direction::SouthEast, Direction::East)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileUp), color); // northwest edge SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), color); // northwest edge } if constexpr (IsAnyOf(TDir, Direction::SouthWest, Direction::West, Direction::NorthWest, Direction::NoDirection)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), color); // west corner } if constexpr (IsAnyOf(TDir, Direction::South, Direction::SouthWest, Direction::West, Direction::NorthWest, Direction::SouthEast, Direction::NoDirection)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), color); // southwest edge SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), color); // southwest edge } if constexpr (IsAnyOf(TDir, Direction::South, Direction::SouthWest, Direction::SouthEast, Direction::NoDirection)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), color); // south corner } if constexpr (IsAnyOf(TDir, Direction::South, Direction::SouthWest, Direction::NorthEast, Direction::East, Direction::SouthEast, Direction::NoDirection)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), color); // southeast edge SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), color); // southeast edge } if constexpr (IsAnyOf(TDir, Direction::NorthEast, Direction::East, Direction::SouthEast, Direction::NoDirection)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), color); // east corner } if constexpr (IsNoneOf(TDir, Direction::South, Direction::SouthWest, Direction::West)) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileUp), color); // northeast edge SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), color); // northeast edge } if constexpr (TDir != Direction::South) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::None), color); // north center } if constexpr (TDir != Direction::East) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown), color); // west center } if constexpr (TDir != Direction::West) { SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::QuarterTileDown), color); // east center } if constexpr (TDir != Direction::North) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), color); // south center } } /** * @brief Draw 4 south-east facing lines, used to communicate trigger locations to the player. */ void DrawStairs(const Surface &out, Point center, uint8_t color) { constexpr int NumStairSteps = 4; const Displacement offset = AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::QuarterTileDown); AmWidthOffset w = AmWidthOffset::QuarterTileLeft; AmHeightOffset h = AmHeightOffset::QuarterTileUp; if (IsAnyOf(leveltype, DTYPE_CATACOMBS, DTYPE_HELL)) { w = AmWidthOffset::QuarterTileLeft; h = AmHeightOffset::ThreeQuartersTileUp; } // Initial point based on the 'center' position. Point p = center + AmOffset(w, h); for (int i = 0; i < NumStairSteps; ++i) { DrawMapLineSE(out, p, AmLine(AmLineLength::DoubleTile), color); p += offset; } } /** * @brief Redraws the bright line of the door diamond that gets overwritten by later drawn lines. */ void FixHorizontalDoor(const Surface &out, Point center, AutomapTile nwTile, uint8_t colorBright) { if (leveltype != DTYPE_CATACOMBS && nwTile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { DrawMapLineNE(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), colorBright); } } /** * @brief Redraws the bright line of the door diamond that gets overwritten by later drawn lines. */ void FixVerticalDoor(const Surface &out, Point center, AutomapTile neTile, uint8_t colorBright) { if (leveltype != DTYPE_CATACOMBS && neTile.hasFlag(AutomapTile::Flags::VerticalDoor)) { DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp), AmLine(AmLineLength::FullTile), colorBright); } } /** * @brief Draw half-tile length lines to connect walls to any walls to the north-west and/or north-east */ void DrawWallConnections(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile neTile, uint8_t colorBright, uint8_t colorDim) { if (tile.hasFlag(AutomapTile::Flags::HorizontalDoor) && nwTile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { // fix missing lower half of the line connecting door pairs in Lazarus' level DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), colorDim); } if (IsAnyOf(nwTile.type, AutomapTile::Types::HorizontalWallLava, AutomapTile::Types::Horizontal, AutomapTile::Types::HorizontalDiamond, AutomapTile::Types::FenceHorizontal, AutomapTile::Types::Cross, AutomapTile::Types::CaveVerticalWoodCross, AutomapTile::Types::CaveRightCorner)) { DrawMapLineSE(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileUp), AmLine(AmLineLength::HalfTile), colorDim); FixHorizontalDoor(out, center, nwTile, colorBright); } if (IsAnyOf(neTile.type, AutomapTile::Types::VerticalWallLava, AutomapTile::Types::Vertical, AutomapTile::Types::VerticalDiamond, AutomapTile::Types::FenceVertical, AutomapTile::Types::Cross, AutomapTile::Types::CaveHorizontalWoodCross, AutomapTile::Types::CaveLeftCorner)) { DrawMapLineNE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), colorDim); FixVerticalDoor(out, center, neTile, colorBright); } } /** * @brief Draws a dotted line to represent a wall grate. */ void DrawMapVerticalGrate(const Surface &out, Point center, uint8_t colorDim) { const Point pos1 = center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::EighthTileUp); const Point pos2 = center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None); const Point pos3 = center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::EighthTileDown); SetMapPixel(out, pos1 + Displacement { 0, 1 }, 0); SetMapPixel(out, pos2 + Displacement { 0, 1 }, 0); SetMapPixel(out, pos3 + Displacement { 0, 1 }, 0); SetMapPixel(out, pos1, colorDim); SetMapPixel(out, pos2, colorDim); SetMapPixel(out, pos3, colorDim); } /** * @brief Draws a dotted line to represent a wall grate. */ void DrawMapHorizontalGrate(const Surface &out, Point center, uint8_t colorDim) { const Point pos1 = center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::EighthTileUp); const Point pos2 = center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None); const Point pos3 = center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None) + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::EighthTileDown); SetMapPixel(out, pos1 + Displacement { 0, 1 }, 0); SetMapPixel(out, pos2 + Displacement { 0, 1 }, 0); SetMapPixel(out, pos3 + Displacement { 0, 1 }, 0); SetMapPixel(out, pos1, colorDim); SetMapPixel(out, pos2, colorDim); SetMapPixel(out, pos3, colorDim); } /** * Left-facing obstacle */ void DrawHorizontal(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile neTile, AutomapTile seTile, uint8_t colorBright, uint8_t colorDim) { AmWidthOffset w = AmWidthOffset::None; AmHeightOffset h = AmHeightOffset::HalfTileUp; AmLineLength l = AmLineLength::FullAndHalfTile; // Draw a diamond in the top tile if (neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate) // NE tile has an arch, so add a diamond for visual consistency || nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) // NW tile has an arch, so add a diamond for visual consistency || tile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::VerticalGrate, AutomapTile::Flags::HorizontalGrate) // Current tile has an arch, add a diamond || tile.type == AutomapTile::Types::HorizontalDiamond) { // wall ending in hell that should end with a diamond w = AmWidthOffset::QuarterTileRight; h = AmHeightOffset::QuarterTileUp; l = AmLineLength::FullTile; // shorten line to avoid overdraw DrawDiamond(out, center, colorDim); FixHorizontalDoor(out, center, nwTile, colorBright); FixVerticalDoor(out, center, neTile, colorBright); } // Shorten line to avoid overdraw if (IsAnyOf(leveltype, DTYPE_CAVES, DTYPE_NEST) && IsAnyOf(tile.type, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWoodCross) && !(IsAnyOf(seTile.type, AutomapTile::Types::Horizontal, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWoodCross, AutomapTile::Types::Corner))) { l = AmLineLength::FullTile; } // Draw the wall line if the wall is solid if (!tile.hasFlag(AutomapTile::Flags::HorizontalPassage)) { DrawMapLineSE(out, center + AmOffset(w, h), AmLine(l), colorDim); return; } // Draw door or grate if (tile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { DrawMapHorizontalDoor(out, center, nwTile, colorBright, colorDim); } else if (tile.hasFlag(AutomapTile::Flags::HorizontalGrate)) { DrawMapHorizontalGrate(out, center, colorDim); } } /** * Right-facing obstacle */ void DrawVertical(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile neTile, AutomapTile swTile, uint8_t colorBright, uint8_t colorDim) { AmWidthOffset w = AmWidthOffset::ThreeQuartersTileLeft; AmHeightOffset h = AmHeightOffset::QuarterTileDown; AmLineLength l = AmLineLength::FullAndHalfTile; // Draw a diamond in the top tile if (neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate) // NE tile has an arch, so add a diamond for visual consistency || nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) // NW tile has an arch, so add a diamond for visual consistency || tile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::VerticalGrate, AutomapTile::Flags::HorizontalGrate) // Current tile has an arch, add a diamond || tile.type == AutomapTile::Types::VerticalDiamond) { // wall ending in hell that should end with a diamond l = AmLineLength::FullTile; // shorten line to avoid overdraw DrawDiamond(out, center, colorDim); FixVerticalDoor(out, center, nwTile, colorBright); FixVerticalDoor(out, center, neTile, colorBright); } // Shorten line to avoid overdraw and adjust offset to match if (IsAnyOf(leveltype, DTYPE_CAVES, DTYPE_NEST) && IsAnyOf(tile.type, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWoodCross) && !(IsAnyOf(swTile.type, AutomapTile::Types::Vertical, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWoodCross, AutomapTile::Types::Corner))) { w = AmWidthOffset::HalfTileLeft; h = AmHeightOffset::None; l = AmLineLength::FullTile; } // Draw the wall line if the wall is solid if (!tile.hasFlag(AutomapTile::Flags::VerticalPassage)) { DrawMapLineNE(out, center + AmOffset(w, h), AmLine(l), colorDim); return; } // Draw door or grate if (tile.hasFlag(AutomapTile::Flags::VerticalDoor)) { DrawMapVerticalDoor(out, center, neTile, colorBright, colorDim); } else if (tile.hasFlag(AutomapTile::Flags::VerticalGrate)) { DrawMapVerticalGrate(out, center, colorDim); } } /** * @brief Draw half-tile length lines to connect walls to any walls to the south-west and/or south-east * (For caves the horizontal/vertical flags are swapped) */ void DrawCaveWallConnections(const Surface &out, Point center, AutomapTile sTile, AutomapTile swTile, AutomapTile seTile, uint8_t colorDim) { if (IsAnyOf(swTile.type, AutomapTile::Types::CaveVerticalWallLava, AutomapTile::Types::CaveVertical, AutomapTile::Types::CaveVerticalWood, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveRightWoodCross, AutomapTile::Types::CaveLeftWoodCross, AutomapTile::Types::CaveRightCorner)) { DrawMapLineNE(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), AmLine(AmLineLength::HalfTile), colorDim); } if (IsAnyOf(seTile.type, AutomapTile::Types::CaveHorizontalWallLava, AutomapTile::Types::CaveHorizontal, AutomapTile::Types::CaveHorizontalWood, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveRightWoodCross, AutomapTile::Types::CaveLeftWoodCross, AutomapTile::Types::CaveLeftCorner)) { DrawMapLineSE(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::HalfTile), colorDim); } } void DrawCaveHorizontalDirt(const Surface &out, Point center, AutomapTile tile, AutomapTile swTile, uint8_t colorDim) { if (swTile.hasFlag(AutomapTile::Flags::Dirt) || (leveltype != DTYPE_TOWN && IsNoneOf(tile.type, AutomapTile::Types::CaveHorizontalWood, AutomapTile::Types::CaveHorizontalWoodCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveLeftWoodCross))) { SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), colorDim); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), colorDim); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::ThreeQuartersTileDown), colorDim); SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); } } /** * For caves the horizontal/vertical flags are swapped */ void DrawCaveHorizontal(const Surface &out, Point center, AutomapTile tile, AutomapTile nwTile, AutomapTile swTile, uint8_t colorBright, uint8_t colorDim) { if (tile.hasFlag(AutomapTile::Flags::VerticalDoor)) { DrawMapHorizontalDoor(out, center, nwTile, colorBright, colorDim); } else { AmWidthOffset w; AmHeightOffset h; AmLineLength l; if (IsAnyOf(tile.type, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWoodCross)) { w = AmWidthOffset::HalfTileLeft; h = AmHeightOffset::None; l = AmLineLength::FullTile; } else { w = AmWidthOffset::ThreeQuartersTileLeft; h = AmHeightOffset::QuarterTileUp; l = AmLineLength::FullAndHalfTile; } DrawCaveHorizontalDirt(out, center, tile, swTile, colorDim); DrawMapLineSE(out, center + AmOffset(w, h), AmLine(l), colorDim); } } void DrawCaveVerticalDirt(const Surface &out, Point center, AutomapTile tile, AutomapTile seTile, uint8_t colorDim) { if (seTile.hasFlag(AutomapTile::Flags::Dirt) || (leveltype != DTYPE_TOWN && IsNoneOf(tile.type, AutomapTile::Types::CaveVerticalWood, AutomapTile::Types::CaveVerticalWoodCross, AutomapTile::Types::CaveWoodCross, AutomapTile::Types::CaveRightWoodCross))) { SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); SetMapPixel(out, center + AmOffset(AmWidthOffset::QuarterTileRight, AmHeightOffset::ThreeQuartersTileDown), colorDim); SetMapPixel(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), colorDim); SetMapPixel(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileRight, AmHeightOffset::QuarterTileDown), colorDim); } } /** * For caves the horizontal/vertical flags are swapped */ void DrawCaveVertical(const Surface &out, Point center, AutomapTile tile, AutomapTile neTile, AutomapTile seTile, uint8_t colorBright, uint8_t colorDim) { if (tile.hasFlag(AutomapTile::Flags::HorizontalDoor)) { DrawMapVerticalDoor(out, center, neTile, colorBright, colorDim); } else { AmLineLength l; if (IsAnyOf(tile.type, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWoodCross)) { l = AmLineLength::FullTile; } else { l = AmLineLength::FullAndHalfTile; } DrawCaveVerticalDirt(out, center, tile, seTile, colorDim); DrawMapLineNE(out, { center + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown) }, AmLine(l), colorDim); } } void DrawCaveLeftCorner(const Surface &out, Point center, uint8_t colorDim) { DrawMapLineSE(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileUp), AmLine(AmLineLength::HalfTile), colorDim); DrawMapLineNE(out, center + AmOffset(AmWidthOffset::ThreeQuartersTileLeft, AmHeightOffset::QuarterTileDown), AmLine(AmLineLength::HalfTile), colorDim); } void DrawCaveRightCorner(const Surface &out, Point center, uint8_t colorDim) { DrawMapLineSE(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), AmLine(AmLineLength::HalfTile), colorDim); DrawMapLineNE(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None), AmLine(AmLineLength::HalfTile), colorDim); } void DrawMapEllipse(const Surface &out, Point from, int radius, uint8_t colorIndex) { const int a = radius; const int b = radius / 2; int x = 0; int y = b; // Offset ellipse so the center of the ellipse is the center of our megatile on the x plane from.x -= radius; // Initial point SetMapPixel(out, { from.x, from.y + b }, colorIndex); SetMapPixel(out, { from.x, from.y - b }, colorIndex); // Initialize the parameters int p1 = (b * b) - (a * a * b) + (a * a) / 4; // Region 1 while ((b * b * x) < (a * a * y)) { x++; if (p1 < 0) { p1 += (2 * b * b * x) + (b * b); } else { y--; p1 += (2 * b * b * x) - (2 * a * a * y) + (b * b); } SetMapPixel(out, { from.x + x, from.y + y }, colorIndex); SetMapPixel(out, { from.x - x, from.y + y }, colorIndex); SetMapPixel(out, { from.x + x, from.y - y }, colorIndex); SetMapPixel(out, { from.x - x, from.y - y }, colorIndex); } // Initialize the second parameter for Region 2 int p2 = (b * b * ((x + 1) * (x + 1))) + (a * a * ((y - 1) * (y - 1))) - (a * a * b * b); // Region 2 while (y > 0) { y--; if (p2 > 0) { p2 += (-2 * a * a * y) + (a * a); } else { x++; p2 += (2 * b * b * x) - (2 * a * a * y) + (a * a); } SetMapPixel(out, { from.x + x, from.y + y }, colorIndex); SetMapPixel(out, { from.x - x, from.y + y }, colorIndex); SetMapPixel(out, { from.x + x, from.y - y }, colorIndex); SetMapPixel(out, { from.x - x, from.y - y }, colorIndex); } } void DrawMapStar(const Surface &out, Point from, int radius, uint8_t color) { const int scaleFactor = 128; Point anchors[5]; // Offset star so the center of the star is the center of our megatile on the x plane from.x -= radius; anchors[0] = { from.x - (121 * radius / scaleFactor), from.y + (19 * radius / scaleFactor) }; // Left Point anchors[1] = { from.x + (121 * radius / scaleFactor), from.y + (19 * radius / scaleFactor) }; // Right Point anchors[2] = { from.x, from.y + (64 * radius / scaleFactor) }; // Bottom Point anchors[3] = { from.x - (75 * radius / scaleFactor), from.y - (51 * radius / scaleFactor) }; // Top Left Point anchors[4] = { from.x + (75 * radius / scaleFactor), from.y - (51 * radius / scaleFactor) }; // Top Right Point // Draw lines between the anchors to form a star DrawMapFreeLine(out, anchors[3], anchors[1], color); // Connect Top Left -> Right DrawMapFreeLine(out, anchors[1], anchors[0], color); // Connect Right -> Left DrawMapFreeLine(out, anchors[0], anchors[4], color); // Connect Left -> Top Right DrawMapFreeLine(out, anchors[4], anchors[2], color); // Connect Top Right -> Bottom DrawMapFreeLine(out, anchors[2], anchors[3], color); // Connect Bottom -> Top Left } /** * @brief Check if a given tile has the provided AutomapTile flag */ bool HasAutomapFlag(Point position, AutomapTile::Flags type) { if (position.x < 0 || position.x >= DMAXX || position.y < 0 || position.y >= DMAXX) { return false; } return AutomapTypeTiles[dungeon[position.x][position.y]].hasFlag(type); } /** * @brief Returns the automap shape at the given coordinate. */ AutomapTile GetAutomapTileType(Point position) { if (position.x < 0 || position.x >= DMAXX || position.y < 0 || position.y >= DMAXX) { return {}; } AutomapTile tile = AutomapTypeTiles[dungeon[position.x][position.y]]; if (tile.type == AutomapTile::Types::Corner) { if (HasAutomapFlag({ position.x - 1, position.y }, AutomapTile::Flags::HorizontalArch)) { if (HasAutomapFlag({ position.x, position.y - 1 }, AutomapTile::Flags::VerticalArch)) { tile.type = AutomapTile::Types::Diamond; } } } return tile; } /** * @brief Returns the automap shape at the given coordinate. */ AutomapTile GetAutomapTypeView(Point map) { if (map.x == -1 && map.y >= 0 && map.y < DMAXY && AutomapView[0][map.y] != MAP_EXP_NONE) { if (HasAutomapFlag({ 0, map.y + 1 }, AutomapTile::Flags::Dirt) && HasAutomapFlag({ 0, map.y }, AutomapTile::Flags::Dirt) && HasAutomapFlag({ 0, map.y - 1 }, AutomapTile::Flags::Dirt)) { return {}; } return { AutomapTile::Types::None, AutomapTile::Flags::Dirt }; } if (map.y == -1 && map.x >= 0 && map.x < DMAXY && AutomapView[map.x][0] != MAP_EXP_NONE) { if (HasAutomapFlag({ map.x + 1, 0 }, AutomapTile::Flags::Dirt) && HasAutomapFlag({ map.x, 0 }, AutomapTile::Flags::Dirt) && HasAutomapFlag({ map.x - 1, 0 }, AutomapTile::Flags::Dirt)) { return {}; } return { AutomapTile::Types::None, AutomapTile::Flags::Dirt }; } if (map.x < 0 || map.x >= DMAXX) { return {}; } if (map.y < 0 || map.y >= DMAXX) { return {}; } if (AutomapView[map.x][map.y] == MAP_EXP_NONE) { return {}; } return GetAutomapTileType(map); } /** * @brief Renders the given automap shape at the specified screen coordinates. */ void DrawAutomapTile(const Surface &out, Point center, Point map) { uint8_t colorBright = MapColorsBright; uint8_t colorDim = MapColorsDim; const MapExplorationType explorationType = static_cast(AutomapView[std::clamp(map.x, 0, DMAXX - 1)][std::clamp(map.y, 0, DMAXY - 1)]); switch (explorationType) { case MAP_EXP_SHRINE: colorDim = PAL16_GRAY + 11; colorBright = PAL16_GRAY + 3; break; case MAP_EXP_OTHERS: colorDim = PAL16_BEIGE + 10; colorBright = PAL16_BEIGE + 2; break; case MAP_EXP_SELF: case MAP_EXP_NONE: case MAP_EXP_OLD: break; } bool noConnect = false; AutomapTile tile = GetAutomapTypeView(map + Direction::NoDirection); AutomapTile nwTile = GetAutomapTypeView(map + Direction::NorthWest); AutomapTile neTile = GetAutomapTypeView(map + Direction::NorthEast); #ifdef _DEBUG if (DebugVision) { if (IsTileLit(map.megaToWorld())) DrawDiamond(out, center, PAL8_ORANGE + 1); if (IsTileLit(map.megaToWorld() + Direction::South)) DrawDiamond(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), PAL8_ORANGE + 1); if (IsTileLit(map.megaToWorld() + Direction::SouthWest)) DrawDiamond(out, center + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), PAL8_ORANGE + 1); if (IsTileLit(map.megaToWorld() + Direction::SouthEast)) DrawDiamond(out, center + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), PAL8_ORANGE + 1); } #endif // If the tile is an arch, grate, or diamond, we draw a diamond and therefore don't want connection lines if (tile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::VerticalArch, AutomapTile::Flags::HorizontalGrate, AutomapTile::Flags::VerticalGrate) || nwTile.hasAnyFlag(AutomapTile::Flags::HorizontalArch, AutomapTile::Flags::HorizontalGrate) || neTile.hasAnyFlag(AutomapTile::Flags::VerticalArch, AutomapTile::Flags::VerticalGrate) || tile.type == AutomapTile::Types::Diamond) { noConnect = true; } // These tilesets have doors where the connection lines would be drawn if (IsAnyOf(leveltype, DTYPE_CATACOMBS, DTYPE_CAVES) && (tile.hasFlag(AutomapTile::Flags::HorizontalDoor) || tile.hasFlag(AutomapTile::Flags::VerticalDoor))) noConnect = true; const AutomapTile swTile = GetAutomapTypeView(map + Direction::SouthWest); const AutomapTile sTile = GetAutomapTypeView(map + Direction::South); const AutomapTile seTile = GetAutomapTypeView(map + Direction::SouthEast); const AutomapTile nTile = GetAutomapTypeView(map + Direction::North); const AutomapTile wTile = GetAutomapTypeView(map + Direction::West); const AutomapTile eTile = GetAutomapTypeView(map + Direction::East); if ((leveltype == DTYPE_TOWN && tile.hasFlag(AutomapTile::Flags::Dirt)) || (tile.hasFlag(AutomapTile::Flags::Dirt) && (tile.type != AutomapTile::Types::None || swTile.type != AutomapTile::Types::None || sTile.type != AutomapTile::Types::None || seTile.type != AutomapTile::Types::None || IsAnyOf(nwTile.type, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveVertical, AutomapTile::Types::CaveVerticalCross, AutomapTile::Types::CaveVerticalWallLava, AutomapTile::Types::CaveLeftWoodCross) || IsAnyOf(nTile.type, AutomapTile::Types::CaveCross) || IsAnyOf(neTile.type, AutomapTile::Types::CaveCross, AutomapTile::Types::CaveHorizontal, AutomapTile::Types::CaveHorizontalCross, AutomapTile::Types::CaveHorizontalWallLava, AutomapTile::Types::CaveRightWoodCross) || IsAnyOf(wTile.type, AutomapTile::Types::CaveVerticalCross) || IsAnyOf(eTile.type, AutomapTile::Types::CaveHorizontalCross)))) { DrawDirt(out, center, nwTile, neTile, colorDim); } if (tile.hasFlag(AutomapTile::Flags::Stairs)) { DrawStairs(out, center, colorBright); } if (!noConnect) { if (IsAnyOf(leveltype, DTYPE_TOWN, DTYPE_CAVES, DTYPE_NEST)) { DrawCaveWallConnections(out, center, sTile, swTile, seTile, colorDim); } DrawWallConnections(out, center, tile, nwTile, neTile, colorBright, colorDim); } uint8_t lavaColor = MapColorsLava; if (leveltype == DTYPE_NEST) { lavaColor = MapColorsAcid; } else if (setlevel && setlvlnum == Quests[Q_PWATER]._qslvl) { if (Quests[Q_PWATER]._qactive != QUEST_DONE) { lavaColor = MapColorsAcid; } else { lavaColor = MapColorsWater; } } switch (tile.type) { case AutomapTile::Types::Diamond: // stand-alone column or other unpassable object DrawDiamond(out, center, colorDim); break; case AutomapTile::Types::Vertical: case AutomapTile::Types::FenceVertical: case AutomapTile::Types::VerticalDiamond: DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); break; case AutomapTile::Types::Horizontal: case AutomapTile::Types::FenceHorizontal: case AutomapTile::Types::HorizontalDiamond: DrawHorizontal(out, center, tile, nwTile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::Cross: DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); DrawHorizontal(out, center, tile, nwTile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveHorizontalCross: case AutomapTile::Types::CaveHorizontalWoodCross: DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); break; case AutomapTile::Types::CaveVerticalCross: case AutomapTile::Types::CaveVerticalWoodCross: DrawHorizontal(out, center, tile, nwTile, neTile, seTile, colorBright, colorDim); DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveHorizontal: case AutomapTile::Types::CaveHorizontalWood: DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); break; case AutomapTile::Types::CaveVertical: case AutomapTile::Types::CaveVerticalWood: DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveCross: // Add the missing dirt pixel SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); [[fallthrough]]; case AutomapTile::Types::CaveWoodCross: case AutomapTile::Types::CaveRightWoodCross: case AutomapTile::Types::CaveLeftWoodCross: DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); break; case AutomapTile::Types::CaveLeftCorner: DrawCaveLeftCorner(out, center, colorDim); break; case AutomapTile::Types::CaveRightCorner: DrawCaveRightCorner(out, center, colorDim); break; case AutomapTile::Types::Corner: break; case AutomapTile::Types::CaveBottomCorner: // Add the missing dirt pixel // BUGFIX: A tile in poisoned water supply isn't drawing this pixel SetMapPixel(out, center + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown), colorDim); break; case AutomapTile::Types::None: break; case AutomapTile::Types::Bridge: DrawBridge(out, center, MapColorsItem); break; case AutomapTile::Types::River: DrawRiver(out, center, MapColorsItem); break; case AutomapTile::Types::RiverCornerEast: DrawRiverCornerEast(out, center, MapColorsItem); break; case AutomapTile::Types::RiverCornerNorth: DrawRiverCornerNorth(out, center, MapColorsItem); break; case AutomapTile::Types::RiverCornerSouth: DrawRiverCornerSouth(out, center, MapColorsItem); break; case AutomapTile::Types::RiverCornerWest: DrawRiverCornerWest(out, center, MapColorsItem); break; case AutomapTile::Types::RiverForkIn: DrawRiverForkIn(out, center, MapColorsItem); break; case AutomapTile::Types::RiverForkOut: DrawRiverForkOut(out, center, MapColorsItem); break; case AutomapTile::Types::RiverLeftIn: DrawRiverLeftIn(out, center, MapColorsItem); break; case AutomapTile::Types::RiverLeftOut: DrawRiverLeftOut(out, center, MapColorsItem); break; case AutomapTile::Types::RiverRightIn: DrawRiverRightIn(out, center, MapColorsItem); break; case AutomapTile::Types::RiverRightOut: DrawRiverRightOut(out, center, MapColorsItem); break; case AutomapTile::Types::HorizontalLavaThin: DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::VerticalLavaThin: DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::BendSouthLavaThin: DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::BendWestLavaThin: DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::BendEastLavaThin: DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::BendNorthLavaThin: DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::VerticalWallLava: DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::HorizontalWallLava: DrawHorizontal(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim); DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::SELava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::SWLava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::NELava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::NWLava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::SLava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::WLava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::ELava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::NLava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::Lava: DrawLava(out, center, lavaColor); break; case AutomapTile::Types::CaveHorizontalWallLava: DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim); DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::CaveVerticalWallLava: DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim); DrawLavaRiver(out, center, lavaColor, false); break; case AutomapTile::Types::HorizontalBridgeLava: DrawLavaRiver(out, center, lavaColor, true); break; case AutomapTile::Types::VerticalBridgeLava: DrawLavaRiver(out, center, lavaColor, true); break; case AutomapTile::Types::PentagramClosed: // Functions are called twice to integrate shadow. Shadows are not drawn inside these functions to avoid shadows being drawn on top of normal pixels. DrawMapEllipse(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow DrawMapStar(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow DrawMapEllipse(out, center, AmLine(AmLineLength::OctupleTile), colorDim); DrawMapStar(out, center, AmLine(AmLineLength::OctupleTile), colorDim); break; case AutomapTile::Types::PentagramOpen: // Functions are called twice to integrate shadow. Shadows are not drawn inside these functions to avoid shadows being drawn on top of normal pixels. DrawMapEllipse(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow DrawMapStar(out, center + Displacement { 0, 1 }, AmLine(AmLineLength::OctupleTile), 0); // shadow DrawMapEllipse(out, center, AmLine(AmLineLength::OctupleTile), MapColorsPentagramOpen); DrawMapStar(out, center, AmLine(AmLineLength::OctupleTile), MapColorsPentagramOpen); break; } } Displacement GetAutomapScreen() { Displacement screen = {}; if (GetAutomapType() == AutomapType::Minimap) { screen = { MinimapRect.position.x + MinimapRect.size.width / 2, MinimapRect.position.y + MinimapRect.size.height / 2 }; } else { screen = { gnScreenWidth / 2, (gnScreenHeight - GetMainPanel().size.height) / 2 }; } screen += AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown); return screen; } void SearchAutomapItem(const Surface &out, const Displacement &myPlayerOffset, int searchRadius, tl::function_ref highlightTile) { const Player &player = *MyPlayer; Point tile = player.position.tile; if (player._pmode == PM_WALK_SIDEWAYS) { tile = player.position.future; if (player._pdir == Direction::West) tile.x++; else tile.y++; } const int startX = std::clamp(tile.x - searchRadius, 0, MAXDUNX); const int startY = std::clamp(tile.y - searchRadius, 0, MAXDUNY); const int endX = std::clamp(tile.x + searchRadius, 0, MAXDUNX); const int endY = std::clamp(tile.y + searchRadius, 0, MAXDUNY); const AutomapType mapType = GetAutomapType(); const int scale = (mapType == AutomapType::Minimap) ? MinimapScale : AutoMapScale; for (int i = startX; i < endX; i++) { for (int j = startY; j < endY; j++) { if (!highlightTile({ i, j })) continue; const int px = i - 2 * AutomapOffset.deltaX - ViewPosition.x; const int py = j - 2 * AutomapOffset.deltaY - ViewPosition.y; Point screen = { (myPlayerOffset.deltaX * scale / 100 / 2) + (px - py) * AmLine(AmLineLength::DoubleTile), (myPlayerOffset.deltaY * scale / 100 / 2) + (px + py) * AmLine(AmLineLength::FullTile), }; screen += GetAutomapScreen(); if (mapType != AutomapType::Minimap && CanPanelsCoverView()) { if (IsRightPanelOpen()) screen.x -= gnScreenWidth / 4; if (IsLeftPanelOpen()) screen.x += gnScreenWidth / 4; } screen.y -= AmLine(AmLineLength::FullTile); DrawDiamond(out, screen, MapColorsItem); } } } uint8_t GetPlayerMapColor(int id) { static constexpr uint8_t PlayerMapColors[] = { MapColorsPlayer1, MapColorsPlayer2, MapColorsPlayer3, MapColorsPlayer4, }; if (id < 0 || id >= static_cast(SDL_arraysize(PlayerMapColors))) return MapColorsPlayer1; return PlayerMapColors[id]; } /** * @brief Renders an arrow on the automap, centered on and facing the direction of the player. */ void DrawAutomapPlr(const Surface &out, const Displacement &myPlayerOffset, const Player &player) { const uint8_t playerColor = GetPlayerMapColor(player.getId()); const Point tile = player.position.tile; const int px = tile.x - 2 * AutomapOffset.deltaX - ViewPosition.x; const int py = tile.y - 2 * AutomapOffset.deltaY - ViewPosition.y; Displacement playerOffset = {}; if (player.isWalking()) playerOffset = GetOffsetForWalking(player.AnimInfo, player._pdir); const int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; Point base = { ((playerOffset.deltaX + myPlayerOffset.deltaX) * scale / 100 / 2) + (px - py) * AmLine(AmLineLength::DoubleTile), ((playerOffset.deltaY + myPlayerOffset.deltaY) * scale / 100 / 2) + (px + py) * AmLine(AmLineLength::FullTile) + AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileUp).deltaY }; base += GetAutomapScreen(); if (CanPanelsCoverView()) { if (IsRightPanelOpen()) base.x -= gnScreenWidth / 4; if (IsLeftPanelOpen()) base.x += gnScreenWidth / 4; } switch (player._pdir) { case Direction::North: { const Point point = base + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp); DrawMapLineNS(out, point, AmLine(AmLineLength::DoubleTile), playerColor); DrawMapLineSteepNE(out, point + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::HalfTile), playerColor); DrawMapLineSteepNW(out, point + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::NorthEast: { const Point point = base + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileUp); DrawMapLineWE(out, point + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::None), AmLine(AmLineLength::FullTile), playerColor); DrawMapLineNE(out, point + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::FullTile), playerColor); DrawMapLineSteepSW(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::East: { const Point point = base + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None); DrawMapLineNW(out, point, AmLine(AmLineLength::HalfTile), playerColor); DrawMapLineWE(out, point + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None), AmLine(AmLineLength::DoubleTile), playerColor); DrawMapLineSW(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::SouthEast: { const Point point = base + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown); DrawMapLineSteepNW(out, point, AmLine(AmLineLength::HalfTile), playerColor); DrawMapLineSE(out, point + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), playerColor); DrawMapLineWE(out, point + AmOffset(AmWidthOffset::QuarterTileLeft, AmHeightOffset::None) + Displacement { -1, 0 }, AmLine(AmLineLength::FullTile) + 1, playerColor); } break; case Direction::South: { const Point point = base + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown); DrawMapLineNS(out, point + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileUp), AmLine(AmLineLength::DoubleTile), playerColor); DrawMapLineSteepSW(out, point + AmOffset(AmWidthOffset::EighthTileRight, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), playerColor); DrawMapLineSteepSE(out, point + AmOffset(AmWidthOffset::EighthTileLeft, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::SouthWest: { const Point point = base + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileDown); DrawMapLineSteepNE(out, point, AmLine(AmLineLength::HalfTile), playerColor); DrawMapLineSW(out, point + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileUp), AmLine(AmLineLength::FullTile), playerColor); DrawMapLineWE(out, point, AmLine(AmLineLength::FullTile) + 1, playerColor); } break; case Direction::West: { const Point point = base + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::None); DrawMapLineNE(out, point, AmLine(AmLineLength::HalfTile), playerColor); DrawMapLineWE(out, point, AmLine(AmLineLength::DoubleTile) + 1, playerColor); DrawMapLineSE(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::NorthWest: { const Point point = base + AmOffset(AmWidthOffset::HalfTileLeft, AmHeightOffset::HalfTileUp); DrawMapLineNW(out, point + AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::HalfTileDown), AmLine(AmLineLength::FullTile), playerColor); DrawMapLineWE(out, point, AmLine(AmLineLength::FullTile) + 1, playerColor); DrawMapLineSteepSE(out, point, AmLine(AmLineLength::HalfTile), playerColor); } break; case Direction::NoDirection: break; } } /** * @brief Renders game info, such as the name of the current level, and in multi player the name of the game and the game password. */ void DrawAutomapText(const Surface &out) { Point linePosition { 8, 8 }; auto advanceLine = [&](int numLines = 1) { linePosition.y += 15 * numLines; }; auto drawStringAndAdvanceLine = [&](std::string_view text, TextRenderOptions opts = {}, int numLines = 1) { DrawString(out, text, linePosition, opts); advanceLine(numLines); }; if (*GetOptions().Graphics.showFPS) { advanceLine(); } if (gbIsMultiplayer) { if (GameName != "0.0.0.0" && !IsLoopback) { std::string description = std::string(_("Game: ")); description.append(GameName); drawStringAndAdvanceLine(description); } std::string description; if (IsLoopback) { description = std::string(_("Offline Game")); } else if (PublicGame) { description = std::string(_("Public Game")); } else { description = std::string(_("Password: ")); description.append(GamePassword); } drawStringAndAdvanceLine(description); } if (setlevel) { drawStringAndAdvanceLine(_(QuestLevelNames[setlvlnum])); } else { std::string description; switch (leveltype) { case DTYPE_NEST: description = fmt::format(fmt::runtime(_("Level: Nest {:d}")), currlevel - 16); break; case DTYPE_CRYPT: description = fmt::format(fmt::runtime(_("Level: Crypt {:d}")), currlevel - 20); break; case DTYPE_TOWN: description = std::string(_("Town")); break; default: description = fmt::format(fmt::runtime(_("Level: {:d}")), currlevel); break; } drawStringAndAdvanceLine(description); } std::string_view difficulty; switch (sgGameInitInfo.nDifficulty) { case DIFF_NORMAL: difficulty = _("Normal"); break; case DIFF_NIGHTMARE: difficulty = _("Nightmare"); break; case DIFF_HELL: difficulty = _("Hell"); break; } const std::string description = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}")), difficulty); drawStringAndAdvanceLine(description); #ifdef _DEBUG if (DebugGodMode || DebugInvisible || DisableLighting || DebugVision || DebugPath || DebugGrid || DebugScrollViewEnabled) { const TextRenderOptions disabled { .flags = UiFlags::ColorBlack, }; const TextRenderOptions enabled { .flags = UiFlags::ColorOrange, }; advanceLine(); drawStringAndAdvanceLine("Debug toggles:"); drawStringAndAdvanceLine("Player:"); drawStringAndAdvanceLine("God Mode", DebugGodMode ? enabled : disabled); drawStringAndAdvanceLine("Invisible", DebugInvisible ? enabled : disabled); drawStringAndAdvanceLine("Display:"); drawStringAndAdvanceLine("Fullbright", DisableLighting ? enabled : disabled); drawStringAndAdvanceLine("Draw Vision", DebugVision ? enabled : disabled); drawStringAndAdvanceLine("Draw Path", DebugPath ? enabled : disabled); drawStringAndAdvanceLine("Draw Grid", DebugGrid ? enabled : disabled); drawStringAndAdvanceLine("Scroll View", DebugScrollViewEnabled ? enabled : disabled); } #endif } std::unique_ptr LoadAutomapData(size_t &tileCount) { switch (leveltype) { case DTYPE_TOWN: return LoadFileInMem("levels\\towndata\\automap.amp", &tileCount); case DTYPE_CATHEDRAL: return LoadFileInMem("levels\\l1data\\l1.amp", &tileCount); case DTYPE_CATACOMBS: return LoadFileInMem("levels\\l2data\\l2.amp", &tileCount); case DTYPE_CAVES: return LoadFileInMem("levels\\l3data\\l3.amp", &tileCount); case DTYPE_HELL: return LoadFileInMem("levels\\l4data\\l4.amp", &tileCount); case DTYPE_NEST: return LoadFileInMem("nlevels\\l6data\\l6.amp", &tileCount); case DTYPE_CRYPT: return LoadFileInMem("nlevels\\l5data\\l5.amp", &tileCount); default: return nullptr; } } } // namespace bool AutomapActive; AutomapType CurrentAutomapType = AutomapType::Opaque; uint8_t AutomapView[DMAXX][DMAXY]; int AutoMapScale; int MinimapScale; Displacement AutomapOffset; Rectangle MinimapRect {}; void InitAutomapOnce() { AutomapActive = false; AutoMapScale = 50; // Set the dimensions and screen position of the minimap relative to the screen dimensions const int minimapWidth = gnScreenWidth / 4; const Size minimapSize { minimapWidth, minimapWidth / 2 }; const int minimapPadding = gnScreenWidth / 128; MinimapRect = Rectangle { { gnScreenWidth - minimapPadding - minimapSize.width, minimapPadding }, minimapSize }; // Set minimap scale const int height = 480; const int scale = 25; const int factor = gnScreenHeight / height; if (factor >= 8) { MinimapScale = scale * 8; } else { MinimapScale = scale * factor; } } void InitAutomap() { size_t tileCount = 0; const std::unique_ptr tileTypes = LoadAutomapData(tileCount); switch (leveltype) { case DTYPE_CATACOMBS: tileTypes[41] = { AutomapTile::Types::FenceHorizontal }; break; case DTYPE_TOWN: // Town automap uses a dun file that contains caves tileset case DTYPE_CAVES: case DTYPE_NEST: tileTypes[4] = { AutomapTile::Types::CaveBottomCorner }; tileTypes[12] = { AutomapTile::Types::CaveRightCorner }; tileTypes[13] = { AutomapTile::Types::CaveLeftCorner }; if (IsAnyOf(leveltype, DTYPE_CAVES)) { tileTypes[129] = { AutomapTile::Types::CaveHorizontalWoodCross }; tileTypes[131] = { AutomapTile::Types::CaveHorizontalWoodCross }; tileTypes[133] = { AutomapTile::Types::CaveHorizontalWood }; tileTypes[135] = { AutomapTile::Types::CaveHorizontalWood }; tileTypes[150] = { AutomapTile::Types::CaveHorizontalWood }; tileTypes[145] = { AutomapTile::Types::CaveHorizontalWood, AutomapTile::Flags::VerticalDoor }; tileTypes[147] = { AutomapTile::Types::CaveHorizontalWood, AutomapTile::Flags::VerticalDoor }; tileTypes[130] = { AutomapTile::Types::CaveVerticalWoodCross }; tileTypes[132] = { AutomapTile::Types::CaveVerticalWoodCross }; tileTypes[134] = { AutomapTile::Types::CaveVerticalWood }; tileTypes[136] = { AutomapTile::Types::CaveVerticalWood }; tileTypes[151] = { AutomapTile::Types::CaveVerticalWood }; tileTypes[146] = { AutomapTile::Types::CaveVerticalWood, AutomapTile::Flags::HorizontalDoor }; tileTypes[148] = { AutomapTile::Types::CaveVerticalWood, AutomapTile::Flags::HorizontalDoor }; tileTypes[137] = { AutomapTile::Types::CaveWoodCross }; tileTypes[140] = { AutomapTile::Types::CaveWoodCross }; tileTypes[141] = { AutomapTile::Types::CaveWoodCross }; tileTypes[142] = { AutomapTile::Types::CaveWoodCross }; tileTypes[138] = { AutomapTile::Types::CaveRightWoodCross }; tileTypes[139] = { AutomapTile::Types::CaveLeftWoodCross }; tileTypes[14] = { AutomapTile::Types::HorizontalLavaThin }; tileTypes[15] = { AutomapTile::Types::HorizontalLavaThin }; tileTypes[16] = { AutomapTile::Types::VerticalLavaThin }; tileTypes[17] = { AutomapTile::Types::VerticalLavaThin }; tileTypes[18] = { AutomapTile::Types::BendSouthLavaThin }; tileTypes[19] = { AutomapTile::Types::BendWestLavaThin }; tileTypes[20] = { AutomapTile::Types::BendEastLavaThin }; tileTypes[21] = { AutomapTile::Types::BendNorthLavaThin }; tileTypes[22] = { AutomapTile::Types::VerticalWallLava }; tileTypes[23] = { AutomapTile::Types::HorizontalWallLava }; tileTypes[24] = { AutomapTile::Types::SELava }; tileTypes[25] = { AutomapTile::Types::SWLava }; tileTypes[26] = { AutomapTile::Types::NELava }; tileTypes[27] = { AutomapTile::Types::NWLava }; tileTypes[28] = { AutomapTile::Types::SLava }; tileTypes[29] = { AutomapTile::Types::WLava }; tileTypes[30] = { AutomapTile::Types::ELava }; tileTypes[31] = { AutomapTile::Types::NLava }; tileTypes[32] = { AutomapTile::Types::Lava }; tileTypes[33] = { AutomapTile::Types::Lava }; tileTypes[34] = { AutomapTile::Types::Lava }; tileTypes[35] = { AutomapTile::Types::Lava }; tileTypes[36] = { AutomapTile::Types::Lava }; tileTypes[37] = { AutomapTile::Types::Lava }; tileTypes[38] = { AutomapTile::Types::Lava }; tileTypes[39] = { AutomapTile::Types::Lava }; tileTypes[40] = { AutomapTile::Types::Lava }; tileTypes[41] = { AutomapTile::Types::CaveHorizontalWallLava }; tileTypes[42] = { AutomapTile::Types::CaveVerticalWallLava }; tileTypes[43] = { AutomapTile::Types::HorizontalBridgeLava }; tileTypes[44] = { AutomapTile::Types::VerticalBridgeLava }; } else if (IsAnyOf(leveltype, DTYPE_NEST)) { tileTypes[102] = { AutomapTile::Types::HorizontalLavaThin }; tileTypes[103] = { AutomapTile::Types::HorizontalLavaThin }; tileTypes[108] = { AutomapTile::Types::HorizontalLavaThin }; tileTypes[104] = { AutomapTile::Types::VerticalLavaThin }; tileTypes[105] = { AutomapTile::Types::VerticalLavaThin }; tileTypes[107] = { AutomapTile::Types::VerticalLavaThin }; tileTypes[112] = { AutomapTile::Types::BendSouthLavaThin }; tileTypes[113] = { AutomapTile::Types::BendWestLavaThin }; tileTypes[110] = { AutomapTile::Types::BendEastLavaThin }; tileTypes[111] = { AutomapTile::Types::BendNorthLavaThin }; tileTypes[134] = { AutomapTile::Types::VerticalWallLava }; tileTypes[135] = { AutomapTile::Types::HorizontalWallLava }; tileTypes[118] = { AutomapTile::Types::SELava }; tileTypes[119] = { AutomapTile::Types::SWLava }; tileTypes[120] = { AutomapTile::Types::NELava }; tileTypes[121] = { AutomapTile::Types::NWLava }; tileTypes[106] = { AutomapTile::Types::SLava }; tileTypes[114] = { AutomapTile::Types::WLava }; tileTypes[130] = { AutomapTile::Types::ELava }; tileTypes[122] = { AutomapTile::Types::NLava }; tileTypes[117] = { AutomapTile::Types::Lava }; tileTypes[124] = { AutomapTile::Types::Lava }; tileTypes[126] = { AutomapTile::Types::Lava }; tileTypes[127] = { AutomapTile::Types::Lava }; tileTypes[128] = { AutomapTile::Types::Lava }; tileTypes[129] = { AutomapTile::Types::Lava }; tileTypes[131] = { AutomapTile::Types::Lava }; tileTypes[132] = { AutomapTile::Types::Lava }; tileTypes[133] = { AutomapTile::Types::Lava }; tileTypes[136] = { AutomapTile::Types::CaveHorizontalWallLava }; tileTypes[137] = { AutomapTile::Types::CaveVerticalWallLava }; tileTypes[115] = { AutomapTile::Types::HorizontalBridgeLava }; tileTypes[116] = { AutomapTile::Types::VerticalBridgeLava }; } break; case DTYPE_HELL: tileTypes[51] = { AutomapTile::Types::VerticalDiamond }; tileTypes[55] = { AutomapTile::Types::HorizontalDiamond }; tileTypes[102] = { AutomapTile::Types::PentagramClosed }; tileTypes[111] = { AutomapTile::Types::PentagramOpen }; break; default: break; } for (unsigned i = 0; i < tileCount; i++) { AutomapTypeTiles[i + 1] = tileTypes[i]; } memset(AutomapView, 0, sizeof(AutomapView)); for (auto &column : dFlags) for (auto &dFlag : column) dFlag &= ~DungeonFlag::Explored; } void StartAutomap() { AutomapOffset = { 0, 0 }; AutomapActive = true; } void AutomapUp() { AutomapOffset.deltaX--; AutomapOffset.deltaY--; } void AutomapDown() { AutomapOffset.deltaX++; AutomapOffset.deltaY++; } void AutomapLeft() { AutomapOffset.deltaX--; AutomapOffset.deltaY++; } void AutomapRight() { AutomapOffset.deltaX++; AutomapOffset.deltaY--; } void AutomapZoomIn() { int &scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; if (scale >= 200) return; scale += 25; } void AutomapZoomOut() { int &scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; if (scale <= 25) return; scale -= 25; } void DrawAutomap(const Surface &out) { Automap = { (ViewPosition.x - 8) / 2, (ViewPosition.y - 8) / 2 }; if (leveltype != DTYPE_TOWN) { Automap += { -4, -4 }; } while (Automap.x + AutomapOffset.deltaX < 0) AutomapOffset.deltaX++; while (Automap.x + AutomapOffset.deltaX >= DMAXX) AutomapOffset.deltaX--; while (Automap.y + AutomapOffset.deltaY < 0) AutomapOffset.deltaY++; while (Automap.y + AutomapOffset.deltaY >= DMAXY) AutomapOffset.deltaY--; Automap += AutomapOffset; const Player &myPlayer = *MyPlayer; Displacement myPlayerOffset = {}; if (myPlayer.isWalking()) myPlayerOffset = GetOffsetForWalking(myPlayer.AnimInfo, myPlayer._pdir, true); const int scale = (GetAutomapType() == AutomapType::Minimap) ? MinimapScale : AutoMapScale; const int d = (scale * 64) / 100; int cells = 2 * (gnScreenWidth / 2 / d) + 1; if (((gnScreenWidth / 2) % d) != 0) cells++; if (((gnScreenWidth / 2) % d) >= (scale * 32) / 100) cells++; if ((myPlayerOffset.deltaX + myPlayerOffset.deltaY) != 0) cells++; if (GetAutomapType() == AutomapType::Minimap) { // Background fill DrawHalfTransparentRectTo(out, MinimapRect.position.x, MinimapRect.position.y, MinimapRect.size.width, MinimapRect.size.height); const uint8_t frameShadowColor = PAL16_YELLOW + 12; // Shadow DrawHorizontalLine(out, MinimapRect.position + Displacement { -1, -1 }, MinimapRect.size.width + 1, frameShadowColor); DrawHorizontalLine(out, MinimapRect.position + Displacement { -2, MinimapRect.size.height + 1 }, MinimapRect.size.width + 4, frameShadowColor); DrawVerticalLine(out, MinimapRect.position + Displacement { -1, 0 }, MinimapRect.size.height, frameShadowColor); DrawVerticalLine(out, MinimapRect.position + Displacement { MinimapRect.size.width + 1, -2 }, MinimapRect.size.height + 3, frameShadowColor); // Frame DrawHorizontalLine(out, MinimapRect.position + Displacement { -2, -2 }, MinimapRect.size.width + 3, MapColorsDim); DrawHorizontalLine(out, MinimapRect.position + Displacement { -2, MinimapRect.size.height }, MinimapRect.size.width + 3, MapColorsDim); DrawVerticalLine(out, MinimapRect.position + Displacement { -2, -1 }, MinimapRect.size.height + 1, MapColorsDim); DrawVerticalLine(out, MinimapRect.position + Displacement { MinimapRect.size.width, -1 }, MinimapRect.size.height + 1, MapColorsDim); if (AutoMapShowItems) SearchAutomapItem(out, myPlayerOffset, 8, [](Point position) { return dItem[position.x][position.y] != 0; }); } Point screen = {}; screen += GetAutomapScreen(); if ((cells & 1) != 0) { screen.x -= AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX * ((cells - 1) / 2); screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::DoubleTileDown).deltaY * ((cells + 1) / 2); } else { screen.x -= AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX * (cells / 2) + AmOffset(AmWidthOffset::FullTileLeft, AmHeightOffset::None).deltaX; screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::DoubleTileDown).deltaY * (cells / 2) + AmOffset(AmWidthOffset::None, AmHeightOffset::FullTileDown).deltaY; } if ((ViewPosition.x & 1) != 0) { screen.x -= AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None).deltaX; screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown).deltaY; } if ((ViewPosition.y & 1) != 0) { screen.x += AmOffset(AmWidthOffset::HalfTileRight, AmHeightOffset::None).deltaX; screen.y -= AmOffset(AmWidthOffset::None, AmHeightOffset::HalfTileDown).deltaY; } screen.x += scale * myPlayerOffset.deltaX / 100 / 2; screen.y += scale * myPlayerOffset.deltaY / 100 / 2; if (CanPanelsCoverView()) { if (IsRightPanelOpen()) { screen.x -= gnScreenWidth / 4; } if (IsLeftPanelOpen()) { screen.x += gnScreenWidth / 4; } } Point map = { Automap.x - cells, Automap.y - 1 }; for (int i = 0; i <= cells + 1; i++) { Point tile1 = screen; for (int j = 0; j < cells; j++) { DrawAutomapTile(out, tile1, { map.x + j, map.y - j }); tile1.x += AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX; } map.y++; Point tile2 = screen + AmOffset(AmWidthOffset::FullTileLeft, AmHeightOffset::FullTileDown); for (int j = 0; j <= cells; j++) { DrawAutomapTile(out, tile2, { map.x + j, map.y - j }); tile2.x += AmOffset(AmWidthOffset::DoubleTileRight, AmHeightOffset::None).deltaX; } map.x++; screen.y += AmOffset(AmWidthOffset::None, AmHeightOffset::DoubleTileDown).deltaY; } for (const Player &player : Players) { if (player.isOnActiveLevel() && player.plractive && !player._pLvlChanging && (&player == MyPlayer || player.friendlyMode)) { DrawAutomapPlr(out, myPlayerOffset, player); } } if (AutoMapShowItems) SearchAutomapItem(out, myPlayerOffset, 8, [](Point position) { return dItem[position.x][position.y] != 0; }); #ifdef _DEBUG if (IsDebugAutomapHighlightNeeded()) SearchAutomapItem(out, myPlayerOffset, std::max(MAXDUNX, MAXDUNY), ShouldHighlightDebugAutomapTile); #endif DrawAutomapText(out); } void UpdateAutomapExplorer(Point map, MapExplorationType explorer) { if (AutomapView[map.x][map.y] < explorer) AutomapView[map.x][map.y] = explorer; } void SetAutomapView(Point position, MapExplorationType explorer) { const Point map { (position.x - 16) / 2, (position.y - 16) / 2 }; if (map.x < 0 || map.x >= DMAXX || map.y < 0 || map.y >= DMAXY) { return; } UpdateAutomapExplorer(map, explorer); const AutomapTile tile = GetAutomapTileType(map); const bool solid = tile.hasFlag(AutomapTile::Flags::Dirt); switch (tile.type) { case AutomapTile::Types::Vertical: if (solid) { auto tileSW = GetAutomapTileType({ map.x, map.y + 1 }); if (tileSW.type == AutomapTile::Types::Corner && tileSW.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y + 1 }, explorer); } else if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); } break; case AutomapTile::Types::Horizontal: if (solid) { auto tileSE = GetAutomapTileType({ map.x + 1, map.y }); if (tileSE.type == AutomapTile::Types::Corner && tileSE.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x + 1, map.y }, explorer); } else if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); } break; case AutomapTile::Types::Cross: if (solid) { auto tileSW = GetAutomapTileType({ map.x, map.y + 1 }); if (tileSW.type == AutomapTile::Types::Corner && tileSW.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y + 1 }, explorer); auto tileSE = GetAutomapTileType({ map.x + 1, map.y }); if (tileSE.type == AutomapTile::Types::Corner && tileSE.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x + 1, map.y }, explorer); } else { if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); if (HasAutomapFlag({ map.x - 1, map.y - 1 }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x - 1, map.y - 1 }, explorer); } break; case AutomapTile::Types::FenceVertical: if (solid) { if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); auto tileSW = GetAutomapTileType({ map.x, map.y + 1 }); if (tileSW.type == AutomapTile::Types::Corner && tileSW.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x, map.y + 1 }, explorer); } else if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); } break; case AutomapTile::Types::FenceHorizontal: if (solid) { if (HasAutomapFlag({ map.x - 1, map.y }, AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x - 1, map.y }, explorer); auto tileSE = GetAutomapTileType({ map.x + 1, map.y }); if (tileSE.type == AutomapTile::Types::Corner && tileSE.hasFlag(AutomapTile::Flags::Dirt)) UpdateAutomapExplorer({ map.x + 1, map.y }, explorer); } else if (HasAutomapFlag({ map.x, map.y - 1 }, AutomapTile::Flags::Dirt)) { UpdateAutomapExplorer({ map.x, map.y - 1 }, explorer); } break; default: break; } } void AutomapZoomReset() { AutomapOffset = { 0, 0 }; } } // namespace devilution