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.
 
 
 
 
 
 

1947 lines
85 KiB

/**
* @file automap.cpp
*
* Implementation of the in-game map overlay.
*/
#include "automap.h"
#include <algorithm>
#include <cstdint>
#include <fmt/format.h>
#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 */
MapColorsPlayer = (PAL8_ORANGE + 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<uint8_t>(flags) & static_cast<uint8_t>(test)) != 0;
}
template <typename... Args>
[[nodiscard]] DVL_ALWAYS_INLINE constexpr bool hasAnyFlag(Flags flag, Args... testFlags)
{
return (static_cast<uint8_t>(this->flags)
& (static_cast<uint8_t>(flag) | ... | static_cast<uint8_t>(testFlags)))
!= 0;
}
};
/**
* Maps from tile_id to automap type.
*/
std::array<AutomapTile, 256> 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 <Direction TDir1, Direction TDir2>
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 <Direction TDir>
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<MapExplorationType>(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<Direction::NorthWest, Direction::SouthEast>(out, center, lavaColor, false);
break;
case AutomapTile::Types::VerticalLavaThin:
DrawLavaRiver<Direction::NorthEast, Direction::SouthWest>(out, center, lavaColor, false);
break;
case AutomapTile::Types::BendSouthLavaThin:
DrawLavaRiver<Direction::SouthWest, Direction::SouthEast>(out, center, lavaColor, false);
break;
case AutomapTile::Types::BendWestLavaThin:
DrawLavaRiver<Direction::NorthWest, Direction::SouthWest>(out, center, lavaColor, false);
break;
case AutomapTile::Types::BendEastLavaThin:
DrawLavaRiver<Direction::NorthEast, Direction::SouthEast>(out, center, lavaColor, false);
break;
case AutomapTile::Types::BendNorthLavaThin:
DrawLavaRiver<Direction::NorthWest, Direction::NorthEast>(out, center, lavaColor, false);
break;
case AutomapTile::Types::VerticalWallLava:
DrawVertical(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim);
DrawLavaRiver<Direction::SouthEast, Direction::NoDirection>(out, center, lavaColor, false);
break;
case AutomapTile::Types::HorizontalWallLava:
DrawHorizontal(out, center, tile, nwTile, neTile, swTile, colorBright, colorDim);
DrawLavaRiver<Direction::SouthWest, Direction::NoDirection>(out, center, lavaColor, false);
break;
case AutomapTile::Types::SELava:
DrawLava<Direction::SouthEast>(out, center, lavaColor);
break;
case AutomapTile::Types::SWLava:
DrawLava<Direction::SouthWest>(out, center, lavaColor);
break;
case AutomapTile::Types::NELava:
DrawLava<Direction::NorthEast>(out, center, lavaColor);
break;
case AutomapTile::Types::NWLava:
DrawLava<Direction::NorthWest>(out, center, lavaColor);
break;
case AutomapTile::Types::SLava:
DrawLava<Direction::South>(out, center, lavaColor);
break;
case AutomapTile::Types::WLava:
DrawLava<Direction::West>(out, center, lavaColor);
break;
case AutomapTile::Types::ELava:
DrawLava<Direction::East>(out, center, lavaColor);
break;
case AutomapTile::Types::NLava:
DrawLava<Direction::North>(out, center, lavaColor);
break;
case AutomapTile::Types::Lava:
DrawLava<Direction::NoDirection>(out, center, lavaColor);
break;
case AutomapTile::Types::CaveHorizontalWallLava:
DrawCaveHorizontal(out, center, tile, nwTile, swTile, colorBright, colorDim);
DrawLavaRiver<Direction::NorthEast, Direction::NoDirection>(out, center, lavaColor, false);
break;
case AutomapTile::Types::CaveVerticalWallLava:
DrawCaveVertical(out, center, tile, neTile, seTile, colorBright, colorDim);
DrawLavaRiver<Direction::NorthWest, Direction::NoDirection>(out, center, lavaColor, false);
break;
case AutomapTile::Types::HorizontalBridgeLava:
DrawLavaRiver<Direction::NorthWest, Direction::SouthEast>(out, center, lavaColor, true);
break;
case AutomapTile::Types::VerticalBridgeLava:
DrawLavaRiver<Direction::NorthEast, Direction::SouthWest>(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<bool(Point position)> 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);
}
}
}
/**
* @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 = MapColorsPlayer + (8 * player.getId()) % 128;
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<AutomapTile[]> LoadAutomapData(size_t &tileCount)
{
switch (leveltype) {
case DTYPE_TOWN:
return LoadFileInMem<AutomapTile>("levels\\towndata\\automap.amp", &tileCount);
case DTYPE_CATHEDRAL:
return LoadFileInMem<AutomapTile>("levels\\l1data\\l1.amp", &tileCount);
case DTYPE_CATACOMBS:
return LoadFileInMem<AutomapTile>("levels\\l2data\\l2.amp", &tileCount);
case DTYPE_CAVES:
return LoadFileInMem<AutomapTile>("levels\\l3data\\l3.amp", &tileCount);
case DTYPE_HELL:
return LoadFileInMem<AutomapTile>("levels\\l4data\\l4.amp", &tileCount);
case DTYPE_NEST:
return LoadFileInMem<AutomapTile>("nlevels\\l6data\\l6.amp", &tileCount);
case DTYPE_CRYPT:
return LoadFileInMem<AutomapTile>("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<AutomapTile[]> 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