Browse Source

Change Keymapper to OptionCategory/OptionEntry

pull/3833/head
obligaron 4 years ago committed by Anders Jenbo
parent
commit
419fe7b7ec
  1. 1
      Source/CMakeLists.txt
  2. 3
      Source/control.cpp
  3. 133
      Source/controls/keymapper.cpp
  4. 69
      Source/controls/keymapper.hpp
  5. 137
      Source/diablo.cpp
  6. 2
      Source/diablo.h
  7. 1
      Source/loadsave.cpp
  8. 204
      Source/options.cpp
  9. 59
      Source/options.h
  10. 17
      Source/panels/spell_list.cpp

1
Source/CMakeLists.txt

@ -81,7 +81,6 @@ set(libdevilutionx_SRCS
controls/menu_controls.cpp
controls/modifier_hints.cpp
controls/plrctrls.cpp
controls/keymapper.cpp
engine/animationinfo.cpp
engine/demomode.cpp
engine/load_cel.cpp

3
Source/control.cpp

@ -14,7 +14,6 @@
#include "DiabloUI/art.h"
#include "DiabloUI/art_draw.h"
#include "automap.h"
#include "controls/keymapper.hpp"
#include "cursor.h"
#include "engine/cel_sprite.hpp"
#include "engine/load_cel.hpp"
@ -93,8 +92,6 @@ const Rectangle &GetRightPanel()
return RightPanel;
}
extern std::array<Keymapper::ActionIndex, 4> quickSpellActionIndexes;
/** Maps from attribute_id to the rectangle on screen used for attribute increment buttons. */
Rectangle ChrBtnsRect[4] = {
{ { 137, 138 }, { 41, 22 } },

133
Source/controls/keymapper.cpp

@ -1,133 +0,0 @@
#include "keymapper.hpp"
#include <cassert>
#include <fmt/format.h>
#include <utility>
#include <SDL.h>
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#endif
#include "control.h"
#include "options.h"
#include "utils/log.hpp"
namespace devilution {
Keymapper::Keymapper()
{
// Insert all supported keys: a-z, 0-9 and F1-F12.
keyIDToKeyName.reserve(('Z' - 'A' + 1) + ('9' - '0' + 1) + 12);
for (char c = 'A'; c <= 'Z'; ++c) {
keyIDToKeyName.emplace(c, std::string(1, c));
}
for (char c = '0'; c <= '9'; ++c) {
keyIDToKeyName.emplace(c, std::string(1, c));
}
for (int i = 0; i < 12; ++i) {
keyIDToKeyName.emplace(DVL_VK_F1 + i, fmt::format("F{}", i + 1));
}
keyNameToKeyID.reserve(keyIDToKeyName.size());
for (const auto &kv : keyIDToKeyName) {
keyNameToKeyID.emplace(kv.second, kv.first);
}
}
Keymapper::ActionIndex Keymapper::AddAction(const Action &action)
{
actions.emplace_back(action);
return actions.size() - 1;
}
void Keymapper::KeyPressed(int key) const
{
auto it = keyIDToAction.find(key);
if (it == keyIDToAction.end())
return; // Ignore unmapped keys.
const auto &action = it->second;
// Check that the action can be triggered and that the chat textbox is not
// open.
if (!action.get().enable() || talkflag)
return;
action();
}
std::string Keymapper::KeyNameForAction(ActionIndex actionIndex) const
{
assert(actionIndex < actions.size());
auto key = actions[actionIndex].key;
auto it = keyIDToKeyName.find(key);
assert(it != keyIDToKeyName.end());
return it->second;
}
void Keymapper::Save() const
{
// Use the action vector to go though the actions to keep the same order.
for (const auto &action : actions) {
if (action.key == DVL_VK_INVALID) {
// Just add an empty config entry if the action is unbound.
SetIniValue("Keymapping", action.name.c_str(), "");
continue;
}
auto keyNameIt = keyIDToKeyName.find(action.key);
if (keyNameIt == keyIDToKeyName.end()) {
Log("Keymapper: no name found for key '{}'", action.key);
continue;
}
SetIniValue("Keymapping", action.name.c_str(), keyNameIt->second.c_str());
}
}
void Keymapper::Load()
{
keyIDToAction.clear();
for (auto &action : actions) {
auto key = GetActionKey(action);
action.key = key;
if (key == DVL_VK_INVALID) {
// Skip if the action has no key bound to it.
continue;
}
// Store the key in action.key and in the map so we can save() the
// actions while keeping the same order as they have been added.
keyIDToAction.emplace(key, action);
}
}
int Keymapper::GetActionKey(const Keymapper::Action &action)
{
std::array<char, 64> result;
if (!GetIniValue("Keymapping", action.name.c_str(), result.data(), result.size()))
return action.defaultKey; // Return the default key if no key has been set.
std::string key = result.data();
if (key.empty())
return DVL_VK_INVALID;
auto keyIt = keyNameToKeyID.find(key);
if (keyIt == keyNameToKeyID.end()) {
// Return the default key if the key is unknown.
Log("Keymapper: unknown key '{}'", key);
return action.defaultKey;
}
auto it = keyIDToAction.find(keyIt->second);
if (it != keyIDToAction.end()) {
// Warn about overwriting keys.
Log("Keymapper: key '{}' is already bound to action '{}', overwriting", key, it->second.get().name);
}
return keyIt->second;
}
} // namespace devilution

69
Source/controls/keymapper.hpp

@ -1,69 +0,0 @@
#pragma once
#include <cstddef>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace devilution {
/** The Keymapper maps keys to actions. */
class Keymapper final {
public:
using ActionIndex = std::size_t;
/**
* Action represents an action that can be triggered using a keyboard
* shortcut.
*/
class Action final {
public:
Action(const std::string &name, int defaultKey, std::function<void()> action)
: name(name)
, defaultKey(defaultKey)
, action(action)
, enable([] { return true; })
{
}
Action(const std::string &name, int defaultKey, std::function<void()> action, std::function<bool()> enable)
: name(name)
, defaultKey(defaultKey)
, action(action)
, enable(enable)
{
}
void operator()() const
{
action();
}
private:
std::string name;
int defaultKey;
std::function<void()> action;
std::function<bool()> enable;
int key {};
friend class Keymapper;
};
Keymapper();
ActionIndex AddAction(const Action &action);
void KeyPressed(int key) const;
std::string KeyNameForAction(ActionIndex actionIndex) const;
void Save() const;
void Load();
private:
int GetActionKey(const Action &action);
std::vector<Action> actions;
std::unordered_map<int, std::reference_wrapper<Action>> keyIDToAction;
std::unordered_map<int, std::string> keyIDToKeyName;
std::unordered_map<std::string, int> keyNameToKeyID;
};
} // namespace devilution

