Browse Source

access: speak class + difficulty descriptions

pull/8474/head
mojsior 2 months ago
parent
commit
ff82e243b8
  1. 93
      Source/DiabloUI/diabloui.cpp
  2. 39
      Source/DiabloUI/diabloui.h
  3. 85
      Source/DiabloUI/hero/selhero.cpp
  4. 50
      Source/DiabloUI/multi/selgame.cpp
  5. 24
      Translations/pl.po

93
Source/DiabloUI/diabloui.cpp

@ -109,14 +109,16 @@ void (*gfnFullscreen)();
bool (*gfnListYesNo)();
std::vector<UiItemBase *> gUiItems;
UiList *gUiList = nullptr;
bool UiItemsWraps;
std::optional<TextInputState> UiTextInputState;
bool allowEmptyTextInput = false;
constexpr Uint32 ListDoubleClickTimeMs = 500;
std::size_t lastListClickIndex = static_cast<std::size_t>(-1);
Uint32 lastListClickTicks = 0;
bool UiItemsWraps;
std::optional<TextInputState> UiTextInputState;
bool allowEmptyTextInput = false;
std::optional<std::string> UiSpokenTextOverride;
constexpr Uint32 ListDoubleClickTimeMs = 500;
std::size_t lastListClickIndex = static_cast<std::size_t>(-1);
Uint32 lastListClickTicks = 0;
struct ScrollBarState {
bool upArrowPressed;
@ -155,14 +157,20 @@ std::string FormatSpokenText(const StringOrView &format, const std::vector<DrawS
return formatted;
}
void SpeakListItem(std::size_t index, bool force = false)
{
if (gUiList == nullptr || index > SelectedItemMax)
return;
const UiListItem *pItem = gUiList->GetItem(index);
if (pItem == nullptr)
return;
void SpeakListItem(std::size_t index, bool force = false)
{
if (gUiList == nullptr || index > SelectedItemMax)
return;
if (UiSpokenTextOverride) {
SpeakText(*UiSpokenTextOverride, force);
UiSpokenTextOverride = std::nullopt;
return;
}
const UiListItem *pItem = gUiList->GetItem(index);
if (pItem == nullptr)
return;
std::string text = FormatSpokenText(pItem->m_text, pItem->args);
@ -180,10 +188,10 @@ void SpeakListItem(std::size_t index, bool force = false)
if (!text.empty())
SpeakText(text, force);
}
void AdjustListOffset(std::size_t itemIndex)
{
}
void AdjustListOffset(std::size_t itemIndex)
{
if (itemIndex >= listOffset + ListViewportSize)
listOffset = itemIndex - (ListViewportSize - 1);
if (itemIndex < listOffset)
@ -232,13 +240,18 @@ void UiUpdateFadePalette()
SystemPaletteUpdated();
if (IsHardwareCursor()) ReinitializeHardwareCursor();
}
} // namespace
bool IsTextInputActive()
{
return UiTextInputState.has_value();
}
} // namespace
void UiSetSpokenTextOverride(std::string text)
{
UiSpokenTextOverride = std::move(text);
}
bool IsTextInputActive()
{
return UiTextInputState.has_value();
}
void UiInitList(void (*fnFocus)(size_t value), void (*fnSelect)(size_t value), void (*fnEsc)(), const std::vector<std::unique_ptr<UiItemBase>> &items, bool itemsWraps, void (*fnFullscreen)(), bool (*fnYesNo)(), size_t selectedItem /*= 0*/)
{
@ -365,19 +378,19 @@ void UiFocus(std::size_t itemIndex, bool checkUp, bool ignoreItemsWraps = false)
}
pItem = gUiList->GetItem(itemIndex);
}
SpeakListItem(itemIndex);
if (HasAnyOf(pItem->uiFlags, UiFlags::NeedsNextElement))
AdjustListOffset(itemIndex + 1);
AdjustListOffset(itemIndex);
SelectedItem = itemIndex;
UiPlayMoveSound();
if (gfnListFocus != nullptr)
gfnListFocus(itemIndex);
}
if (HasAnyOf(pItem->uiFlags, UiFlags::NeedsNextElement))
AdjustListOffset(itemIndex + 1);
AdjustListOffset(itemIndex);
SelectedItem = itemIndex;
UiPlayMoveSound();
if (gfnListFocus != nullptr)
gfnListFocus(itemIndex);
SpeakListItem(itemIndex);
}
void UiFocusUp()
{

39
Source/DiabloUI/diabloui.h

@ -1,13 +1,15 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#ifdef USE_SDL3
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_surface.h>
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#ifdef USE_SDL3
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_surface.h>
#else
#include <SDL.h>
#endif
@ -109,13 +111,16 @@ bool UiLoadBlackBackground();
void LoadBackgroundArt(const char *pszFile, int frames = 1);
void UiAddBackground(std::vector<std::unique_ptr<UiItemBase>> *vecDialog);
void UiAddLogo(std::vector<std::unique_ptr<UiItemBase>> *vecDialog, int y = GetUIRectangle().position.y);
void UiFocusNavigationSelect();
void UiFocusNavigationEsc();
void UiFocusNavigationYesNo();
void UiInitList(void (*fnFocus)(size_t value), void (*fnSelect)(size_t value), void (*fnEsc)(), const std::vector<std::unique_ptr<UiItemBase>> &items, bool wraps = false, void (*fnFullscreen)() = nullptr, bool (*fnYesNo)() = nullptr, size_t selectedItem = 0);
void UiRenderListItems();
void UiInitList_clear();
void UiFocusNavigationSelect();
void UiFocusNavigationEsc();
void UiFocusNavigationYesNo();
/** Overrides what the screen reader will speak for the next focused list item. */
void UiSetSpokenTextOverride(std::string text);
void UiInitList(void (*fnFocus)(size_t value), void (*fnSelect)(size_t value), void (*fnEsc)(), const std::vector<std::unique_ptr<UiItemBase>> &items, bool wraps = false, void (*fnFullscreen)() = nullptr, bool (*fnYesNo)() = nullptr, size_t selectedItem = 0);
void UiRenderListItems();
void UiInitList_clear();
void UiClearScreen();
void UiPollAndRender(std::optional<tl::function_ref<bool(SDL_Event &)>> eventHandler = std::nullopt);

85
Source/DiabloUI/hero/selhero.cpp

@ -65,23 +65,43 @@ std::vector<std::unique_ptr<UiItemBase>> vecSelHeroDialog;
std::vector<std::unique_ptr<UiListItem>> vecSelHeroDlgItems;
std::vector<std::unique_ptr<UiItemBase>> vecSelDlgItems;
UiImageClx *SELHERO_DIALOG_HERO_IMG;
void SelheroListFocus(size_t value);
void SelheroListSelect(size_t value);
void SelheroListEsc();
void SelheroLoadFocus(size_t value);
void SelheroLoadSelect(size_t value);
void SelheroNameSelect(size_t value);
void SelheroNameEsc();
void SelheroClassSelectorFocus(size_t value);
void SelheroClassSelectorSelect(size_t value);
void SelheroClassSelectorEsc();
const char *SelheroGenerateName(HeroClass heroClass);
void SelheroUiFocusNavigationYesNo()
{
if (selhero_isSavegame)
UiImageClx *SELHERO_DIALOG_HERO_IMG;
void SelheroListFocus(size_t value);
void SelheroListSelect(size_t value);
void SelheroListEsc();
void SelheroLoadFocus(size_t value);
void SelheroLoadSelect(size_t value);
void SelheroNameSelect(size_t value);
void SelheroNameEsc();
void SelheroClassSelectorFocus(size_t value);
void SelheroClassSelectorSelect(size_t value);
void SelheroClassSelectorEsc();
const char *SelheroGenerateName(HeroClass heroClass);
std::string_view HeroClassDescriptionForSpeech(HeroClass heroClass)
{
switch (heroClass) {
case HeroClass::Warrior:
return _("A powerful fighter who excels in melee combat.");
case HeroClass::Rogue:
return _("A nimble archer who excels at ranged combat.");
case HeroClass::Sorcerer:
return _("A master of arcane magic who casts powerful spells.");
case HeroClass::Monk:
return _("A holy warrior skilled in martial arts and staves.");
case HeroClass::Bard:
return _("A versatile fighter who blends melee and archery.");
case HeroClass::Barbarian:
return _("A fierce warrior who relies on brute strength.");
default:
return {};
}
}
void SelheroUiFocusNavigationYesNo()
{
if (selhero_isSavegame)
UiFocusNavigationYesNo();
}
@ -248,22 +268,31 @@ void SelheroListEsc()
selhero_result = SELHERO_PREVIOUS;
}
void SelheroClassSelectorFocus(size_t value)
{
const auto heroClass = static_cast<HeroClass>(vecSelHeroDlgItems[value]->m_value);
_uidefaultstats defaults;
gfnHeroStats(heroClass, &defaults);
void SelheroClassSelectorFocus(size_t value)
{
const auto heroClass = static_cast<HeroClass>(vecSelHeroDlgItems[value]->m_value);
_uidefaultstats defaults;
gfnHeroStats(heroClass, &defaults);
selhero_heroInfo.level = 1;
selhero_heroInfo.heroclass = heroClass;
selhero_heroInfo.strength = defaults.strength;
selhero_heroInfo.magic = defaults.magic;
selhero_heroInfo.dexterity = defaults.dexterity;
selhero_heroInfo.vitality = defaults.vitality;
SelheroSetStats();
}
selhero_heroInfo.dexterity = defaults.dexterity;
selhero_heroInfo.vitality = defaults.vitality;
SelheroSetStats();
const PlayerData &playerData = GetPlayerDataForClass(heroClass);
const std::string_view description = HeroClassDescriptionForSpeech(heroClass);
std::string spoken = std::string(_(playerData.className));
if (!description.empty()) {
spoken.append("\n");
spoken.append(description);
}
UiSetSpokenTextOverride(std::move(spoken));
}
bool ShouldPrefillHeroName()
{

50
Source/DiabloUI/multi/selgame.cpp

@ -409,24 +409,38 @@ void selgame_GameSelection_Esc()
selgame_endMenu = true;
}
void selgame_Diff_Focus(size_t value)
{
switch (vecSelGameDlgItems[value]->m_value) {
case DIFF_NORMAL:
CopyUtf8(selgame_Label, _("Normal"), sizeof(selgame_Label));
CopyUtf8(selgame_Description, _("Normal Difficulty\nThis is where a starting character should begin the quest to defeat Diablo."), sizeof(selgame_Description));
break;
case DIFF_NIGHTMARE:
CopyUtf8(selgame_Label, _("Nightmare"), sizeof(selgame_Label));
CopyUtf8(selgame_Description, _("Nightmare Difficulty\nThe denizens of the Labyrinth have been bolstered and will prove to be a greater challenge. This is recommended for experienced characters only."), sizeof(selgame_Description));
break;
case DIFF_HELL:
CopyUtf8(selgame_Label, _("Hell"), sizeof(selgame_Label));
CopyUtf8(selgame_Description, _("Hell Difficulty\nThe most powerful of the underworld's creatures lurk at the gateway into Hell. Only the most experienced characters should venture in this realm."), sizeof(selgame_Description));
break;
}
CopyUtf8(selgame_Description, WordWrapString(selgame_Description, DESCRIPTION_WIDTH), sizeof(selgame_Description));
}
void selgame_Diff_Focus(size_t value)
{
std::string_view tooltip;
switch (vecSelGameDlgItems[value]->m_value) {
case DIFF_NORMAL:
CopyUtf8(selgame_Label, _("Normal"), sizeof(selgame_Label));
tooltip = _("Normal Difficulty\nThis is where a starting character should begin the quest to defeat Diablo.");
CopyUtf8(selgame_Description, tooltip, sizeof(selgame_Description));
break;
case DIFF_NIGHTMARE:
CopyUtf8(selgame_Label, _("Nightmare"), sizeof(selgame_Label));
tooltip = _("Nightmare Difficulty\nThe denizens of the Labyrinth have been bolstered and will prove to be a greater challenge. This is recommended for experienced characters only.");
CopyUtf8(selgame_Description, tooltip, sizeof(selgame_Description));
break;
case DIFF_HELL:
CopyUtf8(selgame_Label, _("Hell"), sizeof(selgame_Label));
tooltip = _("Hell Difficulty\nThe most powerful of the underworld's creatures lurk at the gateway into Hell. Only the most experienced characters should venture in this realm.");
CopyUtf8(selgame_Description, tooltip, sizeof(selgame_Description));
break;
}
CopyUtf8(selgame_Description, WordWrapString(selgame_Description, DESCRIPTION_WIDTH), sizeof(selgame_Description));
std::string spoken = selgame_Label;
std::string_view spokenDescription = tooltip;
if (const size_t newlinePos = spokenDescription.find('\n'); newlinePos != std::string_view::npos)
spokenDescription = spokenDescription.substr(newlinePos + 1);
if (!spokenDescription.empty()) {
spoken.append("\n");
spoken.append(spokenDescription);
}
UiSetSpokenTextOverride(std::move(spoken));
}
bool IsDifficultyAllowed(int value)
{

24
Translations/pl.po

@ -5409,6 +5409,30 @@ msgstr "Barda"
msgid "Barbarian"
msgstr "Barbarzyńca"
#: Source/DiabloUI/hero/selhero.cpp:86
msgid "A powerful fighter who excels in melee combat."
msgstr "Potężny wojownik, który świetnie radzi sobie w walce wręcz."
#: Source/DiabloUI/hero/selhero.cpp:88
msgid "A nimble archer who excels at ranged combat."
msgstr "Zwinna łuczniczka, która świetnie radzi sobie w walce na dystans."
#: Source/DiabloUI/hero/selhero.cpp:90
msgid "A master of arcane magic who casts powerful spells."
msgstr "Mistrz magii tajemnej, który rzuca potężne zaklęcia."
#: Source/DiabloUI/hero/selhero.cpp:92
msgid "A holy warrior skilled in martial arts and staves."
msgstr "Święty wojownik biegły w sztukach walki i władaniu kosturami."
#: Source/DiabloUI/hero/selhero.cpp:94
msgid "A versatile fighter who blends melee and archery."
msgstr "Wszechstronna wojowniczka, która łączy walkę wręcz i łucznictwo."
#: Source/DiabloUI/hero/selhero.cpp:96
msgid "A fierce warrior who relies on brute strength."
msgstr "Zaciekły wojownik, który polega na brutalnej sile."
#: Source/translation_dummy.cpp:17
msgctxt "monster"
msgid "Zombie"

Loading…
Cancel
Save