Browse Source

access: unique stats, item quality, Lazarus exit, stash focus

pull/8474/head
mojsior 1 month ago
parent
commit
eff6150ab0
  1. 54
      Source/controls/plrctrls.cpp
  2. 1
      Source/controls/plrctrls.h
  3. 249
      Source/controls/tracker.cpp
  4. 10
      Source/diablo.cpp
  5. 29
      Source/items.cpp
  6. 18
      Translations/pl.po

54
Source/controls/plrctrls.cpp

@ -2254,18 +2254,48 @@ void InvalidateInventorySlot()
/**
* @brief Moves the mouse to the first inventory slot.
*/
void FocusOnInventory()
{
Slot = SLOTXY_INV_FIRST;
ResetInvCursorPosition();
SpeakInventorySlotForAccessibility();
}
void InventoryMoveFromKeyboard(AxisDirection dir)
{
if (!invflag)
return;
void FocusOnInventory()
{
Slot = SLOTXY_INV_FIRST;
ResetInvCursorPosition();
SpeakInventorySlotForAccessibility();
}
void ToggleStashFocus()
{
if (!IsStashOpen || MyPlayer == nullptr)
return;
const Item &holdItem = MyPlayer->HoldItem;
// If currently focused on inventory/belt, jump to stash. Otherwise jump back to inventory.
if (Slot >= 0) {
BeltReturnsToStash = false;
Slot = -1;
ActiveStashSlot = FindClosestStashSlot(MousePosition);
if (ActiveStashSlot == InvalidStashPoint)
ActiveStashSlot = { 0, 0 };
Point mousePos = GetStashSlotCoord(ActiveStashSlot);
Size itemSize = holdItem.isEmpty() ? Size { 1, 1 } : GetInventorySize(holdItem);
mousePos += Displacement { itemSize.width * INV_SLOT_HALF_SIZE_PX, itemSize.height * INV_SLOT_HALF_SIZE_PX };
SetCursorPos(mousePos);
SpeakText(_("Stash"), /*force=*/true);
return;
}
BeltReturnsToStash = false;
ActiveStashSlot = InvalidStashPoint;
Slot = FindClosestInventorySlot(MousePosition, holdItem);
ResetInvCursorPosition();
SpeakInventorySlotForAccessibility();
}
void InventoryMoveFromKeyboard(AxisDirection dir)
{
if (!invflag)
return;
CheckInventoryMove(dir);
}

1
Source/controls/plrctrls.h

@ -71,6 +71,7 @@ void UpdateSpellTarget(SpellID spell);
bool TryDropItem();
void InvalidateInventorySlot();
void FocusOnInventory();
void ToggleStashFocus();
void InventoryMoveFromKeyboard(AxisDirection dir);
void HotSpellMove(AxisDirection dir);
void PerformSpellAction();

249
Source/controls/tracker.cpp

@ -280,17 +280,51 @@ struct TrackerCandidate {
StringOrView name;
};
[[nodiscard]] bool IsBetterTrackerCandidate(const TrackerCandidate &a, const TrackerCandidate &b)
{
if (a.distance != b.distance)
return a.distance < b.distance;
return a.id < b.id;
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyItemTrackerCandidates(Point playerPosition, int maxDistance)
{
std::vector<TrackerCandidate> result;
result.reserve(ActiveItemCount);
[[nodiscard]] bool IsBetterTrackerCandidate(const TrackerCandidate &a, const TrackerCandidate &b)
{
if (a.distance != b.distance)
return a.distance < b.distance;
return a.id < b.id;
}
[[nodiscard]] constexpr int RedPortalTrackerIdForPosition(Point position)
{
// Encode tile position into a stable negative id.
// MAXDUNX/MAXDUNY are 112, so this easily fits in int.
return -((position.y * MAXDUNX) + position.x + 1);
}
[[nodiscard]] constexpr bool IsRedPortalTrackerId(int id)
{
return id < 0;
}
[[nodiscard]] constexpr Point RedPortalPositionForTrackerId(int id)
{
const int encoded = -id - 1;
return { encoded % MAXDUNX, encoded / MAXDUNX };
}
[[nodiscard]] StringOrView ItemLabelForSpeech(const Item &item)
{
const StringOrView name = item.getName();
if (name.empty())
return name;
switch (item._iMagical) {
case ITEM_QUALITY_MAGIC:
return StrCat(name, ", ", _("magic item"));
case ITEM_QUALITY_UNIQUE:
return StrCat(name, ", ", _("unique item"));
default:
return name;
}
}
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyItemTrackerCandidates(Point playerPosition, int maxDistance)
{
std::vector<TrackerCandidate> result;
result.reserve(ActiveItemCount);
const int minX = std::max(0, playerPosition.x - maxDistance);
const int minY = std::max(0, playerPosition.y - maxDistance);
@ -316,14 +350,14 @@ struct TrackerCandidate {
if (distance > maxDistance)
continue;
result.push_back(TrackerCandidate {
.id = itemId,
.distance = distance,
.name = item.getName(),
});
}
}
result.push_back(TrackerCandidate {
.id = itemId,
.distance = distance,
.name = ItemLabelForSpeech(item),
});
}
}
std::sort(result.begin(), result.end(), IsBetterTrackerCandidate);
return result;
}
@ -644,11 +678,11 @@ template <typename Predicate>
return result;
}
for (int i = 0; i < numtrigs; ++i) {
const TriggerStruct &trigger = trigs[i];
if (setlevel) {
if (trigger._tmsg != WM_DIABRTNLVL)
continue;
for (int i = 0; i < numtrigs; ++i) {
const TriggerStruct &trigger = trigs[i];
if (setlevel) {
if (trigger._tmsg != WM_DIABRTNLVL)
continue;
} else {
if (!IsAnyOf(trigger._tmsg, WM_DIABPREVLVL, WM_DIABTWARPUP))
continue;
@ -659,13 +693,31 @@ template <typename Predicate>
result.push_back(TrackerCandidate {
.id = i,
.distance = distance,
.name = TriggerLabelForSpeech(trigger),
});
}
std::sort(result.begin(), result.end(), IsBetterTrackerCandidate);
return result;
}
.name = TriggerLabelForSpeech(trigger),
});
}
// Lazarus' set level (SL_VILEBETRAYER) uses a RedPortal missile instead of a return trigger.
// Include it so the player can navigate out like other quest levels.
if (setlevel) {
for (const Missile &missile : Missiles) {
if (missile._mitype != MissileID::RedPortal)
continue;
const Point portalPosition = missile.position.tile;
if (!InDungeonBounds(portalPosition))
continue;
const int distance = playerPosition.WalkingDistance(portalPosition);
result.push_back(TrackerCandidate {
.id = RedPortalTrackerIdForPosition(portalPosition),
.distance = distance,
.name = _("Red portal"),
});
}
}
std::sort(result.begin(), result.end(), IsBetterTrackerCandidate);
return result;
}
[[nodiscard]] std::optional<Point> FindTownPortalPositionInTownByPortalIndex(int portalIndex)
{
@ -774,24 +826,39 @@ template <typename Predicate>
if (MyPlayer == nullptr || leveltype == DTYPE_TOWN)
return result;
if (setlevel) {
for (int i = 0; i < numtrigs; ++i) {
const TriggerStruct &trigger = trigs[i];
if (trigger._tmsg != WM_DIABRTNLVL)
continue;
if (setlevel) {
for (int i = 0; i < numtrigs; ++i) {
const TriggerStruct &trigger = trigs[i];
if (trigger._tmsg != WM_DIABRTNLVL)
continue;
const Point triggerPosition { trigger.position.x, trigger.position.y };
const int distance = playerPosition.WalkingDistance(triggerPosition);
result.push_back(TrackerCandidate {
.id = i,
.distance = distance,
.name = TriggerLabelForSpeech(trigger),
});
}
std::sort(result.begin(), result.end(), IsBetterTrackerCandidate);
return result;
}
.name = TriggerLabelForSpeech(trigger),
});
}
// Lazarus' set level (SL_VILEBETRAYER) uses a RedPortal missile instead of a return trigger.
for (const Missile &missile : Missiles) {
if (missile._mitype != MissileID::RedPortal)
continue;
const Point portalPosition = missile.position.tile;
if (!InDungeonBounds(portalPosition))
continue;
const int distance = playerPosition.WalkingDistance(portalPosition);
result.push_back(TrackerCandidate {
.id = RedPortalTrackerIdForPosition(portalPosition),
.distance = distance,
.name = _("Red portal"),
});
}
std::sort(result.begin(), result.end(), IsBetterTrackerCandidate);
return result;
}
constexpr size_t NumQuests = sizeof(Quests) / sizeof(Quests[0]);
result.reserve(NumQuests);
@ -1977,10 +2044,10 @@ void NavigateToTrackerTargetKeyPressed()
}
break;
}
case TrackerTargetCategory::DungeonEntrances: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectDungeonEntranceTrackerCandidates(playerPosition);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
case TrackerTargetCategory::DungeonEntrances: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectDungeonEntranceTrackerCandidates(playerPosition);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) {
if (nearbyCandidates.empty())
SpeakText(_("No dungeon entrances found."), true);
@ -1988,32 +2055,35 @@ void NavigateToTrackerTargetKeyPressed()
SpeakText(_("No next dungeon entrance."), true);
return;
}
} else if (lockedTargetId >= 0 && lockedTargetId < numtrigs) {
targetId = lockedTargetId;
} else if (!nearbyCandidates.empty()) {
targetId = nearbyCandidates.front().id;
}
if (!targetId) {
SpeakText(_("No dungeon entrances found."), true);
return;
}
} else if (!nearbyCandidates.empty()) {
const auto lockedIt = std::find_if(nearbyCandidates.begin(), nearbyCandidates.end(), [id = lockedTargetId](const TrackerCandidate &c) { return c.id == id; });
targetId = lockedIt != nearbyCandidates.end() ? lockedTargetId : nearbyCandidates.front().id;
}
if (!targetId) {
SpeakText(_("No dungeon entrances found."), true);
return;
}
const auto it = std::find_if(nearbyCandidates.begin(), nearbyCandidates.end(), [id = *targetId](const TrackerCandidate &c) { return c.id == id; });
if (it == nearbyCandidates.end()) {
lockedTargetId = -1;
SpeakText(_("No dungeon entrances found."), true);
return;
}
lockedTargetId = *targetId;
targetName = TriggerLabelForSpeech(trigs[*targetId]);
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
const TriggerStruct &trigger = trigs[*targetId];
targetPosition = Point { trigger.position.x, trigger.position.y };
}
break;
}
}
lockedTargetId = *targetId;
targetName = std::string(it->name.str());
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
if (IsRedPortalTrackerId(*targetId)) {
targetPosition = RedPortalPositionForTrackerId(*targetId);
} else {
const TriggerStruct &trigger = trigs[*targetId];
targetPosition = Point { trigger.position.x, trigger.position.y };
}
}
break;
}
case TrackerTargetCategory::Stairs: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectStairsTrackerCandidates(playerPosition);
if (cycleTarget) {
@ -2051,10 +2121,10 @@ void NavigateToTrackerTargetKeyPressed()
}
break;
}
case TrackerTargetCategory::QuestLocations: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectQuestLocationTrackerCandidates(playerPosition);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
case TrackerTargetCategory::QuestLocations: {
const std::vector<TrackerCandidate> nearbyCandidates = CollectQuestLocationTrackerCandidates(playerPosition);
if (cycleTarget) {
targetId = FindNextTrackerCandidateId(nearbyCandidates, lockedTargetId);
if (!targetId) {
if (nearbyCandidates.empty())
SpeakText(_("No quest locations found."), true);
@ -2062,14 +2132,13 @@ void NavigateToTrackerTargetKeyPressed()
SpeakText(_("No next quest location."), true);
return;
}
} else if ((setlevel && lockedTargetId >= 0 && lockedTargetId < numtrigs) || (!setlevel && lockedTargetId >= 0 && lockedTargetId < static_cast<int>(sizeof(Quests) / sizeof(Quests[0])))) {
targetId = lockedTargetId;
} else if (!nearbyCandidates.empty()) {
targetId = nearbyCandidates.front().id;
}
if (!targetId) {
SpeakText(_("No quest locations found."), true);
return;
} else if (!nearbyCandidates.empty()) {
const auto lockedIt = std::find_if(nearbyCandidates.begin(), nearbyCandidates.end(), [id = lockedTargetId](const TrackerCandidate &c) { return c.id == id; });
targetId = lockedIt != nearbyCandidates.end() ? lockedTargetId : nearbyCandidates.front().id;
}
if (!targetId) {
SpeakText(_("No quest locations found."), true);
return;
}
const auto it = std::find_if(nearbyCandidates.begin(), nearbyCandidates.end(), [id = *targetId](const TrackerCandidate &c) { return c.id == id; });
@ -2081,15 +2150,19 @@ void NavigateToTrackerTargetKeyPressed()
lockedTargetId = *targetId;
targetName = std::string(it->name.str());
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
if (setlevel) {
const TriggerStruct &trigger = trigs[*targetId];
targetPosition = Point { trigger.position.x, trigger.position.y };
} else {
const Quest &quest = Quests[static_cast<size_t>(*targetId)];
targetPosition = quest.position;
}
DecorateTrackerTargetNameWithOrdinalIfNeeded(*targetId, targetName, nearbyCandidates);
if (!cycleTarget) {
if (setlevel) {
if (IsRedPortalTrackerId(*targetId)) {
targetPosition = RedPortalPositionForTrackerId(*targetId);
} else {
const TriggerStruct &trigger = trigs[*targetId];
targetPosition = Point { trigger.position.x, trigger.position.y };
}
} else {
const Quest &quest = Quests[static_cast<size_t>(*targetId)];
targetPosition = quest.position;
}
}
break;
}

10
Source/diablo.cpp

@ -1906,7 +1906,15 @@ void InitKeymapActions()
SDLK_TAB,
DoAutoMap,
nullptr,
IsGameRunning);
[]() { return IsGameRunning() && !IsStashOpen; });
options.Keymapper.AddAction(
"ToggleStashFocus",
N_("Toggle stash focus"),
N_("Tab: switches between inventory and stash."),
SDLK_TAB,
ToggleStashFocus,
nullptr,
[]() { return IsStashOpen && !InGameMenu() && !ChatLogFlag; });
options.Keymapper.AddAction(
"CycleAutomapType",
N_("Cycle map type"),

29
Source/items.cpp

@ -4139,16 +4139,25 @@ void PrintItemDetails(const Item &item)
if (item._iPrePower != -1) {
AddItemInfoBoxString(PrintItemPower(item._iPrePower, item));
}
if (item._iSufPower != -1) {
AddItemInfoBoxString(PrintItemPower(item._iSufPower, item));
}
if (item._iMagical == ITEM_QUALITY_UNIQUE) {
AddItemInfoBoxString(_("unique item"));
ShowUniqueItemInfoBox = true;
curruitem = item;
}
PrintItemInfo(item);
}
if (item._iSufPower != -1) {
AddItemInfoBoxString(PrintItemPower(item._iSufPower, item));
}
if (item._iMagical == ITEM_QUALITY_MAGIC) {
AddItemInfoBoxString(_("magic item"));
}
if (item._iMagical == ITEM_QUALITY_UNIQUE) {
AddItemInfoBoxString(_("unique item"));
const UniqueItem &uitem = UniqueItems[item._iUid];
for (const auto &power : uitem.powers) {
if (power.type == IPL_INVALID)
break;
AddItemInfoBoxString(PrintItemPower(power.type, item));
}
ShowUniqueItemInfoBox = true;
curruitem = item;
}
PrintItemInfo(item);
}
void PrintItemDur(const Item &item)
{

18
Translations/pl.po

@ -1544,6 +1544,12 @@ msgid "Toggles if automap is displayed."
msgstr "Przełącza, czy wyświetlana jest automapa."
#: Source/diablo.cpp:1903
msgid "Toggle stash focus"
msgstr "Przełącz fokus skrytki"
msgid "Tab: switches between inventory and stash."
msgstr "Tab: przełącza między ekwipunkiem a skrytką."
msgid "Cycle map type"
msgstr "Przełącz typ mapy"
@ -3347,6 +3353,10 @@ msgstr "Ładunki: {:d}/{:d}"
msgid "unique item"
msgstr "unikat"
#: Source/items.cpp:4146 Source/controls/tracker.cpp:316
msgid "magic item"
msgstr "magiczny"
#: Source/items.cpp:4167 Source/items.cpp:4175 Source/items.cpp:4181
msgid "Not Identified"
msgstr "Nie zidentyfikowano"
@ -3457,6 +3467,10 @@ msgstr "Do Krypty - poziom {:d}"
msgid "Back to Level {:d}"
msgstr "Wróć na poziom {:d}"
#: Source/controls/tracker.cpp:713 Source/controls/tracker.cpp:855
msgid "Red portal"
msgstr "czerwony portal"
#: Source/loadsave.cpp:2013 Source/loadsave.cpp:2470
msgid "Unable to open save file archive"
msgstr "Nie można otworzyć pliku zapisu"
@ -3477,6 +3491,10 @@ msgstr ""
"Nieprawidłowy rozmiar skrytki. Jeśli spróbujesz uzyskać dostęp do skrytki, "
"dane zostaną nadpisane!!"
#: Source/controls/plrctrls.cpp:2283
msgid "Stash"
msgstr "skrytka"
#: Source/loadsave.cpp:2474
msgid "Invalid save file"
msgstr "Nieprawidłowy plik zapisu"

Loading…
Cancel
Save