3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Globalization;
14 using Microsoft.Xna.Framework.Input;
52 public static class GameSettings
64 Language = TextManager.DefaultLanguage,
106 config.AutomaticQuickStartEnabled =
false;
107 config.AutomaticCampaignLoadEnabled =
false;
108 config.TextManagerDebugModeEnabled =
false;
109 config.ModBreakerMode =
false;
118 retVal.DeserializeElement(element);
123 retVal.
Language = TextManager.DefaultLanguage;
127 if (element.GetAttribute(
"RemoteMainMenuContentUrl")?.Value ==
string.Empty)
137 LoadSubEditorImages(element);
176 public bool AutomaticQuickStartEnabled;
177 public bool AutomaticCampaignLoadEnabled;
178 public bool TestScreenEnabled;
179 public bool TextManagerDebugModeEnabled;
180 public bool ModBreakerMode;
205 gfxSettings.
VSync =
true;
217 elements.ForEach(element => retVal.DeserializeElement(element));
239 [StructSerialization.Skip]
244 public static class DeviceNameHandler
246 public static string Read(
string s)
247 => System.Xml.XmlConvert.DecodeName(s)!;
249 public static string Write(
string s)
250 => System.Xml.XmlConvert.EncodeName(s)!;
270 return audioSettings;
276 elements.ForEach(element => retVal.DeserializeElement(element));
292 [StructSerialization.Handler(typeof(DeviceNameHandler))]
294 [StructSerialization.Handler(typeof(DeviceNameHandler))]
301 [StructSerialization.Skip]
307 private readonly
static ImmutableDictionary<InputType, KeyOrMouse> DefaultsQwerty =
308 new Dictionary<InputType, KeyOrMouse>()
331 {
InputType.ContextualCommand, Keys.LeftShift },
335 {
InputType.TakeHalfFromInventorySlot, Keys.LeftShift },
336 {
InputType.TakeOneFromInventorySlot, Keys.LeftControl },
344 {
InputType.SelectNextCharacter, Keys.Z },
345 {
InputType.SelectPreviousCharacter, Keys.X },
351 {
InputType.ShowInteractionLabels, Keys.LeftAlt }
352 }.ToImmutableDictionary();
358 (kvp.Key, kvp.Value.MouseButton ==
MouseButton.None
359 ? (
KeyOrMouse)Keyboard.QwertyToCurrentLayout(kvp.Value.Key)
361 .ToImmutableDictionary()
367 Dictionary<InputType, KeyOrMouse> bindings = fallback?.Bindings?.ToMutable() ?? defaultBindings.ToMutable();
370 if (!bindings.ContainsKey(inputType))
372 bindings.Add(inputType, defaultBindings[inputType]);
376 Dictionary<InputType, KeyOrMouse> savedBindings =
new Dictionary<InputType, KeyOrMouse>();
377 bool playerConfigContainsNewChatBinds =
false;
378 bool playerConfigContainsRestoredVoipBinds =
false;
379 foreach (XElement element
in elements)
381 foreach (XAttribute attribute
in element.Attributes())
383 if (Enum.TryParse(attribute.Name.LocalName, out
InputType result))
385 playerConfigContainsNewChatBinds |= result ==
InputType.ActiveChat;
386 playerConfigContainsRestoredVoipBinds |= result ==
InputType.RadioVoice;
387 var keyOrMouse = element.GetAttributeKeyOrMouse(attribute.Name.LocalName, bindings[result]);
388 savedBindings.Add(result, keyOrMouse);
389 bindings[result] = keyOrMouse;
395 foreach (var defaultBinding
in defaultBindings)
397 if (!IsSetToNone(defaultBinding.Value) && !savedBindings.ContainsKey(defaultBinding.Key))
399 foreach (var savedBinding
in savedBindings)
402 defaultBinding.Key ==
InputType.ContextualCommand)
408 if (savedBinding.Value == defaultBinding.Value)
410 OnGameMainHasLoaded += () =>
412 (string, string)[] replacements =
414 (
"[defaultbind]", $
"\"{TextManager.Get($"inputtype.{defaultBinding.Key}
")}\""),
415 (
"[savedbind]", $
"\"{TextManager.Get($"inputtype.{savedBinding.Key}
")}\""),
416 (
"[key]", $
"\"{defaultBinding.Value.Name}\"")
418 new GUIMessageBox(TextManager.Get(
"warning"), TextManager.GetWithVariables(
"duplicatebindwarning", replacements));
425 static bool IsSetToNone(
KeyOrMouse keyOrMouse) => keyOrMouse == Keys.None && keyOrMouse ==
MouseButton.None;
429 if (!playerConfigContainsNewChatBinds)
432 bindings[
InputType.RadioChat] = Keys.None;
436 if (!playerConfigContainsRestoredVoipBinds)
438 bindings[
InputType.LocalVoice] = Keys.None;
439 bindings[
InputType.RadioVoice] = Keys.None;
442 Bindings = bindings.ToImmutableDictionary();
452 : (kvp.Key, kvp.Value))
453 .ToImmutableDictionary();
457 public ImmutableDictionary<InputType, KeyOrMouse>
Bindings;
462 [StructSerialization.Skip]
488 var thisBindings = Bindings;
491 Bindings = Enumerable.Range(0, thisBindings.Length)
492 .Select(i => i == index ? keyOrMouse : thisBindings[i])
499 var bindings = (fallback?.
Bindings ?? GetDefault().Bindings).ToArray();
500 foreach (XElement element
in elements)
502 for (
int i = 0; i < bindings.Length; i++)
504 bindings[i] = element.GetAttributeKeyOrMouse($
"slot{i}", bindings[i]);
507 Bindings = bindings.ToImmutableArray();
511 [StructSerialization.Skip]
516 public const string PlayerConfigPath =
"config_player.xml";
518 private static Config currentConfig;
519 public static ref readonly
Config CurrentConfig => ref currentConfig;
522 public static Action? OnGameMainHasLoaded;
525 public static void Init()
527 XDocument? currentConfigDoc =
null;
529 if (File.Exists(PlayerConfigPath))
531 currentConfigDoc = XMLExtensions.TryLoadXml(PlayerConfigPath);
534 if (currentConfigDoc !=
null)
536 currentConfig = Config.
FromElement(currentConfigDoc.Root ??
throw new NullReferenceException(
"Config XML element is invalid: document is null."));
540 currentConfigDoc.Root.GetChildElement(
"player"),
541 currentConfigDoc.Root.GetChildElement(
"gameplay")?.GetChildElement(
"jobpreferences"));
554 public static void SetCurrentConfig(in Config newConfig)
556 bool resolutionChanged =
559 bool languageChanged = currentConfig.
Language != newConfig.Language;
562 bool textScaleChanged = Math.Abs(currentConfig.
Graphics.
TextScale - newConfig.Graphics.TextScale) > MathF.Pow(2.0f, -7);
564 bool hudScaleChanged = !MathUtils.NearlyEqual(currentConfig.
Graphics.
HUDScale, newConfig.Graphics.HUDScale);
566 bool setGraphicsMode =
572 bool keybindsChanged =
false;
573 foreach (var kvp
in newConfig.KeyMap.Bindings)
575 if (!currentConfig.
KeyMap.
Bindings.TryGetValue(kvp.Key, out var existingBinding) ||
576 existingBinding != kvp.Value)
578 keybindsChanged =
true;
584 currentConfig = newConfig;
589 GameMain.Instance.ApplyGraphicsSettings(recalculateFontsAndStyles:
true);
591 else if (textScaleChanged)
593 GUIStyle.RecalculateFonts();
596 if (audioOutputChanged)
601 if (voiceCaptureChanged)
608 HUDLayoutSettings.CreateAreas();
609 GameMain.GameSession?.HUDScaleChanged();
614 foreach (var item
in Item.ItemList)
616 foreach (var ic
in item.Components)
624 GameMain.SoundManager?.ApplySettings();
626 if (languageChanged) { TextManager.ClearCache(); }
629 public static void SaveCurrentConfig()
631 XDocument configDoc =
new XDocument();
632 XElement root =
new XElement(
"config"); configDoc.Add(root);
633 currentConfig.SerializeElement(root);
635 XElement graphicsElement =
new XElement(
"graphicssettings"); root.Add(graphicsElement);
636 currentConfig.
Graphics.SerializeElement(graphicsElement);
638 XElement audioElement =
new XElement(
"audio"); root.Add(audioElement);
639 currentConfig.
Audio.SerializeElement(audioElement);
641 XElement contentPackagesElement =
new XElement(
"contentpackages"); root.Add(contentPackagesElement);
642 XComment corePackageComment =
new XComment(ContentPackageManager.EnabledPackages.Core?.Name ??
"Vanilla"); contentPackagesElement.Add(corePackageComment);
643 XElement corePackageElement =
new XElement(ContentPackageManager.CorePackageElementName); contentPackagesElement.Add(corePackageElement);
644 corePackageElement.SetAttributeValue(
"path", ContentPackageManager.EnabledPackages.Core?.Path ?? ContentPackageManager.VanillaFileList);
646 XElement regularPackagesElement =
new XElement(ContentPackageManager.RegularPackagesElementName); contentPackagesElement.Add(regularPackagesElement);
647 foreach (var regularPackage
in ContentPackageManager.EnabledPackages.Regular)
649 XComment packageComment =
new XComment(regularPackage.Name); regularPackagesElement.Add(packageComment);
650 XElement packageElement =
new XElement(ContentPackageManager.RegularPackagesSubElementName); regularPackagesElement.Add(packageElement);
651 packageElement.SetAttributeValue(
"path", regularPackage.Path);
655 XElement serverFiltersElement =
new XElement(
"serverfilters"); root.Add(serverFiltersElement);
656 ServerListFilters.Instance.SaveTo(serverFiltersElement);
658 XElement characterElement =
new XElement(
"player"); root.Add(characterElement);
659 MultiplayerPreferences.Instance.SaveTo(characterElement);
661 XElement ignoredHintsElement =
new XElement(
"ignoredhints"); root.Add(ignoredHintsElement);
662 IgnoredHints.Instance.SaveTo(ignoredHintsElement);
664 XElement debugConsoleMappingElement =
new XElement(
"debugconsolemapping"); root.Add(debugConsoleMappingElement);
667 XElement tutorialsElement =
new XElement(
"tutorials"); root.Add(tutorialsElement);
668 CompletedTutorials.Instance.SaveTo(tutorialsElement);
670 XElement keyMappingElement =
new XElement(
"keymapping",
672 =>
new XAttribute(kvp.Key.ToString(), kvp.Value.ToString())));
673 root.Add(keyMappingElement);
675 XElement inventoryKeyMappingElement =
new XElement(
"inventorykeymapping",
678 .Cast<(
int Index, KeyOrMouse Bind)>()
680 =>
new XAttribute($
"slot{kvp.Index.ToString(CultureInfo.InvariantCulture)}", kvp.Bind.ToString())));
681 root.Add(inventoryKeyMappingElement);
683 SubEditorScreen.ImageManager.Save(root);
685 root.Add(CampaignSettings.CurrentSettings.Save());
688 configDoc.SaveSafe(PlayerConfigPath);
690 System.Xml.XmlWriterSettings settings =
new System.Xml.XmlWriterSettings
693 OmitXmlDeclaration =
true,
694 NewLineOnAttributes =
true
701 configDoc.WriteTo(writer);
707 DebugConsole.ThrowError(
"Saving game settings failed.", e);
708 GameAnalyticsManager.AddErrorEventOnce(
"GameSettings.Save:SaveFailed", GameAnalyticsManager.ErrorSeverity.Error,
709 "Saving game settings failed.\n" + e.Message +
"\n" + e.StackTrace.CleanupStackTrace());
714 private static void LoadSubEditorImages(XElement configElement)
716 XElement? element = configElement?.Element(
"editorimages");
719 SubEditorScreen.ImageManager.Clear(alsoPending:
true);
722 SubEditorScreen.ImageManager.Load(element);
void SaveTo(XElement element)
static void Init(XElement? element)
static DebugConsoleMapping Instance
static void Init(XElement? element)
static XmlWriter Create(string path, System.Xml.XmlWriterSettings settings)
static void Init(XElement? element)
static void Init(params XElement?[] elements)
static void ChangeCaptureDevice(string deviceName)
static void Init(XElement? elem)
InteractionLabelDisplayMode
int VoiceChatCutoffPrevention
bool DisableVoiceChatFilters
bool VoipAttenuationEnabled
static AudioSettings FromElements(IEnumerable< XElement > elements, in AudioSettings? fallback=null)
bool UseDirectionalVoiceChat
static AudioSettings GetDefault()
string VoiceCaptureDevice
bool DynamicRangeCompressionEnabled
static GraphicsSettings FromElements(IEnumerable< XElement > elements, in GraphicsSettings? fallback=null)
static GraphicsSettings GetDefault()
static readonly Point MinSupportedResolution
static InventoryKeyMapping GetDefault()
InventoryKeyMapping WithBinding(int index, KeyOrMouse keyOrMouse)
InventoryKeyMapping(IEnumerable< XElement > elements, InventoryKeyMapping? fallback)
ImmutableArray< KeyOrMouse > Bindings
KeyMapping(IEnumerable< XElement > elements, in KeyMapping? fallback)
KeyMapping WithBinding(InputType type, KeyOrMouse bind)
LocalizedString KeyBindText(InputType inputType)
static KeyMapping GetDefault()
ImmutableDictionary< InputType, KeyOrMouse > Bindings
XElement SavedCampaignSettings
InteractionLabelDisplayMode InteractionLabelDisplayMode
int CorpseDespawnDelayPvP
Eos.EosSteamPrimaryLogin.CrossplayChoice CrossplayChoice
EnemyHealthBarMode ShowEnemyHealthBars
static Config FromElement(XElement element, in Config? fallback=null)
bool ShowOffensiveServerPrompt
InventoryKeyMapping InventoryKeyMap
GraphicsSettings Graphics
static Config GetDefault()
LanguageIdentifier Language
Color SubEditorBackground
bool SaveDebugConsoleLogs
int CorpsesPerSubDespawnThreshold
const float DefaultAimAssist
bool DisableGlobalSpamList
string RemoteMainMenuContentUrl
bool EnableSubmarineAutoSave
int AutoSaveIntervalSeconds
static readonly LanguageIdentifier None