diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index a8fa569548..d106053dd4 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -453,6 +453,7 @@
+
diff --git a/src/openrct2/world/Map.cpp b/src/openrct2/world/Map.cpp
index ee7f92d6ec..8c6e99f95a 100644
--- a/src/openrct2/world/Map.cpp
+++ b/src/openrct2/world/Map.cpp
@@ -52,6 +52,7 @@
#include "Scenery.h"
#include "SmallScenery.h"
#include "Surface.h"
+#include "TileElementsView.h"
#include "TileInspector.h"
#include "Wall.h"
@@ -215,67 +216,35 @@ void map_set_tile_element(const TileCoordsXY& tilePos, TileElement* elements)
SurfaceElement* map_get_surface_element_at(const CoordsXY& coords)
{
- TileElement* tileElement = map_get_first_element_at(coords);
+ auto view = TileElementsView(coords);
- if (tileElement == nullptr)
- return nullptr;
-
- // Find the first surface element
- while (tileElement->GetType() != TILE_ELEMENT_TYPE_SURFACE)
- {
- if (tileElement->IsLastForTile())
- return nullptr;
-
- tileElement++;
- }
-
- return tileElement->AsSurface();
+ return *view.begin();
}
PathElement* map_get_path_element_at(const TileCoordsXYZ& loc)
{
- TileElement* tileElement = map_get_first_element_at(loc.ToCoordsXY());
-
- if (tileElement == nullptr)
- return nullptr;
-
- // Find the path element at known z
- do
+ for (auto* element : TileElementsView(loc.ToCoordsXY()))
{
- if (tileElement->IsGhost())
+ if (element->IsGhost())
continue;
- if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
+ if (element->base_height != loc.z)
continue;
- if (tileElement->base_height != loc.z)
- continue;
-
- return tileElement->AsPath();
- } while (!(tileElement++)->IsLastForTile());
-
+ return element;
+ }
return nullptr;
}
BannerElement* map_get_banner_element_at(const CoordsXYZ& bannerPos, uint8_t position)
{
- auto bannerTilePos = TileCoordsXYZ{ bannerPos };
- TileElement* tileElement = map_get_first_element_at(bannerPos);
-
- if (tileElement == nullptr)
- return nullptr;
-
- // Find the banner element at known z and position
- do
+ const auto bannerTilePos = TileCoordsXYZ{ bannerPos };
+ for (auto* element : TileElementsView(bannerPos))
{
- if (tileElement->GetType() != TILE_ELEMENT_TYPE_BANNER)
+ if (element->base_height != bannerTilePos.z)
continue;
- if (tileElement->base_height != bannerTilePos.z)
+ if (element->GetPosition() != position)
continue;
- if (tileElement->AsBanner()->GetPosition() != position)
- continue;
-
- return tileElement->AsBanner();
- } while (!(tileElement++)->IsLastForTile());
-
+ return element;
+ }
return nullptr;
}
diff --git a/src/openrct2/world/TileElement.h b/src/openrct2/world/TileElement.h
index 04dd468635..905674ac42 100644
--- a/src/openrct2/world/TileElement.h
+++ b/src/openrct2/world/TileElement.h
@@ -99,6 +99,80 @@ struct TileElementBase
uint8_t GetOwner() const;
void SetOwner(uint8_t newOwner);
+
+ template const TType* as() const
+ {
+ return static_cast(GetType()) == TType::ElementType ? reinterpret_cast(this) : nullptr;
+ }
+ template TType* as()
+ {
+ return static_cast(GetType()) == TType::ElementType ? reinterpret_cast(this) : nullptr;
+ }
+
+ const SurfaceElement* AsSurface() const
+ {
+ return as();
+ }
+ SurfaceElement* AsSurface()
+ {
+ return as();
+ }
+ const PathElement* AsPath() const
+ {
+ return as();
+ }
+ PathElement* AsPath()
+ {
+ return as();
+ }
+ const TrackElement* AsTrack() const
+ {
+ return as();
+ }
+ TrackElement* AsTrack()
+ {
+ return as();
+ }
+ const SmallSceneryElement* AsSmallScenery() const
+ {
+ return as();
+ }
+ SmallSceneryElement* AsSmallScenery()
+ {
+ return as();
+ }
+ const LargeSceneryElement* AsLargeScenery() const
+ {
+ return as();
+ }
+ LargeSceneryElement* AsLargeScenery()
+ {
+ return as();
+ }
+ const WallElement* AsWall() const
+ {
+ return as();
+ }
+ WallElement* AsWall()
+ {
+ return as();
+ }
+ const EntranceElement* AsEntrance() const
+ {
+ return as();
+ }
+ EntranceElement* AsEntrance()
+ {
+ return as();
+ }
+ const BannerElement* AsBanner() const
+ {
+ return as();
+ }
+ BannerElement* AsBanner()
+ {
+ return as();
+ }
};
/**
@@ -110,81 +184,6 @@ struct TileElement : public TileElementBase
uint8_t pad_05[3];
uint8_t pad_08[8];
- template const TType* as() const
- {
- return static_cast(GetType()) == TClass ? reinterpret_cast(this) : nullptr;
- }
- template TType* as()
- {
- return static_cast(GetType()) == TClass ? reinterpret_cast(this) : nullptr;
- }
-
-public:
- const SurfaceElement* AsSurface() const
- {
- return as();
- }
- SurfaceElement* AsSurface()
- {
- return as();
- }
- const PathElement* AsPath() const
- {
- return as();
- }
- PathElement* AsPath()
- {
- return as();
- }
- const TrackElement* AsTrack() const
- {
- return as();
- }
- TrackElement* AsTrack()
- {
- return as();
- }
- const SmallSceneryElement* AsSmallScenery() const
- {
- return as();
- }
- SmallSceneryElement* AsSmallScenery()
- {
- return as();
- }
- const LargeSceneryElement* AsLargeScenery() const
- {
- return as();
- }
- LargeSceneryElement* AsLargeScenery()
- {
- return as();
- }
- const WallElement* AsWall() const
- {
- return as();
- }
- WallElement* AsWall()
- {
- return as();
- }
- const EntranceElement* AsEntrance() const
- {
- return as();
- }
- EntranceElement* AsEntrance()
- {
- return as();
- }
- const BannerElement* AsBanner() const
- {
- return as();
- }
- BannerElement* AsBanner()
- {
- return as();
- }
-
void ClearAs(uint8_t newType);
ride_id_t GetRideIndex() const;
@@ -197,6 +196,8 @@ assert_struct_size(TileElement, 16);
struct SurfaceElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Surface;
+
private:
uint8_t Slope;
uint8_t WaterHeight;
@@ -242,6 +243,8 @@ assert_struct_size(SurfaceElement, 16);
struct PathElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Path;
+
private:
PathSurfaceIndex SurfaceIndex; // 5
#pragma clang diagnostic push
@@ -329,6 +332,8 @@ assert_struct_size(PathElement, 16);
struct TrackElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Track;
+
private:
track_type_t TrackType;
union
@@ -428,6 +433,8 @@ assert_struct_size(TrackElement, 16);
struct SmallSceneryElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::SmallScenery;
+
private:
ObjectEntryIndex entryIndex; // 5
uint8_t age; // 7
@@ -459,6 +466,8 @@ assert_struct_size(SmallSceneryElement, 16);
struct LargeSceneryElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::LargeScenery;
+
private:
ObjectEntryIndex EntryIndex;
::BannerIndex BannerIndex;
@@ -494,6 +503,8 @@ assert_struct_size(LargeSceneryElement, 16);
struct WallElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Wall;
+
private:
ObjectEntryIndex entryIndex; // 05
colour_t colour_1; // 07
@@ -537,6 +548,8 @@ assert_struct_size(WallElement, 16);
struct EntranceElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Entrance;
+
private:
uint8_t entranceType; // 5
uint8_t SequenceIndex; // 6. Only uses the lower nibble.
@@ -568,6 +581,8 @@ assert_struct_size(EntranceElement, 16);
struct BannerElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Banner;
+
private:
BannerIndex index; // 5
uint8_t position; // 7
@@ -594,6 +609,8 @@ assert_struct_size(BannerElement, 16);
struct CorruptElement : TileElementBase
{
+ static constexpr TileElementType ElementType = TileElementType::Corrupt;
+
uint8_t pad[3];
uint8_t pad_08[8];
};
diff --git a/src/openrct2/world/TileElementsView.h b/src/openrct2/world/TileElementsView.h
new file mode 100644
index 0000000000..d43e995999
--- /dev/null
+++ b/src/openrct2/world/TileElementsView.h
@@ -0,0 +1,130 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2021 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#include "Location.hpp"
+#include "Map.h"
+#include "TileElement.h"
+
+#include
+
+namespace OpenRCT2
+{
+ namespace Detail
+ {
+ template T* NextMatchingTile(T2* element)
+ {
+ if (element == nullptr)
+ return nullptr;
+
+ for (;;)
+ {
+ auto* res = element->template as();
+ if (res != nullptr)
+ return res;
+
+ if (element->IsLastForTile())
+ {
+ break;
+ }
+ element++;
+ }
+
+ return nullptr;
+ }
+ } // namespace Detail
+
+ template class TileElementsView
+ {
+ const CoordsXY _loc;
+
+ public:
+ struct Iterator
+ {
+ T* element = nullptr;
+
+ Iterator& operator++()
+ {
+ if (element == nullptr)
+ return *this;
+
+ if (element->IsLastForTile())
+ {
+ element = nullptr;
+ }
+ else
+ {
+ element++;
+ if constexpr (!std::is_same_v)
+ {
+ element = Detail::NextMatchingTile(element);
+ }
+ }
+
+ return *this;
+ }
+
+ Iterator operator++(int)
+ {
+ Iterator res = *this;
+ ++(*this);
+ return res;
+ }
+
+ bool operator==(Iterator other) const
+ {
+ return element == other.element;
+ }
+
+ bool operator!=(Iterator other) const
+ {
+ return !(*this == other);
+ }
+
+ T* operator*()
+ {
+ return element;
+ }
+
+ const T* operator*() const
+ {
+ return element;
+ }
+
+ // iterator traits
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using pointer = const T*;
+ using reference = const T&;
+ using iterator_category = std::forward_iterator_tag;
+ };
+
+ TileElementsView(const CoordsXY& loc)
+ : _loc(loc)
+ {
+ }
+
+ Iterator begin() noexcept
+ {
+ T* element = reinterpret_cast(map_get_first_element_at(_loc));
+
+ if constexpr (!std::is_same_v)
+ {
+ element = Detail::NextMatchingTile(element);
+ }
+
+ return Iterator{ element };
+ }
+
+ Iterator end() noexcept
+ {
+ return Iterator{ nullptr };
+ }
+ };
+
+} // namespace OpenRCT2
diff --git a/test/tests/TileElementsView.cpp b/test/tests/TileElementsView.cpp
new file mode 100644
index 0000000000..d70cb57efa
--- /dev/null
+++ b/test/tests/TileElementsView.cpp
@@ -0,0 +1,180 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2021 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#include "TestData.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace OpenRCT2;
+
+class TileElementsViewTests : public testing::Test
+{
+protected:
+ static void SetUpTestCase()
+ {
+ std::string parkPath = TestData::GetParkPath("bpb.sv6");
+ gOpenRCT2Headless = true;
+ gOpenRCT2NoGraphics = true;
+ _context = CreateContext();
+ bool initialised = _context->Initialise();
+ ASSERT_TRUE(initialised);
+
+ load_from_sv6(parkPath.c_str());
+ game_load_init();
+
+ // Changed in some tests. Store to restore its value
+ _gScreenFlags = gScreenFlags;
+ SUCCEED();
+ }
+
+ static void TearDownTestCase()
+ {
+ if (_context)
+ _context.reset();
+
+ gScreenFlags = _gScreenFlags;
+ }
+
+private:
+ static std::shared_ptr _context;
+ static uint8_t _gScreenFlags;
+};
+
+std::shared_ptr TileElementsViewTests::_context;
+uint8_t TileElementsViewTests::_gScreenFlags;
+
+template std::vector BuildListManual(const CoordsXY& pos)
+{
+ std::vector res;
+
+ TileElement* element = map_get_first_element_at(pos);
+ if (element == nullptr)
+ return res;
+
+ do
+ {
+ if constexpr (!std::is_same_v)
+ {
+ auto* res = element->as();
+ if (res)
+ res.push_back(res);
+ }
+ else
+ {
+ res.push_back(element);
+ }
+
+ } while (!(element++)->IsLastForTile());
+
+ return res;
+}
+
+template std::vector BuildListByView(const CoordsXY& pos)
+{
+ std::vector res;
+
+ for (auto* element : TileElementsView(pos))
+ {
+ res.push_back(element);
+ }
+
+ return res;
+}
+
+template bool CompareLists(const CoordsXY& pos)
+{
+ auto listManual = BuildListManual(pos);
+ auto listView = BuildListByView(pos);
+
+ EXPECT_EQ(listManual.size(), listView.size());
+ if (listManual.size() != listView.size())
+ return false;
+
+ for (size_t i = 0; i < listManual.size(); ++i)
+ {
+ EXPECT_EQ(listManual[i], listView[i]) << "[i] = " << i;
+
+ if (listManual[i] != listView[i])
+ return false;
+ }
+
+ return true;
+}
+
+template void CheckMapTiles()
+{
+ for (int x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; ++x)
+ {
+ for (int y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; ++y)
+ {
+ auto pos = TileCoordsXY(x, y).ToCoordsXY();
+
+ bool matches = CompareLists(pos);
+ EXPECT_TRUE(matches) << "x = " << x << ", y = " << y;
+
+ if (!matches)
+ {
+ FAIL();
+ }
+ }
+ }
+ SUCCEED();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeGeneric)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypePathElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeSurfaceElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeTrackElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeSmallSceneryElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeLargeSceneryElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeWallElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeEntranceElements)
+{
+ CheckMapTiles();
+}
+
+TEST_F(TileElementsViewTests, QueryTypeBannerElements)
+{
+ CheckMapTiles();
+}
diff --git a/test/tests/tests.vcxproj b/test/tests/tests.vcxproj
index d192c57318..53a49a7ca0 100644
--- a/test/tests/tests.vcxproj
+++ b/test/tests/tests.vcxproj
@@ -78,6 +78,7 @@
+