mirror of
https://github.com/diasurgical/DevilutionX.git
synced 2026-05-21 05:40:35 +00:00
1966 lines
85 KiB
C++
1966 lines
85 KiB
C++
/**
|
|
* @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 */
|
|
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<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 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, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t GetPlayerMapColor(int id)
|
|
{
|
|
static constexpr uint8_t PlayerMapColors[] = {
|
|
MapColorsPlayer1,
|
|
MapColorsPlayer2,
|
|
MapColorsPlayer3,
|
|
MapColorsPlayer4,
|
|
};
|
|
|
|
if (id < 0 || id >= static_cast<int>(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<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
|