Compare commits

...

14 Commits

Author SHA1 Message Date
8a21ec2e79 Support fallback to vanilla icons if files are not found. Allow configuration for context menu pagination between car/consist option preferences by player 2025-09-06 22:03:25 -05:00
3a04405dad rework context menu and add custom sprites, not registered with in game library though :( 2025-09-05 17:07:23 -05:00
ba94d80401 allows "deprecated" model metadata tag to exlude cars from being utilized for ordering, but will allow them to run out their lives on the rails until they export. Useful for when you mod in a car, and want to remove it without hunting around to find all of them and deleting them. 2025-09-05 10:51:03 -05:00
d692567d95 put everything back in context menu as one big happy family. 2025-09-05 10:50:00 -05:00
32f3e8b2db tweaks to engine roster to show who has engines selected and throttling on AE Waypoint picker to 60fps. 2025-09-02 22:19:41 -05:00
a7ae1d479c limit AutoEngineerDestinationPicker.Loop to running max of 10x per second rather than as fast as it can (caused stuttering in areas with lots of cars). 2025-09-02 10:04:58 -05:00
b562424f8b move loco post notice about WP set to host, so clients get them 2025-09-02 09:42:14 -05:00
f1b68d2827 one more of the ill fated traincontroller shared usages while on host processing. 2025-09-01 23:53:23 -05:00
b911ee6f6f version -> 2.1.7 2025-09-01 12:54:16 -05:00
cc18579507 host now can assert safety first; setting added to allow clients to override. Client does not require mod for this to be enforced! 2025-09-01 12:53:52 -05:00
1dadb04cbe round to whole number vs showing partial full mph in car inspector consist info (when caboose is nearby) 2025-09-01 12:51:29 -05:00
1032edb7ef switch roster MU hiding to be based on cutout not MU 2025-09-01 12:50:50 -05:00
c0e75c3c39 version++ 2025-08-31 18:33:06 -05:00
bd215d0dcc coroutine vs observers 2025-08-31 18:32:48 -05:00
28 changed files with 522 additions and 322 deletions

View File

@@ -2,6 +2,6 @@
<PropertyGroup>
<MajorVersion>2</MajorVersion>
<MinorVersion>1</MinorVersion>
<PatchVersion>4</PatchVersion>
<PatchVersion>8</PatchVersion>
</PropertyGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

View File

@@ -13,7 +13,7 @@ internal class AutoEngineerControlSetBase_UpdateStatusLabel_Patch
static void Postfix(AutoEngineerControlSetBase __instance)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled() || !AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies()) return;
if (!tweaksAndThings.IsEnabled() || !AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(__instance.Locomotive)) return;
string orig = __instance.statusLabel.text;
__instance.statusLabel.text = $"{orig}; <b>Safety</b>";

View File

@@ -0,0 +1,60 @@
using HarmonyLib;
using Helpers;
using Railloader;
using Serilog;
using System.Collections;
using Track;
using UI;
using UnityEngine;
using static UI.AutoEngineerDestinationPicker;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerDestinationPicker))]
[HarmonyPatch(nameof(AutoEngineerDestinationPicker.Loop))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerDestinationPicker_Loop_Patch
{
static bool Prefix(AutoEngineerDestinationPicker __instance, ref IEnumerator __result)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled()) return true;
__result = Loop(__instance);
return false;
}
private static IEnumerator Loop(AutoEngineerDestinationPicker __instance)
{
Hit valueOrDefault;
Location location;
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(1/60);
while (true)
{
Location? currentOrdersGotoLocation = __instance.GetCurrentOrdersGotoLocation();
Hit? hit = __instance.HitLocation();
if (hit.HasValue)
{
valueOrDefault = hit.GetValueOrDefault();
location = valueOrDefault.Location;
Graph.PositionRotation positionRotation = __instance._graph.GetPositionRotation(location);
__instance.destinationMarker.position = WorldTransformer.GameToWorld(positionRotation.Position);
__instance.destinationMarker.rotation = positionRotation.Rotation;
__instance.destinationMarker.gameObject.SetActive(value: true);
if (!currentOrdersGotoLocation.Equals(location) && __instance.MouseClicked)
{
break;
}
}
else
{
__instance.destinationMarker.gameObject.SetActive(value: false);
}
yield return wait;
}
Log.Debug("DestinationPicker Hit: {hit} {car} {end}", valueOrDefault.Location, valueOrDefault.CarInfo?.car, valueOrDefault.CarInfo?.end);
__instance._ordersHelper.SetWaypoint(location, valueOrDefault.CarInfo?.car.id);
__instance.StopLoop();
}
}

View File

