mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-10-19 17:03:37 +02:00
The old starter looked a vehicle up by folder, skin and model. The folder was left out here, and 39 skin names recur across folders - "red" is a Clio, an Audi and a crane - so a vehicle could get another folder's livery.
305 lines
11 KiB
C#
305 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using StarterNG.Classes;
|
|
using StarterNG.Domain.Sceneries;
|
|
using StarterNG.Domain.Vehicles;
|
|
using StarterNG.Application;
|
|
|
|
namespace StarterNG.Domain;
|
|
|
|
public static class TrainsetDisplay
|
|
{
|
|
public static bool IsEmpty(Trainset trainset) =>
|
|
trainset.Vehicles.Count == 0 ||
|
|
trainset.Vehicles.All(v => string.IsNullOrWhiteSpace(v.Name) && string.IsNullOrWhiteSpace(v.SkinFile));
|
|
|
|
public static string? CompositionText(Trainset trainset)
|
|
{
|
|
if (IsEmpty(trainset))
|
|
return null;
|
|
|
|
var sb = new StringBuilder();
|
|
string? prevKey = null;
|
|
int run = 0;
|
|
|
|
void Flush()
|
|
{
|
|
if (prevKey is null || run == 0) return;
|
|
if (sb.Length > 0) sb.Append(" + ");
|
|
sb.Append(run > 1 ? $"{prevKey}({run})" : prevKey);
|
|
prevKey = null;
|
|
run = 0;
|
|
}
|
|
|
|
for (int i = 0; i < trainset.Vehicles.Count; i++)
|
|
{
|
|
var v = trainset.Vehicles[i];
|
|
|
|
if (i == 0 && IsPowered(v))
|
|
{
|
|
sb.Append(VehicleLabel(v, head: true));
|
|
prevKey = null;
|
|
run = 0;
|
|
continue;
|
|
}
|
|
|
|
string key = VehicleLabel(v);
|
|
|
|
if (string.Equals(key, prevKey, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
run++;
|
|
}
|
|
else
|
|
{
|
|
Flush();
|
|
prevKey = key;
|
|
run = 1;
|
|
}
|
|
}
|
|
Flush();
|
|
return sb.Length > 0 ? sb.ToString() : string.Join(" + ", trainset.Vehicles.Select(v =>
|
|
!string.IsNullOrWhiteSpace(v.Name) ? v.Name! : BaseSkin(v)));
|
|
}
|
|
|
|
public static string VehicleLabel(Dynamic v, bool head = false)
|
|
{
|
|
var tex = AppServices.Current.Library.Vehicles.TextureFor(v.DataFolder, v.SkinFile, v.MmdFile);
|
|
string? cls = string.IsNullOrEmpty(tex?.ResolvedClass) ? null : tex!.ResolvedClass;
|
|
string? name = string.IsNullOrWhiteSpace(v.Name) ? null : v.Name;
|
|
return (head ? name ?? cls : cls ?? name) ?? BaseSkin(v);
|
|
}
|
|
|
|
public static bool IsPowered(Dynamic v) =>
|
|
AppServices.Current.Library.Vehicles.TextureFor(v.DataFolder, v.SkinFile, v.MmdFile)?.ResolvedCategory
|
|
is "e" or "s" or "p" or "z" or "a";
|
|
|
|
private static string BaseSkin(Dynamic v) =>
|
|
string.IsNullOrWhiteSpace(v.SkinFile) ? "?" :
|
|
System.IO.Path.GetFileNameWithoutExtension(v.SkinFile);
|
|
|
|
public static string? DefaultStartingVehicle(Trainset trainset)
|
|
{
|
|
if (trainset.Vehicles.Count == 0)
|
|
return null;
|
|
|
|
var last = trainset.Vehicles[^1];
|
|
if (last.DriverType is eDriverType.Headdriver or eDriverType.Reardriver)
|
|
return last.Name;
|
|
|
|
var staffed = trainset.Vehicles.FirstOrDefault(v =>
|
|
v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver or eDriverType.Passenger);
|
|
return staffed?.Name ?? trainset.Vehicles[0].Name;
|
|
}
|
|
|
|
public static List<(string Label, string Value)> StatsFields(
|
|
Trainset? trainset, Func<Dynamic, VehiclePhysics?> physicsFor,
|
|
Func<Dynamic, string?>? categoryOf = null,
|
|
Func<string?, int>? loadWeightKg = null)
|
|
{
|
|
var fields = new List<(string, string)>();
|
|
if (trainset is null)
|
|
return fields;
|
|
|
|
var (lengthM, massKg, loadKg) = RecalcParams(trainset, physicsFor, categoryOf, loadWeightKg, allVehicles: false);
|
|
if (massKg < 200_000)
|
|
(lengthM, massKg, loadKg) = RecalcParams(trainset, physicsFor, categoryOf, loadWeightKg, allVehicles: true);
|
|
|
|
var inv = CultureInfo.InvariantCulture;
|
|
|
|
string track = trainset.Track ?? "";
|
|
if (trainset.Velocity > 0)
|
|
track = $"{track} ({trainset.Velocity.ToString("0.#", inv)} km/h)";
|
|
|
|
fields.Add(($"{App.Loc["Mass"]} [t]:", (massKg / 1000.0).ToString("0.#", inv)));
|
|
fields.Add(($"{App.Loc["MassBrutto"]} [t]:", ((massKg + loadKg) / 1000.0).ToString("0.#", inv)));
|
|
fields.Add(($"{App.Loc["Length"]} [m]:", lengthM.ToString("0.#", inv)));
|
|
fields.Add(($"{App.Loc["VehicleCount"]}:", trainset.Vehicles.Count.ToString(inv)));
|
|
fields.Add(($"{App.Loc["Track"]}:", track));
|
|
return fields;
|
|
}
|
|
|
|
public static string FormatStats(Trainset? trainset, Func<Dynamic, VehiclePhysics?> physicsFor,
|
|
string lengthLabel, string massLabel, string vehiclesLabel, string trackLabel,
|
|
Func<Dynamic, string?>? categoryOf = null,
|
|
Func<string?, int>? loadWeightKg = null)
|
|
{
|
|
if (trainset is null)
|
|
return "";
|
|
|
|
var (lengthM, massKg, loadKg) = RecalcParams(trainset, physicsFor, categoryOf, loadWeightKg, allVehicles: false);
|
|
if (massKg < 200_000)
|
|
(lengthM, massKg, loadKg) = RecalcParams(trainset, physicsFor, categoryOf, loadWeightKg, allVehicles: true);
|
|
|
|
var inv = CultureInfo.InvariantCulture;
|
|
string track = trainset.Track ?? "";
|
|
if (trainset.Velocity > 0)
|
|
track = $"{track} ({trainset.Velocity.ToString("0.#", inv)} km/h)";
|
|
|
|
string massPart = $"{massLabel}: {(massKg / 1000.0).ToString("0.#", inv)} t";
|
|
if (loadKg > 0)
|
|
massPart += $" / {((massKg + loadKg) / 1000.0).ToString("0.#", inv)} t";
|
|
|
|
return $"{lengthLabel}: {lengthM.ToString("0.#", inv)} m · " +
|
|
$"{massPart} · " +
|
|
$"{vehiclesLabel}: {trainset.Vehicles.Count} · " +
|
|
$"{trackLabel}: {track}";
|
|
}
|
|
|
|
private static (double lengthM, double massKg, double loadKg) RecalcParams(
|
|
Trainset trainset,
|
|
Func<Dynamic, VehiclePhysics?> physicsFor,
|
|
Func<Dynamic, string?>? categoryOf,
|
|
Func<string?, int>? loadWeightKg,
|
|
bool allVehicles)
|
|
{
|
|
double lengthM = 0, massKg = 0, loadKg = 0;
|
|
foreach (var car in trainset.Vehicles)
|
|
{
|
|
var p = physicsFor(car);
|
|
if (p != null)
|
|
lengthM += p.Length;
|
|
|
|
if (!IncludeVehicleToMass(car, p, categoryOf?.Invoke(car), allVehicles))
|
|
continue;
|
|
|
|
if (p != null)
|
|
massKg += p.Mass;
|
|
|
|
if (loadWeightKg != null && car.LoadCount > 0 && !string.IsNullOrEmpty(car.LoadType))
|
|
{
|
|
if (LoadAccepted(p, car.LoadType))
|
|
loadKg += (double)car.LoadCount * loadWeightKg(car.LoadType);
|
|
else
|
|
// The scenery loads something this vehicle's .fiz does not
|
|
// accept. It is left out of the gross mass, and saying so beats
|
|
// leaving the reading quietly short.
|
|
AppServices.Current.Log.Log(
|
|
$"{car.Name}: nieobslugiwany ladunek \"{car.LoadType}\"");
|
|
}
|
|
}
|
|
return (lengthM, massKg, loadKg);
|
|
}
|
|
|
|
private static bool IncludeVehicleToMass(Dynamic car, VehiclePhysics? p, string? category, bool allVehicles)
|
|
{
|
|
if (p == null) return false;
|
|
if (allVehicles) return true;
|
|
if (category is "z" or "a") return true;
|
|
return car.DriverType is eDriverType.Passenger or eDriverType.Nobody;
|
|
}
|
|
|
|
private static bool LoadAccepted(VehiclePhysics? p, string loadType)
|
|
{
|
|
if (p == null || string.IsNullOrWhiteSpace(p.LoadAccepted))
|
|
return false;
|
|
return p.LoadAccepted.Contains(loadType, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static string? UniquifyForLaunch(Trainset trainset, Scenery scenery, string? startName,
|
|
VehicleCatalog db)
|
|
{
|
|
var used = CollectNames(scenery);
|
|
|
|
bool mu = trainset.Vehicles.Any(v =>
|
|
{
|
|
string? cat = db.TextureFor(v.DataFolder, v.SkinFile, v.MmdFile)?.ResolvedCategory;
|
|
return cat is "z" or "a";
|
|
});
|
|
|
|
Dynamic? target = null;
|
|
if (!string.IsNullOrEmpty(startName))
|
|
target = trainset.Vehicles.FirstOrDefault(v =>
|
|
string.Equals(v.Name, startName, StringComparison.OrdinalIgnoreCase));
|
|
target ??= trainset.Vehicles.FirstOrDefault(v =>
|
|
v.DriverType is eDriverType.Headdriver or eDriverType.Reardriver or eDriverType.Passenger);
|
|
target ??= trainset.Vehicles.FirstOrDefault();
|
|
|
|
if (mu)
|
|
{
|
|
foreach (var v in trainset.Vehicles)
|
|
Rename(v, used, db);
|
|
return target?.Name;
|
|
}
|
|
|
|
if (target != null)
|
|
Rename(target, used, db);
|
|
return target?.Name;
|
|
}
|
|
|
|
public static string PreferredBaseName(Dynamic v, VehicleCatalog db)
|
|
{
|
|
var tex = db.TextureFor(v.DataFolder, v.SkinFile, v.MmdFile);
|
|
if (tex != null)
|
|
{
|
|
|
|
if (!string.IsNullOrEmpty(tex.TextureMini) &&
|
|
!string.Equals(tex.TextureMini, tex.ResolvedClass, StringComparison.OrdinalIgnoreCase))
|
|
return SanitizeName(tex.TextureMini!);
|
|
if (!string.IsNullOrEmpty(tex.Skinfile))
|
|
return SanitizeName(Path.GetFileNameWithoutExtension(tex.Skinfile));
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(v.SkinFile))
|
|
return SanitizeName(Path.GetFileNameWithoutExtension(v.SkinFile));
|
|
return SanitizeName(string.IsNullOrWhiteSpace(v.Name) ? "vehicle" : v.Name!);
|
|
}
|
|
|
|
public static string EnsureUnique(string baseName, HashSet<string> used)
|
|
{
|
|
baseName = SanitizeName(baseName);
|
|
if (!used.Contains(baseName))
|
|
{
|
|
used.Add(baseName);
|
|
return baseName;
|
|
}
|
|
int n = 1;
|
|
string candidate;
|
|
do
|
|
candidate = $"{baseName}_{n++}";
|
|
while (used.Contains(candidate));
|
|
used.Add(candidate);
|
|
return candidate;
|
|
}
|
|
|
|
public static HashSet<string> CollectNames(Scenery scenery, Trainset? except = null)
|
|
{
|
|
var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (string n in scenery.LooseVehicleNames)
|
|
used.Add(n);
|
|
foreach (var d in scenery.LooseVehicles)
|
|
if (!string.IsNullOrEmpty(d.Name))
|
|
used.Add(d.Name!);
|
|
foreach (var ts in scenery.Trainsets)
|
|
{
|
|
if (ReferenceEquals(ts, except)) continue;
|
|
foreach (var v in ts.Vehicles)
|
|
if (!string.IsNullOrEmpty(v.Name))
|
|
used.Add(v.Name!);
|
|
}
|
|
return used;
|
|
}
|
|
|
|
private static string SanitizeName(string name) =>
|
|
string.IsNullOrWhiteSpace(name) ? "vehicle" : name.Replace(' ', '_');
|
|
|
|
public static (string Base, string Suffix) SplitWagonNumber(string? name)
|
|
{
|
|
string n = name ?? "";
|
|
int hash = n.LastIndexOf('#');
|
|
if (hash >= 0 && hash + 1 < n.Length && n[(hash + 1)..].All(char.IsDigit))
|
|
return (n[..hash], n[hash..]);
|
|
return (n, "");
|
|
}
|
|
|
|
private static void Rename(Dynamic v, HashSet<string> used, VehicleCatalog db)
|
|
{
|
|
var (_, number) = SplitWagonNumber(v.Name);
|
|
if (!string.IsNullOrEmpty(v.Name))
|
|
used.Remove(v.Name!);
|
|
v.Name = EnsureUnique(PreferredBaseName(v, db), used) + number;
|
|
}
|
|
}
|