mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-09-01 03:19:18 +02:00
Merge pull request #3 from xwizard/feature/dynamic-lights-and-macos-paths
Add settings profiles, dynamic lights, macOS config paths, and fix setting defaults
This commit is contained in:
@@ -125,6 +125,28 @@ public sealed class ConfigFile
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates <paramref name="key"/> by turning its line into a comment,
|
||||
/// preserving indentation, the key/value that was there and any trailing
|
||||
/// comment. No-op if the key is not currently active.
|
||||
/// </summary>
|
||||
public void Remove(string key)
|
||||
{
|
||||
if (!_index.TryGetValue(key, out int i))
|
||||
return;
|
||||
|
||||
SplitLine(_lines[i], out string indent, out _, out string value, out string comment);
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(indent).Append("// ").Append(key);
|
||||
if (value.Length > 0)
|
||||
sb.Append(' ').Append(value);
|
||||
if (comment.Length > 0)
|
||||
sb.Append('\t').Append(comment);
|
||||
_lines[i] = sb.ToString();
|
||||
|
||||
_index.Remove(key);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -88,6 +88,12 @@ public sealed class KeyboardConfig
|
||||
if (!string.IsNullOrEmpty(appData))
|
||||
return Path.Combine(appData, "MaSzyna", "eu07_input-keyboard.ini");
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string? home = Environment.GetEnvironmentVariable("HOME");
|
||||
if (!string.IsNullOrEmpty(home))
|
||||
return Path.Combine(home, "Library", "Application Support", "MaSzyna", "eu07_input-keyboard.ini");
|
||||
}
|
||||
else
|
||||
{
|
||||
string? home = Environment.GetEnvironmentVariable("HOME");
|
||||
|
||||
@@ -27,16 +27,32 @@ public sealed class PresetCollection
|
||||
/// <summary>
|
||||
/// The user's vehicle/consist "warehouse": named trainset presets persisted as JSON.
|
||||
/// Stored under the per-user config directory so it survives reinstalls:
|
||||
/// Windows: %appdata%\MaSzyna\starter\userpresets.json
|
||||
/// Unix: ~/.config/MaSzyna/starter/userpresets.json
|
||||
/// (both resolved via <see cref="Environment.SpecialFolder.ApplicationData"/>).
|
||||
/// Windows: %APPDATA%\MaSzyna\starter\userpresets.json
|
||||
/// macOS: ~/Library/Application Support/MaSzyna/starter/userpresets.json
|
||||
/// Linux: ~/.config/MaSzyna/starter/userpresets.json
|
||||
/// All operations are best-effort and never throw.
|
||||
/// </summary>
|
||||
public static class PresetStore
|
||||
{
|
||||
public static string FilePath { get; } = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"MaSzyna", "starter", "userpresets.json");
|
||||
private static string ResolvePath()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
return Path.Combine(appData, "MaSzyna", "starter", "userpresets.json");
|
||||
}
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string home = Environment.GetEnvironmentVariable("HOME") ?? AppContext.BaseDirectory;
|
||||
return Path.Combine(home, "Library", "Application Support", "MaSzyna", "starter", "userpresets.json");
|
||||
}
|
||||
{
|
||||
string home = Environment.GetEnvironmentVariable("HOME") ?? AppContext.BaseDirectory;
|
||||
return Path.Combine(home, ".config", "MaSzyna", "starter", "userpresets.json");
|
||||
}
|
||||
}
|
||||
|
||||
public static string FilePath { get; } = ResolvePath();
|
||||
|
||||
// Source-generated metadata keeps (de)serialisation AOT/trim-safe, matching the
|
||||
// approach used for the vehicle database.
|
||||
|
||||
@@ -94,7 +94,7 @@ public class Scenery
|
||||
/// Rebuilds the full .scn content with the (possibly modified) trainsets
|
||||
/// substituted back into their original positions.
|
||||
/// </summary>
|
||||
public string BuildExportContent()
|
||||
public string BuildExportContent(bool skipDecorTrainsets = false)
|
||||
{
|
||||
string result = _template;
|
||||
|
||||
@@ -105,7 +105,12 @@ public class Scenery
|
||||
result = RewriteWeather(result);
|
||||
|
||||
for (int i = 0; i < Trainsets.Count; i++)
|
||||
result = result.Replace("{{" + i + "}}", Trainsets[i].ToSceneryEntry());
|
||||
{
|
||||
string entry = skipDecorTrainsets && Trainsets[i].Decor
|
||||
? string.Empty
|
||||
: Trainsets[i].ToSceneryEntry();
|
||||
result = result.Replace("{{" + i + "}}", entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace StarterNG.Classes;
|
||||
|
||||
public enum BatteryDefault { Default = 0, AlwaysOff = 1, AlwaysOn = 2 }
|
||||
|
||||
/// <summary>
|
||||
/// Application settings backing the Settings view, persisted to the simulator's
|
||||
/// <c>eu07.ini</c>.
|
||||
@@ -42,19 +46,26 @@ public sealed class Settings
|
||||
public bool Fullscreen; // fullscreen
|
||||
public bool PauseWhenInactive = true; // inactivepause
|
||||
public bool PauseOnStart; // pause
|
||||
public int CursorSensitivity = 3; // |mousescale x|
|
||||
public int CursorSensitivity = 2; // |mousescale x|
|
||||
public bool InvertMouseHorizontal; // mousescale x < 0
|
||||
public bool InvertMouseVertical; // mousescale y < 0
|
||||
|
||||
// ── Communication ─────────────────────────────────────────────────────
|
||||
public bool IgnoreGamepad; // input.gamepad (inverted)
|
||||
public int FeedbackMode; // feedbackmode (0-5)
|
||||
public int FeedbackMode = 1; // feedbackmode (0-5)
|
||||
public int FeedbackPort = 888; // feedbackport
|
||||
public bool UartEnabled; // uart presence
|
||||
public string UartPort = ""; // uart
|
||||
public string UartTune = ""; // uarttune
|
||||
public bool UartDebug; // uartdebug
|
||||
public bool UartMain, UartScnd, UartTrain, UartLocal, UartRadioVolume, UartRadioChannel; // uartfeature tokens
|
||||
|
||||
// ── Other ─────────────────────────────────────────────────────────────
|
||||
public bool SelectExeAutomatically = true; // starter.exe.auto (launcher-only)
|
||||
public string ExecutablePath = "eu07.exe"; // starter.exe.path (launcher-only)
|
||||
public bool DebugMode; // debugmode
|
||||
public bool VirtualShunting; // ai.trainman
|
||||
public bool VirtualShunting = true; // ai.trainman
|
||||
public bool LogMissingVehicleFiles; // starter.logmissingvehicles (launcher-only; not yet wired to any load/export codepath)
|
||||
|
||||
// ── Graphics ──────────────────────────────────────────────────────────
|
||||
public int RenderEngine; // gfxrenderer (index, see RenderEngines)
|
||||
@@ -65,52 +76,71 @@ public sealed class Settings
|
||||
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 int DynamicLights = 7; // dynamiclights (0-7)
|
||||
public double DrawRangeFactor = 3.0; // gfx.drawrange.factor.max
|
||||
public bool VSync; // 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 ChromaticAberration = true; // 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 ShadowMapResolution = 2048; // shadowtune[0]
|
||||
public int ShadowProjectionRange = 250; // shadowtune[1]
|
||||
public int CabShadowsRange = 30; // gfx.shadows.cab.range
|
||||
public int CabShadowsRange; // gfx.shadows.cab.range
|
||||
public int ShadowRankCutoff = 3; // gfx.shadow.rank.cutoff (NOTE)
|
||||
public int ReflectionsFidelity = 2; // gfx.reflections.fidelity (0-2)
|
||||
public int ReflectionsFidelity; // 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)
|
||||
public bool FpsLimitEnabled; // fpslimit (presence)
|
||||
public int FpsLimit = 30; // fpslimit (1-100)
|
||||
public double ShadowAngleLimit = 0.2; // gfx.shadow.angle.min (0.2-1.0)
|
||||
public bool FullscreenWindowed; // fullscreenwindowed
|
||||
public bool RenderAngleVulkan; // gfx.angleplatform ("vulkan")
|
||||
|
||||
// ── 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 LiveTraction = true; // 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
|
||||
public double Friction = 1.0; // friction (0.1-3.0)
|
||||
public double BrakeStep = 1.0; // brakestep (0.4-3.0)
|
||||
public double BrakeSpeed = 3.0; // brakespeed (0.4-3.0)
|
||||
|
||||
// ── 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 Volume = 50; // sound.volume (0-2 → 0-100)
|
||||
public int RadioVolume = 75; // 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
|
||||
public int AmbientVolume = 100; // sound.volume.ambient
|
||||
|
||||
// ── Advanced ──────────────────────────────────────────────────────────
|
||||
public bool CompressTextures = true; // compresstex
|
||||
public bool ScaleSpeculars = true; // scalespeculars
|
||||
public bool UseGLES; // gfx.usegles
|
||||
public bool ShaderGamma; // gfx.shadergamma
|
||||
public bool ScreenMipmaps = true; // python.mipmaps
|
||||
public bool ExtendedModelConversion; // convertmodels ("143"/"0")
|
||||
public bool GfxResourceMove; // gfx.resource.move
|
||||
public bool GfxResourceSweep = true; // gfx.resource.sweep
|
||||
public int TrainPhysicsThreads; // async.trainThreads
|
||||
public bool IgnoreIrrelevantTrains; // starter.ignoreirrelevant (launcher-only)
|
||||
|
||||
// ── Starter (launcher-only, stored under starter.* keys) ───────────────
|
||||
public bool AutoCloseStarter; // starter.autoclose
|
||||
public bool LargeThumbnails; // starter.largethumbnails
|
||||
public bool AutoExpandSceneryTree; // starter.expandtree
|
||||
public BatteryDefault BatteryDefault = BatteryDefault.Default; // starter.batterydefault (launcher-only)
|
||||
|
||||
// List filters (launcher-only). Default on.
|
||||
public bool ShowAiVehicles = true; // starter.show.ai (show //$o "-" consists)
|
||||
@@ -124,12 +154,8 @@ public sealed class Settings
|
||||
/// <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 double _mouseScaleYMagnitude = 0.2;
|
||||
private string[] _shadowTuneExtra = { "400", "300" }; // tokens [2], [3]
|
||||
|
||||
private ConfigFile _config = new();
|
||||
@@ -144,6 +170,12 @@ public sealed class Settings
|
||||
if (!string.IsNullOrEmpty(appData))
|
||||
return Path.Combine(appData, "MaSzyna", "eu07.ini");
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string? home = Environment.GetEnvironmentVariable("HOME");
|
||||
if (!string.IsNullOrEmpty(home))
|
||||
return Path.Combine(home, "Library", "Application Support", "MaSzyna", "eu07.ini");
|
||||
}
|
||||
else
|
||||
{
|
||||
string? home = Environment.GetEnvironmentVariable("HOME");
|
||||
@@ -158,6 +190,10 @@ public sealed class Settings
|
||||
private static string DefaultConfigPath() =>
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "eu07.ini");
|
||||
|
||||
/// <summary>Directory holding the per-user eu07.ini, for locating launcher-only data alongside it.</summary>
|
||||
public static string UserConfigDirectory() =>
|
||||
Path.GetDirectoryName(UserConfigPath()) ?? AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// The simulator executable to launch. With auto-selection on, the working
|
||||
/// directory is scanned for an <c>eu07*</c> binary - <c>eu07</c> on Linux/macOS,
|
||||
@@ -191,6 +227,25 @@ public sealed class Settings
|
||||
return canonical;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans <paramref name="directory"/> (defaults to the working directory) for
|
||||
/// plausible <c>eu07*</c> launcher binaries, newest first, for populating the
|
||||
/// manual-selection dropdown. Does not affect <see cref="ResolveExecutable"/>.
|
||||
/// </summary>
|
||||
public static List<string> ListCandidateExecutables(string? directory = null)
|
||||
{
|
||||
directory ??= Directory.GetCurrentDirectory();
|
||||
try
|
||||
{
|
||||
return Directory.GetFiles(directory, "eu07*")
|
||||
.Where(IsExecutableCandidate)
|
||||
.OrderByDescending(File.GetLastWriteTimeUtc)
|
||||
.Select(Path.GetFileName)
|
||||
.ToList()!;
|
||||
}
|
||||
catch { return new List<string>(); }
|
||||
}
|
||||
|
||||
// Filters the eu07* matches down to plausible launcher binaries, skipping data
|
||||
// and config files (eu07.ini, eu07.log, libraries, …).
|
||||
private static bool IsExecutableCandidate(string path)
|
||||
@@ -201,8 +256,10 @@ public sealed class Settings
|
||||
return false;
|
||||
if (OperatingSystem.IsWindows())
|
||||
return ext == ".exe";
|
||||
// Linux / macOS: no extension (eu07) or a known binary suffix
|
||||
return ext.Length == 0 || ext is ".x86_64" or ".run" or ".appimage";
|
||||
// Linux / macOS: no extension (eu07), a known binary suffix, or .exe
|
||||
// (some builds - notably this project's own build-macos.sh - ship the
|
||||
// macOS binary under the historical "eu07.exe" name).
|
||||
return ext.Length == 0 || ext is ".exe" or ".x86_64" or ".run" or ".appimage";
|
||||
}
|
||||
|
||||
private static readonly Encoding FileEncoding = ResolveEncoding();
|
||||
@@ -226,11 +283,23 @@ public sealed class Settings
|
||||
{
|
||||
_savePath = UserConfigPath();
|
||||
string source = File.Exists(_savePath) ? _savePath : DefaultConfigPath();
|
||||
LoadFrom(source);
|
||||
}
|
||||
|
||||
/// <summary>Captures the latest UI values (if the view is open) and saves.</summary>
|
||||
public void CaptureAndSave()
|
||||
{
|
||||
CaptureFromUi?.Invoke();
|
||||
Save();
|
||||
}
|
||||
|
||||
/// <summary>Loads settings from an arbitrary path without changing where <see cref="Save"/> writes.</summary>
|
||||
public void LoadFrom(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
_config = File.Exists(source)
|
||||
? ConfigFile.Parse(File.ReadAllText(source, FileEncoding))
|
||||
_config = File.Exists(path)
|
||||
? ConfigFile.Parse(File.ReadAllText(path, FileEncoding))
|
||||
: new ConfigFile();
|
||||
}
|
||||
catch
|
||||
@@ -241,27 +310,26 @@ public sealed class Settings
|
||||
ReadFromConfig();
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_savePath))
|
||||
_savePath = UserConfigPath();
|
||||
|
||||
SaveTo(_savePath);
|
||||
}
|
||||
|
||||
/// <summary>Writes settings to an arbitrary path without changing where <see cref="Save"/> writes.</summary>
|
||||
public void SaveTo(string path)
|
||||
{
|
||||
WriteToConfig();
|
||||
|
||||
try
|
||||
{
|
||||
string? dir = Path.GetDirectoryName(_savePath);
|
||||
string? dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
File.WriteAllText(_savePath, _config.ToText(), FileEncoding);
|
||||
File.WriteAllText(path, _config.ToText(), FileEncoding);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -295,13 +363,31 @@ public sealed class Settings
|
||||
|
||||
// Communication
|
||||
IgnoreGamepad = !c.GetBool("input.gamepad", true);
|
||||
FeedbackMode = Clamp(c.GetInt("feedbackmode", 0), 0, 5);
|
||||
FeedbackMode = Clamp(c.GetInt("feedbackmode", 1), 0, 5);
|
||||
FeedbackPort = c.GetInt("feedbackport", 888);
|
||||
UartEnabled = c.Has("uart");
|
||||
UartPort = c.GetString("uart", "");
|
||||
UartTune = c.GetString("uarttune", "");
|
||||
UartDebug = c.GetBool("uartdebug", false);
|
||||
if (c.Has("uartfeature"))
|
||||
{
|
||||
var uartTokens = (c.GetRaw("uartfeature") ?? "").Split('|', StringSplitOptions.RemoveEmptyEntries);
|
||||
var uf = new HashSet<string>(uartTokens, StringComparer.OrdinalIgnoreCase);
|
||||
UartMain = uf.Contains("main"); UartScnd = uf.Contains("scnd"); UartTrain = uf.Contains("train");
|
||||
UartLocal = uf.Contains("local"); UartRadioVolume = uf.Contains("radiovolume"); UartRadioChannel = uf.Contains("radiochannel");
|
||||
}
|
||||
else
|
||||
{
|
||||
UartMain = UartScnd = UartTrain = UartLocal = true;
|
||||
UartRadioVolume = UartRadioChannel = false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
VirtualShunting = c.GetBool("ai.trainman", true);
|
||||
LogMissingVehicleFiles = c.GetBool("starter.logmissingvehicles", false);
|
||||
|
||||
// Graphics
|
||||
RenderEngine = IndexOf(RenderEngines, c.GetString("gfxrenderer", "full"), 0);
|
||||
@@ -312,12 +398,12 @@ public sealed class Settings
|
||||
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);
|
||||
DynamicLights = Clamp(c.GetInt("dynamiclights", 7), 0, 7);
|
||||
DrawRangeFactor = c.GetDouble("gfx.drawrange.factor.max", 3.0);
|
||||
VSync = c.GetBool("vsync", false);
|
||||
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);
|
||||
ChromaticAberration = c.GetBool("gfx.postfx.chromaticaberration.enabled", true);
|
||||
MotionBlur = c.GetBool("gfx.postfx.motionblur.enabled", true);
|
||||
ExtraEffects = c.GetBool("gfx.extraeffects", true);
|
||||
EnvMap = c.GetBool("gfx.envmap.enabled", true);
|
||||
@@ -326,42 +412,61 @@ public sealed class Settings
|
||||
ReflectionsFramerate = c.GetInt("gfx.reflections.framerate", 60);
|
||||
|
||||
var shadowTune = c.GetTokens("shadowtune");
|
||||
if (shadowTune.Length >= 1) ShadowMapResolution = ParseInt(shadowTune[0], 4096);
|
||||
if (shadowTune.Length >= 1) ShadowMapResolution = ParseInt(shadowTune[0], 2048);
|
||||
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));
|
||||
CabShadowsRange = (int)Math.Round(c.GetDouble("gfx.shadows.cab.range", 0));
|
||||
ShadowRankCutoff = c.GetInt("gfx.shadow.rank.cutoff", 3);
|
||||
ReflectionsFidelity = Clamp(c.GetInt("gfx.reflections.fidelity", 2), 0, 2);
|
||||
ReflectionsFidelity = Clamp(c.GetInt("gfx.reflections.fidelity", 0), 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;
|
||||
FpsLimitEnabled = c.Has("fpslimit");
|
||||
FpsLimit = Clamp(c.GetInt("fpslimit", 30), 1, 100);
|
||||
ShadowAngleLimit = c.GetDouble("gfx.shadow.angle.min", 0.2);
|
||||
FullscreenWindowed = c.GetBool("fullscreenwindowed", false);
|
||||
RenderAngleVulkan = string.Equals(c.GetString("gfx.angleplatform", ""), "vulkan", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// Physics
|
||||
SplineFidelity = Clamp(c.GetInt("splinefidelity", 1), 1, 4);
|
||||
FullPhysics = c.GetBool("fullphysics", true);
|
||||
EnableTraction = c.GetBool("enabletraction", true);
|
||||
LiveTraction = c.GetBool("livetraction", false);
|
||||
LiveTraction = c.GetBool("livetraction", true);
|
||||
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);
|
||||
Friction = c.GetDouble("friction", 1.0);
|
||||
BrakeStep = c.GetDouble("brakestep", 1.0);
|
||||
BrakeSpeed = c.GetDouble("brakespeed", 3.0);
|
||||
|
||||
// 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);
|
||||
Volume = Clamp((int)Math.Round(c.GetDouble("sound.volume", 1.0) * 50), 1, 100);
|
||||
RadioVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.radio", 0.75) * 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);
|
||||
AmbientVolume = Clamp((int)Math.Round(c.GetDouble("sound.volume.ambient", 1.0) * 100), 1, 100);
|
||||
|
||||
// Advanced
|
||||
CompressTextures = c.GetBool("compresstex", true);
|
||||
ScaleSpeculars = c.GetBool("scalespeculars", true);
|
||||
UseGLES = c.GetBool("gfx.usegles", false);
|
||||
ShaderGamma = c.GetBool("gfx.shadergamma", false);
|
||||
ScreenMipmaps = c.GetBool("python.mipmaps", true);
|
||||
ExtendedModelConversion = c.GetInt("convertmodels", 0) != 0;
|
||||
GfxResourceMove = c.GetBool("gfx.resource.move", false);
|
||||
GfxResourceSweep = c.GetBool("gfx.resource.sweep", true);
|
||||
TrainPhysicsThreads = Clamp(c.GetInt("async.trainThreads", 0), 0, Environment.ProcessorCount);
|
||||
IgnoreIrrelevantTrains = c.GetBool("starter.ignoreirrelevant", false);
|
||||
|
||||
// Starter
|
||||
AutoCloseStarter = c.GetBool("starter.autoclose", false);
|
||||
LargeThumbnails = c.GetBool("starter.largethumbnails", false);
|
||||
AutoExpandSceneryTree = c.GetBool("starter.expandtree", false);
|
||||
BatteryDefault = (BatteryDefault)Clamp(c.GetInt("starter.batterydefault", 0), 0, 2);
|
||||
ShowAiVehicles = c.GetBool("starter.show.ai", true);
|
||||
DrivableOnly = c.GetBool("starter.drivableonly", true);
|
||||
ShowArchivalSceneries = c.GetBool("starter.show.archival", true);
|
||||
@@ -387,12 +492,25 @@ public sealed class Settings
|
||||
// Communication
|
||||
c.SetBool("input.gamepad", !IgnoreGamepad);
|
||||
c.SetInt("feedbackmode", FeedbackMode);
|
||||
c.SetInt("feedbackport", FeedbackPort);
|
||||
if (UartEnabled && !string.IsNullOrWhiteSpace(UartPort)) c.Set("uart", UartPort); else c.Remove("uart");
|
||||
c.Set("uarttune", UartTune);
|
||||
c.SetBool("uartdebug", UartDebug);
|
||||
var sb = new StringBuilder();
|
||||
if (UartMain) sb.Append("main|");
|
||||
if (UartScnd) sb.Append("scnd|");
|
||||
if (UartTrain) sb.Append("train|");
|
||||
if (UartLocal) sb.Append("local|");
|
||||
if (UartRadioVolume) sb.Append("radiovolume|");
|
||||
if (UartRadioChannel) sb.Append("radiochannel|");
|
||||
c.Set("uartfeature", sb.ToString());
|
||||
|
||||
// Other
|
||||
c.SetBool("starter.exe.auto", SelectExeAutomatically);
|
||||
c.Set("starter.exe.path", ExecutablePath);
|
||||
c.SetBool("debugmode", DebugMode);
|
||||
c.SetBool("ai.trainman", VirtualShunting);
|
||||
c.SetBool("starter.logmissingvehicles", LogMissingVehicleFiles);
|
||||
|
||||
// Graphics
|
||||
c.Set("gfxrenderer", RenderEngines[Clamp(RenderEngine, 0, RenderEngines.Length - 1)]);
|
||||
@@ -403,11 +521,11 @@ public sealed class Settings
|
||||
c.SetInt("maxcabtexturesize", MaxCabTextureSize);
|
||||
c.SetInt("anisotropicfiltering", TextureFiltering);
|
||||
c.SetInt("multisampling", Multisampling);
|
||||
c.SetInt("dynamiclights", DynamicLights);
|
||||
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);
|
||||
@@ -425,8 +543,10 @@ public sealed class Settings
|
||||
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)]);
|
||||
if (FpsLimitEnabled) c.SetInt("fpslimit", FpsLimit); else c.Remove("fpslimit");
|
||||
c.SetDouble("gfx.shadow.angle.min", ShadowAngleLimit);
|
||||
c.SetBool("fullscreenwindowed", FullscreenWindowed);
|
||||
if (RenderAngleVulkan) c.Set("gfx.angleplatform", "vulkan"); else c.Remove("gfx.angleplatform");
|
||||
|
||||
// Physics
|
||||
c.SetInt("splinefidelity", SplineFidelity);
|
||||
@@ -438,6 +558,9 @@ public sealed class Settings
|
||||
c.SetBool("multiplelogs", MultipleLogs);
|
||||
c.SetBool("gfx.skiprendering", !DisplaySimulation);
|
||||
c.SetBool("crashdamage", CrashDamage);
|
||||
c.SetDouble("friction", Friction);
|
||||
c.SetDouble("brakestep", BrakeStep);
|
||||
c.SetDouble("brakespeed", BrakeSpeed);
|
||||
|
||||
// Sound
|
||||
c.SetBool("soundenabled", SoundEnabled);
|
||||
@@ -446,12 +569,24 @@ public sealed class Settings
|
||||
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);
|
||||
|
||||
// Advanced
|
||||
c.SetBool("compresstex", CompressTextures);
|
||||
c.SetBool("scalespeculars", ScaleSpeculars);
|
||||
c.SetBool("gfx.usegles", UseGLES);
|
||||
c.SetBool("gfx.shadergamma", ShaderGamma);
|
||||
c.SetBool("python.mipmaps", ScreenMipmaps);
|
||||
c.Set("convertmodels", ExtendedModelConversion ? "143" : "0");
|
||||
c.SetBool("gfx.resource.move", GfxResourceMove);
|
||||
c.SetBool("gfx.resource.sweep", GfxResourceSweep);
|
||||
c.SetInt("async.trainThreads", TrainPhysicsThreads);
|
||||
c.SetBool("starter.ignoreirrelevant", IgnoreIrrelevantTrains);
|
||||
|
||||
// Starter
|
||||
c.SetBool("starter.autoclose", AutoCloseStarter);
|
||||
c.SetBool("starter.largethumbnails", LargeThumbnails);
|
||||
c.SetBool("starter.expandtree", AutoExpandSceneryTree);
|
||||
c.SetInt("starter.batterydefault", (int)BatteryDefault);
|
||||
c.SetBool("starter.show.ai", ShowAiVehicles);
|
||||
c.SetBool("starter.drivableonly", DrivableOnly);
|
||||
c.SetBool("starter.show.archival", ShowArchivalSceneries);
|
||||
|
||||
34
StarterNG/Classes/SettingsProfileStore.cs
Normal file
34
StarterNG/Classes/SettingsProfileStore.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace StarterNG.Classes;
|
||||
|
||||
public static class SettingsProfileStore
|
||||
{
|
||||
public static string DirectoryPath { get; } =
|
||||
Path.Combine(Settings.UserConfigDirectory(), "starter", "profiles");
|
||||
|
||||
public static IReadOnlyList<string> ListProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(DirectoryPath)) return Array.Empty<string>();
|
||||
return Directory.GetFiles(DirectoryPath, "*.ini")
|
||||
.Select(Path.GetFileNameWithoutExtension)
|
||||
.Where(n => !string.IsNullOrEmpty(n))
|
||||
.OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList()!;
|
||||
}
|
||||
catch { return Array.Empty<string>(); }
|
||||
}
|
||||
|
||||
public static string PathFor(string name)
|
||||
{
|
||||
string safe = name.Trim();
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
safe = safe.Replace(c, '_');
|
||||
return Path.Combine(DirectoryPath, safe + ".ini");
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class Trainset
|
||||
public string Track;
|
||||
public float Offset;
|
||||
public float Velocity;
|
||||
public float OriginalVelocity;
|
||||
public string Description;
|
||||
public List<Dynamic> Vehicles;
|
||||
|
||||
@@ -82,6 +83,7 @@ public class Trainset
|
||||
this.Track = tokens[++i];
|
||||
this.Offset = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
|
||||
this.Velocity = float.Parse(tokens[++i], CultureInfo.InvariantCulture);
|
||||
this.OriginalVelocity = this.Velocity;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,13 +100,23 @@ public partial class MainWindow : Window
|
||||
return;
|
||||
var trainset = AppState.Instance.CurrentTrainset;
|
||||
|
||||
if (trainset is { Vehicles.Count: > 0 })
|
||||
{
|
||||
trainset.Velocity = Settings.Instance.BatteryDefault switch
|
||||
{
|
||||
BatteryDefault.AlwaysOff => 0f,
|
||||
BatteryDefault.AlwaysOn => MathF.Abs(trainset.OriginalVelocity) < 0.01f ? 0.1f : trainset.OriginalVelocity,
|
||||
_ => trainset.OriginalVelocity
|
||||
};
|
||||
}
|
||||
|
||||
// write scenery/$<name>.scn with the replaced trainsets
|
||||
string dir = Path.GetDirectoryName(scenery.Path) ?? "scenery";
|
||||
string exportName = "$" + Path.GetFileName(scenery.Path);
|
||||
string exportPath = Path.Combine(dir, exportName);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(exportPath, scenery.BuildExportContent(), Encoding.GetEncoding(1250));
|
||||
File.WriteAllText(exportPath, scenery.BuildExportContent(Settings.Instance.IgnoreIrrelevantTrains), Encoding.GetEncoding(1250));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -64,6 +64,30 @@
|
||||
<ComboBoxItem Tag="5" Content="{Binding [FeedbackSP], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel x:Name="FeedbackPortRow" Spacing="8" Orientation="Horizontal" IsVisible="False">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [FeedbackPort], Source={x:Static starterNg:App.Loc}}" />
|
||||
<NumericUpDown x:Name="FeedbackPortNud" Minimum="0" Maximum="65535" Value="888" Width="120" />
|
||||
</StackPanel>
|
||||
<Expander x:Name="UartExpander" IsVisible="False" Header="{Binding [UartSection], Source={x:Static starterNg:App.Loc}}">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<CheckBox x:Name="UartEnableCb" Content="{Binding [UartEnable], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [UartPort], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox x:Name="UartPortTb" MinWidth="150" PlaceholderText="COM3" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [UartTune], Source={x:Static starterNg:App.Loc}}" />
|
||||
<TextBox x:Name="UartTuneTb" MinWidth="150" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="UartDebugCb" Content="{Binding [UartDebug], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartMainCb" Content="{Binding [UartMain], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartScndCb" Content="{Binding [UartScnd], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartTrainCb" Content="{Binding [UartTrain], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartLocalCb" Content="{Binding [UartLocal], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartRadioVolumeCb" Content="{Binding [UartRadioVolume], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UartRadioChannelCb" Content="{Binding [UartRadioChannel], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -82,13 +106,11 @@
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding [SelectEXE], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="SelectExeCb" MinWidth="350" MaxWidth="350" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [SelectEXEAuto], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem>eu07.exe</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ComboBox x:Name="SelectExeCb" MinWidth="350" MaxWidth="350" SelectedIndex="0" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="DebugModeCb" Content="{Binding [DebugMode], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="VirtualShuntingCb" Content="{Binding [VirtualShunting], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="LogMissingVehicleFilesCb" Content="{Binding [LogMissingVehicleFiles], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -149,11 +171,28 @@
|
||||
<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 [DynamicLights], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="DynamicLightsSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="7" TickFrequency="1" IsSnapToTickEnabled="True" Value="7" />
|
||||
</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>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<CheckBox x:Name="FpsLimitEnableCb" Content="{Binding [FpsLimitEnable], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="FpsLimitSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="1" IsSnapToTickEnabled="True" Value="30" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [ShadowAngleLimit], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="ShadowAngleLimitSlider" Minimum="0.2" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="1.0" TickFrequency="0.1" Value="0.2" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="FullscreenWindowedCb" Content="{Binding [FullscreenWindowed], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="RenderAngleVulkanCb" Content="{Binding [RenderAngleVulkan], Source={x:Static starterNg:App.Loc}}" />
|
||||
<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">
|
||||
@@ -161,13 +200,6 @@
|
||||
<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}}" />
|
||||
@@ -223,11 +255,6 @@
|
||||
</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>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -249,6 +276,21 @@
|
||||
<CheckBox x:Name="KeepLogsCb" Content="{Binding [KeepPrevLogs], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="DisplaySimulationCb" Content="{Binding [DisplaySimulation], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="CrashDamageCb" Content="{Binding [CrashDamage], Source={x:Static starterNg:App.Loc}}" />
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [Friction], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="FrictionSlider" Minimum="0.1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BrakeStep], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="BrakeStepSlider" Minimum="0.4" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [BrakeSpeed], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="BrakeSpeedSlider" Minimum="0.4" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="3.0" TickFrequency="0.1" Value="1.0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -283,11 +325,29 @@
|
||||
<Slider x:Name="AmbientVolumeSlider" Minimum="1" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="100" TickFrequency="5" IsSnapToTickEnabled="True" Value="100" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Advanced -->
|
||||
<TabItem Header="{Binding [Advanced], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<CheckBox x:Name="CompressTexturesCb" Content="{Binding [CompressTextures], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ScaleSpecularsCb" Content="{Binding [ScaleSpeculars], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="UseGLESCb" Content="{Binding [UseGLES], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ShaderGammaCb" Content="{Binding [ShaderGamma], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ScreenMipmapsCb" Content="{Binding [ScreenMipmaps], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="ExtendedModelConversionCb" Content="{Binding [ExtendedModelConversion], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="GfxResourceMoveCb" Content="{Binding [GfxResourceMove], Source={x:Static starterNg:App.Loc}}" />
|
||||
<CheckBox x:Name="GfxResourceSweepCb" Content="{Binding [GfxResourceSweep], Source={x:Static starterNg:App.Loc}}" />
|
||||
<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" />
|
||||
<TextBlock VerticalAlignment="Center" FontSize="14" Text="{Binding [TrainPhysicsThreads], Source={x:Static starterNg:App.Loc}}" />
|
||||
<Slider x:Name="TrainPhysicsThreadsSlider" Minimum="0" MaxWidth="400" HorizontalAlignment="Left"
|
||||
MinWidth="250" Maximum="0" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" />
|
||||
<Label VerticalAlignment="Center" FontSize="14" Content="{Binding #TrainPhysicsThreadsSlider.Value}" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="IgnoreIrrelevantTrainsCb" Content="{Binding [IgnoreIrrelevantTrains], Source={x:Static starterNg:App.Loc}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -296,9 +356,25 @@
|
||||
<TabItem Header="{Binding [Starter], Source={x:Static starterNg:App.Loc}}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Orientation="Vertical" Margin="12">
|
||||
<StackPanel Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding [SettingsProfile], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="ProfileCb" MinWidth="220" />
|
||||
<Button x:Name="ProfileApplyBtn" Content="{Binding [ProfileApply], Source={x:Static starterNg:App.Loc}}"
|
||||
Click="ProfileApplyButton_OnClick" />
|
||||
<Button x:Name="ProfileSaveAsBtn" Content="{Binding [ProfileSaveAs], Source={x:Static starterNg:App.Loc}}"
|
||||
Click="ProfileSaveAsButton_OnClick" />
|
||||
</StackPanel>
|
||||
<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 Spacing="8" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding [BatteryDefault], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBox x:Name="BatteryDefaultCb" MinWidth="220" SelectedIndex="0">
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultScenario], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultOff], Source={x:Static starterNg:App.Loc}}" />
|
||||
<ComboBoxItem Content="{Binding [BatteryDefaultOn], Source={x:Static starterNg:App.Loc}}" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
@@ -24,6 +25,8 @@ public partial class Settings : UserControl
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
TrainPhysicsThreadsSlider.Maximum = Environment.ProcessorCount;
|
||||
|
||||
this.AttachedToVisualTree += (_, _) =>
|
||||
{
|
||||
TextureResolutionSlider_OnValueChanged(null, null);
|
||||
@@ -37,10 +40,14 @@ public partial class Settings : UserControl
|
||||
_ => 1
|
||||
};
|
||||
|
||||
FeedbackCb.SelectionChanged += FeedbackCb_OnSelectionChanged;
|
||||
FpsLimitEnableCb.IsCheckedChanged += (_, _) => FpsLimitSlider.IsEnabled = IsChecked(FpsLimitEnableCb);
|
||||
|
||||
// The settings instance pulls live values from here whenever the app
|
||||
// closes or the game is launched.
|
||||
StarterNG.Classes.Settings.Instance.CaptureFromUi = ReadFromUi;
|
||||
|
||||
PopulateProfileCombo();
|
||||
ApplyToUi();
|
||||
|
||||
// Controls tab: load the key bindings and build the editor + on-screen
|
||||
@@ -51,6 +58,78 @@ public partial class Settings : UserControl
|
||||
AddHandler(KeyDownEvent, OnControlsKeyDown, RoutingStrategies.Tunnel);
|
||||
}
|
||||
|
||||
private static int FindComboIndexByContent(ComboBox cb, string content)
|
||||
{
|
||||
for (int i = 0; i < cb.Items.Count; i++)
|
||||
if (cb.Items[i] is ComboBoxItem item &&
|
||||
string.Equals(item.Content?.ToString(), content, StringComparison.OrdinalIgnoreCase))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void PopulateProfileCombo()
|
||||
{
|
||||
string? current = (ProfileCb.SelectedItem as ComboBoxItem)?.Content?.ToString();
|
||||
ProfileCb.Items.Clear();
|
||||
foreach (string name in SettingsProfileStore.ListProfiles())
|
||||
ProfileCb.Items.Add(new ComboBoxItem { Content = name });
|
||||
if (current is not null)
|
||||
{
|
||||
int idx = FindComboIndexByContent(ProfileCb, current);
|
||||
if (idx >= 0) ProfileCb.SelectedIndex = idx;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProfileApplyButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ProfileCb.SelectedItem is not ComboBoxItem { Content: string name } || string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
string path = SettingsProfileStore.PathFor(name);
|
||||
if (!File.Exists(path)) return;
|
||||
StarterNG.Classes.Settings.Instance.LoadFrom(path);
|
||||
ApplyToUi();
|
||||
}
|
||||
|
||||
private void ProfileSaveAsButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var nameBox = new TextBox { PlaceholderText = App.Loc["ProfileNamePrompt"], MinWidth = 220 };
|
||||
var ok = new Button { Content = App.Loc["ProfileSaveAs"] };
|
||||
var panel = new StackPanel { Spacing = 6, Margin = new Thickness(8), MinWidth = 240 };
|
||||
panel.Children.Add(new TextBlock { Text = App.Loc["ProfileNamePrompt"], FontWeight = FontWeight.Bold, FontSize = 12 });
|
||||
panel.Children.Add(nameBox);
|
||||
panel.Children.Add(ok);
|
||||
var flyout = new Flyout { Content = panel };
|
||||
|
||||
void Commit()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nameBox.Text)) return;
|
||||
ReadFromUi();
|
||||
StarterNG.Classes.Settings.Instance.SaveTo(SettingsProfileStore.PathFor(nameBox.Text));
|
||||
PopulateProfileCombo();
|
||||
int idx = FindComboIndexByContent(ProfileCb, nameBox.Text.Trim());
|
||||
if (idx >= 0) ProfileCb.SelectedIndex = idx;
|
||||
flyout.Hide();
|
||||
}
|
||||
ok.Click += (_, _) => Commit();
|
||||
nameBox.KeyDown += (_, ev) => { if (ev.Key == Key.Enter) { ev.Handled = true; Commit(); } };
|
||||
flyout.ShowAt(ProfileSaveAsBtn, showAtPointer: false);
|
||||
nameBox.Focus(); nameBox.SelectAll();
|
||||
}
|
||||
|
||||
private void PopulateExeCombo()
|
||||
{
|
||||
string? current = (SelectExeCb.SelectedItem as ComboBoxItem)?.Content?.ToString();
|
||||
SelectExeCb.Items.Clear();
|
||||
SelectExeCb.Items.Add(new ComboBoxItem { Content = App.Loc["SelectEXEAuto"] });
|
||||
foreach (string exe in StarterNG.Classes.Settings.ListCandidateExecutables())
|
||||
SelectExeCb.Items.Add(new ComboBoxItem { Content = exe });
|
||||
if (current is not null)
|
||||
{
|
||||
int idx = FindComboIndexByContent(SelectExeCb, current);
|
||||
SelectExeCb.SelectedIndex = idx >= 0 ? idx : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Settings instance → controls ──────────────────────────────────────
|
||||
private void ApplyToUi()
|
||||
{
|
||||
@@ -58,6 +137,9 @@ public partial class Settings : UserControl
|
||||
_loading = true;
|
||||
try
|
||||
{
|
||||
PopulateProfileCombo();
|
||||
PopulateExeCombo();
|
||||
|
||||
// General
|
||||
ChangeLanguageCb.SelectedIndex = s.Language == "Polski" ? 0 : 1;
|
||||
FullscreenCb.IsChecked = s.Fullscreen;
|
||||
@@ -70,11 +152,26 @@ public partial class Settings : UserControl
|
||||
// Communication
|
||||
GamepadIgnoreCb.IsChecked = s.IgnoreGamepad;
|
||||
FeedbackCb.SelectedIndex = s.FeedbackMode;
|
||||
FeedbackPortNud.Value = (decimal)s.FeedbackPort;
|
||||
UartEnableCb.IsChecked = s.UartEnabled;
|
||||
UartPortTb.Text = s.UartPort;
|
||||
UartTuneTb.Text = s.UartTune;
|
||||
UartDebugCb.IsChecked = s.UartDebug;
|
||||
UartMainCb.IsChecked = s.UartMain;
|
||||
UartScndCb.IsChecked = s.UartScnd;
|
||||
UartTrainCb.IsChecked = s.UartTrain;
|
||||
UartLocalCb.IsChecked = s.UartLocal;
|
||||
UartRadioVolumeCb.IsChecked = s.UartRadioVolume;
|
||||
UartRadioChannelCb.IsChecked = s.UartRadioChannel;
|
||||
UpdateFeedbackDetailVisibility();
|
||||
|
||||
// Other
|
||||
SelectExeCb.SelectedIndex = s.SelectExeAutomatically ? 0 : 1;
|
||||
SelectExeCb.SelectedIndex = s.SelectExeAutomatically
|
||||
? 0
|
||||
: Math.Max(0, FindComboIndexByContent(SelectExeCb, s.ExecutablePath));
|
||||
DebugModeCb.IsChecked = s.DebugMode;
|
||||
VirtualShuntingCb.IsChecked = s.VirtualShunting;
|
||||
LogMissingVehicleFilesCb.IsChecked = s.LogMissingVehicleFiles;
|
||||
|
||||
// Graphics
|
||||
RenderEngineCb.SelectedIndex = s.RenderEngine;
|
||||
@@ -84,11 +181,11 @@ public partial class Settings : UserControl
|
||||
cabTextureResolutionSlider.Value = Log2(s.MaxCabTextureSize, 12);
|
||||
TexFilteringSlider.Value = AnisotropyToSlider(s.TextureFiltering);
|
||||
MultisamplingSlider.Value = s.Multisampling + 1;
|
||||
DynamicLightsSlider.Value = s.DynamicLights;
|
||||
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;
|
||||
@@ -104,7 +201,12 @@ public partial class Settings : UserControl
|
||||
fovSlider.Value = s.FieldOfView;
|
||||
RenderScreensCb.IsChecked = s.PythonScreens;
|
||||
RenderScreensThreadCb.IsChecked = s.PythonThreadedUpload;
|
||||
RenderScreensFramerateSlider.Value = s.ScreenRendererPriority;
|
||||
FpsLimitEnableCb.IsChecked = s.FpsLimitEnabled;
|
||||
FpsLimitSlider.Value = s.FpsLimit;
|
||||
FpsLimitSlider.IsEnabled = s.FpsLimitEnabled;
|
||||
ShadowAngleLimitSlider.Value = s.ShadowAngleLimit;
|
||||
FullscreenWindowedCb.IsChecked = s.FullscreenWindowed;
|
||||
RenderAngleVulkanCb.IsChecked = s.RenderAngleVulkan;
|
||||
|
||||
// Physics
|
||||
TrackCurvesSlider.Value = s.SplineFidelity;
|
||||
@@ -116,6 +218,9 @@ public partial class Settings : UserControl
|
||||
KeepLogsCb.IsChecked = s.MultipleLogs;
|
||||
DisplaySimulationCb.IsChecked = s.DisplaySimulation;
|
||||
CrashDamageCb.IsChecked = s.CrashDamage;
|
||||
FrictionSlider.Value = s.Friction;
|
||||
BrakeStepSlider.Value = s.BrakeStep;
|
||||
BrakeSpeedSlider.Value = s.BrakeSpeed;
|
||||
|
||||
// Sound
|
||||
EnableSoundsCb.IsChecked = s.SoundEnabled;
|
||||
@@ -124,12 +229,24 @@ public partial class Settings : UserControl
|
||||
VehiclesVolumeSlider.Value = s.VehiclesVolume;
|
||||
PositionalVolumeSlider.Value = s.PositionalVolume;
|
||||
AmbientVolumeSlider.Value = s.AmbientVolume;
|
||||
PauseVolumeSlider.Value = s.PausedVolume;
|
||||
|
||||
// Advanced
|
||||
CompressTexturesCb.IsChecked = s.CompressTextures;
|
||||
ScaleSpecularsCb.IsChecked = s.ScaleSpeculars;
|
||||
UseGLESCb.IsChecked = s.UseGLES;
|
||||
ShaderGammaCb.IsChecked = s.ShaderGamma;
|
||||
ScreenMipmapsCb.IsChecked = s.ScreenMipmaps;
|
||||
ExtendedModelConversionCb.IsChecked = s.ExtendedModelConversion;
|
||||
GfxResourceMoveCb.IsChecked = s.GfxResourceMove;
|
||||
GfxResourceSweepCb.IsChecked = s.GfxResourceSweep;
|
||||
TrainPhysicsThreadsSlider.Value = s.TrainPhysicsThreads;
|
||||
IgnoreIrrelevantTrainsCb.IsChecked = s.IgnoreIrrelevantTrains;
|
||||
|
||||
// Starter
|
||||
AutoCloseStarterCb.IsChecked = s.AutoCloseStarter;
|
||||
LargeThumbnailsCb.IsChecked = s.LargeThumbnails;
|
||||
AutoExpandTreeCb.IsChecked = s.AutoExpandSceneryTree;
|
||||
BatteryDefaultCb.SelectedIndex = (int)s.BatteryDefault;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -154,6 +271,17 @@ public partial class Settings : UserControl
|
||||
// Communication
|
||||
s.IgnoreGamepad = IsChecked(GamepadIgnoreCb);
|
||||
s.FeedbackMode = Math.Max(0, FeedbackCb.SelectedIndex);
|
||||
s.FeedbackPort = (int)(FeedbackPortNud.Value ?? 888);
|
||||
s.UartEnabled = IsChecked(UartEnableCb);
|
||||
s.UartPort = UartPortTb.Text ?? "";
|
||||
s.UartTune = UartTuneTb.Text ?? "";
|
||||
s.UartDebug = IsChecked(UartDebugCb);
|
||||
s.UartMain = IsChecked(UartMainCb);
|
||||
s.UartScnd = IsChecked(UartScndCb);
|
||||
s.UartTrain = IsChecked(UartTrainCb);
|
||||
s.UartLocal = IsChecked(UartLocalCb);
|
||||
s.UartRadioVolume = IsChecked(UartRadioVolumeCb);
|
||||
s.UartRadioChannel = IsChecked(UartRadioChannelCb);
|
||||
|
||||
// Other
|
||||
s.SelectExeAutomatically = SelectExeCb.SelectedIndex == 0;
|
||||
@@ -161,6 +289,7 @@ public partial class Settings : UserControl
|
||||
: (SelectExeCb.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "eu07.exe";
|
||||
s.DebugMode = IsChecked(DebugModeCb);
|
||||
s.VirtualShunting = IsChecked(VirtualShuntingCb);
|
||||
s.LogMissingVehicleFiles = IsChecked(LogMissingVehicleFilesCb);
|
||||
|
||||
// Graphics
|
||||
s.RenderEngine = Math.Max(0, RenderEngineCb.SelectedIndex);
|
||||
@@ -171,11 +300,11 @@ public partial class Settings : UserControl
|
||||
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.DynamicLights = (int)DynamicLightsSlider.Value;
|
||||
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);
|
||||
@@ -191,7 +320,11 @@ public partial class Settings : UserControl
|
||||
s.FieldOfView = Clamp((int)fovSlider.Value, 15, 75);
|
||||
s.PythonScreens = IsChecked(RenderScreensCb);
|
||||
s.PythonThreadedUpload = IsChecked(RenderScreensThreadCb);
|
||||
s.ScreenRendererPriority = (int)RenderScreensFramerateSlider.Value;
|
||||
s.FpsLimitEnabled = IsChecked(FpsLimitEnableCb);
|
||||
s.FpsLimit = (int)FpsLimitSlider.Value;
|
||||
s.ShadowAngleLimit = ShadowAngleLimitSlider.Value;
|
||||
s.FullscreenWindowed = IsChecked(FullscreenWindowedCb);
|
||||
s.RenderAngleVulkan = IsChecked(RenderAngleVulkanCb);
|
||||
|
||||
// Physics
|
||||
s.SplineFidelity = (int)TrackCurvesSlider.Value;
|
||||
@@ -203,6 +336,9 @@ public partial class Settings : UserControl
|
||||
s.MultipleLogs = IsChecked(KeepLogsCb);
|
||||
s.DisplaySimulation = IsChecked(DisplaySimulationCb);
|
||||
s.CrashDamage = IsChecked(CrashDamageCb);
|
||||
s.Friction = FrictionSlider.Value;
|
||||
s.BrakeStep = BrakeStepSlider.Value;
|
||||
s.BrakeSpeed = BrakeSpeedSlider.Value;
|
||||
|
||||
// Sound
|
||||
s.SoundEnabled = IsChecked(EnableSoundsCb);
|
||||
@@ -211,12 +347,24 @@ public partial class Settings : UserControl
|
||||
s.VehiclesVolume = (int)VehiclesVolumeSlider.Value;
|
||||
s.PositionalVolume = (int)PositionalVolumeSlider.Value;
|
||||
s.AmbientVolume = (int)AmbientVolumeSlider.Value;
|
||||
s.PausedVolume = (int)PauseVolumeSlider.Value;
|
||||
|
||||
// Advanced
|
||||
s.CompressTextures = IsChecked(CompressTexturesCb);
|
||||
s.ScaleSpeculars = IsChecked(ScaleSpecularsCb);
|
||||
s.UseGLES = IsChecked(UseGLESCb);
|
||||
s.ShaderGamma = IsChecked(ShaderGammaCb);
|
||||
s.ScreenMipmaps = IsChecked(ScreenMipmapsCb);
|
||||
s.ExtendedModelConversion = IsChecked(ExtendedModelConversionCb);
|
||||
s.GfxResourceMove = IsChecked(GfxResourceMoveCb);
|
||||
s.GfxResourceSweep = IsChecked(GfxResourceSweepCb);
|
||||
s.TrainPhysicsThreads = (int)TrainPhysicsThreadsSlider.Value;
|
||||
s.IgnoreIrrelevantTrains = IsChecked(IgnoreIrrelevantTrainsCb);
|
||||
|
||||
// Starter
|
||||
s.AutoCloseStarter = IsChecked(AutoCloseStarterCb);
|
||||
s.LargeThumbnails = IsChecked(LargeThumbnailsCb);
|
||||
s.AutoExpandSceneryTree = IsChecked(AutoExpandTreeCb);
|
||||
s.BatteryDefault = (BatteryDefault)Math.Max(0, BatteryDefaultCb.SelectedIndex);
|
||||
}
|
||||
|
||||
private void SaveButton_OnClick(object? sender, RoutedEventArgs e)
|
||||
@@ -310,6 +458,15 @@ public partial class Settings : UserControl
|
||||
App.ApplyLanguage(lang);
|
||||
}
|
||||
|
||||
private void FeedbackCb_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) =>
|
||||
UpdateFeedbackDetailVisibility();
|
||||
|
||||
private void UpdateFeedbackDetailVisibility()
|
||||
{
|
||||
FeedbackPortRow.IsVisible = FeedbackCb.SelectedIndex == 3;
|
||||
UartExpander.IsVisible = FeedbackCb.SelectedIndex == 5;
|
||||
}
|
||||
|
||||
// ── tiny helpers ──────────────────────────────────────────────────────
|
||||
private static bool IsChecked(CheckBox cb) => cb.IsChecked == true;
|
||||
|
||||
|
||||
@@ -222,11 +222,26 @@
|
||||
<String key="FeedbackPoKeys">PoKeys55</String>
|
||||
<String key="FeedbackSP">Serial port (COM)</String>
|
||||
|
||||
<String key="FeedbackPort">LPT port address</String>
|
||||
|
||||
<String key="UartSection">UART configuration</String>
|
||||
<String key="UartEnable">Enable UART communication</String>
|
||||
<String key="UartPort">Serial port</String>
|
||||
<String key="UartTune">Tuning parameter</String>
|
||||
<String key="UartDebug">Print debug data to the console</String>
|
||||
<String key="UartMain">Master controller</String>
|
||||
<String key="UartScnd">Secondary/shunting controller</String>
|
||||
<String key="UartTrain">Train brake</String>
|
||||
<String key="UartLocal">Locomotive brake</String>
|
||||
<String key="UartRadioVolume">Radio volume</String>
|
||||
<String key="UartRadioChannel">Radio channel</String>
|
||||
|
||||
<String key="Others">Other</String>
|
||||
<String key="SelectEXE">Select exe:</String>
|
||||
<String key="SelectEXEAuto">Select automatically</String>
|
||||
<String key="DebugMode">Debug mode (test mode)</String>
|
||||
<String key="VirtualShunting">Virtual shunting</String>
|
||||
<String key="LogMissingVehicleFiles">Log missing vehicle files during export</String>
|
||||
|
||||
<String key="Graphic">Graphics</String>
|
||||
<String key="RenderEngine">Render engine</String>
|
||||
@@ -243,15 +258,16 @@
|
||||
<String key="MaxTexCabResolution">Maximum cabin texture resolution:</String>
|
||||
<String key="TexFilteringQuality">Texture filtering quality:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="DynamicLights">Dynamic light sources:</String>
|
||||
<String key="RenderRange">Rendering range:</String>
|
||||
<String key="FpsLimitEnable">Limit frame rate</String>
|
||||
<String key="ShadowAngleLimit">Shadow length limit</String>
|
||||
<String key="FullscreenWindowed">Automatic resolution (borderless fullscreen)</String>
|
||||
<String key="RenderAngleVulkan">Use ANGLE Vulkan renderer</String>
|
||||
<String key="VSync">VSync</String>
|
||||
<String key="SmokeDisplay">Smoke display</String>
|
||||
<String key="SmokeParticlesMultiplier">Smoke particles multiplier</String>
|
||||
|
||||
<String key="Postprocessing">Post-processing</String>
|
||||
<String key="PPReinhard">Reinhard</String>
|
||||
<String key="PPACES">ACES</String>
|
||||
|
||||
<String key="ChromaticAberration">Chromatic aberration</String>
|
||||
<String key="MotionBlur">Motion blur</String>
|
||||
<String key="AdditionalShadersEffects">Additional shader effects</String>
|
||||
@@ -276,7 +292,6 @@
|
||||
<String key="FieldOfView">View angle</String>
|
||||
<String key="RenderScreens">Render onboard computer screens</String>
|
||||
<String key="RenderScreensThread">Render screens in a separate thread</String>
|
||||
<String key="RenderScreensFramerate">Onboard screens refresh rate</String>
|
||||
|
||||
<String key="Physics">Physics</String>
|
||||
|
||||
@@ -289,6 +304,9 @@
|
||||
<String key="KeepPrevLogs">Keep previous logs</String>
|
||||
<String key="DisplaySimulation">Display simulation</String>
|
||||
<String key="CrashDamage">Collision damage</String>
|
||||
<String key="Friction">Friction multiplier</String>
|
||||
<String key="BrakeStep">Brake valve sensitivity</String>
|
||||
<String key="BrakeSpeed">FV4a valve sensitivity</String>
|
||||
|
||||
<String key="Sounds">Sound</String>
|
||||
<String key="EnableSounds">Enable sound</String>
|
||||
@@ -297,12 +315,31 @@
|
||||
<String key="VehiclesVolume">Vehicles volume</String>
|
||||
<String key="PositionedSoundsVolume">Positional sounds volume</String>
|
||||
<String key="AmbientVolume">Ambient volume</String>
|
||||
<String key="VolumeDuringPause">Volume during pause</String>
|
||||
|
||||
<String key="Advanced">Advanced</String>
|
||||
<String key="CompressTextures">Compress TGA textures</String>
|
||||
<String key="ScaleSpeculars">Scale specular highlights (old-model compatibility)</String>
|
||||
<String key="UseGLES">Use OpenGL ES</String>
|
||||
<String key="ShaderGamma">Gamma correction in shaders</String>
|
||||
<String key="ScreenMipmaps">Generate mipmaps for in-cab (Python) screen textures</String>
|
||||
<String key="ExtendedModelConversion">Extended E3D model conversion (experimental)</String>
|
||||
<String key="GfxResourceMove">Conservative texture memory management</String>
|
||||
<String key="GfxResourceSweep">Purge unused textures from GPU memory</String>
|
||||
<String key="TrainPhysicsThreads">Vehicle physics threads (0 = main thread)</String>
|
||||
<String key="IgnoreIrrelevantTrains">Skip decorative consists when starting</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Settings profile:</String>
|
||||
<String key="ProfileApply">Apply</String>
|
||||
<String key="ProfileSaveAs">Save as…</String>
|
||||
<String key="ProfileNamePrompt">Profile name</String>
|
||||
<String key="AutoCloseStarter">Automatically close starter</String>
|
||||
<String key="LargeThumbnails">Large thumbnails</String>
|
||||
<String key="AutoExpandSceneryTree">Automatically expand scenery tree</String>
|
||||
<String key="BatteryDefault">Battery on launch:</String>
|
||||
<String key="BatteryDefaultScenario">Scenario default</String>
|
||||
<String key="BatteryDefaultOff">Always off</String>
|
||||
<String key="BatteryDefaultOn">Always on</String>
|
||||
|
||||
<!-- Controls / key bindings -->
|
||||
<String key="Controls">Controls</String>
|
||||
|
||||
@@ -222,11 +222,26 @@
|
||||
<String key="FeedbackPoKeys">PoKeys55</String>
|
||||
<String key="FeedbackSP">Serialport (COM)</String>
|
||||
|
||||
<String key="FeedbackPort">Adres portu LPT</String>
|
||||
|
||||
<String key="UartSection">Konfiguracja UART</String>
|
||||
<String key="UartEnable">Włącz komunikację UART</String>
|
||||
<String key="UartPort">Port szeregowy</String>
|
||||
<String key="UartTune">Parametr strojenia</String>
|
||||
<String key="UartDebug">Wypisuj dane debugowania w konsoli</String>
|
||||
<String key="UartMain">Nastawnik główny</String>
|
||||
<String key="UartScnd">Nastawnik manewrowy/pomocniczy</String>
|
||||
<String key="UartTrain">Hamulec zespolony</String>
|
||||
<String key="UartLocal">Hamulec lokomotywy</String>
|
||||
<String key="UartRadioVolume">Głośność radiotelefonu</String>
|
||||
<String key="UartRadioChannel">Kanał radiotelefonu</String>
|
||||
|
||||
<String key="Others">Inne</String>
|
||||
<String key="SelectEXE">Wybór exe:</String>
|
||||
<String key="SelectEXEAuto">Wybierz automatycznie</String>
|
||||
<String key="DebugMode">Tryb testowy (debugmode)</String>
|
||||
<String key="VirtualShunting">Wirtualny manewrowy</String>
|
||||
<String key="LogMissingVehicleFiles">Zapisuj w logu brakujące pliki pojazdów podczas eksportu</String>
|
||||
|
||||
<String key="Graphic">Grafika</String>
|
||||
<String key="RenderEngine">Silnik renderowania</String>
|
||||
@@ -243,15 +258,16 @@
|
||||
<String key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny:</String>
|
||||
<String key="TexFilteringQuality">Jakość filtrowania tekstur:</String>
|
||||
<String key="Multisampling">Multisampling:</String>
|
||||
<String key="DynamicLights">Dynamiczne źródła światła:</String>
|
||||
<String key="RenderRange">Zasięg renderowania:</String>
|
||||
<String key="FpsLimitEnable">Ogranicz liczbę klatek na sekundę</String>
|
||||
<String key="ShadowAngleLimit">Limit długości cienia</String>
|
||||
<String key="FullscreenWindowed">Automatyczna rozdzielczość (pełny ekran bez obramowania)</String>
|
||||
<String key="RenderAngleVulkan">Użyj renderera ANGLE Vulkan</String>
|
||||
<String key="VSync">VSync</String>
|
||||
<String key="SmokeDisplay">Wyświetlanie dymu</String>
|
||||
<String key="SmokeParticlesMultiplier">Mnożnik ilości cząsteczek dymu</String>
|
||||
|
||||
<String key="Postprocessing">Postprocessing</String>
|
||||
<String key="PPReinhard">Reinhard</String>
|
||||
<String key="PPACES">ACES</String>
|
||||
|
||||
<String key="ChromaticAberration">Aberracja chromatyczna</String>
|
||||
<String key="MotionBlur">Rozmycie podczas ruchu</String>
|
||||
<String key="AdditionalShadersEffects">Dodatkowe efekty shaderów</String>
|
||||
@@ -276,7 +292,6 @@
|
||||
<String key="FieldOfView">Kąt widzenia</String>
|
||||
<String key="RenderScreens">Renderowanie ekranów komputerów pokładowych</String>
|
||||
<String key="RenderScreensThread">Renderowanie ekranów w osobnym wątku</String>
|
||||
<String key="RenderScreensFramerate">Częstotliwość odświeżania ekranów komputerów pokładowych</String>
|
||||
|
||||
<String key="Physics">Fizyka</String>
|
||||
|
||||
@@ -289,6 +304,9 @@
|
||||
<String key="KeepPrevLogs">Zachowuj poprzednie logi</String>
|
||||
<String key="DisplaySimulation">Wyświetlaj symulację</String>
|
||||
<String key="CrashDamage">Uszkodzenia przy zderzeniu</String>
|
||||
<String key="Friction">Mnożnik tarcia</String>
|
||||
<String key="BrakeStep">Czułość zaworu hamulcowego</String>
|
||||
<String key="BrakeSpeed">Czułość zaworu FV4a</String>
|
||||
|
||||
<String key="Sounds">Dżwięk</String>
|
||||
<String key="EnableSounds">Włącz dźwięk</String>
|
||||
@@ -297,12 +315,31 @@
|
||||
<String key="VehiclesVolume">Głośność pojazdów</String>
|
||||
<String key="PositionedSoundsVolume">Głośność dżwięków pozycjonowanych</String>
|
||||
<String key="AmbientVolume">Głośność otoczenia</String>
|
||||
<String key="VolumeDuringPause">Głośność podczas pauzy</String>
|
||||
|
||||
<String key="Advanced">Zaawansowane</String>
|
||||
<String key="CompressTextures">Kompresuj tekstury TGA</String>
|
||||
<String key="ScaleSpeculars">Skaluj odbicia specularne (kompatybilność ze starymi modelami)</String>
|
||||
<String key="UseGLES">Używaj OpenGL ES</String>
|
||||
<String key="ShaderGamma">Korekcja gamma w shaderach</String>
|
||||
<String key="ScreenMipmaps">Generuj mipmapy dla tekstur ekranów kabinowych (Python)</String>
|
||||
<String key="ExtendedModelConversion">Rozszerzona konwersja modeli E3D (eksperymentalne)</String>
|
||||
<String key="GfxResourceMove">Ostrożne zarządzanie pamięcią tekstur</String>
|
||||
<String key="GfxResourceSweep">Usuwaj nieużywane tekstury z pamięci GPU</String>
|
||||
<String key="TrainPhysicsThreads">Wątki fizyki pojazdów (0 = wątek główny)</String>
|
||||
<String key="IgnoreIrrelevantTrains">Pomijaj składy dekoracyjne przy starcie</String>
|
||||
|
||||
<String key="Starter">Starter</String>
|
||||
<String key="SettingsProfile">Profil ustawień:</String>
|
||||
<String key="ProfileApply">Zastosuj</String>
|
||||
<String key="ProfileSaveAs">Zapisz jako…</String>
|
||||
<String key="ProfileNamePrompt">Nazwa profilu</String>
|
||||
<String key="AutoCloseStarter">Zamknij starter automatycznie</String>
|
||||
<String key="LargeThumbnails">Duże miniaturki</String>
|
||||
<String key="AutoExpandSceneryTree">Automatycznie rozwijanie drzewka scenerii</String>
|
||||
<String key="BatteryDefault">Akumulator przy starcie:</String>
|
||||
<String key="BatteryDefaultScenario">Domyślnie ze scenerii</String>
|
||||
<String key="BatteryDefaultOff">Zawsze wyłączony</String>
|
||||
<String key="BatteryDefaultOn">Zawsze włączony</String>
|
||||
|
||||
<!-- Sterowanie / przypisania klawiszy -->
|
||||
<String key="Controls">Sterowanie</String>
|
||||
|
||||
Reference in New Issue
Block a user