137
Source/diablo.cpp

@ -19,7 +19,6 @@
#include "debug.h"
#endif
#include "DiabloUI/diabloui.h"
#include "controls/keymapper.hpp"
#include "controls/plrctrls.h"
#include "controls/touch/gamepad.h"
#include "controls/touch/renderers.h"
@ -105,8 +104,6 @@ bool gbQuietMode = false;
clicktype sgbMouseDown;
uint16_t gnTickDelay = 50;
char gszProductName[64] = "DevilutionX vUnknown";
Keymapper keymapper;
std::array<Keymapper::ActionIndex, 4> quickSpellActionIndexes;
#ifdef _DEBUG
bool DebugDisableNetworkTimeout = false;
@ -443,7 +440,7 @@ void PressKey(int vkey)
if (sgnTimeoutCurs != CURSOR_NONE) {
return;
}
keymapper.KeyPressed(vkey);
sgOptions.Keymapper.KeyPressed(vkey);
if (vkey == DVL_VK_RETURN) {
if (GetAsyncKeyState(DVL_VK_MENU))
sgOptions.Graphics.fullscreen.SetValue(!IsFullScreen());
@ -475,7 +472,7 @@ void PressKey(int vkey)
return;
}
keymapper.KeyPressed(vkey);
sgOptions.Keymapper.KeyPressed(vkey);
if (vkey == DVL_VK_RETURN) {
if (GetAsyncKeyState(DVL_VK_MENU)) {
@ -1388,15 +1385,18 @@ bool IsPlayerDead()
void InitKeymapActions()
{
keymapper.AddAction({
sgOptions.Keymapper.AddAction(
"Help",
N_("Help"),
N_("Open Help Screen."),
DVL_VK_F1,
HelpKeyPressed,
[&]() { return !IsPlayerDead(); },
});
[&]() { return !IsPlayerDead(); });
for (int i = 0; i < 4; ++i) {
quickSpellActionIndexes[i] = keymapper.AddAction({
std::string("QuickSpell") + std::to_string(i + 1),
sgOptions.Keymapper.AddAction(
"QuickSpell{}",
N_("Quick spell {}"),
N_("Hotkey for skill or spell."),
DVL_VK_F5 + i,
[i]() {
if (spselflag) {
@ -1409,68 +1409,81 @@ void InitKeymapActions()
QuickCast(i);
},
[&]() { return !IsPlayerDead(); },
});
i + 1);
}
for (int i = 0; i < 4; ++i) {
keymapper.AddAction({
QuickMessages[i].key,
sgOptions.Keymapper.AddAction(
"QuickMessage{}",
N_("Quick Message {}"),
N_("Use Quick Message in chat."),
DVL_VK_F9 + i,
[i]() { DiabloHotkeyMsg(i); },
});
[] { return true; },
i + 1);
}
keymapper.AddAction({
sgOptions.Keymapper.AddAction(
"DecreaseGamma",
N_("Decrease Gamma"),
N_("Reduce screen brightness."),
'G',
DecreaseGamma,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"IncreaseGamma",
N_("Increase Gamma"),
N_("Increase screen brightness."),
'F',
IncreaseGamma,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"Inventory",
N_("Inventory"),
N_("Open Inventory screen."),
'I',
InventoryKeyPressed,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"Character",
N_("Character"),
N_("Open Character screen."),
'C',
CharacterSheetKeyPressed,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"QuestLog",
N_("Quest log"),
N_("Open Quest log."),
'Q',
QuestLogKeyPressed,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"Zoom",
N_("Zoom"),
N_("Zoom Game Screen."),
'Z',
[] {
zoomflag = !zoomflag;
CalcViewportGeometry();
},
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"DisplaySpells",
N_("Speedbook"),
N_("Open Speedbook."),
'S',
DisplaySpellsKeyPressed,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"SpellBook",
N_("Spellbook"),
N_("Open Spellbook."),
'B',
SpellBookKeyPressed,
[&]() { return !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"GameInfo",
N_("Game info"),
N_("Displays game infos."),
'V',
[] {
char pszStr[MAX_SEND_STR_LEN];
@ -1485,11 +1498,12 @@ void InitKeymapActions()
sizeof(pszStr));
NetSendCmdString(1 << MyPlayerId, pszStr);
},
[&]() { return !IsPlayerDead(); },
});
[&]() { return !IsPlayerDead(); });
for (int i = 0; i < 8; ++i) {
keymapper.AddAction({
std::string("BeltItem") + std::to_string(i + 1),
sgOptions.Keymapper.AddAction(
"BeltItem{}",
N_("Belt item {}"),
N_("Use Belt item."),
'1' + i,
[i] {
auto &myPlayer = Players[MyPlayerId];
@ -1498,40 +1512,45 @@ void InitKeymapActions()
}
},
[&]() { return !IsPlayerDead(); },
});
i + 1);
}
keymapper.AddAction({
sgOptions.Keymapper.AddAction(
"QuickSave",
N_("Quick save"),
N_("Saves the game."),
DVL_VK_F2,
[] { gamemenu_save_game(false); },
[&]() { return !gbIsMultiplayer && !IsPlayerDead(); },
});
keymapper.AddAction({
[&]() { return !gbIsMultiplayer && !IsPlayerDead(); });
sgOptions.Keymapper.AddAction(
"QuickLoad",
N_("Quick load"),
N_("Loads the game."),
DVL_VK_F3,
[] { gamemenu_load_game(false); },
[&]() { return !gbIsMultiplayer && gbValidSaveFile && stextflag == STORE_NONE; },
});
keymapper.AddAction({
[&]() { return !gbIsMultiplayer && gbValidSaveFile && stextflag == STORE_NONE; });
sgOptions.Keymapper.AddAction(
"QuitGame",
N_("Quit game"),
N_("Closes the game."),
DVL_VK_INVALID,
[] { gamemenu_quit_game(false); },
});
keymapper.AddAction({
[] { gamemenu_quit_game(false); });
sgOptions.Keymapper.AddAction(
"StopHero",
N_("Stop hero"),
N_("Stops walking and cancel pending actions."),
DVL_VK_INVALID,
[] { Players[MyPlayerId].Stop(); },
[&]() { return !IsPlayerDead(); },
});
[&]() { return !IsPlayerDead(); });
#ifdef _DEBUG
keymapper.AddAction({
sgOptions.Keymapper.AddAction(
"DebugToggle",
"Debug toggle",
"Programming is like magic.",
'X',
[] {
DebugToggle = !DebugToggle;
},
[&]() { return true; },
});
[&]() { return true; });
#endif
}

2
Source/diablo.h

@ -7,7 +7,6 @@
#include <cstdint>
#include "controls/keymapper.hpp"
#ifdef _DEBUG
#include "monstdat.h"
#endif
@ -102,7 +101,6 @@ void diablo_color_cyc_logic();
/* rdata */
extern Keymapper keymapper;
#ifdef _DEBUG
extern bool DebugDisableNetworkTimeout;
#endif

1
Source/loadsave.cpp

@ -7,6 +7,7 @@
#include <climits>
#include <cstring>
#include <unordered_map>
#include <SDL.h>

204
Source/options.cpp

@ -32,6 +32,7 @@
#define SI_SUPPORT_IOSTREAMS
#include <SimpleIni.h>
#include "control.h"
#include "diablo.h"
#include "discord/discord.h"
#include "engine/demomode.h"
@ -161,6 +162,17 @@ float GetIniFloat(const char *sectionName, const char *keyName, float defaultVal
return (float)GetIni().GetDoubleValue(sectionName, keyName, defaultValue);
}
bool GetIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString = "")
{
const char *value = GetIni().GetValue(sectionName, keyName);
if (value == nullptr) {
CopyUtf8(string, defaultString, stringSize);
return false;
}
CopyUtf8(string, value, stringSize);
return true;
}
bool GetIniStringVector(const char *sectionName, const char *keyName, std::vector<std::string> &stringValues)
{
std::list<CSimpleIni::Entry> values;
@ -197,6 +209,13 @@ void SetIniValue(const char *keyname, const char *valuename, float value)
GetIni().SetDoubleValue(keyname, valuename, value, nullptr, true);
}
void SetIniValue(const char *sectionName, const char *keyName, const char *value)
{
IniChangedChecker changedChecker(sectionName, keyName);
auto &ini = GetIni();
ini.SetValue(sectionName, keyName, value, nullptr, true);
}
void SetIniValue(const char *keyname, const char *valuename, const std::vector<std::string> &stringValues)
{
IniChangedChecker changedChecker(keyname, valuename);
@ -307,25 +326,6 @@ void OptionAudioChanged()
} // namespace
void SetIniValue(const char *sectionName, const char *keyName, const char *value, int len)
{
IniChangedChecker changedChecker(sectionName, keyName);
auto &ini = GetIni();
std::string stringValue(value, len != 0 ? len : strlen(value));
ini.SetValue(sectionName, keyName, stringValue.c_str(), nullptr, true);
}
bool GetIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString)
{
const char *value = GetIni().GetValue(sectionName, keyName);
if (value == nullptr) {
CopyUtf8(string, defaultString, stringSize);
return false;
}
CopyUtf8(string, value, stringSize);
return true;
}
/** Game options */
Options sgOptions;
bool sbWasOptionsLoaded = false;
@ -365,8 +365,6 @@ void LoadOptions()
sgOptions.Controller.bRearTouch = GetIniBool("Controller", "Enable Rear Touchpad", true);
#endif
keymapper.Load();
if (demo::IsRunning())
demo::OverrideOptions();
@ -409,8 +407,6 @@ void SaveOptions()
SetIniValue("Controller", "Enable Rear Touchpad", sgOptions.Controller.bRearTouch);
#endif
keymapper.Save();
SaveIni();
}
@ -1132,4 +1128,166 @@ std::vector<OptionEntryBase *> LanguageOptions::GetEntries()
};
}
KeymapperOptions::KeymapperOptions()
: OptionCategoryBase("Keymapping", N_("Keymapping"), N_("Keymapping Settings"))
{
// Insert all supported keys: a-z, 0-9 and F1-F12.
keyIDToKeyName.reserve(('Z' - 'A' + 1) + ('9' - '0' + 1) + 12);
for (char c = 'A'; c <= 'Z'; ++c) {
keyIDToKeyName.emplace(c, std::string(1, c));
}
for (char c = '0'; c <= '9'; ++c) {
keyIDToKeyName.emplace(c, std::string(1, c));
}
for (int i = 0; i < 12; ++i) {
keyIDToKeyName.emplace(DVL_VK_F1 + i, fmt::format("F{}", i + 1));
}
keyNameToKeyID.reserve(keyIDToKeyName.size());
for (const auto &kv : keyIDToKeyName) {
keyNameToKeyID.emplace(kv.second, kv.first);
}
}
std::vector<OptionEntryBase *> KeymapperOptions::GetEntries()
{
std::vector<OptionEntryBase *> entries;
for (auto &action : actions) {
entries.push_back(action.get());
}
return entries;
}
KeymapperOptions::Action::Action(string_view key, string_view name, string_view description, int defaultKey, std::function<void()> action, std::function<bool()> enable, int index)
: OptionEntryBase(key, OptionEntryFlags::None, name, description)
, defaultKey(defaultKey)
, action(action)
, enable(enable)
, dynamicIndex(index)
{
if (index >= 0) {
dynamicKey = fmt::format(key, index);
this->key = dynamicKey;
}
}
string_view KeymapperOptions::Action::GetName() const
{
if (dynamicIndex < 0)
return name;
dynamicName = fmt::format(_(name.data()), dynamicIndex);
return dynamicName;
}
void KeymapperOptions::Action::LoadFromIni(string_view category)
{
std::array<char, 64> result;
if (!GetIniValue(category.data(), key.data(), result.data(), result.size())) {
SetValue(defaultKey);
return; // Use the default key if no key has been set.
}
std::string readKey = result.data();
if (readKey.empty()) {
SetValue(DVL_VK_INVALID);
return;
}
auto keyIt = sgOptions.Keymapper.keyNameToKeyID.find(readKey);
if (keyIt == sgOptions.Keymapper.keyNameToKeyID.end()) {
// Use the default key if the key is unknown.
Log("Keymapper: unknown key '{}'", readKey);
SetValue(defaultKey);
return;
}
// Store the key in action.key and in the map so we can save() the
// actions while keeping the same order as they have been added.
SetValue(keyIt->second);
}
void KeymapperOptions::Action::SaveToIni(string_view category) const
{
if (boundKey == DVL_VK_INVALID) {
// Just add an empty config entry if the action is unbound.
SetIniValue(category.data(), key.data(), "");
}
auto keyNameIt = sgOptions.Keymapper.keyIDToKeyName.find(boundKey);
if (keyNameIt == sgOptions.Keymapper.keyIDToKeyName.end()) {
Log("Keymapper: no name found for key '{}'", key);
return;
}
SetIniValue(category.data(), key.data(), keyNameIt->second.c_str());
}
string_view KeymapperOptions::Action::GetValueDescription() const
{
if (boundKey == DVL_VK_INVALID)
return "";
auto keyNameIt = sgOptions.Keymapper.keyIDToKeyName.find(boundKey);
if (keyNameIt == sgOptions.Keymapper.keyIDToKeyName.end()) {
return "";
}
return keyNameIt->second.c_str();
}
bool KeymapperOptions::Action::SetValue(int value)
{
if (value != DVL_VK_INVALID && sgOptions.Keymapper.keyIDToKeyName.find(value) == sgOptions.Keymapper.keyIDToKeyName.end()) {
// Ignore invalid key values
return false;
}
// Remove old key
if (boundKey != DVL_VK_INVALID) {
sgOptions.Keymapper.keyIDToAction.erase(boundKey);
boundKey = DVL_VK_INVALID;
}
// Add new key
if (value != DVL_VK_INVALID) {
auto it = sgOptions.Keymapper.keyIDToAction.find(value);
if (it != sgOptions.Keymapper.keyIDToAction.end()) {
// Warn about overwriting keys.
Log("Keymapper: key '{}' is already bound to action '{}', overwriting", value, it->second.get().name);
it->second.get().boundKey = DVL_VK_INVALID;
}
sgOptions.Keymapper.keyIDToAction.insert_or_assign(value, *this);
boundKey = value;
}
return true;
}
void KeymapperOptions::AddAction(string_view key, string_view name, string_view description, int defaultKey, std::function<void()> action, std::function<bool()> enable, int index)
{
actions.push_back(std::unique_ptr<Action>(new Action(key, name, description, defaultKey, action, enable, index)));
}
void KeymapperOptions::KeyPressed(int key) const
{
auto it = keyIDToAction.find(key);
if (it == keyIDToAction.end())
return; // Ignore unmapped keys.
const auto &action = it->second;
// Check that the action can be triggered and that the chat textbox is not
// open.
if (!action.get().enable() || talkflag)
return;
action.get().action();
}
string_view KeymapperOptions::KeyNameForAction(string_view actionName) const
{
for (auto &action : actions) {
if (action->key == actionName && action->boundKey != DVL_VK_INVALID) {
return action->GetValueDescription();
}
}
return "";
}
} // namespace devilution