@@ -1,96 +0,0 @@
using Game.Messages;
using HarmonyLib;
using Model.AI;
using Model.Definition;
using Railloader;
using RMROC451.TweaksAndThings.Extensions;
using Serilog;
using System;
using System.Linq;
using UI.EngineControls;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerOrdersHelper))]
[HarmonyPatch(nameof(AutoEngineerOrdersHelper.SendAutoEngineerCommand), typeof(AutoEngineerMode), typeof(bool), typeof(int), typeof(float), typeof(OrderWaypoint))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
{
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch>();
static bool Prefix(AutoEngineerMode mode, ref int maxSpeedMph)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst()) return true;
if (SafetyFirstGoverningApplies())
{
int orig = maxSpeedMph;
int limitedSpeed = Math.Min(maxSpeedMph, 20);
maxSpeedMph = mode switch
{
AutoEngineerMode.Road => limitedSpeed,
AutoEngineerMode.Waypoint => limitedSpeed,
_ => maxSpeedMph,
};
if (orig != maxSpeedMph)
{
_log.Debug($"{Enum.GetName(typeof(AutoEngineerMode), mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] {nameof(AutoEngineerOrdersExtensions.MaxSpeedMph)} limited to {limitedSpeed} from {orig}; No Caboose in Consist;");
}
}
return true;
}
internal static bool SafetyFirstGoverningApplies()
{
var _persistence = new AutoEngineerPersistence(TrainController.Shared.SelectedLocomotive.KeyValueObject);
var OrdersHelper = new AutoEngineerOrdersHelper(TrainController.Shared.SelectedLocomotive, _persistence);
if (TrainController.Shared.SelectedLocomotive.EnumerateCoupled().All(c => c.IsCaboose() || c.MotivePower())) return false;
bool cabooseReq = SingletonPluginBase<TweaksAndThingsPlugin>.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] ";
Func<bool> firstClass = () =>
{
var output = TrainController.Shared.SelectedEngineExpress();
logMessage += $"\nfirst class {output}";
return output;
};
Func<bool> FreightConsist = () =>
{
bool output = !TrainController.Shared.SelectedLocomotive.EnumerateCoupled().ConsistNoFreight();
logMessage += $"\nFreightConsist? {output}";
logMessage += " " + string.Join(" / ", TrainController.Shared.SelectedLocomotive.EnumerateCoupled().Where(c => !c.MotivePower()).Select(c => $"{c.id} {Enum.GetName(typeof(CarArchetype), c.Archetype)}"));
return output;
};
Func<bool> noCaboose = () =>
{
bool output = TrainController.Shared.SelectedLocomotive.FindMyCabooseSansLoadRequirement() == null;
logMessage += $"\ncaboose? {!output}";
return output;
};
logMessage += $"\nCaboose Required {cabooseReq}";
bool output =
cabooseReq &&
!firstClass() &&
FreightConsist() &&
noCaboose();
logMessage += $"\nGovern AE? {output}";
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Verbose))
logMessage += $"\n{Environment.StackTrace}";
_log.Debug(logMessage);
return output;
}
}

View File

@@ -1,31 +0,0 @@
using Game.Notices;
using Game.State;
using HarmonyLib;
using Model;
using Network;
using Serilog;
using UI.EngineControls;
using UnityEngine;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerOrdersHelper))]
[HarmonyPatch(nameof(AutoEngineerOrdersHelper.SetWaypoint), typeof(Track.Location), typeof(string))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerOrdersHelper_SetWaypoint_patch
{
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerOrdersHelper_SetWaypoint_patch>();
static void Postfix(AutoEngineerOrdersHelper __instance, Track.Location location, string coupleToCarId)
{
if (StateManager.IsHost)
{
_log.Debug($"start setWP");
Car selectedLoco = __instance._locomotive;
_log.Debug($"{selectedLoco?.DisplayName ?? ""} set WP");
Vector3 gamePoint = location.GetPosition();
EntityReference entityReference = new EntityReference(EntityType.Position, new Vector4(gamePoint.x, gamePoint.y, gamePoint.z, 0));
selectedLoco.PostNotice("ai-wpt-rmroc451", new Hyperlink(entityReference.URI(), $"WP SET"));
}
}
}

View File

@@ -0,0 +1,159 @@
using Game;
using Game.Messages;
using Game.Notices;
using Game.State;
using HarmonyLib;
using Model;
using Model.AI;
using Model.Definition;
using Network;
using Network.Messages;
using Railloader;
using RMROC451.TweaksAndThings.Extensions;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Track;
using UI.EngineControls;
using UI.EngineRoster;
using UnityEngine;
using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics;
using static UnityEngine.InputSystem.InputRemoting;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerPlanner))]
[HarmonyPatch(nameof(AutoEngineerPlanner.HandleCommand))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerPlanner_HandleCommand_Patch
{
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerPlanner_HandleCommand_Patch>();
private static int governedSpeed = 20;
static bool Prefix(AutoEngineerPlanner __instance, ref AutoEngineerCommand command, ref IPlayer sender)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
LocoNoticeWPSet(__instance, command, sender);
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst() || (sender.IsRemote && !tweaksAndThings.SafetyFirstClientEnforce()) || command.MaxSpeedMph <= governedSpeed) return true;
BaseLocomotive loco = __instance._locomotive;
if (SafetyFirstGoverningApplies(loco))
{
int orig = command.MaxSpeedMph;
int limitedSpeed = Math.Min(command.MaxSpeedMph, governedSpeed);
command.MaxSpeedMph = command.Mode switch
{
AutoEngineerMode.Road => limitedSpeed,
AutoEngineerMode.Waypoint => limitedSpeed,
_ => command.MaxSpeedMph,
};
string message = $"{Enum.GetName(typeof(AutoEngineerMode), command.Mode)}[{loco.DisplayName}] governed{{0}}due to Safety First rules.";
if (orig != command.MaxSpeedMph)
{
message = string.Format(message, $" from {orig} to {command.MaxSpeedMph} MPH ");
}
else
{
message = string.Format(message, " ");
}
_log.Debug(message);
Multiplayer.SendError(sender, message, AlertLevel.Info);
}
return true;
}
private static void LocoNoticeWPSet(AutoEngineerPlanner __instance, AutoEngineerCommand command, IPlayer sender)
{
OrderWaypoint? wp =
string.IsNullOrEmpty(command.WaypointLocationString) ?
null :
new OrderWaypoint?(new OrderWaypoint(command.WaypointLocationString, command.WaypointCoupleToCarId));
if (
wp.HasValue &&
!string.IsNullOrEmpty(wp.Value.LocationString) &&
!__instance._orders.Waypoint.Equals(wp)
)
{
_log.Debug($"start setWP");
Car selectedLoco = __instance._locomotive;
_log.Debug($"{selectedLoco?.DisplayName ?? ""} set WP");
if (LocationPositionFromString(wp.Value, out Vector3 gamePoint))
{
EntityReference entityReference = new EntityReference(EntityType.Position, new Vector4(gamePoint.x, gamePoint.y, gamePoint.z, 0));
selectedLoco.PostNotice("ai-wpt-rmroc451", new Hyperlink(entityReference.URI(), $"WP SET [{sender.Name}]"));
}
}
}
internal static bool LocationPositionFromString(OrderWaypoint waypoint, out Vector3 position)
{
Location location;
position = default;
try
{
location = Graph.Shared.ResolveLocationString(waypoint.LocationString);
position = location.GetPosition();
return true;
}
catch (Exception exception)
{
Log.Error(exception, "Couldn't get location from waypoint: {locStr}", waypoint.LocationString);
return false;
}
}
internal static bool SafetyFirstGoverningApplies(BaseLocomotive loco)
{
var _persistence = new AutoEngineerPersistence(loco.KeyValueObject);
var OrdersHelper = new AutoEngineerOrdersHelper(loco, _persistence);
if (loco.EnumerateCoupled().All(c => c.IsCaboose() || c.MotivePower())) return false;
bool cabooseReq = SingletonPluginBase<TweaksAndThingsPlugin>.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{loco.DisplayName}] ";
Func<bool> firstClass = () =>
{
var output = TrainController.Shared.SelectedEngineExpress();
logMessage += $"\nfirst class {output}";
return output;
};
Func<bool> FreightConsist = () =>
{
bool output = !loco.EnumerateCoupled().ConsistNoFreight();
logMessage += $"\nFreightConsist? {output}";
logMessage += " " + string.Join(" / ", loco.EnumerateCoupled().Where(c => !c.MotivePower()).Select(c => $"{c.id} {Enum.GetName(typeof(CarArchetype), c.Archetype)}"));
return output;
};
Func<bool> noCaboose = () =>
{
bool output = loco.FindMyCabooseSansLoadRequirement() == null;
logMessage += $"\ncaboose? {!output}";
return output;
};
logMessage += $"\nCaboose Required {cabooseReq}";
bool output =
cabooseReq &&
!firstClass() &&
FreightConsist() &&
noCaboose();
logMessage += $"\nGovern AE? {output}";
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Verbose))
logMessage += $"\n{Environment.StackTrace}";
_log.Debug(logMessage);
return output;
}
}

