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 SpeakSelectedSpeedbookSpell();
void SpellBookKeyPressed();
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &player, Point startPosition, Point destinationPosition);
void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int8_t> &path);
std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &player, Point startPosition, Point destinationPosition, bool allowDestinationNonWalkable = false);
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);
bool gbGameLoopStartup;
@ -2539,8 +2544,8 @@ struct TrackerCandidate {
[[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;
// Track both closed and open doors (to match proximity audio cues).
return object.isDoor() && object.canInteractWith();
}
[[nodiscard]] constexpr bool IsShrineLikeObject(const Object &object)
@ -2695,41 +2700,27 @@ template <typename Predicate>
std::vector<TrackerCandidate> result;
result.reserve(ActiveMonsterCount);
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);
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;
for (size_t i = 0; i < ActiveMonsterCount; ++i) {
const int monsterId = static_cast<int>(ActiveMonsters[i]);
const Monster &monster = Monsters[monsterId];
const Monster &monster = Monsters[monsterId];
if (monster.isInvalid)
continue;
if ((monster.flags & MFLAG_HIDDEN) != 0)
continue;
if (monster.hitPoints <= 0)
continue;
if (monster.isInvalid)
continue;
if ((monster.flags & MFLAG_HIDDEN) != 0)
continue;
if (monster.hitPoints <= 0)
continue;
const Point monsterPosition { monster.position.tile };
const int distance = playerPosition.WalkingDistance(monsterPosition);
if (distance > maxDistance)
continue;
const Point monsterDistancePosition { monster.position.future };
const int distance = playerPosition.ApproxDistance(monsterDistancePosition);
if (distance > maxDistance)
continue;
result.push_back(TrackerCandidate {
.id = monsterId,
.distance = distance,
.name = monster.name(),
});
}
result.push_back(TrackerCandidate {
.id = monsterId,
.distance = distance,
.name = monster.name(),
});
}
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);
}
std::optional<int> FindNearestClosedDoorObjectId(Point playerPosition)
std::optional<int> FindNearestDoorObjectId(Point playerPosition)
{
return FindNearestObjectId(playerPosition, IsTrackedDoorObject);
}
@ -2831,31 +2822,22 @@ std::optional<int> FindNearestMonsterId(Point playerPosition)
std::optional<int> bestId;
int bestDistance = 0;
std::array<bool, MaxMonsters> seen {};
for (int y = 0; y < MAXDUNY; ++y) {
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;
for (size_t i = 0; i < ActiveMonsterCount; ++i) {
const int monsterId = static_cast<int>(ActiveMonsters[i]);
const Monster &monster = Monsters[monsterId];
const Monster &monster = Monsters[monsterId];
if (monster.isInvalid)
continue;
if ((monster.flags & MFLAG_HIDDEN) != 0)
continue;
if (monster.hitPoints <= 0)
continue;
if (monster.isInvalid)
continue;
if ((monster.flags & MFLAG_HIDDEN) != 0)
continue;
if (monster.hitPoints <= 0)
continue;
const Point monsterPosition { monster.position.tile };
const int distance = playerPosition.WalkingDistance(monsterPosition);
if (!bestId || distance < bestDistance) {
bestId = monsterId;
bestDistance = distance;
}
const Point monsterDistancePosition { monster.position.future };
const int distance = playerPosition.ApproxDistance(monsterDistancePosition);
if (!bestId || distance < bestDistance) {
bestId = monsterId;
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> best;
size_t bestPathLength = 0;
int bestDistance = 0;
std::optional<Point> bestFallback;
int bestFallbackDistance = 0;
for (int dy = -1; dy <= 1; ++dy) {
for (int dx = -1; dx <= 1; ++dx) {
if (dx == 0 && dy == 0)
@ -2877,23 +2863,29 @@ std::optional<Point> FindBestAdjacentApproachTile(const Player &player, Point pl
continue;
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;
bestPathLength = pathLength;
bestDistance = distance;
}
}
}
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;
if (best)
return best;
return FindBestAdjacentApproachTile(player, playerPosition, object.position);
return bestFallback;
}
bool PosOkPlayerIgnoreDoors(const Player &player, Point position)
@ -2919,6 +2911,122 @@ bool PosOkPlayerIgnoreDoors(const Player &player, Point position)
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 {
Point beforeDoor;
Point doorPosition;
@ -2938,6 +3046,67 @@ std::optional<DoorBlockInfo> FindFirstClosedDoorOnWalkPath(Point startPosition,
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()
{
if (!CanPlayerTakeAction() || InGameMenu())
@ -2971,6 +3140,7 @@ void NavigateToTrackerTargetKeyPressed()
std::optional<int> targetId;
std::optional<Point> targetPosition;
std::optional<Point> alternateTargetPosition;
StringOrView targetName;
switch (SelectedTrackerTargetCategory) {
@ -3046,11 +3216,9 @@ void NavigateToTrackerTargetKeyPressed()
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;
}
targetPosition = tracked.position;
if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
alternateTargetPosition = tracked.position + Direction::NorthEast;
}
break;
}
@ -3068,7 +3236,7 @@ void NavigateToTrackerTargetKeyPressed()
} else if (lockedTargetId >= 0 && lockedTargetId < MAXOBJECTS) {
targetId = lockedTargetId;
} else {
targetId = FindNearestClosedDoorObjectId(playerPosition);
targetId = FindNearestDoorObjectId(playerPosition);
}
if (!targetId) {
SpeakText(_("No doors found."), true);
@ -3078,7 +3246,7 @@ void NavigateToTrackerTargetKeyPressed()
const Object &object = Objects[*targetId];
if (!IsTrackedDoorObject(object)) {
lockedTargetId = -1;
targetId = FindNearestClosedDoorObjectId(playerPosition);
targetId = FindNearestDoorObjectId(playerPosition);
if (!targetId) {
SpeakText(_("No doors found."), true);
return;
@ -3091,11 +3259,9 @@ void NavigateToTrackerTargetKeyPressed()
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;
}
targetPosition = tracked.position;
if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
alternateTargetPosition = tracked.position + Direction::NorthEast;
}
break;
}
@ -3136,11 +3302,9 @@ void NavigateToTrackerTargetKeyPressed()
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;
}
targetPosition = tracked.position;
if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
alternateTargetPosition = tracked.position + Direction::NorthEast;
}
break;
}
@ -3181,11 +3345,9 @@ void NavigateToTrackerTargetKeyPressed()
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;
}
targetPosition = tracked.position;
if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
alternateTargetPosition = tracked.position + Direction::NorthEast;
}
break;
}
@ -3226,11 +3388,9 @@ void NavigateToTrackerTargetKeyPressed()
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;
}
targetPosition = tracked.position;
if (FindObjectAtPosition(tracked.position + Direction::NorthEast) == &tracked)
alternateTargetPosition = tracked.position + Direction::NorthEast;
}
break;
}
@ -3272,12 +3432,7 @@ void NavigateToTrackerTargetKeyPressed()
targetName = tracked.name();
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
const Point monsterPosition { tracked.position.tile };
targetPosition = FindBestAdjacentApproachTile(*MyPlayer, playerPosition, monsterPosition);
if (!targetPosition) {
SpeakText(_("Can't find a nearby tile to walk to."), true);
return;
}
targetPosition = tracked.position.tile;
}
break;
}
@ -3292,14 +3447,127 @@ void NavigateToTrackerTargetKeyPressed()
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;
if (!targetName.empty())
StrAppend(message, targetName, "\n");
if (!path) {
AppendDirectionalFallback(message, *targetPosition - playerPosition);
} else {
AppendKeyboardWalkPathForSpeech(message, *path);
if (showUnreachableWarning) {
message.append(_("Can't find a path to the target."));
if (spokenPath && !spokenPath->empty())
message.append("\n");
}
if (spokenPath) {
if (!showUnreachableWarning || !spokenPath->empty())
AppendKeyboardWalkPathForSpeech(message, *spokenPath);
}
SpeakText(message, true);
@ -3512,28 +3780,12 @@ void ListTownNpcsKeyPressed()
SpeakText(output, true);
}
bool IsTileNavigableForSpeech(const Player &player, Point position)
{
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;
}
namespace {
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))
return std::nullopt;
@ -3560,10 +3812,14 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
if (visited[idx])
return;
if (!IsTileNavigableForSpeech(player, next))
return;
if (!CanStep(current, next))
return;
const bool ok = posOk(player, next);
if (ok) {
if (!CanStep(current, next))
return;
} else {
if (!allowDestinationNonWalkable || next != destinationPosition)
return;
}
visited[idx] = true;
parentDir[idx] = dir;
@ -3577,22 +3833,199 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
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_SW,
WALK_SE,
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();
queue.pop();
considerBest(current);
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, 4> prioritizedDirs;
std::array<int8_t, 8> prioritizedDirs;
size_t prioritizedCount = 0;
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 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);
@ -3615,7 +4054,7 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
addUniqueDir(yDir);
addUniqueDir(xDir);
}
for (const int8_t dir : WalkDirections) {
for (const int8_t dir : walkDirections) {
addUniqueDir(dir);
}
@ -3624,11 +4063,12 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
}
}
if (!hasReachedDestination())
return std::nullopt;
closestPosition = best;
if (best == startPosition)
return std::vector<int8_t> {};
std::vector<int8_t> path;
Point position = destinationPosition;
Point position = best;
while (position != startPosition) {
const int8_t dir = parentDir[indexOf(position)];
if (dir == WALK_NONE)
@ -3642,6 +4082,56 @@ std::optional<std::vector<int8_t>> FindKeyboardWalkPathForSpeech(const Player &p
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)
{
if (path.empty()) {
@ -3669,6 +4159,14 @@ void AppendKeyboardWalkPathForSpeech(std::string &message, const std::vector<int
return _("east");
case WALK_NW:
return _("west");
case WALK_N:
return _("northwest");
case WALK_E:
return _("northeast");
case WALK_S:
return _("southeast");
case WALK_W:
return _("southwest");
default:
return {};
}

24
Translations/pl.po

@ -12162,6 +12162,22 @@ msgstr "wschód"
msgid "west"
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
msgid "here"
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."
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
msgid "Head"
msgstr "Głowa"

Loading…
Cancel
Save