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 4 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() void FindItemOrObject()
{ {
Point futurePosition = MyPlayer->position.future; WorldTilePosition futurePosition = MyPlayer->position.future;
int rotations = 5; 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 // 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; int8_t itemId = dItem[targetPosition.x][targetPosition.y] - 1;
if (itemId < 0) { if (itemId < 0) {
@ -185,7 +185,7 @@ void FindItemOrObject()
return; // Don't look for objects in town return; // Don't look for objects in town
} }
for (Point targetPosition : searchArea) { for (WorldTilePosition targetPosition : searchArea) {
Object *object = FindObjectAtPosition(targetPosition); Object *object = FindObjectAtPosition(targetPosition);
if (object == nullptr || object->_oSelFlag == 0) { if (object == nullptr || object->_oSelFlag == 0) {
// No object or non-interactive object // No object or non-interactive object
@ -701,7 +701,7 @@ Point FindFirstStashSlotOnItem(StashStruct::StashCell itemInvId)
if (itemInvId == StashStruct::EmptyCell) if (itemInvId == StashStruct::EmptyCell)
return InvalidStashPoint; 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) if (Stash.GetItemIdAtPosition(point) == itemInvId)
return point; return point;
} }
@ -770,7 +770,7 @@ Point FindClosestStashSlot(Point mousePos)
Point bestSlot = {}; Point bestSlot = {};
mousePos += Displacement { -INV_SLOT_HALF_SIZE_PX, -INV_SLOT_HALF_SIZE_PX }; 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)); int distance = mousePos.ManhattanDistance(GetStashSlotCoord(point));
if (distance < shortestDistance) { if (distance < shortestDistance) {
shortestDistance = distance; shortestDistance = distance;
@ -1781,7 +1781,7 @@ void PerformPrimaryAction()
StashStruct::StashCell itemUnderCursor = [](Point stashSlot, Size cursorSizeInCells) -> StashStruct::StashCell { StashStruct::StashCell itemUnderCursor = [](Point stashSlot, Size cursorSizeInCells) -> StashStruct::StashCell {
if (stashSlot != InvalidStashPoint) if (stashSlot != InvalidStashPoint)
return StashStruct::EmptyCell; return StashStruct::EmptyCell;
for (Point slotUnderCursor : PointsInRectangleRange { { stashSlot, cursorSizeInCells } }) { for (Point slotUnderCursor : PointsInRectangle(Rectangle { stashSlot, cursorSizeInCells })) {
if (slotUnderCursor.x >= 10 || slotUnderCursor.y >= 10) if (slotUnderCursor.x >= 10 || slotUnderCursor.y >= 10)
continue; continue;
StashStruct::StashCell itemId = Stash.GetItemIdAtPosition(slotUnderCursor); StashStruct::StashCell itemId = Stash.GetItemIdAtPosition(slotUnderCursor);

7
Source/engine/displacement.hpp

@ -5,8 +5,8 @@
#include <ostream> #include <ostream>
#endif #endif
#include "direction.hpp" #include "engine/direction.hpp"
#include "size.hpp" #include "engine/size.hpp"
#include "utils/stdcompat/abs.hpp" #include "utils/stdcompat/abs.hpp"
namespace devilution { 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) : deltaX(size.width)
, deltaY(size.height) , deltaY(size.height)
{ {

4
Source/engine/point.hpp

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

48
Source/engine/rectangle.hpp

@ -5,13 +5,14 @@
namespace devilution { namespace devilution {
struct Rectangle { template <typename CoordT, typename SizeT = CoordT>
Point position; struct RectangleOf {
Size size; 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) : position(position)
, size(size) , size(size)
{ {
@ -25,26 +26,37 @@ struct Rectangle {
* @param center center point of the target rectangle * @param center center point of the target rectangle
* @param radius a non-negative value indicating how many tiles to include around the center * @param radius a non-negative value indicating how many tiles to include around the center
*/ */
explicit constexpr Rectangle(Point center, int radius) explicit constexpr RectangleOf(PointOf<CoordT> center, SizeT radius)
: position(center - Displacement { radius }) : position(center - DisplacementOf<SizeT> { radius })
, size(2 * radius + 1) , 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 return x >= this->position.x
&& point.x < (this->position.x + this->size.width) && x < (this->position.x + this->size.width)
&& point.y >= this->position.y && y >= this->position.y
&& point.y < (this->position.y + this->size.height); && y < (this->position.y + this->size.height);
} }
/** /**
* @brief Computes the center of this rectangle in integer coordinates. Values are truncated towards zero. * @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 * 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 { return {
position + factor, 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 } // namespace devilution

43
Source/engine/size.hpp

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

5
Source/engine/world_tile.hpp

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

4
Source/levels/crypt.cpp

@ -686,7 +686,7 @@ void SetCryptRoom()
auto dunData = LoadFileInMem<uint16_t>("nlevels\\l5data\\uberroom.dun"); 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); PlaceDunTiles(dunData.get(), position, 0);
} }
@ -697,7 +697,7 @@ void SetCornerRoom()
auto dunData = LoadFileInMem<uint16_t>("nlevels\\l5data\\cornerstone.dun"); 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); PlaceDunTiles(dunData.get(), position, 0);
} }

18
Source/levels/drlg_l1.cpp

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

3
Source/levels/drlg_l1.h

@ -5,12 +5,13 @@
*/ */
#pragma once #pragma once
#include "engine/world_tile.hpp"
#include "levels/gendung.h" #include "levels/gendung.h"
namespace devilution { namespace devilution {
void PlaceMiniSetRandom(const Miniset &miniset, int rndper); void PlaceMiniSetRandom(const Miniset &miniset, int rndper);
Point SelectChamber(); WorldTilePosition SelectChamber();
void CreateL5Dungeon(uint32_t rseed, lvl_entry entry); void CreateL5Dungeon(uint32_t rseed, lvl_entry entry);
void LoadPreL1Dungeon(const char *path); void LoadPreL1Dungeon(const char *path);
void LoadL1Dungeon(const char *path, Point spawn); 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) void PlaceMiniSetRandom(const Miniset &miniset, int rndper)
{ {
int sw = miniset.size.width; const WorldTileCoord sw = miniset.size.width;
int sh = miniset.size.height; const WorldTileCoord sh = miniset.size.height;
for (int sy = 0; sy < DMAXY - sh; sy++) { for (WorldTileCoord sy = 0; sy < DMAXY - sh; sy++) {
for (int sx = 0; sx < DMAXX - sw; sx++) { for (WorldTileCoord sx = 0; sx < DMAXX - sw; sx++) {
if (SetPieceRoom.contains({ sx, sy })) if (SetPieceRoom.contains(sx, sy))
continue; continue;
if (!miniset.matches({ sx, sy })) if (!miniset.matches({ sx, sy }))
continue; continue;
@ -1752,21 +1752,18 @@ void PlaceHallExt(Point position)
* @param nHDir The direction of the hall from nRDest to this room. * @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. * @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; 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; return;
constexpr int RoomMax = 10; WorldTileDisplacement areaDisplacement = bottomRight - topLeft;
constexpr int RoomMin = 4; WorldTileSize area(areaDisplacement.deltaX, areaDisplacement.deltaY);
Size roomSize = area;
constexpr WorldTileCoord RoomMax = 10;
constexpr WorldTileCoord RoomMin = 4;
WorldTileSize roomSize = area;
if (area.width > RoomMin) if (area.width > RoomMin)
roomSize.width = GenerateRnd(std::min(area.width, RoomMax) - RoomMin) + RoomMin; roomSize.width = GenerateRnd(std::min(area.width, RoomMax) - RoomMin) + RoomMin;
if (area.height > RoomMin) if (area.height > RoomMin)
@ -1775,8 +1772,11 @@ void CreateRoom(Point topLeft, Point bottomRight, int nRDest, HallDirection nHDi
if (size) if (size)
roomSize = *size; roomSize = *size;
Point roomTopLeft = topLeft + Displacement { GenerateRnd(area.width), GenerateRnd(area.height) }; const int32_t randomWidth = GenerateRnd(area.width);
Point roomBottomRight = roomTopLeft + Displacement { roomSize }; const int32_t randomHeight = GenerateRnd(area.height);
WorldTilePosition roomTopLeft = topLeft + WorldTileDisplacement(randomWidth, randomHeight);
WorldTilePosition roomBottomRight = roomTopLeft + WorldTileDisplacement(roomSize);
if (roomBottomRight.x > bottomRight.x) { if (roomBottomRight.x > bottomRight.x) {
roomBottomRight.x = bottomRight.x; roomBottomRight.x = bottomRight.x;
roomTopLeft.x = bottomRight.x - roomSize.width; 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.y = bottomRight.y - roomSize.height;
} }
roomTopLeft.x = clamp(roomTopLeft.x, 1, 38); roomTopLeft.x = clamp<WorldTileCoord>(roomTopLeft.x, 1, 38);
roomTopLeft.y = clamp(roomTopLeft.y, 1, 38); roomTopLeft.y = clamp<WorldTileCoord>(roomTopLeft.y, 1, 38);
roomBottomRight.x = clamp(roomBottomRight.x, 1, 38); roomBottomRight.x = clamp<WorldTileCoord>(roomBottomRight.x, 1, 38);
roomBottomRight.y = clamp(roomBottomRight.y, 1, 38); roomBottomRight.y = clamp<WorldTileCoord>(roomBottomRight.y, 1, 38);
DefineRoom(roomTopLeft, roomBottomRight, static_cast<bool>(size)); DefineRoom(roomTopLeft, roomBottomRight, static_cast<bool>(size));
constexpr Displacement standoff { 2, 2 }; constexpr WorldTileDisplacement standoff { 2, 2 };
if (size) if (size)
SetPieceRoom = { roomTopLeft + standoff, roomSize - 1 }; 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 }); HallList.push_back({ { nHx1, nHy1 }, { nHx2, nHy2 }, nHDir });
} }
Point roomBottomLeft { roomTopLeft.x, roomBottomRight.y }; WorldTilePosition roomBottomLeft { roomTopLeft.x, roomBottomRight.y };
Point roomTopRight { roomBottomRight.x, roomTopLeft.y }; WorldTilePosition roomTopRight { roomBottomRight.x, roomTopLeft.y };
if (roomSize.height > roomSize.width) { if (roomSize.height > roomSize.width) {
CreateRoom(topLeft + standoff, roomBottomLeft - standoff, nRid, HallDirection::Right, {}); CreateRoom(topLeft + standoff, roomBottomLeft - standoff, nRid, HallDirection::Right, {});
CreateRoom(roomTopRight + standoff, bottomRight - standoff, nRid, HallDirection::Left, {}); 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(WorldTilePosition { topLeft.x, roomBottomRight.y } + standoff, WorldTilePosition { 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 { roomTopLeft.x, topLeft.y } + standoff, WorldTilePosition { bottomRight.x, roomTopLeft.y } - standoff, nRid, HallDirection::Down, {});
} else { } else {
CreateRoom(topLeft + standoff, roomTopRight - standoff, nRid, HallDirection::Down, {}); CreateRoom(topLeft + standoff, roomTopRight - standoff, nRid, HallDirection::Down, {});
CreateRoom(roomBottomLeft + standoff, bottomRight - standoff, nRid, HallDirection::Up, {}); 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(WorldTilePosition { topLeft.x, roomTopLeft.y } + standoff, WorldTilePosition { 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 { roomBottomRight.x, topLeft.y } + standoff, WorldTilePosition { bottomRight.x, roomBottomRight.y } - standoff, nRid, HallDirection::Left, {});
} }
} }
@ -2059,9 +2059,9 @@ void FixTilesPatterns()
void Substitution() void Substitution()
{ {
for (int y = 0; y < DMAXY; y++) { for (WorldTileCoord y = 0; y < DMAXY; y++) {
for (int x = 0; x < DMAXX; x++) { for (WorldTileCoord x = 0; x < DMAXX; x++) {
if (SetPieceRoom.contains({ x, y })) if (SetPieceRoom.contains(x, y))
continue; continue;
if (!FlipCoin(4)) if (!FlipCoin(4))
continue; continue;
@ -2425,7 +2425,7 @@ bool FillVoids()
bool CreateDungeon() bool CreateDungeon()
{ {
std::optional<Size> size; std::optional<WorldTileSize> size;
switch (currlevel) { switch (currlevel) {
case 5: 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) bool PlaceMiniSetRandom(const Miniset &miniset, int rndper)
{ {
int sw = miniset.size.width; const WorldTileCoord sw = miniset.size.width;
int sh = miniset.size.height; const WorldTileCoord sh = miniset.size.height;
bool placed = false; bool placed = false;
for (int sy = 0; sy < DMAXY - sh; sy++) { for (WorldTileCoord sy = 0; sy < DMAXY - sh; sy++) {
for (int sx = 0; sx < DMAXX - sw; sx++) { for (WorldTileCoord sx = 0; sx < DMAXX - sw; sx++) {
if (!miniset.matches({ sx, sy })) if (!miniset.matches({ sx, sy }))
continue; continue;
// BUGFIX: This should not be applied to Nest levels // 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 (WorldTileCoord 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 i = 1; i < DMAXX; i++) { // BUGFIX: Change '0' to '1' (fixed)
// note the comma operator is used here to advance the RNG state // note the comma operator is used here to advance the RNG state
if (dungeon[i][j] == 7 && (AdvanceRndSeed(), !IsNearThemeRoom({ i, j }))) { if (dungeon[i][j] == 7 && (AdvanceRndSeed(), !IsNearThemeRoom({ i, j }))) {
if (FlipCoin()) { if (FlipCoin()) {
@ -1800,9 +1800,9 @@ void LoadQuestSetPieces()
bool PlaceAnvil() bool PlaceAnvil()
{ {
// growing the size by 2 to allow a 1 tile border on all sides // 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; WorldTileSize areaSize = WorldTileSize(SDL_SwapLE16(pSetPiece[0]), SDL_SwapLE16(pSetPiece[1])) + 2;
int sx = GenerateRnd(DMAXX - areaSize.width); WorldTileCoord sx = GenerateRnd(DMAXX - areaSize.width);
int sy = GenerateRnd(DMAXY - areaSize.height); WorldTileCoord sy = GenerateRnd(DMAXY - areaSize.height);
for (int trys = 0;; trys++, sx++) { for (int trys = 0;; trys++, sx++) {
if (trys > 198) if (trys > 198)
@ -1817,7 +1817,7 @@ bool PlaceAnvil()
} }
bool found = true; 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) { if (Protected.test(tile.x, tile.y) || dungeon[tile.x][tile.y] != 7) {
found = false; found = false;
break; break;
@ -1830,7 +1830,7 @@ bool PlaceAnvil()
PlaceDunTiles(pSetPiece.get(), { sx + 1, sy + 1 }, 7); PlaceDunTiles(pSetPiece.get(), { sx + 1, sy + 1 }, 7);
SetPiece = { { sx, sy }, areaSize }; SetPiece = { { sx, sy }, areaSize };
for (Point tile : PointsInRectangleRange { SetPiece }) { for (WorldTilePosition tile : PointsInRectangle(SetPiece)) {
Protected.set(tile.x, tile.y); Protected.set(tile.x, tile.y);
} }

68
Source/levels/drlg_l4.cpp

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

9
Source/levels/drlg_l4.h

@ -5,14 +5,15 @@
*/ */
#pragma once #pragma once
#include "engine/world_tile.hpp"
#include "levels/gendung.h" #include "levels/gendung.h"
namespace devilution { namespace devilution {
extern Point DiabloQuad1; extern WorldTilePosition DiabloQuad1;
extern Point DiabloQuad2; extern WorldTilePosition DiabloQuad2;
extern Point DiabloQuad3; extern WorldTilePosition DiabloQuad3;
extern Point DiabloQuad4; extern WorldTilePosition DiabloQuad4;
void CreateL4Dungeon(uint32_t rseed, lvl_entry entry); void CreateL4Dungeon(uint32_t rseed, lvl_entry entry);
void LoadPreL4Dungeon(const char *path); 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 dungeon[DMAXX][DMAXY];
uint8_t pdungeon[DMAXX][DMAXY]; uint8_t pdungeon[DMAXX][DMAXY];
Bitset2d<DMAXX, DMAXY> Protected; Bitset2d<DMAXX, DMAXY> Protected;
Rectangle SetPieceRoom; WorldTileRectangle SetPieceRoom;
Rectangle SetPiece; WorldTileRectangle SetPiece;
std::unique_ptr<uint16_t[]> pSetPiece; std::unique_ptr<uint16_t[]> pSetPiece;
OptionalOwnedClxSpriteList pSpecialCels; OptionalOwnedClxSpriteList pSpecialCels;
std::unique_ptr<MegaTile[]> pMegaTiles; std::unique_ptr<MegaTile[]> pMegaTiles;
std::unique_ptr<byte[]> pDungeonCels; std::unique_ptr<byte[]> pDungeonCels;
std::array<TileProperties, MAXTILES> SOLData; std::array<TileProperties, MAXTILES> SOLData;
Point dminPosition; WorldTilePosition dminPosition;
Point dmaxPosition; WorldTilePosition dmaxPosition;
dungeon_type leveltype; dungeon_type leveltype;
uint8_t currlevel; uint8_t currlevel;
bool setlevel; bool setlevel;
@ -89,7 +89,7 @@ std::unique_ptr<uint16_t[]> LoadMinData(size_t &tileCount)
* @param maxSize maximum allowable value for both dimensions * @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 * @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) { if (origin.x + maxSize > DMAXX && origin.y + maxSize > DMAXY) {
return {}; // Original broken bounds check, avoids lower right corner 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 {}; return {};
} }
const int maxWidth = std::min(maxSize, DMAXX - origin.x); const WorldTileCoord maxWidth = std::min<WorldTileCoord>(maxSize, DMAXX - origin.x);
const int maxHeight = std::min(maxSize, DMAXY - origin.y); 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++) { for (WorldTileCoord i = 0; i < maxSize; i++) {
int width = i < room.height ? i : 0; WorldTileCoord width = i < room.height ? i : 0;
if (i < maxHeight) { if (i < maxHeight) {
while (width < room.width) { while (width < room.width) {
if (dungeon[origin.x + width][origin.y + i] != floor) 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) { if (i < maxWidth) {
while (height < room.height) { while (height < room.height) {
if (dungeon[origin.x + i][origin.y + height] != floor) if (dungeon[origin.x + i][origin.y + height] != floor)
@ -342,16 +342,16 @@ void InitGlobals()
DRLG_InitTrans(); DRLG_InitTrans();
dminPosition = Point(0, 0).megaToWorld(); dminPosition = WorldTilePosition(0, 0).megaToWorld();
dmaxPosition = Point(40, 40).megaToWorld(); dmaxPosition = WorldTilePosition(40, 40).megaToWorld();
SetPieceRoom = { { -1, -1 }, { -1, -1 } }; SetPieceRoom = { { 0, 0 }, { 0, 0 } };
SetPiece = { { 0, 0 }, { 0, 0 } }; SetPiece = { { 0, 0 }, { 0, 0 } };
} }
} // namespace } // namespace
#ifdef BUILD_TESTING #ifdef BUILD_TESTING
std::optional<Size> GetSizeForThemeRoom() std::optional<WorldTileSize> GetSizeForThemeRoom()
{ {
return GetSizeForThemeRoom(0, { 0, 0 }, 5, 10); return GetSizeForThemeRoom(0, { 0, 0 }, 5, 10);
} }
@ -500,10 +500,10 @@ void DRLG_InitTrans()
TransVal = 1; TransVal = 1;
} }
void DRLG_RectTrans(Rectangle area) void DRLG_RectTrans(WorldTileRectangle area)
{ {
Point position = area.position; WorldTilePosition position = area.position;
Size size = area.size; WorldTileSize size = area.size;
for (int j = position.y; j <= position.y + size.height; j++) { for (int j = position.y; j <= position.y + size.height; j++) {
for (int i = position.x; i <= position.x + size.width; i++) { for (int i = position.x; i <= position.x + size.width; i++) {
@ -514,17 +514,17 @@ void DRLG_RectTrans(Rectangle area)
TransVal++; TransVal++;
} }
void DRLG_MRectTrans(Rectangle area) void DRLG_MRectTrans(WorldTileRectangle area)
{ {
Point position = area.position.megaToWorld(); WorldTilePosition position = area.position.megaToWorld();
Size size = area.size * 2; 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) 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); SetMapObjects(dunData.get(), 0, 0);
} }
void Make_SetPC(Rectangle area) void Make_SetPC(WorldTileRectangle area)
{ {
Point position = area.position.megaToWorld(); WorldTilePosition position = area.position.megaToWorld();
Size size = area.size * 2; WorldTileSize size = area.size * 2;
for (int j = 0; j < size.height; j++) { for (unsigned j = 0; j < size.height; j++) {
for (int i = 0; i < size.width; i++) { for (unsigned i = 0; i < size.width; i++) {
dFlags[position.x + i][position.y + j] |= DungeonFlag::Populated; 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; themeCount = 0;
memset(themeLoc, 0, sizeof(*themeLoc)); memset(themeLoc, 0, sizeof(*themeLoc));
for (int j = 0; j < DMAXY; j++) { for (WorldTileCoord j = 0; j < DMAXY; j++) {
for (int i = 0; i < DMAXX; i++) { for (WorldTileCoord i = 0; i < DMAXX; i++) {
if (dungeon[i][j] == floor && FlipCoin(freq)) { 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) if (!themeSize)
continue; continue;
@ -669,7 +669,7 @@ void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rn
} }
THEME_LOC &theme = themeLoc[themeCount]; 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)) { if (IsAnyOf(leveltype, DTYPE_CAVES, DTYPE_NEST)) {
DRLG_RectTrans({ (theme.room.position + Direction::South).megaToWorld(), theme.room.size * 2 - 5 }); DRLG_RectTrans({ (theme.room.position + Direction::South).megaToWorld(), theme.room.size * 2 - 5 });
} else { } else {
@ -699,13 +699,13 @@ void DRLG_HoldThemeRooms()
} }
} }
void SetSetPieceRoom(Point position, int floorId) void SetSetPieceRoom(WorldTilePosition position, int floorId)
{ {
if (pSetPiece == nullptr) if (pSetPiece == nullptr)
return; return;
PlaceDunTiles(pSetPiece.get(), position, floorId); 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() 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++) { 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; return true;
} }

39
Source/levels/gendung.h

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

6
Source/levels/setmaps.cpp

@ -35,13 +35,13 @@ namespace {
void AddSKingObjs() void AddSKingObjs()
{ {
constexpr Rectangle SmallSecretRoom { { 20, 7 }, { 3, 3 } }; constexpr WorldTileRectangle SmallSecretRoom { { 20, 7 }, { 3, 3 } };
ObjectAtPosition({ 64, 34 }).InitializeLoadedObject(SmallSecretRoom, 1); 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); 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({ 27, 37 }).InitializeLoadedObject(LargeSecretRoom, 3);
ObjectAtPosition({ 46, 35 }).InitializeLoadedObject(LargeSecretRoom, 3); ObjectAtPosition({ 46, 35 }).InitializeLoadedObject(LargeSecretRoom, 3);
ObjectAtPosition({ 49, 53 }).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) 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) { 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 // note out-of-bounds tiles are not solid, this function relies on the guard in TFit_Obj5 and dungeon border
if (IsTileSolid(testPosition)) { if (IsTileSolid(testPosition)) {
@ -110,7 +110,7 @@ bool TFit_Obj5(int t)
} }
int candidatesFound = 0; 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)) { 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 // 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; themex = tile.x;
@ -152,7 +152,7 @@ bool TFit_GoatShrine(int t)
bool CheckThemeObj3(Point origin, int8_t regionId, unsigned frequency = 0) 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) { return std::all_of(searchArea.cbegin(), searchArea.cend(), [regionId, frequency](Point testPosition) {
if (!InDungeonBounds(testPosition)) { if (!InDungeonBounds(testPosition)) {
return false; return false;

5
Source/missiles.cpp

@ -1475,10 +1475,7 @@ void AddRuneExplosion(Missile &missile, const AddMissileParameter & /*parameter*
missile._midam = dmg; missile._midam = dmg;
auto searchArea = PointsInRectangleRangeColMajor { for (Point position : PointsInRectangleColMajor(Rectangle { missile.position.tile, 1 }))
Rectangle { missile.position.tile, 1 }
};
for (Point position : searchArea)
CheckMissileCol(missile, dmg, dmg, false, position, true); CheckMissileCol(missile, dmg, dmg, false, position, true);
} }
missile._mlid = AddLight(missile.position.start, 8); 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); 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; auto tren = TransVal;
TransVal = 9; 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; TransVal = tren;
Quests[Q_LTBANNER]._qvar1 = 2; Quests[Q_LTBANNER]._qvar1 = 2;
if (Quests[Q_LTBANNER]._qactive == QUEST_INIT) if (Quests[Q_LTBANNER]._qactive == QUEST_INIT)
@ -3606,7 +3606,7 @@ void M_StartStand(Monster &monster, Direction md)
void M_ClearSquares(const Monster &monster) 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) if (FindMonsterAtPosition(searchTile) == &monster)
dMonster[searchTile.x][searchTile.y] = 0; dMonster[searchTile.x][searchTile.y] = 0;
} }
@ -4181,7 +4181,7 @@ void SyncMonsterAnim(Monster &monster)
void M_FallenFear(Point position) void M_FallenFear(Point position)
{ {
const Rectangle fearArea = Rectangle { position, 4 }; const Rectangle fearArea = Rectangle { position, 4 };
for (const Point tile : PointsInRectangleRange { fearArea }) { for (const Point tile : PointsInRectangle(fearArea)) {
if (!InDungeonBounds(tile)) if (!InDungeonBounds(tile))
continue; continue;
int m = dMonster[tile.x][tile.y]; 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 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. * @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 }); std::optional<Point> position = GetRandomObjectPosition({ 2, 2 });
if (!position) 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; LoadingMapObjects = true;
ApplyObjectLighting = true; ApplyObjectLighting = true;
@ -920,10 +920,10 @@ void AddStoryBooks()
void AddHookedBodies(int freq) void AddHookedBodies(int freq)
{ {
for (int j = 0; j < DMAXY; j++) { for (WorldTileCoord j = 0; j < DMAXY; j++) {
int jj = 16 + j * 2; WorldTileCoord jj = 16 + j * 2;
for (int i = 0; i < DMAXX; i++) { for (WorldTileCoord i = 0; i < DMAXX; i++) {
int ii = 16 + i * 2; WorldTileCoord ii = 16 + i * 2;
if (dungeon[i][j] != 1 && dungeon[i][j] != 2) if (dungeon[i][j] != 1 && dungeon[i][j] != 2)
continue; continue;
if (!FlipCoin(freq)) if (!FlipCoin(freq))
@ -2000,7 +2000,7 @@ void OperateBookLever(Object &questBook, bool sendmsg)
SpawnUnique(UITEM_OPTAMULET, SetPiece.position.megaToWorld() + Displacement { 5, 5 }); SpawnUnique(UITEM_OPTAMULET, SetPiece.position.megaToWorld() + Displacement { 5, 5 });
auto tren = TransVal; auto tren = TransVal;
TransVal = 9; 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; TransVal = tren;
} }
} }
@ -3528,7 +3528,7 @@ void SyncQSTLever(const Object &qstLever)
if (qstLever._otype == OBJ_BLINDBOOK) { if (qstLever._otype == OBJ_BLINDBOOK) {
auto tren = TransVal; auto tren = TransVal;
TransVal = 9; 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; TransVal = tren;
} }
} }
@ -3996,7 +3996,7 @@ Object *AddObject(_object_id objType, Point objPos)
AddDoor(object); AddDoor(object);
break; break;
case OBJ_BOOK2R: 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; break;
case OBJ_CHEST1: case OBJ_CHEST1:
case OBJ_CHEST2: case OBJ_CHEST2:
@ -4146,7 +4146,7 @@ void OperateTrap(Object &trap)
Point triggerPosition = { trap._oVar1, trap._oVar2 }; Point triggerPosition = { trap._oVar1, trap._oVar2 };
Point target = triggerPosition; 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) // 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; }); auto foundPosition = std::find_if(searchArea.crbegin(), searchArea.crend(), [](Point testPosition) { return InDungeonBounds(testPosition) && dPlayer[testPosition.x][testPosition.y] != 0; });
if (foundPosition != searchArea.crend()) { if (foundPosition != searchArea.crend()) {

13
Source/objects.h

@ -10,6 +10,7 @@
#include "engine/clx_sprite.hpp" #include "engine/clx_sprite.hpp"
#include "engine/point.hpp" #include "engine/point.hpp"
#include "engine/rectangle.hpp" #include "engine/rectangle.hpp"
#include "engine/world_tile.hpp"
#include "itemdat.h" #include "itemdat.h"
#include "monster.h" #include "monster.h"
#include "objdat.h" #include "objdat.h"
@ -84,7 +85,7 @@ struct Object {
* @param topLeftPosition corner of the map region closest to the origin. * @param topLeftPosition corner of the map region closest to the origin.
* @param bottomRightPosition corner of the map region furthest from 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; _oVar1 = topLeftPosition.x;
_oVar2 = topLeftPosition.y; _oVar2 = topLeftPosition.y;
@ -96,9 +97,9 @@ struct Object {
* @brief Convenience function for SetMapRange(Point, Point). * @brief Convenience function for SetMapRange(Point, Point).
* @param mapRange A rectangle defining the top left corner and size of the affected region. * @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. * @param mapRange The region to be updated when this object is activated.
*/ */
constexpr void InitializeBook(Rectangle mapRange) constexpr void InitializeBook(WorldTileRectangle mapRange)
{ {
SetMapRange(mapRange); SetMapRange(mapRange);
_oVar6 = _oAnimFrame + 1; // Save the frame number for the open book frame _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 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. * @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); InitializeBook(mapRange);
_oVar8 = leverID; _oVar8 = leverID;
@ -133,7 +134,7 @@ struct Object {
* @param mapRange The region which was updated to spawn this 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. * @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); SetMapRange(mapRange);
_oVar8 = leverID; _oVar8 = leverID;

2
Source/player.cpp

@ -2885,7 +2885,7 @@ void StartPlrBlock(Player &player, Direction dir)
void FixPlrWalkTags(const Player &player) 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) { if (PlayerAtPosition(searchTile) == &player) {
dPlayer[searchTile.x][searchTile.y] = 0; dPlayer[searchTile.x][searchTile.y] = 0;
} }

10
Source/qol/stash.cpp

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

13
Source/quests.cpp

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

1
test/CMakeLists.txt

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

27
test/drlg_common_test.cpp

@ -4,20 +4,21 @@
#include <array> #include <array>
#include "engine/points_in_rectangle_range.hpp" #include "engine/points_in_rectangle_range.hpp"
#include "engine/world_tile.hpp"
#include "levels/gendung.h" #include "levels/gendung.h"
namespace devilution { namespace devilution {
TEST(DrlgTest, RectangleRangeIterator) TEST(DrlgTest, RectangleRangeIterator)
{ {
constexpr Rectangle topLeftArea = Rectangle { Point { 1, 1 }, 1 }; constexpr WorldTileRectangle topLeftArea { { 1, 1 }, 1 };
constexpr Rectangle bottomRightArea = Rectangle { Point { 3, 3 }, 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 // 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 {}; std::array<std::array<int, 5>, 5> region {};
int counter = 0; int counter = 0;
// Iterate over a 9 tile area in the top left of the region. // 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; region[position.x][position.y] = ++counter;
} }
@ -35,7 +36,7 @@ TEST(DrlgTest, RectangleRangeIterator)
region = {}; region = {};
counter = 0; 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; }); 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[4][4], 1) << "Reverse iterators are required";
EXPECT_EQ(region[2][4], 3) << "Reverse iterators are required"; EXPECT_EQ(region[2][4], 3) << "Reverse iterators are required";
@ -45,7 +46,7 @@ TEST(DrlgTest, RectangleRangeIterator)
region = {}; region = {};
counter = 0; counter = 0;
for (Point position : PointsInRectangleRangeColMajor { topLeftArea }) { for (WorldTilePosition position : PointsInRectangleColMajor(topLeftArea)) {
region[position.x][position.y] = ++counter; region[position.x][position.y] = ++counter;
} }
@ -63,7 +64,7 @@ TEST(DrlgTest, RectangleRangeIterator)
region = {}; region = {};
counter = 0; 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; }); 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][4], 1) << "Reverse iterators are required";
EXPECT_EQ(region[4][2], 3) << "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)); 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; 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; 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; 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][5] = 0;
dungeon[9][4] = 0; dungeon[9][4] = 0;
dungeon[9][9] = 0; dungeon[9][9] = 0;
@ -90,10 +91,10 @@ TEST(DrlgTest, ThemeRoomSize)
// Time for some unusual cases // Time for some unusual cases
dungeon[7][2] = 1; dungeon[7][2] = 1;
dungeon[5][9] = 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; 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[6][4] = 0;
dungeon[5][9] = 0; dungeon[5][9] = 0;
@ -102,7 +103,7 @@ TEST(DrlgTest, ThemeRoomSize)
dungeon[7][0] = 1; dungeon[7][0] = 1;
dungeon[6][6] = 1; dungeon[6][6] = 1;
dungeon[8][5] = 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 } // 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