View File

@@ -1,79 +1,80 @@
using GalaSoft.MvvmLight.Messaging;
using Game.Events;
using Game.Messages;
using Game.Messages;
using Game.State;
using HarmonyLib;
using KeyValue.Runtime;
using Model;
using Model.AI;
using Model.Ops;
using Model.Ops.Timetable;
using Model.Physics;
using Network;
using Network.Messages;
using Railloader;
using Serilog;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Track;
using Track.Search;
using UI;
using UI.EngineControls;
using UnityEngine;
using UnityEngine.UI;
using static Track.Search.RouteSearch;
using Location = Track.Location;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerWaypointControls))]
[HarmonyPatch(nameof(AutoEngineerWaypointControls.ConfigureOptionsDropdown))]
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
{
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch>();
private static readonly HashSet<IDisposable> _keyChangeObservers = [];
private static Serilog.ILogger _log => Log.ForContext<LocomotiveControlsUIAdapter_UpdateCarText_Postfix>();
private static int lastSeenIntegrationSetCount = default;
private static string? lastLocoSeenCarId = default;
private static Coroutine? watchyWatchy = null;
private static HashSet<OpsCarPosition?> locoConsistDestinations = [];
private static Game.GameDateTime? timetableSaveTime = null;
static string getDictKey(Car car) => car.DisplayName;
static Car placeholder = new();
static void Postfix(AutoEngineerWaypointControls __instance, ref OptionsDropdownConfiguration __result)
static void Postfix(LocomotiveControlsUIAdapter __instance)
{
try
{
PrepLocoUsage(__instance, out BaseLocomotive selectedLoco, out int numberOfCars);
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(selectedLoco)) return;
if (lastLocoSeenCarId != null && lastLocoSeenCarId.Equals(TrainController.Shared?.SelectedLocomotive.id) && watchyWatchy != null) return;
if (watchyWatchy != null) ((MonoBehaviour)__instance).StopCoroutine(watchyWatchy);
watchyWatchy = null;
if (__instance._persistence.Orders.Mode == AutoEngineerMode.Waypoint) watchyWatchy = ((MonoBehaviour)__instance).StartCoroutine(UpdateCogCoroutine(__instance));
}
catch (Exception ex)
{
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you.");
}
}
public static IEnumerator UpdateCogCoroutine(LocomotiveControlsUIAdapter __instance)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(3f);
while (true)
{
if (__instance._persistence.Orders.Mode != AutoEngineerMode.Waypoint || ((AutoEngineerWaypointControls)__instance.aiWaypointControls).Locomotive == null) yield return wait;
PrepLocoUsage((AutoEngineerWaypointControls)__instance.aiWaypointControls, out BaseLocomotive selectedLoco, out int numberOfCars);
HashSet<OpsCarPosition?> destinations = [];
if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(__instance, selectedLoco, out destinations)) yield return wait;
timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified;
Messenger.Default.Unregister(placeholder);
Messenger.Default.Register(placeholder, delegate (TimetableDidChange evt)
{
_log.Debug("Received {evt}, rebuilding.", evt);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
Messenger.Default.Register(placeholder, delegate (CarTrainCrewChanged evt)
{
_log.Debug("Received {evt}, rebuilding {1} - {2}.", evt, selectedLoco.id, evt.CarId);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
Messenger.Default.Register(placeholder, delegate (TrainCrewsDidChange evt)
{
_log.Debug("Received {evt}, rebuilding.", evt);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
Messenger.Default.Register(placeholder, delegate (UpdateTrainCrews evt)
{
_log.Debug("Received {evt}, rebuilding.", evt);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
lastSeenIntegrationSetCount = selectedLoco.set.NumberOfCars;
IterateCarsDetectDestinations(
__instance,
__result,
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
((AutoEngineerWaypointControls)__instance.aiWaypointControls).ConfigureOptionsDropdown(),
selectedLoco,
numberOfCars,
destinations: destinations,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
@@ -81,10 +82,11 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
out AutoEngineerOrdersHelper aeoh
);
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco);
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos =
BuildJumpToOptions((AutoEngineerWaypointControls)__instance.aiWaypointControls, selectedLoco);
__result = WireUpJumpTosToSettingMenu(
__instance,
var config = WireUpJumpTosToSettingMenu(
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
selectedLoco,
rowDatas,
func,
@@ -93,23 +95,36 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
aeoh,
ref jumpTos
);
} catch(Exception ex)
{
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you.");
}
List<DropdownMenu.RowData> list = config.Rows;
Action<int> action = config.OnRowSelected;
__instance.optionsDropdown.Configure(list, action);
((Selectable)__instance.optionsDropdown).interactable = list.Count > 0;
yield return wait;
}
}
private static bool ShouldRecalc(BaseLocomotive selectedLoco)
private static bool ShouldRecalc(LocomotiveControlsUIAdapter __instance, BaseLocomotive selectedLoco, out HashSet<OpsCarPosition?> destinations)
{
bool output = false;
string locoKey = getDictKey(selectedLoco);
List<Car> consist = new List<Car>();
consist = selectedLoco.EnumerateCoupled().ToList();
HashSet<OpsCarPosition?> destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet();
destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet();
output |= !locoConsistDestinations.Equals(destinations);
output |= !(_keyChangeObservers?.Any() ?? false);
//_log.Information($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
//_log.Information($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
output |= !locoConsistDestinations.SetEquals(destinations);
//_log.Information($"{locoKey} 1-> {output}");
if (output) lastSeenIntegrationSetCount = default;
output |= lastSeenIntegrationSetCount != selectedLoco.set.NumberOfCars;
//_log.Information($"{locoKey} 2-> {output}");
//output |= __instance.optionsDropdown.scrollRect.content.childCount != (destinations.Count + timetableDestinations.Count + 1); //+1 for the default "JumpTo" entry)
//_log.Information($"{locoKey} 2.5-> {output} {__instance.optionsDropdown.scrollRect.content.childCount} {(destinations.Count)} {timetableDestinations.Count}");
output |= selectedLoco.TryGetTimetableTrain(out _) && TimetableController.Shared.CurrentDocument.Modified != timetableSaveTime;
//_log.Information($"{locoKey} 3-> {output}");
return output;
}
@@ -119,7 +134,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
OptionsDropdownConfiguration __result;
jumpTos = jumpTos?.OrderBy(c => c.sortDistance)?.ToList() ?? default;
var localJumpTos = jumpTos.ToList();
var safetyFirst = AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies() && jumpTos.Any();
var safetyFirst = AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(selectedLoco) && jumpTos.Any();
rowDatas.AddRange(jumpTos.Select(j =>
new DropdownMenu.RowData(
@@ -198,7 +213,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
? metrics.Distance
: float.MaxValue;
};
}
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add((
destinationId: destId,
@@ -211,11 +227,11 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
if (selectedLoco.TryGetTimetableTrain(out Timetable.Train t))
{
_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong}");
//_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong}");
foreach (var e in t.Entries)
{
var stp = TimetableController.Shared.GetAllStations().FirstOrDefault(ps => ps.code == e.Station);
_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong} -> {e.Station} {stp}");
//_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong} -> {e.Station} {stp}");
if (stp != null)
{
try
@@ -244,7 +260,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
? metrics.Distance
: float.MaxValue;
};
}
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add((
destinationId: destId,
@@ -253,7 +270,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
, sortdistance: sortdistance
, location: (Location?)destLoc
));
} catch (Exception ex)
}
catch (Exception ex)
{
_log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}");
}
@@ -264,52 +282,41 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return jumpTos;
}
private static void IterateCarsDetectDestinations(AutoEngineerWaypointControls __instance, OptionsDropdownConfiguration __result, BaseLocomotive selectedLoco, int numberOfCars, out List<DropdownMenu.RowData> rowDatas, out Action<int> func, out int origCount, out int maxRowOrig, out AutoEngineerOrdersHelper aeoh)
private static void IterateCarsDetectDestinations(
AutoEngineerWaypointControls __instance,
OptionsDropdownConfiguration __result,
BaseLocomotive selectedLoco,
int numberOfCars,
HashSet<OpsCarPosition?> destinations,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
out int maxRowOrig,
out AutoEngineerOrdersHelper aeoh
)
{
rowDatas = __result.Rows.ToList();
func = __result.OnRowSelected;
origCount = rowDatas.Count;
maxRowOrig = origCount - 1;
locoConsistDestinations = [];
HashSet<OpsCarPosition?> seen = [];
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
Car.LogicalEnd logicalEnd = ((selectedLoco.set.IndexOfCar(selectedLoco).GetValueOrDefault(0) >= numberOfCars / 2) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
Car.LogicalEnd end = ((logicalEnd == Car.LogicalEnd.A) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
bool stop = false;
int carIndex = selectedLoco.set.StartIndexForConnected(selectedLoco, logicalEnd, IntegrationSet.EnumerationCondition.Coupled);
Car car;
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
string locoKey = getDictKey(selectedLoco);
while (!stop && (car = selectedLoco.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null)
{
AddObserversToCar(__instance, car);
var ocp = GetCarDestinationIdentifier(car);
_log.Debug($"{locoKey} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}");
if (ocp.HasValue)
{
seen.Add(ocp.Value);
locoConsistDestinations.Add(ocp.Value);
}
}
var dropped =
locoConsistDestinations.Except(seen).ToHashSet(); //are no longer here
locoConsistDestinations.Except(destinations).ToHashSet(); //are no longer here
_log.Debug($"{locoKey} --> [{seen.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.Select(k => k.Value.DisplayName))}");
_log.Debug($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
_log.Debug($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
_log.Debug($"{locoKey} --> [{dropped.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => k.Value.DisplayName))}");
locoConsistDestinations = locoConsistDestinations.Intersect(seen).ToHashSet(); //remove ones that are no longer here
seen.Clear();
locoConsistDestinations = destinations.ToList().ToHashSet(); //remove ones that are no longer here
}
private static void PrepLocoUsage(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars)
{
//wire up that loco
selectedLoco = __instance.Locomotive;
numberOfCars = selectedLoco.set.NumberOfCars;
_log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]");
foreach (var o in _keyChangeObservers) o.Dispose();
_keyChangeObservers.Clear();
numberOfCars = selectedLoco?.set.NumberOfCars ?? -1;
_log.Debug($"{selectedLoco?.id} --> HI BOB[{numberOfCars}]");
}
private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
@@ -323,7 +330,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return destination;
}
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive) {
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
{
bool num = _locomotive.IsStopped();
bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
@@ -346,76 +354,16 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
}
return num + 1.04f * (float)(coupledCarsCached.Count - 1);
}
}
}
private static void AddObserversToCar(AutoEngineerWaypointControls __instance, Car c)
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateOptionsDropdown))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class LocomotiveControlsUIAdapter_UpdateOptionsDropdown_Prefix
{
static bool Prefix(LocomotiveControlsUIAdapter __instance)
{
AddObserver(__instance, c, Car.KeyOpsWaybill);
AddObserver(__instance, c, Car.KeyOpsRepairDestination);
AddObserver(__instance, c, nameof(Car.trainCrewId));
foreach (Car.LogicalEnd logicalEnd in CarInspector_PopulateCarPanel_Patch.ends)
{
AddObserver(__instance, c, Car.KeyValueKeyFor(Car.EndGearStateKey.IsCoupled, c.LogicalToEnd(logicalEnd)), true);
}
}
private static void AddObserver(AutoEngineerWaypointControls __instance, Model.Car car, string key, bool clearCarCache = false)
{
_keyChangeObservers.Add(
car.KeyValueObject.Observe(
key,
delegate (Value value)
{
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
if (!(locoConsistDestinations?.Any() ?? false)) return;
bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key);
string? destId =
waybillChng ?
GetCarDestinationIdentifier(car)?.Identifier ?? null :
null;
if (waybillChng)
{
if (locoConsistDestinations.Contains(GetCarDestinationIdentifier(car)))
{
return;
}
else
{
_log.Debug($"{getDictKey(car)} OBSV {key}: destNew; {destId}; reload");
}
}
else
{
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
}
Rebuild(__instance, car, key);
},
false
)
);
}
private static void Rebuild(AutoEngineerWaypointControls __instance, Car car, string key)
{
try
{
foreach (var o in _keyChangeObservers)
{
o.Dispose();
}
var loco = __instance.Locomotive;
if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null;
_keyChangeObservers.Clear();
locoConsistDestinations = [];
TrainController.Shared.SelectedCar = loco;
}
catch (Exception ex)
{
_log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}");
}
return false;
}
}

