Browse Source

update auto-walk in dungeons to detect closed doors

pull/8474/head
hidwood 2 months ago
parent
commit
e31ece4f2a
  1. 648
      Source/diablo.cpp
  2. 2
      Source/objects.cpp
  3. 23
      test/tile_properties_test.cpp

648
Source/diablo.cpp

@ -1976,16 +1976,16 @@ void UpdateAttackableMonsterAnnouncements()
SpeakText(name, /*force=*/true); SpeakText(name, /*force=*/true);
} }
// Door state values are defined in `Source/objects.cpp` (DOOR_CLOSED=0, DOOR_OPEN=1, DOOR_BLOCKED=2).
constexpr int DoorClosed = 0;
constexpr int DoorOpen = 1;
constexpr int DoorBlocked = 2;
[[nodiscard]] StringOrView DoorLabelForSpeech(const Object &door) [[nodiscard]] StringOrView DoorLabelForSpeech(const Object &door)
{ {
if (!door.isDoor()) if (!door.isDoor())
return door.name(); return door.name();
// Door state values are defined in `Source/objects.cpp` (DOOR_CLOSED=0, DOOR_OPEN=1, DOOR_BLOCKED=2).
constexpr int DoorClosed = 0;
constexpr int DoorOpen = 1;
constexpr int DoorBlocked = 2;
// Catacombs doors are grates, so differentiate them for the screen reader / tracker. // Catacombs doors are grates, so differentiate them for the screen reader / tracker.
if (IsAnyOf(door._otype, _object_id::OBJ_L2LDOOR, _object_id::OBJ_L2RDOOR)) { if (IsAnyOf(door._otype, _object_id::OBJ_L2LDOOR, _object_id::OBJ_L2RDOOR)) {
if (door._oVar4 == DoorOpen) if (door._oVar4 == DoorOpen)
@ -2101,10 +2101,10 @@ void GameLogic()
UpdateAutoWalkTracker(); UpdateAutoWalkTracker();
UpdateLowDurabilityWarnings(); UpdateLowDurabilityWarnings();
} }
if (leveltype != DTYPE_TOWN) { if (leveltype != DTYPE_TOWN) {
gGameLogicStep = GameLogicStep::ProcessMonsters; gGameLogicStep = GameLogicStep::ProcessMonsters;
#ifdef _DEBUG #ifdef _DEBUG
if (!DebugInvisible) if (!DebugInvisible)
#endif #endif
ProcessMonsters(); ProcessMonsters();
gGameLogicStep = GameLogicStep::ProcessObjects; gGameLogicStep = GameLogicStep::ProcessObjects;
@ -2115,19 +2115,19 @@ void GameLogic()
ProcessItems(); ProcessItems();
ProcessLightList(); ProcessLightList();
ProcessVisionList(); ProcessVisionList();
UpdateBossHealthAnnouncements(); UpdateBossHealthAnnouncements();
UpdateProximityAudioCues(); UpdateProximityAudioCues();
UpdateAttackableMonsterAnnouncements(); UpdateAttackableMonsterAnnouncements();
UpdateInteractableDoorAnnouncements(); UpdateInteractableDoorAnnouncements();
} else { } else {
gGameLogicStep = GameLogicStep::ProcessTowners; gGameLogicStep = GameLogicStep::ProcessTowners;
ProcessTowners(); ProcessTowners();
gGameLogicStep = GameLogicStep::ProcessItemsTown; gGameLogicStep = GameLogicStep::ProcessItemsTown;
ProcessItems(); ProcessItems();
gGameLogicStep = GameLogicStep::ProcessMissilesTown; gGameLogicStep = GameLogicStep::ProcessMissilesTown;
ProcessMissiles(); ProcessMissiles();
UpdateProximityAudioCues(); UpdateProximityAudioCues();
} }
UpdatePlayerLowHpWarningSound(); UpdatePlayerLowHpWarningSound();
@ -2227,16 +2227,16 @@ std::vector<int> TownNpcOrder;
int SelectedTownNpc = -1; int SelectedTownNpc = -1;
int AutoWalkTownNpcTarget = -1; int AutoWalkTownNpcTarget = -1;
enum class TrackerTargetCategory : uint8_t { enum class TrackerTargetCategory : uint8_t {
Items, Items,
Chests, Chests,
Doors, Doors,
Shrines, Shrines,
Objects, Objects,
Breakables, Breakables,
Monsters, Monsters,
DeadBodies, DeadBodies,
}; };
TrackerTargetCategory SelectedTrackerTargetCategory = TrackerTargetCategory::Items; TrackerTargetCategory SelectedTrackerTargetCategory = TrackerTargetCategory::Items;
TrackerTargetCategory AutoWalkTrackerTargetCategory = TrackerTargetCategory::Items; ///< Category of the active auto-walk target. TrackerTargetCategory AutoWalkTrackerTargetCategory = TrackerTargetCategory::Items; ///< Category of the active auto-walk target.
@ -2522,14 +2522,14 @@ namespace {
constexpr int TrackerInteractDistanceTiles = 1; constexpr int TrackerInteractDistanceTiles = 1;
constexpr int TrackerCycleDistanceTiles = 12; constexpr int TrackerCycleDistanceTiles = 12;
int LockedTrackerItemId = -1; int LockedTrackerItemId = -1;
int LockedTrackerChestId = -1; int LockedTrackerChestId = -1;
int LockedTrackerDoorId = -1; int LockedTrackerDoorId = -1;
int LockedTrackerShrineId = -1; int LockedTrackerShrineId = -1;
int LockedTrackerObjectId = -1; int LockedTrackerObjectId = -1;
int LockedTrackerBreakableId = -1; int LockedTrackerBreakableId = -1;
int LockedTrackerMonsterId = -1; int LockedTrackerMonsterId = -1;
int LockedTrackerDeadBodyId = -1; int LockedTrackerDeadBodyId = -1;
struct TrackerLevelKey { struct TrackerLevelKey {
dungeon_type levelType; dungeon_type levelType;
@ -2540,17 +2540,17 @@ struct TrackerLevelKey {
std::optional<TrackerLevelKey> LockedTrackerLevelKey; std::optional<TrackerLevelKey> LockedTrackerLevelKey;
void ClearTrackerLocks() void ClearTrackerLocks()
{ {
LockedTrackerItemId = -1; LockedTrackerItemId = -1;
LockedTrackerChestId = -1; LockedTrackerChestId = -1;
LockedTrackerDoorId = -1; LockedTrackerDoorId = -1;
LockedTrackerShrineId = -1; LockedTrackerShrineId = -1;
LockedTrackerObjectId = -1; LockedTrackerObjectId = -1;
LockedTrackerBreakableId = -1; LockedTrackerBreakableId = -1;
LockedTrackerMonsterId = -1; LockedTrackerMonsterId = -1;
LockedTrackerDeadBodyId = -1; LockedTrackerDeadBodyId = -1;
} }
void EnsureTrackerLocksMatchCurrentLevel() void EnsureTrackerLocksMatchCurrentLevel()
{ {
@ -2583,13 +2583,13 @@ int &LockedTrackerTargetId(TrackerTargetCategory category)
return LockedTrackerObjectId; return LockedTrackerObjectId;
case TrackerTargetCategory::Breakables: case TrackerTargetCategory::Breakables:
return LockedTrackerBreakableId; return LockedTrackerBreakableId;
case TrackerTargetCategory::Monsters: case TrackerTargetCategory::Monsters:
return LockedTrackerMonsterId; return LockedTrackerMonsterId;
case TrackerTargetCategory::DeadBodies: case TrackerTargetCategory::DeadBodies:
return LockedTrackerDeadBodyId; return LockedTrackerDeadBodyId;
} }
app_fatal("Invalid TrackerTargetCategory"); app_fatal("Invalid TrackerTargetCategory");
} }
std::string_view TrackerTargetCategoryLabel(TrackerTargetCategory category) std::string_view TrackerTargetCategoryLabel(TrackerTargetCategory category)
{ {
@ -2606,14 +2606,14 @@ std::string_view TrackerTargetCategoryLabel(TrackerTargetCategory category)
return _("objects"); return _("objects");
case TrackerTargetCategory::Breakables: case TrackerTargetCategory::Breakables:
return _("breakables"); return _("breakables");
case TrackerTargetCategory::Monsters: case TrackerTargetCategory::Monsters:
return _("monsters"); return _("monsters");
case TrackerTargetCategory::DeadBodies: case TrackerTargetCategory::DeadBodies:
return _("dead bodies"); return _("dead bodies");
default: default:
return _("items"); return _("items");
} }
} }
void SpeakTrackerTargetCategory() void SpeakTrackerTargetCategory()
{ {
@ -2632,14 +2632,14 @@ void CycleTrackerTargetKeyPressed()
const SDL_Keymod modState = SDL_GetModState(); const SDL_Keymod modState = SDL_GetModState();
const bool cyclePrevious = (modState & SDL_KMOD_SHIFT) != 0; const bool cyclePrevious = (modState & SDL_KMOD_SHIFT) != 0;
if (cyclePrevious) { if (cyclePrevious) {
switch (SelectedTrackerTargetCategory) { switch (SelectedTrackerTargetCategory) {
case TrackerTargetCategory::Items: case TrackerTargetCategory::Items:
SelectedTrackerTargetCategory = TrackerTargetCategory::DeadBodies; SelectedTrackerTargetCategory = TrackerTargetCategory::DeadBodies;
break; break;
case TrackerTargetCategory::Chests: case TrackerTargetCategory::Chests:
SelectedTrackerTargetCategory = TrackerTargetCategory::Items; SelectedTrackerTargetCategory = TrackerTargetCategory::Items;
break; break;
case TrackerTargetCategory::Doors: case TrackerTargetCategory::Doors:
SelectedTrackerTargetCategory = TrackerTargetCategory::Chests; SelectedTrackerTargetCategory = TrackerTargetCategory::Chests;
break; break;
@ -2652,17 +2652,17 @@ void CycleTrackerTargetKeyPressed()
case TrackerTargetCategory::Breakables: case TrackerTargetCategory::Breakables:
SelectedTrackerTargetCategory = TrackerTargetCategory::Objects; SelectedTrackerTargetCategory = TrackerTargetCategory::Objects;
break; break;
case TrackerTargetCategory::Monsters: case TrackerTargetCategory::Monsters:
SelectedTrackerTargetCategory = TrackerTargetCategory::Breakables; SelectedTrackerTargetCategory = TrackerTargetCategory::Breakables;
break; break;
case TrackerTargetCategory::DeadBodies: case TrackerTargetCategory::DeadBodies:
default: default:
SelectedTrackerTargetCategory = TrackerTargetCategory::Monsters; SelectedTrackerTargetCategory = TrackerTargetCategory::Monsters;
break; break;
} }
} else { } else {
switch (SelectedTrackerTargetCategory) { switch (SelectedTrackerTargetCategory) {
case TrackerTargetCategory::Items: case TrackerTargetCategory::Items:
SelectedTrackerTargetCategory = TrackerTargetCategory::Chests; SelectedTrackerTargetCategory = TrackerTargetCategory::Chests;
break; break;
case TrackerTargetCategory::Chests: case TrackerTargetCategory::Chests:
@ -2677,26 +2677,26 @@ void CycleTrackerTargetKeyPressed()
case TrackerTargetCategory::Objects: case TrackerTargetCategory::Objects:
SelectedTrackerTargetCategory = TrackerTargetCategory::Breakables; SelectedTrackerTargetCategory = TrackerTargetCategory::Breakables;
break; break;
case TrackerTargetCategory::Breakables: case TrackerTargetCategory::Breakables:
SelectedTrackerTargetCategory = TrackerTargetCategory::Monsters; SelectedTrackerTargetCategory = TrackerTargetCategory::Monsters;
break; break;
case TrackerTargetCategory::Monsters: case TrackerTargetCategory::Monsters:
SelectedTrackerTargetCategory = TrackerTargetCategory::DeadBodies; SelectedTrackerTargetCategory = TrackerTargetCategory::DeadBodies;
break; break;
case TrackerTargetCategory::DeadBodies: case TrackerTargetCategory::DeadBodies:
default: default:
SelectedTrackerTargetCategory = TrackerTargetCategory::Items; SelectedTrackerTargetCategory = TrackerTargetCategory::Items;
break; break;
} }
} }
SpeakTrackerTargetCategory(); SpeakTrackerTargetCategory();
} }
std::optional<int> FindNearestGroundItemId(Point playerPosition) std::optional<int> FindNearestGroundItemId(Point playerPosition)
{ {
std::optional<int> bestId; std::optional<int> bestId;
int bestDistance = 0; int bestDistance = 0;
for (int y = 0; y < MAXDUNY; ++y) { for (int y = 0; y < MAXDUNY; ++y) {
for (int x = 0; x < MAXDUNX; ++x) { for (int x = 0; x < MAXDUNX; ++x) {
@ -2715,47 +2715,47 @@ std::optional<int> FindNearestGroundItemId(Point playerPosition)
} }
} }
} }
return bestId; return bestId;
} }
[[nodiscard]] constexpr int CorpseTrackerIdForPosition(Point position) [[nodiscard]] constexpr int CorpseTrackerIdForPosition(Point position)
{ {
return position.x + position.y * MAXDUNX; return position.x + position.y * MAXDUNX;
} }
[[nodiscard]] constexpr Point CorpsePositionForTrackerId(int corpseId) [[nodiscard]] constexpr Point CorpsePositionForTrackerId(int corpseId)
{ {
return { corpseId % MAXDUNX, corpseId / MAXDUNX }; return { corpseId % MAXDUNX, corpseId / MAXDUNX };
} }
std::optional<int> FindNearestCorpseId(Point playerPosition) std::optional<int> FindNearestCorpseId(Point playerPosition)
{ {
std::optional<int> bestId; std::optional<int> bestId;
int bestDistance = 0; int bestDistance = 0;
for (int y = 0; y < MAXDUNY; ++y) { for (int y = 0; y < MAXDUNY; ++y) {
for (int x = 0; x < MAXDUNX; ++x) { for (int x = 0; x < MAXDUNX; ++x) {
if (dCorpse[x][y] == 0) if (dCorpse[x][y] == 0)
continue; continue;
const Point position { x, y }; const Point position { x, y };
const int distance = playerPosition.WalkingDistance(position); const int distance = playerPosition.WalkingDistance(position);
if (!bestId || distance < bestDistance) { if (!bestId || distance < bestDistance) {
bestId = CorpseTrackerIdForPosition(position); bestId = CorpseTrackerIdForPosition(position);
bestDistance = distance; bestDistance = distance;
} }
} }
} }
return bestId; return bestId;
} }
struct TrackerCandidate { struct TrackerCandidate {
int id; int id;
int distance; int distance;
StringOrView name; StringOrView name;
}; };
[[nodiscard]] bool IsBetterTrackerCandidate(const TrackerCandidate &a, const TrackerCandidate &b) [[nodiscard]] bool IsBetterTrackerCandidate(const TrackerCandidate &a, const TrackerCandidate &b)
{ {
@ -2801,45 +2801,45 @@ struct TrackerCandidate {
} }
} }
std::sort(result.begin(), result.end(), [](const TrackerCandidate &a, const TrackerCandidate &b) { return IsBetterTrackerCandidate(a, b); }); std::sort(result.begin(), result.end(), [](const TrackerCandidate &a, const TrackerCandidate &b) { return IsBetterTrackerCandidate(a, b); });
return result; return result;
} }
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyCorpseTrackerCandidates(Point playerPosition, int maxDistance) [[nodiscard]] std::vector<TrackerCandidate> CollectNearbyCorpseTrackerCandidates(Point playerPosition, int maxDistance)
{ {
std::vector<TrackerCandidate> result; std::vector<TrackerCandidate> result;
const int minX = std::max(0, playerPosition.x - maxDistance); const int minX = std::max(0, playerPosition.x - maxDistance);
const int minY = std::max(0, playerPosition.y - maxDistance); const int minY = std::max(0, playerPosition.y - maxDistance);
const int maxX = std::min(MAXDUNX - 1, playerPosition.x + maxDistance); const int maxX = std::min(MAXDUNX - 1, playerPosition.x + maxDistance);
const int maxY = std::min(MAXDUNY - 1, playerPosition.y + maxDistance); const int maxY = std::min(MAXDUNY - 1, playerPosition.y + maxDistance);
for (int y = minY; y <= maxY; ++y) { for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) { for (int x = minX; x <= maxX; ++x) {
if (dCorpse[x][y] == 0) if (dCorpse[x][y] == 0)
continue; continue;
const Point position { x, y }; const Point position { x, y };
const int distance = playerPosition.WalkingDistance(position); const int distance = playerPosition.WalkingDistance(position);
if (distance > maxDistance) if (distance > maxDistance)
continue; continue;
result.push_back(TrackerCandidate { result.push_back(TrackerCandidate {
.id = CorpseTrackerIdForPosition(position), .id = CorpseTrackerIdForPosition(position),
.distance = distance, .distance = distance,
.name = _("Dead body"), .name = _("Dead body"),
}); });
} }
} }
std::sort(result.begin(), result.end(), [](const TrackerCandidate &a, const TrackerCandidate &b) { return IsBetterTrackerCandidate(a, b); }); std::sort(result.begin(), result.end(), [](const TrackerCandidate &a, const TrackerCandidate &b) { return IsBetterTrackerCandidate(a, b); });
return result; return result;
} }
[[nodiscard]] constexpr bool IsTrackedChestObject(const Object &object) [[nodiscard]] constexpr bool IsTrackedChestObject(const Object &object)
{ {
return object.canInteractWith() && (object.IsChest() || object._otype == _object_id::OBJ_SIGNCHEST); return object.canInteractWith() && (object.IsChest() || object._otype == _object_id::OBJ_SIGNCHEST);
} }
[[nodiscard]] constexpr bool IsTrackedDoorObject(const Object &object) [[nodiscard]] constexpr bool IsTrackedDoorObject(const Object &object)
{ {
@ -3085,32 +3085,32 @@ void DecorateTrackerTargetNameWithOrdinalIfNeeded(int targetId, StringOrView &ta
targetName = std::move(decorated); targetName = std::move(decorated);
} }
[[nodiscard]] bool IsGroundItemPresent(int itemId) [[nodiscard]] bool IsGroundItemPresent(int itemId)
{ {
if (itemId < 0 || itemId > MAXITEMS) if (itemId < 0 || itemId > MAXITEMS)
return false; return false;
for (uint8_t i = 0; i < ActiveItemCount; ++i) { for (uint8_t i = 0; i < ActiveItemCount; ++i) {
if (ActiveItems[i] == itemId) if (ActiveItems[i] == itemId)
return true; return true;
} }
return false; return false;
} }
[[nodiscard]] bool IsCorpsePresent(int corpseId) [[nodiscard]] bool IsCorpsePresent(int corpseId)
{ {
if (corpseId < 0 || corpseId >= MAXDUNX * MAXDUNY) if (corpseId < 0 || corpseId >= MAXDUNX * MAXDUNY)
return false; return false;
const Point position = CorpsePositionForTrackerId(corpseId); const Point position = CorpsePositionForTrackerId(corpseId);
return InDungeonBounds(position) && dCorpse[position.x][position.y] != 0; return InDungeonBounds(position) && dCorpse[position.x][position.y] != 0;
} }
std::optional<int> FindNearestUnopenedChestObjectId(Point playerPosition) std::optional<int> FindNearestUnopenedChestObjectId(Point playerPosition)
{ {
return FindNearestObjectId(playerPosition, IsTrackedChestObject); return FindNearestObjectId(playerPosition, IsTrackedChestObject);
} }
std::optional<int> FindNearestDoorObjectId(Point playerPosition) std::optional<int> FindNearestDoorObjectId(Point playerPosition)
{ {
@ -3353,7 +3353,7 @@ std::optional<DoorBlockInfo> FindFirstClosedDoorOnWalkPath(Point startPosition,
for (int i = 0; i < steps; ++i) { for (int i = 0; i < steps; ++i) {
const Point next = NextPositionForWalkDirection(position, path[i]); const Point next = NextPositionForWalkDirection(position, path[i]);
Object *object = FindObjectAtPosition(next); Object *object = FindObjectAtPosition(next);
if (object != nullptr && object->isDoor() && object->_oSolidFlag) { if (object != nullptr && object->isDoor() && object->_oVar4 == DoorClosed) {
return DoorBlockInfo { .beforeDoor = position, .doorPosition = next }; return DoorBlockInfo { .beforeDoor = position, .doorPosition = next };
} }
position = next; position = next;
@ -3385,7 +3385,7 @@ struct TrackerPathBlockInfo {
} }
Object *object = FindObjectAtPosition(next); Object *object = FindObjectAtPosition(next);
if (considerDoors && object != nullptr && object->isDoor() && object->_oSolidFlag) { if (considerDoors && object != nullptr && object->isDoor() && object->_oVar4 == DoorClosed) {
return TrackerPathBlockInfo { return TrackerPathBlockInfo {
.type = TrackerPathBlockType::Door, .type = TrackerPathBlockType::Door,
.stepIndex = i, .stepIndex = i,
@ -3422,14 +3422,14 @@ struct TrackerPathBlockInfo {
return std::nullopt; return std::nullopt;
} }
void NavigateToTrackerTargetKeyPressed() void NavigateToTrackerTargetKeyPressed()
{ {
if (!CanPlayerTakeAction() || InGameMenu()) if (!CanPlayerTakeAction() || InGameMenu())
return; return;
if (leveltype == DTYPE_TOWN && IsNoneOf(SelectedTrackerTargetCategory, TrackerTargetCategory::Items, TrackerTargetCategory::DeadBodies)) { if (leveltype == DTYPE_TOWN && IsNoneOf(SelectedTrackerTargetCategory, TrackerTargetCategory::Items, TrackerTargetCategory::DeadBodies)) {
SpeakText(_("Not in a dungeon."), true); SpeakText(_("Not in a dungeon."), true);
return; return;
} }
if (AutomapActive) { if (AutomapActive) {
SpeakText(_("Close the map first."), true); SpeakText(_("Close the map first."), true);
return; return;
@ -3714,10 +3714,10 @@ void NavigateToTrackerTargetKeyPressed()
} }
break; break;
} }
case TrackerTargetCategory::Monsters: { case TrackerTargetCategory::Monsters: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyMonsterTrackerCandidates(playerPosition, TrackerCycleDistanceTiles); const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyMonsterTrackerCandidates(playerPosition, TrackerCycleDistanceTiles);
if (cycleTarget) { if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId); targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) { if (!targetId) {
if (nearbyCandidates.empty()) if (nearbyCandidates.empty())
SpeakText(_("No monsters found."), true); SpeakText(_("No monsters found."), true);
@ -3750,51 +3750,51 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = tracked.position.tile; targetPosition = tracked.position.tile;
} }
break; break;
} }
case TrackerTargetCategory::DeadBodies: { case TrackerTargetCategory::DeadBodies: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyCorpseTrackerCandidates(playerPosition, TrackerCycleDistanceTiles); const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyCorpseTrackerCandidates(playerPosition, TrackerCycleDistanceTiles);
if (cycleTarget) { if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId); targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) { if (!targetId) {
if (nearbyCandidates.empty()) if (nearbyCandidates.empty())
SpeakText(_("No dead bodies found."), true); SpeakText(_("No dead bodies found."), true);
else else
SpeakText(_("No next dead body."), true); SpeakText(_("No next dead body."), true);
return; return;
} }
} else if (IsCorpsePresent(lockedTargetId)) { } else if (IsCorpsePresent(lockedTargetId)) {
targetId = lockedTargetId; targetId = lockedTargetId;
} else { } else {
targetId = FindNearestCorpseId(playerPosition); targetId = FindNearestCorpseId(playerPosition);
} }
if (!targetId) { if (!targetId) {
SpeakText(_("No dead bodies found."), true); SpeakText(_("No dead bodies found."), true);
return; return;
} }
if (!IsCorpsePresent(*targetId)) { if (!IsCorpsePresent(*targetId)) {
lockedTargetId = -1; lockedTargetId = -1;
SpeakText(_("No dead bodies found."), true); SpeakText(_("No dead bodies found."), true);
return; return;
} }
lockedTargetId = *targetId; lockedTargetId = *targetId;
targetName = _("Dead body"); targetName = _("Dead body");
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = CorpsePositionForTrackerId(*targetId); targetPosition = CorpsePositionForTrackerId(*targetId);
} }
break; break;
} }
} }
if (cycleTarget) { if (cycleTarget) {
SpeakText(targetName.str(), /*force=*/true); SpeakText(targetName.str(), /*force=*/true);
return; return;
} }
if (!targetPosition) { if (!targetPosition) {
@ -4087,10 +4087,10 @@ void UpdateAutoWalkTracker()
if (!ValidateAutoWalkObjectTarget(myPlayer, playerPosition, IsTrackedBreakableObject, N_("Target breakable is gone."), N_("Breakable in range."), destination)) if (!ValidateAutoWalkObjectTarget(myPlayer, playerPosition, IsTrackedBreakableObject, N_("Target breakable is gone."), N_("Breakable in range."), destination))
return; return;
break; break;
case TrackerTargetCategory::Monsters: { case TrackerTargetCategory::Monsters: {
const int monsterId = AutoWalkTrackerTargetId; const int monsterId = AutoWalkTrackerTargetId;
if (monsterId < 0 || monsterId >= static_cast<int>(MaxMonsters)) { if (monsterId < 0 || monsterId >= static_cast<int>(MaxMonsters)) {
AutoWalkTrackerTargetId = -1; AutoWalkTrackerTargetId = -1;
SpeakText(_("Target monster is gone."), true); SpeakText(_("Target monster is gone."), true);
return; return;
} }
@ -4105,29 +4105,29 @@ void UpdateAutoWalkTracker()
AutoWalkTrackerTargetId = -1; AutoWalkTrackerTargetId = -1;
SpeakText(_("Monster in range."), true); SpeakText(_("Monster in range."), true);
return; return;
} }
destination = FindBestAdjacentApproachTile(myPlayer, playerPosition, monsterPosition); destination = FindBestAdjacentApproachTile(myPlayer, playerPosition, monsterPosition);
break; break;
} }
case TrackerTargetCategory::DeadBodies: { case TrackerTargetCategory::DeadBodies: {
const int corpseId = AutoWalkTrackerTargetId; const int corpseId = AutoWalkTrackerTargetId;
if (!IsCorpsePresent(corpseId)) { if (!IsCorpsePresent(corpseId)) {
AutoWalkTrackerTargetId = -1; AutoWalkTrackerTargetId = -1;
SpeakText(_("Target dead body is gone."), true); SpeakText(_("Target dead body is gone."), true);
return; return;
} }
const Point corpsePosition = CorpsePositionForTrackerId(corpseId); const Point corpsePosition = CorpsePositionForTrackerId(corpseId);
if (playerPosition.WalkingDistance(corpsePosition) <= TrackerInteractDistanceTiles) { if (playerPosition.WalkingDistance(corpsePosition) <= TrackerInteractDistanceTiles) {
AutoWalkTrackerTargetId = -1; AutoWalkTrackerTargetId = -1;
SpeakText(_("Dead body in range."), true); SpeakText(_("Dead body in range."), true);
return; return;
} }
destination = corpsePosition; destination = corpsePosition;
break; break;
} }
} }
if (!destination) { if (!destination) {
AutoWalkTrackerTargetId = -1; AutoWalkTrackerTargetId = -1;
@ -4285,10 +4285,10 @@ void AutoWalkToTrackerTargetKeyPressed()
if (!targetId) if (!targetId)
return; return;
break; break;
case TrackerTargetCategory::Monsters: { case TrackerTargetCategory::Monsters: {
if (lockedTargetId >= 0 && lockedTargetId < static_cast<int>(MaxMonsters)) { if (lockedTargetId >= 0 && lockedTargetId < static_cast<int>(MaxMonsters)) {
targetId = lockedTargetId; targetId = lockedTargetId;
} else { } else {
targetId = FindNearestMonsterId(playerPosition); targetId = FindNearestMonsterId(playerPosition);
} }
if (!targetId) { if (!targetId) {
@ -4304,30 +4304,30 @@ void AutoWalkToTrackerTargetKeyPressed()
return; return;
} }
} }
lockedTargetId = *targetId; lockedTargetId = *targetId;
targetName = Monsters[*targetId].name(); targetName = Monsters[*targetId].name();
break; break;
} }
case TrackerTargetCategory::DeadBodies: { case TrackerTargetCategory::DeadBodies: {
if (IsCorpsePresent(lockedTargetId)) { if (IsCorpsePresent(lockedTargetId)) {
targetId = lockedTargetId; targetId = lockedTargetId;
} else { } else {
targetId = FindNearestCorpseId(playerPosition); targetId = FindNearestCorpseId(playerPosition);
} }
if (!targetId) { if (!targetId) {
SpeakText(_("No dead bodies found."), true); SpeakText(_("No dead bodies found."), true);
return; return;
} }
if (!IsCorpsePresent(*targetId)) { if (!IsCorpsePresent(*targetId)) {
lockedTargetId = -1; lockedTargetId = -1;
SpeakText(_("No dead bodies found."), true); SpeakText(_("No dead bodies found."), true);
return; return;
} }
lockedTargetId = *targetId; lockedTargetId = *targetId;
targetName = _("Dead body"); targetName = _("Dead body");
break; break;
} }
} }
std::string msg; std::string msg;
StrAppend(msg, _("Going to: "), targetName); StrAppend(msg, _("Going to: "), targetName);
@ -6062,14 +6062,14 @@ void InitKeymapActions()
SpeakNearestStairsUpKeyPressed, SpeakNearestStairsUpKeyPressed,
nullptr, nullptr,
[]() { return CanPlayerTakeAction() && leveltype != DTYPE_TOWN; }); []() { return CanPlayerTakeAction() && leveltype != DTYPE_TOWN; });
options.Keymapper.AddAction( options.Keymapper.AddAction(
"CycleTrackerTarget", "CycleTrackerTarget",
N_("Cycle tracker target"), N_("Cycle tracker target"),
N_("Cycles what the tracker looks for (items, chests, doors, shrines, objects, breakables, monsters, dead bodies). Hold Shift to cycle backwards."), N_("Cycles what the tracker looks for (items, chests, doors, shrines, objects, breakables, monsters, dead bodies). Hold Shift to cycle backwards."),
'T', 'T',
CycleTrackerTargetKeyPressed, CycleTrackerTargetKeyPressed,
nullptr, nullptr,
[]() { return CanPlayerTakeAction() && !InGameMenu(); }); []() { return CanPlayerTakeAction() && !InGameMenu(); });
options.Keymapper.AddAction( options.Keymapper.AddAction(
"NavigateToTrackerTarget", "NavigateToTrackerTarget",
N_("Tracker directions"), N_("Tracker directions"),

2
Source/objects.cpp

@ -1182,11 +1182,13 @@ void AddDoor(Object &door)
case OBJ_L5LDOOR: case OBJ_L5LDOOR:
door._oVar1 = dPiece[door.position.x][door.position.y] + 1; door._oVar1 = dPiece[door.position.x][door.position.y] + 1;
door._oVar2 = dPiece[door.position.x][door.position.y - 1] + 1; door._oVar2 = dPiece[door.position.x][door.position.y - 1] + 1;
dObject[door.position.x][door.position.y - 1] = -(static_cast<int8_t>(door.GetId()) + 1);
break; break;
case OBJ_L1RDOOR: case OBJ_L1RDOOR:
case OBJ_L5RDOOR: case OBJ_L5RDOOR:
door._oVar1 = dPiece[door.position.x][door.position.y] + 1; door._oVar1 = dPiece[door.position.x][door.position.y] + 1;
door._oVar2 = dPiece[door.position.x - 1][door.position.y] + 1; door._oVar2 = dPiece[door.position.x - 1][door.position.y] + 1;
dObject[door.position.x - 1][door.position.y] = -(static_cast<int8_t>(door.GetId()) + 1);
break; break;
default: default:
break; break;

23
test/tile_properties_test.cpp

@ -56,6 +56,29 @@ TEST(TilePropertiesTest, Walkable)
EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Solid tiles occupied by an open door become walkable when ignoring doors"; EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Solid tiles occupied by an open door become walkable when ignoring doors";
} }
TEST(TilePropertiesTest, DoorArchwaySolidTileBecomesWalkableWhenIgnoringDoors)
{
dPiece[5][4] = 0;
SOLData[0] = TileProperties::Solid;
dObject[5][4] = 0;
EXPECT_FALSE(IsTileWalkable({ 5, 4 }, true))
<< "Solid tile with no object is unwalkable even when ignoring doors";
Objects[0]._otype = _object_id::OBJ_L1LDOOR;
Objects[0]._oSolidFlag = false;
dObject[5][5] = 1;
dObject[5][4] = -(static_cast<int8_t>(0) + 1); // archway tile (large object convention)
EXPECT_TRUE(IsTileWalkable({ 5, 4 }, true))
<< "Solid archway tile referencing a door becomes walkable when ignoring doors";
EXPECT_FALSE(IsTileWalkable({ 5, 4 }))
<< "Solid archway tile referencing a door is still unwalkable normally";
// Cleanup
dObject[5][5] = 0;
dObject[5][4] = 0;
Objects[0] = {};
}
TEST(TilePropertiesTest, CanStepTest) TEST(TilePropertiesTest, CanStepTest)
{ {
dPiece[0][0] = 0; dPiece[0][0] = 0;

Loading…
Cancel
Save