Browse Source

Fix N tracker pathfinding for non-items

access
mojsior 2 months ago
parent
commit
e56b7d96d0
  1. 776
      Source/diablo.cpp
  2. 24
      Translations/pl.po

776
Source/diablo.cpp

@ -181,8 +181,13 @@ char gszVersionNumber[64] = "internal version unknown";
void UpdateAutoWalkTracker(); void UpdateAutoWalkTracker();
void SpeakSelectedSpeedbookSpell(); void SpeakSelectedSpeedbookSpell();
void SpellBookKeyPressed(); void SpellBookKeyPressed();
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &player, Point startPosition, Point destinationPosition); std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable = false);
void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int8_t> &path); std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechRespectingDoors(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable = false);
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechIgnoringMonsters(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable = false);
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechRespectingDoorsIgnoringMonsters(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable = false);
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechLenient(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable = false);
std::optional<std::vector<int8_t>> FindKeyboardWalkPathToClosestReachableForSpeech(const Player &player, Point startPosition, Point destinationPosition, Point &closestPosition);
void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int8_t> &path);
void AppendDirectionalFallback(std::string &message, const Displacement &delta); void AppendDirectionalFallback(std::string &message, const Displacement &delta);
bool gbGameLoopStartup; bool gbGameLoopStartup;
@ -2539,8 +2544,8 @@ struct TrackerCandidate {
[[nodiscard]] constexpr bool IsTrackedDoorObject(const Object &object) [[nodiscard]] constexpr bool IsTrackedDoorObject(const Object &object)
{ {
// Only closed doors (solid), because open doors are mostly just floor. // Track both closed and open doors (to match proximity audio cues).
return object.isDoor() && object.canInteractWith() && object._oSolidFlag; return object.isDoor() && object.canInteractWith();
} }
[[nodiscard]] constexpr bool IsShrineLikeObject(const Object &object) [[nodiscard]] constexpr bool IsShrineLikeObject(const Object &object)
@ -2695,41 +2700,27 @@ template <typename Predicate>
std::vector<TrackerCandidate> result; std::vector<TrackerCandidate> result;
result.reserve(ActiveMonsterCount); result.reserve(ActiveMonsterCount);
const int minX = std::max(0, playerPosition.x - maxDistance); for (size_t i = 0; i < ActiveMonsterCount; ++i) {
const int minY = std::max(0, playerPosition.y - maxDistance); const int monsterId = static_cast<int>(ActiveMonsters[i]);
const int maxX = std::min(MAXDUNX - 1, playerPosition.x + maxDistance); const Monster &monster = Monsters[monsterId];
const int maxY = std::min(MAXDUNY - 1, playerPosition.y + maxDistance);
std::array<bool, MaxMonsters> seen {};
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
const int monsterId = std::abs(dMonster[x][y]) - 1;
if (monsterId < 0 || monsterId >= static_cast<int>(MaxMonsters))
continue;
if (seen[monsterId])
continue;
seen[monsterId] = true;
const Monster &monster = Monsters[monsterId]; if (monster.isInvalid)
if (monster.isInvalid) continue;
continue; if ((monster.flags & MFLAG_HIDDEN) != 0)
if ((monster.flags & MFLAG_HIDDEN) != 0) continue;
continue; if (monster.hitPoints <= 0)
if (monster.hitPoints <= 0) continue;
continue;
const Point monsterPosition { monster.position.tile }; const Point monsterDistancePosition { monster.position.future };
const int distance = playerPosition.WalkingDistance(monsterPosition); const int distance = playerPosition.ApproxDistance(monsterDistancePosition);
if (distance > maxDistance) if (distance > maxDistance)
continue; continue;
result.push_back(TrackerCandidate { result.push_back(TrackerCandidate {
.id = monsterId, .id = monsterId,
.distance = distance, .distance = distance,
.name = monster.name(), .name = monster.name(),
}); });
}
} }
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); });
@ -2806,7 +2797,7 @@ std::optional<int> FindNearestUnopenedChestObjectId(Point playerPosition)
return FindNearestObjectId(playerPosition, IsTrackedChestObject); return FindNearestObjectId(playerPosition, IsTrackedChestObject);
} }
std::optional<int> FindNearestClosedDoorObjectId(Point playerPosition) std::optional<int> FindNearestDoorObjectId(Point playerPosition)
{ {
return FindNearestObjectId(playerPosition, IsTrackedDoorObject); return FindNearestObjectId(playerPosition, IsTrackedDoorObject);
} }
@ -2831,31 +2822,22 @@ std::optional<int> FindNearestMonsterId(Point playerPosition)
std::optional<int> bestId; std::optional<int> bestId;
int bestDistance = 0; int bestDistance = 0;
std::array<bool, MaxMonsters> seen {}; for (size_t i = 0; i < ActiveMonsterCount; ++i) {
const int monsterId = static_cast<int>(ActiveMonsters[i]);
for (int y = 0; y < MAXDUNY; ++y) { const Monster &monster = Monsters[monsterId];
for (int x = 0; x < MAXDUNX; ++x) {
const int monsterId = std::abs(dMonster[x][y]) - 1;
if (monsterId < 0 || monsterId >= static_cast<int>(MaxMonsters))
continue;
if (seen[monsterId])
continue;
seen[monsterId] = true;
const Monster &monster = Monsters[monsterId]; if (monster.isInvalid)
if (monster.isInvalid) continue;
continue; if ((monster.flags & MFLAG_HIDDEN) != 0)
if ((monster.flags & MFLAG_HIDDEN) != 0) continue;
continue; if (monster.hitPoints <= 0)
if (monster.hitPoints <= 0) continue;
continue;
const Point monsterPosition { monster.position.tile }; const Point monsterDistancePosition { monster.position.future };
const int distance = playerPosition.WalkingDistance(monsterPosition); const int distance = playerPosition.ApproxDistance(monsterDistancePosition);
if (!bestId || distance < bestDistance) { if (!bestId || distance < bestDistance) {
bestId = monsterId; bestId = monsterId;
bestDistance = distance; bestDistance = distance;
}
} }
} }
@ -2865,8 +2847,12 @@ std::optional<int> FindNearestMonsterId(Point playerPosition)
std::optional<Point> FindBestAdjacentApproachTile(const Player &player, Point playerPosition, Point targetPosition) std::optional<Point> FindBestAdjacentApproachTile(const Player &player, Point playerPosition, Point targetPosition)
{ {
std::optional<Point> best; std::optional<Point> best;
size_t bestPathLength = 0;
int bestDistance = 0; int bestDistance = 0;
std::optional<Point> bestFallback;
int bestFallbackDistance = 0;
for (int dy = -1; dy <= 1; ++dy) { for (int dy = -1; dy <= 1; ++dy) {
for (int dx = -1; dx <= 1; ++dx) { for (int dx = -1; dx <= 1; ++dx) {
if (dx == 0 && dy == 0) if (dx == 0 && dy == 0)
@ -2877,23 +2863,29 @@ std::optional<Point> FindBestAdjacentApproachTile(const Player &player, Point pl
continue; continue;
const int distance = playerPosition.WalkingDistance(tile); const int distance = playerPosition.WalkingDistance(tile);
if (!best || distance < bestDistance) {
if (!bestFallback || distance < bestFallbackDistance) {
bestFallback = tile;
bestFallbackDistance = distance;
}
const std::optional<std::vector<int8_t>> path = FindKeyboardWalkPathForSpeech(player, playerPosition, tile);
if (!path)
continue;
const size_t pathLength = path->size();
if (!best || pathLength < bestPathLength || (pathLength == bestPathLength && distance < bestDistance)) {
best = tile; best = tile;
bestPathLength = pathLength;
bestDistance = distance; bestDistance = distance;
} }
} }
} }
return best; if (best)
} 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); return bestFallback;
} }
bool PosOkPlayerIgnoreDoors(const Player &player, Point position) bool PosOkPlayerIgnoreDoors(const Player &player, Point position)
@ -2919,6 +2911,122 @@ bool PosOkPlayerIgnoreDoors(const Player &player, Point position)
return true; return true;
} }
[[nodiscard]] bool IsTileWalkableForTrackerPath(Point position, bool ignoreDoors, bool ignoreBreakables)
{
Object *object = FindObjectAtPosition(position);
if (object != nullptr) {
if (ignoreDoors && object->isDoor()) {
return true;
}
if (ignoreBreakables && object->_oSolidFlag && object->IsBreakable()) {
return true;
}
if (object->_oSolidFlag) {
return false;
}
}
return IsTileNotSolid(position);
}
bool PosOkPlayerIgnoreMonsters(const Player &player, Point position)
{
if (!InDungeonBounds(position))
return false;
if (!IsTileWalkableForTrackerPath(position, /*ignoreDoors=*/false, /*ignoreBreakables=*/false))
return false;
Player *otherPlayer = PlayerAtPosition(position);
if (otherPlayer != nullptr && otherPlayer != &player && !otherPlayer->hasNoLife())
return false;
return true;
}
bool PosOkPlayerIgnoreDoorsAndMonsters(const Player &player, Point position)
{
if (!InDungeonBounds(position))
return false;
if (!IsTileWalkableForTrackerPath(position, /*ignoreDoors=*/true, /*ignoreBreakables=*/false))
return false;
Player *otherPlayer = PlayerAtPosition(position);
if (otherPlayer != nullptr && otherPlayer != &player && !otherPlayer->hasNoLife())
return false;
return true;
}
bool PosOkPlayerIgnoreDoorsMonstersAndBreakables(const Player &player, Point position)
{
if (!InDungeonBounds(position))
return false;
if (!IsTileWalkableForTrackerPath(position, /*ignoreDoors=*/true, /*ignoreBreakables=*/true))
return false;
Player *otherPlayer = PlayerAtPosition(position);
if (otherPlayer != nullptr && otherPlayer != &player && !otherPlayer->hasNoLife())
return false;
return true;
}
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;
std::optional<Point> best;
size_t bestPathLength = 0;
int bestDistance = 0;
std::optional<Point> bestFallback;
int bestFallbackDistance = 0;
const auto considerTile = [&](Point tile) {
if (!PosOkPlayerIgnoreDoors(player, tile))
return;
const int distance = playerPosition.WalkingDistance(tile);
if (!bestFallback || distance < bestFallbackDistance) {
bestFallback = tile;
bestFallbackDistance = distance;
}
const std::optional<std::vector<int8_t>> path = FindKeyboardWalkPathForSpeech(player, playerPosition, tile);
if (!path)
return;
const size_t pathLength = path->size();
if (!best || pathLength < bestPathLength || (pathLength == bestPathLength && distance < bestDistance)) {
best = tile;
bestPathLength = pathLength;
bestDistance = distance;
}
};
for (int dy = -1; dy <= 1; ++dy) {
for (int dx = -1; dx <= 1; ++dx) {
if (dx == 0 && dy == 0)
continue;
considerTile(object.position + Displacement { dx, dy });
}
}
if (FindObjectAtPosition(object.position + Direction::NorthEast) == &object) {
// Special case for large objects (e.g. sarcophagi): allow approaching from one tile further to the north.
for (int dx = -1; dx <= 1; ++dx) {
considerTile(object.position + Displacement { dx, -2 });
}
}
if (best)
return best;
return bestFallback;
}
struct DoorBlockInfo { struct DoorBlockInfo {
Point beforeDoor; Point beforeDoor;
Point doorPosition; Point doorPosition;
@ -2938,6 +3046,67 @@ std::optional<DoorBlockInfo> FindFirstClosedDoorOnWalkPath(Point startPosition,
return std::nullopt; return std::nullopt;
} }
enum class TrackerPathBlockType : uint8_t {
Door,
Monster,
Breakable,
};
struct TrackerPathBlockInfo {
TrackerPathBlockType type;
size_t stepIndex;
Point beforeBlock;
Point blockPosition;
};
[[nodiscard]] std::optional<TrackerPathBlockInfo> FindFirstTrackerPathBlock(Point startPosition, const int8_t *path, size_t steps, bool considerDoors, bool considerMonsters, bool considerBreakables, Point targetPosition)
{
Point position = startPosition;
for (size_t i = 0; i < steps; ++i) {
const Point next = NextPositionForWalkDirection(position, path[i]);
if (next == targetPosition) {
position = next;
continue;
}
Object *object = FindObjectAtPosition(next);
if (considerDoors && object != nullptr && object->isDoor() && object->_oSolidFlag) {
return TrackerPathBlockInfo {
.type = TrackerPathBlockType::Door,
.stepIndex = i,
.beforeBlock = position,
.blockPosition = next,
};
}
if (considerBreakables && object != nullptr && object->_oSolidFlag && object->IsBreakable()) {
return TrackerPathBlockInfo {
.type = TrackerPathBlockType::Breakable,
.stepIndex = i,
.beforeBlock = position,
.blockPosition = next,
};
}
if (considerMonsters && leveltype != DTYPE_TOWN && dMonster[next.x][next.y] != 0) {
const int monsterRef = dMonster[next.x][next.y];
const int monsterId = std::abs(monsterRef) - 1;
const bool blocks = monsterRef <= 0 || (monsterId >= 0 && monsterId < static_cast<int>(MaxMonsters) && !Monsters[monsterId].hasNoLife());
if (blocks) {
return TrackerPathBlockInfo {
.type = TrackerPathBlockType::Monster,
.stepIndex = i,
.beforeBlock = position,
.blockPosition = next,
};
}
}
position = next;
}
return std::nullopt;
}
void NavigateToTrackerTargetKeyPressed() void NavigateToTrackerTargetKeyPressed()
{ {
if (!CanPlayerTakeAction() || InGameMenu()) if (!CanPlayerTakeAction() || InGameMenu())
@ -2971,6 +3140,7 @@ void NavigateToTrackerTargetKeyPressed()
std::optional<int> targetId; std::optional<int> targetId;
std::optional<Point> targetPosition; std::optional<Point> targetPosition;
std::optional<Point> alternateTargetPosition;
StringOrView targetName; StringOrView targetName;
switch (SelectedTrackerTargetCategory) { switch (SelectedTrackerTargetCategory) {
@ -3046,11 +3216,9 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked); targetPosition = tracked.position;
if (!targetPosition) { if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
SpeakText(_("Can't find a nearby tile to walk to."), true); alternateTargetPosition = tracked.position + Direction::NorthEast;
return;
}
} }
break; break;
} }
@ -3068,7 +3236,7 @@ void NavigateToTrackerTargetKeyPressed()
} else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) { } else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) {
targetId = lockedTargetId; targetId = lockedTargetId;
} else { } else {
targetId = FindNearestClosedDoorObjectId(playerPosition); targetId = FindNearestDoorObjectId(playerPosition);
} }
if (!targetId) { if (!targetId) {
SpeakText(_("No doors found."), true); SpeakText(_("No doors found."), true);
@ -3078,7 +3246,7 @@ void NavigateToTrackerTargetKeyPressed()
const Object &object = Objects[*targetId]; const Object &object = Objects[*targetId];
if (!IsTrackedDoorObject(object)) { if (!IsTrackedDoorObject(object)) {
lockedTargetId = -1; lockedTargetId = -1;
targetId = FindNearestClosedDoorObjectId(playerPosition); targetId = FindNearestDoorObjectId(playerPosition);
if (!targetId) { if (!targetId) {
SpeakText(_("No doors found."), true); SpeakText(_("No doors found."), true);
return; return;
@ -3091,11 +3259,9 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked); targetPosition = tracked.position;
if (!targetPosition) { if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
SpeakText(_("Can't find a nearby tile to walk to."), true); alternateTargetPosition = tracked.position + Direction::NorthEast;
return;
}
} }
break; break;
} }
@ -3136,11 +3302,9 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked); targetPosition = tracked.position;
if (!targetPosition) { if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
SpeakText(_("Can't find a nearby tile to walk to."), true); alternateTargetPosition = tracked.position + Direction::NorthEast;
return;
}
} }
break; break;
} }
@ -3181,11 +3345,9 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked); targetPosition = tracked.position;
if (!targetPosition) { if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
SpeakText(_("Can't find a nearby tile to walk to."), true); alternateTargetPosition = tracked.position + Direction::NorthEast;
return;
}
} }
break; break;
} }
@ -3226,11 +3388,9 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
targetPosition = FindBestApproachTileForObject(*MyPlayer, playerPosition, tracked); targetPosition = tracked.position;
if (!targetPosition) { if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
SpeakText(_("Can't find a nearby tile to walk to."), true); alternateTargetPosition = tracked.position + Direction::NorthEast;
return;
}
} }
break; break;
} }
@ -3272,12 +3432,7 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name(); targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates); DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) { if (!cycleTarget) {
const Point monsterPosition { tracked.position.tile }; targetPosition = tracked.position.tile;
targetPosition = FindBestAdjacentApproachTile(*MyPlayer, playerPosition, monsterPosition);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
}
} }
break; break;
} }
@ -3292,14 +3447,127 @@ void NavigateToTrackerTargetKeyPressed()
return; return;
} }
const std::optional<std::vector<int8_t>> path = FindKeyboardWalkPathForSpeech(*MyPlayer, playerPosition, *targetPosition); Point chosenTargetPosition = *targetPosition;
enum class TrackerPathMode : uint8_t {
RespectDoorsAndMonsters,
IgnoreDoors,
IgnoreMonsters,
IgnoreDoorsAndMonsters,
Lenient,
};
auto findPathToTarget = [&](Point destination, TrackerPathMode mode) -> std::optional<std::vector<int8_t>> {
const bool allowDestinationNonWalkable = !PosOkPlayer(*MyPlayer, destination);
switch (mode) {
case TrackerPathMode::RespectDoorsAndMonsters:
return FindKeyboardWalkPathForSpeechRespectingDoors(*MyPlayer, playerPosition, destination, allowDestinationNonWalkable);
case TrackerPathMode::IgnoreDoors:
return FindKeyboardWalkPathForSpeech(*MyPlayer, playerPosition, destination, allowDestinationNonWalkable);
case TrackerPathMode::IgnoreMonsters:
return FindKeyboardWalkPathForSpeechRespectingDoorsIgnoringMonsters(*MyPlayer, playerPosition, destination, allowDestinationNonWalkable);
case TrackerPathMode::IgnoreDoorsAndMonsters:
return FindKeyboardWalkPathForSpeechIgnoringMonsters(*MyPlayer, playerPosition, destination, allowDestinationNonWalkable);
case TrackerPathMode::Lenient:
return FindKeyboardWalkPathForSpeechLenient(*MyPlayer, playerPosition, destination, allowDestinationNonWalkable);
default:
return std::nullopt;
}
};
std::optional<std::vector<int8_t>> spokenPath;
bool pathIgnoresDoors = false;
bool pathIgnoresMonsters = false;
bool pathIgnoresBreakables = false;
const auto considerDestination = [&](Point destination, TrackerPathMode mode) {
const std::optional<std::vector<int8_t>> candidate = findPathToTarget(destination, mode);
if (!candidate)
return;
if (!spokenPath || candidate->size() < spokenPath->size()) {
spokenPath = *candidate;
chosenTargetPosition = destination;
pathIgnoresDoors = mode == TrackerPathMode::IgnoreDoors || mode == TrackerPathMode::IgnoreDoorsAndMonsters || mode == TrackerPathMode::Lenient;
pathIgnoresMonsters = mode == TrackerPathMode::IgnoreMonsters || mode == TrackerPathMode::IgnoreDoorsAndMonsters || mode == TrackerPathMode::Lenient;
pathIgnoresBreakables = mode == TrackerPathMode::Lenient;
}
};
considerDestination(*targetPosition, TrackerPathMode::RespectDoorsAndMonsters);
if (alternateTargetPosition)
considerDestination(*alternateTargetPosition, TrackerPathMode::RespectDoorsAndMonsters);
if (!spokenPath) {
considerDestination(*targetPosition, TrackerPathMode::IgnoreDoors);
if (alternateTargetPosition)
considerDestination(*alternateTargetPosition, TrackerPathMode::IgnoreDoors);
}
if (!spokenPath) {
considerDestination(*targetPosition, TrackerPathMode::IgnoreMonsters);
if (alternateTargetPosition)
considerDestination(*alternateTargetPosition, TrackerPathMode::IgnoreMonsters);
}
if (!spokenPath) {
considerDestination(*targetPosition, TrackerPathMode::IgnoreDoorsAndMonsters);
if (alternateTargetPosition)
considerDestination(*alternateTargetPosition, TrackerPathMode::IgnoreDoorsAndMonsters);
}
if (!spokenPath) {
considerDestination(*targetPosition, TrackerPathMode::Lenient);
if (alternateTargetPosition)
considerDestination(*alternateTargetPosition, TrackerPathMode::Lenient);
}
bool showUnreachableWarning = false;
if (!spokenPath) {
showUnreachableWarning = true;
Point closestPosition;
spokenPath = FindKeyboardWalkPathToClosestReachableForSpeech(*MyPlayer, playerPosition, chosenTargetPosition, closestPosition);
pathIgnoresDoors = true;
pathIgnoresMonsters = false;
pathIgnoresBreakables = false;
}
if (spokenPath && !showUnreachableWarning && !PosOkPlayer(*MyPlayer, chosenTargetPosition)) {
if (!spokenPath->empty())
spokenPath->pop_back();
}
if (spokenPath && (pathIgnoresDoors || pathIgnoresMonsters || pathIgnoresBreakables)) {
const std::optional<TrackerPathBlockInfo> block = FindFirstTrackerPathBlock(playerPosition, spokenPath->data(), spokenPath->size(), pathIgnoresDoors, pathIgnoresMonsters, pathIgnoresBreakables, chosenTargetPosition);
if (block) {
if (playerPosition.WalkingDistance(block->blockPosition) <= TrackerInteractDistanceTiles) {
switch (block->type) {
case TrackerPathBlockType::Door:
SpeakText(_("A door is blocking the path. Open it and try again."), true);
return;
case TrackerPathBlockType::Monster:
SpeakText(_("A monster is blocking the path. Clear it and try again."), true);
return;
case TrackerPathBlockType::Breakable:
SpeakText(_("A breakable object is blocking the path. Destroy it and try again."), true);
return;
}
}
spokenPath = std::vector<int8_t>(spokenPath->begin(), spokenPath->begin() + block->stepIndex);
}
}
std::string message; std::string message;
if (!targetName.empty()) if (!targetName.empty())
StrAppend(message, targetName, "\n"); StrAppend(message, targetName, "\n");
if (!path) { if (showUnreachableWarning) {
AppendDirectionalFallback(message, *targetPosition - playerPosition); message.append(_("Can't find a path to the target."));
} else { if (spokenPath && !spokenPath->empty())
AppendKeyboardWalkPathForSpeech(message, *path); message.append("\n");
}
if (spokenPath) {
if (!showUnreachableWarning || !spokenPath->empty())
AppendKeyboardWalkPathForSpeech(message, *spokenPath);
} }
SpeakText(message, true); SpeakText(message, true);
@ -3512,28 +3780,12 @@ void ListTownNpcsKeyPressed()
SpeakText(output, true); SpeakText(output, true);
} }
bool IsTileNavigableForSpeech(const Player &player, Point position) namespace {
{
if (!InDungeonBounds(position))
return false;
if (!IsTileWalkable(position, /*ignoreDoors=*/true))
return false;
Player *otherPlayer = PlayerAtPosition(position);
if (otherPlayer != nullptr && otherPlayer != &player && !otherPlayer->hasNoLife())
return false;
if (leveltype == DTYPE_TOWN) {
// In town, treat NPCs as blocking.
if (dMonster[position.x][position.y] != 0)
return false;
}
return true; using PosOkForSpeechFn = bool (*)(const Player &, Point);
}
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &player, Point startPosition, Point destinationPosition) template <size_t NumDirections>
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechBfs(const Player &player, Point startPosition, Point destinationPosition, PosOkForSpeechFn posOk, const std::array<int8_t, NumDirections> &walkDirections, bool allowDiagonalSteps, bool allowDestinationNonWalkable)
{ {
if (!InDungeonBounds(startPosition) || !InDungeonBounds(destinationPosition)) if (!InDungeonBounds(startPosition) || !InDungeonBounds(destinationPosition))
return std::nullopt; return std::nullopt;
@ -3560,10 +3812,14 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
if (visited[idx]) if (visited[idx])
return; return;
if (!IsTileNavigableForSpeech(player, next)) const bool ok = posOk(player, next);
return; if (ok) {
if (!CanStep(current, next)) if (!CanStep(current, next))
return; return;
} else {
if (!allowDestinationNonWalkable || next != destinationPosition)
return;
}
visited[idx] = true; visited[idx] = true;
parentDir[idx] = dir; parentDir[idx] = dir;
@ -3577,22 +3833,199 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
return visited[indexOf(destinationPosition)]; return visited[indexOf(destinationPosition)];
}; };
constexpr std::array<int8_t, 4> WalkDirections = { while (!queue.empty() && !hasReachedDestination()) {
const Point current = queue.front();
queue.pop();
const Displacement delta = destinationPosition - current;
const int deltaAbsX = delta.deltaX >= 0 ? delta.deltaX : -delta.deltaX;
const int deltaAbsY = delta.deltaY >= 0 ? delta.deltaY : -delta.deltaY;
std::array<int8_t, 8> prioritizedDirs;
size_t prioritizedCount = 0;
const auto addUniqueDir = [&](int8_t dir) {
if (dir == WALK_NONE)
return;
for (size_t i = 0; i < prioritizedCount; ++i) {
if (prioritizedDirs[i] == dir)
return;
}
prioritizedDirs[prioritizedCount++] = dir;
};
const int8_t xDir = delta.deltaX > 0 ? WALK_SE : (delta.deltaX < 0 ? WALK_NW : WALK_NONE);
const int8_t yDir = delta.deltaY > 0 ? WALK_SW : (delta.deltaY < 0 ? WALK_NE : WALK_NONE);
if (allowDiagonalSteps && delta.deltaX != 0 && delta.deltaY != 0) {
const int8_t diagDir =
delta.deltaX > 0 ? (delta.deltaY > 0 ? WALK_S : WALK_E) : (delta.deltaY > 0 ? WALK_W : WALK_N);
addUniqueDir(diagDir);
}
if (deltaAbsX >= deltaAbsY) {
addUniqueDir(xDir);
addUniqueDir(yDir);
} else {
addUniqueDir(yDir);
addUniqueDir(xDir);
}
for (const int8_t dir : walkDirections) {
addUniqueDir(dir);
}
for (size_t i = 0; i < prioritizedCount; ++i) {
enqueue(current, prioritizedDirs[i]);
}
}
if (!hasReachedDestination())
return std::nullopt;
std::vector<int8_t> path;
Point position = destinationPosition;
while (position != startPosition) {
const int8_t dir = parentDir[indexOf(position)];
if (dir == WALK_NONE)
return std::nullopt;
path.push_back(dir);
position = NextPositionForWalkDirection(position, OppositeWalkDirection(dir));
}
std::reverse(path.begin(), path.end());
return path;
}
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechWithPosOk(const Player &player, Point startPosition, Point destinationPosition, PosOkForSpeechFn posOk, bool allowDestinationNonWalkable)
{
constexpr std::array<int8_t, 4> AxisDirections = {
WALK_NE,
WALK_SW,
WALK_SE,
WALK_NW,
};
constexpr std::array<int8_t, 8> AllDirections = {
WALK_NE, WALK_NE,
WALK_SW, WALK_SW,
WALK_SE, WALK_SE,
WALK_NW, WALK_NW,
WALK_N,
WALK_E,
WALK_S,
WALK_W,
}; };
while (!queue.empty() && !hasReachedDestination()) { if (const std::optional<std::vector<int8_t>> axisPath = FindKeyboardWalkPathForSpeechBfs(player, startPosition, destinationPosition, posOk, AxisDirections, /*allowDiagonalSteps=*/false, allowDestinationNonWalkable); axisPath) {
return axisPath;
}
return FindKeyboardWalkPathForSpeechBfs(player, startPosition, destinationPosition, posOk, AllDirections, /*allowDiagonalSteps=*/true, allowDestinationNonWalkable);
}
} // namespace
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable)
{
return FindKeyboardWalkPathForSpeechWithPosOk(player, startPosition, destinationPosition, PosOkPlayerIgnoreDoors, allowDestinationNonWalkable);
}
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechRespectingDoors(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable)
{
return FindKeyboardWalkPathForSpeechWithPosOk(player, startPosition, destinationPosition, PosOkPlayer, allowDestinationNonWalkable);
}
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechIgnoringMonsters(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable)
{
return FindKeyboardWalkPathForSpeechWithPosOk(player, startPosition, destinationPosition, PosOkPlayerIgnoreDoorsAndMonsters, allowDestinationNonWalkable);
}
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechRespectingDoorsIgnoringMonsters(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable)
{
return FindKeyboardWalkPathForSpeechWithPosOk(player, startPosition, destinationPosition, PosOkPlayerIgnoreMonsters, allowDestinationNonWalkable);
}
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeechLenient(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable)
{
return FindKeyboardWalkPathForSpeechWithPosOk(player, startPosition, destinationPosition, PosOkPlayerIgnoreDoorsMonstersAndBreakables, allowDestinationNonWalkable);
}
namespace {
template <size_t NumDirections>
std::optional<std::vector<int8_t>> FindKeyboardWalkPathToClosestReachableForSpeechBfs(const Player &player, Point startPosition, Point destinationPosition, PosOkForSpeechFn posOk, const std::array<int8_t, NumDirections> &walkDirections, bool allowDiagonalSteps, Point &closestPosition)
{
if (!InDungeonBounds(startPosition) || !InDungeonBounds(destinationPosition))
return std::nullopt;
if (startPosition == destinationPosition) {
closestPosition = destinationPosition;
return std::vector<int8_t> {};
}
std::array<bool, MAXDUNX * MAXDUNY> visited {};
std::array<int8_t, MAXDUNX * MAXDUNY> parentDir {};
std::array<uint16_t, MAXDUNX * MAXDUNY> depth {};
parentDir.fill(WALK_NONE);
depth.fill(0);
std::queue<Point> queue;
const auto indexOf = [](Point position) -> size_t {
return static_cast<size_t>(position.x) + static_cast<size_t>(position.y) * MAXDUNX;
};
const auto enqueue = [&](Point current, int8_t dir) {
const Point next = NextPositionForWalkDirection(current, dir);
if (!InDungeonBounds(next))
return;
const size_t nextIdx = indexOf(next);
if (visited[nextIdx])
return;
if (!posOk(player, next))
return;
if (!CanStep(current, next))
return;
const size_t currentIdx = indexOf(current);
visited[nextIdx] = true;
parentDir[nextIdx] = dir;
depth[nextIdx] = static_cast<uint16_t>(depth[currentIdx] + 1);
queue.push(next);
};
const size_t startIdx = indexOf(startPosition);
visited[startIdx] = true;
queue.push(startPosition);
Point best = startPosition;
int bestDistance = startPosition.WalkingDistance(destinationPosition);
uint16_t bestDepth = 0;
const auto considerBest = [&](Point position) {
const int distance = position.WalkingDistance(destinationPosition);
const uint16_t posDepth = depth[indexOf(position)];
if (distance < bestDistance || (distance == bestDistance && posDepth < bestDepth)) {
best = position;
bestDistance = distance;
bestDepth = posDepth;
}
};
while (!queue.empty()) {
const Point current = queue.front(); const Point current = queue.front();
queue.pop(); queue.pop();
considerBest(current);
const Displacement delta = destinationPosition - current; const Displacement delta = destinationPosition - current;
const int deltaAbsX = delta.deltaX >= 0 ? delta.deltaX : -delta.deltaX; const int deltaAbsX = delta.deltaX >= 0 ? delta.deltaX : -delta.deltaX;
const int deltaAbsY = delta.deltaY >= 0 ? delta.deltaY : -delta.deltaY; const int deltaAbsY = delta.deltaY >= 0 ? delta.deltaY : -delta.deltaY;
std::array<int8_t, 4> prioritizedDirs; std::array<int8_t, 8> prioritizedDirs;
size_t prioritizedCount = 0; size_t prioritizedCount = 0;
const auto addUniqueDir = [&](int8_t dir) { const auto addUniqueDir = [&](int8_t dir) {
@ -3608,6 +4041,12 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
const int8_t xDir = delta.deltaX > 0 ? WALK_SE : (delta.deltaX < 0 ? WALK_NW : WALK_NONE); const int8_t xDir = delta.deltaX > 0 ? WALK_SE : (delta.deltaX < 0 ? WALK_NW : WALK_NONE);
const int8_t yDir = delta.deltaY > 0 ? WALK_SW : (delta.deltaY < 0 ? WALK_NE : WALK_NONE); const int8_t yDir = delta.deltaY > 0 ? WALK_SW : (delta.deltaY < 0 ? WALK_NE : WALK_NONE);
if (allowDiagonalSteps && delta.deltaX != 0 && delta.deltaY != 0) {
const int8_t diagDir =
delta.deltaX > 0 ? (delta.deltaY > 0 ? WALK_S : WALK_E) : (delta.deltaY > 0 ? WALK_W : WALK_N);
addUniqueDir(diagDir);
}
if (deltaAbsX >= deltaAbsY) { if (deltaAbsX >= deltaAbsY) {
addUniqueDir(xDir); addUniqueDir(xDir);
addUniqueDir(yDir); addUniqueDir(yDir);
@ -3615,7 +4054,7 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
addUniqueDir(yDir); addUniqueDir(yDir);
addUniqueDir(xDir); addUniqueDir(xDir);
} }
for (const int8_t dir : WalkDirections) { for (const int8_t dir : walkDirections) {
addUniqueDir(dir); addUniqueDir(dir);
} }
@ -3624,11 +4063,12 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
} }
} }
if (!hasReachedDestination()) closestPosition = best;
return std::nullopt; if (best == startPosition)
return std::vector<int8_t> {};
std::vector<int8_t> path; std::vector<int8_t> path;
Point position = destinationPosition; Point position = best;
while (position != startPosition) { while (position != startPosition) {
const int8_t dir = parentDir[indexOf(position)]; const int8_t dir = parentDir[indexOf(position)];
if (dir == WALK_NONE) if (dir == WALK_NONE)
@ -3642,6 +4082,56 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
return path; return path;
} }
} // namespace
std::optional<std::vector<int8_t>> FindKeyboardWalkPathToClosestReachableForSpeech(const Player &player, Point startPosition, Point destinationPosition, Point &closestPosition)
{
constexpr std::array<int8_t, 4> AxisDirections = {
WALK_NE,
WALK_SW,
WALK_SE,
WALK_NW,
};
constexpr std::array<int8_t, 8> AllDirections = {
WALK_NE,
WALK_SW,
WALK_SE,
WALK_NW,
WALK_N,
WALK_E,
WALK_S,
WALK_W,
};
Point axisClosest;
const std::optional<std::vector<int8_t>> axisPath = FindKeyboardWalkPathToClosestReachableForSpeechBfs(player, startPosition, destinationPosition, PosOkPlayerIgnoreDoors, AxisDirections, /*allowDiagonalSteps=*/false, axisClosest);
Point diagClosest;
const std::optional<std::vector<int8_t>> diagPath = FindKeyboardWalkPathToClosestReachableForSpeechBfs(player, startPosition, destinationPosition, PosOkPlayerIgnoreDoors, AllDirections, /*allowDiagonalSteps=*/true, diagClosest);
if (!axisPath && !diagPath)
return std::nullopt;
if (!axisPath) {
closestPosition = diagClosest;
return diagPath;
}
if (!diagPath) {
closestPosition = axisClosest;
return axisPath;
}
const int axisDistance = axisClosest.WalkingDistance(destinationPosition);
const int diagDistance = diagClosest.WalkingDistance(destinationPosition);
if (diagDistance < axisDistance) {
closestPosition = diagClosest;
return diagPath;
}
closestPosition = axisClosest;
return axisPath;
}
void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int8_t> &path) void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int8_t> &path)
{ {
if (path.empty()) { if (path.empty()) {
@ -3669,6 +4159,14 @@ void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int
return _("east"); return _("east");
case WALK_NW: case WALK_NW:
return _("west"); return _("west");
case WALK_N:
return _("northwest");
case WALK_E:
return _("northeast");
case WALK_S:
return _("southeast");
case WALK_W:
return _("southwest");
default: default:
return {}; return {};
} }

24
Translations/pl.po

@ -12162,6 +12162,22 @@ msgstr "wschód"
msgid "west" msgid "west"
msgstr "zachód" msgstr "zachód"
#: Source/diablo.cpp
msgid "northwest"
msgstr "północny zachód"
#: Source/diablo.cpp
msgid "northeast"
msgstr "północny wschód"
#: Source/diablo.cpp
msgid "southeast"
msgstr "południowy wschód"
#: Source/diablo.cpp
msgid "southwest"
msgstr "południowy zachód"
#: Source/diablo.cpp #: Source/diablo.cpp
msgid "here" msgid "here"
msgstr "tutaj" msgstr "tutaj"
@ -12298,6 +12314,14 @@ msgstr "Nie mogę znaleźć ścieżki do celu."
msgid "A door is blocking the path. Open it and try again." msgid "A door is blocking the path. Open it and try again."
msgstr "Drzwi blokują drogę. Otwórz je i spróbuj ponownie." msgstr "Drzwi blokują drogę. Otwórz je i spróbuj ponownie."
#: Source/diablo.cpp
msgid "A monster is blocking the path. Clear it and try again."
msgstr "Potwór blokuje drogę. Pokonaj go i spróbuj ponownie."
#: Source/diablo.cpp
msgid "A breakable object is blocking the path. Destroy it and try again."
msgstr "Niszczalny obiekt blokuje drogę. Zniszcz go i spróbuj ponownie."
#: Source/controls/plrctrls.cpp #: Source/controls/plrctrls.cpp
msgid "Head" msgid "Head"
msgstr "Głowa" msgstr "Głowa"

Loading…
Cancel
Save