Browse Source

update auto-walk in dungeons to detect closed doors

pull/8474/head
hidwood 2 months ago
parent
commit
e31ece4f2a
  1. 648
      Source/diablo.cpp
  2. 2
      Source/objects.cpp
  3. 23
      test/tile_properties_test.cpp

648
Source/diablo.cpp

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

2
Source/objects.cpp

@ -1182,11 +1182,13 @@ void AddDoor(Object &door)
case OBJ_L5LDOOR:
door._oVar1 = dPiece[door.position.x][door.position.y] + 1;
door._oVar2 = dPiece[door.position.x][door.position.y - 1] + 1;
dObject[door.position.x][door.position.y - 1] = -(static_cast<int8_t>(door.GetId()) + 1);
break;
case OBJ_L1RDOOR:
case OBJ_L5RDOOR:
door._oVar1 = dPiece[door.position.x][door.position.y] + 1;
door._oVar2 = dPiece[door.position.x - 1][door.position.y] + 1;
dObject[door.position.x - 1][door.position.y] = -(static_cast<int8_t>(door.GetId()) + 1);
break;
default:
break;

23
test/tile_properties_test.cpp

@ -56,6 +56,29 @@ TEST(TilePropertiesTest, Walkable)
EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Solid tiles occupied by an open door become walkable when ignoring doors";
}
TEST(TilePropertiesTest, DoorArchwaySolidTileBecomesWalkableWhenIgnoringDoors)
{
dPiece[5][4] = 0;
SOLData[0] = TileProperties::Solid;
dObject[5][4] = 0;
EXPECT_FALSE(IsTileWalkable({ 5, 4 }, true))
<< "Solid tile with no object is unwalkable even when ignoring doors";
Objects[0]._otype = _object_id::OBJ_L1LDOOR;
Objects[0]._oSolidFlag = false;
dObject[5][5] = 1;
dObject[5][4] = -(static_cast<int8_t>(0) + 1); // archway tile (large object convention)
EXPECT_TRUE(IsTileWalkable({ 5, 4 }, true))
<< "Solid archway tile referencing a door becomes walkable when ignoring doors";
EXPECT_FALSE(IsTileWalkable({ 5, 4 }))
<< "Solid archway tile referencing a door is still unwalkable normally";
// Cleanup
dObject[5][5] = 0;
dObject[5][4] = 0;
Objects[0] = {};
}
TEST(TilePropertiesTest, CanStepTest)
{
dPiece[0][0] = 0;

Loading…
Cancel
Save