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,
105 config.AutomaticQuickStartEnabled =
false;
106 config.AutomaticCampaignLoadEnabled =
false;
107 config.TextManagerDebugModeEnabled =
false;
108 config.ModBreakerMode =
false;
117 retVal.DeserializeElement(element);
122 retVal.
Language = TextManager.DefaultLanguage;
126 if (element.GetAttribute(
"RemoteMainMenuContentUrl")?.Value ==
string.Empty)
136 LoadSubEditorImages(element);
174 public bool AutomaticQuickStartEnabled;
175 public bool AutomaticCampaignLoadEnabled;
176 public bool TestScreenEnabled;
177 public bool TextManagerDebugModeEnabled;
178 public bool ModBreakerMode;
203 gfxSettings.
VSync =
true;
215 elements.ForEach(element => retVal.DeserializeElement(element));
237 [StructSerialization.Skip]
242 public static class DeviceNameHandler
244 public static string Read(
string s)
245 => System.Xml.XmlConvert.DecodeName(s)!;
247 public static string Write(
string s)
248 => System.Xml.XmlConvert.EncodeName(s)!;
268 return audioSettings;
274 elements.ForEach(element => retVal.DeserializeElement(element));
290 [StructSerialization.Handler(typeof(DeviceNameHandler))]
292 [StructSerialization.Handler(typeof(DeviceNameHandler))]
299 [StructSerialization.Skip]
305 private readonly
static ImmutableDictionary<InputType, KeyOrMouse> DefaultsQwerty =
306 new Dictionary<InputType, KeyOrMouse>()
329 {
InputType.ContextualCommand, Keys.LeftShift },
333 {
InputType.TakeHalfFromInventorySlot, Keys.LeftShift },
334 {
InputType.TakeOneFromInventorySlot, Keys.LeftControl },
342 {
InputType.SelectNextCharacter, Keys.Z },
343 {
InputType.SelectPreviousCharacter, Keys.X },
349 {
InputType.ShowInteractionLabels, Keys.LeftAlt }
350 }.ToImmutableDictionary();
356 (kvp.Key, kvp.Value.MouseButton ==
MouseButton.None
357 ? (
KeyOrMouse)Keyboard.QwertyToCurrentLayout(kvp.Value.Key)
359 .ToImmutableDictionary()
365 Dictionary<InputType, KeyOrMouse> bindings = fallback?.Bindings?.ToMutable() ?? defaultBindings.ToMutable();
368 if (!bindings.ContainsKey(inputType))
370 bindings.Add(inputType, defaultBindings[inputType]);
374 Dictionary<InputType, KeyOrMouse> savedBindings =
new Dictionary<InputType, KeyOrMouse>();
375 bool playerConfigContainsNewChatBinds =
false;
376 bool playerConfigContainsRestoredVoipBinds =
false;
377 foreach (XElement element
in elements)
379 foreach (XAttribute attribute
in element.Attributes())
381 if (Enum.TryParse(attribute.Name.LocalName, out
InputType result))
383 playerConfigContainsNewChatBinds |= result ==
InputType.ActiveChat;
384 playerConfigContainsRestoredVoipBinds |= result ==
InputType.RadioVoice;
385 var keyOrMouse = element.GetAttributeKeyOrMouse(attribute.Name.LocalName, bindings[result]);
386 savedBindings.Add(result, keyOrMouse);
387 bindings[result] = keyOrMouse;
393 foreach (var defaultBinding
in defaultBindings)
395 if (!IsSetToNone(defaultBinding.Value) && !savedBindings.ContainsKey(defaultBinding.Key))
397 foreach (var savedBinding
in savedBindings)
400 defaultBinding.Key ==
InputType.ContextualCommand)
406 if (savedBinding.Value == defaultBinding.Value)
408 OnGameMainHasLoaded += () =>
410 (string, string)[] replacements =
412 (
"[defaultbind]", $
"\"{TextManager.Get($"inputtype.{defaultBinding.Key}
")}\""),
413 (
"[savedbind]", $
"\"{TextManager.Get($"inputtype.{savedBinding.Key}
")}\""),
414 (
"[key]", $
"\"{defaultBinding.Value.Name}\"")
416 new GUIMessageBox(TextManager.Get(
"warning"), TextManager.GetWithVariables(
"duplicatebindwarning", replacements));
423 static bool IsSetToNone(
KeyOrMouse keyOrMouse) => keyOrMouse == Keys.None && keyOrMouse ==
MouseButton.None;
427 if (!playerConfigContainsNewChatBinds)
430 bindings[
InputType.RadioChat] = Keys.None;
434 if (!playerConfigContainsRestoredVoipBinds)
436 bindings[
InputType.LocalVoice] = Keys.None;
437 bindings[
InputType.RadioVoice] = Keys.None;
440 Bindings = bindings.ToImmutableDictionary();
450 : (kvp.Key, kvp.Value))
451 .ToImmutableDictionary();
455 public ImmutableDictionary<InputType, KeyOrMouse>
Bindings;
460 [StructSerialization.Skip]
486 var thisBindings = Bindings;
489 Bindings = Enumerable.Range(0, thisBindings.Length)
490 .Select(i => i == index ? keyOrMouse : thisBindings[i])
497 var bindings = (fallback?.
Bindings ?? GetDefault().Bindings).ToArray();
498 foreach (XElement element
in elements)
500 for (
int i = 0; i < bindings.Length; i++)
502 bindings[i] = element.GetAttributeKeyOrMouse($
"slot{i}", bindings[i]);
505 Bindings = bindings.ToImmutableArray();
509 [StructSerialization.Skip]
514 public const string PlayerConfigPath =
"config_player.xml";
516 private static Config currentConfig;
517 public static ref readonly
Config CurrentConfig => ref currentConfig;
520 public static Action? OnGameMainHasLoaded;
523 public static void Init()
525 XDocument? currentConfigDoc =
null;
527 if (File.Exists(PlayerConfigPath))
529 currentConfigDoc = XMLExtensions.TryLoadXml(PlayerConfigPath);
532 if (currentConfigDoc !=
null)
534 currentConfig = Config.
FromElement(currentConfigDoc.Root ??
throw new NullReferenceException(
"Config XML element is invalid: document is null."));
538 currentConfigDoc.Root.GetChildElement(
"player"),
539 currentConfigDoc.Root.GetChildElement(
"gameplay")?.GetChildElement(
"jobpreferences"));
552 public static void SetCurrentConfig(in Config newConfig)
554 bool resolutionChanged =
557 bool languageChanged = currentConfig.
Language != newConfig.Language;
560 bool textScaleChanged = Math.Abs(currentConfig.
Graphics.
TextScale - newConfig.Graphics.TextScale) > MathF.Pow(2.0f, -7);
562 bool hudScaleChanged = !MathUtils.NearlyEqual(currentConfig.
Graphics.
HUDScale, newConfig.Graphics.HUDScale);
564 bool setGraphicsMode =
570 bool keybindsChanged =
false;
571 foreach (var kvp
in newConfig.KeyMap.Bindings)
573 if (!currentConfig.
KeyMap.
Bindings.TryGetValue(kvp.Key, out var existingBinding) ||
574 existingBinding != kvp.Value)
576 keybindsChanged =
true;
582 currentConfig = newConfig;
587 GameMain.Instance.ApplyGraphicsSettings(recalculateFontsAndStyles:
true);
589 else if (textScaleChanged)
591 GUIStyle.RecalculateFonts();
594 if (audioOutputChanged)
599 if (voiceCaptureChanged)
606 HUDLayoutSettings.CreateAreas();
607 GameMain.GameSession?.HUDScaleChanged();
612 foreach (var item
in Item.ItemList)
614 foreach (var ic
in item.Components)
622 GameMain.SoundManager?.ApplySettings();
624 if (languageChanged) { TextManager.ClearCache(); }
627 public static void SaveCurrentConfig()
629 XDocument configDoc =
new XDocument();
630 XElement root =
new XElement(
"config"); configDoc.Add(root);
631 currentConfig.SerializeElement(root);
633 XElement graphicsElement =
new XElement(
"graphicssettings"); root.Add(graphicsElement);
634 currentConfig.
Graphics.SerializeElement(graphicsElement);
636 XElement audioElement =
new XElement(
"audio"); root.Add(audioElement);
637 currentConfig.
Audio.SerializeElement(audioElement);
639 XElement contentPackagesElement =
new XElement(
"contentpackages"); root.Add(contentPackagesElement);
640 XComment corePackageComment =
new XComment(ContentPackageManager.EnabledPackages.Core?.Name ??
"Vanilla"); contentPackagesElement.Add(corePackageComment);
641 XElement corePackageElement =
new XElement(ContentPackageManager.CorePackageElementName); contentPackagesElement.Add(corePackageElement);
642 corePackageElement.SetAttributeValue(
"path", ContentPackageManager.EnabledPackages.Core?.Path ?? ContentPackageManager.VanillaFileList);
644 XElement regularPackagesElement =
new XElement(ContentPackageManager.RegularPackagesElementName); contentPackagesElement.Add(regularPackagesElement);
645 foreach (var regularPackage
in ContentPackageManager.EnabledPackages.Regular)
647 XComment packageComment =
new XComment(regularPackage.Name); regularPackagesElement.Add(packageComment);
648 XElement packageElement =
new XElement(ContentPackageManager.RegularPackagesSubElementName); regularPackagesElement.Add(packageElement);
649 packageElement.SetAttributeValue(
"path", regularPackage.Path);
653 XElement serverFiltersElement =
new XElement(
"serverfilters"); root.Add(serverFiltersElement);
654 ServerListFilters.Instance.SaveTo(serverFiltersElement);
656 XElement characterElement =
new XElement(
"player"); root.Add(characterElement);
657 MultiplayerPreferences.Instance.SaveTo(characterElement);
659 XElement ignoredHintsElement =
new XElement(
"ignoredhints"); root.Add(ignoredHintsElement);
660 IgnoredHints.Instance.SaveTo(ignoredHintsElement);
662 XElement debugConsoleMappingElement =
new XElement(
"debugconsolemapping"); root.Add(debugConsoleMappingElement);
665 XElement tutorialsElement =
new XElement(
"tutorials"); root.Add(tutorialsElement);
666 CompletedTutorials.Instance.SaveTo(tutorialsElement);
668 XElement keyMappingElement =
new XElement(
"keymapping",
670 =>
new XAttribute(kvp.Key.ToString(), kvp.Value.ToString())));
671 root.Add(keyMappingElement);
673 XElement inventoryKeyMappingElement =
new XElement(
"inventorykeymapping",
676 .Cast<(
int Index, KeyOrMouse Bind)>()
678 =>
new XAttribute($
"slot{kvp.Index.ToString(CultureInfo.InvariantCulture)}", kvp.Bind.ToString())));
679 root.Add(inventoryKeyMappingElement);
681 SubEditorScreen.ImageManager.Save(root);
683 root.Add(CampaignSettings.CurrentSettings.Save());
686 configDoc.SaveSafe(PlayerConfigPath);
688 System.Xml.XmlWriterSettings settings =
new System.Xml.XmlWriterSettings
691 OmitXmlDeclaration =
true,
692 NewLineOnAttributes =
true
699 configDoc.WriteTo(writer);
705 DebugConsole.ThrowError(
"Saving game settings failed.", e);
706 GameAnalyticsManager.AddErrorEventOnce(
"GameSettings.Save:SaveFailed", GameAnalyticsManager.ErrorSeverity.Error,
707 "Saving game settings failed.\n" + e.Message +
"\n" + e.StackTrace.CleanupStackTrace());
712 private static void LoadSubEditorImages(XElement configElement)
714 XElement? element = configElement?.Element(
"editorimages");
717 SubEditorScreen.ImageManager.Clear(alsoPending:
true);
720 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
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