View File

@@ -88,7 +88,7 @@ internal class CarInspector_PopulateCarPanel_Patch
int consistLength = consist.Count();
int tonnage = LocomotiveControlsHoverArea.CalculateTonnage(consist);
int lengthInMeters = UnityEngine.Mathf.CeilToInt(LocomotiveControlsHoverArea.CalculateLengthInMeters(consist.ToList()) * 3.28084f);
var newSubTitle = () => string.Format("{0}, {1:N0}T, {2:N0}ft, {3:0.0} mph", consistLength.Pluralize("car"), tonnage, lengthInMeters, __instance._car.VelocityMphAbs);
var newSubTitle = () => string.Format("{0}, {1:N0}T, {2:N0}ft, {3:N0} mph", consistLength.Pluralize("car"), tonnage, lengthInMeters, __instance._car.VelocityMphAbs);
field.AddLabel(() => newSubTitle(), UIPanelBuilder.Frequency.Fast)
.Tooltip("Consist Info", "Reflects info about consist.").FlexibleWidth();

View File

@@ -1,4 +1,5 @@
using HarmonyLib;
using Game.State;
using HarmonyLib;
using Model;
using Railloader;
using RMROC451.TweaksAndThings.Enums;
@@ -8,6 +9,8 @@ using System;
using System.Linq;
using UI;
using UI.ContextMenu;
using UnityEngine;
using ContextMenu = UI.ContextMenu.ContextMenu;
namespace RMROC451.TweaksAndThings.Patches;
@@ -34,50 +37,79 @@ internal class CarPickable_HandleShowContextMenu_Patch
{
trainController.SelectedCar = ((trainController.SelectedCar == car) ? null : car);
});
if (GameInput.IsShiftDown)
bool shiftPagination = (tweaksAndThings.ShiftForPagination() && GameInput.IsShiftDown) || !tweaksAndThings.ShiftForPagination();
bool nonShiftShow = (tweaksAndThings.ShiftForPagination() && !GameInput.IsShiftDown) || !tweaksAndThings.ShiftForPagination();
if (shiftPagination && car.EnumerateCoupled().Any(c => c.SupportsBleed()))
{
if (!car.EnumerateCoupled().Any(c => !c.SupportsBleed()))
Sprite? bleedConsist = MapWindow_OnClick_Patch.LoadTexture("BleedConsist.png", "BleedConsist");
if (bleedConsist == null) bleedConsist = SpriteName.Bleed.Sprite();
shared.AddButton(ContextMenuQuadrant.Brakes, $"Bleed Consist", bleedConsist, delegate
{
shared.AddButton(ContextMenuQuadrant.Brakes, $"Bleed Consist", SpriteName.Bleed, delegate
{
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.BleedAirSystem, buttonsHaveCost);
});
}
shared.AddButton(ContextMenuQuadrant.Brakes, $"{(car.EnumerateCoupled().Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} Consist", SpriteName.Handbrake, delegate
{
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost);
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.BleedAirSystem, buttonsHaveCost);
});
}
if (nonShiftShow && car.SupportsBleed())
{
Sprite? bleedCar = MapWindow_OnClick_Patch.LoadTexture("BleedCar.png", "BleedCar");
if (bleedCar == null) bleedCar = SpriteName.Bleed.Sprite();
shared.AddButton(ContextMenuQuadrant.Brakes, "Bleed", bleedCar, car.SetBleed);
}
if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
{
shared.AddButton(ContextMenuQuadrant.General, $"Air Up Consist", SpriteName.Select, delegate
if (shiftPagination)
{
string text = car.EnumerateCoupled().Any(c => c.HandbrakeApplied()) ? "Release " : "Set ";
Sprite? consistBrakes = MapWindow_OnClick_Patch.LoadTexture($"Consist{text.Trim()}Brake.png", $"{text.Trim()}Consist");
if (consistBrakes == null) consistBrakes = SpriteName.Handbrake.Sprite();
shared.AddButton(ContextMenuQuadrant.Brakes, $"{text}Consist", consistBrakes, delegate
{
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost);
});
}
if (nonShiftShow)
{
string textCar = car.HandbrakeApplied() ? "Release " : "Set ";
Sprite? carBrakes = MapWindow_OnClick_Patch.LoadTexture($"{textCar.Trim()}Brake.png", $"{textCar.Trim()}Brake");
if (carBrakes == null) carBrakes = SpriteName.Handbrake.Sprite();
shared.AddButton(ContextMenuQuadrant.Brakes, $"{textCar}Handbrake", carBrakes, delegate
{
bool apply = !car.air.handbrakeApplied;
car.SetHandbrake(apply);
});
}
if (shiftPagination && car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
{
Sprite? connectAir = MapWindow_OnClick_Patch.LoadTexture($"ConnectAir.png", "ConnectAir");
if (connectAir == null) connectAir = SpriteName.Select.Sprite();
shared.AddButton(ContextMenuQuadrant.General, $"Air Up Consist", connectAir, delegate
{
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
});
}
}
else
if (StateManager.IsHost && shiftPagination && car.EnumerateCoupled().Any(c => c.NeedsOiling || c.HasHotbox))
{
if (car.SupportsBleed())
Sprite? oilCan = MapWindow_OnClick_Patch.LoadTexture("OilCan.png", "OilCan");
if (oilCan == null) oilCan = SpriteName.Select.Sprite();
shared.AddButton(ContextMenuQuadrant.General, $"Oil Consist", oilCan, delegate
{
shared.AddButton(ContextMenuQuadrant.Brakes, "Bleed", SpriteName.Bleed, car.SetBleed);
}
shared.AddButton(ContextMenuQuadrant.Brakes, car.air.handbrakeApplied ? "Release Handbrake" : "Apply Handbrake", SpriteName.Handbrake, delegate
{
bool apply = !car.air.handbrakeApplied;
car.SetHandbrake(apply);
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Oil, buttonsHaveCost);
});
}
shared.AddButton(ContextMenuQuadrant.General, $"Follow", SpriteName.Inspect, delegate
Sprite? follow = MapWindow_OnClick_Patch.LoadTexture($"Follow.png", "ConnectAir");
if (follow == null) follow = SpriteName.Inspect.Sprite();
shared.AddButton(ContextMenuQuadrant.General, $"Follow", follow, delegate
{
CameraSelector.shared.FollowCar(car);
});
string secondaryLine = car.Waybill.HasValue ? $"{Environment.NewLine}{car.Waybill.Value.Destination.DisplayName}" : string.Empty;
secondaryLine = secondaryLine.Length > 10 + Environment.NewLine.Length ? $"{secondaryLine.Substring(0, 7+ Environment.NewLine.Length)}..." : secondaryLine;
shared.Show($"{car.DisplayName}{secondaryLine}");
secondaryLine = secondaryLine.Length > 10 + Environment.NewLine.Length ? $"{secondaryLine.Substring(0, 7 + Environment.NewLine.Length)}..." : secondaryLine;
shared.Show($"{car.EnumerateCoupled().Count()} Cars{Environment.NewLine}{car.DisplayName}{secondaryLine}");
shared.BuildItemAngles();
shared.StartCoroutine(shared.AnimateButtonsShown());
return false;

View File

@@ -20,7 +20,7 @@ internal class EngineRosterPanel_Populate_Patch
__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>();
var hiddenEntries = rows.Where(r => r.Engine.locomotiveControl.air.IsCutOut && !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()}]");

View File

@@ -15,6 +15,7 @@ using UI.EngineRoster;
using UI.Tooltips;
using UnityEngine;
using Game.State;
using Game;
namespace RMROC451.TweaksAndThings.Patches;
@@ -34,6 +35,8 @@ internal class EngineRosterRow_Refresh_Patch
string fuelInfoText = string.Empty;
string fuelInfoTooltip = string.Empty;
TweakyTweakTweakers(__instance);
if (tweaksAndThings == null ||
rosterFuelColumnSettings == null ||
!tweaksAndThings.IsEnabled() ||
@@ -52,7 +55,7 @@ internal class EngineRosterRow_Refresh_Patch
bool cabooseRequirementFulfilled =
!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()
|| consist.ConsistNoFreight()
|| (bool)engineOrTender.FindMyCabooseSansLoadRequirement();
|| (bool)engineOrTender.FindMyCabooseSansLoadRequirement();
float offendingPercentage = 100f;
foreach (Car loco in locos)
@@ -131,13 +134,54 @@ internal class EngineRosterRow_Refresh_Patch
default:
break;
}
} catch (Exception ex)
}
catch (Exception ex)
{
rosterFuelColumnSettings.EngineRosterFuelStatusColumn = EngineRosterFuelDisplayColumn.None;
Log.Error(ex, "Error Detecting fuel status for engine roster");
}
}
private static void TweakyTweakTweakers(EngineRosterRow __instance)
{
var helperData = EngineTextHelper(__instance._engine);
if (helperData.HasValue)
{
__instance.nameLabel.text = helperData.Value.nameLabel;
__instance.nameTooltip.tooltipText += helperData.Value.nameTooltip;
}
__instance.crewLabel.text = string.Empty;
for (int i = __instance._crewComponents.Count - 1; i >= 0; i--)
{
string str = __instance._crewComponents[i];
if ((new[] { "MU", "AE" }).Contains(str))
str = $"<sup>{str} </sup>";
__instance.crewLabel.text = $"{str}{__instance.crewLabel.text}";
}
}
internal static (string nameLabel, string nameTooltip, int selectedCount)? EngineTextHelper(Car loco, bool mapIcon = false)
{
(string nameLabel, string nameTooltip, int selectedCount)? output = null;
int selectedCount = 0;
Dictionary<PlayerId, IPlayer> dictionary = StateManager.Shared.PlayersManager.AllPlayers.ToDictionary((IPlayer p) => p.PlayerId, (IPlayer p) => p);
List<string> usersSelected = new();
foreach (var kvp in dictionary)
{
if (new PlayerProperties(PlayerPropertiesManager.Shared._object[kvp.Key.ToString()]).SelectedCarId == loco.id)
{
usersSelected.Add(kvp.Value.Name);
selectedCount++;
}
}
if (selectedCount > 0 && dictionary.Count > 1)
output = ($"{(mapIcon && loco is BaseLocomotive ? loco.Ident.RoadNumber : loco.DisplayName)}<sub>{selectedCount}</sub>", $"{Environment.NewLine}Selected by: {string.Join(", ", usersSelected)}", selectedCount);
return output;
}
private static void SetLabelAndTooltip(ref TMP_Text label, ref UITooltipProvider tooltip, string fuelInfoText, string fuelInfoTooltip)
{
label.text = $" {fuelInfoText} {label.text}";

View File

@@ -34,6 +34,11 @@ internal class MapWindow_OnClick_Patch
public static Sprite? LoadTexture(string fileName, string name)
{
string path = Path.Combine(SingletonPluginBase<TweaksAndThingsPlugin>.Shared.ModDirectory, fileName);
if (!File.Exists(path))
{
_log.Debug($"Unable to find {name} icon at {path}!");
return null;
}
Texture2D texture2D = new Texture2D(128, 128, TextureFormat.DXT5, mipChain: false);
texture2D.name = name;
texture2D.wrapMode = TextureWrapMode.Clamp;

View File

@@ -0,0 +1,42 @@
using HarmonyLib;
using Helpers;
using Model.Database;
using Model.Definition;
using Model.Definition.Data;
using Model.Ops;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(PrefabStoreExtensions))]
[HarmonyPatch(nameof(PrefabStoreExtensions.Random))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal static class PrefabStoreExtensions_Random_Patch
{
public static bool Prefix(IPrefabStore prefabStore, CarTypeFilter carTypeFilter, IndustryContext.CarSizePreference sizePreference, Random rnd, ref TypedContainerItem<CarDefinition> __result)
{
List<TypedContainerItem<CarDefinition>> list =
(from p in prefabStore.AllCarDefinitionInfos.ToList().FindAll((TypedContainerItem<CarDefinition> p) =>
carTypeFilter.Matches(p.Definition.CarType) && !p.Metadata.Tags.Any(t => t.Equals("deprecated")))
orderby p.Definition.WeightEmpty
select p).ToList();
if (list.Count == 0)
{
Log.Error($"Couldn't find car for condition: {carTypeFilter}");
__result = null;
}
__result = list.RandomElementUsingNormalDistribution(sizePreference switch
{
IndustryContext.CarSizePreference.Small => 0.2f,
IndustryContext.CarSizePreference.Medium => 0.4f,
IndustryContext.CarSizePreference.Large => 0.6f,
IndustryContext.CarSizePreference.ExtraLarge => 0.8f,
_ => 0.5f,
}, rnd);
return false;
}
}

View File

@@ -55,4 +55,8 @@
<ItemGroup>
<Publicize Include="Assembly-CSharp" />
</ItemGroup>
<ItemGroup>
<None Include="Images\**" CopyToOutputDirectory="Always"/>
</ItemGroup>
</Project>

View File

@@ -28,9 +28,11 @@ public class Settings
bool cabooseRequiredForLocoTagOilIndication,
bool servicingFundPenalty,
bool safetyFirst,
bool safetyFirstClientEnforce,
CrewHourLoadMethod loadCrewHoursMethod,
float cabeeseSearchRadiusFtInMeters,
bool trainBrakeDisplayShowsColorsInCalloutMode
bool trainBrakeDisplayShowsColorsInCalloutMode,
bool shiftPaginationOnContextMenu
)
{
WebhookSettingsList = webhookSettingsList;
@@ -42,9 +44,11 @@ public class Settings
CabooseRequiredForLocoTagOilIndication = cabooseRequiredForLocoTagOilIndication;
ServicingFundPenalty = servicingFundPenalty;
SafetyFirst = safetyFirst;
SafetyFirstClientEnforce = safetyFirstClientEnforce;
LoadCrewHoursMethod = loadCrewHoursMethod;
CabeeseSearchRadiusFtInMeters = cabeeseSearchRadiusFtInMeters;
TrainBrakeDisplayShowsColorsInCalloutMode = trainBrakeDisplayShowsColorsInCalloutMode;
ShiftPaginationOnContextMenu = shiftPaginationOnContextMenu;
}
public readonly UIState<string> _selectedTabState = new UIState<string>(null);
@@ -57,9 +61,11 @@ public class Settings
public bool CabooseRequiredForLocoTagOilIndication;
public bool ServicingFundPenalty;
public bool SafetyFirst;
public bool SafetyFirstClientEnforce;
public CrewHourLoadMethod LoadCrewHoursMethod;
public float CabeeseSearchRadiusFtInMeters;
public bool TrainBrakeDisplayShowsColorsInCalloutMode;
public bool ShiftPaginationOnContextMenu;
internal void AddAnotherRow()
{
@@ -136,9 +142,13 @@ public static class SettingsExtensions
input?.settings?.ServicingFundPenalty ?? false;
public static bool SafetyFirst(this TweaksAndThingsPlugin input) =>
input?.settings?.SafetyFirst ?? false;
public static bool SafetyFirstClientEnforce(this TweaksAndThingsPlugin input) =>
input?.settings?.SafetyFirstClientEnforce ?? true;
public static bool DayLoadCrewHours(this TweaksAndThingsPlugin input) =>
(input?.settings?.LoadCrewHoursMethod ?? CrewHourLoadMethod.Tracks) == CrewHourLoadMethod.Daily;
public static bool TrainBrakeDisplayShowsColorsInCalloutMode(this TweaksAndThingsPlugin input) =>
input?.settings?.TrainBrakeDisplayShowsColorsInCalloutMode ?? false;
public static bool ShiftForPagination(this TweaksAndThingsPlugin input) =>
input?.settings?.ShiftPaginationOnContextMenu ?? false;
}

