mirror of
https://github.com/rmroc451/TweaksAndThings.git
synced 2025-12-18 10:19:38 -06:00
using "area" cabeese search rather than iterating nearby cars in a catchment area. Adding MU auto filter for engine roster and including other loco fuel levels in single engine when in same consist/mu'd.
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
using Model.AI;
|
using Game.Messages;
|
||||||
|
using Model.AI;
|
||||||
|
using Network;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -61,7 +63,8 @@ namespace RMROC451.TweaksAndThings.Extensions
|
|||||||
if (car.HasHotbox && car.Oiled == 1f && cabooseRequired && foundCaboose)
|
if (car.HasHotbox && car.Oiled == 1f && cabooseRequired && foundCaboose)
|
||||||
{
|
{
|
||||||
_log.Information("AutoOiler {name}: {foundCaboose} repaired hotbox {car}", oiler.name, foundCaboose, car);
|
_log.Information("AutoOiler {name}: {foundCaboose} repaired hotbox {car}", oiler.name, foundCaboose, car);
|
||||||
car.AdjustHotboxValue();
|
Multiplayer.Broadcast($"{Hyperlink.To(oiler._originCar)}: \"{Hyperlink.To(car)} hotbox repaired!\"");
|
||||||
|
car.SendPropertyChange(PropertyChange.Control.Hotbox, false);
|
||||||
}
|
}
|
||||||
num += adjustedTimeToWalk;
|
num += adjustedTimeToWalk;
|
||||||
oiler._pendingRunDuration += adjustedTimeToWalk;
|
oiler._pendingRunDuration += adjustedTimeToWalk;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Game.Messages;
|
using Game.Messages;
|
||||||
|
using Game.State;
|
||||||
using Helpers;
|
using Helpers;
|
||||||
using Model;
|
using Model;
|
||||||
using Model.Definition;
|
using Model.Definition;
|
||||||
@@ -64,6 +65,8 @@ public static class Car_Extensions
|
|||||||
|
|
||||||
public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose;
|
public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose;
|
||||||
|
|
||||||
|
public static Car? CarCaboose(this Car car) => car.IsCaboose() ? car : null;
|
||||||
|
|
||||||
public static bool IsCabooseAndStoppedForLoadRefresh(this Car car, bool isFull) => car.IsCaboose() && car.IsStopped(30f) && !isFull;
|
public static bool IsCabooseAndStoppedForLoadRefresh(this Car car, bool isFull) => car.IsCaboose() && car.IsStopped(30f) && !isFull;
|
||||||
|
|
||||||
public static Car? CabooseInConsist(this IEnumerable<Car> input) => input.FirstOrDefault(IsCaboose);
|
public static Car? CabooseInConsist(this IEnumerable<Car> input) => input.FirstOrDefault(IsCaboose);
|
||||||
@@ -96,12 +99,14 @@ public static class Car_Extensions
|
|||||||
t.TrainClass == Timetable.TrainClass.First;
|
t.TrainClass == Timetable.TrainClass.First;
|
||||||
|
|
||||||
public static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false) =>
|
public static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false) =>
|
||||||
car.CarsNearCurrentCar(timeNeeded, decrement).FindNearestCabooseFromNearbyCars();
|
(
|
||||||
|
car.CarCaboose() ?? car.CarsNearCurrentCar(timeNeeded, decrement).FindNearestCabooseFromNearbyCars()
|
||||||
|
)?.CabooseWithSufficientCrewHours(timeNeeded, decrement);
|
||||||
|
|
||||||
public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool decrement = false)
|
public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool decrement = false)
|
||||||
{
|
{
|
||||||
Car? output = null;
|
Car? output = null;
|
||||||
if (!car.IsCaboose()) return null;
|
if (car is null || !car.IsCaboose()) return null;
|
||||||
|
|
||||||
List<LoadSlot> loadSlots = car.Definition.LoadSlots;
|
List<LoadSlot> loadSlots = car.Definition.LoadSlots;
|
||||||
for (int i = 0; i < loadSlots.Count; i++)
|
for (int i = 0; i < loadSlots.Count; i++)
|
||||||
@@ -118,29 +123,24 @@ public static class Car_Extensions
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Car FindNearestCabooseFromNearbyCars(this List<(Car car, bool crewCar, float distance)> source) =>
|
private static Car? FindNearestCabooseFromNearbyCars(this IEnumerable<(Car car, bool crewCar, float distance)> source) =>
|
||||||
source
|
source
|
||||||
.OrderBy(c => c.crewCar ? 0 : 1)
|
?.OrderBy(c => c.crewCar ? 0 : 1)
|
||||||
.ThenBy(c => c.distance)
|
?.ThenBy(c => c.distance)
|
||||||
.Select(c => c.car)
|
?.Select(c => c.car)
|
||||||
.FirstOrDefault();
|
?.FirstOrDefault();
|
||||||
|
|
||||||
private static List<(Car car, bool crewCar, float distance)> CarsNearCurrentCar(this Car car, float timeNeeded, bool decrement)
|
private static IEnumerable<(Car car, bool crewCar, float distance)> CarsNearCurrentCar(this Car car, float timeNeeded, bool decrement)
|
||||||
{
|
{
|
||||||
|
Area carArea = OpsController.Shared.ClosestArea(car);
|
||||||
|
|
||||||
var cabeese =
|
var cabeese = OpsController.Shared
|
||||||
car.EnumerateCoupled().SelectMany(consistCar =>
|
.CarsInArea(carArea)
|
||||||
{
|
.Select(c => TrainController.Shared.CarForId(c.Id))
|
||||||
Vector3 position = consistCar.GetMotionSnapshot().Position;
|
.Union(car.EnumerateCoupled())
|
||||||
Vector3 center = WorldTransformer.WorldToGame(position);
|
.Where(c => c.IsCaboose());
|
||||||
var o = TrainController.Shared
|
|
||||||
.CarIdsInRadius(center, SingletonPluginBase<TweaksAndThingsPlugin>.Shared.CabeeseSearchRadiusInMeters())
|
|
||||||
.Where(c => TrainController.Shared.CarForId(c).IsCaboose());
|
|
||||||
return o;
|
|
||||||
}).Distinct().Select(c => TrainController.Shared.CarForId(c));
|
|
||||||
|
|
||||||
|
//if (cabeese?.Any() ?? false) Log.Information($"{nameof(CarsNearCurrentCar)}[{car.DisplayName}] => {cabeese.Count()}");
|
||||||
Log.Information($"{nameof(CarsNearCurrentCar)} => {cabeese.Count()}");
|
|
||||||
|
|
||||||
List<(Car car, bool crewCar, float distance)> source =
|
List<(Car car, bool crewCar, float distance)> source =
|
||||||
cabeese.Select(c => (car: c, crewCar: c.IsCrewCar(), distance: car.Distance(c))).ToList();
|
cabeese.Select(c => (car: c, crewCar: c.IsCrewCar(), distance: car.Distance(c))).ToList();
|
||||||
@@ -158,9 +158,16 @@ public static class Car_Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsCrewCar(this Car car) =>
|
public static bool IsCrewCar(this Car car) =>
|
||||||
!string.IsNullOrEmpty(TrainController.Shared.SelectedLocomotive.trainCrewId) &&
|
!string.IsNullOrEmpty(TrainController.Shared.SelectedLocomotive?.trainCrewId) &&
|
||||||
car.trainCrewId == TrainController.Shared.SelectedLocomotive.trainCrewId;
|
car.trainCrewId == TrainController.Shared.SelectedLocomotive?.trainCrewId;
|
||||||
|
|
||||||
|
|
||||||
public static void AdjustHotboxValue(this Car car) => car.ControlProperties[PropertyChange.Control.Hotbox] = null;
|
//public static void AdjustHotboxValue(this Car car) => car.ControlProperties[PropertyChange.Control.Hotbox] = null;
|
||||||
|
public static void AdjustHotboxValue(this Car car, float hotboxValue = 0f) =>
|
||||||
|
StateManager.ApplyLocal(
|
||||||
|
new PropertyChange(
|
||||||
|
car.id, PropertyChange.KeyForControl(PropertyChange.Control.Hotbox),
|
||||||
|
new FloatPropertyValue(hotboxValue)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
31
TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs
Normal file
31
TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using Railloader;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UI;
|
||||||
|
using UI.EngineRoster;
|
||||||
|
|
||||||
|
namespace RMROC451.TweaksAndThings.Patches;
|
||||||
|
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(EngineRosterPanel))]
|
||||||
|
[HarmonyPatch(nameof(EngineRosterPanel.Populate))]
|
||||||
|
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||||
|
internal class EngineRosterPanel_Populate_Patch
|
||||||
|
{
|
||||||
|
private static bool Prefix(EngineRosterPanel __instance, ref List<RosterRowData> rows)
|
||||||
|
{
|
||||||
|
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||||
|
|
||||||
|
__instance._window.Title = __instance._window.Title.Split(':')[0].Trim();
|
||||||
|
if (!tweaksAndThings.IsEnabled()) return true;
|
||||||
|
|
||||||
|
var hiddenEntries = rows.Where(r => r.Engine.IsMuEnabled && !r.IsSelected && !r.IsFavorite).Select(r => r.Engine.id) ?? Enumerable.Empty<string>();
|
||||||
|
|
||||||
|
if (hiddenEntries.Any()) __instance._window.Title =string.Format("{0} : {1}", __instance._window.Title, $"Hidden MU Count [{hiddenEntries.Count()}]");
|
||||||
|
|
||||||
|
rows = rows.Where(r => !hiddenEntries.Contains(r.Engine.id)).ToList();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ namespace RMROC451.TweaksAndThings.Patches;
|
|||||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||||
internal class EngineRosterRow_Refresh_Patch
|
internal class EngineRosterRow_Refresh_Patch
|
||||||
{
|
{
|
||||||
|
private static Serilog.ILogger _log => Log.ForContext<EngineRosterRow_Refresh_Patch>();
|
||||||
|
|
||||||
public static void Postfix(EngineRosterRow __instance)
|
public static void Postfix(EngineRosterRow __instance)
|
||||||
{
|
{
|
||||||
TweaksAndThingsPlugin? tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
TweaksAndThingsPlugin? tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||||
@@ -35,46 +37,60 @@ internal class EngineRosterRow_Refresh_Patch
|
|||||||
if (tweaksAndThings == null ||
|
if (tweaksAndThings == null ||
|
||||||
rosterFuelColumnSettings == null ||
|
rosterFuelColumnSettings == null ||
|
||||||
!tweaksAndThings.IsEnabled() ||
|
!tweaksAndThings.IsEnabled() ||
|
||||||
rosterFuelColumnSettings.EngineRosterFuelStatusColumn == EngineRosterFuelDisplayColumn.None || (!GameInput.IsAltDown && !rosterFuelColumnSettings.EngineRosterShowsFuelStatusAlways))
|
rosterFuelColumnSettings.EngineRosterFuelStatusColumn == EngineRosterFuelDisplayColumn.None || (!GameInput.IsAltDown && !rosterFuelColumnSettings.EngineRosterShowsFuelStatusAlways) ||
|
||||||
|
__instance._engine.IsMuEnabled
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IEnumerable<Car> consist = __instance._engine.EnumerateCoupled().Where(c => c.EnableOiling);
|
Car engineOrTender = __instance._engine;
|
||||||
|
IEnumerable<Car> locos = engineOrTender.EnumerateCoupled().Where(c => c.IsLocomotive).ToList();
|
||||||
|
IEnumerable<Car> consist = engineOrTender.EnumerateCoupled().Where(c => c.EnableOiling).ToList();
|
||||||
bool cabooseRequirementFulfilled =
|
bool cabooseRequirementFulfilled =
|
||||||
!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()
|
!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()
|
||||||
|| consist.ConsistNoFreight()
|
|| consist.ConsistNoFreight()
|
||||||
|| (bool)__instance._engine.FindMyCaboose(0.0f, false);
|
|| (bool)engineOrTender.FindMyCaboose(0.0f, false);
|
||||||
float offendingPercentage = 0;
|
float offendingPercentage = 100f;
|
||||||
Car engineOrTender = __instance._engine;
|
|
||||||
List<LoadSlot> loadSlots = __instance._engine.Definition.LoadSlots;
|
|
||||||
if (!loadSlots.Any())
|
|
||||||
{
|
|
||||||
engineOrTender = __instance._engine.DetermineFuelCar()!;
|
|
||||||
loadSlots = engineOrTender != null ? engineOrTender.Definition.LoadSlots : Enumerable.Empty<LoadSlot>().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
var offender = loadSlots.OrderBy(ls => (engineOrTender.GetLoadInfo(ls.RequiredLoadIdentifier, out int slotIndex)?.Quantity ?? 0) / loadSlots[slotIndex].MaximumCapacity).FirstOrDefault().RequiredLoadIdentifier;
|
foreach (Car loco in locos)
|
||||||
|
|
||||||
for (int i = 0; i < loadSlots.Count; i++)
|
|
||||||
{
|
{
|
||||||
CarLoadInfo? loadInfo = engineOrTender.GetLoadInfo(i);
|
var investigate = loco;
|
||||||
if (loadInfo.HasValue)
|
List<LoadSlot> loadSlots = investigate.Definition.LoadSlots;
|
||||||
|
if (!loadSlots.Any())
|
||||||
{
|
{
|
||||||
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
|
investigate = investigate.DetermineFuelCar()!;
|
||||||
var fuelLevel = FuelLevel(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
|
loadSlots = investigate != null ? investigate.Definition.LoadSlots : Enumerable.Empty<LoadSlot>().ToList();
|
||||||
offendingPercentage = CalcPercentLoad(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
|
}
|
||||||
fuelInfoText += loadSlots[i].RequiredLoadIdentifier == offender ? fuelLevel + " " : string.Empty;
|
|
||||||
//fuelInfoText += TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity) + " ";
|
var offender = loadSlots.OrderBy(ls => (investigate.GetLoadInfo(ls.RequiredLoadIdentifier, out int slotIndex)?.Quantity ?? 0) / loadSlots[slotIndex].MaximumCapacity).FirstOrDefault().RequiredLoadIdentifier;
|
||||||
fuelInfoTooltip += $"{TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity)} {valueOrDefault.LoadString(CarPrototypeLibrary.instance.LoadForId(valueOrDefault.LoadId))}\n";
|
|
||||||
|
|
||||||
|
for (int i = 0; i < loadSlots.Count; i++)
|
||||||
|
{
|
||||||
|
CarLoadInfo? loadInfo = investigate.GetLoadInfo(i);
|
||||||
|
if (loadInfo.HasValue)
|
||||||
|
{
|
||||||
|
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
|
||||||
|
var fuelLevel = FuelLevel(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
|
||||||
|
var offenderCheck = CalcPercentLoad(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
|
||||||
|
|
||||||
|
if (offenderCheck < offendingPercentage)
|
||||||
|
{
|
||||||
|
offendingPercentage = offenderCheck;
|
||||||
|
fuelInfoText = loadSlots[i].RequiredLoadIdentifier == offender ? $"{fuelLevel} " : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
fuelInfoTooltip += $"{TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity)} {valueOrDefault.LoadString(CarPrototypeLibrary.instance.LoadForId(valueOrDefault.LoadId))} {(!loco.id.Equals(__instance._engine.id) ? $"{loco.DisplayName}" : "")}\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature)
|
if (cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature && consist.Any())
|
||||||
{
|
{
|
||||||
float lowestOilLevel = consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled;
|
float lowestOilLevel = consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled;
|
||||||
var oilLevel = FuelLevel(lowestOilLevel, 1);
|
var oilLevel = FuelLevel(lowestOilLevel, 1);
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup><!-- Optionally, set a few things to your liking -->
|
||||||
<!-- Optionally, set a few things to your liking -->
|
|
||||||
<!-- <MajorVersion>1</MajorVersion> -->
|
<!-- <MajorVersion>1</MajorVersion> -->
|
||||||
<!-- <MinorVersion>0</MinorVersion> -->
|
<!-- <MinorVersion>0</MinorVersion> -->
|
||||||
|
|
||||||
|
<SignAssembly>False</SignAssembly>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<Optimize>False</Optimize>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Patches\BindingsWindow_Patches.cs" />
|
<Compile Remove="Patches\BindingsWindow_Patches.cs" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Ignore Spelling: RMROC
|
// Ignore Spelling: RMROC
|
||||||
|
|
||||||
using GalaSoft.MvvmLight.Messaging;
|
using GalaSoft.MvvmLight.Messaging;
|
||||||
|
using Game;
|
||||||
using Game.Messages;
|
using Game.Messages;
|
||||||
using Game.State;
|
using Game.State;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
@@ -205,24 +206,6 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
|
|||||||
).Tooltip("Safety First", $@"On non-express timetabled consists, a caboose is required in the consist increase AE max speed > 20 in {Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Road)}/{Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Waypoint)} mode.");
|
).Tooltip("Safety First", $@"On non-express timetabled consists, a caboose is required in the consist increase AE max speed > 20 in {Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Road)}/{Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Waypoint)} mode.");
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region CabeeseSearchRadius
|
|
||||||
builder.Spacer(spacing);
|
|
||||||
builder.AddField(
|
|
||||||
"Cabeese Search Radius",
|
|
||||||
builder.AddSlider(
|
|
||||||
() => this.CabeeseSearchRadiusInMeters(),
|
|
||||||
() => $"{string.Format(Mathf.CeilToInt(this.CabeeseSearchRadiusInMeters() * 3.28084f).ToString(), "N0")}ft",
|
|
||||||
delegate (float input) {
|
|
||||||
settings = settings ?? new();
|
|
||||||
settings.CabeeseSearchRadiusFtInMeters = Mathf.CeilToInt(input);
|
|
||||||
builder.Rebuild();
|
|
||||||
},
|
|
||||||
minValue: 1f,
|
|
||||||
maxValue: Mathf.CeilToInt(5280f / 2f / 3.28084f),
|
|
||||||
wholeNumbers: true
|
|
||||||
)
|
|
||||||
).Tooltip("Cabeese Catchment Area", "How far should the cabeese hunting logic look away from the cars in the area to find a caboose?");
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UiUpdates(UIPanelBuilder builder)
|
private void UiUpdates(UIPanelBuilder builder)
|
||||||
|
|||||||
Reference in New Issue
Block a user