Browse Source

`WorldTileRectangle/Size`

Adds a custom sized type for the world tile rectagle.

This allows us to better express intent.
It also allows us to make certain globals smaller, e.g. `THEME_LOC`.
pull/5416/head
Gleb Mazovetskiy 3 years ago
parent
commit
759ca7f055
  1. 14
      Source/controls/plrctrls.cpp
  2. 7
      Source/engine/displacement.hpp
  3. 4
      Source/engine/point.hpp
  4. 79
      Source/engine/points_in_rectangle_range.hpp
  5. 48
      Source/engine/rectangle.hpp
  6. 43
      Source/engine/size.hpp
  7. 5
      Source/engine/world_tile.hpp
  8. 4
      Source/levels/crypt.cpp
  9. 18
      Source/levels/drlg_l1.cpp
  10. 3
      Source/levels/drlg_l1.h
  11. 66
      Source/levels/drlg_l2.cpp
  12. 22
      Source/levels/drlg_l3.cpp
  13. 68
      Source/levels/drlg_l4.cpp
  14. 9
      Source/levels/drlg_l4.h
  15. 74
      Source/levels/gendung.cpp
  16. 39
      Source/levels/gendung.h
  17. 6
      Source/levels/setmaps.cpp
  18. 6
      Source/levels/themes.cpp
  19. 5
      Source/missiles.cpp
  20. 6
      Source/monster.cpp
  21. 20
      Source/objects.cpp
  22. 13
      Source/objects.h
  23. 2
      Source/player.cpp
  24. 10
      Source/qol/stash.cpp
  25. 13
      Source/quests.cpp
  26. 1
      test/CMakeLists.txt
  27. 27
      test/drlg_common_test.cpp
  28. 33
      test/rectangle_test.cpp

14
Source/controls/plrctrls.cpp

