settings_state.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #include "settings_state.h"
  2. #include <array>
  3. #include <cstddef>
  4. #include <optional>
  5. namespace sunrise::state::account::settings {
  6. namespace {
  7. /** Inclusive domain for one authored setting value. */
  8. template <typename Value> struct SettingsRange {
  9. Value minimum;
  10. Value maximum;
  11. };
  12. /** The controller layout menu publishes these 7 stored values. */
  13. constexpr std::array<std::int8_t, 7> kButtonLayouts{0, 1, 2, 3, 5, 6, 9};
  14. /** The movement-layout menu stores 4 choices, 0 to 3. */
  15. constexpr SettingsRange<std::int8_t> kMovementModes{0, 3};
  16. /** Controller sensitivity stores the 10 menu choices as 0 to 9. */
  17. constexpr SettingsRange<std::int8_t> kControllerSensitivity{0, 9};
  18. /** Mouse sensitivity accepts the menu's 1 to 100 scale. */
  19. constexpr SettingsRange<std::int32_t> kMouseSensitivity{1, 100};
  20. /** ADS sensitivity stores the menu's 0.5 through 1.5 multiplier. */
  21. constexpr SettingsRange<float> kAdsSensitivity{0.5F, 1.5F};
  22. /** Double-press delay stores the 5 menu choices, 0 to 4. */
  23. constexpr SettingsRange<std::int8_t> kDoublePressDelay{0, 4};
  24. /** Two-choice selectors store only the menu values 0 and 1. */
  25. constexpr SettingsRange<std::int8_t> kTwoChoiceSelector{0, 1};
  26. /** Voice output stores 3 menu choices, 0 to 2. */
  27. constexpr SettingsRange<std::int8_t> kVoiceOutputMode{0, 2};
  28. /** Chat volume exposes 9 menu levels, 0 to 8. */
  29. constexpr SettingsRange<std::int8_t> kChatVolume{0, 8};
  30. /** Sound, dialogue and music expose 11 levels, 0 to 10. */
  31. constexpr SettingsRange<std::int8_t> kAudioVolume{0, 10};
  32. /** Brightness stores the 7 menu choices, 0 to 6. */
  33. constexpr SettingsRange<std::int8_t> kBrightness{0, 6};
  34. /** VSync stores the DXGI presentation interval from 0 to 4. */
  35. constexpr SettingsRange<std::uint8_t> kVerticalSyncInterval{0, 4};
  36. /** Field of view accepts the game's full 55 through 155 degree range. */
  37. constexpr SettingsRange<std::int32_t> kFieldOfView{55, 155};
  38. /** The first unidentified calibration field uses the working renderer fallback. */
  39. constexpr float kCalibrationPrimary = 10000.0F;
  40. /** The second unidentified calibration field uses the working renderer alpha. */
  41. constexpr float kCalibrationAlpha = 0.0F;
  42. /** Subtitle mode stores 3 menu choices, 0 to 2. */
  43. constexpr SettingsRange<std::int8_t> kSubtitlesMode{0, 2};
  44. /** Colorblind mode stores 4 menu choices, 0 to 3. */
  45. constexpr SettingsRange<std::int8_t> kColorblindMode{0, 3};
  46. /** HUD opacity stores 4 menu choices, 0 to 3. */
  47. constexpr SettingsRange<std::int8_t> kHudOpacity{0, 3};
  48. /** Background opacity stores 5 menu choices, 0 to 4. */
  49. constexpr SettingsRange<std::int8_t> kBackgroundOpacity{0, 4};
  50. /** Reticle color stores 7 menu choices, 0 to 6. */
  51. constexpr SettingsRange<std::int8_t> kReticleColor{0, 6};
  52. /** Text size stores 5 menu choices, 0 to 4. */
  53. constexpr SettingsRange<std::int8_t> kTextSize{0, 4};
  54. /** Text color and background style each store 4 choices, 0 to 3. */
  55. constexpr SettingsRange<std::int8_t> kTextPresentationMode{0, 3};
  56. /** Unidentified text settings stay on their only known working value. */
  57. constexpr std::int8_t kUnidentifiedTextValue = 0;
  58. /** Text chat stores 4 menu choices, 0 to 3. */
  59. constexpr SettingsRange<std::int8_t> kTextChatMode{0, 3};
  60. /**
  61. * Tests one scalar against an inclusive Sunrise settings policy range.
  62. * @param range Inclusive supported range.
  63. * @return True when the value is inside the range.
  64. */
  65. template <typename Value>
  66. [[nodiscard]] bool within(Value value, SettingsRange<Value> range) noexcept {
  67. return value >= range.minimum && value <= range.maximum;
  68. }
  69. /**
  70. * Tests one scalar against a Sunrise settings policy domain with gaps.
  71. * @param values Every supported value.
  72. * @return True when the candidate is one of them.
  73. */
  74. template <typename Value, std::size_t Count>
  75. [[nodiscard]] bool contains(const std::array<Value, Count>& values, Value candidate) noexcept {
  76. for (const Value value : values) {
  77. if (value == candidate) {
  78. return true;
  79. }
  80. }
  81. return false;
  82. }
  83. /**
  84. * Checks controller and mouse values against their authored menu domains.
  85. * @return True when every number belongs to its supported domain.
  86. */
  87. [[nodiscard]] bool valid_controls(const Controls& value) noexcept {
  88. return contains(kButtonLayouts, value.buttonLayout)
  89. && within(value.movementMode, kMovementModes)
  90. && within(value.controllerLookSensitivity, kControllerSensitivity)
  91. && within(value.mouseLookSensitivity, kMouseSensitivity)
  92. && within(value.adsSensitivityModifier, kAdsSensitivity)
  93. && within(value.doublePressDelay, kDoublePressDelay);
  94. }
  95. /**
  96. * Checks voice and volume values against their authored menu domains.
  97. * @return True when every number belongs to its supported domain.
  98. */
  99. [[nodiscard]] bool valid_audio(const Audio& value) noexcept {
  100. return within(value.voiceOutputMode, kVoiceOutputMode)
  101. && within(value.teamVoiceChannel, kTwoChoiceSelector)
  102. && within(value.reservedMode, kTwoChoiceSelector)
  103. && value.migrationVersion == kCompletedAudioMigrationVersion
  104. && within(value.chatVolume, kChatVolume)
  105. && within(value.soundEffectsVolume, kAudioVolume)
  106. && within(value.dialogueVolume, kAudioVolume) && within(value.musicVolume, kAudioVolume);
  107. }
  108. /**
  109. * Checks renderer values against the supported choices and working calibration values.
  110. * @return True when every number belongs to its supported domain.
  111. */
  112. [[nodiscard]] bool valid_display(const Display& value) noexcept {
  113. return within(value.brightness, kBrightness) && within(value.hdrMode, kTwoChoiceSelector)
  114. && within(value.verticalSyncInterval, kVerticalSyncInterval)
  115. && within(value.fieldOfView, kFieldOfView)
  116. && value.calibrationPrimary == kCalibrationPrimary
  117. && value.calibrationAlpha == kCalibrationAlpha;
  118. }
  119. /**
  120. * Checks HUD and text values against their authored menu domains.
  121. * @return True when every number belongs to its supported domain.
  122. */
  123. [[nodiscard]] bool valid_interface(const Interface& value) noexcept {
  124. return within(value.subtitlesMode, kSubtitlesMode)
  125. && within(value.colorblindMode, kColorblindMode)
  126. && within(value.helmetMode, kTwoChoiceSelector) && within(value.hudOpacity, kHudOpacity)
  127. && within(value.backgroundOpacity, kBackgroundOpacity)
  128. && within(value.reticleLocation, kTwoChoiceSelector)
  129. && within(value.reticleColor, kReticleColor) && within(value.textSize, kTextSize)
  130. && within(value.textColor, kTextPresentationMode)
  131. && within(value.textBackgroundStyle, kTextPresentationMode)
  132. && within(value.textBackgroundOpacity, kBackgroundOpacity)
  133. && value.reservedTextMode == kUnidentifiedTextValue
  134. && value.subtitleOptionsEntry == kUnidentifiedTextValue;
  135. }
  136. /**
  137. * Checks chat values against their authored menu domains.
  138. * @return True when every number belongs to its supported domain.
  139. */
  140. [[nodiscard]] bool valid_social(const Social& value) noexcept {
  141. return within(value.textChatMode, kTextChatMode)
  142. && within(value.whisperChatMode, kTwoChoiceSelector)
  143. && within(value.teamChatJoinMode, kTwoChoiceSelector)
  144. && within(value.localChatJoinMode, kTwoChoiceSelector)
  145. && within(value.clanChatJoinMode, kTwoChoiceSelector)
  146. && within(value.chatAutoHideMode, kTwoChoiceSelector);
  147. }
  148. /** Checks one optional native input code without interpreting an absent binding half. */
  149. [[nodiscard]] bool valid_input_code(const std::optional<std::uint16_t>& value) noexcept {
  150. if (!value.has_value()) {
  151. return true;
  152. }
  153. const std::uint16_t input = *value & bindings::kInputCodeMask;
  154. const std::uint16_t modifier = *value & bindings::kModifierMask;
  155. return input <= bindings::kMaximumBindableInputCode
  156. && (modifier == 0 || modifier == bindings::kAltModifierFlag
  157. || modifier == bindings::kControlModifierFlag
  158. || modifier == bindings::kShiftModifierFlag);
  159. }
  160. /** Checks every fixed binding row and rejects unsupported or combined modifier bits. */
  161. [[nodiscard]] bool valid_key_bindings(const bindings::KeyBindings& value) noexcept {
  162. if (!value.configured) {
  163. return false;
  164. }
  165. for (const bindings::Binding& binding : value.values) {
  166. if (!valid_input_code(binding.primary) || !valid_input_code(binding.secondary)) {
  167. return false;
  168. }
  169. }
  170. return true;
  171. }
  172. } // namespace
  173. /** Checks a whole account-settings object against the supported menu domains. */
  174. bool valid(const AccountSettings& value) noexcept {
  175. const bool validBindingSource = value.keyBindingSource == KeyBindingSource::account
  176. || value.keyBindingSource == KeyBindingSource::computer;
  177. return value.configured && valid_key_bindings(value.keyBindings) && validBindingSource
  178. && valid_controls(value.controls) && valid_audio(value.audio)
  179. && valid_display(value.display) && valid_interface(value.interface)
  180. && valid_social(value.social);
  181. }
  182. } // namespace sunrise::state::account::settings