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