#pragma once #include #include #include #include #include #include #include #include #include "controls/controller.h" #include "controls/controller_buttons.h" #include "controls/game_controls.h" #include "engine/sound_defs.hpp" #include "pack.h" #include "utils/enum_traits.h" namespace devilution { enum class StartUpGameMode : uint8_t { /** @brief If hellfire is present, asks the user what game they want to start. */ Ask = 0, Hellfire = 1, Diablo = 2, }; enum class StartUpIntro : uint8_t { Off = 0, Once = 1, On = 2, }; /** @brief Defines what splash screen should be shown at startup. */ enum class StartUpSplash : uint8_t { /** @brief Show no splash screen. */ None = 0, /** @brief Show only TitleDialog. */ TitleDialog = 1, /** @brief Show Logo and TitleDialog. */ LogoAndTitleDialog = 2, }; enum class ScalingQuality : uint8_t { NearestPixel, BilinearFiltering, AnisotropicFiltering, }; enum class Resampler : uint8_t { #ifdef DEVILUTIONX_RESAMPLER_SPEEX Speex = 0, #endif #ifdef DVL_AULIB_SUPPORTS_SDL_RESAMPLER SDL, #endif }; std::string_view ResamplerToString(Resampler resampler); std::optional ResamplerFromString(std::string_view resampler); enum class FloatingNumbers : uint8_t { /** @brief Show no floating numbers. */ Off = 0, /** @brief Show floating numbers at random angles. */ Random = 1, /** @brief Show floating numbers vertically only. */ Vertical = 2, }; enum class OptionEntryType : uint8_t { Boolean, List, Key, PadButton, }; enum class OptionEntryFlags : uint8_t { /** @brief No special logic. */ None = 0, /** @brief Shouldn't be shown in settings dialog. */ Invisible = 1 << 0, /** @brief Need to restart the current running game (single- or multiplayer) to take effect. */ CantChangeInGame = 1 << 1, /** @brief Need to restart the current running multiplayer game to take effect. */ CantChangeInMultiPlayer = 1 << 2, /** @brief Option is only relevant for Hellfire. */ OnlyHellfire = 1 << 3, /** @brief Option is only relevant for Diablo. */ OnlyDiablo = 1 << 4, /** @brief After option is changed the UI needs to be recreated. */ RecreateUI = 1 << 5, /** @brief diablo.mpq must be present. */ NeedDiabloMpq = 1 << 6, /** @brief hellfire.mpq must be present. */ NeedHellfireMpq = 1 << 7, }; use_enum_as_flags(OptionEntryFlags); class OptionEntryBase { public: OptionEntryBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description) : flags(flags) , key(key) , name(name) , description(description) { } [[nodiscard]] virtual std::string_view GetName() const; [[nodiscard]] std::string_view GetDescription() const; [[nodiscard]] virtual OptionEntryType GetType() const = 0; [[nodiscard]] OptionEntryFlags GetFlags() const; void SetValueChangedCallback(std::function callback); [[nodiscard]] virtual std::string_view GetValueDescription() const = 0; virtual void LoadFromIni(std::string_view category) = 0; virtual void SaveToIni(std::string_view category) const = 0; OptionEntryFlags flags; protected: std::string_view key; const char *name; const char *description; void NotifyValueChanged(); private: std::function callback; }; class OptionEntryBoolean : public OptionEntryBase { public: OptionEntryBoolean(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, bool defaultValue) : OptionEntryBase(key, flags, name, description) , defaultValue(defaultValue) , value(defaultValue) { } [[nodiscard]] bool operator*() const { return value; } void SetValue(bool value); [[nodiscard]] OptionEntryType GetType() const override; [[nodiscard]] std::string_view GetValueDescription() const override; void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; private: bool defaultValue; bool value; }; class OptionEntryListBase : public OptionEntryBase { public: [[nodiscard]] virtual size_t GetListSize() const = 0; [[nodiscard]] virtual std::string_view GetListDescription(size_t index) const = 0; [[nodiscard]] virtual size_t GetActiveListIndex() const = 0; virtual void SetActiveListIndex(size_t index) = 0; [[nodiscard]] OptionEntryType GetType() const override; [[nodiscard]] std::string_view GetValueDescription() const override; protected: OptionEntryListBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description) : OptionEntryBase(key, flags, name, description) { } }; class OptionEntryEnumBase : public OptionEntryListBase { public: void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; protected: OptionEntryEnumBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, int defaultValue) : OptionEntryListBase(key, flags, name, description) , defaultValue(defaultValue) , value(defaultValue) { } [[nodiscard]] int GetValueInternal() const { return value; } void SetValueInternal(int value); void AddEntry(int value, std::string_view name); private: int defaultValue; int value; std::vector entryNames; std::vector entryValues; }; template class OptionEntryEnum : public OptionEntryEnumBase { public: OptionEntryEnum(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue, std::initializer_list> entries) : OptionEntryEnumBase(key, flags, name, description, static_cast(defaultValue)) { for (auto &&[key, value] : entries) { AddEntry(static_cast(key), value); } } [[nodiscard]] T operator*() const { return static_cast(GetValueInternal()); } void SetValue(T value) { SetValueInternal(static_cast(value)); } }; class OptionEntryIntBase : public OptionEntryListBase { public: void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; protected: OptionEntryIntBase(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, int defaultValue) : OptionEntryListBase(key, flags, name, description) , defaultValue(defaultValue) , value(defaultValue) { } [[nodiscard]] int GetValueInternal() const { return value; } void SetValueInternal(int value); void AddEntry(int value); private: int defaultValue; int value; mutable std::vector entryNames; std::vector entryValues; }; template class OptionEntryInt : public OptionEntryIntBase { public: OptionEntryInt(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue, std::initializer_list entries) : OptionEntryIntBase(key, flags, name, description, static_cast(defaultValue)) { for (auto entry : entries) { AddEntry(static_cast(entry)); } } OptionEntryInt(std::string_view key, OptionEntryFlags flags, const char *name, const char *description, T defaultValue) : OptionEntryInt(key, flags, name, description, defaultValue, { defaultValue }) { } [[nodiscard]] T operator*() const { return static_cast(GetValueInternal()); } void SetValue(T value) { SetValueInternal(static_cast(value)); } }; class OptionEntryLanguageCode : public OptionEntryListBase { public: OptionEntryLanguageCode(); void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; std::string_view operator*() const { return szCode; } OptionEntryLanguageCode &operator=(std::string_view code) { assert(code.size() < 6); memcpy(szCode, code.data(), code.size()); szCode[code.size()] = '\0'; return *this; } private: /** @brief Language code (ISO-15897) for text. */ char szCode[6]; mutable std::vector> languages; void CheckLanguagesAreInitialized() const; }; class OptionEntryResolution : public OptionEntryListBase { public: OptionEntryResolution(); void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; void InvalidateList(); Size operator*() const { return size; } private: /** @brief View size. */ Size size; mutable std::vector> resolutions; void CheckResolutionsAreInitialized() const; }; class OptionEntryResampler : public OptionEntryListBase { public: OptionEntryResampler(); void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; Resampler operator*() const { return resampler_; } private: void UpdateDependentOptions() const; Resampler resampler_; }; class OptionEntryAudioDevice : public OptionEntryListBase { public: OptionEntryAudioDevice(); void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] size_t GetListSize() const override; [[nodiscard]] std::string_view GetListDescription(size_t index) const override; [[nodiscard]] size_t GetActiveListIndex() const override; void SetActiveListIndex(size_t index) override; std::string operator*() const { for (size_t i = 0; i < GetListSize(); i++) { std::string_view deviceName = GetDeviceName(i); if (deviceName == deviceName_) return deviceName_; } return ""; } private: std::string_view GetDeviceName(size_t index) const; std::string deviceName_; }; struct OptionCategoryBase { OptionCategoryBase(std::string_view key, const char *name, const char *description) : key(key) , name(name) , description(description) { } [[nodiscard]] std::string_view GetKey() const; [[nodiscard]] std::string_view GetName() const; [[nodiscard]] std::string_view GetDescription() const; virtual std::vector GetEntries() = 0; protected: std::string_view key; const char *name; const char *description; }; struct StartUpOptions : OptionCategoryBase { StartUpOptions(); std::vector GetEntries() override; OptionEntryEnum gameMode; OptionEntryBoolean shareware; /** @brief Play game intro video on diablo startup. */ OptionEntryEnum diabloIntro; /** @brief Play game intro video on hellfire startup. */ OptionEntryEnum hellfireIntro; OptionEntryEnum splash; }; struct DiabloOptions : OptionCategoryBase { DiabloOptions(); std::vector GetEntries() override; /** @brief Remembers what singleplayer hero/save was last used. */ OptionEntryInt lastSinglePlayerHero; /** @brief Remembers what multiplayer hero/save was last used. */ OptionEntryInt lastMultiplayerHero; }; struct HellfireOptions : OptionCategoryBase { HellfireOptions(); std::vector GetEntries() override; /** @brief Cornerstone of the world item. */ char szItem[sizeof(ItemPack) * 2 + 1]; /** @brief Remembers what singleplayer hero/save was last used. */ OptionEntryInt lastSinglePlayerHero; /** @brief Remembers what multiplayer hero/save was last used. */ OptionEntryInt lastMultiplayerHero; }; struct AudioOptions : OptionCategoryBase { AudioOptions(); std::vector GetEntries() override; /** @brief Movie and SFX volume. */ OptionEntryInt soundVolume; /** @brief Music volume. */ OptionEntryInt musicVolume; /** @brief Player emits sound when walking. */ OptionEntryBoolean walkingSound; /** @brief Automatically equipping items on pickup emits the equipment sound. */ OptionEntryBoolean autoEquipSound; /** @brief Picking up items emits the items pickup sound. */ OptionEntryBoolean itemPickupSound; /** @brief Output sample rate (Hz). */ OptionEntryInt sampleRate; /** @brief The number of output channels (1 or 2) */ OptionEntryInt channels; /** @brief Buffer size (number of frames per channel) */ OptionEntryInt bufferSize; /** @brief Resampler implementation. */ OptionEntryResampler resampler; /** @brief Quality of the resampler, from 0 (lowest) to 10 (highest). Available for the speex resampler only. */ OptionEntryInt resamplingQuality; /** @brief Audio device. */ OptionEntryAudioDevice device; }; struct GraphicsOptions : OptionCategoryBase { GraphicsOptions(); std::vector GetEntries() override; OptionEntryResolution resolution; /** @brief Run in fullscreen or windowed mode. */ OptionEntryBoolean fullscreen; #if !defined(USE_SDL1) || defined(__3DS__) /** @brief Expand the aspect ratio to match the screen. */ OptionEntryBoolean fitToScreen; #endif #ifndef USE_SDL1 /** @brief Scale the image after rendering. */ OptionEntryBoolean upscale; /** @brief See SDL_HINT_RENDER_SCALE_QUALITY. */ OptionEntryEnum scaleQuality; /** @brief Only scale by values divisible by the width and height. */ OptionEntryBoolean integerScaling; /** @brief Enable vsync on the output. */ OptionEntryBoolean vSync; #endif /** @brief Gamma correction level. */ OptionEntryInt gammaCorrection; /** @brief Zoom on start. */ OptionEntryBoolean zoom; /** @brief Enable color cycling animations. */ OptionEntryBoolean colorCycling; /** @brief Use alternate nest palette. */ OptionEntryBoolean alternateNestArt; #if SDL_VERSION_ATLEAST(2, 0, 0) /** @brief Use a hardware cursor (SDL2 only). */ OptionEntryBoolean hardwareCursor; /** @brief Use a hardware cursor for items. */ OptionEntryBoolean hardwareCursorForItems; /** @brief Maximum width / height for the hardware cursor. Larger cursors fall back to software. */ OptionEntryInt hardwareCursorMaxSize; #endif /** @brief Enable FPS Limiter. */ OptionEntryBoolean limitFPS; /** @brief Show FPS, even without the -f command line flag. */ OptionEntryBoolean showFPS; }; struct GameplayOptions : OptionCategoryBase { GameplayOptions(); std::vector GetEntries() override; /** @brief Gameplay ticks per second. */ OptionEntryInt tickRate; /** @brief Enable double walk speed when in town. */ OptionEntryBoolean runInTown; /** @brief Do not let the mouse leave the application window. */ OptionEntryBoolean grabInput; /** @brief Enable the Theo quest. */ OptionEntryBoolean theoQuest; /** @brief Enable the cow quest. */ OptionEntryBoolean cowQuest; /** @brief Will players still damage other players in non-PvP mode. */ OptionEntryBoolean friendlyFire; /** @brief Enables the full/uncut singleplayer version of quests. */ OptionEntryBoolean multiplayerFullQuests; /** @brief Enable the bard hero class. */ OptionEntryBoolean testBard; /** @brief Enable the babarian hero class. */ OptionEntryBoolean testBarbarian; /** @brief Show the current level progress. */ OptionEntryBoolean experienceBar; /** @brief Show item graphics to the left of item descriptions in store menus. */ OptionEntryBoolean showItemGraphicsInStores; /** @brief Display current/max health values on health globe. */ OptionEntryBoolean showHealthValues; /** @brief Display current/max mana values on mana globe. */ OptionEntryBoolean showManaValues; /** @brief Show enemy health at the top of the screen. */ OptionEntryBoolean enemyHealthBar; /** @brief Automatically pick up gold when walking over it. */ OptionEntryBoolean autoGoldPickup; /** @brief Auto-pickup elixirs */ OptionEntryBoolean autoElixirPickup; /** @brief Auto-pickup oils */ OptionEntryBoolean autoOilPickup; /** @brief Enable or Disable auto-pickup in town */ OptionEntryBoolean autoPickupInTown; /** @brief Recover mana when talking to Adria. */ OptionEntryBoolean adriaRefillsMana; /** @brief Automatically attempt to equip weapon-type items when picking them up. */ OptionEntryBoolean autoEquipWeapons; /** @brief Automatically attempt to equip armor-type items when picking them up. */ OptionEntryBoolean autoEquipArmor; /** @brief Automatically attempt to equip helm-type items when picking them up. */ OptionEntryBoolean autoEquipHelms; /** @brief Automatically attempt to equip shield-type items when picking them up. */ OptionEntryBoolean autoEquipShields; /** @brief Automatically attempt to equip jewelry-type items when picking them up. */ OptionEntryBoolean autoEquipJewelry; /** @brief Only enable 2/3 quests in each game session */ OptionEntryBoolean randomizeQuests; /** @brief Indicates whether or not monster type (Animal, Demon, Undead) is shown along with other monster information. */ OptionEntryBoolean showMonsterType; /** @brief Displays item labels for items on the ground. */ OptionEntryBoolean showItemLabels; /** @brief Refill belt from inventory, or rather, use potions/scrolls from inventory first when belt item is consumed. */ OptionEntryBoolean autoRefillBelt; /** @brief Locally disable clicking on shrines which permanently cripple character. */ OptionEntryBoolean disableCripplingShrines; /** @brief Spell hotkeys instantly cast the spell. */ OptionEntryBoolean quickCast; /** @brief Number of Healing potions to pick up automatically */ OptionEntryInt numHealPotionPickup; /** @brief Number of Full Healing potions to pick up automatically */ OptionEntryInt numFullHealPotionPickup; /** @brief Number of Mana potions to pick up automatically */ OptionEntryInt numManaPotionPickup; /** @brief Number of Full Mana potions to pick up automatically */ OptionEntryInt numFullManaPotionPickup; /** @brief Number of Rejuvenating potions to pick up automatically */ OptionEntryInt numRejuPotionPickup; /** @brief Number of Full Rejuvenating potions to pick up automatically */ OptionEntryInt numFullRejuPotionPickup; /** @brief Enable floating numbers. */ OptionEntryEnum enableFloatingNumbers; }; struct ControllerOptions : OptionCategoryBase { ControllerOptions(); std::vector GetEntries() override; /** @brief SDL Controller mapping, see SDL_GameControllerDB. */ char szMapping[1024]; /** @brief Configure gamepad joysticks deadzone */ float fDeadzone; #ifdef __vita__ /** @brief Enable input via rear touchpad */ bool bRearTouch; #endif }; struct NetworkOptions : OptionCategoryBase { NetworkOptions(); std::vector GetEntries() override; /** @brief Optionally bind to a specific network interface. */ char szBindAddress[129]; /** @brief Most recently entered ZeroTier Game ID. */ char szPreviousZTGame[129]; /** @brief Most recently entered Hostname in join dialog. */ char szPreviousHost[129]; /** @brief What network port to use. */ OptionEntryInt port; }; struct ChatOptions : OptionCategoryBase { ChatOptions(); std::vector GetEntries() override; /** @brief Quick chat messages. */ std::vector szHotKeyMsgs[QUICK_MESSAGE_OPTIONS]; }; struct LanguageOptions : OptionCategoryBase { LanguageOptions(); std::vector GetEntries() override; OptionEntryLanguageCode code; }; constexpr uint32_t KeymapperMouseButtonMask = 1 << 31; /** 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: // OptionEntryBase::key may be referencing Action::dynamicKey. // The implicit copy constructor would copy that reference instead of referencing the copy. Action(const Action &) = delete; Action(std::string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index); [[nodiscard]] std::string_view GetName() const override; [[nodiscard]] OptionEntryType GetType() const override { return OptionEntryType::Key; } void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] std::string_view GetValueDescription() const override; bool SetValue(int value); private: uint32_t defaultKey; std::function actionPressed; std::function actionReleased; std::function enable; uint32_t boundKey = SDLK_UNKNOWN; unsigned dynamicIndex; std::string dynamicKey; mutable std::string dynamicName; friend struct KeymapperOptions; }; KeymapperOptions(); std::vector GetEntries() override; void AddAction( std::string_view key, const char *name, const char *description, uint32_t defaultKey, std::function actionPressed, std::function actionReleased = nullptr, std::function enable = nullptr, unsigned index = 0); void CommitActions(); void KeyPressed(uint32_t key) const; void KeyReleased(SDL_Keycode key) const; bool IsTextEntryKey(SDL_Keycode vkey) const; bool IsNumberEntryKey(SDL_Keycode vkey) const; std::string_view KeyNameForAction(std::string_view actionName) const; uint32_t KeyForAction(std::string_view actionName) const; private: std::forward_list actions; std::unordered_map> keyIDToAction; std::unordered_map keyIDToKeyName; std::unordered_map keyNameToKeyID; }; /** The Padmapper maps gamepad buttons to actions. */ struct PadmapperOptions : OptionCategoryBase { /** * Action represents an action that can be triggered using a gamepad * button combo. */ class Action final : public OptionEntryBase { public: Action(std::string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased, std::function enable, unsigned index); // OptionEntryBase::key may be referencing Action::dynamicKey. // The implicit copy constructor would copy that reference instead of referencing the copy. Action(const Action &) = delete; [[nodiscard]] std::string_view GetName() const override; [[nodiscard]] OptionEntryType GetType() const override { return OptionEntryType::PadButton; } void LoadFromIni(std::string_view category) override; void SaveToIni(std::string_view category) const override; [[nodiscard]] std::string_view GetValueDescription() const override; [[nodiscard]] std::string_view GetValueDescription(bool useShortName) const; bool SetValue(ControllerButtonCombo value); private: ControllerButtonCombo defaultInput; std::function actionPressed; std::function actionReleased; std::function enable; ControllerButtonCombo boundInput {}; mutable GamepadLayout boundInputDescriptionType = GamepadLayout::Generic; mutable std::string boundInputDescription; mutable std::string boundInputShortDescription; unsigned dynamicIndex; std::string dynamicKey; mutable std::string dynamicName; void UpdateValueDescription() const; std::string_view Shorten(std::string_view buttonName) const; friend struct PadmapperOptions; }; PadmapperOptions(); std::vector GetEntries() override; void AddAction( std::string_view key, const char *name, const char *description, ControllerButtonCombo defaultInput, std::function actionPressed, std::function actionReleased = nullptr, std::function enable = nullptr, unsigned index = 0); void CommitActions(); void ButtonPressed(ControllerButton button); void ButtonReleased(ControllerButton button, bool invokeAction = true); void ReleaseAllActiveButtons(); bool IsActive(std::string_view actionName) const; std::string_view ActionNameTriggeredByButtonEvent(ControllerButtonEvent ctrlEvent) const; std::string_view InputNameForAction(std::string_view actionName, bool useShortName = false) const; ControllerButtonCombo ButtonComboForAction(std::string_view actionName) const; private: std::forward_list actions; std::array::value> buttonToReleaseAction; std::array::value> buttonToButtonName; std::unordered_map buttonNameToButton; bool committed = false; const Action *FindAction(ControllerButton button) const; bool CanDeferToMovementHandler(const Action &action) const; }; struct Options { StartUpOptions StartUp; DiabloOptions Diablo; HellfireOptions Hellfire; AudioOptions Audio; GameplayOptions Gameplay; GraphicsOptions Graphics; ControllerOptions Controller; NetworkOptions Network; ChatOptions Chat; LanguageOptions Language; KeymapperOptions Keymapper; PadmapperOptions Padmapper; [[nodiscard]] std::vector GetCategories() { return { &Language, &StartUp, &Graphics, &Audio, &Diablo, &Hellfire, &Gameplay, &Controller, &Network, &Chat, &Keymapper, &Padmapper, }; } }; extern DVL_API_FOR_TEST Options sgOptions; bool HardwareCursorSupported(); /** * @brief Save game configurations to ini file */ void SaveOptions(); /** * @brief Load game configurations from ini file */ void LoadOptions(); } // namespace devilution