59
Source/options.h

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <unordered_map>
#include <SDL_version.h>
@ -42,6 +43,7 @@ enum class ScalingQuality {
enum class OptionEntryType {
Boolean,
List,
Key,
};
enum class OptionEntryFlags {
@ -75,7 +77,7 @@ public:
, description(description)
{
}
[[nodiscard]] string_view GetName() const;
[[nodiscard]] virtual string_view GetName() const;
[[nodiscard]] string_view GetDescription() const;
[[nodiscard]] virtual OptionEntryType GetType() const = 0;
[[nodiscard]] OptionEntryFlags GetFlags() const;
@ -519,6 +521,56 @@ struct LanguageOptions : OptionCategoryBase {
OptionEntryLanguageCode code;
};
/** The Keymapper maps keys to actions. */
struct KeymapperOptions : OptionCategoryBase {
/**
* Action represents an action that can be triggered using a keyboard
* shortcut.
*/
class Action final : public OptionEntryBase {
public:
[[nodiscard]] string_view GetName() const override;
[[nodiscard]] OptionEntryType GetType() const override
{
return OptionEntryType::Key;
}
void LoadFromIni(string_view category) override;
void SaveToIni(string_view category) const override;
[[nodiscard]] string_view GetValueDescription() const override;
bool SetValue(int value);
private:
Action(string_view key, string_view name, string_view description, int defaultKey, std::function<void()> action, std::function<bool()> enable, int index);
int defaultKey;
std::function<void()> action;
std::function<bool()> enable;
int boundKey = DVL_VK_INVALID;
int dynamicIndex;
std::string dynamicKey;
mutable std::string dynamicName;
friend struct KeymapperOptions;
};
KeymapperOptions();
std::vector<OptionEntryBase *> GetEntries() override;
void AddAction(
string_view key, string_view name, string_view description, int defaultKey,
std::function<void()> action, std::function<bool()> enable = [] { return true; }, int index = -1);
void KeyPressed(int key) const;
string_view KeyNameForAction(string_view actionName) const;
private:
std::vector<std::unique_ptr<Action>> actions;
std::unordered_map<int, std::reference_wrapper<Action>> keyIDToAction;
std::unordered_map<int, std::string> keyIDToKeyName;
std::unordered_map<std::string, int> keyNameToKeyID;
};
struct Options {
StartUpOptions StartUp;
DiabloOptions Diablo;
@ -530,6 +582,7 @@ struct Options {
NetworkOptions Network;
ChatOptions Chat;
LanguageOptions Language;
KeymapperOptions Keymapper;
[[nodiscard]] std::vector<OptionCategoryBase *> GetCategories()
{
@ -544,13 +597,11 @@ struct Options {
&Network,
&Chat,
&Language,
&Keymapper,
};
}
};
bool GetIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString = "");
void SetIniValue(const char *sectionName, const char *keyName, const char *value, int len = 0);
extern DVL_API_FOR_TEST Options sgOptions;
extern bool sbWasOptionsLoaded;

17
Source/panels/spell_list.cpp

@ -3,10 +3,10 @@
#include <fmt/format.h>
#include "control.h"
#include "controls/keymapper.hpp"
#include "engine.h"
#include "engine/render/text_render.hpp"
#include "inv_iterators.hpp"
#include "options.h"
#include "palette.h"
#include "panels/spell_icons.hpp"
#include "player.h"
@ -17,8 +17,6 @@
namespace devilution {
extern std::array<Keymapper::ActionIndex, 4> quickSpellActionIndexes;
namespace {
void PrintSBookSpellType(const Surface &out, Point position, const std::string &text, uint8_t rectColorIndex)
@ -53,10 +51,10 @@ void PrintSBookSpellType(const Surface &out, Point position, const std::string &
DrawString(out, text, position, UiFlags::ColorWhite);
}
void PrintSBookHotkey(const Surface &out, Point position, const std::string &text)
void PrintSBookHotkey(const Surface &out, Point position, const string_view text)
{
// Align the hot key text with the top-right corner of the spell icon
position += Displacement { SPLICONLENGTH - (GetLineWidth(text.c_str()) + 5), 5 - SPLICONLENGTH };
position += Displacement { SPLICONLENGTH - (GetLineWidth(text.data()) + 5), 5 - SPLICONLENGTH };
// Draw a drop shadow below and to the left of the text
DrawString(out, text, position + Displacement { -1, 1 }, UiFlags::ColorBlack);
@ -83,13 +81,14 @@ bool GetSpellListSelection(spell_id &pSpell, spell_type &pSplType)
return false;
}
std::optional<std::string> GetHotkeyName(spell_id spellId, spell_type spellType)
std::optional<string_view> GetHotkeyName(spell_id spellId, spell_type spellType)
{
auto &myPlayer = Players[MyPlayerId];
for (int t = 0; t < 4; t++) {
if (myPlayer._pSplHotKey[t] != spellId || myPlayer._pSplTHotKey[t] != spellType)
continue;
return keymapper.KeyNameForAction(quickSpellActionIndexes[t]);
auto quickSpellActionKey = fmt::format("QuickSpell{}", t + 1);
return sgOptions.Keymapper.KeyNameForAction(quickSpellActionKey);
}
return {};
}
@ -117,7 +116,7 @@ void DrawSpell(const Surface &out)
const Point position { PANEL_X + 565, PANEL_Y + 119 };
DrawSpellCel(out, position, nCel);
std::optional<std::string> hotkeyName = GetHotkeyName(spl, myPlayer._pRSplType);
std::optional<string_view> hotkeyName = GetHotkeyName(spl, myPlayer._pRSplType);
if (hotkeyName)
PrintSBookHotkey(out, position, *hotkeyName);
}
@ -146,7 +145,7 @@ void DrawSpellList(const Surface &out)
SetSpellTrans(transType);
DrawSpellCel(out, spellListItem.location, SpellITbl[static_cast<size_t>(spellId)]);
std::optional<std::string> hotkeyName = GetHotkeyName(spellId, spellListItem.type);
std::optional<string_view> hotkeyName = GetHotkeyName(spellId, spellListItem.type);
if (hotkeyName)
PrintSBookHotkey(out, spellListItem.location, *hotkeyName);

Loading…
Cancel
Save