From 6dc9b77a24b4921d86bc65dfca43dcb25ee4af89 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Thu, 11 Jun 2026 23:01:49 +0200 Subject: [PATCH] Initial depot view --- StarterNG.sln.DotSettings.user | 1 + StarterNG/Assets/Langs/English.axaml | 32 ++ StarterNG/Assets/Langs/Polski.axaml | 34 +- StarterNG/Classes/Scenery.cs | 10 +- StarterNG/Classes/Trainset.cs | 26 + StarterNG/Classes/VehicleDatabase.cs | 232 +++++++++ StarterNG/MainWindow.axaml | 2 +- StarterNG/Views/Depot.axaml | 95 ++++ StarterNG/Views/Depot.axaml.cs | 713 +++++++++++++++++++++++++++ 9 files changed, 1142 insertions(+), 3 deletions(-) create mode 100644 StarterNG/Classes/VehicleDatabase.cs create mode 100644 StarterNG/Views/Depot.axaml create mode 100644 StarterNG/Views/Depot.axaml.cs diff --git a/StarterNG.sln.DotSettings.user b/StarterNG.sln.DotSettings.user index 6827f48..680ff6c 100644 --- a/StarterNG.sln.DotSettings.user +++ b/StarterNG.sln.DotSettings.user @@ -4,6 +4,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/StarterNG/Assets/Langs/English.axaml b/StarterNG/Assets/Langs/English.axaml index 2e8cce6..9b65a09 100644 --- a/StarterNG/Assets/Langs/English.axaml +++ b/StarterNG/Assets/Langs/English.axaml @@ -7,6 +7,38 @@ Consist Start + + Vehicle database + Search... + Load consist from scenery + New consist + Selected vehicle + Vehicle + with crew + reversed + No vehicles found in databases/vehicles/. + Add vehicles from the database on the left, or load an existing consist from a scenery file. + Move left + Move right + Reverse vehicle + Remove from consist + Change crew (cab 1 / cab 2 / passenger / none) + Scenery + Category + Class + All + Diesel + Electric + EMU + DMU + Carriages A + Carriages B + Brakes + Loads + Coupling + Select a vehicle in the consist to edit it. + Editing options coming soon. + General diff --git a/StarterNG/Assets/Langs/Polski.axaml b/StarterNG/Assets/Langs/Polski.axaml index dbc4dd5..290e810 100644 --- a/StarterNG/Assets/Langs/Polski.axaml +++ b/StarterNG/Assets/Langs/Polski.axaml @@ -6,7 +6,39 @@ Opis scenariusza Zestawienie Uruchom - + + + Baza pojazdów + Szukaj... + Wczytaj skład ze scenerii + Nowy skład + Wybrany pojazd + Pojazd + z obsadą + obrócony + Nie znaleziono pojazdów w databases/vehicles/. + Dodaj pojazdy z bazy po lewej stronie lub wczytaj istniejący skład z pliku scenerii. + Przesuń w lewo + Przesuń w prawo + Obróć pojazd + Usuń ze składu + Zmień obsadę (kabina 1 / kabina 2 / pasażer / brak) + Sceneria + Kategoria + Klasa + Wszystkie + Spalinowe + Elektryczne + EZT + SZT + Wagony A + Wagony B + Hamulce + Ładunki + Sprzęg + Wybierz pojazd w składzie, aby go edytować. + Opcje edycji wkrótce. + Ogólne diff --git a/StarterNG/Classes/Scenery.cs b/StarterNG/Classes/Scenery.cs index dcb042d..7e30106 100644 --- a/StarterNG/Classes/Scenery.cs +++ b/StarterNG/Classes/Scenery.cs @@ -44,7 +44,15 @@ public class Scenery foreach (string trainsetEntry in trainsetEntries) { - Trainsets.Add(new Trainset(trainsetEntry)); + try + { + Trainsets.Add(new Trainset(trainsetEntry)); + } + catch + { + // skip malformed trainset entries so one bad scenery + // doesn't break loading the rest + } } } } \ No newline at end of file diff --git a/StarterNG/Classes/Trainset.cs b/StarterNG/Classes/Trainset.cs index 12ab3dd..50b51d7 100644 --- a/StarterNG/Classes/Trainset.cs +++ b/StarterNG/Classes/Trainset.cs @@ -167,4 +167,30 @@ public class Dynamic public eDriverType DriverType; public byte couplingData; public string Tail; + + // --- depot / consist-builder extras (not part of the .scn syntax) --- + + /// Explicit miniature name (from the vehicle database). Falls back to SkinFile. + public string? MiniName; + + /// Whether the vehicle is visually reversed in the consist. + public bool Flipped; + + /// Deep copy, used when importing a trainset into the editable consist. + public Dynamic Clone() => new Dynamic + { + RangeMax = RangeMax, + RangeMin = RangeMin, + Name = Name, + DataFolder = DataFolder, + SkinFile = SkinFile, + MmdFile = MmdFile, + PathName = PathName, + Offset = Offset, + DriverType = DriverType, + couplingData = couplingData, + Tail = Tail, + MiniName = MiniName, + Flipped = Flipped + }; } \ No newline at end of file diff --git a/StarterNG/Classes/VehicleDatabase.cs b/StarterNG/Classes/VehicleDatabase.cs new file mode 100644 index 0000000..8127464 --- /dev/null +++ b/StarterNG/Classes/VehicleDatabase.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace StarterNG.Classes; + +// ===================================================================== +// Vehicle JSON database (databases/vehicles/*.json) +// Format documented in vehicleEntryDoc.md (schema_version 1). +// ===================================================================== + +/// Root object of a single vehicle JSON file. +public class VehicleEntry +{ + [JsonPropertyName("uuid")] public string? Uuid { get; set; } + [JsonPropertyName("schema_version")] public int SchemaVersion { get; set; } + [JsonPropertyName("groups")] public List Groups { get; set; } = new(); + [JsonPropertyName("textures")] public List Textures { get; set; } = new(); + [JsonPropertyName("sets")] public List Sets { get; set; } = new(); + [JsonPropertyName("unknown")] public List Unknown { get; set; } = new(); +} + +/// Merged database root (databases/vehicles/vehicles.json). +public class VehicleEntryCollection +{ + [JsonPropertyName("vehicles")] public List Vehicles { get; set; } = new(); +} + +/// Legacy header / vehicle category group. +public class VehicleGroup +{ + [JsonPropertyName("id")] public string Id { get; set; } = ""; + [JsonPropertyName("category")] public string? Category { get; set; } + [JsonPropertyName("mini")] public string? Mini { get; set; } + [JsonPropertyName("archived")] public bool Archived { get; set; } + [JsonPropertyName("implicit")] public bool Implicit { get; set; } +} + +/// A single .mat skin entry. +public class VehicleTexture +{ + [JsonPropertyName("uuid")] public string? Uuid { get; set; } + [JsonPropertyName("directory")] public string Directory { get; set; } = ""; + [JsonPropertyName("skinfile")] public string Skinfile { get; set; } = ""; + [JsonPropertyName("model")] public string? Model { get; set; } + [JsonPropertyName("group")] public string? Group { get; set; } + [JsonPropertyName("mini_ref")] public string? MiniRef { get; set; } + [JsonPropertyName("texture_mini")] public string? TextureMini { get; set; } + [JsonPropertyName("wreck")] public bool Wreck { get; set; } + [JsonPropertyName("aliases")] public List Aliases { get; set; } = new(); + [JsonPropertyName("meta")] public VehicleMeta? Meta { get; set; } + + /// Full skin path = directory + skinfile. + [JsonIgnore] public string FullPath => Directory + Skinfile; +} + +/// Alternate mapping from a malformed multi-= legacy line. +public class VehicleAlias +{ + [JsonPropertyName("model")] public string? Model { get; set; } + [JsonPropertyName("group")] public string? Group { get; set; } + [JsonPropertyName("mini_ref")] public string? MiniRef { get; set; } + [JsonPropertyName("texture_mini")] public string? TextureMini { get; set; } +} + +/// Parsed metadata from the legacy // comment section. +public class VehicleMeta +{ + [JsonPropertyName("raw")] public string? Raw { get; set; } + [JsonPropertyName("version")] public string? Version { get; set; } + [JsonPropertyName("vehicle")] public string? Vehicle { get; set; } + [JsonPropertyName("operator")] public string? Operator { get; set; } + [JsonPropertyName("depot")] public string? Depot { get; set; } + [JsonPropertyName("revision_date")] public string? RevisionDate { get; set; } + [JsonPropertyName("revision_place")] public string? RevisionPlace { get; set; } + [JsonPropertyName("texture_author")] public string? TextureAuthor { get; set; } + [JsonPropertyName("photo_author")] public string? PhotoAuthor { get; set; } + [JsonPropertyName("extra")] public List Extra { get; set; } = new(); +} + +/// +/// Aggregated, in-memory view over every vehicle JSON file found in +/// databases/vehicles/. Provides lookup helpers for the depot UI. +/// +public class VehicleDatabase +{ + public List Textures { get; } = new(); + public Dictionary GroupsById { get; } = new(); + public List Sets { get; } = new(); + + /// Texture-uuid -> automatic-consist set that contains it. + public Dictionary SetByTextureUuid { get; } = new(); + + private static readonly JsonSerializerOptions JsonOpts = new() + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + /// + /// Loads the database. When a merged vehicles.json is present it is used, + /// otherwise every per-vehicle *.json file in the directory is read. + /// Never throws: unreadable files are skipped. + /// + public void Load(string directory = "databases/vehicles/") + { + Textures.Clear(); + GroupsById.Clear(); + Sets.Clear(); + SetByTextureUuid.Clear(); + + if (!Directory.Exists(directory)) + return; + + string merged = Path.Combine(directory, "vehicles.json"); + if (File.Exists(merged)) + { + LoadMerged(merged); + } + else + { + foreach (string file in Directory.GetFiles(directory, "*.json")) + LoadEntryFile(file); + } + + BuildSetIndex(); + } + + private void LoadMerged(string file) + { + try + { + var collection = JsonSerializer.Deserialize( + File.ReadAllText(file), JsonOpts); + if (collection?.Vehicles is null) return; + foreach (var entry in collection.Vehicles) + Ingest(entry); + } + catch + { + // ignore malformed merged database + } + } + + private void LoadEntryFile(string file) + { + try + { + var entry = JsonSerializer.Deserialize( + File.ReadAllText(file), JsonOpts); + if (entry is not null) + Ingest(entry); + } + catch + { + // ignore malformed single-vehicle file + } + } + + private void Ingest(VehicleEntry entry) + { + foreach (var group in entry.Groups) + { + if (!string.IsNullOrEmpty(group.Id)) + GroupsById.TryAdd(group.Id, group); + } + + foreach (var texture in entry.Textures) + { + // skip wrecks from the standard browser list + if (texture.Wreck) continue; + Textures.Add(texture); + } + + Sets.AddRange(entry.Sets); + } + + private void BuildSetIndex() + { + foreach (var set in Sets) + { + if (set.TextureRefs is null) continue; + foreach (string uuid in set.TextureRefs) + { + if (!string.IsNullOrEmpty(uuid)) + SetByTextureUuid[uuid] = set; + } + } + } + + /// Best miniature name for a texture (texture_mini -> mini_ref -> group mini). + public string? ResolveMiniName(VehicleTexture texture) + { + if (!string.IsNullOrEmpty(texture.TextureMini)) return texture.TextureMini; + if (!string.IsNullOrEmpty(texture.MiniRef)) return texture.MiniRef; + if (texture.Group is not null && GroupsById.TryGetValue(texture.Group, out var grp)) + return grp.Mini; + return null; + } + + /// Header label for the group a texture belongs to. + public string GroupHeader(string? groupId) + { + if (!string.IsNullOrEmpty(groupId) && GroupsById.TryGetValue(groupId, out var grp)) + { + string mini = string.IsNullOrEmpty(grp.Mini) ? grp.Id : grp.Mini!; + return string.IsNullOrEmpty(grp.Category) ? mini : $"{mini} ({grp.Category})"; + } + return groupId ?? ""; + } + + /// Resolves a miniature .bmp path under textures/mini/, or null if missing. + public static string? MiniPath(string? miniName, string miniDir = "textures/mini/") + { + if (string.IsNullOrEmpty(miniName)) return null; + string path = Path.Combine(miniDir, miniName + ".bmp"); + return File.Exists(path) ? path : null; + } +} + +/// Automatic consist definition (legacy ^x grouping). +public class VehicleSet +{ + [JsonPropertyName("uuid")] public string? Uuid { get; set; } + [JsonPropertyName("mode")] public string? Mode { get; set; } + [JsonPropertyName("count")] public int Count { get; set; } + [JsonPropertyName("texture_refs")] public List TextureRefs { get; set; } = new(); +} diff --git a/StarterNG/MainWindow.axaml b/StarterNG/MainWindow.axaml index 0f52b74..e282b51 100644 --- a/StarterNG/MainWindow.axaml +++ b/StarterNG/MainWindow.axaml @@ -27,7 +27,7 @@ - + diff --git a/StarterNG/Views/Depot.axaml b/StarterNG/Views/Depot.axaml new file mode 100644 index 0000000..33c1533 --- /dev/null +++ b/StarterNG/Views/Depot.axaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +