Starter improvements

- scenery Description
- scenery mini
- extracted translations outside
- autoselect first consist on scenery selection
- autoexpand on group click
This commit is contained in:
2026-06-17 22:00:01 +02:00
parent 9be97c7bda
commit b2f693113d
22 changed files with 776 additions and 634 deletions

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAvaloniaXamlLoader_002Ecs_002Fl_003AC_0021_003FUsers_003FHirek_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F80462644bd1cc7e0b229dc4f5752b48c01cb67b46ae563b1b5078cc2556b98_003FAvaloniaXamlLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACultureInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fab103317a9fe11e3738e327b94ff38c20438b33e5d64ea21aa6fe7297a2cd5b_003FCultureInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACultureInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fab103317a9fe11e3738e327b94ff38c20438b33e5d64ea21aa6fe7297a2cd5b_003FCultureInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACursor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fa7f93665b4356a06f3cf982169411cf193d28423c1b8ab119423d4fd19ccd1_003FCursor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACursor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fa7f93665b4356a06f3cf982169411cf193d28423c1b8ab119423d4fd19ccd1_003FCursor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEncoding_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F1675dc7b710feeeb3e0bc8728be8a947537155c199480fb23b776e81d459_003FEncoding_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEncoding_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F1675dc7b710feeeb3e0bc8728be8a947537155c199480fb23b776e81d459_003FEncoding_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -7,6 +8,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANumber_002EParsing_002Ecs_002Fl_003AC_0021_003FUsers_003FHirek_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_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_003AC_0021_003FUsers_003FHirek_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_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_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_003AStandardAssetLoader_002Ecs_002Fl_003AC_0021_003FUsers_003FHirek_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7910424d8e0c4633b470c5702cbb2d0a229000_003Faf_003F5f796e06_003FStandardAssetLoader_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_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>

View File

@@ -3,6 +3,7 @@
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI" xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
x:Class="StarterNG.App" x:Class="StarterNG.App"
x:CompileBindings="True"
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

View File

@@ -4,9 +4,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using StarterNG.Classes; using StarterNG.Classes;
using StarterNG.Services; using StarterNG.Services;
@@ -37,28 +37,22 @@ public partial class App : Application
}; };
Settings.Instance.Language = langName; Settings.Instance.Language = langName;
var langDict = new ResourceInclude(new Uri("avares://StarterNG/")) ApplyLanguage(langName);
{ }
Source = new Uri($"Assets/Langs/{langName}.axaml", UriKind.Relative)
};
var resources = Current!.Resources; /// <summary>
/// Swaps the active language by loading the matching XML file from the
var languageDictionaries = resources.MergedDictionaries /// <c>lang/</c> folder next to the executable (see <see cref="LocalizationService"/>).
.OfType<ResourceInclude>() /// The translations are no longer compiled into the assembly, so they can be
.Where(d => d.Source != null && d.Source.OriginalString.Contains("Langs/")) /// edited or added without rebuilding the launcher. Accepts either a display
.ToList(); /// name ("English", "Polski") or a short code ("en", "pl").
/// </summary>
foreach (var dict in languageDictionaries) public static void ApplyLanguage(string langNameOrCode)
{ {
resources.MergedDictionaries.Remove(dict); // Loc.Load resolves the file and returns the canonical display name,
} // which we mirror back into the settings so the stored value stays valid.
var resolvedName = Loc.Load(langNameOrCode);
resources.MergedDictionaries.Add(langDict); Settings.Instance.Language = resolvedName;
Loc.SetLanguage(langDict, langName);
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()

View File

@@ -18,6 +18,6 @@ public static class AppInfo
#if DEBUG #if DEBUG
text += " (DEBUG)"; text += " (DEBUG)";
#endif #endif
return text; return text + " public beta";
} }
} }

View File

