diff --git a/Source/controls/tracker.cpp b/Source/controls/tracker.cpp index 3363981a3..a3f236e0d 100644 --- a/Source/controls/tracker.cpp +++ b/Source/controls/tracker.cpp @@ -557,10 +557,10 @@ template return CollectNearbyObjectTrackerCandidates(playerPosition, maxDistance, IsTrackedMiscInteractableObject); } -[[nodiscard]] std::vector CollectNearbyMonsterTrackerCandidates(Point playerPosition, int maxDistance) -{ - std::vector result; - result.reserve(ActiveMonsterCount); +[[nodiscard]] std::vector CollectNearbyMonsterTrackerCandidates(Point playerPosition, int maxDistance) +{ + std::vector result; + result.reserve(ActiveMonsterCount); for (size_t i = 0; i < ActiveMonsterCount; ++i) { const int monsterId = static_cast(ActiveMonsters[i]); @@ -571,15 +571,15 @@ template const int distance = playerPosition.ApproxDistance(monster.position.future); if (distance > maxDistance) continue; - - result.push_back(TrackerCandidate { - .id = monsterId, - .distance = distance, - .name = monster.name(), - }); - } - - std::sort(result.begin(), result.end(), IsBetterTrackerCandidate); + + result.push_back(TrackerCandidate { + .id = monsterId, + .distance = distance, + .name = MonsterLabelForSpeech(monster), + }); + } + + std::sort(result.begin(), result.end(), IsBetterTrackerCandidate); return result; } @@ -1896,10 +1896,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); @@ -1926,15 +1926,15 @@ void NavigateToTrackerTargetKeyPressed() return; } } - - lockedTargetId = *targetId; - const Monster &tracked = Monsters[*targetId]; - - targetName = tracked.name(); - DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); - if (!cycleTarget) { - targetPosition = tracked.position.tile; - } + + lockedTargetId = *targetId; + const Monster &tracked = Monsters[*targetId]; + + targetName = MonsterLabelForSpeech(tracked); + DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); + if (!cycleTarget) { + targetPosition = tracked.position.tile; + } break; } case TrackerTargetCategory::DeadBodies: { diff --git a/Source/utils/accessibility_announcements.cpp b/Source/utils/accessibility_announcements.cpp index 0bdf593c7..d1e48c582 100644 --- a/Source/utils/accessibility_announcements.cpp +++ b/Source/utils/accessibility_announcements.cpp @@ -306,11 +306,11 @@ void UpdateBossHealthAnnouncements() } } -void UpdateAttackableMonsterAnnouncements() -{ - static std::optional LastAttackableMonsterId; - - if (MyPlayer == nullptr) { +void UpdateAttackableMonsterAnnouncements() +{ + static std::optional LastAttackableMonsterId; + + if (MyPlayer == nullptr) { LastAttackableMonsterId = std::nullopt; return; } @@ -372,18 +372,42 @@ void UpdateAttackableMonsterAnnouncements() if (LastAttackableMonsterId && *LastAttackableMonsterId == *bestId) return; - - LastAttackableMonsterId = *bestId; - - const std::string_view name = Monsters[*bestId].name(); - if (!name.empty()) - SpeakText(name, /*force=*/true); -} - -StringOrView DoorLabelForSpeech(const Object &door) -{ - if (!door.isDoor()) - return door.name(); + + LastAttackableMonsterId = *bestId; + + const StringOrView label = MonsterLabelForSpeech(Monsters[*bestId]); + if (!label.empty()) + SpeakText(label.str(), /*force=*/true); +} + +StringOrView MonsterLabelForSpeech(const Monster &monster) +{ + const std::string_view name = monster.name(); + if (name.empty()) + return name; + + std::string_view type; + switch (monster.data().monsterClass) { + case MonsterClass::Animal: + type = _("Animal"); + break; + case MonsterClass::Demon: + type = _("Demon"); + break; + case MonsterClass::Undead: + type = _("Undead"); + break; + } + + if (type.empty()) + return name; + return StrCat(name, ", ", type); +} + +StringOrView DoorLabelForSpeech(const Object &door) +{ + if (!door.isDoor()) + return door.name(); // Catacombs doors are grates, so differentiate them for the screen reader / tracker. if (IsAnyOf(door._otype, _object_id::OBJ_L2LDOOR, _object_id::OBJ_L2RDOOR)) { diff --git a/Source/utils/accessibility_announcements.hpp b/Source/utils/accessibility_announcements.hpp index af8bdd75f..d4d3d99f7 100644 --- a/Source/utils/accessibility_announcements.hpp +++ b/Source/utils/accessibility_announcements.hpp @@ -8,15 +8,17 @@ #include "utils/string_or_view.hpp" -namespace devilution { - -struct Object; - -void UpdatePlayerLowHpWarningSound(); -void UpdateLowDurabilityWarnings(); -void UpdateBossHealthAnnouncements(); -void UpdateAttackableMonsterAnnouncements(); -StringOrView DoorLabelForSpeech(const Object &door); -void UpdateInteractableDoorAnnouncements(); - -} // namespace devilution +namespace devilution { + +struct Monster; +struct Object; + +void UpdatePlayerLowHpWarningSound(); +void UpdateLowDurabilityWarnings(); +void UpdateBossHealthAnnouncements(); +void UpdateAttackableMonsterAnnouncements(); +StringOrView MonsterLabelForSpeech(const Monster &monster); +StringOrView DoorLabelForSpeech(const Object &door); +void UpdateInteractableDoorAnnouncements(); + +} // namespace devilution