diff --git a/3rdParty/tl/CMakeLists.txt b/3rdParty/tl/CMakeLists.txt
new file mode 100644
index 000000000..63dc97a7e
--- /dev/null
+++ b/3rdParty/tl/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_library(tl INTERFACE)
+
+target_include_directories(tl INTERFACE ${CMAKE_CURRENT_LIST_DIR})
diff --git a/3rdParty/tl/function_ref.hpp b/3rdParty/tl/function_ref.hpp
new file mode 100644
index 000000000..d8326833e
--- /dev/null
+++ b/3rdParty/tl/function_ref.hpp
@@ -0,0 +1,216 @@
+///
+// function_ref - A low-overhead non-owning function
+// Written in 2017 by Simon Brand (@TartanLlama)
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this software to the
+// public domain worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication
+// along with this software. If not, see
+// .
+///
+
+#ifndef TL_FUNCTION_REF_HPP
+#define TL_FUNCTION_REF_HPP
+
+#define TL_FUNCTION_REF_VERSION_MAJOR 1
+#define TL_FUNCTION_REF_VERSION_MINOR 0
+#define TL_FUNCTION_REF_VERSION_PATCH 0
+
+#if (defined(_MSC_VER) && _MSC_VER == 1900)
+/// \exclude
+#define TL_FUNCTION_REF_MSVC2015
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \
+ !defined(__clang__))
+/// \exclude
+#define TL_FUNCTION_REF_GCC49
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \
+ !defined(__clang__))
+/// \exclude
+#define TL_FUNCTION_REF_GCC54
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \
+ !defined(__clang__))
+// GCC < 5 doesn't support overloading on const&& for member functions
+/// \exclude
+#define TL_FUNCTION_REF_NO_CONSTRR
+#endif
+
+#if __cplusplus > 201103L
+/// \exclude
+#define TL_FUNCTION_REF_CXX14
+#endif
+
+// constexpr implies const in C++11, not C++14
+#if (__cplusplus == 201103L || defined(TL_FUNCTION_REF_MSVC2015) || \
+ defined(TL_FUNCTION_REF_GCC49)) && \
+ !defined(TL_FUNCTION_REF_GCC54)
+/// \exclude
+#define TL_FUNCTION_REF_11_CONSTEXPR
+#else
+/// \exclude
+#define TL_FUNCTION_REF_11_CONSTEXPR constexpr
+#endif
+
+#include
+#include
+
+namespace tl {
+namespace detail {
+namespace fnref {
+// C++14-style aliases for brevity
+template using remove_const_t = typename std::remove_const::type;
+template
+using remove_reference_t = typename std::remove_reference::type;
+template using decay_t = typename std::decay::type;
+template
+using enable_if_t = typename std::enable_if::type;
+template
+using conditional_t = typename std::conditional::type;
+
+// std::invoke from C++17
+// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround
+template >::value>,
+ int = 0>
+constexpr auto invoke(Fn &&f, Args &&... args) noexcept(
+ noexcept(std::mem_fn(f)(std::forward(args)...)))
+ -> decltype(std::mem_fn(f)(std::forward(args)...)) {
+ return std::mem_fn(f)(std::forward(args)...);
+}
+
+template >{}>>
+constexpr auto invoke(Fn &&f, Args &&... args) noexcept(
+ noexcept(std::forward(f)(std::forward(args)...)))
+ -> decltype(std::forward(f)(std::forward(args)...)) {
+ return std::forward(f)(std::forward(args)...);
+}
+
+// std::invoke_result from C++17
+template struct invoke_result_impl;
+
+template
+struct invoke_result_impl<
+ F, decltype(tl::detail::fnref::invoke(std::declval(), std::declval()...), void()),
+ Us...> {
+ using type = decltype(tl::detail::fnref::invoke(std::declval(), std::declval()...));
+};
+
+template
+using invoke_result = invoke_result_impl;
+
+template
+using invoke_result_t = typename invoke_result::type;
+
+template
+struct is_invocable_r_impl : std::false_type {};
+
+template
+struct is_invocable_r_impl<
+ typename std::is_convertible, R>::type, R, F, Args...>
+ : std::true_type {};
+
+template
+using is_invocable_r = is_invocable_r_impl;
+
+} // namespace detail
+} // namespace fnref
+
+/// A lightweight non-owning reference to a callable.
+///
+/// Example usage:
+///
+/// ```cpp
+/// void foo (function_ref func) {
+/// std::cout << "Result is " << func(21); //42
+/// }
+///
+/// foo([](int i) { return i*2; });
+template class function_ref;
+
+/// Specialization for function types.
+template class function_ref {
+public:
+ constexpr function_ref() noexcept = delete;
+
+ /// Creates a `function_ref` which refers to the same callable as `rhs`.
+ constexpr function_ref(const function_ref &rhs) noexcept = default;
+
+ /// Constructs a `function_ref` referring to `f`.
+ ///
+ /// \synopsis template constexpr function_ref(F &&f) noexcept
+ template , function_ref>::value &&
+ detail::fnref::is_invocable_r::value> * = nullptr>
+ TL_FUNCTION_REF_11_CONSTEXPR function_ref(F &&f) noexcept
+ : obj_(const_cast(reinterpret_cast(std::addressof(f)))) {
+ callback_ = [](void *obj, Args... args) -> R {
+ return detail::fnref::invoke(
+ *reinterpret_cast::type>(obj),
+ std::forward(args)...);
+ };
+ }
+
+ /// Makes `*this` refer to the same callable as `rhs`.
+ TL_FUNCTION_REF_11_CONSTEXPR function_ref &
+ operator=(const function_ref &rhs) noexcept = default;
+
+ /// Makes `*this` refer to `f`.
+ ///
+ /// \synopsis template constexpr function_ref &operator=(F &&f) noexcept;
+ template ::value>
+ * = nullptr>
+ TL_FUNCTION_REF_11_CONSTEXPR function_ref &operator=(F &&f) noexcept {
+ obj_ = reinterpret_cast(std::addressof(f));
+ callback_ = [](void *obj, Args... args) {
+ return detail::fnref::invoke(
+ *reinterpret_cast::type>(obj),
+ std::forward(args)...);
+ };
+
+ return *this;
+ }
+
+ /// Swaps the referred callables of `*this` and `rhs`.
+ constexpr void swap(function_ref &rhs) noexcept {
+ std::swap(obj_, rhs.obj_);
+ std::swap(callback_, rhs.callback_);
+ }
+
+ /// Call the stored callable with the given arguments.
+ R operator()(Args... args) const {
+ return callback_(obj_, std::forward(args)...);
+ }
+
+private:
+ void *obj_ = nullptr;
+ R (*callback_)(void *, Args...) = nullptr;
+};
+
+/// Swaps the referred callables of `lhs` and `rhs`.
+template
+constexpr void swap(function_ref &lhs,
+ function_ref &rhs) noexcept {
+ lhs.swap(rhs);
+}
+
+#if __cplusplus >= 201703L
+template
+function_ref(R (*)(Args...))->function_ref;
+
+// TODO, will require some kind of callable traits
+// template
+// function_ref(F) -> function_ref* deduced if possible */>;
+#endif
+} // namespace tl
+
+#endif
diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake
index 6ef3041fe..fd23477ee 100644
--- a/CMake/Dependencies.cmake
+++ b/CMake/Dependencies.cmake
@@ -185,6 +185,8 @@ endif()
add_subdirectory(3rdParty/libmpq)
+add_subdirectory(3rdParty/tl)
+
add_subdirectory(3rdParty/hoehrmann_utf8)
add_subdirectory(3rdParty/PKWare)
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 6fe558764..e07a6dedb 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -248,6 +248,7 @@ target_link_libraries(libdevilutionx PUBLIC
libmpq
libsmackerdec
simpleini::simpleini
+ tl
hoehrmann_utf8
)
diff --git a/Source/debug.cpp b/Source/debug.cpp
index 72f68007e..f6709bd0e 100644
--- a/Source/debug.cpp
+++ b/Source/debug.cpp
@@ -723,7 +723,7 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter)
int spawnedMonster = 0;
- auto ret = Crawl(0, MaxCrawlRadius, [&](auto displacement) -> std::optional {
+ auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional {
Point pos = myPlayer.position.tile + displacement;
if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0)
return {};
@@ -809,7 +809,7 @@ std::string DebugCmdSpawnMonster(const string_view parameter)
int spawnedMonster = 0;
- auto ret = Crawl(0, MaxCrawlRadius, [&](auto displacement) -> std::optional {
+ auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional {
Point pos = myPlayer.position.tile + displacement;
if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0)
return {};
diff --git a/Source/engine/path.cpp b/Source/engine/path.cpp
index 3505cc593..afb477763 100644
--- a/Source/engine/path.cpp
+++ b/Source/engine/path.cpp
@@ -436,7 +436,7 @@ bool path_solid_pieces(Point startPosition, Point destinationPosition)
std::optional FindClosestValidPosition(const std::function &posOk, Point startingPosition, unsigned int minimumRadius, unsigned int maximumRadius)
{
- return Crawl(minimumRadius, maximumRadius, [&](auto displacement) -> std::optional {
+ return Crawl(minimumRadius, maximumRadius, [&](Displacement displacement) -> std::optional {
Point candidatePosition = startingPosition + displacement;
if (posOk(candidatePosition))
return candidatePosition;
diff --git a/Source/lighting.cpp b/Source/lighting.cpp
index cae9512de..9deadb79c 100644
--- a/Source/lighting.cpp
+++ b/Source/lighting.cpp
@@ -128,8 +128,68 @@ void DoUnLight(int nXPos, int nYPos, int nRadius)
}
}
+bool CrawlFlipsX(Displacement mirrored, tl::function_ref function)
+{
+ for (const Displacement displacement : { mirrored.flipX(), mirrored }) {
+ if (!function(displacement))
+ return false;
+ }
+ return true;
+}
+
+bool CrawlFlipsY(Displacement mirrored, tl::function_ref function)
+{
+ for (const Displacement displacement : { mirrored, mirrored.flipY() }) {
+ if (!function(displacement))
+ return false;
+ }
+ return true;
+}
+
+bool CrawlFlipsXY(Displacement mirrored, tl::function_ref function)
+{
+ for (const Displacement displacement : { mirrored.flipX(), mirrored, mirrored.flipXY(), mirrored.flipY() }) {
+ if (!function(displacement))
+ return false;
+ }
+ return true;
+}
+
} // namespace
+bool DoCrawl(unsigned radius, tl::function_ref function)
+{
+ if (radius == 0)
+ return function(Displacement { 0, 0 });
+
+ if (!CrawlFlipsY({ 0, static_cast(radius) }, function))
+ return false;
+ for (unsigned i = 1; i < radius; i++) {
+ if (!CrawlFlipsXY({ static_cast(i), static_cast(radius) }, function))
+ return false;
+ }
+ if (radius > 1) {
+ if (!CrawlFlipsXY({ static_cast(radius) - 1, static_cast(radius) - 1 }, function))
+ return false;
+ }
+ if (!CrawlFlipsX({ static_cast(radius), 0 }, function))
+ return false;
+ for (unsigned i = 1; i < radius; i++) {
+ if (!CrawlFlipsXY({ static_cast(radius), static_cast(i) }, function))
+ return false;
+ }
+ return true;
+}
+
+bool DoCrawl(unsigned minRadius, unsigned maxRadius, tl::function_ref function)
+{
+ for (unsigned i = minRadius; i <= maxRadius; i++) {
+ if (!DoCrawl(i, function))
+ return false;
+ }
+ return true;
+}
+
void DoLighting(Point position, int nRadius, int lnum)
{
int xoff = 0;
diff --git a/Source/lighting.h b/Source/lighting.h
index 8a6d3f76a..42c72224d 100644
--- a/Source/lighting.h
+++ b/Source/lighting.h
@@ -8,12 +8,15 @@
#include
#include
+#include
+
#include "automap.h"
#include "engine.h"
#include "engine/point.hpp"
#include "miniwin/miniwin.h"
#include "utils/attributes.h"
#include "utils/stdcompat/invoke_result_t.hpp"
+#include "utils/stdcompat/optional.hpp"
namespace devilution {
@@ -75,42 +78,6 @@ void ChangeVisionXY(int id, Point position);
void ProcessVisionList();
void lighting_color_cycling();
-template
-auto CrawlFlipsX(Displacement mirrored, F function) -> invoke_result_t
-{
- const Displacement Flips[] = { mirrored.flipX(), mirrored };
- for (auto displacement : Flips) {
- auto ret = function(displacement);
- if (ret)
- return ret;
- }
- return {};
-}
-
-template
-auto CrawlFlipsY(Displacement mirrored, F function) -> invoke_result_t
-{
- const Displacement Flips[] = { mirrored, mirrored.flipY() };
- for (auto displacement : Flips) {
- auto ret = function(displacement);
- if (ret)
- return ret;
- }
- return {};
-}
-
-template
-auto CrawlFlipsXY(Displacement mirrored, F function) -> invoke_result_t
-{
- const Displacement Flips[] = { mirrored.flipX(), mirrored, mirrored.flipXY(), mirrored.flipY() };
- for (auto displacement : Flips) {
- auto ret = function(displacement);
- if (ret)
- return ret;
- }
- return {};
-}
-
constexpr int MaxCrawlRadius = 18;
/**
@@ -137,45 +104,29 @@ constexpr int MaxCrawlRadius = 18;
* +-------> x
*/
+bool DoCrawl(unsigned radius, tl::function_ref function);
+bool DoCrawl(unsigned minRadius, unsigned maxRadius, tl::function_ref function);
+
template
auto Crawl(unsigned radius, F function) -> invoke_result_t
{
- if (radius == 0)
- return function(Displacement { 0, 0 });
-
- auto ret = CrawlFlipsY({ 0, static_cast(radius) }, function);
- if (ret)
- return ret;
- for (unsigned i = 1; i < radius; i++) {
- ret = CrawlFlipsXY({ static_cast(i), static_cast(radius) }, function);
- if (ret)
- return ret;
- }
- if (radius > 1) {
- ret = CrawlFlipsXY({ static_cast(radius) - 1, static_cast(radius) - 1 }, function);
- if (ret)
- return ret;
- }
- ret = CrawlFlipsX({ static_cast(radius), 0 }, function);
- if (ret)
- return ret;
- for (unsigned i = 1; i < radius; i++) {
- ret = CrawlFlipsXY({ static_cast(radius), static_cast(i) }, function);
- if (ret)
- return ret;
- }
- return {};
+ invoke_result_t result;
+ DoCrawl(radius, [&result, &function](Displacement displacement) -> bool {
+ result = function(displacement);
+ return !result;
+ });
+ return result;
}
template
auto Crawl(unsigned minRadius, unsigned maxRadius, F function) -> invoke_result_t
{
- for (unsigned i = minRadius; i <= maxRadius; i++) {
- auto displacement = Crawl(i, function);
- if (displacement)
- return displacement;
- }
- return {};
+ invoke_result_t result;
+ DoCrawl(minRadius, maxRadius, [&result, &function](Displacement displacement) -> bool {
+ result = function(displacement);
+ return !result;
+ });
+ return result;
}
/* rdata */
diff --git a/Source/missiles.cpp b/Source/missiles.cpp
index db34138cd..99d89e098 100644
--- a/Source/missiles.cpp
+++ b/Source/missiles.cpp
@@ -1218,7 +1218,7 @@ void AddJester(Missile &missile, const AddMissileParameter ¶meter)
void AddStealPotions(Missile &missile, const AddMissileParameter & /*parameter*/)
{
- Crawl(0, 2, [&](auto displacement) {
+ Crawl(0, 2, [&](Displacement displacement) {
Point target = missile.position.start + displacement;
if (!InDungeonBounds(target))
return false;
@@ -3020,7 +3020,7 @@ void MI_FireRing(Missile &missile)
if (missile.limitReached)
return;
- Crawl(3, [&](auto displacement) {
+ Crawl(3, [&](Displacement displacement) {
Point target = Point { missile.var1, missile.var2 } + displacement;
if (!InDungeonBounds(target))
return false;
@@ -3376,7 +3376,7 @@ void MI_Chain(Missile &missile)
Direction dir = GetDirection(position, dst);
AddMissile(position, dst, dir, MIS_LIGHTCTRL, TARGET_MONSTERS, id, 1, missile._mispllvl);
int rad = std::min(missile._mispllvl + 3, MaxCrawlRadius);
- Crawl(1, rad, [&](auto displacement) {
+ Crawl(1, rad, [&](Displacement displacement) {
Point target = position + displacement;
if (InDungeonBounds(target) && dMonster[target.x][target.y] > 0) {
dir = GetDirection(position, target);
diff --git a/test/lighting_test.cpp b/test/lighting_test.cpp
index b0204655a..a3b5ddaae 100644
--- a/test/lighting_test.cpp
+++ b/test/lighting_test.cpp
@@ -13,7 +13,7 @@ TEST(Lighting, CrawlTables)
int x = 20;
int y = 20;
- Crawl(0, MaxCrawlRadius, [&](auto displacement) {
+ Crawl(0, MaxCrawlRadius, [&](Displacement displacement) {
int dx = x + displacement.deltaX;
int dy = y + displacement.deltaY;
EXPECT_EQ(added[dx][dy], false) << "displacement " << displacement.deltaX << ":" << displacement.deltaY << " added twice";