@@ -1,188 +0,0 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Nav menu -->
<x:String x:Key="NavScenarios">Scenarios</x:String>
<x:String x:Key="NavDepot">Depot</x:String>
<x:String x:Key="NavSettings">Settings</x:String>
<x:String x:Key="NavControls">Controls</x:String>
<!-- General view -->
<x:String x:Key="Sceneries">Sceneries</x:String>
<x:String x:Key="Vehicles">Vehicles</x:String>
<x:String x:Key="ScenarioDescription">Scenario description</x:String>
<x:String x:Key="Consist">Consist</x:String>
<x:String x:Key="Start">Start</x:String>
<x:String x:Key="ShowLatestLogTooltip">Open latest simulation log</x:String>
<!-- Depot / consist creation -->
<x:String x:Key="VehicleDatabase">Vehicle database</x:String>
<x:String x:Key="Search">Search...</x:String>
<x:String x:Key="LoadFromScenery">Load consist from scenery</x:String>
<x:String x:Key="NewConsist">New consist</x:String>
<x:String x:Key="SelectedVehicle">Selected vehicle</x:String>
<x:String x:Key="Vehicle">Vehicle</x:String>
<x:String x:Key="Crew">with crew</x:String>
<x:String x:Key="Reversed">reversed</x:String>
<x:String x:Key="NoVehicles">No vehicles found in databases/vehicles/.</x:String>
<x:String x:Key="ConsistEmpty">Add vehicles from the database on the left, or load an existing consist from a scenery file.</x:String>
<x:String x:Key="TipMoveLeft">Move left</x:String>
<x:String x:Key="TipMoveRight">Move right</x:String>
<x:String x:Key="TipFlip">Reverse vehicle</x:String>
<x:String x:Key="TipRemove">Remove from consist</x:String>
<x:String x:Key="TipDriver">Change crew (cab 1 / cab 2 / passenger / none)</x:String>
<x:String x:Key="Scenery">Scenery</x:String>
<x:String x:Key="Category">Category</x:String>
<x:String x:Key="Class">Class</x:String>
<x:String x:Key="All">All</x:String>
<x:String x:Key="CatDiesel">Diesel</x:String>
<x:String x:Key="CatElectric">Electric</x:String>
<x:String x:Key="CatEMU">EMU</x:String>
<x:String x:Key="CatDMU">DMU</x:String>
<x:String x:Key="CatCarriagesA">Carriages A</x:String>
<x:String x:Key="CatCarriagesB">Carriages B</x:String>
<x:String x:Key="CatElectricLoco">Electric locomotive</x:String>
<x:String x:Key="CatDieselLoco">Diesel locomotive</x:String>
<x:String x:Key="CatSteamLoco">Steam locomotive</x:String>
<x:String x:Key="CatRailbus">Railbus</x:String>
<x:String x:Key="CatDraisine">Draisine</x:String>
<x:String x:Key="CatWork">Work vehicles</x:String>
<x:String x:Key="CatPrototype">Prototype</x:String>
<x:String x:Key="CatTram">Tram</x:String>
<x:String x:Key="CatCar">Car</x:String>
<x:String x:Key="CatBus">Bus</x:String>
<x:String x:Key="CatTruck">Truck</x:String>
<x:String x:Key="CatPeople">People</x:String>
<x:String x:Key="CatAnimals">Animals</x:String>
<x:String x:Key="CatWagons">Wagons</x:String>
<x:String x:Key="CatOther">Other</x:String>
<x:String x:Key="Brakes">Brakes</x:String>
<x:String x:Key="Loads">Loads</x:String>
<x:String x:Key="Coupling">Coupling</x:String>
<x:String x:Key="SelectVehicleHint">Select a vehicle in the consist to edit it.</x:String>
<x:String x:Key="ComingSoon">Editing options coming soon.</x:String>
<x:String x:Key="Split">Split</x:String>
<x:String x:Key="TipSplit">Split the unit into individual cars</x:String>
<x:String x:Key="AddVehicle">Add</x:String>
<x:String x:Key="TipAddVehicle">Add this vehicle to the consist</x:String>
<x:String x:Key="TipAddUnit">Add the whole unit to the consist</x:String>
<!-- Loading splash -->
<x:String x:Key="LoadingVehicles">Loading vehicle database</x:String>
<x:String x:Key="LoadingSceneries">Loading sceneries</x:String>
<x:String x:Key="LoadingDone">Starting…</x:String>
<!-- Settings -->
<x:String x:Key="General">General</x:String>
<x:String x:Key="Language">Language</x:String>
<x:String x:Key="Fullscreen">Fullscreen</x:String>
<x:String x:Key="PauseSimInactive">Pause simulation when window is inactive</x:String>
<x:String x:Key="PauseSimStart">Pause on start</x:String>
<x:String x:Key="CursorSensitivity">Cursor sensitivity:</x:String>
<x:String x:Key="MouseHorInvert">Invert mouse horizontally</x:String>
<x:String x:Key="MouseVertInvert">Invert mouse vertically</x:String>
<x:String x:Key="Communication">Communication</x:String>
<x:String x:Key="GamepadSignalIgnore">Ignore gamepad input</x:String>
<x:String x:Key="Feedback">Feedback</x:String>
<x:String x:Key="FeedbackOff">Off</x:String>
<x:String x:Key="FeedbackCASHP">Caps: CA/SHP; ScrollLock: starting resistances</x:String>
<x:String x:Key="FeedbackCA">Caps: CA; ScrollLock: SHP</x:String>
<x:String x:Key="FeedbackLPT">LPT</x:String>
<x:String x:Key="FeedbackPoKeys">PoKeys55</x:String>
<x:String x:Key="FeedbackSP">Serial port (COM)</x:String>
<x:String x:Key="Others">Other</x:String>
<x:String x:Key="SelectEXE">Select exe:</x:String>
<x:String x:Key="SelectEXEAuto">Select automatically</x:String>
<x:String x:Key="DebugMode">Debug mode (test mode)</x:String>
<x:String x:Key="VirtualShunting">Virtual shunting</x:String>
<x:String x:Key="Graphic">Graphics</x:String>
<x:String x:Key="RenderEngine">Render engine</x:String>
<x:String x:Key="REFull">Full</x:String>
<x:String x:Key="REOld">Old</x:String>
<x:String x:Key="RESimpShader">Simplified shader</x:String>
<x:String x:Key="RESimp">Simplified</x:String>
<x:String x:Key="REOff">Disable visual layer</x:String>
<x:String x:Key="REExperimental">Experimental</x:String>
<x:String x:Key="Resolution">Resolution:</x:String>
<x:String x:Key="BufforResolution">Buffer resolution:</x:String>
<x:String x:Key="MaxTexResolution">Maximum texture resolution:</x:String>
<x:String x:Key="MaxTexCabResolution">Maximum cabin texture resolution:</x:String>
<x:String x:Key="TexFilteringQuality">Texture filtering quality:</x:String>
<x:String x:Key="Multisampling">Multisampling:</x:String>
<x:String x:Key="RenderRange">Rendering range:</x:String>
<x:String x:Key="VSync">VSync</x:String>
<x:String x:Key="SmokeDisplay">Smoke display</x:String>
<x:String x:Key="SmokeParticlesMultiplier">Smoke particles multiplier</x:String>
<x:String x:Key="Postprocessing">Post-processing</x:String>
<x:String x:Key="PPReinhard">Reinhard</x:String>
<x:String x:Key="PPACES">ACES</x:String>
<x:String x:Key="ChromaticAberration">Chromatic aberration</x:String>
<x:String x:Key="MotionBlur">Motion blur</x:String>
<x:String x:Key="AdditionalShadersEffects">Additional shader effects</x:String>
<x:String x:Key="ReflectionsCubeMap">Reflections as cubemap</x:String>
<x:String x:Key="RenderVBO">Render VBO</x:String>
<x:String x:Key="RenderShadows">Render shadows</x:String>
<x:String x:Key="ReflectionsFramerate">Reflections refresh rate</x:String>
<x:String x:Key="ShadowsResolution">Shadows resolution</x:String>
<x:String x:Key="ShaderRange">Reflections refresh rate</x:String>
<x:String x:Key="CabShadowsRange">Cabin shadow sources range</x:String>
<x:String x:Key="ShadowDisplay">Display shadows</x:String>
<x:String x:Key="CSROnlyImportant">Only important</x:String>
<x:String x:Key="CSRLimited">Limited</x:String>
<x:String x:Key="CSRAll">All</x:String>
<x:String x:Key="ReflectionsDetails">Reflections detail level</x:String>
<x:String x:Key="RDTerrain">Terrain geometry only</x:String>
<x:String x:Key="RDTerrainModels">Terrain geometry and static models</x:String>
<x:String x:Key="RDTerrainModelsVehicles">Terrain geometry, static models and vehicles</x:String>
<x:String x:Key="ShadowsCabRange">Cabin shadow sources range</x:String>
<x:String x:Key="RenderScreens">Render onboard computer screens</x:String>
<x:String x:Key="RenderScreensThread">Render screens in a separate thread</x:String>
<x:String x:Key="RenderScreensFramerate">Onboard screens refresh rate</x:String>
<x:String x:Key="Physics">Physics</x:String>
<x:String x:Key="TrackCurvesQuality">Track curves quality</x:String>
<x:String x:Key="PhysicsAccuracyIncreased">Increased physics accuracy</x:String>
<x:String x:Key="BreakingPantographPossibility">Possibility of pantograph breaking</x:String>
<x:String x:Key="PowerOverheadLineOnly">Powered only under overhead line</x:String>
<x:String x:Key="SaveSpeedometerTapes">Save speedometer tapes</x:String>
<x:String x:Key="SaveSimLogs">Save simulation log</x:String>
<x:String x:Key="KeepPrevLogs">Keep previous logs</x:String>
<x:String x:Key="DisplaySimulation">Display simulation</x:String>
<x:String x:Key="CrashDamage">Collision damage</x:String>
<x:String x:Key="Sounds">Sound</x:String>
<x:String x:Key="EnableSounds">Enable sound</x:String>
<x:String x:Key="Volume">Volume</x:String>
<x:String x:Key="RadiophoneVolume">Radio volume</x:String>
<x:String x:Key="VehiclesVolume">Vehicles volume</x:String>
<x:String x:Key="PositionedSoundsVolume">Positional sounds volume</x:String>
<x:String x:Key="AmbientVolume">Ambient volume</x:String>
<x:String x:Key="VolumeDuringPause">Volume during pause</x:String>
<x:String x:Key="Starter">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="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>

View File

@@ -1,188 +0,0 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Menu nawigacji -->
<x:String x:Key="NavScenarios">Scenariusze</x:String>
<x:String x:Key="NavDepot">Zajezdnia</x:String>
<x:String x:Key="NavSettings">Ustawienia</x:String>
<x:String x:Key="NavControls">Sterowanie</x:String>
<!-- Widok ogolny -->
<x:String x:Key="Sceneries">Scenerie</x:String>
<x:String x:Key="Vehicles">Pojazdy</x:String>
<x:String x:Key="ScenarioDescription">Opis scenariusza</x:String>
<x:String x:Key="Consist">Zestawienie</x:String>
<x:String x:Key="Start">Uruchom</x:String>
<x:String x:Key="ShowLatestLogTooltip">Otwórz ostatni log symulatora</x:String>
<!-- Lokomotywownia / tworzenie składu -->
<x:String x:Key="VehicleDatabase">Baza pojazdów</x:String>
<x:String x:Key="Search">Szukaj...</x:String>
<x:String x:Key="LoadFromScenery">Wczytaj skład ze scenerii</x:String>
<x:String x:Key="NewConsist">Nowy skład</x:String>
<x:String x:Key="SelectedVehicle">Wybrany pojazd</x:String>
<x:String x:Key="Vehicle">Pojazd</x:String>
<x:String x:Key="Crew">z obsadą</x:String>
<x:String x:Key="Reversed">obrócony</x:String>
<x:String x:Key="NoVehicles">Nie znaleziono pojazdów w databases/vehicles/.</x:String>
<x:String x:Key="ConsistEmpty">Dodaj pojazdy z bazy po lewej stronie lub wczytaj istniejący skład z pliku scenerii.</x:String>
<x:String x:Key="TipMoveLeft">Przesuń w lewo</x:String>
<x:String x:Key="TipMoveRight">Przesuń w prawo</x:String>
<x:String x:Key="TipFlip">Obróć pojazd</x:String>
<x:String x:Key="TipRemove">Usuń ze składu</x:String>
<x:String x:Key="TipDriver">Zmień obsadę (kabina 1 / kabina 2 / pasażer / brak)</x:String>
<x:String x:Key="Scenery">Sceneria</x:String>
<x:String x:Key="Category">Kategoria</x:String>
<x:String x:Key="Class">Klasa</x:String>
<x:String x:Key="All">Wszystkie</x:String>
<x:String x:Key="CatDiesel">Spalinowe</x:String>
<x:String x:Key="CatElectric">Elektryczne</x:String>
<x:String x:Key="CatEMU">EZT</x:String>
<x:String x:Key="CatDMU">SZT</x:String>
<x:String x:Key="CatCarriagesA">Wagony A</x:String>
<x:String x:Key="CatCarriagesB">Wagony B</x:String>
<x:String x:Key="CatElectricLoco">Lokomotywa elektryczna</x:String>
<x:String x:Key="CatDieselLoco">Lokomotywa spalinowa</x:String>
<x:String x:Key="CatSteamLoco">Parowóz</x:String>
<x:String x:Key="CatRailbus">Szynobus</x:String>
<x:String x:Key="CatDraisine">Drezyna</x:String>
<x:String x:Key="CatWork">Pojazdy robocze</x:String>
<x:String x:Key="CatPrototype">Prototyp</x:String>
<x:String x:Key="CatTram">Tramwaj</x:String>
<x:String x:Key="CatCar">Samochód osobowy</x:String>
<x:String x:Key="CatBus">Autobus</x:String>
<x:String x:Key="CatTruck">Samochód ciężarowy</x:String>
<x:String x:Key="CatPeople">Ludzie</x:String>
<x:String x:Key="CatAnimals">Zwierzęta</x:String>
<x:String x:Key="CatWagons">Wagony</x:String>
<x:String x:Key="CatOther">Inne</x:String>
<x:String x:Key="Brakes">Hamulce</x:String>
<x:String x:Key="Loads">Ładunki</x:String>
<x:String x:Key="Coupling">Sprzęg</x:String>
<x:String x:Key="SelectVehicleHint">Wybierz pojazd w składzie, aby go edytować.</x:String>
<x:String x:Key="ComingSoon">Opcje edycji wkrótce.</x:String>
<x:String x:Key="Split">Rozdziel</x:String>
<x:String x:Key="TipSplit">Rozdziel zespół na pojedyncze człony</x:String>
<x:String x:Key="AddVehicle">Dodaj</x:String>
<x:String x:Key="TipAddVehicle">Dodaj ten pojazd do składu</x:String>
<x:String x:Key="TipAddUnit">Dodaj cały zespół do składu</x:String>
<!-- Ekran ładowania -->
<x:String x:Key="LoadingVehicles">Wczytywanie bazy pojazdów</x:String>
<x:String x:Key="LoadingSceneries">Wczytywanie scenerii</x:String>
<x:String x:Key="LoadingDone">Uruchamianie…</x:String>
<!-- Ustawienia -->
<x:String x:Key="General">Ogólne</x:String>
<x:String x:Key="Language">Język</x:String>
<x:String x:Key="Fullscreen">Tryb pełnoekranowy</x:String>
<x:String x:Key="PauseSimInactive">Wstrzymaj symulację, gdy okno nie jest aktywne</x:String>
<x:String x:Key="PauseSimStart">Pauza po uruchomieniu</x:String>
<x:String x:Key="CursorSensitivity">Czułość kursora:</x:String>
<x:String x:Key="MouseHorInvert">Inwersja myszy w poziomie</x:String>
<x:String x:Key="MouseVertInvert">Inwersja myszy w pionie</x:String>
<x:String x:Key="Communication">Komunikacja</x:String>
<x:String x:Key="GamepadSignalIgnore">Ignoruj sygnały z gamepada</x:String>
<x:String x:Key="Feedback">Informacja zwrotna</x:String>
<x:String x:Key="FeedbackOff">Wyłączona</x:String>
<x:String x:Key="FeedbackCASHP">Caps: CA/SHP; ScrollLock: opory rozr.</x:String>
<x:String x:Key="FeedbackCA">Caps: CA; Scrollock: SHP</x:String>
<x:String x:Key="FeedbackLPT">LPT</x:String>
<x:String x:Key="FeedbackPoKeys">PoKeys55</x:String>
<x:String x:Key="FeedbackSP">Serialport (COM)</x:String>
<x:String x:Key="Others">Inne</x:String>
<x:String x:Key="SelectEXE">Wybór exe:</x:String>
<x:String x:Key="SelectEXEAuto">Wybierz automatycznie</x:String>
<x:String x:Key="DebugMode">Tryb testowy (debugmode)</x:String>
<x:String x:Key="VirtualShunting">Wirtualny manewrowy</x:String>
<x:String x:Key="Graphic">Grafika</x:String>
<x:String x:Key="RenderEngine">Silnik renderowania</x:String>
<x:String x:Key="REFull">Pełny</x:String>
<x:String x:Key="REOld">Stary</x:String>
<x:String x:Key="RESimpShader">Uproszczony shaderowy</x:String>
<x:String x:Key="RESimp">Uproszczony</x:String>
<x:String x:Key="REOff">Wyłącz warstwę wizualną</x:String>
<x:String x:Key="REExperimental">Eksperymentalny</x:String>
<x:String x:Key="Resolution">Rozdzielczość:</x:String>
<x:String x:Key="BufforResolution">Rozdzielczość bufora:</x:String>
<x:String x:Key="MaxTexResolution">Maksymalna rozdzielczość tekstur:</x:String>
<x:String x:Key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny:</x:String>
<x:String x:Key="TexFilteringQuality">Jakość filtrowania tekstur:</x:String>
<x:String x:Key="Multisampling">Multisampling:</x:String>
<x:String x:Key="RenderRange">Zasięg renderowania:</x:String>
<x:String x:Key="VSync">VSync</x:String>
<x:String x:Key="SmokeDisplay">Wyświetlanie dymu</x:String>
<x:String x:Key="SmokeParticlesMultiplier">Mnożnik ilości cząsteczek dymu</x:String>
<x:String x:Key="Postprocessing">Postprocessing</x:String>
<x:String x:Key="PPReinhard">Reinhard</x:String>
<x:String x:Key="PPACES">ACES</x:String>
<x:String x:Key="ChromaticAberration">Aberracja chromatyczna</x:String>
<x:String x:Key="MotionBlur">Rozmycie podczas ruchu</x:String>
<x:String x:Key="AdditionalShadersEffects">Dodatkowe efekty shaderów</x:String>
<x:String x:Key="ReflectionsCubeMap">Odbicia jako cubemapa</x:String>
<x:String x:Key="RenderVBO">Renderowanie VBO</x:String>
<x:String x:Key="RenderShadows">Renderowanie cieni</x:String>
<x:String x:Key="ReflectionsFramerate">Odświeżanie odbić</x:String>
<x:String x:Key="ShadowsResolution">Rozdzielczość cieni</x:String>
<x:String x:Key="ShaderRange">Odświeżanie odbić</x:String>
<x:String x:Key="CabShadowsRange">Zasięg źródeł cieni w kabinie</x:String>
<x:String x:Key="ShadowDisplay">Wyświetlanie cieni</x:String>
<x:String x:Key="CSROnlyImportant">Tylko ważne</x:String>
<x:String x:Key="CSRLimited">Ograniczone</x:String>
<x:String x:Key="CSRAll">Wszystkie</x:String>
<x:String x:Key="ReflectionsDetails">Szczegółowość odbić</x:String>
<x:String x:Key="RDTerrain">Tylko geometria terenu</x:String>
<x:String x:Key="RDTerrainModels">Geometria terenu i modele statyczne</x:String>
<x:String x:Key="RDTerrainModelsVehicles">Geometria terenu, modele statyczne i pojazdy</x:String>
<x:String x:Key="ShadowsCabRange">Zasięg źródeł cieni w kabinie</x:String>
<x:String x:Key="RenderScreens">Renderowanie ekranów komputerów pokładowych</x:String>
<x:String x:Key="RenderScreensThread">Renderowanie ekranów w osobnym wątku</x:String>
<x:String x:Key="RenderScreensFramerate">Częstotliwość odświeżania ekranów komputerów pokładowych</x:String>
<x:String x:Key="Physics">Fizyka</x:String>
<x:String x:Key="TrackCurvesQuality">Jakość łuków torów</x:String>
<x:String x:Key="PhysicsAccuracyIncreased">Zwiększona dokładność fizyki</x:String>
<x:String x:Key="BreakingPantographPossibility">Możliwość złamania pantografu</x:String>
<x:String x:Key="PowerOverheadLineOnly">Zasilanie tylko pod siecią trakcyjna</x:String>
<x:String x:Key="SaveSpeedometerTapes">Zapisuj taśmy z prędkościomierza</x:String>
<x:String x:Key="SaveSimLogs">Zapisuj log z symulacji</x:String>
<x:String x:Key="KeepPrevLogs">Zachowuj poprzednie logi</x:String>
<x:String x:Key="DisplaySimulation">Wyświetlaj symulację</x:String>
<x:String x:Key="CrashDamage">Uszkodzenia przy zderzeniu</x:String>
<x:String x:Key="Sounds">Dżwięk</x:String>
<x:String x:Key="EnableSounds">Włącz dźwięk</x:String>
<x:String x:Key="Volume">Głośność</x:String>
<x:String x:Key="RadiophoneVolume">Głośność radiotelefonu</x:String>
<x:String x:Key="VehiclesVolume">Głośność pojazdów</x:String>
<x:String x:Key="PositionedSoundsVolume">Głośność dżwięków pozycjonowanych</x:String>
<x:String x:Key="AmbientVolume">Głośność otoczenia</x:String>
<x:String x:Key="VolumeDuringPause">Głośność podczas pauzy</x:String>
<x:String x:Key="Starter">Starter</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="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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -12,10 +12,20 @@ public class Scenery
public string Group; public string Group;
public string Path; public string Path;
// Starter directives (comment-syntax metadata, see wiki "Plik scenerii").
// They do not affect the simulation, only how a starter presents the scenery.
public string Name; // //$n - scenery name
public string Description; // //$d - scenery description
public string ImageName; // //$i - main-window image (scenery thumbnail)
// The file content with each trainset block replaced by a {{i}} placeholder, // The file content with each trainset block replaced by a {{i}} placeholder,
// used to rebuild the .scn on export. // used to rebuild the .scn on export.
private readonly string _template; private readonly string _template;
// Lazily resolved, cached path to the //$i image on disk (null if not found).
private string _imagePath;
private bool _imagePathResolved;
public Scenery(string path) public Scenery(string path)
{ {
this.Path = path; this.Path = path;
@@ -25,13 +35,13 @@ public class Scenery
var encoding = Encoding.GetEncoding(1250); // Windows-1250 var encoding = Encoding.GetEncoding(1250); // Windows-1250
string content = File.ReadAllText(path, encoding); string content = File.ReadAllText(path, encoding);
// property scanning // property scanning - starter directives written as // comments.
var match = Regex.Match( // \b after the letter keeps //$d from matching //$decor, //$i from
content, // matching //$it, etc.
@"^//\$l\s*([^\r\n]*)", this.Group = MatchDirective(content, "l");
RegexOptions.Multiline this.Name = MatchDirective(content, "n");
); this.Description = MatchDirective(content, "d");
this.Group = match.Success ? match.Groups[1].Value : null; this.ImageName = MatchDirective(content, "i");
// parsing trainsets // parsing trainsets
@@ -65,4 +75,64 @@ public class Scenery
result = result.Replace("{{" + i + "}}", Trainsets[i].ToSceneryEntry()); result = result.Replace("{{" + i + "}}", Trainsets[i].ToSceneryEntry());
return result; return result;
} }
/// <summary>
/// Reads a single starter directive value (the text after //$&lt;letter&gt;).
/// Returns null when the directive is absent. The trailing whitespace
/// requirement separates e.g. //$d from //$decor and //$i from //$it.
/// </summary>
private static string MatchDirective(string content, string letter)
{
var match = Regex.Match(
content,
@"^//\$" + letter + @"\b[ \t]*([^\r\n]*)",
RegexOptions.Multiline
);
return match.Success ? match.Groups[1].Value.Trim() : null;
}
/// <summary>
/// Resolved on-disk path of the //$i scenery image, or null if none is
/// declared or the file cannot be found. The value of //$i may be a bare
/// file name or a path; common locations are probed and the result cached.
/// </summary>
public string ImagePath
{
get
{
if (_imagePathResolved)
return _imagePath;
_imagePathResolved = true;
_imagePath = ResolveImagePath();
return _imagePath;
}
}
private string ResolveImagePath()
{
if (string.IsNullOrWhiteSpace(ImageName))
return null;
// normalise legacy back-slashes so paths work cross-platform
string name = ImageName.Replace('\\', '/').Trim();
string scnDir = System.IO.Path.GetDirectoryName(Path) ?? "."; // e.g. scenery/
string root = System.IO.Path.GetDirectoryName(scnDir) ?? "."; // MaSzyna root
// probe the usual places, first hit wins
var candidates = new List<string>
{
name, // as given (cwd / absolute)
System.IO.Path.Combine(root, name), // relative to MaSzyna root
System.IO.Path.Combine(scnDir, name), // next to the .scn
System.IO.Path.Combine(scnDir, "images", name), // scenery/images/
System.IO.Path.Combine(root, "scenery", "images", name), // scenery/images/ from root
};
foreach (string candidate in candidates)
{
if (File.Exists(candidate))
return candidate;
}
return null;
}
} }