View File

@@ -40,7 +40,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
static TweaksAndThingsPlugin()
{
Log.Debug("Hello! Static Constructor was called!");
}
public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self)
@@ -58,7 +57,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
public override void OnEnable()
{
logger.Debug("OnEnable() was called!");
var harmony = new Harmony(modDefinition.Id);
harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty));
}
@@ -72,13 +70,10 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
public void Update()
{
logger.Verbose("UPDATE()");
}
public void ModTabDidOpen(UIPanelBuilder builder)
{
logger.Debug("Daytime!");
if (settings == null) settings = new();
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new();
@@ -208,6 +203,23 @@ 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.");
#endregion
#region SafetyFirstClient
if (settings?.SafetyFirst ?? false)
{
builder.Spacer(spacing);
builder.AddFieldToggle(
"Safety First! (Enforce Client Speed Restrictions)",
() => settings?.SafetyFirstClientEnforce ?? false,
delegate (bool enabled)
{
if (settings == null) settings = new();
settings.SafetyFirstClientEnforce = enabled;
builder.Rebuild();
}
).Tooltip("Safety First! (Enforce Client Speed Restrictions)", $@"Enforce cabeese dominance on clients; uncheck to allow clients to override the 20mph restriction.");
}
#endregion
}
private void UiUpdates(UIPanelBuilder builder)
@@ -250,6 +262,18 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
}
).Tooltip("Train Brake Color Mode", $@"When enabled/checked and car tag callout mode is enabled (showing car tags hovering over them), the train brake display of the selected locomotive will change the cars/engines to their destination area's color to help you visualize sets of cars at a glance.");
builder.Spacer(spacing);
builder.AddFieldToggle(
"Context Menu Shift Modifier",
() => this.ShiftForPagination(),
delegate (bool enabled)
{
if (settings == null) settings = new();
settings.ShiftPaginationOnContextMenu = enabled;
builder.Rebuild();
}
).Tooltip("Context Menu Shift Modifier", $@"When enabled/checked, utilizing `SHIFT` while initiating the context menu of a car will show consist level options, and without shows only car level. If this is unchecked, all car/consist options show up in a happy goulash!");
builder.Spacer(spacing);
EngineRosterShowsFuelStatusUISection(builder);
}
@@ -347,7 +371,6 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
public void ModTabDidClose()
{
logger.Debug("Nighttime...");
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
}
}