@ -150,12 +150,12 @@ int GetDistanceRanged(Point destination)
void FindItemOrObject()
{
Point futurePosition = MyPlayer->position.future;
WorldTilePosition futurePosition = MyPlayer->position.future;
int rotations = 5;
auto searchArea = PointsInRectangleRangeColMajor { Rectangle { futurePosition, 1 } };
auto searchArea = PointsInRectangleColMajor(WorldTileRectangle { futurePosition, 1 });
for (Point targetPosition : searchArea) {
for (WorldTilePosition targetPosition : searchArea) {
// As the player can not stand on the edge of the map this is safe from OOB
int8_t itemId = dItem[targetPosition.x][targetPosition.y] - 1;
if (itemId < 0) {
@ -185,7 +185,7 @@ void FindItemOrObject()
return; // Don't look for objects in town
}
for (Point targetPosition : searchArea) {
for (WorldTilePosition targetPosition : searchArea) {
Object *object = FindObjectAtPosition(targetPosition);
if (object == nullptr || object->_oSelFlag == 0) {
// No object or non-interactive object
@ -701,7 +701,7 @@ Point FindFirstStashSlotOnItem(StashStruct::StashCell itemInvId)
if (itemInvId == StashStruct::EmptyCell)
return InvalidStashPoint;
for (auto point : PointsInRectangleRange({ { 0, 0 }, Size { 10, 10 } })) {
for (WorldTilePosition point : PointsInRectangle(WorldTileRectangle { { 0, 0 }, { 10, 10 } })) {
if (Stash.GetItemIdAtPosition(point) == itemInvId)
return point;
}
@ -770,7 +770,7 @@ Point FindClosestStashSlot(Point mousePos)
Point bestSlot = {};
mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX };
for (auto point : PointsInRectangleRange({ { 0, 0 }, Size { 10, 10 } })) {
for (Point point : PointsInRectangle(Rectangle { { 0, 0 }, Size { 10, 10 } })) {
int distance = mousePos.ManhattanDistance(GetStashSlotCoord(point));
if (distance < shortestDistance) {
shortestDistance = distance;
@ -1781,7 +1781,7 @@ void PerformPrimaryAction()
StashStruct::StashCell itemUnderCursor = [](Point stashSlot, Size cursorSizeInCells) -> StashStruct::StashCell {
if (stashSlot != InvalidStashPoint)
return StashStruct::EmptyCell;
for (Point slotUnderCursor : PointsInRectangleRange { { stashSlot, cursorSizeInCells } }) {
for (Point slotUnderCursor : PointsInRectangle(Rectangle { stashSlot, cursorSizeInCells })) {
if (slotUnderCursor.x >= 10 || slotUnderCursor.y >= 10)
continue;
StashStruct::StashCell itemId = Stash.GetItemIdAtPosition(slotUnderCursor);

7
Source/engine/displacement.hpp

@ -5,8 +5,8 @@
#include <ostream>
#endif
#include "direction.hpp"
#include "size.hpp"
#include "engine/direction.hpp"
#include "engine/size.hpp"
#include "utils/stdcompat/abs.hpp"
namespace devilution {
@ -42,7 +42,8 @@ struct DisplacementOf {
{
}
explicit constexpr DisplacementOf(const Size &size)
template <typename SizeT>
explicit constexpr DisplacementOf(const SizeOf<SizeT> &size)
: deltaX(size.width)
, deltaY(size.height)
{

4
Source/engine/point.hpp

@ -151,7 +151,7 @@ struct PointOf {
*/
constexpr PointOf<CoordT> megaToWorld() const
{
return { 16 + 2 * x, 16 + 2 * y };
return { static_cast<CoordT>(16 + 2 * x), static_cast<CoordT>(16 + 2 * y) };
}
/**
@ -159,7 +159,7 @@ struct PointOf {
*/
constexpr PointOf<CoordT> worldToMega() const
{
return { (x - 16) / 2, (y - 16) / 2 };
return { static_cast<CoordT>((x - 16) / 2), static_cast<CoordT>((y - 16) / 2) };
}
};

79
Source/engine/points_in_rectangle_range.hpp

@ -7,16 +7,17 @@
namespace devilution {
template <typename CoordT>
class PointsInRectangleIteratorBase {
public:
using iterator_category = std::random_access_iterator_tag;
using difference_type = int;
using value_type = Point;
using difference_type = CoordT;
using value_type = PointOf<CoordT>;
using pointer = void;
using reference = value_type;
protected:
constexpr PointsInRectangleIteratorBase(Point origin, int majorDimension, int majorIndex, int minorIndex)
constexpr PointsInRectangleIteratorBase(PointOf<CoordT> origin, int majorDimension, int majorIndex, int minorIndex)
: origin(origin)
, majorDimension(majorDimension)
, majorIndex(majorIndex)
@ -24,7 +25,7 @@ protected:
{
}
explicit constexpr PointsInRectangleIteratorBase(Point origin, int majorDimension, int index = 0)
explicit constexpr PointsInRectangleIteratorBase(PointOf<CoordT> origin, int majorDimension, int index = 0)
: PointsInRectangleIteratorBase(origin, majorDimension, index / majorDimension, index % majorDimension)
{
}
@ -57,7 +58,7 @@ protected:
}
}
Point origin;
PointOf<CoordT> origin;
int majorDimension;
@ -65,19 +66,26 @@ protected:
int minorIndex;
};
template <typename CoordT>
class PointsInRectangleRange {
public:
using const_iterator = class PointsInRectangleIterator : public PointsInRectangleIteratorBase {
using const_iterator = class PointsInRectangleIterator : public PointsInRectangleIteratorBase<CoordT> {
public:
constexpr PointsInRectangleIterator(Rectangle region, int index = 0)
: PointsInRectangleIteratorBase(region.position, region.size.width, index)
using iterator_category = typename PointsInRectangleIteratorBase<CoordT>::iterator_category;
using difference_type = typename PointsInRectangleIteratorBase<CoordT>::difference_type;
using value_type = typename PointsInRectangleIteratorBase<CoordT>::value_type;
using pointer = typename PointsInRectangleIteratorBase<CoordT>::pointer;
using reference = typename PointsInRectangleIteratorBase<CoordT>::reference;
constexpr PointsInRectangleIterator(RectangleOf<CoordT> region, int index = 0)
: PointsInRectangleIteratorBase<CoordT>(region.position, region.size.width, index)
{
}
value_type operator*() const
{
// Row-major iteration e.g. {0, 0}, {1, 0}, {2, 0}, {0, 1}, {1, 1}, ...
return origin + Displacement { minorIndex, majorIndex };
return this->origin + Displacement { this->minorIndex, this->majorIndex };
}
// Equality comparable concepts
@ -114,13 +122,13 @@ public:
difference_type operator-(const PointsInRectangleIterator &rhs) const
{
return (this->majorIndex - rhs.majorIndex) * majorDimension + (this->minorIndex - rhs.minorIndex);
return (this->majorIndex - rhs.majorIndex) * this->majorDimension + (this->minorIndex - rhs.minorIndex);
}
// Forward concepts
PointsInRectangleIterator &operator++()
{
Increment();
this->Increment();
return *this;
}
@ -134,7 +142,7 @@ public:
// Bidirectional concepts
PointsInRectangleIterator &operator--()
{
Decrement();
this->Decrement();
return *this;
}
@ -154,7 +162,7 @@ public:
PointsInRectangleIterator &operator+=(difference_type delta)
{
Offset(delta);
this->Offset(delta);
return *this;
}
@ -180,7 +188,7 @@ public:
}
};
constexpr PointsInRectangleRange(Rectangle region)
constexpr PointsInRectangleRange(RectangleOf<CoordT> region)
: region(region)
{
}
@ -228,22 +236,35 @@ public:
}
protected:
Rectangle region;
RectangleOf<CoordT> region;
};
class PointsInRectangleRangeColMajor {
template <typename CoordT>
PointsInRectangleRange<CoordT> PointsInRectangle(RectangleOf<CoordT> region)
{
return PointsInRectangleRange<CoordT> { region };
}
template <typename CoordT>
class PointsInRectangleColMajorRange {
public:
using const_iterator = class PointsInRectangleIteratorColMajor : public PointsInRectangleIteratorBase {
using const_iterator = class PointsInRectangleIteratorColMajor : public PointsInRectangleIteratorBase<CoordT> {
public:
constexpr PointsInRectangleIteratorColMajor(Rectangle region, int index = 0)
: PointsInRectangleIteratorBase(region.position, region.size.height, index)
using iterator_category = typename PointsInRectangleIteratorBase<CoordT>::iterator_category;
using difference_type = typename PointsInRectangleIteratorBase<CoordT>::difference_type;
using value_type = typename PointsInRectangleIteratorBase<CoordT>::value_type;
using pointer = typename PointsInRectangleIteratorBase<CoordT>::pointer;
using reference = typename PointsInRectangleIteratorBase<CoordT>::reference;
constexpr PointsInRectangleIteratorColMajor(RectangleOf<CoordT> region, int index = 0)
: PointsInRectangleIteratorBase<CoordT>(region.position, region.size.height, index)
{
}
value_type operator*() const
{
// Col-major iteration e.g. {0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, ...
return origin + Displacement { majorIndex, minorIndex };
return this->origin + Displacement { this->majorIndex, this->minorIndex };
}
// Equality comparable concepts
@ -280,13 +301,13 @@ public:
difference_type operator-(const PointsInRectangleIteratorColMajor &rhs) const
{
return (this->majorIndex - rhs.majorIndex) * majorDimension + (this->minorIndex - rhs.minorIndex);
return (this->majorIndex - rhs.majorIndex) * this->majorDimension + (this->minorIndex - rhs.minorIndex);
}
// Forward concepts
PointsInRectangleIteratorColMajor &operator++()
{
Increment();
this->Increment();
return *this;
}
@ -300,7 +321,7 @@ public:
// Bidirectional concepts
PointsInRectangleIteratorColMajor &operator--()
{
Decrement();
this->Decrement();
return *this;
}
@ -320,7 +341,7 @@ public:
PointsInRectangleIteratorColMajor &operator+=(difference_type delta)
{
Offset(delta);
this->Offset(delta);
return *this;
}
@ -347,7 +368,7 @@ public:
};
// gcc6 needs a defined constructor?
constexpr PointsInRectangleRangeColMajor(Rectangle region)
constexpr PointsInRectangleColMajorRange(RectangleOf<CoordT> region)
: region(region)
{
}
@ -395,7 +416,13 @@ public:
}
protected:
Rectangle region;
RectangleOf<CoordT> region;
};
template <typename CoordT = int>
PointsInRectangleColMajorRange<CoordT> PointsInRectangleColMajor(RectangleOf<CoordT> region)
{
return PointsInRectangleColMajorRange<CoordT> { region };
}
} // namespace devilution

48
Source/engine/rectangle.hpp

@ -5,13 +5,14 @@
namespace devilution {
struct Rectangle {
Point position;
Size size;
template <typename CoordT, typename SizeT = CoordT>
struct RectangleOf {
PointOf<CoordT> position;
SizeOf<SizeT> size;
Rectangle() = default;
RectangleOf() = default;
constexpr Rectangle(Point position, Size size)
constexpr RectangleOf(PointOf<CoordT> position, SizeOf<SizeT> size)
: position(position)
, size(size)
{
@ -25,26 +26,37 @@ struct Rectangle {
* @param center center point of the target rectangle
* @param radius a non-negative value indicating how many tiles to include around the center
*/
explicit constexpr Rectangle(Point center, int radius)
: position(center - Displacement { radius })
, size(2 * radius + 1)
explicit constexpr RectangleOf(PointOf<CoordT> center, SizeT radius)
: position(center - DisplacementOf<SizeT> { radius })
, size(static_cast<SizeT>(2 * radius + 1))
{
}
constexpr bool contains(Point point) const
/**
* @brief Whether this rectangle contains the given point.
* Works correctly even if the point uses a different underlying numeric type
*/
template <typename PointCoordT>
constexpr bool contains(PointOf<PointCoordT> point) const
{
return contains(point.x, point.y);
}
template <typename T>
constexpr bool contains(T x, T y) const
{
return point.x >= this->position.x
&& point.x < (this->position.x + this->size.width)
&& point.y >= this->position.y
&& point.y < (this->position.y + this->size.height);
return x >= this->position.x
&& x < (this->position.x + this->size.width)
&& y >= this->position.y
&& y < (this->position.y + this->size.height);
}
/**
* @brief Computes the center of this rectangle in integer coordinates. Values are truncated towards zero.
*/
constexpr Point Center() const
constexpr PointOf<CoordT> Center() const
{
return position + Displacement(size / 2);
return position + DisplacementOf<SizeT>(size / 2);
}
/**
@ -52,13 +64,15 @@ struct Rectangle {
*
* Effectively moves the left/right edges in by deltaX, and the top/bottom edges in by deltaY
*/
constexpr Rectangle inset(Displacement factor) const
constexpr RectangleOf<CoordT, SizeT> inset(DisplacementOf<SizeT> factor) const
{
return {
position + factor,
Size { size.width - factor.deltaX * 2, size.height - factor.deltaY * 2 }
SizeOf<SizeT>(size.width - factor.deltaX * 2, size.height - factor.deltaY * 2)
};
}
};
using Rectangle = RectangleOf<int, int>;
} // namespace devilution

43
Source/engine/size.hpp

@ -6,86 +6,87 @@
namespace devilution {
struct Size {
int width;
int height;
template <typename SizeT>
struct SizeOf {
SizeT width;
SizeT height;
Size() = default;
SizeOf() = default;
constexpr Size(int width, int height)
constexpr SizeOf(SizeT width, SizeT height)
: width(width)
, height(height)
{
}
explicit constexpr Size(int size)
explicit constexpr SizeOf(SizeT size)
: width(size)
, height(size)
{
}
bool operator==(const Size &other) const
bool operator==(const SizeOf<SizeT> &other) const
{
return width == other.width && height == other.height;
}
bool operator!=(const Size &other) const
bool operator!=(const SizeOf<SizeT> &other) const
{
return !(*this == other);
}
constexpr Size &operator+=(const int factor)
constexpr SizeOf<SizeT> &operator+=(SizeT factor)
{
width += factor;
height += factor;
return *this;
}
constexpr Size &operator-=(const int factor)
constexpr SizeOf<SizeT> &operator-=(SizeT factor)
{
return *this += -factor;
}
constexpr Size &operator*=(const int factor)
constexpr SizeOf<SizeT> &operator*=(SizeT factor)
{
width *= factor;
height *= factor;
return *this;
}
constexpr Size &operator*=(const float factor)
constexpr SizeOf<SizeT> &operator*=(float factor)
{
width = static_cast<int>(width * factor);
height = static_cast<int>(height * factor);
width = static_cast<SizeT>(width * factor);
height = static_cast<SizeT>(height * factor);
return *this;
}
constexpr Size &operator/=(const int factor)
constexpr SizeOf<SizeT> &operator/=(SizeT factor)
{
width /= factor;
height /= factor;
return *this;
}
constexpr friend Size operator+(Size a, const int factor)
constexpr friend SizeOf<SizeT> operator+(SizeOf<SizeT> a, SizeT factor)
{
a += factor;
return a;
}
constexpr friend Size operator-(Size a, const int factor)
constexpr friend SizeOf<SizeT> operator-(SizeOf<SizeT> a, SizeT factor)
{
a -= factor;
return a;
}
constexpr friend Size operator*(Size a, const int factor)
constexpr friend SizeOf<SizeT> operator*(SizeOf<SizeT> a, SizeT factor)
{
a *= factor;
return a;
}
constexpr friend Size operator/(Size a, const int factor)
constexpr friend SizeOf<SizeT> operator/(SizeOf<SizeT> a, SizeT factor)
{
a /= factor;
return a;
@ -98,11 +99,13 @@ struct Size {
* @param size Object to display
* @return the stream, to allow chaining
*/
friend std::ostream &operator<<(std::ostream &stream, const Size &size)
friend std::ostream &operator<<(std::ostream &stream, const SizeOf<SizeT> &size)
{
return stream << "(width: " << size.width << ", height: " << size.height << ")";
}
#endif
};
using Size = SizeOf<int>;
} // namespace devilution

5
Source/engine/world_tile.hpp

@ -1,6 +1,8 @@
#pragma once
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
#include "engine/size.hpp"
namespace devilution {
@ -10,6 +12,9 @@ using WorldTilePosition = PointOf<WorldTileCoord>;
using WorldTileOffset = int8_t;
using WorldTileDisplacement = DisplacementOf<WorldTileOffset>;
using WorldTileSize = SizeOf<WorldTileCoord>;
using WorldTileRectangle = RectangleOf<WorldTileCoord>;
} // namespace devilution
namespace std {

4
Source/levels/crypt.cpp

@ -686,7 +686,7 @@ void SetCryptRoom()
auto dunData = LoadFileInMem<uint16_t>("nlevels\\l5data\\uberroom.dun");
SetPiece = { position, { dunData[0], dunData[1] } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) };
PlaceDunTiles(dunData.get(), position, 0);
}
@ -697,7 +697,7 @@ void SetCornerRoom()
auto dunData = LoadFileInMem<uint16_t>("nlevels\\l5data\\cornerstone.dun");
SetPiece = { position, { dunData[0], dunData[1] } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) };
PlaceDunTiles(dunData.get(), position, 0);
}

18
Source/levels/drlg_l1.cpp

@ -456,7 +456,9 @@ void GenerateRoom(Rectangle area, bool verticalLayout)
Rectangle room1;
for (int num = 0; num < 20; num++) {
room1.size = { (GenerateRnd(5) + 2) & ~1, (GenerateRnd(5) + 2) & ~1 };
const int32_t randomWidth = (GenerateRnd(5) + 2) & ~1;
const int32_t randomHeight = (GenerateRnd(5) + 2) & ~1;
room1.size = { randomWidth, randomHeight };
room1.position = area.position;
if (verticalLayout) {
room1.position += Displacement { -room1.size.width, area.size.height / 2 - room1.size.height / 2 };
@ -1239,11 +1241,11 @@ void Pass3()
void PlaceMiniSetRandom(const Miniset &miniset, int rndper)
{
int sw = miniset.size.width;
int sh = miniset.size.height;
const WorldTileCoord sw = miniset.size.width;
const WorldTileCoord sh = miniset.size.height;
for (int sy = 0; sy < DMAXY - sh; sy++) {
for (int sx = 0; sx < DMAXX - sw; sx++) {
for (WorldTileCoord sy = 0; sy < DMAXY - sh; sy++) {
for (WorldTileCoord sx = 0; sx < DMAXX - sw; sx++) {
if (!miniset.matches({ sx, sy }, false))
continue;
// BUGFIX: This code is copied from Cave and should not be applied for crypt
@ -1256,7 +1258,7 @@ void PlaceMiniSetRandom(const Miniset &miniset, int rndper)
}
}
Point SelectChamber()
WorldTilePosition SelectChamber()
{
int chamber;
if (HasChamber1 && HasChamber2 && HasChamber3) {
@ -1276,9 +1278,9 @@ Point SelectChamber()
switch (chamber) {
case 1:
return VerticalLayout ? Point { 16, 2 } : Point { 2, 16 };
return VerticalLayout ? WorldTilePosition { 16, 2 } : WorldTilePosition { 2, 16 };
case 3:
return VerticalLayout ? Point { 16, 30 } : Point { 30, 16 };
return VerticalLayout ? WorldTilePosition { 16, 30 } : WorldTilePosition { 30, 16 };
default:
return { 16, 16 };
}

3
Source/levels/drlg_l1.h

@ -5,12 +5,13 @@
*/
#pragma once
#include "engine/world_tile.hpp"
#include "levels/gendung.h"
namespace devilution {
void PlaceMiniSetRandom(const Miniset &miniset, int rndper);
Point SelectChamber();
WorldTilePosition SelectChamber();
void CreateL5Dungeon(uint32_t rseed, lvl_entry entry);
void LoadPreL1Dungeon(const char *path);
void LoadL1Dungeon(const char *path, Point spawn);

66
Source/levels/drlg_l2.cpp

@ -1595,12 +1595,12 @@ void ApplyShadowsPatterns()
void PlaceMiniSetRandom(const Miniset &miniset, int rndper)
{
int sw = miniset.size.width;
int sh = miniset.size.height;
const WorldTileCoord sw = miniset.size.width;
const WorldTileCoord sh = miniset.size.height;
for (int sy = 0; sy < DMAXY - sh; sy++) {
for (int sx = 0; sx < DMAXX - sw; sx++) {
if (SetPieceRoom.contains({ sx, sy }))
for (WorldTileCoord sy = 0; sy < DMAXY - sh; sy++) {
for (WorldTileCoord sx = 0; sx < DMAXX - sw; sx++) {
if (SetPieceRoom.contains(sx, sy))
continue;
if (!miniset.matches({ sx, sy }))
continue;
@ -1752,21 +1752,18 @@ void PlaceHallExt(Point position)
* @param nHDir The direction of the hall from nRDest to this room.
* @param size If set, is is used used for room size instead of random values.
*/
void CreateRoom(Point topLeft, Point bottomRight, int nRDest, HallDirection nHDir, std::optional<Size> size)
void CreateRoom(WorldTilePosition topLeft, WorldTilePosition bottomRight, int nRDest, HallDirection nHDir, std::optional<WorldTileSize> size)
{
if (nRoomCnt >= 80)
return;
Displacement areaDisplacement = bottomRight - topLeft;
Size area { areaDisplacement.deltaX, areaDisplacement.deltaY };
constexpr int AreaMin = 2;
if (area.width < AreaMin || area.height < AreaMin)
if (nRoomCnt >= 80 || topLeft.x + AreaMin > bottomRight.x || topLeft.y + AreaMin > bottomRight.y)
return;
constexpr int RoomMax = 10;
constexpr int RoomMin = 4;
Size roomSize = area;
WorldTileDisplacement areaDisplacement = bottomRight - topLeft;
WorldTileSize area(areaDisplacement.deltaX, areaDisplacement.deltaY);
constexpr WorldTileCoord RoomMax = 10;
constexpr WorldTileCoord RoomMin = 4;
WorldTileSize roomSize = area;
if (area.width > RoomMin)
roomSize.width = GenerateRnd(std::min(area.width, RoomMax) - RoomMin) + RoomMin;
if (area.height > RoomMin)
@ -1775,8 +1772,11 @@ void CreateRoom(Point topLeft, Point bottomRight, int nRDest, HallDirection nHDi
if (size)
roomSize = *size;
Point roomTopLeft = topLeft + Displacement { GenerateRnd(area.width), GenerateRnd(area.height) };
Point roomBottomRight = roomTopLeft + Displacement { roomSize };
const int32_t randomWidth = GenerateRnd(area.width);
const int32_t randomHeight = GenerateRnd(area.height);
WorldTilePosition roomTopLeft = topLeft + WorldTileDisplacement(randomWidth, randomHeight);
WorldTilePosition roomBottomRight = roomTopLeft + WorldTileDisplacement(roomSize);
if (roomBottomRight.x > bottomRight.x) {
roomBottomRight.x = bottomRight.x;
roomTopLeft.x = bottomRight.x - roomSize.width;
@ -1786,14 +1786,14 @@ void CreateRoom(Point topLeft, Point bottomRight, int nRDest, HallDirection nHDi
roomTopLeft.y = bottomRight.y - roomSize.height;
}
roomTopLeft.x = clamp(roomTopLeft.x, 1, 38);
roomTopLeft.y = clamp(roomTopLeft.y, 1, 38);
roomBottomRight.x = clamp(roomBottomRight.x, 1, 38);
roomBottomRight.y = clamp(roomBottomRight.y, 1, 38);
roomTopLeft.x = clamp<WorldTileCoord>(roomTopLeft.x, 1, 38);
roomTopLeft.y = clamp<WorldTileCoord>(roomTopLeft.y, 1, 38);
roomBottomRight.x = clamp<WorldTileCoord>(roomBottomRight.x, 1, 38);
roomBottomRight.y = clamp<WorldTileCoord>(roomBottomRight.y, 1, 38);
DefineRoom(roomTopLeft, roomBottomRight, static_cast<bool>(size));
constexpr Displacement standoff { 2, 2 };
constexpr WorldTileDisplacement standoff { 2, 2 };
if (size)
SetPieceRoom = { roomTopLeft + standoff, roomSize - 1 };
@ -1835,18 +1835,18 @@ void CreateRoom(Point topLeft, Point bottomRight, int nRDest, HallDirection nHDi
HallList.push_back({ { nHx1, nHy1 }, { nHx2, nHy2 }, nHDir });
}
Point roomBottomLeft { roomTopLeft.x, roomBottomRight.y };
Point roomTopRight { roomBottomRight.x, roomTopLeft.y };
WorldTilePosition roomBottomLeft { roomTopLeft.x, roomBottomRight.y };
WorldTilePosition roomTopRight { roomBottomRight.x, roomTopLeft.y };
if (roomSize.height > roomSize.width) {
CreateRoom(topLeft + standoff, roomBottomLeft - standoff, nRid, HallDirection::Right, {});
CreateRoom(roomTopRight + standoff, bottomRight - standoff, nRid, HallDirection::Left, {});
CreateRoom(Point { topLeft.x, roomBottomRight.y } + standoff, Point { roomBottomRight.x, bottomRight.y } - standoff, nRid, HallDirection::Up, {});
CreateRoom(Point { roomTopLeft.x, topLeft.y } + standoff, Point { bottomRight.x, roomTopLeft.y } - standoff, nRid, HallDirection::Down, {});
CreateRoom(WorldTilePosition { topLeft.x, roomBottomRight.y } + standoff, WorldTilePosition { roomBottomRight.x, bottomRight.y } - standoff, nRid, HallDirection::Up, {});
CreateRoom(WorldTilePosition { roomTopLeft.x, topLeft.y } + standoff, WorldTilePosition { bottomRight.x, roomTopLeft.y } - standoff, nRid, HallDirection::Down, {});
} else {
CreateRoom(topLeft + standoff, roomTopRight - standoff, nRid, HallDirection::Down, {});
CreateRoom(roomBottomLeft + standoff, bottomRight - standoff, nRid, HallDirection::Up, {});
CreateRoom(Point { topLeft.x, roomTopLeft.y } + standoff, Point { roomTopLeft.x, bottomRight.y } - standoff, nRid, HallDirection::Right, {});
CreateRoom(Point { roomBottomRight.x, topLeft.y } + standoff, Point { bottomRight.x, roomBottomRight.y } - standoff, nRid, HallDirection::Left, {});
CreateRoom(WorldTilePosition { topLeft.x, roomTopLeft.y } + standoff, WorldTilePosition { roomTopLeft.x, bottomRight.y } - standoff, nRid, HallDirection::Right, {});
CreateRoom(WorldTilePosition { roomBottomRight.x, topLeft.y } + standoff, WorldTilePosition { bottomRight.x, roomBottomRight.y } - standoff, nRid, HallDirection::Left, {});
}
}
@ -2059,9 +2059,9 @@ void FixTilesPatterns()
void Substitution()
{
for (int y = 0; y < DMAXY; y++) {
for (int x = 0; x < DMAXX; x++) {
if (SetPieceRoom.contains({ x, y }))
for (WorldTileCoord y = 0; y < DMAXY; y++) {
for (WorldTileCoord x = 0; x < DMAXX; x++) {
if (SetPieceRoom.contains(x, y))
continue;
if (!FlipCoin(4))
continue;
@ -2425,7 +2425,7 @@ bool FillVoids()
bool CreateDungeon()
{
std::optional<Size> size;
std::optional<WorldTileSize> size;
switch (currlevel) {
case 5:

22
Source/levels/drlg_l3.cpp

@ -1387,12 +1387,12 @@ bool CanReplaceTile(uint8_t replace, Point tile)
*/
bool PlaceMiniSetRandom(const Miniset &miniset, int rndper)
{
int sw = miniset.size.width;
int sh = miniset.size.height;
const WorldTileCoord sw = miniset.size.width;
const WorldTileCoord sh = miniset.size.height;
bool placed = false;
for (int sy = 0; sy < DMAXY - sh; sy++) {
for (int sx = 0; sx < DMAXX - sw; sx++) {
for (WorldTileCoord sy = 0; sy < DMAXY - sh; sy++) {
for (WorldTileCoord sx = 0; sx < DMAXX - sw; sx++) {
if (!miniset.matches({ sx, sy }))
continue;
// BUGFIX: This should not be applied to Nest levels
@ -1680,8 +1680,8 @@ void Fence()
}
}
for (int j = 1; j < DMAXY; j++) { // BUGFIX: Change '0' to '1' (fixed)
for (int i = 1; i < DMAXX; i++) { // BUGFIX: Change '0' to '1' (fixed)
for (WorldTileCoord j = 1; j < DMAXY; j++) { // BUGFIX: Change '0' to '1' (fixed)
for (WorldTileCoord i = 1; i < DMAXX; i++) { // BUGFIX: Change '0' to '1' (fixed)
// note the comma operator is used here to advance the RNG state
if (dungeon[i][j] == 7 && (AdvanceRndSeed(), !IsNearThemeRoom({ i, j }))) {
if (FlipCoin()) {
@ -1800,9 +1800,9 @@ void LoadQuestSetPieces()
bool PlaceAnvil()
{
// growing the size by 2 to allow a 1 tile border on all sides
Size areaSize = Size { SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1]) } + 2;
int sx = GenerateRnd(DMAXX - areaSize.width);
int sy = GenerateRnd(DMAXY - areaSize.height);
WorldTileSize areaSize = WorldTileSize(SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1])) + 2;
WorldTileCoord sx = GenerateRnd(DMAXX - areaSize.width);
WorldTileCoord sy = GenerateRnd(DMAXY - areaSize.height);
for (int trys = 0;; trys++, sx++) {
if (trys > 198)
@ -1817,7 +1817,7 @@ bool PlaceAnvil()
}
bool found = true;
for (Point tile : PointsInRectangleRange { { { sx, sy }, areaSize } }) {
for (WorldTilePosition tile : PointsInRectangle(WorldTileRectangle { { sx, sy }, areaSize })) {
if (Protected.test(tile.x, tile.y) || dungeon[tile.x][tile.y] != 7) {
found = false;
break;
@ -1830,7 +1830,7 @@ bool PlaceAnvil()
PlaceDunTiles(pSetPiece.get(), { sx + 1, sy + 1 }, 7);
SetPiece = { { sx, sy }, areaSize };
for (Point tile : PointsInRectangleRange { SetPiece }) {
for (WorldTilePosition tile : PointsInRectangle(SetPiece)) {
Protected.set(tile.x, tile.y);
}

68
Source/levels/drlg_l4.cpp

@ -14,15 +14,15 @@
namespace devilution {
Point DiabloQuad1;
Point DiabloQuad2;
Point DiabloQuad3;
Point DiabloQuad4;
WorldTilePosition DiabloQuad1;
WorldTilePosition DiabloQuad2;
WorldTilePosition DiabloQuad3;
WorldTilePosition DiabloQuad4;
namespace {
bool hallok[20];
Point L4Hold;
WorldTilePosition L4Hold;
/**
* A lookup table for the 16 possible patterns of a 2x2 area,
@ -172,16 +172,16 @@ void InitDungeonFlags()
memset(dungeon, 30, sizeof(dungeon));
}
void MapRoom(Rectangle room)
void MapRoom(WorldTileRectangle room)
{
for (int y = 0; y < room.size.height && y + room.position.y < DMAXY / 2; y++) {
for (int x = 0; x < room.size.width && x + room.position.x < DMAXX / 2; x++) {
for (WorldTileCoord y = 0; y < room.size.height && y + room.position.y < DMAXY / 2; y++) {
for (WorldTileCoord x = 0; x < room.size.width && x + room.position.x < DMAXX / 2; x++) {
DungeonMask.set(room.position.x + x, room.position.y + y);
}
}
}
bool CheckRoom(Rectangle room)
bool CheckRoom(WorldTileRectangle room)
{
if (room.position.x <= 0 || room.position.y <= 0) {
return false;
@ -201,39 +201,41 @@ bool CheckRoom(Rectangle room)
return true;
}
void GenerateRoom(Rectangle area, bool verticalLayout)
void GenerateRoom(WorldTileRectangle area, bool verticalLayout)
{
bool rotate = !FlipCoin(4);
verticalLayout = (!verticalLayout && rotate) || (verticalLayout && !rotate);
bool placeRoom1;
Rectangle room1;
WorldTileRectangle room1;
for (int num = 0; num < 20; num++) {
room1.size = { (GenerateRnd(5) + 2) & ~1, (GenerateRnd(5) + 2) & ~1 };
const int32_t randomWidth = (GenerateRnd(5) + 2) & ~1;
const int32_t randomHeight = (GenerateRnd(5) + 2) & ~1;
room1.size = WorldTileSize(randomWidth, randomHeight);
room1.position = area.position;
if (verticalLayout) {
room1.position += Displacement { -room1.size.width, area.size.height / 2 - room1.size.height / 2 };
placeRoom1 = CheckRoom({ room1.position + Displacement { -1, -1 }, { room1.size.height + 2, room1.size.width + 1 } }); /// BUGFIX: swap height and width ({ room1.size.width + 1, room1.size.height + 2 }) (workaround applied below)
room1.position += WorldTileDisplacement(-room1.size.width, area.size.height / 2 - room1.size.height / 2);
placeRoom1 = CheckRoom({ room1.position + WorldTileDisplacement { -1, -1 }, WorldTileSize(room1.size.height + 2, room1.size.width + 1) }); /// BUGFIX: swap height and width ({ room1.size.width + 1, room1.size.height + 2 }) (workaround applied below)
} else {
room1.position += Displacement { area.size.width / 2 - room1.size.width / 2, -room1.size.height };
placeRoom1 = CheckRoom({ room1.position + Displacement { -1, -1 }, { room1.size.width + 2, room1.size.height + 1 } });
room1.position += WorldTileDisplacement(area.size.width / 2 - room1.size.width / 2, -room1.size.height);
placeRoom1 = CheckRoom({ room1.position + WorldTileDisplacement { -1, -1 }, WorldTileSize(room1.size.width + 2, room1.size.height + 1) });
}
if (placeRoom1)
break;
}
if (placeRoom1)
MapRoom({ room1.position, { std::min(DMAXX - room1.position.x, room1.size.width), std::min(DMAXX - room1.position.y, room1.size.height) } });
MapRoom({ room1.position, WorldTileSize(std::min<int>(DMAXX - room1.position.x, room1.size.width), std::min<int>(DMAXX - room1.position.y, room1.size.height)) });
bool placeRoom2;
Rectangle room2 = room1;
WorldTileRectangle room2 = room1;
if (verticalLayout) {
room2.position.x = area.position.x + area.size.width;
placeRoom2 = CheckRoom({ room2.position + Displacement { 0, -1 }, { room2.size.width + 1, room2.size.height + 2 } });
placeRoom2 = CheckRoom({ room2.position + WorldTileDisplacement { 0, -1 }, WorldTileSize(room2.size.width + 1, room2.size.height + 2) });
} else {
room2.position.y = area.position.y + area.size.height;
placeRoom2 = CheckRoom({ room2.position + Displacement { -1, 0 }, { room2.size.width + 2, room2.size.height + 1 } });
placeRoom2 = CheckRoom({ room2.position + WorldTileDisplacement { -1, 0 }, WorldTileSize(room2.size.width + 2, room2.size.height + 1) });
}
if (placeRoom2)
@ -246,7 +248,7 @@ void GenerateRoom(Rectangle area, bool verticalLayout)
void FirstRoom()
{
Rectangle room { { 0, 0 }, { 14, 14 } };
WorldTileRectangle room { { 0, 0 }, { 14, 14 } };
if (currlevel != 16) {
if (currlevel == Quests[Q_WARLORD]._qlevel && Quests[Q_WARLORD]._qactive != QUEST_NOTAVAIL) {
assert(!gbIsMultiplayer);
@ -254,7 +256,9 @@ void FirstRoom()
} else if (currlevel == Quests[Q_BETRAYER]._qlevel && gbIsMultiplayer) {
room.size = { 11, 11 };
} else {
room.size = { GenerateRnd(5) + 2, GenerateRnd(5) + 2 };
const int32_t randomWidth = GenerateRnd(5) + 2;
const int32_t randomHeight = GenerateRnd(5) + 2;
room.size = WorldTileSize(randomWidth, randomHeight);
}
}
@ -262,13 +266,15 @@ void FirstRoom()
int xmax = DMAXX / 2 - 1 - room.size.width;
int ymin = (DMAXY / 2 - room.size.height) / 2;
int ymax = DMAXY / 2 - 1 - room.size.height;
room.position = { GenerateRnd(xmax - xmin + 1) + xmin, GenerateRnd(ymax - ymin + 1) + ymin };
const int32_t randomX = GenerateRnd(xmax - xmin + 1) + xmin;
const int32_t randomY = GenerateRnd(ymax - ymin + 1) + ymin;
room.position = WorldTilePosition(randomX, randomY);
if (currlevel == 16) {
L4Hold = room.position;
}
if (Quests[Q_WARLORD].IsAvailable() || (currlevel == Quests[Q_BETRAYER]._qlevel && gbIsMultiplayer)) {
SetPieceRoom = { room.position + Displacement { 1, 1 }, { room.size.width + 1, room.size.height + 1 } };
SetPieceRoom = { room.position + WorldTileDisplacement { 1, 1 }, WorldTileSize(room.size.width + 1, room.size.height + 1) };
} else {
SetPieceRoom = {};
}
@ -940,22 +946,22 @@ void LoadDiabQuads(bool preflag)
{
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l4data\\diab1.dun");
DiabloQuad1 = L4Hold + Displacement { 4, 4 };
DiabloQuad1 = L4Hold + WorldTileDisplacement { 4, 4 };
PlaceDunTiles(dunData.get(), DiabloQuad1, 6);
}
{
auto dunData = LoadFileInMem<uint16_t>(preflag ? "levels\\l4data\\diab2b.dun" : "levels\\l4data\\diab2a.dun");
DiabloQuad2 = { 27 - L4Hold.x, 1 + L4Hold.y };
DiabloQuad2 = WorldTilePosition(27 - L4Hold.x, 1 + L4Hold.y);
PlaceDunTiles(dunData.get(), DiabloQuad2, 6);
}
{
auto dunData = LoadFileInMem<uint16_t>(preflag ? "levels\\l4data\\diab3b.dun" : "levels\\l4data\\diab3a.dun");
DiabloQuad3 = { 1 + L4Hold.x, 27 - L4Hold.y };
DiabloQuad3 = WorldTilePosition(1 + L4Hold.x, 27 - L4Hold.y);
PlaceDunTiles(dunData.get(), DiabloQuad3, 6);
}
{
auto dunData = LoadFileInMem<uint16_t>(preflag ? "levels\\l4data\\diab4b.dun" : "levels\\l4data\\diab4a.dun");
DiabloQuad4 = { 28 - L4Hold.x, 28 - L4Hold.y };
DiabloQuad4 = WorldTilePosition(28 - L4Hold.x, 28 - L4Hold.y);
PlaceDunTiles(dunData.get(), DiabloQuad4, 6);
}
}
@ -1174,10 +1180,10 @@ void GenerateLevel(lvl_entry entry)
DRLG_CheckQuests(SetPieceRoom.position);
if (currlevel == 15) {
for (int j = 0; j < DMAXY; j++) {
for (int i = 0; i < DMAXX; i++) {
for (WorldTileCoord j = 1; j < DMAXY; j++) {
for (WorldTileCoord i = 1; i < DMAXX; i++) {
if (IsAnyOf(dungeon[i][j], 98, 107)) {
Make_SetPC({ { i - 1, j - 1 }, { 5, 5 } });
Make_SetPC({ WorldTilePosition(i - 1, j - 1), { 5, 5 } });
if (Quests[Q_BETRAYER]._qactive >= QUEST_ACTIVE) { /// Lazarus staff skip bug fixed
// Set the portal position to the location of the northmost pentagram tile.
Quests[Q_BETRAYER].position = { i, j };

9
Source/levels/drlg_l4.h

@ -5,14 +5,15 @@
*/
#pragma once
#include "engine/world_tile.hpp"
#include "levels/gendung.h"
namespace devilution {
extern Point DiabloQuad1;
extern Point DiabloQuad2;
extern Point DiabloQuad3;
extern Point DiabloQuad4;
extern WorldTilePosition DiabloQuad1;
extern WorldTilePosition DiabloQuad2;
extern WorldTilePosition DiabloQuad3;
extern WorldTilePosition DiabloQuad4;
void CreateL4Dungeon(uint32_t rseed, lvl_entry entry);
void LoadPreL4Dungeon(const char *path);

74
Source/levels/gendung.cpp

@ -19,15 +19,15 @@ Bitset2d<DMAXX, DMAXY> DungeonMask;
uint8_t dungeon[DMAXX][DMAXY];
uint8_t pdungeon[DMAXX][DMAXY];
Bitset2d<DMAXX, DMAXY> Protected;
Rectangle SetPieceRoom;
Rectangle SetPiece;
WorldTileRectangle SetPieceRoom;
WorldTileRectangle SetPiece;
std::unique_ptr<uint16_t[]> pSetPiece;
OptionalOwnedClxSpriteList pSpecialCels;
std::unique_ptr<MegaTile[]> pMegaTiles;
std::unique_ptr<byte[]> pDungeonCels;
std::array<TileProperties, MAXTILES> SOLData;
Point dminPosition;
Point dmaxPosition;
WorldTilePosition dminPosition;
WorldTilePosition dmaxPosition;
dungeon_type leveltype;
uint8_t currlevel;
bool setlevel;
@ -89,7 +89,7 @@ std::unique_ptr<uint16_t[]> LoadMinData(size_t &tileCount)
* @param maxSize maximum allowable value for both dimensions
* @return how much width/height is available for a theme room or an empty optional if there's not enough space
*/
std::optional<Size> GetSizeForThemeRoom(int floor, Point origin, int minSize, int maxSize)
std::optional<WorldTileSize> GetSizeForThemeRoom(uint8_t floor, WorldTilePosition origin, WorldTileCoord minSize, WorldTileCoord maxSize)
{
if (origin.x + maxSize > DMAXX && origin.y + maxSize > DMAXY) {
return {}; // Original broken bounds check, avoids lower right corner
@ -98,13 +98,13 @@ std::optional<Size> GetSizeForThemeRoom(int floor, Point origin, int minSize, in
return {};
}
const int maxWidth = std::min(maxSize, DMAXX - origin.x);
const int maxHeight = std::min(maxSize, DMAXY - origin.y);
const WorldTileCoord maxWidth = std::min<WorldTileCoord>(maxSize, DMAXX - origin.x);
const WorldTileCoord maxHeight = std::min<WorldTileCoord>(maxSize, DMAXY - origin.y);
Size room { maxWidth, maxHeight };
WorldTileSize room { maxWidth, maxHeight };
for (int i = 0; i < maxSize; i++) {
int width = i < room.height ? i : 0;
for (WorldTileCoord i = 0; i < maxSize; i++) {
WorldTileCoord width = i < room.height ? i : 0;
if (i < maxHeight) {
while (width < room.width) {
if (dungeon[origin.x + width][origin.y + i] != floor)
@ -114,7 +114,7 @@ std::optional<Size> GetSizeForThemeRoom(int floor, Point origin, int minSize, in
}
}
int height = i < room.width ? i : 0;
WorldTileCoord height = i < room.width ? i : 0;
if (i < maxWidth) {
while (height < room.height) {
if (dungeon[origin.x + i][origin.y + height] != floor)
@ -342,16 +342,16 @@ void InitGlobals()
DRLG_InitTrans();
dminPosition = Point(0, 0).megaToWorld();
dmaxPosition = Point(40, 40).megaToWorld();
SetPieceRoom = { { -1, -1 }, { -1, -1 } };
dminPosition = WorldTilePosition(0, 0).megaToWorld();
dmaxPosition = WorldTilePosition(40, 40).megaToWorld();
SetPieceRoom = { { 0, 0 }, { 0, 0 } };
SetPiece = { { 0, 0 }, { 0, 0 } };
}
} // namespace
#ifdef BUILD_TESTING
std::optional<Size> GetSizeForThemeRoom()
std::optional<WorldTileSize> GetSizeForThemeRoom()
{
return GetSizeForThemeRoom(0, { 0, 0 }, 5, 10);
}
@ -500,10 +500,10 @@ void DRLG_InitTrans()
TransVal = 1;
}
void DRLG_RectTrans(Rectangle area)
void DRLG_RectTrans(WorldTileRectangle area)
{
Point position = area.position;
Size size = area.size;
WorldTilePosition position = area.position;
WorldTileSize size = area.size;
for (int j = position.y; j <= position.y + size.height; j++) {
for (int i = position.x; i <= position.x + size.width; i++) {
@ -514,17 +514,17 @@ void DRLG_RectTrans(Rectangle area)
TransVal++;
}
void DRLG_MRectTrans(Rectangle area)
void DRLG_MRectTrans(WorldTileRectangle area)
{
Point position = area.position.megaToWorld();
Size size = area.size * 2;
WorldTilePosition position = area.position.megaToWorld();
WorldTileSize size = area.size * 2;
DRLG_RectTrans({ position + Displacement { 1, 1 }, { size.width - 1, size.height - 1 } });
DRLG_RectTrans({ position + WorldTileDisplacement { 1, 1 }, WorldTileSize(size.width - 1, size.height - 1) });
}
void DRLG_MRectTrans(Point origin, Point extent)
void DRLG_MRectTrans(WorldTilePosition origin, WorldTilePosition extent)
{
DRLG_MRectTrans({ origin, { extent.x - origin.x, extent.y - origin.y } });
DRLG_MRectTrans({ origin, WorldTileSize(extent.x - origin.x, extent.y - origin.y) });
}
void DRLG_CopyTrans(int sx, int sy, int dx, int dy)
@ -569,13 +569,13 @@ void LoadDungeonBase(const char *path, Point spawn, int floorId, int dirtId)
SetMapObjects(dunData.get(), 0, 0);
}
void Make_SetPC(Rectangle area)
void Make_SetPC(WorldTileRectangle area)
{
Point position = area.position.megaToWorld();
Size size = area.size * 2;
WorldTilePosition position = area.position.megaToWorld();
WorldTileSize size = area.size * 2;
for (int j = 0; j < size.height; j++) {
for (int i = 0; i < size.width; i++) {
for (unsigned j = 0; j < size.height; j++) {
for (unsigned i = 0; i < size.width; i++) {
dFlags[position.x + i][position.y + j] |= DungeonFlag::Populated;
}
}
@ -649,10 +649,10 @@ void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rn
{
themeCount = 0;
memset(themeLoc, 0, sizeof(*themeLoc));
for (int j = 0; j < DMAXY; j++) {
for (int i = 0; i < DMAXX; i++) {
for (WorldTileCoord j = 0; j < DMAXY; j++) {
for (WorldTileCoord i = 0; i < DMAXX; i++) {
if (dungeon[i][j] == floor && FlipCoin(freq)) {
std::optional<Size> themeSize = GetSizeForThemeRoom(floor, { i, j }, minSize, maxSize);
std::optional<WorldTileSize> themeSize = GetSizeForThemeRoom(floor, { i, j }, minSize, maxSize);
if (!themeSize)
continue;
@ -669,7 +669,7 @@ void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rn
}
THEME_LOC &theme = themeLoc[themeCount];
theme.room = { Point { i, j } + Direction::South, *themeSize };
theme.room = { WorldTilePosition { i, j } + Direction::South, *themeSize };
if (IsAnyOf(leveltype, DTYPE_CAVES, DTYPE_NEST)) {
DRLG_RectTrans({ (theme.room.position + Direction::South).megaToWorld(), theme.room.size * 2 - 5 });
} else {
@ -699,13 +699,13 @@ void DRLG_HoldThemeRooms()
}
}
void SetSetPieceRoom(Point position, int floorId)
void SetSetPieceRoom(WorldTilePosition position, int floorId)
{
if (pSetPiece == nullptr)
return;
PlaceDunTiles(pSetPiece.get(), position, floorId);
SetPiece = { position, { SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1]) } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1])) };
}
void FreeQuestSetPieces()
@ -748,10 +748,10 @@ void DRLG_LPass3(int lv)
}
}
bool IsNearThemeRoom(Point testPosition)
bool IsNearThemeRoom(WorldTilePosition testPosition)
{
for (int i = 0; i < themeCount; i++) {
if (Rectangle(themeLoc[i].room.position - Displacement { 2 }, themeLoc[i].room.size + 5).contains(testPosition))
if (WorldTileRectangle(themeLoc[i].room.position - WorldTileDisplacement { 2 }, themeLoc[i].room.size + 5).contains(testPosition))
return true;
}

39
Source/levels/gendung.h

@ -13,6 +13,7 @@
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
#include "engine/render/scrollrt.h"
#include "engine/world_tile.hpp"
#include "utils/attributes.h"
#include "utils/bitset2d.hpp"
#include "utils/enum_traits.h"
@ -120,7 +121,7 @@ enum _difficulty : uint8_t {
};
struct THEME_LOC {
Rectangle room;
RectangleOf<uint8_t> room;
int16_t ttval;
};
@ -153,9 +154,9 @@ extern DVL_API_FOR_TEST uint8_t dungeon[DMAXX][DMAXY];
extern uint8_t pdungeon[DMAXX][DMAXY];
/** Tile that may not be overwritten by the level generator */
extern Bitset2d<DMAXX, DMAXY> Protected;
extern Rectangle SetPieceRoom;
extern WorldTileRectangle SetPieceRoom;
/** Specifies the active set quest piece in coordinate. */
extern Rectangle SetPiece;
extern WorldTileRectangle SetPiece;
/** Contains the contents of the single player quest DUN file. */
extern std::unique_ptr<uint16_t[]> pSetPiece;
extern OptionalOwnedClxSpriteList pSpecialCels;
@ -167,9 +168,9 @@ extern std::unique_ptr<byte[]> pDungeonCels;
*/
extern DVL_API_FOR_TEST std::array<TileProperties, MAXTILES> SOLData;
/** Specifies the minimum X,Y-coordinates of the map. */
extern Point dminPosition;
extern WorldTilePosition dminPosition;
/** Specifies the maximum X,Y-coordinates of the map. */
extern Point dmaxPosition;
extern WorldTilePosition dmaxPosition;
/** Specifies the active dungeon type of the current game. */
extern DVL_API_FOR_TEST dungeon_type leveltype;
/** Specifies the active dungeon level of the current game. */
@ -223,7 +224,7 @@ extern int themeCount;
extern THEME_LOC themeLoc[MAXTHEMES];
#ifdef BUILD_TESTING
std::optional<Size> GetSizeForThemeRoom();
std::optional<WorldTileSize> GetSizeForThemeRoom();
#endif
dungeon_type GetLevelType(int level);
@ -291,7 +292,7 @@ constexpr bool IsTileLit(Point position)
}
struct Miniset {
Size size;
WorldTileSize size;
/* these are indexed as [y][x] */
uint8_t search[6][6];
uint8_t replace[6][6];
@ -300,10 +301,10 @@ struct Miniset {
* @param position Coordinates of the dungeon tile to check
* @param respectProtected Match bug from Crypt levels if false
*/
bool matches(Point position, bool respectProtected = true) const
bool matches(WorldTilePosition position, bool respectProtected = true) const
{
for (int yy = 0; yy < size.height; yy++) {
for (int xx = 0; xx < size.width; xx++) {
for (WorldTileCoord yy = 0; yy < size.height; yy++) {
for (WorldTileCoord xx = 0; xx < size.width; xx++) {
if (search[yy][xx] != 0 && dungeon[xx + position.x][yy + position.y] != search[yy][xx])
return false;
if (respectProtected && Protected.test(xx + position.x, yy + position.y))
@ -313,10 +314,10 @@ struct Miniset {
return true;
}
void place(Point position, bool protect = false) const
void place(WorldTilePosition position, bool protect = false) const
{
for (int y = 0; y < size.height; y++) {
for (int x = 0; x < size.width; x++) {
for (WorldTileCoord y = 0; y < size.height; y++) {
for (WorldTileCoord x = 0; x < size.width; x++) {
if (replace[y][x] == 0)
continue;
dungeon[x + position.x][y + position.y] = replace[y][x];
@ -331,13 +332,13 @@ bool TileHasAny(int tileId, TileProperties property);
void LoadLevelSOLData();
void SetDungeonMicros();
void DRLG_InitTrans();
void DRLG_MRectTrans(Point origin, Point extent);
void DRLG_MRectTrans(Rectangle area);
void DRLG_RectTrans(Rectangle area);
void DRLG_MRectTrans(WorldTilePosition origin, WorldTilePosition extent);
void DRLG_MRectTrans(WorldTileRectangle area);
void DRLG_RectTrans(WorldTileRectangle area);
void DRLG_CopyTrans(int sx, int sy, int dx, int dy);
void LoadTransparency(const uint16_t *dunData);
void LoadDungeonBase(const char *path, Point spawn, int floorId, int dirtId);
void Make_SetPC(Rectangle area);
void Make_SetPC(WorldTileRectangle area);
/**
* @param miniset The miniset to place
* @param tries Tiles to try, 1600 will scan the full map
@ -347,7 +348,7 @@ std::optional<Point> PlaceMiniSet(const Miniset &miniset, int tries = 199, bool
void PlaceDunTiles(const uint16_t *dunData, Point position, int floorId = 0);
void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize);
void DRLG_HoldThemeRooms();
void SetSetPieceRoom(Point position, int floorId);
void SetSetPieceRoom(WorldTilePosition position, int floorId);
void FreeQuestSetPieces();
void DRLG_LPass3(int lv);
@ -356,7 +357,7 @@ void DRLG_LPass3(int lv);
* @param position Target location in dungeon coordinates
* @return True if a theme room is near (within 2 tiles of) this point, false if it is free.
*/
bool IsNearThemeRoom(Point position);
bool IsNearThemeRoom(WorldTilePosition position);
void InitLevels();
void FloodTransparencyValues(uint8_t floorID);

6
Source/levels/setmaps.cpp

@ -35,13 +35,13 @@ namespace {
void AddSKingObjs()
{
constexpr Rectangle SmallSecretRoom { { 20, 7 }, { 3, 3 } };
constexpr WorldTileRectangle SmallSecretRoom { { 20, 7 }, { 3, 3 } };
ObjectAtPosition({ 64, 34 }).InitializeLoadedObject(SmallSecretRoom, 1);
constexpr Rectangle Gate { { 20, 14 }, { 1, 2 } };
constexpr WorldTileRectangle Gate { { 20, 14 }, { 1, 2 } };
ObjectAtPosition({ 64, 59 }).InitializeLoadedObject(Gate, 2);
constexpr Rectangle LargeSecretRoom { { 8, 1 }, { 7, 10 } };
constexpr WorldTileRectangle LargeSecretRoom { { 8, 1 }, { 7, 10 } };
ObjectAtPosition({ 27, 37 }).InitializeLoadedObject(LargeSecretRoom, 3);
ObjectAtPosition({ 46, 35 }).InitializeLoadedObject(LargeSecretRoom, 3);
ObjectAtPosition({ 49, 53 }).InitializeLoadedObject(LargeSecretRoom, 3);

6
Source/levels/themes.cpp

@ -85,7 +85,7 @@ bool TFit_Shrine(int i)
bool CheckThemeObj5(Point origin, int8_t regionId)
{
const PointsInRectangleRange searchArea { Rectangle { origin, 2 } };
const auto searchArea = PointsInRectangle(Rectangle { origin, 2 });
return std::all_of(searchArea.cbegin(), searchArea.cend(), [regionId](Point testPosition) {
// note out-of-bounds tiles are not solid, this function relies on the guard in TFit_Obj5 and dungeon border
if (IsTileSolid(testPosition)) {
@ -110,7 +110,7 @@ bool TFit_Obj5(int t)
}
int candidatesFound = 0;
for (Point tile : PointsInRectangleRange { { { 0, 0 }, { MAXDUNX, MAXDUNY } } }) {
for (Point tile : PointsInRectangle(Rectangle { { 0, 0 }, { MAXDUNX, MAXDUNY } })) {
if (dTransVal[tile.x][tile.y] == themes[t].ttval && IsTileNotSolid(tile) && CheckThemeObj5(tile, themes[t].ttval)) {
// Use themex/y to keep track of the last candidate area found, in case we end up with fewer candidates than the target
themex = tile.x;
@ -152,7 +152,7 @@ bool TFit_GoatShrine(int t)
bool CheckThemeObj3(Point origin, int8_t regionId, unsigned frequency = 0)
{
const PointsInRectangleRange searchArea { Rectangle { origin, 1 } };
const auto searchArea = PointsInRectangle(Rectangle { origin, 1 });
return std::all_of(searchArea.cbegin(), searchArea.cend(), [regionId, frequency](Point testPosition) {
if (!InDungeonBounds(testPosition)) {
return false;

5
Source/missiles.cpp

@ -1475,10 +1475,7 @@ void AddRuneExplosion(Missile &missile, const AddMissileParameter & /*parameter*
missile._midam = dmg;
auto searchArea = PointsInRectangleRangeColMajor {
Rectangle { missile.position.tile, 1 }
};
for (Point position : searchArea)
for (Point position : PointsInRectangleColMajor(Rectangle { missile.position.tile, 1 }))
CheckMissileCol(missile, dmg, dmg, false, position, true);
}
missile._mlid = AddLight(missile.position.start, 8);

6
Source/monster.cpp

@ -1420,7 +1420,7 @@ void MonsterTalk(Monster &monster)
ObjChangeMap(SetPiece.position.x, SetPiece.position.y, SetPiece.position.x + (SetPiece.size.width / 2) + 2, SetPiece.position.y + (SetPiece.size.height / 2) - 2);
auto tren = TransVal;
TransVal = 9;
DRLG_MRectTrans({ SetPiece.position, { SetPiece.size.width / 2 + 4, SetPiece.size.height / 2 } });
DRLG_MRectTrans({ SetPiece.position, WorldTileSize(SetPiece.size.width / 2 + 4, SetPiece.size.height / 2) });
TransVal = tren;
Quests[Q_LTBANNER]._qvar1 = 2;
if (Quests[Q_LTBANNER]._qactive == QUEST_INIT)
@ -3606,7 +3606,7 @@ void M_StartStand(Monster &monster, Direction md)
void M_ClearSquares(const Monster &monster)
{
for (Point searchTile : PointsInRectangleRange { Rectangle { monster.position.old, 1 } }) {
for (Point searchTile : PointsInRectangle(Rectangle { monster.position.old, 1 })) {
if (FindMonsterAtPosition(searchTile) == &monster)
dMonster[searchTile.x][searchTile.y] = 0;
}
@ -4181,7 +4181,7 @@ void SyncMonsterAnim(Monster &monster)
void M_FallenFear(Point position)
{
const Rectangle fearArea = Rectangle { position, 4 };
for (const Point tile : PointsInRectangleRange { fearArea }) {
for (const Point tile : PointsInRectangle(fearArea)) {
if (!InDungeonBounds(tile))
continue;
int m = dMonster[tile.x][tile.y];

20
Source/objects.cpp

@ -469,7 +469,7 @@ void AddCandles()
* @param affectedArea The map region to be updated when this object is activated by the player.
* @param msg The quest text to play when the player activates the book.
*/
void AddBookLever(_object_id type, Rectangle affectedArea, _speech_id msg)
void AddBookLever(_object_id type, WorldTileRectangle affectedArea, _speech_id msg)
{
std::optional<Point> position = GetRandomObjectPosition({ 2, 2 });
if (!position)
@ -647,7 +647,7 @@ void AddChestTraps()
}
}
void LoadMapObjects(const char *path, Point start, Rectangle mapRange = {}, int leveridx = 0)
void LoadMapObjects(const char *path, Point start, WorldTileRectangle mapRange = {}, int leveridx = 0)
{
LoadingMapObjects = true;
ApplyObjectLighting = true;
@ -920,10 +920,10 @@ void AddStoryBooks()
void AddHookedBodies(int freq)
{
for (int j = 0; j < DMAXY; j++) {
int jj = 16 + j * 2;
for (int i = 0; i < DMAXX; i++) {
int ii = 16 + i * 2;
for (WorldTileCoord j = 0; j < DMAXY; j++) {
WorldTileCoord jj = 16 + j * 2;
for (WorldTileCoord i = 0; i < DMAXX; i++) {
WorldTileCoord ii = 16 + i * 2;
if (dungeon[i][j] != 1 && dungeon[i][j] != 2)
continue;
if (!FlipCoin(freq))
@ -2000,7 +2000,7 @@ void OperateBookLever(Object &questBook, bool sendmsg)
SpawnUnique(UITEM_OPTAMULET, SetPiece.position.megaToWorld() + Displacement { 5, 5 });
auto tren = TransVal;
TransVal = 9;
DRLG_MRectTrans({ questBook._oVar1, questBook._oVar2 }, { questBook._oVar3, questBook._oVar4 });
DRLG_MRectTrans(WorldTilePosition(questBook._oVar1, questBook._oVar2), WorldTilePosition(questBook._oVar3, questBook._oVar4));
TransVal = tren;
}
}
@ -3528,7 +3528,7 @@ void SyncQSTLever(const Object &qstLever)
if (qstLever._otype == OBJ_BLINDBOOK) {
auto tren = TransVal;
TransVal = 9;
DRLG_MRectTrans({ qstLever._oVar1, qstLever._oVar2 }, { qstLever._oVar3, qstLever._oVar4 });
DRLG_MRectTrans(WorldTilePosition(qstLever._oVar1, qstLever._oVar2), WorldTilePosition(qstLever._oVar3, qstLever._oVar4));
TransVal = tren;
}
}
@ -3996,7 +3996,7 @@ Object *AddObject(_object_id objType, Point objPos)
AddDoor(object);
break;
case OBJ_BOOK2R:
object.InitializeBook({ SetPiece.position, { SetPiece.size.width + 1, SetPiece.size.height + 1 } });
object.InitializeBook({ SetPiece.position, WorldTileSize(SetPiece.size.width + 1, SetPiece.size.height + 1) });
break;
case OBJ_CHEST1:
case OBJ_CHEST2:
@ -4146,7 +4146,7 @@ void OperateTrap(Object &trap)
Point triggerPosition = { trap._oVar1, trap._oVar2 };
Point target = triggerPosition;
PointsInRectangleRange searchArea { Rectangle { target, 1 } };
auto searchArea = PointsInRectangle(Rectangle { target, 1 });
// look for a player near the trigger (using a reverse search to match vanilla behaviour)
auto foundPosition = std::find_if(searchArea.crbegin(), searchArea.crend(), [](Point testPosition) { return InDungeonBounds(testPosition) && dPlayer[testPosition.x][testPosition.y] != 0; });
if (foundPosition != searchArea.crend()) {

13
Source/objects.h

@ -10,6 +10,7 @@
#include "engine/clx_sprite.hpp"
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
#include "engine/world_tile.hpp"
#include "itemdat.h"
#include "monster.h"
#include "objdat.h"
@ -84,7 +85,7 @@ struct Object {
* @param topLeftPosition corner of the map region closest to the origin.
* @param bottomRightPosition corner of the map region furthest from the origin.
*/
constexpr void SetMapRange(Point topLeftPosition, Point bottomRightPosition)
constexpr void SetMapRange(WorldTilePosition topLeftPosition, WorldTilePosition bottomRightPosition)
{
_oVar1 = topLeftPosition.x;
_oVar2 = topLeftPosition.y;
@ -96,9 +97,9 @@ struct Object {
* @brief Convenience function for SetMapRange(Point, Point).
* @param mapRange A rectangle defining the top left corner and size of the affected region.
*/
constexpr void SetMapRange(Rectangle mapRange)
constexpr void SetMapRange(WorldTileRectangle mapRange)
{
SetMapRange(mapRange.position, mapRange.position + Displacement { mapRange.size });
SetMapRange(mapRange.position, mapRange.position + DisplacementOf<uint8_t>(mapRange.size));
}
/**
@ -109,7 +110,7 @@ struct Object {
*
* @param mapRange The region to be updated when this object is activated.
*/
constexpr void InitializeBook(Rectangle mapRange)
constexpr void InitializeBook(WorldTileRectangle mapRange)
{
SetMapRange(mapRange);
_oVar6 = _oAnimFrame + 1; // Save the frame number for the open book frame
@ -121,7 +122,7 @@ struct Object {
* @param leverID An ID (distinct from the object index) to identify the new objects spawned after updating the map.
* @param message The quest text to play when this object is activated.
*/
constexpr void InitializeQuestBook(Rectangle mapRange, int leverID, _speech_id message)
constexpr void InitializeQuestBook(WorldTileRectangle mapRange, int leverID, _speech_id message)
{
InitializeBook(mapRange);
_oVar8 = leverID;
@ -133,7 +134,7 @@ struct Object {
* @param mapRange The region which was updated to spawn this object.
* @param leverID The id (*not* an object ID/index) of the lever responsible for the map change.
*/
constexpr void InitializeLoadedObject(Rectangle mapRange, int leverID)
constexpr void InitializeLoadedObject(WorldTileRectangle mapRange, int leverID)
{
SetMapRange(mapRange);
_oVar8 = leverID;

2
Source/player.cpp

@ -2885,7 +2885,7 @@ void StartPlrBlock(Player &player, Direction dir)
void FixPlrWalkTags(const Player &player)
{
for (Point searchTile : PointsInRectangleRange { Rectangle { player.position.old, 1 } }) {
for (Point searchTile : PointsInRectangle(Rectangle { player.position.old, 1 })) {
if (PlayerAtPosition(searchTile) == &player) {
dPlayer[searchTile.x][searchTile.y] = 0;
}

10
Source/qol/stash.cpp

@ -50,7 +50,7 @@ constexpr Rectangle StashButtonRect[] = {
// clang-format on
};
constexpr PointsInRectangleRange StashGridRange { { { 0, 0 }, Size { 10, 10 } } };
constexpr PointsInRectangleRange<int> StashGridRange { { { 0, 0 }, Size { 10, 10 } } };
OptionalOwnedClxSpriteList StashPanelArt;
OptionalOwnedClxSpriteList StashNavButtonArt;
@ -63,7 +63,7 @@ OptionalOwnedClxSpriteList StashNavButtonArt;
*/
void AddItemToStashGrid(unsigned page, Point position, uint16_t stashListIndex, Size itemSize)
{
for (auto point : PointsInRectangleRange({ position, itemSize })) {
for (Point point : PointsInRectangle(Rectangle { position, itemSize })) {
Stash.stashGrids[page][point.x][point.y] = stashListIndex + 1;
}
}
@ -121,7 +121,7 @@ void CheckStashPaste(Point cursorPosition)
// Check that no more than 1 item is replaced by the move
StashStruct::StashCell stashIndex = StashStruct::EmptyCell;
for (auto point : PointsInRectangleRange({ firstSlot, itemSize })) {
for (Point point : PointsInRectangle(Rectangle { firstSlot, itemSize })) {
StashStruct::StashCell iv = Stash.GetItemIdAtPosition(point);
if (iv == StashStruct::EmptyCell || stashIndex == iv)
continue;
@ -681,10 +681,10 @@ bool AutoPlaceItemInStash(Player &player, const Item &item, bool persistItem)
if (pageIndex >= CountStashPages)
pageIndex -= CountStashPages;
// Search all possible position in stash grid
for (auto stashPosition : PointsInRectangleRange({ { 0, 0 }, Size { 10 - (itemSize.width - 1), 10 - (itemSize.height - 1) } })) {
for (auto stashPosition : PointsInRectangle(Rectangle { { 0, 0 }, Size { 10 - (itemSize.width - 1), 10 - (itemSize.height - 1) } })) {
// Check that all needed slots are free
bool isSpaceFree = true;
for (auto itemPoint : PointsInRectangleRange({ stashPosition, itemSize })) {
for (auto itemPoint : PointsInRectangle(Rectangle { stashPosition, itemSize })) {
uint16_t iv = Stash.stashGrids[pageIndex][itemPoint.x][itemPoint.y];
if (iv != 0) {
isSpaceFree = false;

13
Source/quests.cpp

@ -14,6 +14,7 @@
#include "engine/random.hpp"
#include "engine/render/clx_render.hpp"
#include "engine/render/text_render.hpp"
#include "engine/world_tile.hpp"
#include "init.h"
#include "levels/gendung.h"
#include "levels/trigs.h"
@ -139,7 +140,7 @@ void DrawWarLord(Point position)
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l4data\\warlord2.dun");
SetPiece = { position, { SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1]) } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) };
PlaceDunTiles(dunData.get(), position, 6);
}
@ -148,7 +149,7 @@ void DrawSChamber(quest_id q, Point position)
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l2data\\bonestr1.dun");
SetPiece = { position, { SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1]) } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) };
PlaceDunTiles(dunData.get(), position, 3);
@ -162,7 +163,7 @@ void DrawLTBanner(Point position)
int width = SDL_SwapLE16(dunData[0]);
int height = SDL_SwapLE16(dunData[1]);
SetPiece = { position, { SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1]) } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) };
const uint16_t *tileLayer = &dunData[2];
@ -189,7 +190,7 @@ void DrawBlood(Point position)
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l2data\\blood2.dun");
SetPiece = { position, { SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1]) } };
SetPiece = { position, WorldTileSize(SDL_SwapLE16(dunData[0]), SDL_SwapLE16(dunData[1])) };
PlaceDunTiles(dunData.get(), position, 0);
}
@ -626,7 +627,7 @@ void ResyncQuests()
SyncObjectAnim(Objects[ActiveObjects[i]]);
auto tren = TransVal;
TransVal = 9;
DRLG_MRectTrans({ SetPiece.position, { SetPiece.size.width / 2 + 4, SetPiece.size.height / 2 } });
DRLG_MRectTrans({ SetPiece.position, WorldTileSize(SetPiece.size.width / 2 + 4, SetPiece.size.height / 2) });
TransVal = tren;
}
if (Quests[Q_LTBANNER]._qvar1 == 3) {
@ -635,7 +636,7 @@ void ResyncQuests()
SyncObjectAnim(Objects[ActiveObjects[i]]);
auto tren = TransVal;
TransVal = 9;
DRLG_MRectTrans({ SetPiece.position, { SetPiece.size.width / 2 + 4, SetPiece.size.height / 2 } });
DRLG_MRectTrans({ SetPiece.position, WorldTileSize(SetPiece.size.width / 2 + 4, SetPiece.size.height / 2) });
TransVal = tren;
}
}

1
test/CMakeLists.txt

@ -34,6 +34,7 @@ set(tests
player_test
quests_test
random_test
rectangle_test
scrollrt_test
stores_test
timedemo_test

27
test/drlg_common_test.cpp

@ -4,20 +4,21 @@
#include <array>
#include "engine/points_in_rectangle_range.hpp"
#include "engine/world_tile.hpp"
#include "levels/gendung.h"
namespace devilution {
TEST(DrlgTest, RectangleRangeIterator)
{
constexpr Rectangle topLeftArea = Rectangle { Point { 1, 1 }, 1 };
constexpr Rectangle bottomRightArea = Rectangle { Point { 3, 3 }, 1 };
constexpr WorldTileRectangle topLeftArea { { 1, 1 }, 1 };
constexpr WorldTileRectangle bottomRightArea { { 3, 3 }, 1 };
// Dungeon generation depends on the iteration order remaining unchanged to ensure we generate the same layout as vanilla Diablo/Hellfire
std::array<std::array<int, 5>, 5> region {};
int counter = 0;
// Iterate over a 9 tile area in the top left of the region.
for (Point position : PointsInRectangleRange { topLeftArea }) {
for (WorldTilePosition position : PointsInRectangle(topLeftArea)) {
region[position.x][position.y] = ++counter;
}
@ -35,7 +36,7 @@ TEST(DrlgTest, RectangleRangeIterator)
region = {};
counter = 0;
PointsInRectangleRange rowMajorRange { bottomRightArea };
const auto rowMajorRange = PointsInRectangle(bottomRightArea);
std::for_each(rowMajorRange.rbegin(), rowMajorRange.rend(), [&region, &counter](Point position) { region[position.x][position.y] = ++counter; });
EXPECT_EQ(region[4][4], 1) << "Reverse iterators are required";
EXPECT_EQ(region[2][4], 3) << "Reverse iterators are required";
@ -45,7 +46,7 @@ TEST(DrlgTest, RectangleRangeIterator)
region = {};
counter = 0;
for (Point position : PointsInRectangleRangeColMajor { topLeftArea }) {
for (WorldTilePosition position : PointsInRectangleColMajor(topLeftArea)) {
region[position.x][position.y] = ++counter;
}
@ -63,7 +64,7 @@ TEST(DrlgTest, RectangleRangeIterator)
region = {};
counter = 0;
PointsInRectangleRangeColMajor colMajorRange { bottomRightArea };
const auto colMajorRange = PointsInRectangleColMajor(bottomRightArea);
std::for_each(colMajorRange.rbegin(), colMajorRange.rend(), [&region, &counter](Point position) { region[position.x][position.y] = ++counter; });
EXPECT_EQ(region[4][4], 1) << "Reverse iterators are required";
EXPECT_EQ(region[4][2], 3) << "Reverse iterators are required";
@ -75,14 +76,14 @@ TEST(DrlgTest, ThemeRoomSize)
{
memset(dungeon, 0, sizeof(dungeon));
EXPECT_EQ(GetSizeForThemeRoom(), Size(8, 8)) << "All floor theme area should be 8x8";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(8, 8)) << "All floor theme area should be 8x8";
dungeon[9][9] = 1;
EXPECT_EQ(GetSizeForThemeRoom(), Size(7, 7)) << "Corners shrink the chosen dimensions";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(7, 7)) << "Corners shrink the chosen dimensions";
dungeon[9][5] = 1;
EXPECT_EQ(GetSizeForThemeRoom(), Size(7, 3)) << "Minimum dimensions are determined by corners outside the min area";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(7, 3)) << "Minimum dimensions are determined by corners outside the min area";
dungeon[9][4] = 1;
EXPECT_EQ(GetSizeForThemeRoom(), Size(7, 8)) << "Walls below the min size let larger opposing dimensions get picked";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(7, 8)) << "Walls below the min size let larger opposing dimensions get picked";
dungeon[9][5] = 0;
dungeon[9][4] = 0;
dungeon[9][9] = 0;
@ -90,10 +91,10 @@ TEST(DrlgTest, ThemeRoomSize)
// Time for some unusual cases
dungeon[7][2] = 1;
dungeon[5][9] = 1;
EXPECT_EQ(GetSizeForThemeRoom(), Size(5, 7)) << "Search space terminates at width 8 due to the wall being in the first three rows";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(5, 7)) << "Search space terminates at width 8 due to the wall being in the first three rows";
dungeon[6][4] = 1;
EXPECT_EQ(GetSizeForThemeRoom(), Size(4, 7)) << "Smallest width now defined by row 5, height still extends due to minSize";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(4, 7)) << "Smallest width now defined by row 5, height still extends due to minSize";
dungeon[6][4] = 0;
dungeon[5][9] = 0;
@ -102,7 +103,7 @@ TEST(DrlgTest, ThemeRoomSize)
dungeon[7][0] = 1;
dungeon[6][6] = 1;
dungeon[8][5] = 1;
EXPECT_EQ(GetSizeForThemeRoom(), Size(4, 4)) << "Search is terminated by the 0 width row 7, inset corner gives a larger height than otherwise expected";
EXPECT_EQ(GetSizeForThemeRoom(), WorldTileSize(4, 4)) << "Search is terminated by the 0 width row 7, inset corner gives a larger height than otherwise expected";
}
} // namespace devilution

33
test/rectangle_test.cpp

@ -0,0 +1,33 @@
#include <gtest/gtest.h>
#include "engine/point.hpp"
#include "engine/rectangle.hpp"
namespace devilution {
namespace {
TEST(RectangleTest, Contains_LargerSize)
{
RectangleOf<uint8_t> rect { { 0, 0 }, { 10, 20 } };
EXPECT_TRUE(rect.contains(Point(9, 9)));
EXPECT_FALSE(rect.contains(Point(-1, -1)));
EXPECT_FALSE(rect.contains(Point(257, 257)));
}
TEST(RectangleTest, Contains_UnsignedRectangle_SignedPointSameSize)
{
RectangleOf<uint8_t> rect { { 0, 0 }, { 255, 255 } };
EXPECT_TRUE(rect.contains(PointOf<int8_t>(5, 5)));
EXPECT_FALSE(rect.contains(PointOf<int8_t>(-1, -1)));
EXPECT_FALSE(rect.contains(PointOf<int8_t>(-2, -2)));
}
TEST(RectangleTest, Contains_SignedRectangle_UnsignedPointSameSize)
{
RectangleOf<int8_t> rect { { -10, -10 }, { 127, 127 } };
EXPECT_TRUE(rect.contains(PointOf<uint8_t>(0, 0)));
EXPECT_FALSE(rect.contains(PointOf<uint8_t>(255, 255)));
}
} // namespace
} // namespace devilution
Loading…
Cancel
Save