View File

@@ -2,7 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace StarterNG.Classes; namespace StarterNG.Classes;
@@ -14,43 +16,43 @@ namespace StarterNG.Classes;
/// <summary>Root object of a single vehicle JSON file.</summary> /// <summary>Root object of a single vehicle JSON file.</summary>
public class VehicleEntry public class VehicleEntry
{ {
[JsonProperty("uuid")] public string? Uuid { get; set; } [JsonPropertyName("uuid")] public string? Uuid { get; set; }
[JsonProperty("schema_version")] public int SchemaVersion { get; set; } [JsonPropertyName("schema_version")] public int SchemaVersion { get; set; }
[JsonProperty("groups")] public List<VehicleGroup> Groups { get; set; } = new(); [JsonPropertyName("groups")] public List<VehicleGroup> Groups { get; set; } = new();
[JsonProperty("textures")] public List<VehicleTexture> Textures { get; set; } = new(); [JsonPropertyName("textures")] public List<VehicleTexture> Textures { get; set; } = new();
[JsonProperty("sets")] public List<VehicleSet> Sets { get; set; } = new(); [JsonPropertyName("sets")] public List<VehicleSet> Sets { get; set; } = new();
[JsonProperty("unknown")] public List<string> Unknown { get; set; } = new(); [JsonPropertyName("unknown")] public List<string> Unknown { get; set; } = new();
} }
/// <summary>Merged database root (databases/vehicles/vehicles.json).</summary> /// <summary>Merged database root (databases/vehicles/vehicles.json).</summary>
public class VehicleEntryCollection public class VehicleEntryCollection
{ {
[JsonProperty("vehicles")] public List<VehicleEntry> Vehicles { get; set; } = new(); [JsonPropertyName("vehicles")] public List<VehicleEntry> Vehicles { get; set; } = new();
} }
/// <summary>Legacy header / vehicle category group.</summary> /// <summary>Legacy header / vehicle category group.</summary>
public class VehicleGroup public class VehicleGroup
{ {
[JsonProperty("id")] public string Id { get; set; } = ""; [JsonPropertyName("id")] public string Id { get; set; } = "";
[JsonProperty("category")] public string? Category { get; set; } [JsonPropertyName("category")] public string? Category { get; set; }
[JsonProperty("mini")] public string? Mini { get; set; } [JsonPropertyName("mini")] public string? Mini { get; set; }
[JsonProperty("archived")] public bool Archived { get; set; } [JsonPropertyName("archived")] public bool Archived { get; set; }
[JsonProperty("implicit")] public bool Implicit { get; set; } [JsonPropertyName("implicit")] public bool Implicit { get; set; }
} }
/// <summary>A single .mat skin entry.</summary> /// <summary>A single .mat skin entry.</summary>
public class VehicleTexture public class VehicleTexture
{ {
[JsonProperty("uuid")] public string? Uuid { get; set; } [JsonPropertyName("uuid")] public string? Uuid { get; set; }
[JsonProperty("directory")] public string Directory { get; set; } = ""; [JsonPropertyName("directory")] public string Directory { get; set; } = "";
[JsonProperty("skinfile")] public string Skinfile { get; set; } = ""; [JsonPropertyName("skinfile")] public string Skinfile { get; set; } = "";
[JsonProperty("model")] public string? Model { get; set; } [JsonPropertyName("model")] public string? Model { get; set; }
[JsonProperty("group")] public string? Group { get; set; } [JsonPropertyName("group")] public string? Group { get; set; }
[JsonProperty("mini_ref")] public string? MiniRef { get; set; } [JsonPropertyName("mini_ref")] public string? MiniRef { get; set; }
[JsonProperty("texture_mini")] public string? TextureMini { get; set; } [JsonPropertyName("texture_mini")] public string? TextureMini { get; set; }
[JsonProperty("wreck")] public bool Wreck { get; set; } [JsonPropertyName("wreck")] public bool Wreck { get; set; }
[JsonProperty("aliases")] public List<VehicleAlias> Aliases { get; set; } = new(); [JsonPropertyName("aliases")] public List<VehicleAlias> Aliases { get; set; } = new();
[JsonProperty("meta")] public VehicleMeta? Meta { get; set; } [JsonPropertyName("meta")] public VehicleMeta? Meta { get; set; }
/// <summary>Full skin path = directory + skinfile.</summary> /// <summary>Full skin path = directory + skinfile.</summary>
[JsonIgnore] public string FullPath => Directory + Skinfile; [JsonIgnore] public string FullPath => Directory + Skinfile;
@@ -69,25 +71,25 @@ public class VehicleTexture
/// <summary>Alternate mapping from a malformed multi-= legacy line.</summary> /// <summary>Alternate mapping from a malformed multi-= legacy line.</summary>
public class VehicleAlias public class VehicleAlias
{ {
[JsonProperty("model")] public string? Model { get; set; } [JsonPropertyName("model")] public string? Model { get; set; }
[JsonProperty("group")] public string? Group { get; set; } [JsonPropertyName("group")] public string? Group { get; set; }
[JsonProperty("mini_ref")] public string? MiniRef { get; set; } [JsonPropertyName("mini_ref")] public string? MiniRef { get; set; }
[JsonProperty("texture_mini")] public string? TextureMini { get; set; } [JsonPropertyName("texture_mini")] public string? TextureMini { get; set; }
} }
/// <summary>Parsed metadata from the legacy // comment section.</summary> /// <summary>Parsed metadata from the legacy // comment section.</summary>
public class VehicleMeta public class VehicleMeta
{ {
[JsonProperty("raw")] public string? Raw { get; set; } [JsonPropertyName("raw")] public string? Raw { get; set; }
[JsonProperty("version")] public string? Version { get; set; } [JsonPropertyName("version")] public string? Version { get; set; }
[JsonProperty("vehicle")] public string? Vehicle { get; set; } [JsonPropertyName("vehicle")] public string? Vehicle { get; set; }
[JsonProperty("operator")] public string? Operator { get; set; } [JsonPropertyName("operator")] public string? Operator { get; set; }
[JsonProperty("depot")] public string? Depot { get; set; } [JsonPropertyName("depot")] public string? Depot { get; set; }
[JsonProperty("revision_date")] public string? RevisionDate { get; set; } [JsonPropertyName("revision_date")] public string? RevisionDate { get; set; }
[JsonProperty("revision_place")] public string? RevisionPlace { get; set; } [JsonPropertyName("revision_place")] public string? RevisionPlace { get; set; }
[JsonProperty("texture_author")] public string? TextureAuthor { get; set; } [JsonPropertyName("texture_author")] public string? TextureAuthor { get; set; }
[JsonProperty("photo_author")] public string? PhotoAuthor { get; set; } [JsonPropertyName("photo_author")] public string? PhotoAuthor { get; set; }
[JsonProperty("extra")] public List<string> Extra { get; set; } = new(); [JsonPropertyName("extra")] public List<string> Extra { get; set; } = new();
} }
/// <summary> /// <summary>
@@ -109,12 +111,16 @@ public class VehicleDatabase
/// <summary>skinfile (without extension, lower-case) -> texture.</summary> /// <summary>skinfile (without extension, lower-case) -> texture.</summary>
public Dictionary<string, VehicleTexture> TextureBySkin { get; } = new(); public Dictionary<string, VehicleTexture> TextureBySkin { get; } = new();
// Newtonsoft.Json matches property names case-insensitively, allows trailing // System.Text.Json options matching the previous Newtonsoft leniency: property
// commas, and skips // and /* */ comments by default — the same leniency the // names matched case-insensitively, // and /* */ comments skipped, and trailing
// previous System.Text.Json options provided. Unknown members are ignored. // commas allowed. Unknown members are ignored by default. The source-generated
private static readonly JsonSerializerSettings JsonOpts = new() // VehicleJsonContext resolver keeps deserialization AOT/trim-safe (no reflection).
private static readonly JsonSerializerOptions JsonOpts = new()
{ {
MissingMemberHandling = MissingMemberHandling.Ignore PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
TypeInfoResolver = VehicleJsonContext.Default,
}; };
/// <summary> /// <summary>
@@ -177,8 +183,9 @@ public class VehicleDatabase
{ {
try try
{ {
var collection = JsonConvert.DeserializeObject<VehicleEntryCollection>( var collection = JsonSerializer.Deserialize(
File.ReadAllText(file), JsonOpts); File.ReadAllText(file),
(JsonTypeInfo<VehicleEntryCollection>)JsonOpts.GetTypeInfo(typeof(VehicleEntryCollection)));
if (collection?.Vehicles is null) return; if (collection?.Vehicles is null) return;
foreach (var entry in collection.Vehicles) foreach (var entry in collection.Vehicles)
Ingest(entry); Ingest(entry);
@@ -193,8 +200,9 @@ public class VehicleDatabase
{ {
try try
{ {
var entry = JsonConvert.DeserializeObject<VehicleEntry>( var entry = JsonSerializer.Deserialize(
File.ReadAllText(file), JsonOpts); File.ReadAllText(file),
(JsonTypeInfo<VehicleEntry>)JsonOpts.GetTypeInfo(typeof(VehicleEntry)));
if (entry is not null) if (entry is not null)
Ingest(entry); Ingest(entry);
} }
@@ -359,8 +367,17 @@ public class VehicleDatabase
/// <summary>Automatic consist definition (legacy ^x grouping).</summary> /// <summary>Automatic consist definition (legacy ^x grouping).</summary>
public class VehicleSet public class VehicleSet
{ {
[JsonProperty("uuid")] public string? Uuid { get; set; } [JsonPropertyName("uuid")] public string? Uuid { get; set; }
[JsonProperty("mode")] public string? Mode { get; set; } [JsonPropertyName("mode")] public string? Mode { get; set; }
[JsonProperty("count")] public int Count { get; set; } [JsonPropertyName("count")] public int Count { get; set; }
[JsonProperty("texture_refs")] public List<string> TextureRefs { get; set; } = new(); [JsonPropertyName("texture_refs")] public List<string> TextureRefs { get; set; } = new();
}
// Source-generated JSON metadata — makes deserialization AOT/trim-safe by avoiding
// runtime reflection over the model types. Nested types (VehicleGroup, VehicleTexture,
// VehicleSet, VehicleAlias, VehicleMeta) are pulled in automatically by the generator.
[JsonSerializable(typeof(VehicleEntry))]
[JsonSerializable(typeof(VehicleEntryCollection))]
internal partial class VehicleJsonContext : JsonSerializerContext
{
} }

View File

@@ -1,16 +1,16 @@
<sukiUi:SukiWindow <sukiUi:SukiWindow
x:Class="StarterNG.MainWindow" x:Class="StarterNG.MainWindow"
x:CompileBindings="True"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI" xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
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"
xmlns:svg="clr-namespace:Avalonia.Svg;assembly=Avalonia.Svg"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:StarterNG.Views" xmlns:views="clr-namespace:StarterNG.Views"
xmlns:starterNg="clr-namespace:StarterNG" xmlns:starterNg="clr-namespace:StarterNG"
BackgroundStyle="Flat" BackgroundStyle="Flat"
Title="MaSzyna - Symulator pojazdów szynowych" Height="800" Width="1000" WindowStartupLocation="CenterScreen"> Title="MaSzyna - Symulator pojazdów szynowych" Height="800" Width="1200" WindowStartupLocation="CenterScreen">
<sukiUi:SukiWindow.RightWindowTitleBarControls> <sukiUi:SukiWindow.RightWindowTitleBarControls>
<Button Classes="Basic" Click="openLastLogClick" <Button Classes="Basic" Click="openLastLogClick"
ToolTip.Tip="{Binding [ShowLatestLogTooltip], Source={x:Static starterNg:App.Loc}}"> ToolTip.Tip="{Binding [ShowLatestLogTooltip], Source={x:Static starterNg:App.Loc}}">
@@ -73,8 +73,11 @@
<sukiUi:SukiSideMenu.HeaderContent> <sukiUi:SukiSideMenu.HeaderContent>
<svg:Svg <!-- Logo PNG. Replace Assets/Logo/maszyna_white.png (white logo for the
Path="/Assets/Logo/maszyna_white.svg" dark side-menu header). A maszyna_black.png variant also lives in
Assets/Logo for light backgrounds - swap the Source if needed. -->
<Image
Source="/Assets/Logo/maszyna_white.png"
Width="200" Width="200"
Margin="16" Margin="16"
Height="60" /> Height="60" />
@@ -104,9 +107,6 @@
<materialIcons:MaterialIcon Kind="Github" /> <materialIcons:MaterialIcon Kind="Github" />
</Button> </Button>
</StackPanel> </StackPanel>
</sukiUi:SukiSideMenu.FooterContent> </sukiUi:SukiSideMenu.FooterContent>
</sukiUi:SukiSideMenu> </sukiUi:SukiSideMenu>
</sukiUi:SukiWindow> </sukiUi:SukiWindow>

View File

@@ -1,35 +1,166 @@
using System.ComponentModel; using System;
using Avalonia.Controls; using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Xml;
namespace StarterNG.Services; namespace StarterNG.Services;
/// <summary>
/// Loads UI translations from plain XML files shipped in a <c>lang/</c> folder next
/// to the executable (instead of dictionaries compiled into the assembly). The files
/// are parsed with <see cref="XmlReader"/>, which works under Native AOT — unlike the
/// Avalonia XAML loader, which has no runtime parser in AOT builds.
///
/// File format (lang/en.xml):
/// <code>
/// &lt;Language code="en" name="English"&gt;
/// &lt;String key="NavScenarios"&gt;Scenarios&lt;/String&gt;
/// &lt;/Language&gt;
/// </code>
/// </summary>
public class LocalizationService : INotifyPropertyChanged public class LocalizationService : INotifyPropertyChanged
{ {
private IResourceProvider _currentProvider = new ResourceDictionary(); /// <summary>Folder holding the language files, resolved as starter/lang next to the executable.</summary>
public static string LangDirectory =>
Path.Combine(AppContext.BaseDirectory, "starter", "lang");
private Dictionary<string, string> _strings = new();
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>Display name of the active language (e.g. "English", "Polski").</summary>
public string CurrentLanguage { get; private set; } = "English";
public string CurrentLanguage; /// <summary>Short ISO code of the active language (e.g. "en", "pl").</summary>
public string CurrentLangCode { get; private set; } = "en";
public string this[string key] /// <summary>Indexer used by the XAML bindings: returns the key itself when missing.</summary>
public string this[string key] =>
_strings.TryGetValue(key, out var value) ? value : key;
/// <summary>
/// Metadata for one discovered language file.
/// </summary>
public readonly record struct LanguageInfo(string Code, string Name, string Path);
/// <summary>
/// Scans the lang/ folder and returns the header metadata of every *.xml file
/// without loading all of its strings. Used to populate the language selector.
/// </summary>
public static IReadOnlyList<LanguageInfo> AvailableLanguages()
{ {
get var list = new List<LanguageInfo>();
if (!Directory.Exists(LangDirectory))
return list;
foreach (var path in Directory.EnumerateFiles(LangDirectory, "*.xml"))
{ {
if (_currentProvider.TryGetResource(key, null, out var value)) try
{ {
return value?.ToString() ?? key; var (code, name) = ReadHeader(path);
if (!string.IsNullOrEmpty(code))
list.Add(new LanguageInfo(code, string.IsNullOrEmpty(name) ? code : name, path));
}
catch
{
// Skip malformed files rather than crashing the launcher.
} }
return key;
} }
return list;
} }
public void SetLanguage(IResourceProvider provider, string langName) /// <summary>
/// Loads the language whose code or display name matches <paramref name="codeOrName"/>.
/// Falls back to English ("en") when no match is found. Returns the resolved
/// display name so callers can keep their stored setting consistent.
/// </summary>
public string Load(string codeOrName)
{ {
_currentProvider = provider; var languages = AvailableLanguages();
CurrentLanguage = langName;
LanguageInfo? match = null;
foreach (var lang in languages)
{
if (string.Equals(lang.Code, codeOrName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(lang.Name, codeOrName, StringComparison.OrdinalIgnoreCase))
{
match = lang;
break;
}
}
// Fall back to English, then to whatever is available first.
if (match is null)
{
foreach (var lang in languages)
if (string.Equals(lang.Code, "en", StringComparison.OrdinalIgnoreCase))
{
match = lang;
break;
}
if (match is null && languages.Count > 0)
match = languages[0];
}
if (match is { } info)
LoadFile(info.Path, info.Code, info.Name);
else
{
// No files at all: keep keys visible instead of blank UI.
_strings = new Dictionary<string, string>();
CurrentLangCode = "en";
CurrentLanguage = "English";
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item"));
return CurrentLanguage;
} }
private void LoadFile(string path, string code, string name)
{
var dict = new Dictionary<string, string>(StringComparer.Ordinal);
var settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
using var reader = XmlReader.Create(path, settings);
// NOTE: ReadElementContentAsString() already advances the reader past the
// element's end tag onto the *next* node, so we must NOT call Read() again
// in that case - doing so would skip every second <String> entry. We only
// advance explicitly for nodes we don't consume here.
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "String")
{
var key = reader.GetAttribute("key");
var value = reader.ReadElementContentAsString(); // advances the reader
if (!string.IsNullOrEmpty(key))
dict[key] = value;
}
else
{
reader.Read();
}
}
_strings = dict;
CurrentLangCode = code;
CurrentLanguage = name;
}
private static (string code, string name) ReadHeader(string path)
{
var settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
using var reader = XmlReader.Create(path, settings);
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Language")
{
return (reader.GetAttribute("code") ?? "",
reader.GetAttribute("name") ?? "");
}
}
return ("", "");
}
} }

View File

@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
@@ -8,6 +10,8 @@
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion> <FileVersion>1.0.0.0</FileVersion>
<AssemblyName>Starter</AssemblyName>
<Company>eu07.pl</Company>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@@ -17,20 +21,18 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.10" /> <PackageReference Include="Avalonia" Version="12.0.4" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.10" /> <PackageReference Include="Avalonia.Desktop" Version="12.0.4" />
<PackageReference Include="Avalonia.Svg" Version="11.3.0" /> <PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.10" /> <PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.4" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.10"> <PackageReference Include="Avalonia.Diagnostics" Version="11.3.17">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets> <IncludeAssets Condition="'$(Configuration)' != 'Debug'">none</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets> <PrivateAssets Condition="'$(Configuration)' != 'Debug'">all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="HotAvalonia" Version="3.0.2" /> <PackageReference Include="HotAvalonia" Version="3.0.2" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.4.1" /> <PackageReference Include="Material.Icons.Avalonia" Version="3.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" /> <PackageReference Include="SukiUI" Version="7.0.1" />
<PackageReference Include="SukiUI" Version="6.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -38,15 +40,24 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Assets\Logo\maszyna_black.svg" />
<AvaloniaResource Include="Assets\Logo\maszyna_black.svg" />
<None Remove="Assets\Logo\maszyna_white.svg" />
<AvaloniaResource Include="Assets\Logo\maszyna_white.svg" />
<None Remove="Assets\Logo\Other\Discord.svg" /> <None Remove="Assets\Logo\Other\Discord.svg" />
<AvaloniaResource Include="Assets\Logo\Other\Discord.svg" /> <AvaloniaResource Include="Assets\Logo\Other\Discord.svg" />
<None Remove="Assets\Logo\splash.png" /> <!-- Logo bitmaps (splash.png + maszyna_black/white.png). Globbed so newly
<AvaloniaResource Include="Assets\Logo\splash.png" /> added PNGs are embedded automatically without editing this file. -->
<None Remove="Assets\Logo\*.png" />
<AvaloniaResource Include="Assets\Logo\*.png" />
<None Remove="Assets\Test\ep07p.bmp" /> <None Remove="Assets\Test\ep07p.bmp" />
<AvaloniaResource Include="Assets\Test\ep07p.bmp" /> <AvaloniaResource Include="Assets\Test\ep07p.bmp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<!-- Translations are plain XML loaded at runtime (LocalizationService), not
compiled into the assembly. They are copied verbatim into a starter/lang/
folder next to the executable so they can be edited or extended without a
rebuild. XmlReader parsing is AOT-safe, unlike the Avalonia XAML loader. -->
<Content Include="starter\lang\*.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>starter\lang\%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
</Project> </Project>

View File

@@ -5,6 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:starterNg="clr-namespace:StarterNG" xmlns:starterNg="clr-namespace:StarterNG"
xmlns:views="clr-namespace:StarterNG.Views" xmlns:views="clr-namespace:StarterNG.Views"
x:CompileBindings="True"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="700"
x:Class="StarterNG.Views.Depot"> x:Class="StarterNG.Views.Depot">
<Grid Margin="16" ColumnSpacing="16" RowSpacing="16"> <Grid Margin="16" ColumnSpacing="16" RowSpacing="16">

View File

@@ -1,15 +1,17 @@
<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:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI" xmlns:sukiUi="clr-namespace:SukiUI.Controls;assembly=SukiUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
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.Scenarios"> x:Class="StarterNG.Views.Scenarios"
x:CompileBindings="True">
<Grid Margin="16" ColumnSpacing="16" RowSpacing="16"> <Grid Margin="16" ColumnSpacing="16" RowSpacing="16">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="300" /> <ColumnDefinition Width="300" />
<ColumnDefinition /> <ColumnDefinition />
<ColumnDefinition Width="280" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
@@ -34,7 +36,20 @@
</sukiUi:GroupBox> </sukiUi:GroupBox>
<sukiUi:GroupBox Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}" Grid.Row="2" Grid.ColumnSpan="2"> <!-- Scenery image (//$i) shown as a 1:1 square thumbnail -->
<Border Grid.Column="2" Grid.Row="0" ClipToBounds="True" VerticalAlignment="Top"
Height="{Binding $self.Bounds.Width}">
<Image Name="sceneryImage" Stretch="Uniform" />
</Border>
<!-- Scenery description (//$d), distinct from the scenario/consist description -->
<sukiUi:GroupBox Grid.Column="2" Grid.Row="1" Header="{Binding [SceneryDescription], Source={x:Static starterNg:App.Loc}}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock TextWrapping="Wrap" Name="sceneryDescription">
</TextBlock>
</ScrollViewer>
</sukiUi:GroupBox>
<sukiUi:GroupBox Header="{Binding [Consist], Source={x:Static starterNg:App.Loc}}" Grid.Row="2" Grid.ColumnSpan="3">
<ScrollViewer VerticalAlignment="Bottom" <ScrollViewer VerticalAlignment="Bottom"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled"> VerticalScrollBarVisibility="Disabled">
@@ -44,7 +59,7 @@
</sukiUi:GroupBox> </sukiUi:GroupBox>
<Button Grid.Column="0" Height="36" Classes="Flat" <Button Grid.Column="0" Height="36" Classes="Flat"
VerticalAlignment="Bottom" Grid.Row="3" Grid.ColumnSpan="2" VerticalAlignment="Bottom" Grid.Row="3" Grid.ColumnSpan="3"
Click="StartButton_OnClick" Click="StartButton_OnClick"
Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}"> Content="{Binding [Start], Source={x:Static starterNg:App.Loc}}">

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -74,6 +74,9 @@ public partial class Scenarios : UserControl
Scenery selectedScn = Sceneries[tag]; Scenery selectedScn = Sceneries[tag];
vehicleList.Items.Clear(); vehicleList.Items.Clear();
// scenery-level info (distinct from the per-consist scenario description)
ShowSceneryInfo(selectedScn);
// add all trainsets to list // add all trainsets to list
for (int i = 0; i < selectedScn.Trainsets.Count; i++) for (int i = 0; i < selectedScn.Trainsets.Count; i++)
{ {
@@ -91,6 +94,29 @@ public partial class Scenarios : UserControl
} }
// Shows the selected scenery's description (//$d) and 1:1 image (//$i).
private void ShowSceneryInfo(Scenery scenery)
{
sceneryDescription.Text = scenery.Description;
string? imagePath = scenery.ImagePath;
if (imagePath is not null)
{
try
{
sceneryImage.Source = new Bitmap(imagePath);
}
catch
{
sceneryImage.Source = null; // unreadable / unsupported image
}
}
else
{
sceneryImage.Source = null;
}
}
private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) private void VehicleList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{ {
// get selected scenery // get selected scenery

View File

@@ -6,7 +6,8 @@
xmlns:objectModel="clr-namespace:System.Collections.ObjectModel;assembly=System.ObjectModel" xmlns:objectModel="clr-namespace:System.Collections.ObjectModel;assembly=System.ObjectModel"
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"
x:CompileBindings="True">
<DockPanel> <DockPanel>
<!-- Save / reset bar --> <!-- Save / reset bar -->
<Border DockPanel.Dock="Bottom" Padding="16,10" Background="#11000000"> <Border DockPanel.Dock="Bottom" Padding="16,10" Background="#11000000">

View File

@@ -288,22 +288,7 @@ public partial class Settings : UserControl
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/")) App.ApplyLanguage(lang);
{
Source = new Uri($"Assets/Langs/{lang}.axaml", UriKind.Relative)
};
var currentLangDict = App.Current.Resources.MergedDictionaries
.OfType<ResourceInclude>()
.FirstOrDefault(d => d.Source != null && d.Source.OriginalString.Contains("Langs/"));
if (currentLangDict != null)
{
App.Current.Resources.MergedDictionaries.Remove(currentLangDict);
}
App.Current.Resources.MergedDictionaries.Add(langDict);
App.Loc.SetLanguage(langDict, lang);
} }
// ── tiny helpers ────────────────────────────────────────────────────── // ── tiny helpers ──────────────────────────────────────────────────────

View File

@@ -0,0 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- StarterNG language file. Loaded at runtime from the lang/ folder next to the executable. -->
<!-- Add a new language by copying this file and changing the code/name attributes below. -->
<Language code="en" name="English">
<!-- Nav menu -->
<String key="NavScenarios">Scenarios</String>
<String key="NavDepot">Depot</String>
<String key="NavSettings">Settings</String>
<String key="NavControls">Controls</String>
<!-- General view -->
<String key="Sceneries">Sceneries</String>
<String key="Vehicles">Vehicles</String>
<String key="ScenarioDescription">Scenario description</String>
<String key="SceneryDescription">Scenery description</String>
<String key="Consist">Consist</String>
<String key="Start">Start</String>
<String key="ShowLatestLogTooltip">Open latest simulation log</String>
<!-- Depot / consist creation -->
<String key="VehicleDatabase">Vehicle database</String>
<String key="Search">Search...</String>
<String key="LoadFromScenery">Load consist from scenery</String>
<String key="NewConsist">New consist</String>
<String key="SelectedVehicle">Selected vehicle</String>
<String key="Vehicle">Vehicle</String>
<String key="Crew">with crew</String>
<String key="Reversed">reversed</String>
<String key="NoVehicles">No vehicles found in databases/vehicles/.</String>
<String key="ConsistEmpty">Add vehicles from the database on the left, or load an existing consist from a scenery file.</String>
<String key="TipMoveLeft">Move left</String>
<String key="TipMoveRight">Move right</String>
<String key="TipFlip">Reverse vehicle</String>
<String key="TipRemove">Remove from consist</String>
<String key="TipDriver">Change crew (cab 1 / cab 2 / passenger / none)</String>
<String key="Scenery">Scenery</String>
<String key="Category">Category</String>
<String key="Class">Class</String>
<String key="All">All</String>
<String key="CatDiesel">Diesel</String>
<String key="CatElectric">Electric</String>
<String key="CatEMU">EMU</String>
<String key="CatDMU">DMU</String>
<String key="CatCarriagesA">Carriages A</String>
<String key="CatCarriagesB">Carriages B</String>
<String key="CatElectricLoco">Electric locomotive</String>
<String key="CatDieselLoco">Diesel locomotive</String>
<String key="CatSteamLoco">Steam locomotive</String>
<String key="CatRailbus">Railbus</String>
<String key="CatDraisine">Draisine</String>
<String key="CatWork">Work vehicles</String>
<String key="CatPrototype">Prototype</String>
<String key="CatTram">Tram</String>
<String key="CatCar">Car</String>
<String key="CatBus">Bus</String>
<String key="CatTruck">Truck</String>
<String key="CatPeople">People</String>
<String key="CatAnimals">Animals</String>
<String key="CatWagons">Wagons</String>
<String key="CatOther">Other</String>
<String key="Brakes">Brakes</String>
<String key="Loads">Loads</String>
<String key="Coupling">Coupling</String>
<String key="SelectVehicleHint">Select a vehicle in the consist to edit it.</String>
<String key="ComingSoon">Editing options coming soon.</String>
<String key="Split">Split</String>
<String key="TipSplit">Split the unit into individual cars</String>
<String key="AddVehicle">Add</String>
<String key="TipAddVehicle">Add this vehicle to the consist</String>
<String key="TipAddUnit">Add the whole unit to the consist</String>
<!-- Loading splash -->
<String key="LoadingVehicles">Loading vehicle database</String>
<String key="LoadingSceneries">Loading sceneries</String>
<String key="LoadingDone">Starting…</String>
<!-- Settings -->
<String key="General">General</String>
<String key="Language">Language</String>
<String key="Fullscreen">Fullscreen</String>
<String key="PauseSimInactive">Pause simulation when window is inactive</String>
<String key="PauseSimStart">Pause on start</String>
<String key="CursorSensitivity">Cursor sensitivity:</String>
<String key="MouseHorInvert">Invert mouse horizontally</String>
<String key="MouseVertInvert">Invert mouse vertically</String>
<String key="Communication">Communication</String>
<String key="GamepadSignalIgnore">Ignore gamepad input</String>
<String key="Feedback">Feedback</String>
<String key="FeedbackOff">Off</String>
<String key="FeedbackCASHP">Caps: CA/SHP; ScrollLock: starting resistances</String>
<String key="FeedbackCA">Caps: CA; ScrollLock: SHP</String>
<String key="FeedbackLPT">LPT</String>
<String key="FeedbackPoKeys">PoKeys55</String>
<String key="FeedbackSP">Serial port (COM)</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="Graphic">Graphics</String>
<String key="RenderEngine">Render engine</String>
<String key="REFull">Full</String>
<String key="REOld">Old</String>
<String key="RESimpShader">Simplified shader</String>
<String key="RESimp">Simplified</String>
<String key="REOff">Disable visual layer</String>
<String key="REExperimental">Experimental</String>
<String key="Resolution">Resolution:</String>
<String key="BufforResolution">Buffer resolution:</String>
<String key="MaxTexResolution">Maximum texture resolution:</String>
<String key="MaxTexCabResolution">Maximum cabin texture resolution:</String>
<String key="TexFilteringQuality">Texture filtering quality:</String>
<String key="Multisampling">Multisampling:</String>
<String key="RenderRange">Rendering range:</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>
<String key="ReflectionsCubeMap">Reflections as cubemap</String>
<String key="RenderVBO">Render VBO</String>
<String key="RenderShadows">Render shadows</String>
<String key="ReflectionsFramerate">Reflections refresh rate</String>
<String key="ShadowsResolution">Shadows resolution</String>
<String key="ShaderRange">Reflections refresh rate</String>
<String key="CabShadowsRange">Cabin shadow sources range</String>
<String key="ShadowDisplay">Display shadows</String>
<String key="CSROnlyImportant">Only important</String>
<String key="CSRLimited">Limited</String>
<String key="CSRAll">All</String>
<String key="ReflectionsDetails">Reflections detail level</String>
<String key="RDTerrain">Terrain geometry only</String>
<String key="RDTerrainModels">Terrain geometry and static models</String>
<String key="RDTerrainModelsVehicles">Terrain geometry, static models and vehicles</String>
<String key="ShadowsCabRange">Cabin shadow sources range</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>
<String key="TrackCurvesQuality">Track curves quality</String>
<String key="PhysicsAccuracyIncreased">Increased physics accuracy</String>
<String key="BreakingPantographPossibility">Possibility of pantograph breaking</String>
<String key="PowerOverheadLineOnly">Powered only under overhead line</String>
<String key="SaveSpeedometerTapes">Save speedometer tapes</String>
<String key="SaveSimLogs">Save simulation log</String>
<String key="KeepPrevLogs">Keep previous logs</String>
<String key="DisplaySimulation">Display simulation</String>
<String key="CrashDamage">Collision damage</String>
<String key="Sounds">Sound</String>
<String key="EnableSounds">Enable sound</String>
<String key="Volume">Volume</String>
<String key="RadiophoneVolume">Radio volume</String>
<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="Starter">Starter</String>
<String key="AutoCloseStarter">Automatically close starter</String>
<String key="LargeThumbnails">Large thumbnails</String>
<String key="AutoExpandSceneryTree">Automatically expand scenery tree</String>
<!-- Settings actions -->
<String key="Save">Save</String>
<String key="Reset">Reset</String>
<String key="SettingsSaved">Settings saved</String>
</Language>

View File

@@ -0,0 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- StarterNG language file. Loaded at runtime from the lang/ folder next to the executable. -->
<!-- Add a new language by copying this file and changing the code/name attributes below. -->
<Language code="pl" name="Polski">
<!-- Menu nawigacji -->
<String key="NavScenarios">Scenariusze</String>
<String key="NavDepot">Zajezdnia</String>
<String key="NavSettings">Ustawienia</String>
<String key="NavControls">Sterowanie</String>
<!-- Widok ogolny -->
<String key="Sceneries">Scenerie</String>
<String key="Vehicles">Pojazdy</String>
<String key="ScenarioDescription">Opis scenariusza</String>
<String key="SceneryDescription">Opis scenerii</String>
<String key="Consist">Zestawienie</String>
<String key="Start">Uruchom</String>
<String key="ShowLatestLogTooltip">Otwórz ostatni log symulatora</String>
<!-- Lokomotywownia / tworzenie składu -->
<String key="VehicleDatabase">Baza pojazdów</String>
<String key="Search">Szukaj...</String>
<String key="LoadFromScenery">Wczytaj skład ze scenerii</String>
<String key="NewConsist">Nowy skład</String>
<String key="SelectedVehicle">Wybrany pojazd</String>
<String key="Vehicle">Pojazd</String>
<String key="Crew">z obsadą</String>
<String key="Reversed">obrócony</String>
<String key="NoVehicles">Nie znaleziono pojazdów w databases/vehicles/.</String>
<String key="ConsistEmpty">Dodaj pojazdy z bazy po lewej stronie lub wczytaj istniejący skład z pliku scenerii.</String>
<String key="TipMoveLeft">Przesuń w lewo</String>
<String key="TipMoveRight">Przesuń w prawo</String>
<String key="TipFlip">Obróć pojazd</String>
<String key="TipRemove">Usuń ze składu</String>
<String key="TipDriver">Zmień obsadę (kabina 1 / kabina 2 / pasażer / brak)</String>
<String key="Scenery">Sceneria</String>
<String key="Category">Kategoria</String>
<String key="Class">Klasa</String>
<String key="All">Wszystkie</String>
<String key="CatDiesel">Spalinowe</String>
<String key="CatElectric">Elektryczne</String>
<String key="CatEMU">EZT</String>
<String key="CatDMU">SZT</String>
<String key="CatCarriagesA">Wagony A</String>
<String key="CatCarriagesB">Wagony B</String>
<String key="CatElectricLoco">Lokomotywa elektryczna</String>
<String key="CatDieselLoco">Lokomotywa spalinowa</String>
<String key="CatSteamLoco">Parowóz</String>
<String key="CatRailbus">Szynobus</String>
<String key="CatDraisine">Drezyna</String>
<String key="CatWork">Pojazdy robocze</String>
<String key="CatPrototype">Prototyp</String>
<String key="CatTram">Tramwaj</String>
<String key="CatCar">Samochód osobowy</String>
<String key="CatBus">Autobus</String>
<String key="CatTruck">Samochód ciężarowy</String>
<String key="CatPeople">Ludzie</String>
<String key="CatAnimals">Zwierzęta</String>
<String key="CatWagons">Wagony</String>
<String key="CatOther">Inne</String>
<String key="Brakes">Hamulce</String>
<String key="Loads">Ładunki</String>
<String key="Coupling">Sprzęg</String>
<String key="SelectVehicleHint">Wybierz pojazd w składzie, aby go edytować.</String>
<String key="ComingSoon">Opcje edycji wkrótce.</String>
<String key="Split">Rozdziel</String>
<String key="TipSplit">Rozdziel zespół na pojedyncze człony</String>
<String key="AddVehicle">Dodaj</String>
<String key="TipAddVehicle">Dodaj ten pojazd do składu</String>
<String key="TipAddUnit">Dodaj cały zespół do składu</String>
<!-- Ekran ładowania -->
<String key="LoadingVehicles">Wczytywanie bazy pojazdów</String>
<String key="LoadingSceneries">Wczytywanie scenerii</String>
<String key="LoadingDone">Uruchamianie…</String>
<!-- Ustawienia -->
<String key="General">Ogólne</String>
<String key="Language">Język</String>
<String key="Fullscreen">Tryb pełnoekranowy</String>
<String key="PauseSimInactive">Wstrzymaj symulację, gdy okno nie jest aktywne</String>
<String key="PauseSimStart">Pauza po uruchomieniu</String>
<String key="CursorSensitivity">Czułość kursora:</String>
<String key="MouseHorInvert">Inwersja myszy w poziomie</String>
<String key="MouseVertInvert">Inwersja myszy w pionie</String>
<String key="Communication">Komunikacja</String>
<String key="GamepadSignalIgnore">Ignoruj sygnały z gamepada</String>
<String key="Feedback">Informacja zwrotna</String>
<String key="FeedbackOff">Wyłączona</String>
<String key="FeedbackCASHP">Caps: CA/SHP; ScrollLock: opory rozr.</String>
<String key="FeedbackCA">Caps: CA; Scrollock: SHP</String>
<String key="FeedbackLPT">LPT</String>
<String key="FeedbackPoKeys">PoKeys55</String>
<String key="FeedbackSP">Serialport (COM)</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="Graphic">Grafika</String>
<String key="RenderEngine">Silnik renderowania</String>
<String key="REFull">Pełny</String>
<String key="REOld">Stary</String>
<String key="RESimpShader">Uproszczony shaderowy</String>
<String key="RESimp">Uproszczony</String>
<String key="REOff">Wyłącz warstwę wizualną</String>
<String key="REExperimental">Eksperymentalny</String>
<String key="Resolution">Rozdzielczość:</String>
<String key="BufforResolution">Rozdzielczość bufora:</String>
<String key="MaxTexResolution">Maksymalna rozdzielczość tekstur:</String>
<String key="MaxTexCabResolution">Maksymalna rozdzielczość tekstur kabiny:</String>
<String key="TexFilteringQuality">Jakość filtrowania tekstur:</String>
<String key="Multisampling">Multisampling:</String>
<String key="RenderRange">Zasięg renderowania:</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>
<String key="ReflectionsCubeMap">Odbicia jako cubemapa</String>
<String key="RenderVBO">Renderowanie VBO</String>
<String key="RenderShadows">Renderowanie cieni</String>
<String key="ReflectionsFramerate">Odświeżanie odbić</String>
<String key="ShadowsResolution">Rozdzielczość cieni</String>
<String key="ShaderRange">Odświeżanie odbić</String>
<String key="CabShadowsRange">Zasięg źródeł cieni w kabinie</String>
<String key="ShadowDisplay">Wyświetlanie cieni</String>
<String key="CSROnlyImportant">Tylko ważne</String>
<String key="CSRLimited">Ograniczone</String>
<String key="CSRAll">Wszystkie</String>
<String key="ReflectionsDetails">Szczegółowość odbić</String>
<String key="RDTerrain">Tylko geometria terenu</String>
<String key="RDTerrainModels">Geometria terenu i modele statyczne</String>
<String key="RDTerrainModelsVehicles">Geometria terenu, modele statyczne i pojazdy</String>
<String key="ShadowsCabRange">Zasięg źródeł cieni w kabinie</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>
<String key="TrackCurvesQuality">Jakość łuków torów</String>
<String key="PhysicsAccuracyIncreased">Zwiększona dokładność fizyki</String>
<String key="BreakingPantographPossibility">Możliwość złamania pantografu</String>
<String key="PowerOverheadLineOnly">Zasilanie tylko pod siecią trakcyjna</String>
<String key="SaveSpeedometerTapes">Zapisuj taśmy z prędkościomierza</String>
<String key="SaveSimLogs">Zapisuj log z symulacji</String>
<String key="KeepPrevLogs">Zachowuj poprzednie logi</String>
<String key="DisplaySimulation">Wyświetlaj symulację</String>
<String key="CrashDamage">Uszkodzenia przy zderzeniu</String>
<String key="Sounds">Dżwięk</String>
<String key="EnableSounds">Włącz dźwięk</String>
<String key="Volume">Głośność</String>
<String key="RadiophoneVolume">Głośność radiotelefonu</String>
<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="Starter">Starter</String>
<String key="AutoCloseStarter">Zamknij starter automatycznie</String>
<String key="LargeThumbnails">Duże miniaturki</String>
<String key="AutoExpandSceneryTree">Automatycznie rozwijanie drzewka scenerii</String>
<!-- Settings actions -->
<String key="Save">Zapisz</String>
<String key="Reset">Przywróć</String>
<String key="SettingsSaved">Zapisano ustawienia</String>
</Language>