diff --git a/Source/debug.cpp b/Source/debug.cpp index ec37b46c9..72f68007e 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -723,28 +723,30 @@ std::string DebugCmdSpawnUniqueMonster(const string_view parameter) int spawnedMonster = 0; - for (const auto &table : CrawlTable) { - for (auto displacement : table) { - Point pos = myPlayer.position.tile + displacement; - if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) - continue; - if (!IsTileWalkable(pos)) - continue; - - Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); - if (monster == nullptr) - return fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); - PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[uniqueIndex]); - ActiveMonsterCount--; - monster->corpseId = 1; - spawnedMonster += 1; - - if (spawnedMonster >= count) - return "Let the fighting begin!"; - } - } - - return fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); + auto ret = Crawl(0, MaxCrawlRadius, [&](auto displacement) -> std::optional { + Point pos = myPlayer.position.tile + displacement; + if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) + return {}; + if (!IsTileWalkable(pos)) + return {}; + + Monster *monster = AddMonster(pos, myPlayer._pdir, id, true); + if (monster == nullptr) + return fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); + PrepareUniqueMonst(*monster, uniqueIndex, 0, 0, UniqueMonstersData[uniqueIndex]); + ActiveMonsterCount--; + monster->corpseId = 1; + spawnedMonster += 1; + + if (spawnedMonster >= count) + return "Let the fighting begin!"; + + return {}; + }); + + if (!ret) + ret = fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); + return *ret; } std::string DebugCmdSpawnMonster(const string_view parameter) @@ -807,24 +809,26 @@ std::string DebugCmdSpawnMonster(const string_view parameter) int spawnedMonster = 0; - for (const auto &table : CrawlTable) { - for (auto displacement : table) { - Point pos = myPlayer.position.tile + displacement; - if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) - continue; - if (!IsTileWalkable(pos)) - continue; + auto ret = Crawl(0, MaxCrawlRadius, [&](auto displacement) -> std::optional { + Point pos = myPlayer.position.tile + displacement; + if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0) + return {}; + if (!IsTileWalkable(pos)) + return {}; - if (AddMonster(pos, myPlayer._pdir, id, true) == nullptr) - return fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); - spawnedMonster += 1; + if (AddMonster(pos, myPlayer._pdir, id, true) == nullptr) + return fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); + spawnedMonster += 1; - if (spawnedMonster >= count) - return "Let the fighting begin!"; - } - } + if (spawnedMonster >= count) + return "Let the fighting begin!"; + + return {}; + }); - return fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); + if (!ret) + ret = fmt::format("I could only summon {} Monsters. The rest strike for shorter working hours.", spawnedMonster); + return *ret; } std::string DebugCmdShowTileData(const string_view parameter) diff --git a/Source/lighting.cpp b/Source/lighting.cpp index 6115cf8f3..cae9512de 100644 --- a/Source/lighting.cpp +++ b/Source/lighting.cpp @@ -24,71 +24,6 @@ std::array LightTables; bool DisableLighting; bool UpdateLighting; -namespace { - -std::vector> CrawlFlips(const DisplacementOf *begin, const DisplacementOf *end) -{ - std::vector> ret; - for (; begin != end; ++begin) { - DisplacementOf displacement = *begin; - if (displacement.deltaX != 0) - ret.emplace_back(displacement.flipX()); - ret.emplace_back(displacement); - if (displacement.deltaX != 0 && displacement.deltaY != 0) - ret.emplace_back(displacement.flipXY()); - if (displacement.deltaY != 0) - ret.emplace_back(displacement.flipY()); - } - return ret; -} - -std::vector> CrawlRow(int8_t row) -{ - StaticVector, (CrawlTableSize - 1) * 2 + 1> ret; - for (int8_t i = 0; i < row; i++) - ret.emplace_back(i, row); - if (row > 1) - ret.emplace_back(static_cast(row - 1), static_cast(row - 1)); - for (int8_t i = 0; i < row; i++) - ret.emplace_back(row, i); - return CrawlFlips(ret.begin(), ret.end()); -} - -} // namespace - -/** - * CrawlTable specifies X- and Y-coordinate deltas from a missile target coordinate. - * - * n=4 - * - * y - * ^ - * | 1 - * | 3#4 - * | 2 - * +-----> x - * - * n=16 - * - * y - * ^ - * | 314 - * | B7 8C - * | F # G - * | D9 AE - * | 526 - * +-------> x - */ - -const std::array>, CrawlTableSize> CrawlTable = []() { - std::array>, CrawlTableSize> ret; - ret[0].emplace_back(0, 0); - for (size_t row = 1; row < CrawlTableSize; ++row) { - ret[row] = CrawlRow(static_cast(row)); - } - return ret; -}(); - /* * X- Y-coordinate offsets of lighting visions. * The last entry-pair is only for alignment. diff --git a/Source/lighting.h b/Source/lighting.h index 23a92f834..9f9a52a57 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -13,6 +13,7 @@ #include "engine/point.hpp" #include "miniwin/miniwin.h" #include "utils/attributes.h" +#include "utils/stdcompat/invoke_result_t.hpp" namespace devilution { @@ -74,10 +75,113 @@ 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; + +/** + * CrawlTable specifies X- and Y-coordinate deltas from a missile target coordinate. + * + * n=4 + * + * y + * ^ + * | 1 + * | 3#4 + * | 2 + * +-----> x + * + * n=16 + * + * y + * ^ + * | 314 + * | B7 8C + * | F # G + * | D9 AE + * | 526 + * +-------> x + */ + +template +auto Crawl(unsigned radius, F function) -> invoke_result_t +{ + assert(radius <= MaxCrawlRadius); + + 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 {}; +} + +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 {}; +} + /* rdata */ -constexpr size_t CrawlTableSize = 19; -extern DVL_API_FOR_TEST const std::array>, CrawlTableSize> CrawlTable; extern const uint8_t VisionCrawlTable[23][30]; } // namespace devilution diff --git a/Source/missiles.cpp b/Source/missiles.cpp index af8915107..4411b9a4c 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -1218,69 +1218,69 @@ void AddJester(Missile &missile, const AddMissileParameter ¶meter) void AddStealPotions(Missile &missile, const AddMissileParameter & /*parameter*/) { - for (int i = 0; i < 3; i++) { - for (auto displacement : CrawlTable[i]) { - Point target = missile.position.start + displacement; - if (!InDungeonBounds(target)) - continue; - int8_t pnum = dPlayer[target.x][target.y]; - if (pnum == 0) - continue; - Player &player = Players[abs(pnum) - 1]; - - bool hasPlayedSFX = false; - for (int si = 0; si < MaxBeltItems; si++) { - int ii = -1; - if (player.SpdList[si]._itype == ItemType::Misc) { - if (GenerateRnd(2) == 0) - continue; - switch (player.SpdList[si]._iMiscId) { - case IMISC_FULLHEAL: - ii = ItemMiscIdIdx(IMISC_HEAL); - break; - case IMISC_HEAL: - case IMISC_MANA: - player.RemoveSpdBarItem(si); - break; - case IMISC_FULLMANA: + Crawl(0, 2, [&](auto displacement) { + Point target = missile.position.start + displacement; + if (!InDungeonBounds(target)) + return false; + int8_t pnum = dPlayer[target.x][target.y]; + if (pnum == 0) + return false; + Player &player = Players[abs(pnum) - 1]; + + bool hasPlayedSFX = false; + for (int si = 0; si < MaxBeltItems; si++) { + int ii = -1; + if (player.SpdList[si]._itype == ItemType::Misc) { + if (GenerateRnd(2) == 0) + continue; + switch (player.SpdList[si]._iMiscId) { + case IMISC_FULLHEAL: + ii = ItemMiscIdIdx(IMISC_HEAL); + break; + case IMISC_HEAL: + case IMISC_MANA: + player.RemoveSpdBarItem(si); + break; + case IMISC_FULLMANA: + ii = ItemMiscIdIdx(IMISC_MANA); + break; + case IMISC_REJUV: + if (GenerateRnd(2) != 0) { ii = ItemMiscIdIdx(IMISC_MANA); + } else { + ii = ItemMiscIdIdx(IMISC_HEAL); + } + break; + case IMISC_FULLREJUV: + switch (GenerateRnd(3)) { + case 0: + ii = ItemMiscIdIdx(IMISC_FULLMANA); break; - case IMISC_REJUV: - if (GenerateRnd(2) != 0) { - ii = ItemMiscIdIdx(IMISC_MANA); - } else { - ii = ItemMiscIdIdx(IMISC_HEAL); - } - break; - case IMISC_FULLREJUV: - switch (GenerateRnd(3)) { - case 0: - ii = ItemMiscIdIdx(IMISC_FULLMANA); - break; - case 1: - ii = ItemMiscIdIdx(IMISC_FULLHEAL); - break; - default: - ii = ItemMiscIdIdx(IMISC_REJUV); - break; - } + case 1: + ii = ItemMiscIdIdx(IMISC_FULLHEAL); break; default: - continue; + ii = ItemMiscIdIdx(IMISC_REJUV); + break; } + break; + default: + continue; } - if (ii != -1) { - InitializeItem(player.SpdList[si], ii); - player.SpdList[si]._iStatFlag = true; - } - if (!hasPlayedSFX) { - PlaySfxLoc(IS_POPPOP2, target); - hasPlayedSFX = true; - } } - force_redraw = 255; + if (ii != -1) { + InitializeItem(player.SpdList[si], ii); + player.SpdList[si]._iStatFlag = true; + } + if (!hasPlayedSFX) { + PlaySfxLoc(IS_POPPOP2, target); + hasPlayedSFX = true; + } } - } + force_redraw = 255; + + return false; + }); missile._miDelFlag = true; } @@ -3020,24 +3020,25 @@ void MI_FireRing(Missile &missile) if (missile.limitReached) return; - for (auto displacement : CrawlTable[3]) { + Crawl(3, [&](auto displacement) { Point target = Point { missile.var1, missile.var2 } + displacement; if (!InDungeonBounds(target)) - continue; + return false; int dp = dPiece[target.x][target.y]; if (TileHasAny(dp, TileProperties::Solid)) - continue; + return false; if (IsObjectAtPosition(target)) - continue; + return false; if (!LineClearMissile(missile.position.tile, target)) - continue; + return false; if (TileHasAny(dp, TileProperties::BlockMissile)) { missile.limitReached = true; - return; + return true; } AddMissile(target, target, Direction::South, MIS_FIREWALL, TARGET_BOTH, src, dmg, missile._mispllvl); - } + return false; + }); } void MI_Search(Missile &missile) @@ -3374,16 +3375,15 @@ void MI_Chain(Missile &missile) Point dst { missile.var1, missile.var2 }; Direction dir = GetDirection(position, dst); AddMissile(position, dst, dir, MIS_LIGHTCTRL, TARGET_MONSTERS, id, 1, missile._mispllvl); - int rad = std::min(missile._mispllvl + 3, CrawlTable.size() - 1); - for (int i = 1; i < rad; i++) { - for (auto displacement : CrawlTable[i]) { - Point target = position + displacement; - if (InDungeonBounds(target) && dMonster[target.x][target.y] > 0) { - dir = GetDirection(position, target); - AddMissile(position, target, dir, MIS_LIGHTCTRL, TARGET_MONSTERS, id, 1, missile._mispllvl); - } + int rad = std::min(missile._mispllvl + 3, MaxCrawlRadius); + Crawl(1, rad, [&](auto displacement) { + Point target = position + displacement; + if (InDungeonBounds(target) && dMonster[target.x][target.y] > 0) { + dir = GetDirection(position, target); + AddMissile(position, target, dir, MIS_LIGHTCTRL, TARGET_MONSTERS, id, 1, missile._mispllvl); } - } + return false; + }); missile._mirange--; if (missile._mirange == 0) missile._miDelFlag = true; diff --git a/Source/utils/stdcompat/invoke_result_t.hpp b/Source/utils/stdcompat/invoke_result_t.hpp new file mode 100644 index 000000000..06480a688 --- /dev/null +++ b/Source/utils/stdcompat/invoke_result_t.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace devilution { + +#if defined(__cplusplus) && __cplusplus >= 201703L +using std::invoke_result_t; +#else +template +using invoke_result_t = typename std::result_of::type; +#endif + +} // namespace devilution diff --git a/test/lighting_test.cpp b/test/lighting_test.cpp index 645a0c909..b0204655a 100644 --- a/test/lighting_test.cpp +++ b/test/lighting_test.cpp @@ -10,22 +10,22 @@ TEST(Lighting, CrawlTables) bool added[40][40]; memset(added, 0, sizeof(added)); - for (size_t j = 0; j < CrawlTable.size(); j++) { - int x = 20; - int y = 20; - for (unsigned i = 0; i < CrawlTable[j].size(); i++) { - int dx = x + CrawlTable[j][i].deltaX; - int dy = y + CrawlTable[j][i].deltaY; - EXPECT_EQ(added[dx][dy], false) << "location " << i << ":" << j << " added twice"; - added[dx][dy] = true; - } - } + int x = 20; + int y = 20; + + Crawl(0, MaxCrawlRadius, [&](auto displacement) { + int dx = x + displacement.deltaX; + int dy = y + displacement.deltaY; + EXPECT_EQ(added[dx][dy], false) << "displacement " << displacement.deltaX << ":" << displacement.deltaY << " added twice"; + added[dx][dy] = true; + return false; + }); - for (int i = -18; i <= 18; i++) { - for (int j = -18; j <= 18; j++) { + for (int i = -MaxCrawlRadius; i <= MaxCrawlRadius; i++) { + for (int j = -MaxCrawlRadius; j <= MaxCrawlRadius; j++) { if (added[i + 20][j + 20]) continue; - if ((i == -18 && j == -18) || (i == -18 && j == 18) || (i == 18 && j == -18) || (i == 18 && j == 18)) + if (abs(i) == MaxCrawlRadius && abs(j) == MaxCrawlRadius) continue; // Limit of the crawl table rage EXPECT_EQ(false, true) << "while checking location " << i << ":" << j; }