Browse Source
Remove the legacy town_npc_nav tracker system which was superseded by the unified tracker. The old tracker was still firing on PgUp/PgDown due to hardcoded fallback logic in PressKey(). Changes: - Remove PgUp/PgDown fallbacks in PressKey() (keep store/chat handling) - Remove 5 keymapper registrations (ListTownNpcs, PreviousTownNpc, NextTownNpc, SpeakSelectedTownNpc, GoToSelectedTownNpc) - Remove UpdateAutoWalkTownNpc() and ResetAutoWalkTownNpc() calls - Remove migration code references in options.cpp - Delete town_npc_nav.cpp and town_npc_nav.hpp - Update CMakeLists.txt Town NPC tracking is now handled by the unified tracker's NPCs category. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>pull/8474/head
5 changed files with 95 additions and 514 deletions
@ -1,348 +0,0 @@
|
||||
/**
|
||||
* @file controls/town_npc_nav.cpp |
||||
* |
||||
* Town NPC navigation for accessibility. |
||||
*/ |
||||
#include "controls/town_npc_nav.hpp" |
||||
|
||||
#include <algorithm> |
||||
#include <array> |
||||
#include <cstdint> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include <fmt/format.h> |
||||
|
||||
#include "controls/accessibility_keys.hpp" |
||||
#include "controls/plrctrls.h" |
||||
#include "diablo.h" |
||||
#include "engine/path.h" |
||||
#include "help.h" |
||||
#include "levels/gendung.h" |
||||
#include "levels/tile_properties.hpp" |
||||
#include "multi.h" |
||||
#include "options.h" |
||||
#include "player.h" |
||||
#include "qol/chatlog.h" |
||||
#include "stores.h" |
||||
#include "towners.h" |
||||
#include "utils/language.h" |
||||
#include "utils/screen_reader.hpp" |
||||
#include "utils/str_cat.hpp" |
||||
#include "utils/walk_path_speech.hpp" |
||||
|
||||
namespace devilution { |
||||
|
||||
namespace { |
||||
|
||||
std::vector<int> TownNpcOrder; |
||||
int SelectedTownNpc = -1; |
||||
int AutoWalkTownNpcTarget = -1; |
||||
|
||||
void ResetTownNpcSelection() |
||||
{ |
||||
TownNpcOrder.clear(); |
||||
SelectedTownNpc = -1; |
||||
} |
||||
|
||||
void RefreshTownNpcOrder(bool selectFirst = false) |
||||
{ |
||||
TownNpcOrder.clear(); |
||||
if (leveltype != DTYPE_TOWN) |
||||
return; |
||||
if (MyPlayer == nullptr) { |
||||
SelectedTownNpc = -1; |
||||
return; |
||||
} |
||||
|
||||
const Point playerPosition = MyPlayer->position.future; |
||||
|
||||
for (size_t i = 0; i < GetNumTowners(); ++i) { |
||||
const Towner &towner = Towners[i]; |
||||
if (!IsTownerPresent(towner._ttype)) |
||||
continue; |
||||
if (towner._ttype == TOWN_COW) |
||||
continue; |
||||
TownNpcOrder.push_back(static_cast<int>(i)); |
||||
} |
||||
|
||||
if (TownNpcOrder.empty()) { |
||||
SelectedTownNpc = -1; |
||||
return; |
||||
} |
||||
|
||||
std::sort(TownNpcOrder.begin(), TownNpcOrder.end(), [&playerPosition](int a, int b) { |
||||
const Towner &townerA = Towners[a]; |
||||
const Towner &townerB = Towners[b]; |
||||
const int distanceA = playerPosition.WalkingDistance(townerA.position); |
||||
const int distanceB = playerPosition.WalkingDistance(townerB.position); |
||||
if (distanceA != distanceB) |
||||
return distanceA < distanceB; |
||||
return townerA.name < townerB.name; |
||||
}); |
||||
|
||||
if (selectFirst) { |
||||
SelectedTownNpc = TownNpcOrder.front(); |
||||
return; |
||||
} |
||||
|
||||
const auto it = std::find(TownNpcOrder.begin(), TownNpcOrder.end(), SelectedTownNpc); |
||||
if (it == TownNpcOrder.end()) |
||||
SelectedTownNpc = TownNpcOrder.front(); |
||||
} |
||||
|
||||
void EnsureTownNpcOrder() |
||||
{ |
||||
if (leveltype != DTYPE_TOWN) { |
||||
ResetTownNpcSelection(); |
||||
return; |
||||
} |
||||
if (TownNpcOrder.empty()) { |
||||
RefreshTownNpcOrder(true); |
||||
return; |
||||
} |
||||
if (SelectedTownNpc < 0 || SelectedTownNpc >= static_cast<int>(GetNumTowners())) { |
||||
RefreshTownNpcOrder(true); |
||||
return; |
||||
} |
||||
const auto it = std::find(TownNpcOrder.begin(), TownNpcOrder.end(), SelectedTownNpc); |
||||
if (it == TownNpcOrder.end()) |
||||
SelectedTownNpc = TownNpcOrder.front(); |
||||
} |
||||
|
||||
void SelectTownNpcRelative(int delta) |
||||
{ |
||||
if (!IsTownNpcActionAllowed()) |
||||
return; |
||||
|
||||
EnsureTownNpcOrder(); |
||||
if (TownNpcOrder.empty()) { |
||||
SpeakText(_("No town NPCs found."), true); |
||||
return; |
||||
} |
||||
|
||||
auto it = std::find(TownNpcOrder.begin(), TownNpcOrder.end(), SelectedTownNpc); |
||||
int currentIndex = (it != TownNpcOrder.end()) ? static_cast<int>(it - TownNpcOrder.begin()) : 0; |
||||
|
||||
const int size = static_cast<int>(TownNpcOrder.size()); |
||||
int newIndex = (currentIndex + delta) % size; |
||||
if (newIndex < 0) |
||||
newIndex += size; |
||||
SelectedTownNpc = TownNpcOrder[static_cast<size_t>(newIndex)]; |
||||
SpeakSelectedTownNpc(); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
bool IsTownNpcActionAllowed() |
||||
{ |
||||
return CanPlayerTakeAction() |
||||
&& leveltype == DTYPE_TOWN |
||||
&& !IsPlayerInStore() |
||||
&& !ChatLogFlag |
||||
&& !HelpFlag; |
||||
} |
||||
|
||||
void SpeakSelectedTownNpc() |
||||
{ |
||||
EnsureTownNpcOrder(); |
||||
|
||||
if (SelectedTownNpc < 0 || SelectedTownNpc >= static_cast<int>(GetNumTowners())) { |
||||
SpeakText(_("No NPC selected."), true); |
||||
return; |
||||
} |
||||
if (MyPlayer == nullptr) { |
||||
SpeakText(_("No NPC selected."), true); |
||||
return; |
||||
} |
||||
|
||||
const Towner &towner = Towners[SelectedTownNpc]; |
||||
const Point playerPosition = MyPlayer->position.future; |
||||
const int distance = playerPosition.WalkingDistance(towner.position); |
||||
|
||||
std::string msg; |
||||
StrAppend(msg, towner.name); |
||||
StrAppend(msg, "\n", _("Distance: "), distance); |
||||
StrAppend(msg, "\n", _("Position: "), towner.position.x, ", ", towner.position.y); |
||||
SpeakText(msg, true); |
||||
} |
||||
|
||||
void SelectNextTownNpcKeyPressed() |
||||
{ |
||||
SelectTownNpcRelative(+1); |
||||
} |
||||
|
||||
void SelectPreviousTownNpcKeyPressed() |
||||
{ |
||||
SelectTownNpcRelative(-1); |
||||
} |
||||
|
||||
void GoToSelectedTownNpcKeyPressed() |
||||
{ |
||||
if (!IsTownNpcActionAllowed()) |
||||
return; |
||||
|
||||
EnsureTownNpcOrder(); |
||||
if (SelectedTownNpc < 0 || SelectedTownNpc >= static_cast<int>(GetNumTowners())) { |
||||
SpeakText(_("No NPC selected."), true); |
||||
return; |
||||
} |
||||
|
||||
const Towner &towner = Towners[SelectedTownNpc]; |
||||
|
||||
std::string msg; |
||||
StrAppend(msg, _("Going to: "), towner.name); |
||||
SpeakText(msg, true); |
||||
|
||||
AutoWalkTownNpcTarget = SelectedTownNpc; |
||||
UpdateAutoWalkTownNpc(); |
||||
} |
||||
|
||||
void UpdateAutoWalkTownNpc() |
||||
{ |
||||
if (AutoWalkTownNpcTarget < 0) |
||||
return; |
||||
if (leveltype != DTYPE_TOWN || IsPlayerInStore() || ChatLogFlag || HelpFlag) { |
||||
AutoWalkTownNpcTarget = -1; |
||||
return; |
||||
} |
||||
if (!CanPlayerTakeAction()) |
||||
return; |
||||
if (MyPlayer == nullptr) { |
||||
AutoWalkTownNpcTarget = -1; |
||||
return; |
||||
} |
||||
|
||||
if (MyPlayer->_pmode != PM_STAND) |
||||
return; |
||||
if (MyPlayer->walkpath[0] != WALK_NONE) |
||||
return; |
||||
if (MyPlayer->destAction != ACTION_NONE) |
||||
return; |
||||
|
||||
if (AutoWalkTownNpcTarget >= static_cast<int>(GetNumTowners())) { |
||||
AutoWalkTownNpcTarget = -1; |
||||
SpeakText(_("No NPC selected."), true); |
||||
return; |
||||
} |
||||
|
||||
const Towner &towner = Towners[AutoWalkTownNpcTarget]; |
||||
if (!IsTownerPresent(towner._ttype) || towner._ttype == TOWN_COW) { |
||||
AutoWalkTownNpcTarget = -1; |
||||
SpeakText(_("No NPC selected."), true); |
||||
return; |
||||
} |
||||
|
||||
Player &myPlayer = *MyPlayer; |
||||
const Point playerPosition = myPlayer.position.future; |
||||
if (playerPosition.WalkingDistance(towner.position) < 2) { |
||||
const int townerIdx = AutoWalkTownNpcTarget; |
||||
AutoWalkTownNpcTarget = -1; |
||||
NetSendCmdLocParam1(true, CMD_TALKXY, towner.position, static_cast<uint16_t>(townerIdx)); |
||||
return; |
||||
} |
||||
|
||||
constexpr size_t MaxAutoWalkPathLength = 512; |
||||
std::array<int8_t, MaxAutoWalkPathLength> path; |
||||
path.fill(WALK_NONE); |
||||
|
||||
const int steps = FindPath(CanStep, [&myPlayer](Point position) { return PosOkPlayer(myPlayer, position); }, playerPosition, towner.position, path.data(), path.size()); |
||||
if (steps == 0) { |
||||
AutoWalkTownNpcTarget = -1; |
||||
std::string error; |
||||
StrAppend(error, _("Can't find a path to: "), towner.name); |
||||
SpeakText(error, true); |
||||
return; |
||||
} |
||||
|
||||
// FindPath returns 0 when it fails to find a usable path.
|
||||
// The player walkpath buffer is MaxPathLengthPlayer, so keep segments strictly shorter.
|
||||
if (steps < static_cast<int>(MaxPathLengthPlayer)) { |
||||
const int townerIdx = AutoWalkTownNpcTarget; |
||||
AutoWalkTownNpcTarget = -1; |
||||
NetSendCmdLocParam1(true, CMD_TALKXY, towner.position, static_cast<uint16_t>(townerIdx)); |
||||
return; |
||||
} |
||||
|
||||
const int segmentSteps = std::min(steps - 1, static_cast<int>(MaxPathLengthPlayer - 1)); |
||||
const Point waypoint = PositionAfterWalkPathSteps(playerPosition, path.data(), segmentSteps); |
||||
NetSendCmdLoc(MyPlayerId, true, CMD_WALKXY, waypoint); |
||||
} |
||||
|
||||
void ResetAutoWalkTownNpc() |
||||
{ |
||||
AutoWalkTownNpcTarget = -1; |
||||
} |
||||
|
||||
void ListTownNpcsKeyPressed() |
||||
{ |
||||
if (leveltype != DTYPE_TOWN) { |
||||
ResetTownNpcSelection(); |
||||
SpeakText(_("Not in town."), true); |
||||
return; |
||||
} |
||||
if (IsPlayerInStore()) |
||||
return; |
||||
|
||||
std::vector<const Towner *> townNpcs; |
||||
std::vector<const Towner *> cows; |
||||
|
||||
townNpcs.reserve(Towners.size()); |
||||
cows.reserve(Towners.size()); |
||||
if (MyPlayer == nullptr) { |
||||
SpeakText(_("No town NPCs found."), true); |
||||
return; |
||||
} |
||||
|
||||
const Point playerPosition = MyPlayer->position.future; |
||||
|
||||
for (const Towner &towner : Towners) { |
||||
if (!IsTownerPresent(towner._ttype)) |
||||
continue; |
||||
|
||||
if (towner._ttype == TOWN_COW) { |
||||
cows.push_back(&towner); |
||||
continue; |
||||
} |
||||
|
||||
townNpcs.push_back(&towner); |
||||
} |
||||
|
||||
if (townNpcs.empty() && cows.empty()) { |
||||
ResetTownNpcSelection(); |
||||
SpeakText(_("No town NPCs found."), true); |
||||
return; |
||||
} |
||||
|
||||
std::sort(townNpcs.begin(), townNpcs.end(), [&playerPosition](const Towner *a, const Towner *b) { |
||||
const int distanceA = playerPosition.WalkingDistance(a->position); |
||||
const int distanceB = playerPosition.WalkingDistance(b->position); |
||||
if (distanceA != distanceB) |
||||
return distanceA < distanceB; |
||||
return a->name < b->name; |
||||
}); |
||||
|
||||
std::string output; |
||||
StrAppend(output, _("Town NPCs:")); |
||||
for (size_t i = 0; i < townNpcs.size(); ++i) { |
||||
StrAppend(output, "\n", i + 1, ". ", townNpcs[i]->name); |
||||
} |
||||
if (!cows.empty()) { |
||||
StrAppend(output, "\n", _("Cows: "), static_cast<int>(cows.size())); |
||||
} |
||||
|
||||
RefreshTownNpcOrder(true); |
||||
if (SelectedTownNpc >= 0 && SelectedTownNpc < static_cast<int>(GetNumTowners())) { |
||||
const Towner &towner = Towners[SelectedTownNpc]; |
||||
StrAppend(output, "\n", _("Selected: "), towner.name); |
||||
StrAppend(output, "\n", _("PageUp/PageDown: select. Home: go. End: repeat.")); |
||||
} |
||||
const std::string_view exitKey = GetOptions().Keymapper.KeyNameForAction("SpeakNearestExit"); |
||||
if (!exitKey.empty()) { |
||||
StrAppend(output, "\n", fmt::format(fmt::runtime(_("Cathedral entrance: press {:s}.")), exitKey)); |
||||
} |
||||
|
||||
SpeakText(output, true); |
||||
} |
||||
|
||||
} // namespace devilution
|
||||
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @file controls/town_npc_nav.hpp |
||||
* |
||||
* Town NPC navigation for accessibility. |
||||
*/ |
||||
#pragma once |
||||
|
||||
namespace devilution { |
||||
|
||||
void SelectNextTownNpcKeyPressed(); |
||||
void SelectPreviousTownNpcKeyPressed(); |
||||
void GoToSelectedTownNpcKeyPressed(); |
||||
void SpeakSelectedTownNpc(); |
||||
void ListTownNpcsKeyPressed(); |
||||
void UpdateAutoWalkTownNpc(); |
||||
bool IsTownNpcActionAllowed(); |
||||
void ResetAutoWalkTownNpc(); |
||||
|
||||
} // namespace devilution
|
||||
Loading…
Reference in new issue