mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-07-17 17:09:19 +02:00
Add settings parsing
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANumber_002EParsing_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fde8a243f75215d958afa80cf80a41b4981a44efea7db93bffcdaf7bd6ba378c0_003FNumber_002EParsing_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANumber_002EParsing_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fde8a243f75215d958afa80cf80a41b4981a44efea7db93bffcdaf7bd6ba378c0_003FNumber_002EParsing_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProcess_002EWindows_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe4b93d8bba7294f6676125d63f2c965fc3e4293b7fac7f8460a64f1950f84d9_003FProcess_002EWindows_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProcess_002EWindows_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe4b93d8bba7294f6676125d63f2c965fc3e4293b7fac7f8460a64f1950f84d9_003FProcess_002EWindows_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStarterNG_002EMainWindow_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe3e158158d7662e3d588342a349d6a29b4cf57_003FStarterNG_002EMainWindow_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStarterNG_002EMainWindow_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe3e158158d7662e3d588342a349d6a29b4cf57_003FStarterNG_002EMainWindow_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStyledElement_002Ecs_002Fl_003AC_0021_003FUsers_003FHirek_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe49d9521ff091d353928d1c44539ba0a4c93a9ebb2e65190880b4fe5eb8_003FStyledElement_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASvgDocument_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc89c4efb3d60208a50dd75ff12772229193779693bbd59534fd57d83f645d3d6_003FSvgDocument_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASvgDocument_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc89c4efb3d60208a50dd75ff12772229193779693bbd59534fd57d83f645d3d6_003FSvgDocument_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVisual_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F85fdd7bdff5ab3bf6e3dd75da8acd9fff1826782ae485f15c5520f43334124_003FVisual_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVisual_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F85fdd7bdff5ab3bf6e3dd75da8acd9fff1826782ae485f15c5520f43334124_003FVisual_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
@@ -22,13 +22,20 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
|
// Load the persisted configuration up front so the saved language wins;
|
||||||
|
// fall back to the system culture only when the file has no lang entry.
|
||||||
|
Settings.Instance.Load();
|
||||||
|
|
||||||
CultureInfo ci = CultureInfo.InstalledUICulture ;
|
CultureInfo ci = CultureInfo.InstalledUICulture ;
|
||||||
|
|
||||||
var langName = ci.Name switch
|
var langName = Settings.Instance.LanguageWasSet
|
||||||
{
|
? Settings.Instance.Language
|
||||||
"pl-PL" => "Polski",
|
: ci.Name switch
|
||||||
_ => "English"
|
{
|
||||||
};
|
"pl-PL" => "Polski",
|
||||||
|
_ => "English"
|
||||||
|
};
|
||||||
|
Settings.Instance.Language = langName;
|
||||||
|
|
||||||
var langDict = new ResourceInclude(new Uri("avares://StarterNG/"))
|
var langDict = new ResourceInclude(new Uri("avares://StarterNG/"))
|
||||||
{
|
{
|
||||||
@@ -61,6 +68,9 @@ public partial class App : Application
|
|||||||
// Code page 1250 is needed to parse scenery files (done during load).
|
// Code page 1250 is needed to parse scenery files (done during load).
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
|
||||||
|
// Persist settings when the launcher is closing.
|
||||||
|
desktop.ShutdownRequested += (_, _) => Settings.Instance.CaptureAndSave();
|
||||||
|
|
||||||
var splash = new SplashWindow();
|
var splash = new SplashWindow();
|
||||||
desktop.MainWindow = splash;
|
desktop.MainWindow = splash;
|
||||||
splash.Show();
|
splash.Show();
|
||||||
|
|||||||
@@ -171,5 +171,10 @@
|
|||||||
<x:String x:Key="AutoCloseStarter">Automatically close starter</x:String>
|
<x:String x:Key="AutoCloseStarter">Automatically close starter</x:String>
|
||||||
<x:String x:Key="LargeThumbnails">Large thumbnails</x:String>
|
<x:String x:Key="LargeThumbnails">Large thumbnails</x:String>
|
||||||
<x:String x:Key="AutoExpandSceneryTree">Automatically expand scenery tree</x:String>
|
<x:String x:Key="AutoExpandSceneryTree">Automatically expand scenery tree</x:String>
|
||||||
|
|
||||||
|
<!-- Settings actions -->
|
||||||
|
<x:String x:Key="Save">Save</x:String>
|
||||||
|
<x:String x:Key="Reset">Reset</x:String>
|
||||||
|
<x:String x:Key="SettingsSaved">Settings saved</x:String>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -171,8 +171,10 @@
|
|||||||
<x:String x:Key="AutoCloseStarter">Zamknij starter automatycznie</x:String>
|
<x:String x:Key="AutoCloseStarter">Zamknij starter automatycznie</x:String>
|
||||||
<x:String x:Key="LargeThumbnails">Duże miniaturki</x:String>
|
<x:String x:Key="LargeThumbnails">Duże miniaturki</x:String>
|
||||||
<x:String x:Key="AutoExpandSceneryTree">Automatycznie rozwijanie drzewka scenerii</x:String>
|
<x:String x:Key="AutoExpandSceneryTree">Automatycznie rozwijanie drzewka scenerii</x:String>
|
||||||
|
|
||||||
|
<!-- Settings actions -->
|
||||||
|
<x:String x:Key="Save">Zapisz</x:String>
|
||||||
|
<x:String x:Key="Reset">Przywróć</x:String>
|
||||||
|
<x:String x:Key="SettingsSaved">Zapisano ustawienia</x:String>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
221
StarterNG/Classes/ConfigFile.cs
Normal file
221
StarterNG/Classes/ConfigFile.cs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A line-oriented reader/writer for the EU07 <c>eu07.ini</c> configuration
|
||||||
|
/// file.
|
||||||
|
///
|
||||||
|
/// The file is not a classic INI: it is a flat list of <c>key value [value…]</c>
|
||||||
|
/// entries, one per line, with <c>//</c> comments (either trailing a line or
|
||||||
|
/// commenting the whole line out). The simulator parses it as a stream of
|
||||||
|
/// whitespace-separated tokens.
|
||||||
|
///
|
||||||
|
/// This class keeps every line verbatim and only rewrites the value of a key
|
||||||
|
/// when <see cref="Set"/> is called. Comments, blank lines, commented-out
|
||||||
|
/// entries and — crucially — any keys the launcher does not understand are
|
||||||
|
/// preserved on save, so editing a handful of settings never drops the rest of
|
||||||
|
/// the user's configuration.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ConfigFile
|
||||||
|
{
|
||||||
|
private readonly List<string> _lines = new();
|
||||||
|
|
||||||
|
// Active (uncommented) setting key -> index into _lines. Last occurrence wins,
|
||||||
|
// matching the simulator which lets later entries override earlier ones.
|
||||||
|
private readonly Dictionary<string, int> _index =
|
||||||
|
new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <summary>Parses raw file text, preserving line breaks and content.</summary>
|
||||||
|
public static ConfigFile Parse(string text)
|
||||||
|
{
|
||||||
|
var cfg = new ConfigFile();
|
||||||
|
// Normalise CRLF/CR to LF for splitting; output uses Environment.NewLine.
|
||||||
|
text = text.Replace("\r\n", "\n").Replace('\r', '\n');
|
||||||
|
foreach (var line in text.Split('\n'))
|
||||||
|
cfg.AddLine(line);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLine(string raw)
|
||||||
|
{
|
||||||
|
int idx = _lines.Count;
|
||||||
|
_lines.Add(raw);
|
||||||
|
if (TryParseKey(raw, out string key))
|
||||||
|
_index[key] = idx; // later duplicates override earlier ones
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>True if the key is present as an active (uncommented) entry.</summary>
|
||||||
|
public bool Has(string key) => _index.ContainsKey(key);
|
||||||
|
|
||||||
|
/// <summary>Raw value (all tokens after the key, comment stripped), or null.</summary>
|
||||||
|
public string? GetRaw(string key)
|
||||||
|
{
|
||||||
|
if (!_index.TryGetValue(key, out int i))
|
||||||
|
return null;
|
||||||
|
SplitLine(_lines[i], out _, out _, out string value, out _);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetString(string key, string fallback) =>
|
||||||
|
GetRaw(key) is { Length: > 0 } v ? v : fallback;
|
||||||
|
|
||||||
|
public bool GetBool(string key, bool fallback)
|
||||||
|
{
|
||||||
|
var v = FirstToken(GetRaw(key));
|
||||||
|
if (v is null) return fallback;
|
||||||
|
return v.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"yes" or "true" or "1" or "tak" => true,
|
||||||
|
"no" or "false" or "0" or "nie" => false,
|
||||||
|
_ => fallback
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetInt(string key, int fallback)
|
||||||
|
{
|
||||||
|
var v = FirstToken(GetRaw(key));
|
||||||
|
return v != null && int.TryParse(v, NumberStyles.Integer,
|
||||||
|
CultureInfo.InvariantCulture, out int n) ? n : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double GetDouble(string key, double fallback)
|
||||||
|
{
|
||||||
|
var v = FirstToken(GetRaw(key));
|
||||||
|
return v != null && double.TryParse(v, NumberStyles.Float,
|
||||||
|
CultureInfo.InvariantCulture, out double d) ? d : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>All whitespace-separated value tokens for a key (never null).</summary>
|
||||||
|
public string[] GetTokens(string key)
|
||||||
|
{
|
||||||
|
var v = GetRaw(key);
|
||||||
|
return string.IsNullOrEmpty(v)
|
||||||
|
? Array.Empty<string>()
|
||||||
|
: v.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the value of <paramref name="key"/>. If the key already exists its
|
||||||
|
/// line is rewritten in place, preserving original indentation and any
|
||||||
|
/// trailing comment. Otherwise a new line is appended.
|
||||||
|
/// </summary>
|
||||||
|
public void Set(string key, string value)
|
||||||
|
{
|
||||||
|
value ??= string.Empty;
|
||||||
|
if (_index.TryGetValue(key, out int i))
|
||||||
|
{
|
||||||
|
SplitLine(_lines[i], out string indent, out _, out _, out string comment);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(indent).Append(key);
|
||||||
|
if (value.Length > 0)
|
||||||
|
sb.Append(' ').Append(value);
|
||||||
|
if (comment.Length > 0)
|
||||||
|
sb.Append('\t').Append(comment);
|
||||||
|
_lines[i] = sb.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int idx = _lines.Count;
|
||||||
|
_lines.Add(value.Length > 0 ? $"{key} {value}" : key);
|
||||||
|
_index[key] = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetBool(string key, bool value) => Set(key, value ? "yes" : "no");
|
||||||
|
public void SetInt(string key, int value) =>
|
||||||
|
Set(key, value.ToString(CultureInfo.InvariantCulture));
|
||||||
|
public void SetDouble(string key, double value) =>
|
||||||
|
Set(key, value.ToString("0.###", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
/// <summary>Serialises back to text using the platform newline.</summary>
|
||||||
|
public string ToText() => string.Join(Environment.NewLine, _lines);
|
||||||
|
|
||||||
|
// --- line parsing helpers -------------------------------------------------
|
||||||
|
|
||||||
|
private static bool TryParseKey(string raw, out string key)
|
||||||
|
{
|
||||||
|
SplitLine(raw, out _, out key, out _, out _);
|
||||||
|
return key.Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decomposes a raw line into leading whitespace, key, value (tokens after
|
||||||
|
/// the key) and trailing comment (including the <c>//</c>). For blank or
|
||||||
|
/// fully commented-out lines, key is empty.
|
||||||
|
/// </summary>
|
||||||
|
private static void SplitLine(string raw, out string indent, out string key,
|
||||||
|
out string value, out string comment)
|
||||||
|
{
|
||||||
|
indent = key = value = comment = string.Empty;
|
||||||
|
|
||||||
|
// leading whitespace
|
||||||
|
int start = 0;
|
||||||
|
while (start < raw.Length && char.IsWhiteSpace(raw[start]))
|
||||||
|
start++;
|
||||||
|
indent = raw.Substring(0, start);
|
||||||
|
|
||||||
|
string body = raw.Substring(start);
|
||||||
|
|
||||||
|
// A line whose first non-space content is "//" is a pure comment.
|
||||||
|
if (body.StartsWith("//"))
|
||||||
|
{
|
||||||
|
comment = body;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (body.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Split off a trailing comment: the first "//" preceded by whitespace
|
||||||
|
// (so URLs such as tcp://… inside a value are not mistaken for one).
|
||||||
|
int commentAt = FindCommentStart(body);
|
||||||
|
string content = commentAt >= 0 ? body.Substring(0, commentAt) : body;
|
||||||
|
if (commentAt >= 0)
|
||||||
|
comment = body.Substring(commentAt).TrimEnd();
|
||||||
|
|
||||||
|
content = content.TrimEnd();
|
||||||
|
if (content.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int sp = IndexOfWhitespace(content);
|
||||||
|
if (sp < 0)
|
||||||
|
{
|
||||||
|
key = content;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
key = content.Substring(0, sp);
|
||||||
|
value = content.Substring(sp).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int FindCommentStart(string body)
|
||||||
|
{
|
||||||
|
for (int i = 0; i + 1 < body.Length; i++)
|
||||||
|
{
|
||||||
|
if (body[i] == '/' && body[i + 1] == '/' &&
|
||||||
|
(i == 0 || char.IsWhiteSpace(body[i - 1])))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int IndexOfWhitespace(string s)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s.Length; i++)
|
||||||
|
if (char.IsWhiteSpace(s[i]))
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? FirstToken(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return null;
|
||||||
|
int sp = IndexOfWhitespace(value);
|
||||||
|
return sp < 0 ? value : value.Substring(0, sp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +1,415 @@
|
|||||||
namespace StarterNG.Classes;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace StarterNG.Classes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application settings backing the Settings view. Load/Save are stubs for now;
|
/// Application settings backing the Settings view, persisted to the simulator's
|
||||||
/// the real persistence will be implemented later.
|
/// <c>eu07.ini</c>.
|
||||||
|
///
|
||||||
|
/// <para><b>Where it loads/saves.</b> The active file lives next to the
|
||||||
|
/// simulator's own config: <c>%APPDATA%\MaSzyna\eu07.ini</c> on Windows or
|
||||||
|
/// <c>~/.config/MaSzyna/eu07.ini</c> elsewhere. If that file does not exist yet,
|
||||||
|
/// defaults are read from an <c>eu07.ini</c> in the launcher's working directory
|
||||||
|
/// (the same fallback the simulator uses). Saving always writes to the per-user
|
||||||
|
/// path.</para>
|
||||||
|
///
|
||||||
|
/// <para><b>Unknown keys are kept.</b> The whole file is round-tripped through
|
||||||
|
/// <see cref="ConfigFile"/>; only the keys the UI actually edits are rewritten,
|
||||||
|
/// so any extra settings the launcher does not model survive a save.</para>
|
||||||
|
///
|
||||||
|
/// <para>Some mappings are best-effort where the wiki does not document an exact
|
||||||
|
/// key; those are marked with NOTE and can be adjusted against the running exe.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Settings
|
public sealed class Settings
|
||||||
{
|
{
|
||||||
public static Settings Instance { get; } = new();
|
public static Settings Instance { get; } = new();
|
||||||
|
|
||||||
// General
|
/// <summary>
|
||||||
public string Language = "English";
|
/// Pulls the current values out of the Settings view into this instance.
|
||||||
public bool Fullscreen;
|
/// The view assigns this when it is constructed; null if the view was never
|
||||||
public bool PauseWhenInactive;
|
/// opened (in which case Save just re-writes the loaded values).
|
||||||
public bool PauseOnStart;
|
/// </summary>
|
||||||
public int CursorSensitivity = 3;
|
public Action? CaptureFromUi { get; set; }
|
||||||
public bool InvertMouseHorizontal;
|
|
||||||
public bool InvertMouseVertical;
|
|
||||||
|
|
||||||
// Other
|
/// <summary>True if the loaded config explicitly contained a <c>lang</c> entry.</summary>
|
||||||
public string ExecutablePath = "eu07.exe"; // game executable used to launch
|
public bool LanguageWasSet { get; private set; }
|
||||||
public bool SelectExeAutomatically = true;
|
|
||||||
public bool DebugMode;
|
|
||||||
public bool VirtualShunting;
|
|
||||||
|
|
||||||
// Graphics (subset)
|
// ── General ───────────────────────────────────────────────────────────
|
||||||
public string Resolution = "1280x720";
|
public string Language = "English"; // lang (pl/en)
|
||||||
public bool VSync = true;
|
public bool Fullscreen; // fullscreen
|
||||||
public bool RenderShadows = true;
|
public bool PauseWhenInactive = true; // inactivepause
|
||||||
|
public bool PauseOnStart; // pause
|
||||||
|
public int CursorSensitivity = 3; // |mousescale x|
|
||||||
|
public bool InvertMouseHorizontal; // mousescale x < 0
|
||||||
|
public bool InvertMouseVertical; // mousescale y < 0
|
||||||
|
|
||||||
// Sound
|
// ── Communication ─────────────────────────────────────────────────────
|
||||||
public bool SoundEnabled = true;
|
public bool IgnoreGamepad; // input.gamepad (inverted)
|
||||||
public int Volume = 100;
|
public int FeedbackMode; // feedbackmode (0-5)
|
||||||
|
|
||||||
// Starter
|
// ── Other ─────────────────────────────────────────────────────────────
|
||||||
public bool AutoCloseStarter;
|
public bool SelectExeAutomatically = true; // starter.exe.auto (launcher-only)
|
||||||
public bool LargeThumbnails;
|
public string ExecutablePath = "eu07.exe"; // starter.exe.path (launcher-only)
|
||||||
public bool AutoExpandSceneryTree;
|
public bool DebugMode; // debugmode
|
||||||
|
public bool VirtualShunting; // ai.trainman
|
||||||
|
|
||||||
/// <summary>Loads settings from disk. TODO: implement persistence.</summary>
|
// ── Graphics ──────────────────────────────────────────────────────────
|
||||||
|
public int RenderEngine; // gfxrenderer (index, see RenderEngines)
|
||||||
|
public int Width = 1280; // width
|
||||||
|
public int Height = 720; // height
|
||||||
|
public int BufferScalePercent = 100; // starter.bufferscale (NOTE: launcher-only)
|
||||||
|
public int MaxTextureSize = 4096; // maxtexturesize
|
||||||
|
public int MaxCabTextureSize = 4096; // maxcabtexturesize
|
||||||
|
public int TextureFiltering = 8; // anisotropicfiltering (1/2/4/8/16)
|
||||||
|
public int Multisampling = 2; // multisampling (0-3)
|
||||||
|
public double DrawRangeFactor = 1.0; // gfx.drawrange.factor.max
|
||||||
|
public bool VSync = true; // vsync
|
||||||
|
public bool Smoke = true; // gfx.smoke
|
||||||
|
public int SmokeFidelity = 1; // gfx.smoke.fidelity (1-4)
|
||||||
|
public int Tonemapping; // gfx.postfx.tonemapping (NOTE: 0=reinhard,1=aces)
|
||||||
|
public bool ChromaticAberration; // gfx.postfx.chromaticaberration.enabled
|
||||||
|
public bool MotionBlur = true; // gfx.postfx.motionblur.enabled
|
||||||
|
public bool ExtraEffects = true; // gfx.extraeffects
|
||||||
|
public bool EnvMap = true; // gfx.envmap.enabled
|
||||||
|
public bool UseVbo = true; // usevbo
|
||||||
|
public bool RenderShadows = true; // shadows + gfx.shadowmap.enabled
|
||||||
|
public int ReflectionsFramerate = 60; // gfx.reflections.framerate
|
||||||
|
public int ShadowMapResolution = 4096; // shadowtune[0]
|
||||||
|
public int ShadowProjectionRange = 250; // shadowtune[1]
|
||||||
|
public int CabShadowsRange = 30; // gfx.shadows.cab.range
|
||||||
|
public int ShadowRankCutoff = 3; // gfx.shadow.rank.cutoff (NOTE)
|
||||||
|
public int ReflectionsFidelity = 2; // gfx.reflections.fidelity (0-2)
|
||||||
|
public int FieldOfView = 45; // fieldofview (15-75) NOTE: 'fovSlider' in XAML
|
||||||
|
public bool PythonScreens = true; // python.enabled
|
||||||
|
public bool PythonThreadedUpload = true; // python.threadedupload
|
||||||
|
public int ScreenRendererPriority = 4; // pyscreenrendererpriority (1-5, see ScreenPriorities)
|
||||||
|
|
||||||
|
// ── Physics ───────────────────────────────────────────────────────────
|
||||||
|
public int SplineFidelity = 1; // splinefidelity (1-4)
|
||||||
|
public bool FullPhysics = true; // fullphysics
|
||||||
|
public bool EnableTraction = true; // enabletraction (pantograph can break)
|
||||||
|
public bool LiveTraction; // livetraction
|
||||||
|
public bool PhysicsLog; // physicslog (speedometer tapes)
|
||||||
|
public bool DebugLog = true; // debuglog (3 / 0)
|
||||||
|
public bool MultipleLogs; // multiplelogs
|
||||||
|
public bool DisplaySimulation = true; // gfx.skiprendering (inverted)
|
||||||
|
public bool CrashDamage = true; // crashdamage
|
||||||
|
|
||||||
|
// ── Sound ─────────────────────────────────────────────────────────────
|
||||||
|
public bool SoundEnabled = true; // soundenabled
|
||||||
|
public int Volume = 100; // sound.volume (0-2 → 0-100)
|
||||||
|
public int RadioVolume = 80; // sound.volume.radio (0-1 → 0-100)
|
||||||
|
public int VehiclesVolume = 100; // sound.volume.vehicle
|
||||||
|
public int PositionalVolume = 100; // sound.volume.positional
|
||||||
|
public int AmbientVolume = 80; // sound.volume.ambient
|
||||||
|
public int PausedVolume; // sound.volume.paused
|
||||||
|
|
||||||
|
// ── Starter (launcher-only, stored under starter.* keys) ───────────────
|
||||||
|
public bool AutoCloseStarter; // starter.autoclose
|
||||||
|
public bool LargeThumbnails; // starter.largethumbnails
|
||||||
|
public bool AutoExpandSceneryTree; // starter.expandtree
|
||||||
|
|
||||||
|
/// <summary>gfxrenderer tokens in the order shown by the render-engine combo.</summary>
|
||||||
|
public static readonly string[] RenderEngines =
|
||||||
|
{ "full", "legacy", "simpleshader", "simple", "off", "experimental" };
|
||||||
|
|
||||||
|
/// <summary>Anisotropic-filtering steps for the texture-quality slider (1-5).</summary>
|
||||||
|
public static readonly int[] AnisotropySteps = { 1, 2, 4, 8, 16 };
|
||||||
|
|
||||||
|
/// <summary>pyscreenrendererpriority tokens indexed by the slider value (1-5).</summary>
|
||||||
|
public static readonly string[] ScreenPriorities =
|
||||||
|
{ "off", "idle", "lowest", "lower", "normal" };
|
||||||
|
|
||||||
|
// Tokens of mousescale / shadowtune that the UI does not edit but must keep.
|
||||||
|
private double _mouseScaleYMagnitude = 0.5;
|
||||||
|
private string[] _shadowTuneExtra = { "400", "300" }; // tokens [2], [3]
|
||||||
|
|
||||||
|
private ConfigFile _config = new();
|
||||||
|
private string _savePath = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Resolves the per-user eu07.ini path for the current OS.</summary>
|
||||||
|
public static string UserConfigPath()
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
string? appData = Environment.GetEnvironmentVariable("APPDATA");
|
||||||
|
if (!string.IsNullOrEmpty(appData))
|
||||||
|
return Path.Combine(appData, "MaSzyna", "eu07.ini");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string? home = Environment.GetEnvironmentVariable("HOME");
|
||||||
|
if (!string.IsNullOrEmpty(home))
|
||||||
|
return Path.Combine(home, ".config", "MaSzyna", "eu07.ini");
|
||||||
|
}
|
||||||
|
// Last resort: alongside the launcher.
|
||||||
|
return Path.Combine(AppContext.BaseDirectory, "eu07.ini");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Default config shipped in the launcher's working directory.</summary>
|
||||||
|
private static string DefaultConfigPath() =>
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "eu07.ini");
|
||||||
|
|
||||||
|
private static readonly Encoding FileEncoding = ResolveEncoding();
|
||||||
|
|
||||||
|
private static Encoding ResolveEncoding()
|
||||||
|
{
|
||||||
|
// eu07.ini comments use Polish diacritics in Windows-1250; preserve them.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
return Encoding.GetEncoding(1250);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Encoding.UTF8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Loads settings from the per-user file, or the working-dir default.</summary>
|
||||||
public void Load()
|
public void Load()
|
||||||
{
|
{
|
||||||
// intentionally empty for now
|
_savePath = UserConfigPath();
|
||||||
|
string source = File.Exists(_savePath) ? _savePath : DefaultConfigPath();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_config = File.Exists(source)
|
||||||
|
? ConfigFile.Parse(File.ReadAllText(source, FileEncoding))
|
||||||
|
: new ConfigFile();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_config = new ConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadFromConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Persists settings to disk. TODO: implement persistence.</summary>
|
/// <summary>Captures the latest UI values (if the view is open) and saves.</summary>
|
||||||
|
public void CaptureAndSave()
|
||||||
|
{
|
||||||
|
CaptureFromUi?.Invoke();
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Persists settings to the per-user eu07.ini, keeping unknown keys.</summary>
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
// intentionally empty for now
|
if (string.IsNullOrEmpty(_savePath))
|
||||||
|
_savePath = UserConfigPath();
|
||||||
|
|
||||||
|
WriteToConfig();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? dir = Path.GetDirectoryName(_savePath);
|
||||||
|
if (!string.IsNullOrEmpty(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
File.WriteAllText(_savePath, _config.ToText(), FileEncoding);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Could not write (permissions / path). Leave values in memory.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── config → fields ───────────────────────────────────────────────────
|
||||||
|
private void ReadFromConfig()
|
||||||
|
{
|
||||||
|
var c = _config;
|
||||||
|
|
||||||
|
// General
|
||||||
|
LanguageWasSet = c.Has("lang");
|
||||||
|
Language = c.GetString("lang", "en").ToLowerInvariant() == "pl" ? "Polski" : "English";
|
||||||
|
Fullscreen = c.GetBool("fullscreen", false);
|
||||||
|
PauseWhenInactive = c.GetBool("inactivepause", true);
|
||||||
|
PauseOnStart = c.GetBool("pause", false);
|
||||||
|
|
||||||
|
var mouse = c.GetTokens("mousescale");
|
||||||
|
if (mouse.Length >= 1 && double.TryParse(mouse[0], NumberStyles.Float, CultureInfo.InvariantCulture, out double mx))
|
||||||
|
{
|
||||||
|
InvertMouseHorizontal = mx < 0;
|
||||||
|
CursorSensitivity = Clamp((int)Math.Round(Math.Abs(mx)), 1, 5);
|
||||||
|
}
|
||||||
|
if (mouse.Length >= 2 && double.TryParse(mouse[1], NumberStyles.Float, CultureInfo.InvariantCulture, out double my))
|
||||||
|
{
|
||||||
|
InvertMouseVertical = my < 0;
|
||||||
|
_mouseScaleYMagnitude = Math.Abs(my);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Communication
|
||||||
|
IgnoreGamepad = !c.GetBool("input.gamepad", true);
|
||||||
|
FeedbackMode = Clamp(c.GetInt("feedbackmode", 0), 0, 5);
|
||||||
|
|
||||||
|
// Other
|
||||||
|
SelectExeAutomatically = c.GetBool("starter.exe.auto", true);
|
||||||
|
ExecutablePath = c.GetString("starter.exe.path", "eu07.exe");
|
||||||
|
DebugMode = c.GetBool("debugmode", false);
|
||||||
|
VirtualShunting = c.GetBool("ai.trainman", false);
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
RenderEngine = IndexOf(RenderEngines, c.GetString("gfxrenderer", "full"), 0);
|
||||||
|
Width = c.GetInt("width", 1280);
|
||||||
|
Height = c.GetInt("height", 720);
|
||||||
|
BufferScalePercent = Clamp(c.GetInt("starter.bufferscale", 100), 50, 200);
|
||||||
|
MaxTextureSize = c.GetInt("maxtexturesize", 4096);
|
||||||
|
MaxCabTextureSize = c.GetInt("maxcabtexturesize", 4096);
|
||||||
|
TextureFiltering = c.GetInt("anisotropicfiltering", 8);
|
||||||
|
Multisampling = Clamp(c.GetInt("multisampling", 2), 0, 3);
|
||||||
|
DrawRangeFactor = c.GetDouble("gfx.drawrange.factor.max", 1.0);
|
||||||
|
VSync = c.GetBool("vsync", true);
|
||||||
|
Smoke = c.GetBool("gfx.smoke", true);
|
||||||
|
SmokeFidelity = Clamp(c.GetInt("gfx.smoke.fidelity", 1), 1, 4);
|
||||||
|
Tonemapping = c.GetString("gfx.postfx.tonemapping", "reinhard").ToLowerInvariant() == "aces" ? 1 : 0;
|
||||||
|
ChromaticAberration = c.GetBool("gfx.postfx.chromaticaberration.enabled", false);
|
||||||
|
MotionBlur = c.GetBool("gfx.postfx.motionblur.enabled", true);
|
||||||
|
ExtraEffects = c.GetBool("gfx.extraeffects", true);
|
||||||
|
EnvMap = c.GetBool("gfx.envmap.enabled", true);
|
||||||
|
UseVbo = c.GetBool("usevbo", true);
|
||||||
|
RenderShadows = c.GetBool("shadows", true);
|
||||||
|
ReflectionsFramerate = c.GetInt("gfx.reflections.framerate", 60);
|
||||||
|
|
||||||
|
var shadowTune = c.GetTokens("shadowtune");
|
||||||
|
if (shadowTune.Length >= 1) ShadowMapResolution = ParseInt(shadowTune[0], 4096);
|
||||||
|
if (shadowTune.Length >= 2) ShadowProjectionRange = ParseInt(shadowTune[1], 250);
|
||||||
|
if (shadowTune.Length >= 4) _shadowTuneExtra = new[] { shadowTune[2], shadowTune[3] };
|
||||||
|
|
||||||
|
CabShadowsRange = (int)Math.Round(c.GetDouble("gfx.shadows.cab.range", 30));
|
||||||
|
ShadowRankCutoff = c.GetInt("gfx.shadow.rank.cutoff", 3);
|
||||||
|
ReflectionsFidelity = Clamp(c.GetInt("gfx.reflections.fidelity", 2), 0, 2);
|
||||||
|
FieldOfView = Clamp((int)Math.Round(c.GetDouble("fieldofview", 45)), 15, 75);
|
||||||
|
PythonScreens = c.GetBool("python.enabled", true);
|
||||||
|
PythonThreadedUpload = c.GetBool("python.threadedupload", true);
|
||||||
|
ScreenRendererPriority = IndexOf(ScreenPriorities, c.GetString("pyscreenrendererpriority", "lower"), 3) + 1;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
SplineFidelity = Clamp(c.GetInt("splinefidelity", 1), 1, 4);
|
||||||
|
FullPhysics = c.GetBool("fullphysics", true);
|
||||||
|
EnableTraction = c.GetBool("enabletraction", true);
|
||||||
|
LiveTraction = c.GetBool("livetraction", false);
|
||||||
|
PhysicsLog = c.GetBool("physicslog", false);
|
||||||
|
DebugLog = c.GetInt("debuglog", 3) != 0;
|
||||||
|
MultipleLogs = c.GetBool("multiplelogs", false);
|
||||||
|
DisplaySimulation = !c.GetBool("gfx.skiprendering", false);
|
||||||
|
CrashDamage = c.GetBool("crashdamage", true);
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
SoundEnabled = c.GetBool("soundenabled", true);
|
||||||
|
Volume = Clamp((int)Math.Round(c.GetDouble("sound.volume", 1.5) * 50), 1, 100);
|
||||||
|
RadioVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.radio", 0.8) * 100), 1, 100);
|
||||||
|
VehiclesVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.vehicle", 1.0) * 100), 1, 100);
|
||||||
|
PositionalVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.positional", 1.0) * 100), 1, 100);
|
||||||
|
AmbientVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.ambient", 0.8) * 100), 1, 100);
|
||||||
|
PausedVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.paused", 0.0) * 100), 0, 100);
|
||||||
|
|
||||||
|
// Starter
|
||||||
|
AutoCloseStarter = c.GetBool("starter.autoclose", false);
|
||||||
|
LargeThumbnails = c.GetBool("starter.largethumbnails", false);
|
||||||
|
AutoExpandSceneryTree = c.GetBool("starter.expandtree", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── fields → config ───────────────────────────────────────────────────
|
||||||
|
private void WriteToConfig()
|
||||||
|
{
|
||||||
|
var c = _config;
|
||||||
|
|
||||||
|
// General
|
||||||
|
c.Set("lang", Language == "Polski" ? "pl" : "en");
|
||||||
|
c.SetBool("fullscreen", Fullscreen);
|
||||||
|
c.SetBool("inactivepause", PauseWhenInactive);
|
||||||
|
c.SetBool("pause", PauseOnStart);
|
||||||
|
|
||||||
|
double mx = CursorSensitivity * (InvertMouseHorizontal ? -1 : 1);
|
||||||
|
double my = _mouseScaleYMagnitude * (InvertMouseVertical ? -1 : 1);
|
||||||
|
c.Set("mousescale",
|
||||||
|
$"{mx.ToString("0.###", CultureInfo.InvariantCulture)} " +
|
||||||
|
$"{my.ToString("0.###", CultureInfo.InvariantCulture)}");
|
||||||
|
|
||||||
|
// Communication
|
||||||
|
c.SetBool("input.gamepad", !IgnoreGamepad);
|
||||||
|
c.SetInt("feedbackmode", FeedbackMode);
|
||||||
|
|
||||||
|
// Other
|
||||||
|
c.SetBool("starter.exe.auto", SelectExeAutomatically);
|
||||||
|
c.Set("starter.exe.path", ExecutablePath);
|
||||||
|
c.SetBool("debugmode", DebugMode);
|
||||||
|
c.SetBool("ai.trainman", VirtualShunting);
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
c.Set("gfxrenderer", RenderEngines[Clamp(RenderEngine, 0, RenderEngines.Length - 1)]);
|
||||||
|
c.SetInt("width", Width);
|
||||||
|
c.SetInt("height", Height);
|
||||||
|
c.SetInt("starter.bufferscale", BufferScalePercent);
|
||||||
|
c.SetInt("maxtexturesize", MaxTextureSize);
|
||||||
|
c.SetInt("maxcabtexturesize", MaxCabTextureSize);
|
||||||
|
c.SetInt("anisotropicfiltering", TextureFiltering);
|
||||||
|
c.SetInt("multisampling", Multisampling);
|
||||||
|
c.SetDouble("gfx.drawrange.factor.max", DrawRangeFactor);
|
||||||
|
c.SetBool("vsync", VSync);
|
||||||
|
c.SetBool("gfx.smoke", Smoke);
|
||||||
|
c.SetInt("gfx.smoke.fidelity", SmokeFidelity);
|
||||||
|
c.Set("gfx.postfx.tonemapping", Tonemapping == 1 ? "aces" : "reinhard");
|
||||||
|
c.SetBool("gfx.postfx.chromaticaberration.enabled", ChromaticAberration);
|
||||||
|
c.SetBool("gfx.postfx.motionblur.enabled", MotionBlur);
|
||||||
|
c.SetBool("gfx.extraeffects", ExtraEffects);
|
||||||
|
c.SetBool("gfx.envmap.enabled", EnvMap);
|
||||||
|
c.SetBool("usevbo", UseVbo);
|
||||||
|
c.SetBool("shadows", RenderShadows);
|
||||||
|
c.SetBool("gfx.shadowmap.enabled", RenderShadows);
|
||||||
|
c.SetInt("gfx.reflections.framerate", ReflectionsFramerate);
|
||||||
|
c.Set("shadowtune",
|
||||||
|
$"{ShadowMapResolution} {ShadowProjectionRange} " +
|
||||||
|
$"{_shadowTuneExtra[0]} {_shadowTuneExtra[1]}");
|
||||||
|
c.SetInt("gfx.shadows.cab.range", CabShadowsRange);
|
||||||
|
c.SetInt("gfx.shadow.rank.cutoff", ShadowRankCutoff);
|
||||||
|
c.SetInt("gfx.reflections.fidelity", ReflectionsFidelity);
|
||||||
|
c.SetInt("fieldofview", FieldOfView);
|
||||||
|
c.SetBool("python.enabled", PythonScreens);
|
||||||
|
c.SetBool("python.threadedupload", PythonThreadedUpload);
|
||||||
|
c.Set("pyscreenrendererpriority",
|
||||||
|
ScreenPriorities[Clamp(ScreenRendererPriority - 1, 0, ScreenPriorities.Length - 1)]);
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
c.SetInt("splinefidelity", SplineFidelity);
|
||||||
|
c.SetBool("fullphysics", FullPhysics);
|
||||||
|
c.SetBool("enabletraction", EnableTraction);
|
||||||
|
c.SetBool("livetraction", LiveTraction);
|
||||||
|
c.SetBool("physicslog", PhysicsLog);
|
||||||
|
c.SetInt("debuglog", DebugLog ? 3 : 0);
|
||||||
|
c.SetBool("multiplelogs", MultipleLogs);
|
||||||
|
c.SetBool("gfx.skiprendering", !DisplaySimulation);
|
||||||
|
c.SetBool("crashdamage", CrashDamage);
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
c.SetBool("soundenabled", SoundEnabled);
|
||||||
|
c.SetDouble("sound.volume", Volume / 50.0);
|
||||||
|
c.SetDouble("sound.volume.radio", RadioVolume / 100.0);
|
||||||
|
c.SetDouble("sound.volume.vehicle", VehiclesVolume / 100.0);
|
||||||
|
c.SetDouble("sound.volume.positional", PositionalVolume / 100.0);
|
||||||
|
c.SetDouble("sound.volume.ambient", AmbientVolume / 100.0);
|
||||||
|
c.SetDouble("sound.volume.paused", PausedVolume / 100.0);
|
||||||
|
|
||||||
|
// Starter
|
||||||
|
c.SetBool("starter.autoclose", AutoCloseStarter);
|
||||||
|
c.SetBool("starter.largethumbnails", LargeThumbnails);
|
||||||
|
c.SetBool("starter.expandtree", AutoExpandSceneryTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── small helpers ──────────────────────────────────────────────────────
|
||||||
|
private static int Clamp(int v, int lo, int hi) => v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
|
||||||
|
private static int ParseInt(string s, int fallback) =>
|
||||||
|
int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out int n) ? n : fallback;
|
||||||
|
|
||||||
|
private static int IndexOf(string[] values, string token, int fallback)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
if (string.Equals(values[i], token, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return i;
|
||||||
|
return fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ public partial class Scenarios : UserControl
|
|||||||
Tag = i
|
Tag = i
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refresh the consist preview when the view is shown again (e.g. after
|
||||||
|
// editing the consist in the depot)
|
||||||
|
AttachedToVisualTree += (_, _) => RefreshSelectedConsist();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SceneryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
private void SceneryList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
@@ -89,16 +93,14 @@ public partial class Scenarios : UserControl
|
|||||||
|
|
||||||
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
consistStack.Children.Clear();
|
|
||||||
|
|
||||||
// get selected scenery
|
// get selected scenery
|
||||||
var tItem = sceneryList.SelectedItem as TreeViewItem;
|
var tItem = sceneryList.SelectedItem as TreeViewItem;
|
||||||
if (tItem?.Tag is not int tTag)
|
if (tItem?.Tag is not int tTag)
|
||||||
return;
|
return;
|
||||||
Scenery selectedScn = sceneries[tTag];
|
Scenery selectedScn = sceneries[tTag];
|
||||||
|
|
||||||
// get selected trainset
|
// get selected trainset
|
||||||
ListBoxItem vItem = vehicleList.SelectedItem as ListBoxItem;
|
ListBoxItem? vItem = vehicleList.SelectedItem as ListBoxItem;
|
||||||
if (vItem?.Tag is not int vTag)
|
if (vItem?.Tag is not int vTag)
|
||||||
return;
|
return;
|
||||||
Trainset selectedTrainset = selectedScn.Trainsets[vTag];
|
Trainset selectedTrainset = selectedScn.Trainsets[vTag];
|
||||||
@@ -107,29 +109,41 @@ public partial class Scenarios : UserControl
|
|||||||
AppState.Instance.CurrentScenery = selectedScn;
|
AppState.Instance.CurrentScenery = selectedScn;
|
||||||
AppState.Instance.CurrentTrainset = selectedTrainset;
|
AppState.Instance.CurrentTrainset = selectedTrainset;
|
||||||
|
|
||||||
|
ShowConsist(selectedTrainset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders the consist preview for a trainset (re-reads its current vehicles
|
||||||
|
// so depot edits are reflected).
|
||||||
|
private void ShowConsist(Trainset trainset)
|
||||||
|
{
|
||||||
|
consistStack.Children.Clear();
|
||||||
|
|
||||||
var db = GameData.Instance.Vehicles;
|
var db = GameData.Instance.Vehicles;
|
||||||
foreach (var train in selectedTrainset.Vehicles)
|
foreach (var train in trainset.Vehicles)
|
||||||
{
|
{
|
||||||
// thumbnail name comes from the matching texture's texture_mini
|
// thumbnail name comes from the matching texture's texture_mini
|
||||||
string miniName = db.MiniForSkin(train.SkinFile) ?? train.SkinFile;
|
string miniName = db.MiniForSkin(train.SkinFile) ?? train.SkinFile;
|
||||||
string? path = VehicleDatabase.MiniPath(miniName);
|
string? path = VehicleDatabase.MiniPath(miniName);
|
||||||
if (path is null)
|
if (path is null)
|
||||||
{
|
continue; // fallback: no mini
|
||||||
// fallback
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bitmap = new Bitmap(path);
|
consistStack.Children.Add(new Image
|
||||||
var image = new Image
|
|
||||||
{
|
{
|
||||||
Source = bitmap,
|
Source = new Bitmap(path),
|
||||||
Height = 45,
|
Height = 45,
|
||||||
Stretch = Avalonia.Media.Stretch.Uniform,
|
Stretch = Avalonia.Media.Stretch.Uniform,
|
||||||
Margin = new Thickness(0)
|
Margin = new Thickness(0)
|
||||||
};
|
});
|
||||||
consistStack.Children.Add(image);
|
|
||||||
}
|
}
|
||||||
missionDescription.Text = selectedTrainset.Description;
|
missionDescription.Text = trainset.Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When returning to this view, refresh the preview of the selected consist
|
||||||
|
// in case it was modified in the depot.
|
||||||
|
private void RefreshSelectedConsist()
|
||||||
|
{
|
||||||
|
if (AppState.Instance.CurrentTrainset is { } trainset)
|
||||||
|
ShowConsist(trainset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exports the (possibly depot-modified) scenery to a $-prefixed copy and
|
// Exports the (possibly depot-modified) scenery to a $-prefixed copy and
|
||||||
@@ -159,6 +173,9 @@ public partial class Scenarios : UserControl
|
|||||||
.FirstOrDefault(v => v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver)?.Name
|
.FirstOrDefault(v => v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver)?.Name
|
||||||
?? trainset?.Vehicles.FirstOrDefault()?.Name;
|
?? trainset?.Vehicles.FirstOrDefault()?.Name;
|
||||||
|
|
||||||
|
// make sure the latest settings are on disk before the sim reads them
|
||||||
|
StarterNG.Classes.Settings.Instance.CaptureAndSave();
|
||||||
|
|
||||||
// launch the game: -s $<name>.scn -v <vehicle>
|
// launch the game: -s $<name>.scn -v <vehicle>
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@@ -7,376 +7,392 @@
|
|||||||
xmlns:starterNg="clr-namespace:StarterNG"
|
xmlns:starterNg="clr-namespace:StarterNG"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="StarterNG.Views.Settings">
|
x:Class="StarterNG.Views.Settings">
|
||||||
<suki:SettingsLayout>
|
<DockPanel>
|
||||||
<suki:SettingsLayout.Items>
|
<!-- Save / reset bar -->
|
||||||
<objectModel:ObservableCollection x:TypeArguments="suki:SettingsLayoutItem">
|
<Border DockPanel.Dock="Bottom" Padding="16,10" Background="#11000000">
|
||||||
<suki:SettingsLayoutItem Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
|
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Right">
|
||||||
<suki:SettingsLayoutItem.Content>
|
<TextBlock x:Name="SaveStatus" VerticalAlignment="Center" Opacity="0.8" Text="" />
|
||||||
|
<Button x:Name="ResetButton" Classes="Flat" Click="ResetButton_OnClick"
|
||||||
|
Content="{Binding [Reset], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
<Button x:Name="SaveButton" Classes="Flat Accent" Click="SaveButton_OnClick"
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
Content="{Binding [Save], Source={x:Static starterNg:App.Loc}}" />
|
||||||
<Label Content="{Binding [Language], Source={x:Static starterNg:App.Loc}}" VerticalAlignment="Center" FontSize="14"/>
|
</StackPanel>
|
||||||
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350" SelectedIndex="1" SelectionChanged="LanguageComboBox_OnSelectionChanged">
|
</Border>
|
||||||
<ComboBoxItem>Polski</ComboBoxItem>
|
|
||||||
<ComboBoxItem>English</ComboBoxItem>
|
<suki:SettingsLayout>
|
||||||
</ComboBox>
|
<suki:SettingsLayout.Items>
|
||||||
|
<objectModel:ObservableCollection x:TypeArguments="suki:SettingsLayoutItem">
|
||||||
|
<suki:SettingsLayoutItem Header="{Binding [General], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<suki:SettingsLayoutItem.Content>
|
||||||
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<Label Content="{Binding [Language], Source={x:Static starterNg:App.Loc}}" VerticalAlignment="Center" FontSize="14"/>
|
||||||
|
<ComboBox x:Name="ChangeLanguageCb" MinWidth="350" MaxWidth="350" SelectedIndex="1" SelectionChanged="LanguageComboBox_OnSelectionChanged">
|
||||||
|
<ComboBoxItem>Polski</ComboBoxItem>
|
||||||
|
<ComboBoxItem>English</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox x:Name="FullscreenCb" Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="PauseInactiveCb" Content="{Binding [PauseSimInactive], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
|
<CheckBox x:Name="PauseStartCb" Content="{Binding [PauseSimStart], Source={x:Static starterNg:App.Loc}}" />
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="CursorSensitivitySlider" Minimum="1" Maximum="5" TickFrequency="1" MinWidth="200" MaxWidth="200"
|
||||||
|
IsSnapToTickEnabled="True" Value="3">
|
||||||
|
</Slider>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox x:Name="MouseHorInvertCb" Content="{Binding [MouseHorInvert], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<CheckBox x:Name="MouseVertInvertCb" Content="{Binding [MouseVertInvert], Source={x:Static starterNg:App.Loc}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</suki:SettingsLayoutItem.Content>
|
||||||
<CheckBox Content="{Binding [Fullscreen], Source={x:Static starterNg:App.Loc}}"/>
|
</suki:SettingsLayoutItem>
|
||||||
|
<suki:SettingsLayoutItem Header="{Binding [Communication], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<suki:SettingsLayoutItem.Content>
|
||||||
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
|
<CheckBox x:Name="GamepadIgnoreCb" Content="{Binding [GamepadSignalIgnore], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
<CheckBox Content="{Binding [PauseSimInactive], Source={x:Static starterNg:App.Loc}}" />
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Feedback], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBox x:Name="FeedbackCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Tag="0" Content="{Binding [FeedbackOff], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Tag="1" Content="{Binding [FeedbackCASHP], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Tag="2" Content="{Binding [FeedbackCA], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Tag="3" Content="{Binding [FeedbackLPT], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Tag="4" Content="{Binding [FeedbackPoKeys], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<CheckBox Content="{Binding [PauseSimStart], Source={x:Static starterNg:App.Loc}}" />
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [CursorSensitivity], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" Maximum="5" TickFrequency="1" MinWidth="200" MaxWidth="200"
|
|
||||||
IsSnapToTickEnabled="True" Value="3">
|
|
||||||
</Slider>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</suki:SettingsLayoutItem.Content>
|
||||||
|
</suki:SettingsLayoutItem>
|
||||||
|
|
||||||
<CheckBox Content="{Binding [MouseHorInvert], Source={x:Static starterNg:App.Loc}}"/>
|
<suki:SettingsLayoutItem Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
|
||||||
<CheckBox Content="{Binding [MouseVertInvert], Source={x:Static starterNg:App.Loc}}" />
|
<suki:SettingsLayoutItem.Content>
|
||||||
</StackPanel>
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
</suki:SettingsLayoutItem.Content>
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
</suki:SettingsLayoutItem>
|
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<suki:SettingsLayoutItem Header="{Binding [Communication], Source={x:Static starterNg:App.Loc}}">
|
<ComboBox x:Name="SelectExeCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
||||||
<suki:SettingsLayoutItem.Content>
|
<ComboBoxItem Content="{Binding [SelectEXEAuto], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
<ComboBoxItem>eu07.exe</ComboBoxItem>
|
||||||
<CheckBox Content="{Binding [GamepadSignalIgnore], Source={x:Static starterNg:App.Loc}}"/>
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Feedback], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBox MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<ComboBoxItem Tag="0" Content="{Binding [FeedbackOff], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Tag="1" Content="{Binding [FeedbackCASHP], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Tag="2" Content="{Binding [FeedbackCA], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Tag="3" Content="{Binding [FeedbackLPT], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Tag="4" Content="{Binding [FeedbackPoKeys], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</suki:SettingsLayoutItem.Content>
|
||||||
|
</suki:SettingsLayoutItem>
|
||||||
|
|
||||||
</StackPanel>
|
<suki:SettingsLayoutItem Header="{Binding [Graphic], Source={x:Static starterNg:App.Loc}}">
|
||||||
</suki:SettingsLayoutItem.Content>
|
<suki:SettingsLayoutItem.Content>
|
||||||
</suki:SettingsLayoutItem>
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [RenderEngine], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBox x:Name="RenderEngineCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [REFull], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [REOld], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [RESimpShader], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [RESimp], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [REOff], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [REExperimental], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBox x:Name="ResolutionCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
||||||
|
<ComboBoxItem>3840x2160</ComboBoxItem>
|
||||||
|
<ComboBoxItem>2560x1440</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1920x1080</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1600x900</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1366x768</ComboBoxItem>
|
||||||
|
<ComboBoxItem>1280x720</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="bufferScale" Minimum="50" MaxWidth="400" HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="200" TickFrequency="1" IsSnapToTickEnabled="True"
|
||||||
|
Value="100" />
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{Binding #bufferScale.Value, StringFormat='{}{0}%'}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<suki:SettingsLayoutItem Header="{Binding [Others], Source={x:Static starterNg:App.Loc}}">
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
<suki:SettingsLayoutItem.Content>
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexResolution], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
<Slider Name="textureResolutionSlider" Minimum="9" MaxWidth="400"
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
HorizontalAlignment="Left"
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}"/>
|
MinWidth="250" Maximum="14"
|
||||||
<ComboBox MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
ValueChanged="TextureResolutionSlider_OnValueChanged" TickFrequency="1"
|
||||||
<ComboBoxItem Content="{Binding [SelectEXEAuto], Source={x:Static starterNg:App.Loc}}"/>
|
IsSnapToTickEnabled="True" Value="12" />
|
||||||
<ComboBoxItem>eu07.exe</ComboBoxItem>
|
<TextBlock Name="texResolution" VerticalAlignment="Center" Text="4096" />
|
||||||
</ComboBox>
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexCabResolution], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="cabTextureResolutionSlider" Minimum="9" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="14"
|
||||||
|
ValueChanged="CabTextureResolutionSlider_OnValueChanged" TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="12" />
|
||||||
|
<TextBlock Name="cabTexResolution" VerticalAlignment="Center" Text="4096" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TexFilteringQuality], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="TexFilteringSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="5"
|
||||||
|
TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="4" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Multisampling], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="MultisamplingSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="4"
|
||||||
|
TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="3" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderRange], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="RenderRangeSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="3" Value="1" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox x:Name="VSyncCb" Content="{Binding [VSync], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="SmokeDisplayCb" Content="{Binding [SmokeDisplay], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [SmokeParticlesMultiplier], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="SmokeParticlesSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="4"
|
||||||
|
TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="2" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Postprocessing], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBox x:Name="PostprocessingCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [PPReinhard], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [PPACES], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox x:Name="ChromaticAberrationCb" Content="{Binding [ChromaticAberration], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="MotionBlurCb" Content="{Binding [MotionBlur], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="AdditionalShadersCb" Content="{Binding [AdditionalShadersEffects], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="ReflectionsCubeMapCb" Content="{Binding [ReflectionsCubeMap], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="RenderVBOCb" Content="{Binding [RenderVBO], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<CheckBox x:Name="RenderShadowsCb" Content="{Binding [RenderShadows], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsFramerate], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="reflectionsFramerate" Minimum="5" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="120"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="30" />
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14"
|
||||||
|
Content="{Binding #reflectionsFramerate.Value, StringFormat='{}{0} FPS'}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsResolution], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="shaderResolutionSlider" Minimum="9" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="14"
|
||||||
|
ValueChanged="shaderResolutionSlider_OnValueChanged" TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="12" />
|
||||||
|
<TextBlock Name="shaderResolution" VerticalAlignment="Center" Text="4096 px" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShaderRange], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="shaderRange" Minimum="25" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="400"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="150" />
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14"
|
||||||
|
Content="{Binding #shaderRange.Value, StringFormat='{}{0} m'}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [CabShadowsRange], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="cabShaderSourceRange" Minimum="0" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="10"
|
||||||
|
IsSnapToTickEnabled="True" Value="30" />
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14"
|
||||||
|
Content="{Binding #cabShaderSourceRange.Value, StringFormat='{}{0} m'}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowDisplay], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBox x:Name="ShadowDisplayCb" MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [CSROnlyImportant], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [CSRLimited], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [CSRAll], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsDetails], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBox x:Name="ReflectionsDetailsCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="{Binding [RDTerrain], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [RDTerrainModels], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<ComboBoxItem Content="{Binding [RDTerrainModelsVehicles], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsCabRange], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider Name="fovSlider" Minimum="5" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="110"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="30" />
|
||||||
|
<Label VerticalAlignment="Center" FontSize="14"
|
||||||
|
Content="{Binding #fovSlider.Value, StringFormat='{}{0} stopni'}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<CheckBox x:Name="RenderScreensCb" Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<CheckBox x:Name="RenderScreensThreadCb" Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderScreensFramerate], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="RenderScreensFramerateSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="5"
|
||||||
|
TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="5" />
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</suki:SettingsLayoutItem.Content>
|
||||||
|
</suki:SettingsLayoutItem>
|
||||||
|
|
||||||
<CheckBox Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}"/>
|
<suki:SettingsLayoutItem Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
|
||||||
|
<suki:SettingsLayoutItem.Content>
|
||||||
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="TrackCurvesSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="4"
|
||||||
|
TickFrequency="1"
|
||||||
|
IsSnapToTickEnabled="True" Value="4" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<CheckBox Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}"/>
|
<CheckBox x:Name="PhysicsAccuracyCb" Content="{Binding [PhysicsAccuracyIncreased], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
</StackPanel>
|
|
||||||
</suki:SettingsLayoutItem.Content>
|
|
||||||
</suki:SettingsLayoutItem>
|
|
||||||
|
|
||||||
<suki:SettingsLayoutItem Header="{Binding [Graphic], Source={x:Static starterNg:App.Loc}}">
|
<CheckBox x:Name="PantographBreakCb" Content="{Binding [BreakingPantographPossibility], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<suki:SettingsLayoutItem.Content>
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
<CheckBox x:Name="OverheadOnlyCb" Content="{Binding [PowerOverheadLineOnly], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [RenderEngine], Source={x:Static starterNg:App.Loc}}"/>
|
<CheckBox x:Name="SpeedometerTapesCb" Content="{Binding [SaveSpeedometerTapes], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<ComboBox MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [REFull], Source={x:Static starterNg:App.Loc}}"/>
|
<CheckBox x:Name="SimLogsCb" Content="{Binding [SaveSimLogs], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<ComboBoxItem Content="{Binding [REOld], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [RESimpShader], Source={x:Static starterNg:App.Loc}}"/>
|
<CheckBox x:Name="KeepLogsCb" Content="{Binding [KeepPrevLogs], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<ComboBoxItem Content="{Binding [RESimp], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [REOff], Source={x:Static starterNg:App.Loc}}"/>
|
<CheckBox x:Name="DisplaySimulationCb" Content="{Binding [DisplaySimulation], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
<ComboBoxItem Content="{Binding [REExperimental], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</ComboBox>
|
<CheckBox x:Name="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
</suki:SettingsLayoutItem.Content>
|
||||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [Resolution], Source={x:Static starterNg:App.Loc}}"/>
|
</suki:SettingsLayoutItem>
|
||||||
<ComboBox MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
|
||||||
<ComboBoxItem>2560x1440</ComboBoxItem>
|
<suki:SettingsLayoutItem Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
|
||||||
</ComboBox>
|
<suki:SettingsLayoutItem.Content>
|
||||||
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
|
<CheckBox x:Name="EnableSoundsCb" Content="{Binding [EnableSounds], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Volume], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="VolumeSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RadiophoneVolume], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="RadioVolumeSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VehiclesVolume], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="VehiclesVolumeSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [PositionedSoundsVolume], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="PositionalVolumeSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [AmbientVolume], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="AmbientVolumeSlider" Minimum="1" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="100" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VolumeDuringPause], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<Slider x:Name="PauseVolumeSlider" Minimum="0" MaxWidth="400"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MinWidth="250" Maximum="100"
|
||||||
|
TickFrequency="5"
|
||||||
|
IsSnapToTickEnabled="True" Value="0" />
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
</suki:SettingsLayoutItem.Content>
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BufforResolution], Source={x:Static starterNg:App.Loc}}"/>
|
</suki:SettingsLayoutItem>
|
||||||
<Slider Name="bufferScale" Minimum="50" MaxWidth="400" HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="200" TickFrequency="1" IsSnapToTickEnabled="True"
|
<suki:SettingsLayoutItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
||||||
Value="100" />
|
<suki:SettingsLayoutItem.Content>
|
||||||
<TextBlock VerticalAlignment="Center"
|
<StackPanel Spacing="8" Orientation="Vertical">
|
||||||
Text="{Binding #bufferScale.Value, StringFormat='{}{0}%'}" />
|
<CheckBox x:Name="AutoCloseStarterCb" Content="{Binding [AutoCloseStarter], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<CheckBox x:Name="LargeThumbnailsCb" Content="{Binding [LargeThumbnails], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
|
<CheckBox x:Name="AutoExpandTreeCb" Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</suki:SettingsLayoutItem.Content>
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
</suki:SettingsLayoutItem>
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexResolution], Source={x:Static starterNg:App.Loc}}"/>
|
</objectModel:ObservableCollection>
|
||||||
<Slider Name="textureResolutionSlider" Minimum="9" MaxWidth="400"
|
</suki:SettingsLayout.Items>
|
||||||
HorizontalAlignment="Left"
|
</suki:SettingsLayout>
|
||||||
MinWidth="250" Maximum="14"
|
</DockPanel>
|
||||||
ValueChanged="TextureResolutionSlider_OnValueChanged" TickFrequency="1"
|
</UserControl>
|
||||||
IsSnapToTickEnabled="True" Value="12" />
|
|
||||||
<TextBlock Name="texResolution" VerticalAlignment="Center" Text="4096" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [MaxTexCabResolution], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Name="cabTextureResolutionSlider" Minimum="9" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="14"
|
|
||||||
ValueChanged="CabTextureResolutionSlider_OnValueChanged" TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="12" />
|
|
||||||
<TextBlock Name="cabTexResolution" VerticalAlignment="Center" Text="4096" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TexFilteringQuality], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="5"
|
|
||||||
TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="4" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Multisampling], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="4"
|
|
||||||
TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="3" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderRange], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="3" Value="1" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [VSync], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [SmokeDisplay], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [SmokeParticlesMultiplier], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="4"
|
|
||||||
TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="2" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Postprocessing], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBox MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [PPReinhard], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [PPACES], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [ChromaticAberration], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [MotionBlur], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [AdditionalShadersEffects], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [ReflectionsCubeMap], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [RenderVBO], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [RenderShadows], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsFramerate], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Name="reflectionsFramerate" Minimum="5" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="120"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14"
|
|
||||||
Content="{Binding #reflectionsFramerate.Value, StringFormat='{}{0} FPS'}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsResolution], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Name="shaderResolutionSlider" Minimum="9" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="14"
|
|
||||||
ValueChanged="shaderResolutionSlider_OnValueChanged" TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="12" />
|
|
||||||
<TextBlock Name="shaderResolution" VerticalAlignment="Center" Text="4096 px" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShaderRange], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Name="shaderRange" Minimum="25" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="400"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="150" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14"
|
|
||||||
Content="{Binding #shaderRange.Value, StringFormat='{}{0} m'}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [CabShadowsRange], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Name="cabShaderSourceRange" Minimum="0" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="10"
|
|
||||||
IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14"
|
|
||||||
Content="{Binding #cabShaderSourceRange.Value, StringFormat='{}{0} m'}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowDisplay], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBox MinWidth="150" MaxWidth="150" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [CSROnlyImportant], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [CSRLimited], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [CSRAll], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ReflectionsDetails], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBox MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
|
||||||
<ComboBoxItem Content="{Binding [RDTerrain], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [RDTerrainModels], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<ComboBoxItem Content="{Binding [RDTerrainModelsVehicles], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowsCabRange], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Name="fovSlider" Minimum="5" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="110"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="30" />
|
|
||||||
<Label VerticalAlignment="Center" FontSize="14"
|
|
||||||
Content="{Binding #fovSlider.Value, StringFormat='{}{0} stopni'}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [RenderScreens], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<CheckBox Content="{Binding [RenderScreensThread], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RenderScreensFramerate], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="5"
|
|
||||||
TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="5" />
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</suki:SettingsLayoutItem.Content>
|
|
||||||
</suki:SettingsLayoutItem>
|
|
||||||
|
|
||||||
<suki:SettingsLayoutItem Header="{Binding [Physics], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<suki:SettingsLayoutItem.Content>
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrackCurvesQuality], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="4"
|
|
||||||
TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True" Value="4" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [PhysicsAccuracyIncreased], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [BreakingPantographPossibility], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [PowerOverheadLineOnly], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [SaveSpeedometerTapes], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [SaveSimLogs], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [KeepPrevLogs], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [DisplaySimulation], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</suki:SettingsLayoutItem.Content>
|
|
||||||
</suki:SettingsLayoutItem>
|
|
||||||
|
|
||||||
<suki:SettingsLayoutItem Header="{Binding [Sounds], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<suki:SettingsLayoutItem.Content>
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
|
||||||
<CheckBox Content="{Binding [EnableSounds], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Volume], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [RadiophoneVolume], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VehiclesVolume], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [PositionedSoundsVolume], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [AmbientVolume], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [VolumeDuringPause], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<Slider Minimum="1" MaxWidth="400"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
MinWidth="250" Maximum="100"
|
|
||||||
TickFrequency="5"
|
|
||||||
IsSnapToTickEnabled="True" Value="100" />
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</suki:SettingsLayoutItem.Content>
|
|
||||||
</suki:SettingsLayoutItem>
|
|
||||||
|
|
||||||
<suki:SettingsLayoutItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
|
||||||
<suki:SettingsLayoutItem.Content>
|
|
||||||
<StackPanel Spacing="8" Orientation="Vertical">
|
|
||||||
<CheckBox Content="{Binding [AutoCloseStarter], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<CheckBox Content="{Binding [LargeThumbnails], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
<CheckBox Content="{Binding [AutoExpandSceneryTree], Source={x:Static starterNg:App.Loc}}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</suki:SettingsLayoutItem.Content>
|
|
||||||
</suki:SettingsLayoutItem>
|
|
||||||
</objectModel:ObservableCollection>
|
|
||||||
</suki:SettingsLayout.Items>
|
|
||||||
</suki:SettingsLayout>
|
|
||||||
</UserControl>
|
|
||||||
|
|||||||
@@ -1,30 +1,259 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml.Styling;
|
using Avalonia.Markup.Xaml.Styling;
|
||||||
|
using StarterNG.Classes;
|
||||||
|
|
||||||
namespace StarterNG.Views;
|
namespace StarterNG.Views;
|
||||||
|
|
||||||
public partial class Settings : UserControl
|
public partial class Settings : UserControl
|
||||||
{
|
{
|
||||||
|
private bool _loading;
|
||||||
|
|
||||||
public Settings()
|
public Settings()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
this.AttachedToVisualTree += (_, _) =>
|
this.AttachedToVisualTree += (_, _) =>
|
||||||
{
|
{
|
||||||
TextureResolutionSlider_OnValueChanged(null, null);
|
TextureResolutionSlider_OnValueChanged(null, null);
|
||||||
CabTextureResolutionSlider_OnValueChanged(null, null);
|
CabTextureResolutionSlider_OnValueChanged(null, null);
|
||||||
shaderResolutionSlider_OnValueChanged(null, null);
|
shaderResolutionSlider_OnValueChanged(null, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
ChangeLanguageCb.SelectedIndex = App.Loc.CurrentLanguage switch
|
ChangeLanguageCb.SelectedIndex = App.Loc.CurrentLanguage switch
|
||||||
{
|
{
|
||||||
"Polski" => 0,
|
"Polski" => 0,
|
||||||
_ => 1
|
_ => 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The settings instance pulls live values from here whenever the app
|
||||||
|
// closes or the game is launched.
|
||||||
|
StarterNG.Classes.Settings.Instance.CaptureFromUi = ReadFromUi;
|
||||||
|
|
||||||
|
ApplyToUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Settings instance → controls ──────────────────────────────────────
|
||||||
|
private void ApplyToUi()
|
||||||
|
{
|
||||||
|
var s = StarterNG.Classes.Settings.Instance;
|
||||||
|
_loading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// General
|
||||||
|
ChangeLanguageCb.SelectedIndex = s.Language == "Polski" ? 0 : 1;
|
||||||
|
FullscreenCb.IsChecked = s.Fullscreen;
|
||||||
|
PauseInactiveCb.IsChecked = s.PauseWhenInactive;
|
||||||
|
PauseStartCb.IsChecked = s.PauseOnStart;
|
||||||
|
CursorSensitivitySlider.Value = s.CursorSensitivity;
|
||||||
|
MouseHorInvertCb.IsChecked = s.InvertMouseHorizontal;
|
||||||
|
MouseVertInvertCb.IsChecked = s.InvertMouseVertical;
|
||||||
|
|
||||||
|
// Communication
|
||||||
|
GamepadIgnoreCb.IsChecked = s.IgnoreGamepad;
|
||||||
|
FeedbackCb.SelectedIndex = s.FeedbackMode;
|
||||||
|
|
||||||
|
// Other
|
||||||
|
SelectExeCb.SelectedIndex = s.SelectExeAutomatically ? 0 : 1;
|
||||||
|
DebugModeCb.IsChecked = s.DebugMode;
|
||||||
|
VirtualShuntingCb.IsChecked = s.VirtualShunting;
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
RenderEngineCb.SelectedIndex = s.RenderEngine;
|
||||||
|
SelectResolution(s.Width, s.Height);
|
||||||
|
bufferScale.Value = s.BufferScalePercent;
|
||||||
|
textureResolutionSlider.Value = Log2(s.MaxTextureSize, 12);
|
||||||
|
cabTextureResolutionSlider.Value = Log2(s.MaxCabTextureSize, 12);
|
||||||
|
TexFilteringSlider.Value = AnisotropyToSlider(s.TextureFiltering);
|
||||||
|
MultisamplingSlider.Value = s.Multisampling + 1;
|
||||||
|
RenderRangeSlider.Value = (int)Math.Round(s.DrawRangeFactor);
|
||||||
|
VSyncCb.IsChecked = s.VSync;
|
||||||
|
SmokeDisplayCb.IsChecked = s.Smoke;
|
||||||
|
SmokeParticlesSlider.Value = s.SmokeFidelity;
|
||||||
|
PostprocessingCb.SelectedIndex = s.Tonemapping;
|
||||||
|
ChromaticAberrationCb.IsChecked = s.ChromaticAberration;
|
||||||
|
MotionBlurCb.IsChecked = s.MotionBlur;
|
||||||
|
AdditionalShadersCb.IsChecked = s.ExtraEffects;
|
||||||
|
ReflectionsCubeMapCb.IsChecked = s.EnvMap;
|
||||||
|
RenderVBOCb.IsChecked = s.UseVbo;
|
||||||
|
RenderShadowsCb.IsChecked = s.RenderShadows;
|
||||||
|
reflectionsFramerate.Value = s.ReflectionsFramerate;
|
||||||
|
shaderResolutionSlider.Value = Log2(s.ShadowMapResolution, 12);
|
||||||
|
shaderRange.Value = s.ShadowProjectionRange;
|
||||||
|
cabShaderSourceRange.Value = s.CabShadowsRange;
|
||||||
|
ShadowDisplayCb.SelectedIndex = Clamp(s.ShadowRankCutoff - 1, 0, 2);
|
||||||
|
ReflectionsDetailsCb.SelectedIndex = s.ReflectionsFidelity;
|
||||||
|
fovSlider.Value = s.FieldOfView;
|
||||||
|
RenderScreensCb.IsChecked = s.PythonScreens;
|
||||||
|
RenderScreensThreadCb.IsChecked = s.PythonThreadedUpload;
|
||||||
|
RenderScreensFramerateSlider.Value = s.ScreenRendererPriority;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
TrackCurvesSlider.Value = s.SplineFidelity;
|
||||||
|
PhysicsAccuracyCb.IsChecked = s.FullPhysics;
|
||||||
|
PantographBreakCb.IsChecked = s.EnableTraction;
|
||||||
|
OverheadOnlyCb.IsChecked = s.LiveTraction;
|
||||||
|
SpeedometerTapesCb.IsChecked = s.PhysicsLog;
|
||||||
|
SimLogsCb.IsChecked = s.DebugLog;
|
||||||
|
KeepLogsCb.IsChecked = s.MultipleLogs;
|
||||||
|
DisplaySimulationCb.IsChecked = s.DisplaySimulation;
|
||||||
|
CrashDamageCb.IsChecked = s.CrashDamage;
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
EnableSoundsCb.IsChecked = s.SoundEnabled;
|
||||||
|
VolumeSlider.Value = s.Volume;
|
||||||
|
RadioVolumeSlider.Value = s.RadioVolume;
|
||||||
|
VehiclesVolumeSlider.Value = s.VehiclesVolume;
|
||||||
|
PositionalVolumeSlider.Value = s.PositionalVolume;
|
||||||
|
AmbientVolumeSlider.Value = s.AmbientVolume;
|
||||||
|
PauseVolumeSlider.Value = s.PausedVolume;
|
||||||
|
|
||||||
|
// Starter
|
||||||
|
AutoCloseStarterCb.IsChecked = s.AutoCloseStarter;
|
||||||
|
LargeThumbnailsCb.IsChecked = s.LargeThumbnails;
|
||||||
|
AutoExpandTreeCb.IsChecked = s.AutoExpandSceneryTree;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── controls → Settings instance ──────────────────────────────────────
|
||||||
|
private void ReadFromUi()
|
||||||
|
{
|
||||||
|
var s = StarterNG.Classes.Settings.Instance;
|
||||||
|
|
||||||
|
// General
|
||||||
|
s.Language = ChangeLanguageCb.SelectedIndex == 0 ? "Polski" : "English";
|
||||||
|
s.Fullscreen = IsChecked(FullscreenCb);
|
||||||
|
s.PauseWhenInactive = IsChecked(PauseInactiveCb);
|
||||||
|
s.PauseOnStart = IsChecked(PauseStartCb);
|
||||||
|
s.CursorSensitivity = (int)CursorSensitivitySlider.Value;
|
||||||
|
s.InvertMouseHorizontal = IsChecked(MouseHorInvertCb);
|
||||||
|
s.InvertMouseVertical = IsChecked(MouseVertInvertCb);
|
||||||
|
|
||||||
|
// Communication
|
||||||
|
s.IgnoreGamepad = IsChecked(GamepadIgnoreCb);
|
||||||
|
s.FeedbackMode = Math.Max(0, FeedbackCb.SelectedIndex);
|
||||||
|
|
||||||
|
// Other
|
||||||
|
s.SelectExeAutomatically = SelectExeCb.SelectedIndex == 0;
|
||||||
|
s.ExecutablePath = s.SelectExeAutomatically ? "eu07.exe"
|
||||||
|
: (SelectExeCb.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "eu07.exe";
|
||||||
|
s.DebugMode = IsChecked(DebugModeCb);
|
||||||
|
s.VirtualShunting = IsChecked(VirtualShuntingCb);
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
s.RenderEngine = Math.Max(0, RenderEngineCb.SelectedIndex);
|
||||||
|
ReadResolution(s);
|
||||||
|
s.BufferScalePercent = (int)bufferScale.Value;
|
||||||
|
s.MaxTextureSize = 1 << (int)textureResolutionSlider.Value;
|
||||||
|
s.MaxCabTextureSize = 1 << (int)cabTextureResolutionSlider.Value;
|
||||||
|
s.TextureFiltering = StarterNG.Classes.Settings.AnisotropySteps[
|
||||||
|
Clamp((int)TexFilteringSlider.Value - 1, 0, StarterNG.Classes.Settings.AnisotropySteps.Length - 1)];
|
||||||
|
s.Multisampling = Clamp((int)MultisamplingSlider.Value - 1, 0, 3);
|
||||||
|
s.DrawRangeFactor = RenderRangeSlider.Value;
|
||||||
|
s.VSync = IsChecked(VSyncCb);
|
||||||
|
s.Smoke = IsChecked(SmokeDisplayCb);
|
||||||
|
s.SmokeFidelity = (int)SmokeParticlesSlider.Value;
|
||||||
|
s.Tonemapping = Math.Max(0, PostprocessingCb.SelectedIndex);
|
||||||
|
s.ChromaticAberration = IsChecked(ChromaticAberrationCb);
|
||||||
|
s.MotionBlur = IsChecked(MotionBlurCb);
|
||||||
|
s.ExtraEffects = IsChecked(AdditionalShadersCb);
|
||||||
|
s.EnvMap = IsChecked(ReflectionsCubeMapCb);
|
||||||
|
s.UseVbo = IsChecked(RenderVBOCb);
|
||||||
|
s.RenderShadows = IsChecked(RenderShadowsCb);
|
||||||
|
s.ReflectionsFramerate = (int)reflectionsFramerate.Value;
|
||||||
|
s.ShadowMapResolution = 1 << (int)shaderResolutionSlider.Value;
|
||||||
|
s.ShadowProjectionRange = (int)shaderRange.Value;
|
||||||
|
s.CabShadowsRange = (int)cabShaderSourceRange.Value;
|
||||||
|
s.ShadowRankCutoff = Math.Max(0, ShadowDisplayCb.SelectedIndex) + 1;
|
||||||
|
s.ReflectionsFidelity = Math.Max(0, ReflectionsDetailsCb.SelectedIndex);
|
||||||
|
s.FieldOfView = Clamp((int)fovSlider.Value, 15, 75);
|
||||||
|
s.PythonScreens = IsChecked(RenderScreensCb);
|
||||||
|
s.PythonThreadedUpload = IsChecked(RenderScreensThreadCb);
|
||||||
|
s.ScreenRendererPriority = (int)RenderScreensFramerateSlider.Value;
|
||||||
|
|
||||||
|
// Physics
|
||||||
|
s.SplineFidelity = (int)TrackCurvesSlider.Value;
|
||||||
|
s.FullPhysics = IsChecked(PhysicsAccuracyCb);
|
||||||
|
s.EnableTraction = IsChecked(PantographBreakCb);
|
||||||
|
s.LiveTraction = IsChecked(OverheadOnlyCb);
|
||||||
|
s.PhysicsLog = IsChecked(SpeedometerTapesCb);
|
||||||
|
s.DebugLog = IsChecked(SimLogsCb);
|
||||||
|
s.MultipleLogs = IsChecked(KeepLogsCb);
|
||||||
|
s.DisplaySimulation = IsChecked(DisplaySimulationCb);
|
||||||
|
s.CrashDamage = IsChecked(CrashDamageCb);
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
s.SoundEnabled = IsChecked(EnableSoundsCb);
|
||||||
|
s.Volume = (int)VolumeSlider.Value;
|
||||||
|
s.RadioVolume = (int)RadioVolumeSlider.Value;
|
||||||
|
s.VehiclesVolume = (int)VehiclesVolumeSlider.Value;
|
||||||
|
s.PositionalVolume = (int)PositionalVolumeSlider.Value;
|
||||||
|
s.AmbientVolume = (int)AmbientVolumeSlider.Value;
|
||||||
|
s.PausedVolume = (int)PauseVolumeSlider.Value;
|
||||||
|
|
||||||
|
// Starter
|
||||||
|
s.AutoCloseStarter = IsChecked(AutoCloseStarterCb);
|
||||||
|
s.LargeThumbnails = IsChecked(LargeThumbnailsCb);
|
||||||
|
s.AutoExpandSceneryTree = IsChecked(AutoExpandTreeCb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ReadFromUi();
|
||||||
|
StarterNG.Classes.Settings.Instance.Save();
|
||||||
|
if (SaveStatus is not null)
|
||||||
|
SaveStatus.Text = App.Loc["SettingsSaved"];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetButton_OnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
StarterNG.Classes.Settings.Instance.Load();
|
||||||
|
ApplyToUi();
|
||||||
|
if (SaveStatus is not null)
|
||||||
|
SaveStatus.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── resolution combo helpers ──────────────────────────────────────────
|
||||||
|
private void SelectResolution(int width, int height)
|
||||||
|
{
|
||||||
|
string target = $"{width}x{height}";
|
||||||
|
foreach (var obj in ResolutionCb.Items)
|
||||||
|
{
|
||||||
|
if (obj is ComboBoxItem item && item.Content?.ToString() == target)
|
||||||
|
{
|
||||||
|
ResolutionCb.SelectedItem = item;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not in the predefined list: add and select it.
|
||||||
|
var added = new ComboBoxItem { Content = target };
|
||||||
|
ResolutionCb.Items.Add(added);
|
||||||
|
ResolutionCb.SelectedItem = added;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadResolution(Classes.Settings s)
|
||||||
|
{
|
||||||
|
var text = (ResolutionCb.SelectedItem as ComboBoxItem)?.Content?.ToString();
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return;
|
||||||
|
var parts = text.Split('x');
|
||||||
|
if (parts.Length == 2 &&
|
||||||
|
int.TryParse(parts[0], out int w) && int.TryParse(parts[1], out int h))
|
||||||
|
{
|
||||||
|
s.Width = w;
|
||||||
|
s.Height = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── existing slider readouts ──────────────────────────────────────────
|
||||||
private void TextureResolutionSlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
private void TextureResolutionSlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (texResolution is null || textureResolutionSlider is null)
|
if (texResolution is null || textureResolutionSlider is null)
|
||||||
@@ -54,12 +283,11 @@ public partial class Settings : UserControl
|
|||||||
|
|
||||||
private void LanguageComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
private void LanguageComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (sender is not ComboBox { SelectedItem: ComboBoxItem item }) return;
|
if (sender is not ComboBox { SelectedItem: ComboBoxItem item }) return;
|
||||||
if (!item.IsKeyboardFocusWithin) return;
|
if (!item.IsKeyboardFocusWithin) return; // ignore programmatic (ApplyToUi) changes
|
||||||
var lang = item.Content?.ToString();
|
var lang = item.Content?.ToString();
|
||||||
if (string.IsNullOrEmpty(lang)) return;
|
if (string.IsNullOrEmpty(lang)) return;
|
||||||
|
|
||||||
var langDict = new ResourceInclude(new Uri("avares://StarterNG/"))
|
var langDict = new ResourceInclude(new Uri("avares://StarterNG/"))
|
||||||
{
|
{
|
||||||
Source = new Uri($"Assets/Langs/{lang}.axaml", UriKind.Relative)
|
Source = new Uri($"Assets/Langs/{lang}.axaml", UriKind.Relative)
|
||||||
@@ -77,4 +305,25 @@ public partial class Settings : UserControl
|
|||||||
|
|
||||||
App.Loc.SetLanguage(langDict, lang);
|
App.Loc.SetLanguage(langDict, lang);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// ── tiny helpers ──────────────────────────────────────────────────────
|
||||||
|
private static bool IsChecked(CheckBox cb) => cb.IsChecked == true;
|
||||||
|
|
||||||
|
private static int Clamp(int v, int lo, int hi) => v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
|
||||||
|
private static int Log2(int value, int fallback)
|
||||||
|
{
|
||||||
|
if (value <= 0) return fallback;
|
||||||
|
int log = (int)Math.Round(Math.Log2(value));
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int AnisotropyToSlider(int anisotropy)
|
||||||
|
{
|
||||||
|
var steps = StarterNG.Classes.Settings.AnisotropySteps;
|
||||||
|
for (int i = 0; i < steps.Length; i++)
|
||||||
|
if (steps[i] == anisotropy)
|
||||||
|
return i + 1;
|
||||||
|
return 4; // default → 8x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user