Adds end-gear couple/air/cut on the vanilla pie, plus a consist wheel for set-lead, brakes, bleed, air, and idle without opening extra windows.
169 lines
5 KiB
C#
169 lines
5 KiB
C#
using System.Collections.Generic;
|
|
using Game.Messages;
|
|
using HarmonyLib;
|
|
using Model;
|
|
using Model.AI;
|
|
using Track.Search;
|
|
using UnityEngine;
|
|
|
|
namespace S3.Modules.QuickActions;
|
|
|
|
static class TrainReadout
|
|
{
|
|
public readonly struct Snapshot
|
|
{
|
|
public readonly int Cars;
|
|
public readonly float LengthFt;
|
|
public readonly float Tons;
|
|
public readonly float RatedTeLbf;
|
|
public readonly float CurrentTeLbf;
|
|
public readonly float HereLbf;
|
|
public readonly float NeedLbf;
|
|
public readonly float WeightMarkLbf;
|
|
public readonly bool HasWaypoint;
|
|
public readonly bool CanMakeIt;
|
|
|
|
public Snapshot(
|
|
int cars, float lengthFt, float tons,
|
|
float ratedTeLbf, float currentTeLbf, float hereLbf, float needLbf, float weightMarkLbf,
|
|
bool hasWaypoint, bool canMakeIt)
|
|
{
|
|
Cars = cars;
|
|
LengthFt = lengthFt;
|
|
Tons = tons;
|
|
RatedTeLbf = ratedTeLbf;
|
|
CurrentTeLbf = currentTeLbf;
|
|
HereLbf = hereLbf;
|
|
NeedLbf = needLbf;
|
|
WeightMarkLbf = weightMarkLbf;
|
|
HasWaypoint = hasWaypoint;
|
|
CanMakeIt = canMakeIt;
|
|
}
|
|
|
|
public string StatsBlock()
|
|
{
|
|
return $"{LengthFt:0} ft\n{Tons:0} T";
|
|
}
|
|
|
|
public static string FormatTe(float lbf)
|
|
{
|
|
if (lbf >= 1000f) return $"{lbf / 1000f:0.0}k lbf";
|
|
return $"{lbf:0} lbf";
|
|
}
|
|
}
|
|
|
|
public static Snapshot Measure(Car origin)
|
|
{
|
|
int cars = 0;
|
|
float meters = 0f;
|
|
float pounds = 0f;
|
|
float rated = 0f;
|
|
float current = 0f;
|
|
float gravity = 0f;
|
|
BaseLocomotive? loco = null;
|
|
try
|
|
{
|
|
foreach (Car c in origin.EnumerateCoupled())
|
|
{
|
|
cars++;
|
|
meters += c.carLength;
|
|
pounds += c.Weight;
|
|
gravity += c.GravityForce;
|
|
if (c is BaseLocomotive l)
|
|
{
|
|
rated += l.RatedTractiveEffort;
|
|
current += Mathf.Abs(l.TractiveEffort);
|
|
loco ??= l;
|
|
}
|
|
}
|
|
}
|
|
catch { /* measured what we could */ }
|
|
|
|
float tons = pounds / 2000f;
|
|
float weightMark = tons * 20f;
|
|
float here = Mathf.Abs(gravity);
|
|
float need = 0f;
|
|
bool hasWp = false;
|
|
if (TryWaypointNeed(origin, loco, tons, out float routeNeed, out bool wp) && wp)
|
|
{
|
|
hasWp = true;
|
|
need = routeNeed;
|
|
}
|
|
|
|
bool can = rated + 0.5f >= Mathf.Max(here, need);
|
|
return new Snapshot(
|
|
cars, meters * 3.28084f, tons,
|
|
rated, current, here, need, weightMark,
|
|
hasWp, can);
|
|
}
|
|
|
|
static bool TryWaypointNeed(Car origin, BaseLocomotive? first, float tons, out float need, out bool hasWaypoint)
|
|
{
|
|
need = 0f;
|
|
hasWaypoint = false;
|
|
try
|
|
{
|
|
foreach (Car c in origin.EnumerateCoupled())
|
|
{
|
|
if (c is not BaseLocomotive l) continue;
|
|
var planner = l.AutoEngineerPlanner;
|
|
if (planner == null) continue;
|
|
object? raw = Traverse.Create(planner).Field("_orders").GetValue();
|
|
if (raw is not Orders orders) continue;
|
|
if (orders.Mode != AutoEngineerMode.Waypoint || !orders.Waypoint.HasValue)
|
|
continue;
|
|
hasWaypoint = true;
|
|
if (TryRouteGrade(planner, out float gradePct))
|
|
need = tons * 20f * Mathf.Max(0f, gradePct);
|
|
else
|
|
need = Mathf.Abs(first != null ? SumGravity(origin) : 0f);
|
|
return true;
|
|
}
|
|
}
|
|
catch { /* no waypoint */ }
|
|
return false;
|
|
}
|
|
|
|
static float SumGravity(Car origin)
|
|
{
|
|
float n = 0f;
|
|
try
|
|
{
|
|
foreach (Car c in origin.EnumerateCoupled())
|
|
n += c.GravityForce;
|
|
}
|
|
catch { /* */ }
|
|
return n;
|
|
}
|
|
|
|
static bool TryRouteGrade(AutoEngineerPlanner planner, out float maxAdversePct)
|
|
{
|
|
maxAdversePct = 0f;
|
|
try
|
|
{
|
|
object? raw = Traverse.Create(planner).Field("_route").GetValue();
|
|
if (raw is not List<RouteSearch.Step> route || route.Count < 2)
|
|
return false;
|
|
Vector3 prev = route[0].Position;
|
|
for (int i = 1; i < route.Count; i++)
|
|
{
|
|
Vector3 p = route[i].Position;
|
|
Vector3 d = p - prev;
|
|
float horiz = new Vector2(d.x, d.z).magnitude;
|
|
if (horiz < 0.5f)
|
|
{
|
|
prev = p;
|
|
continue;
|
|
}
|
|
float pct = d.y / horiz * 100f;
|
|
if (pct > maxAdversePct) maxAdversePct = pct;
|
|
prev = p;
|
|
}
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|