Browse Source

Accessibility fixes for spells/stash/tracker; release 1.4.0

pull/8474/head
mojsior 7 days ago
parent
commit
5fc37119ef
  1. 42
      Source/controls/accessibility_keys.cpp
  2. 11
      Source/controls/plrctrls.cpp
  3. 13
      Source/controls/tracker.cpp
  4. 12
      Source/diablo.cpp
  5. 120
      Source/panels/spell_list.cpp
  6. 28
      Source/panels/spell_list.hpp
  7. 2
      VERSION

42
Source/controls/accessibility_keys.cpp

@ -258,16 +258,20 @@ void QuestLogKeyPressed()
CloseStash();
}
void SpeakSelectedSpeedbookSpell()
{
for (const auto &spellListItem : GetSpellListItems()) {
if (spellListItem.isSelected) {
SpeakText(pgettext("spell", GetSpellData(spellListItem.id).sNameText), /*force=*/true);
return;
}
}
SpeakText(_("No spell selected."), /*force=*/true);
}
void SpeakSelectedSpeedbookSpell()
{
if (MyPlayer == nullptr)
return;
const Player &player = *MyPlayer;
for (const auto &spellListItem : GetSpellListItems()) {
if (spellListItem.isSelected) {
SpeakText(BuildSpellDetailsForSpeech(player, spellListItem.id, spellListItem.type), /*force=*/true);
return;
}
}
SpeakText(_("No spell selected."), /*force=*/true);
}
void DisplaySpellsKeyPressed()
{
@ -290,15 +294,15 @@ void SpellBookKeyPressed()
{
if (IsPlayerInStore())
return;
SpellbookFlag = !SpellbookFlag;
if (SpellbookFlag && MyPlayer != nullptr) {
const Player &player = *MyPlayer;
if (IsValidSpell(player._pRSpell)) {
SpeakText(pgettext("spell", GetSpellData(player._pRSpell).sNameText), /*force=*/true);
} else {
SpeakText(_("No spell selected."), /*force=*/true);
}
}
SpellbookFlag = !SpellbookFlag;
if (SpellbookFlag && MyPlayer != nullptr) {
const Player &player = *MyPlayer;
if (IsValidSpell(player._pRSpell)) {
SpeakText(BuildSpellDetailsForSpeech(player, player._pRSpell, player._pRSplType), /*force=*/true);
} else {
SpeakText(_("No spell selected."), /*force=*/true);
}
}
if (!IsLeftPanelOpen() && CanPanelsCoverView()) {
if (!SpellbookFlag) { // We closed the spellbook
if (MousePosition.x < 480 && MousePosition.y < GetMainPanel().position.y) {

11
Source/controls/plrctrls.cpp

@ -850,21 +850,26 @@ void SpeakStashSlotForAccessibility()
return;
}
const int row = ActiveStashSlot.y + 1;
const int column = ActiveStashSlot.x + 1;
const int cell = ActiveStashSlot.y * StashGridSize.width + ActiveStashSlot.x + 1;
const std::string positionInfo = fmt::format("Row {}, Column {}, Cell {}: ", row, column, cell);
const StashStruct::StashCell itemId = Stash.GetItemIdAtPosition(ActiveStashSlot);
if (itemId != StashStruct::EmptyCell) {
const Item &item = Stash.stashList[itemId];
if (!item.isEmpty()) {
if (item._itype == ItemType::Gold) {
const int nGold = item._ivalue;
SpeakText(fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)), /*force=*/true);
SpeakText(StrCat(positionInfo, fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold))), /*force=*/true);
} else {
SpeakText(item.getName(), /*force=*/true);
SpeakText(StrCat(positionInfo, item.getName()), /*force=*/true);
}
return;
}
}
SpeakText(_("empty"), /*force=*/true);
SpeakText(StrCat(positionInfo, _("empty")), /*force=*/true);
}
/**

13
Source/controls/tracker.cpp

@ -454,12 +454,13 @@ struct TrackerCandidate {
return true;
}
[[nodiscard]] bool IsTrackedMonster(const Monster &monster)
{
return !monster.isInvalid
&& (monster.flags & MFLAG_HIDDEN) == 0
&& monster.hitPoints > 0;
}
[[nodiscard]] bool IsTrackedMonster(const Monster &monster)
{
return !monster.isInvalid
&& (monster.flags & MFLAG_HIDDEN) == 0
&& monster.hitPoints > 0
&& !(monster.type().type == MT_GOLEM && monster.position.tile == GolemHoldingCell);
}
template <typename Predicate>
[[nodiscard]] std::vector<TrackerCandidate> CollectNearbyObjectTrackerCandidates(Point playerPosition, int maxDistance, Predicate predicate)

12
Source/diablo.cpp

@ -621,9 +621,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState)
} else if (SpellbookFlag && MyPlayer != nullptr && !IsInspectingPlayer()) {
const Player &player = *MyPlayer;
if (IsValidSpell(player._pRSpell)) {
std::string msg;
StrAppend(msg, _("Selected: "), pgettext("spell", GetSpellData(player._pRSpell).sNameText));
SpeakText(msg, /*force=*/true);
SpeakText(StrCat(_("Selected: "), BuildSpellDetailsForSpeech(player, player._pRSpell, player._pRSplType)), /*force=*/true);
} else {
SpeakText(_("No spell selected."), /*force=*/true);
}
@ -655,7 +653,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState)
: SpellType::Spell;
UpdateSpellTarget(*next);
RedrawEverything();
SpeakText(pgettext("spell", GetSpellData(*next).sNameText), /*force=*/true);
SpeakText(BuildSpellDetailsForSpeech(*MyPlayer, *next, MyPlayer->_pRSplType), /*force=*/true);
}
} else if (invflag) {
InventoryMoveFromKeyboard({ AxisDirectionX_NONE, AxisDirectionY_UP });
@ -688,7 +686,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState)
: SpellType::Spell;
UpdateSpellTarget(*next);
RedrawEverything();
SpeakText(pgettext("spell", GetSpellData(*next).sNameText), /*force=*/true);
SpeakText(BuildSpellDetailsForSpeech(*MyPlayer, *next, MyPlayer->_pRSplType), /*force=*/true);
}
} else if (invflag) {
InventoryMoveFromKeyboard({ AxisDirectionX_NONE, AxisDirectionY_DOWN });
@ -729,7 +727,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState)
: SpellType::Spell;
UpdateSpellTarget(*first);
RedrawEverything();
SpeakText(pgettext("spell", GetSpellData(*first).sNameText), /*force=*/true);
SpeakText(BuildSpellDetailsForSpeech(*MyPlayer, *first, MyPlayer->_pRSplType), /*force=*/true);
}
}
} else if (invflag) {
@ -756,7 +754,7 @@ void PressKey(SDL_Keycode vkey, uint16_t modState)
: SpellType::Spell;
UpdateSpellTarget(*first);
RedrawEverything();
SpeakText(pgettext("spell", GetSpellData(*first).sNameText), /*force=*/true);
SpeakText(BuildSpellDetailsForSpeech(*MyPlayer, *first, MyPlayer->_pRSplType), /*force=*/true);
}
}
} else if (invflag) {

120
Source/panels/spell_list.cpp

@ -1,8 +1,9 @@
#include "panels/spell_list.hpp"
#include <cstdint>
#include <fmt/format.h>
#include "panels/spell_list.hpp"
#include <cstdint>
#include <string>
#include <fmt/format.h>
#include "control/control.hpp"
#include "controls/control_mode.hpp"
@ -11,8 +12,9 @@
#include "engine/palette.h"
#include "engine/render/primitive_render.hpp"
#include "engine/render/text_render.hpp"
#include "inv_iterators.hpp"
#include "options.h"
#include "inv_iterators.hpp"
#include "missiles.h"
#include "options.h"
#include "panels/spell_icons.hpp"
#include "player.h"
#include "spells.h"
@ -67,7 +69,7 @@ bool GetSpellListSelection(SpellID &pSpell, SpellType &pSplType)
return false;
}
std::optional<std::string_view> GetHotkeyName(SpellID spellId, SpellType spellType, bool useShortName = false)
std::optional<std::string_view> GetHotkeyName(SpellID spellId, SpellType spellType, bool useShortName = false)
{
const Player &myPlayer = *MyPlayer;
for (size_t t = 0; t < NumHotkeys; t++) {
@ -78,13 +80,99 @@ std::optional<std::string_view> GetHotkeyName(SpellID spellId, SpellType spellTy
return GetOptions().Padmapper.InputNameForAction(quickSpellActionKey, useShortName);
return GetOptions().Keymapper.KeyNameForAction(quickSpellActionKey);
}
return {};
}
} // namespace
void DrawSpell(const Surface &out)
{
return {};
}
std::string GetSpellPowerTextForSpeech(SpellID spell, int spellLevel)
{
if (spellLevel == 0) {
return {};
}
if (spell == SpellID::BoneSpirit) {
return std::string(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Dmg: 1/3 target hp"));
}
const auto [min, max] = GetDamageAmt(spell, spellLevel);
if (min == -1) {
return {};
}
if (spell == SpellID::Healing || spell == SpellID::HealOther) {
return fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}")), min, max);
}
return fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Damage: {:d} - {:d}")), min, max);
}
void AppendSpellSpeechSegment(std::string &speech, std::string_view segment)
{
if (segment.empty()) {
return;
}
if (!speech.empty()) {
StrAppend(speech, ", ");
}
StrAppend(speech, segment);
}
} // namespace
std::string BuildSpellDetailsForSpeech(const Player &player, SpellID spellId, SpellType spellType)
{
if (!IsValidSpell(spellId)) {
return std::string(_("No spell selected."));
}
if (spellId == GetPlayerStartingLoadoutForClass(player._pClass).skill) {
spellType = SpellType::Skill;
}
const std::string_view spellName = pgettext("spell", GetSpellData(spellId).sNameText);
std::string speech;
switch (spellType) {
case SpellType::Skill:
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(_("{:s} Skill")), spellName));
break;
case SpellType::Spell: {
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(_("{:s} Spell")), spellName));
if (spellId == SpellID::HolyBolt) {
AppendSpellSpeechSegment(speech, _("Damages undead only"));
}
const int spellLevel = player.GetSpellLevel(spellId);
AppendSpellSpeechSegment(speech,
spellLevel == 0 ? std::string(_("Spell Level 0 - Unusable")) : fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel));
if (spellLevel > 0) {
const std::string power = GetSpellPowerTextForSpeech(spellId, spellLevel);
if (!power.empty()) {
AppendSpellSpeechSegment(speech, power);
}
const int mana = GetManaAmount(player, spellId) >> 6;
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(pgettext("spellbook", "Mana: {:d}")), mana));
}
} break;
case SpellType::Scroll: {
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(_("Scroll of {:s}")), spellName));
const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { player }, [spellId](const Item &item) {
return item.isScrollOf(spellId);
});
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount));
} break;
case SpellType::Charges: {
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(_("Staff of {:s}")), spellName));
const int charges = player.InvBody[INVLOC_HAND_LEFT]._iCharges;
AppendSpellSpeechSegment(speech, fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", charges)), charges));
} break;
case SpellType::Invalid:
AppendSpellSpeechSegment(speech, spellName);
break;
}
if (speech.empty()) {
speech = spellName;
}
return speech;
}
void DrawSpell(const Surface &out)
{
const Player &myPlayer = *MyPlayer;
SpellID spl = myPlayer._pRSpell;
SpellType st = myPlayer._pRSplType;
@ -329,7 +417,7 @@ void ToggleSpell(size_t slot)
myPlayer._pRSpell = myPlayer._pSplHotKey[slot];
myPlayer._pRSplType = myPlayer._pSplTHotKey[slot];
RedrawEverything();
SpeakText(pgettext("spell", GetSpellData(myPlayer._pRSpell).sNameText), /*force=*/true);
SpeakText(BuildSpellDetailsForSpeech(myPlayer, myPlayer._pRSpell, myPlayer._pRSplType), /*force=*/true);
}
}

28
Source/panels/spell_list.hpp

@ -1,7 +1,8 @@
#pragma once
#include <cstddef>
#include <vector>
#pragma once
#include <cstddef>
#include <string>
#include <vector>
#include "engine/point.hpp"
#include "engine/surface.hpp"
@ -9,12 +10,14 @@
namespace devilution {
struct SpellListItem {
Point location;
SpellType type;
SpellID id;
bool isSelected;
};
struct SpellListItem {
Point location;
SpellType type;
SpellID id;
bool isSelected;
};
struct Player;
/**
* @brief draws the current right mouse button spell.
@ -25,8 +28,9 @@ void DrawSpellList(const Surface &out);
std::vector<SpellListItem> GetSpellListItems();
void SetSpell();
void SetSpeedSpell(size_t slot);
bool IsValidSpeedSpell(size_t slot);
void ToggleSpell(size_t slot);
bool IsValidSpeedSpell(size_t slot);
void ToggleSpell(size_t slot);
std::string BuildSpellDetailsForSpeech(const Player &player, SpellID spellId, SpellType spellType);
/**
* Draws the "Speed Book": the rows of known spells for quick-setting a spell that

2
VERSION

@ -1 +1 @@
1.3.1
1.4.0

Loading…
Cancel
Save