Browse Source

Expand tracker categories for dungeon navigation

Adds new tracker categories (T) for doors, shrines, interactable objects, and breakables, and updates N/Shift+N to navigate/cycle within these categories.

Object tracking uses the dungeon object grid (dObject) for reliable discovery, and Polish translations were updated.
access
mojsior 2 months ago
parent
commit
4fbe18fe2b
  1. 423
      Source/diablo.cpp
  2. 52
      Translations/pl.po

423
Source/diablo.cpp

@ -7,6 +7,7 @@
#include <array>
#include <cstdlib>
#include <cstdint>
#include <limits>
#include <queue>
#include <string_view>
#include <vector>
@ -1881,6 +1882,10 @@ int AutoWalkTownNpcTarget = -1;
enum class TrackerTargetCategory : uint8_t {
Items,
Chests,
Doors,
Shrines,
Objects,
Breakables,
Monsters,
};
@ -2168,6 +2173,10 @@ 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;
struct TrackerLevelKey {
@ -2183,6 +2192,10 @@ void ClearTrackerLocks()
{
LockedTrackerItemId = -1;
LockedTrackerChestId = -1;
LockedTrackerDoorId = -1;
LockedTrackerShrineId = -1;
LockedTrackerObjectId = -1;
LockedTrackerBreakableId = -1;
LockedTrackerMonsterId = -1;
}
@ -2209,6 +2222,14 @@ int &LockedTrackerTargetId(TrackerTargetCategory category)
return LockedTrackerItemId;
case TrackerTargetCategory::Chests:
return LockedTrackerChestId;
case TrackerTargetCategory::Doors:
return LockedTrackerDoorId;
case TrackerTargetCategory::Shrines:
return LockedTrackerShrineId;
case TrackerTargetCategory::Objects:
return LockedTrackerObjectId;
case TrackerTargetCategory::Breakables:
return LockedTrackerBreakableId;
case TrackerTargetCategory::Monsters:
default:
return LockedTrackerMonsterId;
@ -2222,6 +2243,14 @@ std::string_view TrackerTargetCategoryLabel(TrackerTargetCategory category)
return _("items");
case TrackerTargetCategory::Chests:
return _("chests");
case TrackerTargetCategory::Doors:
return _("doors");
case TrackerTargetCategory::Shrines:
return _("shrines");
case TrackerTargetCategory::Objects:
return _("objects");
case TrackerTargetCategory::Breakables:
return _("breakables");
case TrackerTargetCategory::Monsters:
return _("monsters");
default:
@ -2248,6 +2277,18 @@ void CycleTrackerTargetKeyPressed()
SelectedTrackerTargetCategory = TrackerTargetCategory::Chests;
break;
case TrackerTargetCategory::Chests:
SelectedTrackerTargetCategory = TrackerTargetCategory::Doors;
break;
case TrackerTargetCategory::Doors:
SelectedTrackerTargetCategory = TrackerTargetCategory::Shrines;
break;
case TrackerTargetCategory::Shrines:
SelectedTrackerTargetCategory = TrackerTargetCategory::Objects;
break;
case TrackerTargetCategory::Objects:
SelectedTrackerTargetCategory = TrackerTargetCategory::Breakables;
break;
case TrackerTargetCategory::Breakables:
SelectedTrackerTargetCategory = TrackerTargetCategory::Monsters;
break;
case TrackerTargetCategory::Monsters:
@ -2339,24 +2380,87 @@ struct TrackerCandidate {
return result;
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyChestTrackerCandidates(Point playerPosition, int maxDistance)
[[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)
{
// Only closed doors (solid), because open doors are mostly just floor.
return object.isDoor() && object.canInteractWith() && object._oSolidFlag;
}
[[nodiscard]] constexpr bool IsShrineLikeObject(const Object &object)
{
return object.canInteractWith()
&& (object.IsShrine()
|| IsAnyOf(object._otype, _object_id::OBJ_BLOODFTN, _object_id::OBJ_PURIFYINGFTN, _object_id::OBJ_GOATSHRINE, _object_id::OBJ_CAULDRON,
_object_id::OBJ_MURKYFTN, _object_id::OBJ_TEARFTN));
}
[[nodiscard]] constexpr bool IsTrackedBreakableObject(const Object &object)
{
return object.IsBreakable();
}
[[nodiscard]] constexpr bool IsTrackedMiscInteractableObject(const Object &object)
{
if (!object.canInteractWith())
return false;
if (object.IsChest() || object._otype == _object_id::OBJ_SIGNCHEST)
return false;
if (object.isDoor())
return false;
if (IsShrineLikeObject(object))
return false;
if (object.IsBreakable())
return false;
return true;
}
template <typename Predicate>
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyObjectTrackerCandidates(Point playerPosition, int maxDistance, Predicate predicate)
{
std::vector<TrackerCandidate> result;
result.reserve(ActiveObjectCount);
for (int i = 0; i < ActiveObjectCount; i++) {
const int objectId = ActiveObjects[i];
if (objectId < 0 || objectId >= MAXOBJECTS)
continue;
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);
const Object &object = Objects[objectId];
if (!object.canInteractWith() || !object.IsChest())
continue;
std::array<int, MAXOBJECTS> bestDistanceById {};
bestDistanceById.fill(std::numeric_limits<int>::max());
const int distance = playerPosition.WalkingDistance(object.position);
if (distance > maxDistance)
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
const int objectId = std::abs(dObject[x][y]) - 1;
if (objectId < 0 || objectId >= MAXOBJECTS)
continue;
const Object &object = Objects[objectId];
if (object._otype == OBJ_NULL)
continue;
if (!predicate(object))
continue;
const int distance = playerPosition.WalkingDistance(Point { x, y });
if (distance > maxDistance)
continue;
int &bestDistance = bestDistanceById[objectId];
if (distance < bestDistance)
bestDistance = distance;
}
}
for (int objectId = 0; objectId < MAXOBJECTS; ++objectId) {
const int distance = bestDistanceById[objectId];
if (distance == std::numeric_limits<int>::max())
continue;
const Object &object = Objects[objectId];
result.push_back(TrackerCandidate {
.id = objectId,
.distance = distance,
@ -2368,6 +2472,72 @@ struct TrackerCandidate {
return result;
}
template <typename Predicate>
[[nodiscard]] std::optional<int> FindNearestObjectId(Point playerPosition, Predicate predicate)
{
std::array<int, MAXOBJECTS> bestDistanceById {};
bestDistanceById.fill(std::numeric_limits<int>::max());
for (int y = 0; y < MAXDUNY; ++y) {
for (int x = 0; x < MAXDUNX; ++x) {
const int objectId = std::abs(dObject[x][y]) - 1;
if (objectId < 0 || objectId >= MAXOBJECTS)
continue;
const Object &object = Objects[objectId];
if (object._otype == OBJ_NULL)
continue;
if (!predicate(object))
continue;
const int distance = playerPosition.WalkingDistance(Point { x, y });
int &bestDistance = bestDistanceById[objectId];
if (distance < bestDistance)
bestDistance = distance;
}
}
std::optional<int> bestId;
int bestDistance = 0;
for (int objectId = 0; objectId < MAXOBJECTS; ++objectId) {
const int distance = bestDistanceById[objectId];
if (distance == std::numeric_limits<int>::max())
continue;
if (!bestId || distance < bestDistance) {
bestId = objectId;
bestDistance = distance;
}
}
return bestId;
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyChestTrackerCandidates(Point playerPosition, int maxDistance)
{
return CollectNearbyObjectTrackerCandidates(playerPosition, maxDistance, IsTrackedChestObject);
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyDoorTrackerCandidates(Point playerPosition, int maxDistance)
{
return CollectNearbyObjectTrackerCandidates(playerPosition, maxDistance, IsTrackedDoorObject);
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyShrineTrackerCandidates(Point playerPosition, int maxDistance)
{
return CollectNearbyObjectTrackerCandidates(playerPosition, maxDistance, IsShrineLikeObject);
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyBreakableTrackerCandidates(Point playerPosition, int maxDistance)
{
return CollectNearbyObjectTrackerCandidates(playerPosition, maxDistance, IsTrackedBreakableObject);
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyObjectInteractableTrackerCandidates(Point playerPosition, int maxDistance)
{
return CollectNearbyObjectTrackerCandidates(playerPosition, maxDistance, IsTrackedMiscInteractableObject);
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyMonsterTrackerCandidates(Point playerPosition, int maxDistance)
{
std::vector<TrackerCandidate> result;
@ -2481,31 +2651,27 @@ void DecorateTrackerTargetNameWithOrdinalIfNeeded(int targetId, StringOrView &ta
std::optional<int> FindNearestUnopenedChestObjectId(Point playerPosition)
{
if (ActiveObjectCount == 0)
return std::nullopt;
std::optional<int> bestId;
int bestDistance = 0;
return FindNearestObjectId(playerPosition, IsTrackedChestObject);
}
for (int i = 0; i < ActiveObjectCount; i++) {
const int objectId = ActiveObjects[i];
if (objectId < 0 || objectId >= MAXOBJECTS)
continue;
std::optional<int> FindNearestClosedDoorObjectId(Point playerPosition)
{
return FindNearestObjectId(playerPosition, IsTrackedDoorObject);
}
const Object &object = Objects[objectId];
if (!object.canInteractWith())
continue;
if (!object.IsChest())
continue;
std::optional<int> FindNearestShrineObjectId(Point playerPosition)
{
return FindNearestObjectId(playerPosition, IsShrineLikeObject);
}
const int distance = playerPosition.WalkingDistance(object.position);
if (!bestId || distance < bestDistance) {
bestId = objectId;
bestDistance = distance;
}
}
std::optional<int> FindNearestBreakableObjectId(Point playerPosition)
{
return FindNearestObjectId(playerPosition, IsTrackedBreakableObject);
}
return bestId;
std::optional<int> FindNearestMiscInteractableObjectId(Point playerPosition)
{
return FindNearestObjectId(playerPosition, IsTrackedMiscInteractableObject);
}
std::optional<int> FindNearestMonsterId(Point playerPosition)
@ -2569,6 +2735,15 @@ std::optional<Point> FindBestAdjacentApproachTile(const Player &player, Point pl
return best;
}
std::optional<Point> FindBestApproachTileForObject(const Player &player, Point playerPosition, const Object &object)
{
// Some interactable objects are placed on a walkable tile (e.g. floor switches). Prefer stepping on the tile in that case.
if (!object._oSolidFlag && PosOkPlayer(player, object.position))
return object.position;
return FindBestAdjacentApproachTile(player, playerPosition, object.position);
}
bool PosOkPlayerIgnoreDoors(const Player &player, Point position)
{
if (!InDungeonBounds(position))
@ -2704,7 +2879,7 @@ void NavigateToTrackerTargetKeyPressed()
}
const Object &object = Objects[*targetId];
if (!object.IsChest() || !object.canInteractWith()) {
if (!IsTrackedChestObject(object)) {
lockedTargetId = -1;
targetId = FindNearestUnopenedChestObjectId(playerPosition);
if (!targetId) {
@ -2719,7 +2894,187 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
targetPosition = FindBestAdjacentApproachTile(*MyPlayer, playerPosition, tracked.position);
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
}
}
break;
}
case TrackerTargetCategory::Doors: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyDoorTrackerCandidates(playerPosition, TrackerCycleDistanceTiles);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) {
if (nearbyCandidates.empty())
SpeakText(_("No doors found."), true);
else
SpeakText(_("No next door."), true);
return;
}
} else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) {
targetId = lockedTargetId;
} else {
targetId = FindNearestClosedDoorObjectId(playerPosition);
}
if (!targetId) {
SpeakText(_("No doors found."), true);
return;
}
const Object &object = Objects[*targetId];
if (!IsTrackedDoorObject(object)) {
lockedTargetId = -1;
targetId = FindNearestClosedDoorObjectId(playerPosition);
if (!targetId) {
SpeakText(_("No doors found."), true);
return;
}
}
lockedTargetId = *targetId;
const Object &tracked = Objects[*targetId];
targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
}
}
break;
}
case TrackerTargetCategory::Shrines: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyShrineTrackerCandidates(playerPosition, TrackerCycleDistanceTiles);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) {
if (nearbyCandidates.empty())
SpeakText(_("No shrines found."), true);
else
SpeakText(_("No next shrine."), true);
return;
}
} else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) {
targetId = lockedTargetId;
} else {
targetId = FindNearestShrineObjectId(playerPosition);
}
if (!targetId) {
SpeakText(_("No shrines found."), true);
return;
}
const Object &object = Objects[*targetId];
if (!IsShrineLikeObject(object)) {
lockedTargetId = -1;
targetId = FindNearestShrineObjectId(playerPosition);
if (!targetId) {
SpeakText(_("No shrines found."), true);
return;
}
}
lockedTargetId = *targetId;
const Object &tracked = Objects[*targetId];
targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
}
}
break;
}
case TrackerTargetCategory::Objects: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyObjectInteractableTrackerCandidates(playerPosition, TrackerCycleDistanceTiles);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) {
if (nearbyCandidates.empty())
SpeakText(_("No objects found."), true);
else
SpeakText(_("No next object."), true);
return;
}
} else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) {
targetId = lockedTargetId;
} else {
targetId = FindNearestMiscInteractableObjectId(playerPosition);
}
if (!targetId) {
SpeakText(_("No objects found."), true);
return;
}
const Object &object = Objects[*targetId];
if (!IsTrackedMiscInteractableObject(object)) {
lockedTargetId = -1;
targetId = FindNearestMiscInteractableObjectId(playerPosition);
if (!targetId) {
SpeakText(_("No objects found."), true);
return;
}
}
lockedTargetId = *targetId;
const Object &tracked = Objects[*targetId];
targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
}
}
break;
}
case TrackerTargetCategory::Breakables: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectNearbyBreakableTrackerCandidates(playerPosition, TrackerCycleDistanceTiles);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) {
if (nearbyCandidates.empty())
SpeakText(_("No breakables found."), true);
else
SpeakText(_("No next breakable."), true);
return;
}
} else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) {
targetId = lockedTargetId;
} else {
targetId = FindNearestBreakableObjectId(playerPosition);
}
if (!targetId) {
SpeakText(_("No breakables found."), true);
return;
}
const Object &object = Objects[*targetId];
if (!IsTrackedBreakableObject(object)) {
lockedTargetId = -1;
targetId = FindNearestBreakableObjectId(playerPosition);
if (!targetId) {
SpeakText(_("No breakables found."), true);
return;
}
}
lockedTargetId = *targetId;
const Object &tracked = Objects[*targetId];
targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
@ -4106,7 +4461,7 @@ void InitKeymapActions()
options.Keymapper.AddAction(
"CycleTrackerTarget",
N_("Cycle tracker target"),
N_("Cycles what the tracker looks for (items, chests, monsters)."),
N_("Cycles what the tracker looks for (items, chests, doors, shrines, objects, breakables, monsters)."),
'T',
CycleTrackerTargetKeyPressed,
nullptr,

52
Translations/pl.po

@ -6508,6 +6508,22 @@ msgstr "Nie ma następnego przedmiotu."
msgid "No next chest."
msgstr "Nie ma następnej skrzyni."
#: Source/diablo.cpp
msgid "No next door."
msgstr "Nie ma następnych drzwi."
#: Source/diablo.cpp
msgid "No next shrine."
msgstr "Nie ma następnej kapliczki."
#: Source/diablo.cpp
msgid "No next object."
msgstr "Nie ma następnego obiektu."
#: Source/diablo.cpp
msgid "No next breakable."
msgstr "Nie ma następnego niszczalnego obiektu."
#: Source/diablo.cpp
msgid "No next monster."
msgstr "Nie ma następnego potwora."
@ -12147,8 +12163,8 @@ msgid "Cycle tracker target"
msgstr "Zmień cel trackera"
#: Source/diablo.cpp
msgid "Cycles what the tracker looks for (items, chests, monsters)."
msgstr "Zmienia, czego szuka tracker (przedmioty, skrzynie, potwory)."
msgid "Cycles what the tracker looks for (items, chests, doors, shrines, objects, breakables, monsters)."
msgstr "Zmienia, czego szuka tracker (przedmioty, skrzynie, drzwi, kapliczki, obiekty, niszczalne, potwory)."
#: Source/diablo.cpp
msgid "Navigate to tracker target"
@ -12170,6 +12186,22 @@ msgstr "przedmioty"
msgid "chests"
msgstr "skrzynie"
#: Source/diablo.cpp
msgid "doors"
msgstr "drzwi"
#: Source/diablo.cpp
msgid "shrines"
msgstr "kapliczki"
#: Source/diablo.cpp
msgid "objects"
msgstr "obiekty"
#: Source/diablo.cpp
msgid "breakables"
msgstr "niszczalne"
#: Source/diablo.cpp
msgid "monsters"
msgstr "potwory"
@ -12182,6 +12214,22 @@ msgstr "Nie znaleziono żadnych przedmiotów."
msgid "No chests found."
msgstr "Nie znaleziono żadnych skrzyń."
#: Source/diablo.cpp
msgid "No doors found."
msgstr "Nie znaleziono żadnych drzwi."
#: Source/diablo.cpp
msgid "No shrines found."
msgstr "Nie znaleziono żadnych kapliczek."
#: Source/diablo.cpp
msgid "No objects found."
msgstr "Nie znaleziono żadnych obiektów."
#: Source/diablo.cpp
msgid "No breakables found."
msgstr "Nie znaleziono żadnych niszczalnych obiektów."
#: Source/diablo.cpp
msgid "No monsters found."
msgstr "Nie znaleziono żadnych potworów."

Loading…
Cancel
Save