diff --git a/Assembly.version b/Assembly.version
index 45f4823..8829fb5 100644
--- a/Assembly.version
+++ b/Assembly.version
@@ -1,7 +1,7 @@
2
- 0
- 1
+ 1
+ 7
\ No newline at end of file
diff --git a/README.md b/README.md
index 8911242..0255838 100644
--- a/README.md
+++ b/README.md
@@ -28,22 +28,191 @@ This mod requires Railloader by Zamu.
### What does this mod do?
**PLEASE READ AS THE WAY THIS MOD FUNCTIONS HAS CHANGED FROM PRIOR VERSIONS**
-1. Car Inspector : Handbrake & Air Line Helper
- * Gives two buttons that will scan the current car's connections, including the whole consist, and automatically release hand brakes, set anglecocks and connect glad hands.
-2. Car Tag Updates
- * Shows an indication of which cars in the FOV have Air System or Handbrake issues.
- * **hold SHIFT** to only show the tags in the FOV for cars with an issue!
-3. Discord Webhooks
- * Allows the console messages to post to a discord webhook. useful for those wanting to keep an eye on 24/7 hosted saves.
- * Locomotive messages grab the locomotive `Ident.RoadNumber` and check the `CTC Panel Markers` if they exist. If found, they will use the red/green color and embed the locomotive as an image in the message. If no marker is found, it defaults to blue.
- * Currently, One person per server should have this per discord webhook, otherwise you will get duplicate messages to the webhook.
- * **Multiple hooks**: Allows for many different webhooks per client to be setup, and filtered to the `Ident.ReportingMark` so you can get messages to different hooks based on what save/server you are playing on.
- * **Customizable** from the in-game Railloader settings, find `RMROC451.TweaksAndThings`
+Basically, this mod has a couple zones of focus. Caboose tweaks and other QOL things. Some of those QOL things, I added the option for the cabeese to be required & charge you a "crew salary" to utilize, or pay a monetary penalty.
+
+I was disappointed the vanilla cabeese were largely for show, didn't provide any real reason to have them except for role playing.
+
+Enter Tweaks and Things.
+
+### QOL & Cabeese Modifications:
+
+ - A: Car Level Updates:
+
+ - A1: If a car is a participant in a disconnected air hose (currently uses the copy waybill icon)
+ - A2: If a car's hand brake is set (currently uses the handbrake icon)
+ - A3 (🟢 NEW v2.0.0): Oiling Level/Hotbox Indication : pie chart or 🔥 icon
+
+ - A3a: On Rolling Stock: Indicates the car's oiling level, if oiling feature is enabled
+ - A3b: On Locomotive: Indicates the worst oiling level of a car from the connected consist (see S1)
+
+
+ - A4: Adds a "+" on cabeese tags when on a track span that reloads their crew hours load (see C)
+ - A5 (🟢 NEW v2.0.0): Car Click Hotkey Modifiers
+
+ - A5a: `alt left click` : toggle car hand brake and connect glad hands on both ends
+
+ - [ ] https://github.com/rmroc451/TweaksAndThings/issues/43 : Add setting to dump air vs connecting when used (requested by CD WEISS)
+
+
+ - A5b: `alt shift click` : toggle consists brakes and connect all glad hands
+ - A5c: `ctrl alt click` : drop all brakes and connect all glad hands in the consist
+ - A5d: `ctrl alt shift click` : same as above but will auto oil the entire consist!
+
+
+
+
+ - B: Car Context Menu Updates
+
+ - B1 (🔵 MODIFIED v2.0.0): Context Menu (right click car) Updates
+ When right clicking on a car you get some new individual car options:
+
+ - B1a: Bleed
+ Dumps all of the air in the car's air system
+ - B1b: Apply/Release Handbrake
+ Toggles the individual car's handbrake
+
+
+ - B2 (🟢 NEW v2.0.0): SHIFT Context Menu (right click car) Updates
+ When right clicking on a car and holding SHIFT you get some new consist level options:
+
+ - B2a: Bleed Consist
+ Dumps all of the air in all the consist car air systems
+ - B2b: Set/Release Consist Handbrakes
+ If handbrakes are detected on, it knocks them all off.
+ If no handbrakes are detected in the consist, it utilizes the RailRoader base game handbrake detection for when cuts of cars are spawned.
+ - B2c: Air Up Consist
+ Connects all gladhands and opens angle cocks for the consist.
+
+
+
+
+ - C: Cabeese Modifications
+
+ - C1: Adding `crew-hours` load to caboose type cars
+
+ - C1a: Gives the cabeese a depletable resource that is used to simulate the crew that resided in the caboose.
+ - C1b: When certain actions are utilized at a consist level, the depletion of this resource is used to simulate a crew's stamina for the day.
+ - C1c: When a request to adjust the crew-hours below the remaining quantity, things start costing over time, if S1b is enabled (1.5x modifier).
+ - C1d: This load/resource is utilized when S1b is enabled and for S1e integration.
+ - C1e: See S1b1/S1b2/S1b3 for what this is used for.
+
+
+ - C2 (🔵 MODIFIED v2.0.0): Proximity Detection
+ Order of detection when requesting to adjust crew-hours for an action:
+
+ - C2a: Car initiating the action is a caboose, if so use this caboose
+ - C2b: Cabeese near the car that initiated the request:
+
+ - C2a: Gather from consist of the requesting car.
+ - C2b: Gather from OpsController.Shared.ClosestArea for cars that are in the same Area.
+
+
+ - C2c: Sort the found cars by:
+
+ - C2c1: Preference for cars with same crew-id as selected locomotive (engine controls, bottom left), still order by C2c2
+ - C2c2: Distance from the requesting car, ascending (pick closest one)
+
+
+
+
+
+
+ D: Discord Webhooks
+
+ - D1: Allows the console messages to post to a discord webhook. useful for those wanting to keep an eye on 24/7 hosted saves.
+ - D2: Locomotive messages grab the locomotive `Ident.RoadNumber` and check the `CTC Panel Markers` if they exist. If found, they will use the red/green color and embed the locomotive as an image in the message. If no marker is found, it defaults to blue.
+ - D3: Currently, One person per server should have this per discord webhook, otherwise you will get duplicate messages to the webhook.
+ - D4: Multiple hooks: Allows for many different webhooks per client to be setup, and filtered to the `Ident.ReportingMark` so you can get messages to different hooks based on what save/server you are playing on.
+ - D5: Customizable from the in-game Railloader settings, find RMROC451.TweaksAndThings (see S3)
+
+
+ M: Miscellaneous
+
+ - M1 (🟢 NEW v2.0.0): Repair tracks now require cars to be waybilled, or they will not be serviced/overhauled.
They will report on the company window's location section as 'No Work Order Assigned'.
+ - M2: Engine Roster Tweaks
+
+ - M2a (🟢 NEW v2.0.0): MU'd locomotives will automatically be hidden unless they are SELECTED or FAVORITED.
+ - M2b: Fuel Display in Engine Roster
+ Will add reamaing fuel indication to Engine Roster (with details in roster row tool tip (see S2c))
+
+ - M2b1: MU'd locomotives fuel information will combine with MU primary (see S2c).
+
+
+
+
+ M3 (🟢 NEW v2.0.1): MU Adjacency Restriction Removal (USE AT OWN RISK)
+ Engines no longer are required to be adjacent to eachother to contribute to MU. They can be dispersed throughout the train.
+ The primary MU engine still acts as the main air reservoir, meaning train braking emits from that engine at this time.
+
+ M4 (🟢 NEW v2.0.0): `ctrl alt click` on a track in the map, sets the selected locomotives waypoint there when in waypoint mode.
+ If you have mapenhancer with cars displayed, if you keycombo click on a car icon, it will set the auto couple attempt.
+
+
+
+ S: Settings
+
+ - S1: Caboose Mods
+
+ - S1a: Consist Oil Indication
A caboose is required in the consist to report the lowest oil level in the consist in the locomotive's tag(see A3b) & roster entry(see M2).
+ - S1b: Caboose Use / Enable End Gear Helper Cost
+
+ - S1b1: Will cost 1 minute of AI Brake Crew & Caboose Crew time per car in the consist when the new inspector or shift context wheel buttons are utilized.
+ - S1b2: 1.5x multiplier penalty to AI Brake Crew cost if no sufficiently crewed caboose nearby (see C2).
+ - S1b3: Caboose starts reloading `Crew Hours` at any Team or Repair track (no waybill), after being stationary for 30 seconds.
+ - S1b4: AutoOiler Update: Increases limit that crew will oiling a car from 75% -> 99%, also halves the time it takes (simulating crew from lead end and caboose handling half the train).
+
- S1b5: AutoOiler Update: if S1b & S1d checked, when a caboose is present (see C2), the AutoOiler will repair hotboxes afer oiling them to 100%.
+
- S1b6: AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - 30 seconds (Safety Is Everyone's Job)
+ - S1b6: Costs from S1B1/S1B2: added to financials at end of day with an entry of AI Brake Crew.
+
+ - S1c (🟢 NEW v2.0.0): Refill / Crew Hours Load Option
Select whether you want to manually reload cabeese via:
+
+ - S1c1: track method - (team/repair/passenger stop)
+ - S1c2: daily caboose top off - refill to 8h at new day
+
+
+ - S1d: AutoAI Requirement (AI Hotbox\Oiler Requires Caboose)
A caboose is required in the consist to check for Hotboxes and perform Auto Oiler, if checked.
+ - S1e (🟢 NEW v2.0.0): Safety First!
On non-express timetabled freight consists, a caboose with some crew-hours (see C1) is required in the consist to increase AE max speed > 20 in ROAD/WAYPOINT modes.
+
+ - S2: UI
+
+ - S2a: Enable Tag Updates
+ Allows all tag updates from A to display.
+ - S2b (🟢 NEW v2.0.0): Debt Allowance
+ Will allow interchange service and repair shops to still function when you are insolvent, at a 20% overdraft fee.
+ - S2c: Engine Roster Fuel/Info
+
+ - S2c1: Enable Fuel Display in Engine Roster
+ Will add reamaing fuel indication to Engine Roster (with details in roster row tool tip).
+ Select where to display:
+
+ - S2c1a None/Off
+ - S2c1b Engine Column
+ - S2c1c Crew Column
+ - S2c1d Status Column
+
+
+ - S2c2: Always Visible?
+ Always displayed, if you want it hidden and only shown when you care to see, uncheck this, and then you can press ALT for it to populate on the next UI refresh cycle.
+
+
+
+
+
+ - S3: Webhooks
+
+ - S3a: Webhook Enabled
Will parse the console messages and transmit to a Discord webhook.
+ - S3b: Reporting Mark
Reporting mark of the company this Discord webhook applies to.
+ - S3c: Webhook Url
Url of Discord webhook to publish messages to.
+
+
+
+
+
### Does this work in Multiplayer?
Yes, these are client side mods. Host doesn't need to have them.
### What version of Railroader does this mod work with?
-2024.4.4
+2024.6 -> [Full Requirements](./TweaksAndThings/Definition.json)
*Special thanks and credit to Zamu for creating Railloader and for help with making the mod a bit more robust.*
diff --git a/TweaksAndThings/Commands/CrewUpdateCommand.cs b/TweaksAndThings/Commands/CrewUpdateCommand.cs
index a949ac9..b1bec51 100644
--- a/TweaksAndThings/Commands/CrewUpdateCommand.cs
+++ b/TweaksAndThings/Commands/CrewUpdateCommand.cs
@@ -2,6 +2,7 @@
using Game.State;
using Helpers;
using Model.Ops;
+using Model.Ops.Timetable;
using Network;
using RMROC451.TweaksAndThings.Extensions;
using RMROC451.TweaksAndThings.Patches;
@@ -46,9 +47,16 @@ public class EchoCommand : IConsoleCommand
EntityReference loco = new EntityReference(EntityType.Car, car.id);
if (comps[2] == "+") message = new Hyperlink(entityReference.URI(), string.Format(message, OpsController.Shared.ClosestArea(car)?.name ?? "???"));
- car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}");
- ExpandedConsole_Add_Patch.SendMs(null, $"{Hyperlink.To(car)} {message}");
- Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {Hyperlink.To(car)}: \"{message}\"");
+ string hlt = Hyperlink.To(car);
+ hlt = car.TryGetTimetableTrain(out Timetable.Train t) ? hlt.Replace(car.DisplayName, t.DisplayStringLong) : hlt;
+
+ if (StateManager.IsHost)
+ {
+ car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}");
+
+ ExpandedConsole_Add_Patch.SendMs(null, $"{StateManager.Shared._playersManager.LocalPlayer} {hlt} {message}");
+ }
+ if (!StateManager.IsHost) Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {hlt}: \"{message}\"");
return string.Empty;
}
diff --git a/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs b/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs
index a71600c..e229595 100644
--- a/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs
+++ b/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs
@@ -19,7 +19,7 @@ namespace RMROC451.TweaksAndThings.Extensions
public static IEnumerator MrocAutoOilerLoop(this AutoOiler oiler, Serilog.ILogger _log, bool cabooseRequired)
{
int originIndex = oiler.FindOriginIndex();
- Model.Car? foundCaboose = oiler._originCar.FindMyCaboose(0.0f, false);
+ Model.Car? foundCaboose = oiler._originCar.FindMyCabooseSansLoadRequirement();
if (originIndex < 0)
{
_log.Error("Couldn't find origin car {car}", oiler._originCar);
@@ -30,7 +30,7 @@ namespace RMROC451.TweaksAndThings.Extensions
yield break;
}
oiler._reverse = originIndex > oiler._cars.Count - originIndex;
- _log.Information(
+ _log.Debug(
"AutoOiler {name} starting, rev = {reverse}, caboose required = {req}, caboose halving adjustment = {hasCaboose}, oil limit = {limit}",
oiler.name,
oiler._reverse,
@@ -41,7 +41,7 @@ namespace RMROC451.TweaksAndThings.Extensions
while (true)
{
yield return new WaitForSeconds(AutoOiler.StartDelay.CabooseHalvedFloat(foundCaboose));
- foundCaboose = oiler._originCar.FindMyCaboose(0.0f,false);
+ foundCaboose = oiler._originCar.FindMyCabooseSansLoadRequirement();
int carIndex = originIndex;
float adjustedTimeToWalk = AutoOiler.TimeToWalkCar.CabooseHalvedFloat(foundCaboose);
do
@@ -58,11 +58,11 @@ namespace RMROC451.TweaksAndThings.Extensions
num += num3;
oiler._pendingRunDuration += num3;
oiler._oiledCount++;
- _log.Information("AutoOiler {name}: oiled {car} from {orig} => {new}", oiler.name, car, origOil, car.Oiled);
+ _log.Debug("AutoOiler {name}: oiled {car} from {orig} => {new}", oiler.name, car, origOil, car.Oiled);
}
if (car.HasHotbox && car.Oiled == 1f && cabooseRequired && foundCaboose)
{
- _log.Information("AutoOiler {name}: {foundCaboose} repaired hotbox {car}", oiler.name, foundCaboose, car);
+ _log.Debug("AutoOiler {name}: {foundCaboose} repaired hotbox {car}", oiler.name, foundCaboose, car);
Multiplayer.Broadcast($"{Hyperlink.To(oiler._originCar)}: \"{Hyperlink.To(car)} hotbox repaired!\"");
car.SendPropertyChange(PropertyChange.Control.Hotbox, false);
}
@@ -80,7 +80,7 @@ namespace RMROC451.TweaksAndThings.Extensions
public static IEnumerator MrocAutoHotboxSpotterLoop(this AutoHotboxSpotter spotter, Serilog.ILogger _log, bool cabooseRequired)
{
- Func foundCaboose = () => spotter._locomotive.FindMyCaboose(0.0f, false);
+ Func foundCaboose = () => spotter._locomotive.FindMyCabooseSansLoadRequirement();
while (true)
{
if (!spotter.HasCars)
@@ -89,7 +89,7 @@ namespace RMROC451.TweaksAndThings.Extensions
continue;
}
var fc = foundCaboose();
- _log.Information("AutoHotboxSpotter {name}: Hotbox Spotter Running, Found Caboose => {hasCaboose}; Has Cars {hasCars}; Requires Caboose {requiresCaboose}",
+ _log.Debug("AutoHotboxSpotter {name}: Hotbox Spotter Running, Found Caboose => {hasCaboose}; Has Cars {hasCars}; Requires Caboose {requiresCaboose}",
spotter.name, fc, spotter.HasCars, cabooseRequired);
if (CabooseRequirementChecker(string.Format("{0} {1}", spotter.GetType().Name, spotter.name), cabooseRequired, fc, _log))
{
@@ -104,7 +104,7 @@ namespace RMROC451.TweaksAndThings.Extensions
{
var numOrig = num;
num = Random.Range(15, 30);
- _log.Information("AutoHotboxSpotter {name}: Next check went from num(60,300) => {numOrig}; to num(15,30) => {hasCaboose}; Requires Caboose {requiresCaboose}", spotter.name, numOrig, num, fc, cabooseRequired);
+ _log.Debug("AutoHotboxSpotter {name}: Next check went from num(60,300) => {numOrig}; to num(15,30) => {hasCaboose}; Requires Caboose {requiresCaboose}", spotter.name, numOrig, num, fc, cabooseRequired);
}
yield return new WaitForSeconds(num);
spotter.CheckForHotbox();
diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs
index 18f62c6..05aa655 100644
--- a/TweaksAndThings/Extensions/Car_Extensions.cs
+++ b/TweaksAndThings/Extensions/Car_Extensions.cs
@@ -7,6 +7,7 @@ using Model.Definition.Data;
using Model.Ops;
using Model.Ops.Timetable;
using Railloader;
+using RMROC451.TweaksAndThings.Patches;
using Serilog;
using System;
using System.Collections.Generic;
@@ -98,21 +99,28 @@ public static class Car_Extensions
input.SelectedLocomotive.TryGetTimetableTrain(out Timetable.Train t) &&
t.TrainClass == Timetable.TrainClass.First;
- public static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false) =>
+ public static Car? FindMyCabooseSansLoadRequirement(this Car car) =>
+ FindMyCaboose(car, 0f, decrement: false, requireLoad: false);
+
+ public static Car? FindMyCabooseWithLoadRequirement(this Car car, float timeNeeded, bool decrement) =>
+ FindMyCaboose(car, timeNeeded, decrement, requireLoad: true);
+
+ private static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false, bool requireLoad = true) =>
(
car.CarCaboose() ?? car.CarsNearCurrentCar(timeNeeded, decrement).FindNearestCabooseFromNearbyCars()
- )?.CabooseWithSufficientCrewHours(timeNeeded, decrement);
+ )?.CabooseWithSufficientCrewHours(timeNeeded: timeNeeded, requireLoad:requireLoad, decrement: decrement);
- public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool decrement = false)
+ public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool requireLoad, bool decrement = false)
{
Car? output = null;
if (car is null || !car.IsCaboose()) return null;
+ if (!requireLoad) return car;
List loadSlots = car.Definition.LoadSlots;
for (int i = 0; i < loadSlots.Count; i++)
{
CarLoadInfo? loadInfo = car.GetLoadInfo(i);
- if (loadInfo.HasValue)
+ if (loadInfo.HasValue && loadInfo.Value.LoadId == OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.id)
{
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
output = valueOrDefault.Quantity >= timeNeeded ? car : null;
@@ -140,7 +148,7 @@ public static class Car_Extensions
.Union(car.EnumerateCoupled())
.Where(c => c.IsCaboose());
- //if (cabeese?.Any() ?? false) Log.Information($"{nameof(CarsNearCurrentCar)}[{car.DisplayName}] => {cabeese.Count()}");
+ if (cabeese?.Any() ?? false) Log.Debug($"{nameof(CarsNearCurrentCar)}[{car.DisplayName}] => {cabeese.Count()}");
List<(Car car, bool crewCar, float distance)> source =
cabeese.Select(c => (car: c, crewCar: c.IsCrewCar(), distance: car.Distance(c))).ToList();
diff --git a/TweaksAndThings/Patches/AutoEngineerControlSetBase_UpdateStatusLabel_Patch.cs b/TweaksAndThings/Patches/AutoEngineerControlSetBase_UpdateStatusLabel_Patch.cs
index d48abda..ef57970 100644
--- a/TweaksAndThings/Patches/AutoEngineerControlSetBase_UpdateStatusLabel_Patch.cs
+++ b/TweaksAndThings/Patches/AutoEngineerControlSetBase_UpdateStatusLabel_Patch.cs
@@ -13,7 +13,7 @@ internal class AutoEngineerControlSetBase_UpdateStatusLabel_Patch
static void Postfix(AutoEngineerControlSetBase __instance)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.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}; Safety";
diff --git a/TweaksAndThings/Patches/AutoEngineerDestinationPicker_Loop_Patch.cs b/TweaksAndThings/Patches/AutoEngineerDestinationPicker_Loop_Patch.cs
new file mode 100644
index 0000000..8b260f1
--- /dev/null
+++ b/TweaksAndThings/Patches/AutoEngineerDestinationPicker_Loop_Patch.cs
@@ -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.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();
+ }
+}
diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs
deleted file mode 100644
index 623a66f..0000000
--- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs
+++ /dev/null
@@ -1,92 +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();
-
- static bool Prefix(AutoEngineerMode mode, ref int maxSpeedMph)
- {
- TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.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.Information($"{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);
-
- bool cabooseReq = SingletonPluginBase.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
- string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] ";
- Func firstClass = () =>
- {
- var output = TrainController.Shared.SelectedEngineExpress();
- logMessage += $"\nfirst class {output}";
- return output;
- };
-
- Func 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 noCaboose = () =>
- {
- var output = TrainController.Shared.SelectedLocomotive.FindMyCaboose(0.0f, false) == null;
- logMessage += $"\ncaboose? {!output}";
- return output;
- };
-
- logMessage += $"\nCaboose Required {cabooseReq}";
-
- bool output =
- cabooseReq &&
- !firstClass() &&
- FreightConsist() &&
- noCaboose();
-
- logMessage += $"\nGovern AE? {output}";
-
- _log.Information(logMessage);
-
- return output;
- }
-}
diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs
deleted file mode 100644
index a512189..0000000
--- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Game.Notices;
-using HarmonyLib;
-using Model;
-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();
- static void Postfix(AutoEngineerOrdersHelper __instance, Track.Location location, string coupleToCarId)
- {
- _log.Information($"start setWP");
- Car selectedLoco = __instance._locomotive;
- _log.Information($"{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"));
- }
-}
diff --git a/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs
new file mode 100644
index 0000000..fa3edbc
--- /dev/null
+++ b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs
@@ -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();
+ private static int governedSpeed = 20;
+
+ static bool Prefix(AutoEngineerPlanner __instance, ref AutoEngineerCommand command, ref IPlayer sender)
+ {
+ TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.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.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
+ string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{loco.DisplayName}] ";
+ Func firstClass = () =>
+ {
+ var output = TrainController.Shared.SelectedEngineExpress();
+ logMessage += $"\nfirst class {output}";
+ return output;
+ };
+
+ Func 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 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;
+ }
+}
+
diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs
new file mode 100644
index 0000000..6737d87
--- /dev/null
+++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs
@@ -0,0 +1,369 @@
+using Game.Messages;
+using Game.State;
+using HarmonyLib;
+using Model;
+using Model.AI;
+using Model.Ops;
+using Model.Ops.Timetable;
+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(LocomotiveControlsUIAdapter))]
+[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
+[HarmonyPatchCategory("RMROC451TweaksAndThings")]
+internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
+{
+ private static Serilog.ILogger _log => Log.ForContext();
+ private static int lastSeenIntegrationSetCount = default;
+ private static string? lastLocoSeenCarId = default;
+ private static Coroutine? watchyWatchy = null;
+ private static HashSet locoConsistDestinations = [];
+ private static Game.GameDateTime? timetableSaveTime = null;
+ static string getDictKey(Car car) => car.DisplayName;
+
+ static void Postfix(LocomotiveControlsUIAdapter __instance)
+ {
+ try
+ {
+
+ 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.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 destinations = [];
+ if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(__instance, selectedLoco, out destinations)) yield return wait;
+ timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified;
+ lastSeenIntegrationSetCount = selectedLoco.set.NumberOfCars;
+
+ IterateCarsDetectDestinations(
+ (AutoEngineerWaypointControls)__instance.aiWaypointControls,
+ ((AutoEngineerWaypointControls)__instance.aiWaypointControls).ConfigureOptionsDropdown(),
+ selectedLoco,
+ numberOfCars,
+ destinations: destinations,
+ out List rowDatas,
+ out Action func,
+ out int origCount,
+ out int maxRowOrig,
+ out AutoEngineerOrdersHelper aeoh
+ );
+
+ List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos =
+ BuildJumpToOptions((AutoEngineerWaypointControls)__instance.aiWaypointControls, selectedLoco);
+
+ var config = WireUpJumpTosToSettingMenu(
+ (AutoEngineerWaypointControls)__instance.aiWaypointControls,
+ selectedLoco,
+ rowDatas,
+ func,
+ origCount,
+ maxRowOrig,
+ aeoh,
+ ref jumpTos
+ );
+
+ List list = config.Rows;
+ Action action = config.OnRowSelected;
+
+ __instance.optionsDropdown.Configure(list, action);
+ ((Selectable)__instance.optionsDropdown).interactable = list.Count > 0;
+ yield return wait;
+ }
+ }
+
+ private static bool ShouldRecalc(LocomotiveControlsUIAdapter __instance, BaseLocomotive selectedLoco, out HashSet destinations)
+ {
+ bool output = false;
+ string locoKey = getDictKey(selectedLoco);
+ List consist = new List();
+ consist = selectedLoco.EnumerateCoupled().ToList();
+ destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet();
+
+ //_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;
+ }
+
+ private static OptionsDropdownConfiguration WireUpJumpTosToSettingMenu(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco, List rowDatas, Action func, int origCount, int maxRowOrig, AutoEngineerOrdersHelper aeoh, ref List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos)
+ {
+ OptionsDropdownConfiguration __result;
+ jumpTos = jumpTos?.OrderBy(c => c.sortDistance)?.ToList() ?? default;
+ var localJumpTos = jumpTos.ToList();
+ var safetyFirst = AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(selectedLoco) && jumpTos.Any();
+
+ rowDatas.AddRange(jumpTos.Select(j =>
+ new DropdownMenu.RowData(
+ $"{j.destination} ({(j.distance.HasValue ? Units.DistanceText(j.distance.Value) : "N/A")})",
+ !safetyFirst ? null : "Disabled; Safety First!"
+ )
+ ));
+
+ __result = new OptionsDropdownConfiguration(
+ rowDatas
+ , delegate (int row)
+ {
+ _log.Debug($"{__instance.Locomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}");
+ if (row <= maxRowOrig)
+ {
+ func(row);
+ }
+ if (row > maxRowOrig && localJumpTos[row - origCount].location.HasValue)
+ {
+ if (safetyFirst)
+ {
+ Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, "Safety First, find yourself a caboose!", AlertLevel.Error);
+ return;
+ }
+ float trainMomentum = 0f;
+ Location end = localJumpTos[row - origCount].location.Value;
+ Location start = RouteStartLocation(__instance, selectedLoco);
+ HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
+ List list = new List();
+
+ var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
+ if (!Graph.Shared.FindRoute(start, end, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum))
+ {
+ RouteSearch.Metrics metrics2;
+ bool flag = Graph.Shared.FindRoute(start, end, autoEngineer, null, out metrics2);
+ Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, flag ? (getDictKey(selectedLoco) + " Train too long to navigate to waypoint.") : (getDictKey(selectedLoco) + " Unable to find a path to waypoint."), AlertLevel.Error);
+ }
+ else
+ {
+ var mw = (location: end, carId: string.Empty);
+ aeoh.SetWaypoint(mw.location, mw.carId);
+ aeoh.SetOrdersValue(maybeWaypoint: mw);
+ }
+ }
+ }
+ );
+ return __result;
+ }
+
+ private static List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco)
+ {
+ List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos = new();
+ foreach (OpsCarPosition ocp in locoConsistDestinations)
+ {
+ string destName = ocp.DisplayName;
+ string destId = ocp.Identifier;
+ float? distance = null;
+ float sortdistance = 0f;
+ if (
+ Graph.Shared.TryGetLocationFromPoint(
+ ocp.Spans?.FirstOrDefault().GetSegments().FirstOrDefault(),
+ ocp.Spans?.FirstOrDefault()?.GetCenterPoint() ?? default,
+ 200f,
+ out Location destLoc
+ )
+ )
+ {
+ float trainMomentum = 0f;
+ Location start = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.RouteStartLocation(out trainMomentum) : RouteStartLocation(__instance, selectedLoco);
+ HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
+ List list = new List();
+ var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
+ distance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum)
+ ? metrics.Distance
+ : null;
+ 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,
+ destination: $"WP> {destName}"
+ , distance: distance
+ , sortdistance: sortdistance
+ , location: (Location?)destLoc
+ ));
+ }
+
+ if (selectedLoco.TryGetTimetableTrain(out Timetable.Train t))
+ {
+ //_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}");
+ if (stp != null)
+ {
+ try
+ {
+ string destName = t.TrainType == Timetable.TrainType.Passenger ? stp.passengerStop.DisplayName : stp.DisplayName;
+ string destId = t.TrainType == Timetable.TrainType.Passenger ? stp.passengerStop.identifier : stp.code;
+ float? distance = null;
+ float sortdistance = 0f;
+ if (
+ Graph.Shared.TryGetLocationFromPoint(
+ stp.passengerStop.TrackSpans?.FirstOrDefault().GetSegments().FirstOrDefault(),
+ stp.passengerStop.TrackSpans?.FirstOrDefault()?.GetCenterPoint() ?? default,
+ 200f,
+ out Location destLoc
+ )
+ )
+ {
+ float trainMomentum = 0f;
+ Location start = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.RouteStartLocation(out trainMomentum) : RouteStartLocation(__instance, selectedLoco);
+ HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
+ List list = new List();
+ var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
+ distance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum)
+ ? metrics.Distance
+ : null;
+ 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,
+ destination: $"{t.DisplayStringLong} > {destName}"
+ , distance: distance
+ , sortdistance: sortdistance
+ , location: (Location?)destLoc
+ ));
+ }
+ catch (Exception ex)
+ {
+ _log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}");
+ }
+ }
+ }
+ }
+
+ return jumpTos;
+ }
+
+ private static void IterateCarsDetectDestinations(
+ AutoEngineerWaypointControls __instance,
+ OptionsDropdownConfiguration __result,
+ BaseLocomotive selectedLoco,
+ int numberOfCars,
+ HashSet destinations,
+ out List rowDatas,
+ out Action func,
+ out int origCount,
+ out int maxRowOrig,
+ out AutoEngineerOrdersHelper aeoh
+ )
+ {
+ rowDatas = __result.Rows.ToList();
+ func = __result.OnRowSelected;
+ origCount = rowDatas.Count;
+ maxRowOrig = origCount - 1;
+ aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
+ string locoKey = getDictKey(selectedLoco);
+
+ var dropped =
+ locoConsistDestinations.Except(destinations).ToHashSet(); //are no longer here
+
+ _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 = 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 ?? -1;
+ _log.Debug($"{selectedLoco?.id} --> HI BOB[{numberOfCars}]");
+ }
+
+ private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
+ {
+ OpsCarPosition? destination = null;
+ if (c.TryGetOverrideDestination(OverrideDestination.Repair, OpsController.Shared, out (OpsCarPosition, string)? result))
+ destination = result.Value.Item1;
+
+ if (!destination.HasValue && c.Waybill.HasValue && !c.Waybill.Value.Completed)
+ destination = c.Waybill.Value.Destination;
+ return destination;
+ }
+
+ private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
+ {
+ bool num = _locomotive.IsStopped();
+ bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
+
+ bool flag2 = flag ?? __instance.OrdersHelper.Orders.Forward;
+ if (_locomotive.EndToLogical((!flag2) ? Car.End.R : Car.End.F) == Car.LogicalEnd.A)
+ {
+ return _locomotive.EnumerateCoupled().First().WheelBoundsA;
+ }
+
+ return _locomotive.EnumerateCoupled(Car.LogicalEnd.B).First().WheelBoundsB.Flipped();
+ }
+
+ private static float CalculateTotalLength(BaseLocomotive selectedLoco)
+ {
+ List coupledCarsCached = selectedLoco.EnumerateCoupled().ToList();
+ float num = 0f;
+ foreach (Car item in coupledCarsCached)
+ {
+ num += item.carLength;
+ }
+
+ return num + 1.04f * (float)(coupledCarsCached.Count - 1);
+ }
+}
+
+[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
+[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateOptionsDropdown))]
+[HarmonyPatchCategory("RMROC451TweaksAndThings")]
+internal class LocomotiveControlsUIAdapter_UpdateOptionsDropdown_Prefix
+{
+ static bool Prefix(LocomotiveControlsUIAdapter __instance)
+ {
+ return false;
+ }
+}
diff --git a/TweaksAndThings/Patches/BaseLocomotive_FindSourceLocomotive_Patch.cs b/TweaksAndThings/Patches/BaseLocomotive_FindSourceLocomotive_Patch.cs
index bc2db78..887d234 100644
--- a/TweaksAndThings/Patches/BaseLocomotive_FindSourceLocomotive_Patch.cs
+++ b/TweaksAndThings/Patches/BaseLocomotive_FindSourceLocomotive_Patch.cs
@@ -43,3 +43,89 @@ internal class BaseLocomotive_FindSourceLocomotive_Patch
return null;
}
}
+
+//[HarmonyPatch(typeof(LocomotiveAirSystem))]
+//[HarmonyPatch(nameof(LocomotiveAirSystem._ShouldDeferToLocomotiveAir))]
+//[HarmonyPatchCategory("RMROC451TweaksAndThings")]
+//internal class LocomotiveAirSystem__ShouldDeferToLocomotiveAir_Patch
+//{
+// private static void Postfix(LocomotiveAirSystem __instance, ref LocomotiveAirSystem locomotiveAirSystem, ref bool __result)
+// {
+// TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared;
+// if (!tweaksAndThings.IsEnabled()) return;
+
+// __result = _ShouldDeferToLocomotiveAir(__instance, out locomotiveAirSystem);
+// }
+// public static bool _ShouldDeferToLocomotiveAir(LocomotiveAirSystem __instance, out LocomotiveAirSystem locomotiveAirSystem)
+// {
+// locomotiveAirSystem = null;
+// if (__instance.car.set == null)
+// {
+// return false;
+// }
+// if (!(__instance.car.air is LocomotiveAirSystem locomotiveAirSystem2) || !(__instance.car is BaseLocomotive baseLocomotive))
+// {
+// return false;
+// }
+// if (!locomotiveAirSystem2.IsCutOut || locomotiveAirSystem2.IsMuEnabled)
+// {
+// return false;
+// }
+// BaseLocomotive baseLocomotive2 = baseLocomotive.FindMuSourceLocomotive();
+// if (baseLocomotive2 == null)
+// {
+// return false;
+// }
+// if (!(baseLocomotive2.air is LocomotiveAirSystem locomotiveAirSystem3))
+// {
+// return false;
+// }
+// locomotiveAirSystem = locomotiveAirSystem3;
+// return true;
+// }
+//}
+
+//[HarmonyPatch(typeof(CarAirSystem))]
+//[HarmonyPatch(nameof(CarAirSystem.ShouldDeferToLocomotiveAir))]
+//[HarmonyPatchCategory("RMROC451TweaksAndThings")]
+//internal class CarAirSystem_ShouldDeferToLocomotiveAir_Patch
+//{
+// private static void Postfix(CarAirSystem __instance, ref LocomotiveAirSystem locomotiveAirSystem, ref bool __result)
+// {
+// TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared;
+// if (!tweaksAndThings.IsEnabled()) return;
+
+// __result = ShouldDeferToLocomotiveAir(__instance, out locomotiveAirSystem);
+// }
+
+// public static bool ShouldDeferToLocomotiveAir(CarAirSystem __instance, out LocomotiveAirSystem locomotiveAirSystem)
+// {
+// locomotiveAirSystem = null;
+// if (__instance.car.set == null)
+// {
+// return false;
+// }
+// if (__instance.car.Archetype != CarArchetype.Tender)
+// {
+// return false;
+// }
+// if (!__instance.car.TryGetAdjacentCar(__instance.car.EndToLogical(Car.End.F), out var adjacent) || !adjacent.IsLocomotive)
+// {
+// return false;
+// }
+// if (!(adjacent.air is LocomotiveAirSystem locomotiveAirSystem2))
+// {
+// return false;
+// }
+// locomotiveAirSystem = locomotiveAirSystem2;
+// if (locomotiveAirSystem.IsMuEnabled)
+// {
+// return true;
+// }
+// if (!locomotiveAirSystem.IsCutOut)
+// {
+// return true;
+// }
+// return locomotiveAirSystem.ShouldDeferToLocomotiveAir(out locomotiveAirSystem);
+// }
+//}
diff --git a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs
index ecd6ae8..235dd2f 100644
--- a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs
+++ b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs
@@ -28,7 +28,7 @@ namespace RMROC451.TweaksAndThings.Patches;
internal class CarInspector_PopulateCarPanel_Patch
{
private static ILogger _log => Log.ForContext();
- private static IEnumerable ends = Enum.GetValues(typeof(LogicalEnd)).Cast();
+ internal static IEnumerable ends = Enum.GetValues(typeof(LogicalEnd)).Cast();
///
/// If a caboose inspector is opened, it will auto set Anglecocks, gladhands and hand brakes
@@ -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();
@@ -181,7 +181,7 @@ internal class CarInspector_PopulateCarPanel_Patch
case MrocHelperType.BleedAirSystem:
consist = consist.Where(c => !c.MotivePower());
- _log.ForContext("car", car).Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
+ _log.ForContext("car", car).Debug($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
foreach (Model.Car bleed in consist)
{
StateManager.ApplyLocal(new PropertyChange(bleed.id, PropertyChange.Control.Bleed, 1));
@@ -190,7 +190,7 @@ internal class CarInspector_PopulateCarPanel_Patch
case MrocHelperType.Oil:
consist = consist.Where(c => c.NeedsOiling || c.HasHotbox);
- _log.ForContext("car", car).Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
+ _log.ForContext("car", car).Debug($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
foreach (Model.Car oil in consist)
{
StateManager.ApplyLocal(new PropertyChange(oil.id, nameof(Car.Oiled).ToLower(), new FloatPropertyValue(1)));
@@ -227,12 +227,12 @@ internal class CarInspector_PopulateCarPanel_Patch
float originalTimeCost = consist.CalculateCostForAutoEngineerEndGearSetting();
float timeCost = originalTimeCost;
float crewCost = timeCost / 3600; //hours of time deducted from caboose.
- var tsString = crewCost.FormatCrewHours(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad().description);
- Car? cabooseWithAvailCrew = car.FindMyCaboose(crewCost, buttonsHaveCost);
+ var tsString = crewCost.FormatCrewHours(OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.description);
+ Car? cabooseWithAvailCrew = car.FindMyCabooseWithLoadRequirement(crewCost, buttonsHaveCost);
if (cabooseWithAvailCrew == null) timeCost *= 1.5f;
var cabooseFoundDisplay = cabooseWithAvailCrew?.DisplayName ?? "No caboose";
- _log.ForContext("car", car).Information($"{nameof(MrocConsistHelper)} {mrocHelperType} : [VACINITY CABEESE FOUND:{cabooseWithAvailCrew?.ToString() ?? "NONE"}] => Consist Length {consist.Count()} => costs {timeCost / 60} minutes of AI Engineer time, $5 per hour = ~${Math.Ceiling((decimal)(timeCost / 3600) * 5)} (*2 if no caboose nearby)");
+ _log.ForContext("car", car).Debug($"{nameof(MrocConsistHelper)} {mrocHelperType} : [VACINITY CABEESE FOUND:{cabooseWithAvailCrew?.ToString() ?? "NONE"}] => Consist Length {consist.Count()} => costs {timeCost / 60} minutes of AI Engineer time, $5 per hour = ~${Math.Ceiling((decimal)(timeCost / 3600) * 5)} (*2 if no caboose nearby)");
Multiplayer.SendError(StateManager.Shared._playersManager.LocalPlayer, $"{(cabooseWithAvailCrew != null ? $"{cabooseWithAvailCrew.DisplayName} Hours Adjusted: ({tsString})\n" : string.Empty)}Wages: ~(${Math.Ceiling((decimal)(timeCost / 3600) * 5)})");
diff --git a/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs b/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs
index f20795a..ae1678c 100644
--- a/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs
+++ b/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs
@@ -2,6 +2,7 @@
using HarmonyLib;
using Model;
using Model.Ops;
+using Model.Physics;
using Network;
using Railloader;
using RMROC451.TweaksAndThings.Enums;
@@ -9,6 +10,7 @@ using RMROC451.TweaksAndThings.Extensions;
using RollingStock;
using Serilog;
using System;
+using System.Collections.Generic;
using System.Linq;
using UI;
using UI.Tags;
@@ -40,92 +42,179 @@ internal class CarPickable_Activate_Patch
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared;
if (!tweaksAndThings.IsEnabled()) return true;
- bool bCtrlAltHeld = GameInput.IsControlDown && GameInput.IsAltDown;
- _log.ForContext("car", __instance.car).Information($"{GameInput.IsShiftDown} {GameInput.IsControlDown} {GameInput.IsAltDown} {bCtrlAltHeld} ");
+ _log.ForContext("car", __instance.car).Debug($"{GameInput.IsShiftDown} {GameInput.IsControlDown} {GameInput.IsAltDown}");
if (OnPointerDown(evt, PickableActivation.Primary))
{
CameraSelector.shared.FollowCar(__instance.car);
- _log.ForContext("car", __instance.car).Information("just click!");
+ _log.ForContext("car", __instance.car).Debug("just click!");
return false;
- }
- //single click with keys pressed:
- else if (evt.Activation == PickableActivation.Primary)
- {
- bool output = true;
- var consist = __instance.car.EnumerateCoupled();
- bool handbrakesApplied = consist.Any(c => c.HandbrakeApplied());
- bool airSystemIssues = consist.Any(c => c.EndAirSystemIssue());
- Func cabooseNear = () => (bool)__instance.car.FindMyCaboose(0.0f, false);
- bool needsOiling = GameInput.IsShiftDown && consist.All(c => c.IsStopped()) && consist.Any(c => c.NeedsOiling || c.HasHotbox) && (!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || cabooseNear());
- var chargeIt = handbrakesApplied || airSystemIssues || needsOiling;
- //CTRL + ALT + SHIFT : BrakesAngleCocksAndOiling
- //CTRL + ALT : Release Consist Brakes and Check AngleCocks
- //ALT + SHIFT : toggle consist brakes
- //CTRL + SHIFT : Check Consist Angle Cocks
- //ALT : Toggle car brakes and & air up cars
- //CTRL : NOTHING; BASE CAR INSPECTOR
+ }
+ return HandleCarOrTrainBrakeDisplayClick(__instance.car, tweaksAndThings, evt.Activation);
+ }
+ internal static bool HandleCarOrTrainBrakeDisplayClick(Car car, TweaksAndThingsPlugin tweaksAndThings, PickableActivation activation)
+ {
+ bool bCtrlAltHeld = GameInput.IsControlDown && GameInput.IsAltDown;
+ bool output = true;
+ var consist = car.EnumerateCoupled();
+ bool handbrakesApplied = consist.Any(c => c.HandbrakeApplied());
+ bool airSystemIssues = consist.Any(c => c.EndAirSystemIssue());
+ Func cabooseNear = () => (bool)car.FindMyCabooseSansLoadRequirement();
+ bool needsOiling = GameInput.IsShiftDown && consist.All(c => c.IsStopped()) && consist.Any(c => c.NeedsOiling || c.HasHotbox) && (!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || cabooseNear());
+ var chargeIt = handbrakesApplied || airSystemIssues || needsOiling;
+ //CTRL + ALT + SHIFT : BrakesAngleCocksAndOiling
+ //CTRL + ALT : Release Consist Brakes and Check AngleCocks
+ //ALT + SHIFT : toggle consist brakes
+ //CTRL + SHIFT : Check Consist Angle Cocks
+ //ALT : Toggle car brakes and & air up cars
+ //CTRL : NOTHING; BASE CAR INSPECTOR
+ //SHIFT : FOLLOW
+
+ if (activation == PickableActivation.Primary)
+ {
if (bCtrlAltHeld)
{
- BrakesAngleCocksAndOiling(__instance, tweaksAndThings, GameInput.IsShiftDown, consist, handbrakesApplied, airSystemIssues, needsOiling, chargeIt);
- _log.ForContext("car", __instance.car).Information($"ctrlAlt{(GameInput.IsShiftDown ? "shift" : string.Empty)}Held!");
+ BrakesAngleCocksAndOiling(car, tweaksAndThings, GameInput.IsShiftDown, consist, handbrakesApplied, airSystemIssues, needsOiling, chargeIt);
+ _log.ForContext("car", car).Debug($"ctrlAlt{(GameInput.IsShiftDown ? "shift" : string.Empty)}Held!");
output = false;
}
else if (GameInput.IsAltDown && GameInput.IsShiftDown)
{
- CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment());
- _log.ForContext("car", __instance.car).Information("ctrlShiftHeld!");
+ CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment());
+ _log.ForContext("car", car).Debug("ctrlShiftHeld!");
output = false;
}
else if (GameInput.IsControlDown && GameInput.IsShiftDown)
{
if (airSystemIssues)
- CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment());
- _log.ForContext("car", __instance.car).Information("altShiftHeld!");
+ CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment());
+ _log.ForContext("car", car).Debug("altShiftHeld!");
output = false;
}
else if (GameInput.IsAltDown)
{
- __instance.car.SetHandbrake(!__instance.car.HandbrakeApplied());
- CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(__instance.car);
- if (__instance.car.TryGetAdjacentCar(Car.LogicalEnd.A, out Model.Car cA)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cA);
- if (__instance.car.TryGetAdjacentCar(Car.LogicalEnd.B, out Model.Car cB)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cB);
- _log.ForContext("car", __instance.car).Information("ctrlHeld!");
- TagController.Shared.UpdateTag(__instance.car, __instance.car.TagCallout, OpsController.Shared);
- __instance.car.TagCallout.Update();
+ car.SetHandbrake(!car.HandbrakeApplied());
+ CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(car);
+ if (car.TryGetAdjacentCar(Car.LogicalEnd.A, out Model.Car cA)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cA);
+ if (car.TryGetAdjacentCar(Car.LogicalEnd.B, out Model.Car cB)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cB);
+ _log.ForContext("car", car).Debug("ctrlHeld!");
+ TagController.Shared.UpdateTag(car, car.TagCallout, OpsController.Shared);
+ car.TagCallout.Update();
output = false;
}
- return output;
-
-
- //else if (ctrlHeld && shiftHeld)
- //{
- // var selected = UI.CarInspector.CarInspector._instance._selectedTabState.Value;
- // UI.CarInspector.CarInspector.Show(__instance.car);
- // UI.CarInspector.CarInspector._instance._selectedTabState.Value = selected;
-
- //}
+ else if (GameInput.IsShiftDown)
+ {
+ CameraSelector.shared.FollowCar(car);
+ output = false;
+ }
+ }
+ else if ((GameInput.IsControlDown || GameInput.IsAltDown) && activation == PickableActivation.Secondary)
+ {
+ AltClickageMyBrosif(car, activation);
+ output = false;
+ }
+ else if (activation == PickableActivation.Secondary)
+ {
+ //AltClickageMyBrosif(car, activation);
+ CarPickable.HandleShowContextMenu(car);
+ output = false;
}
- return true;
+ return output;
}
- private static void BrakesAngleCocksAndOiling(CarPickable __instance, TweaksAndThingsPlugin tweaksAndThings, bool shiftHeld, System.Collections.Generic.IEnumerable consist, bool handbrakesApplied, bool airSystemIssues, bool needsOiling, bool chargeIt)
+ private static void AltClickageMyBrosif(Car car, PickableActivation activation)
+ {
+ //if (GameInput.IsControlDown)
+ //{
+
+ //}
+ //else if (GameInput.IsAltDown)
+ //{
+ // logSet(car);
+ // //car.TryGetAdjacentCar(Car.LogicalEnd.A, out var OutCarA);
+ // //car.TryGetAdjacentCar(Car.LogicalEnd.B, out var OutCarB);
+ // output = false;
+ //}
+ List cars = TrainController.Shared.SelectedLocomotive.EnumerateCoupled().ToList();
+ logSet(car);
+ //car.TryGetAdjacentCar(Car.LogicalEnd.A, out var OutCarA);
+ //car.TryGetAdjacentCar(Car.LogicalEnd.B, out var OutCarB);
+ //bool b4 = false;
+ //if ((Object)(object)OutCarA != (Object)null && OutCarA.id == cars[i - 1].id)
+ //{
+ // cars[i - 1].ApplyEndGearChange(Car.LogicalEnd.B, Car.EndGearStateKey.Anglecock, 0f);
+ // ccar.ApplyEndGearChange(Car.LogicalEnd.A, Car.EndGearStateKey.Anglecock, 0f);
+ // ccar.HandleCouplerClick(ccar.EndGearA.Coupler);
+ // logger.Information("{indName}: Decoupled {ccarName} from {cars1Name} at NOA{FR}", new object[4]
+ // {
+ // ((Object)base.Industry).name,
+ // ccar.DisplayName,
+ // cars[i - 1].DisplayName,
+ // (ccar.LogicalToEnd(Car.LogicalEnd.A) == Car.End.F) ? "F" : "R"
+ // });
+ // Storage.LastCarEnd = ccar.LogicalToEnd(Car.LogicalEnd.A);
+ // b4 = true;
+ //}
+ //if ((Object)(object)OutCarB != (Object)null && !b4 && OutCarB.id == cars[i - 1].id)
+ //{
+ // cars[i - 1].ApplyEndGearChange(Car.LogicalEnd.A, Car.EndGearStateKey.Anglecock, 0f);
+ // ccar.ApplyEndGearChange(Car.LogicalEnd.B, Car.EndGearStateKey.Anglecock, 0f);
+ // ccar.HandleCouplerClick(ccar.EndGearB.Coupler);
+ // logger.Information("{indName}: Decoupled {ccarName} from {carsm1Name} at NOB{FR}", new object[4]
+ // {
+ // ((Object)base.Industry).name,
+ // ccar.DisplayName,
+ // cars[i - 1].DisplayName,
+ // (ccar.LogicalToEnd(Car.LogicalEnd.A) == Car.End.F) ? "F" : "R"
+ // });
+ // Storage.LastCarEnd = ccar.LogicalToEnd(Car.LogicalEnd.B);
+ // b4 = true;
+ //}
+ //if (!b4)
+ //{
+ // logger.Information("{IndName}: ERROR: NLC {ccarName} can't find LCC {carsm1Name} ({OutCarAName},{OutCarBName})", new object[5]
+ // {
+ // ((Object)base.Industry).name,
+ // ccar.DisplayName,
+ // cars[i - 1].DisplayName,
+ // ((Object)(object)OutCarA != (Object)null) ? OutCarA.DisplayName : "no Car",
+ // ((Object)(object)OutCarB != (Object)null) ? OutCarB.DisplayName : "no Car"
+ // });
+ // return;
+ //}
+ }
+
+ private static void logSet(Car refCar)
+ {
+ IntegrationSet set = refCar.set;
+ foreach (Car car in set.Cars)
+ {
+ Car acar;
+ string endA = (car.TryGetAdjacentCar(Car.LogicalEnd.A, out acar) ? acar.DisplayName : "none");
+ Car bcar;
+ string endB = (car.TryGetAdjacentCar(Car.LogicalEnd.B, out bcar) ? bcar.DisplayName : "none");
+ _log.Information("[Car: {id}:(EndA:{carA}),(EndB:{carB}),(FisA:{fisA})]", new object[4] { car.DisplayName, endA, endB, car.FrontIsA });
+ }
+ }
+
+
+ private static void BrakesAngleCocksAndOiling(Car car, TweaksAndThingsPlugin tweaksAndThings, bool shiftHeld, System.Collections.Generic.IEnumerable consist, bool handbrakesApplied, bool airSystemIssues, bool needsOiling, bool chargeIt)
{
int hbFix = 0;
if (handbrakesApplied)
- CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Handbrake, false);
+ CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, false);
if (airSystemIssues)
- CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, false);
+ CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, false);
if (needsOiling)
- hbFix = CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Oil, false);
+ hbFix = CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Oil, false);
if (hbFix > 0)
- Multiplayer.Broadcast($"Near {Hyperlink.To(__instance.car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\"");
+ Multiplayer.Broadcast($"Near {Hyperlink.To(car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\"");
if (chargeIt)
- CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist);
+ CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist);
}
public static bool OnPointerDown(
diff --git a/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs b/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs
index fc174b0..5cfde76 100644
--- a/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs
+++ b/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs
@@ -4,6 +4,7 @@ using Railloader;
using RMROC451.TweaksAndThings.Enums;
using RMROC451.TweaksAndThings.Extensions;
using RollingStock;
+using System;
using System.Linq;
using UI;
using UI.ContextMenu;
@@ -35,8 +36,6 @@ internal class CarPickable_HandleShowContextMenu_Patch
});
if (GameInput.IsShiftDown)
{
-
-
if (!car.EnumerateCoupled().Any(c => !c.SupportsBleed()))
{
shared.AddButton(ContextMenuQuadrant.Brakes, $"Bleed Consist", SpriteName.Bleed, delegate
@@ -44,23 +43,24 @@ internal class CarPickable_HandleShowContextMenu_Patch
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);
- });
- if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
- {
- shared.AddButton(ContextMenuQuadrant.Unused2, $"Air Up Consist", SpriteName.Select, delegate
+ shared.AddButton(ContextMenuQuadrant.Brakes, $"{(car.EnumerateCoupled().Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} Consist", SpriteName.Handbrake, delegate
+ {
+ CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost);
+ });
+
+ if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
+ {
+ shared.AddButton(ContextMenuQuadrant.General, $"Air Up Consist", SpriteName.Select, delegate
{
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
});
+ }
}
- }
- else {
- if (car.SupportsBleed())
+ else
{
+ if (car.SupportsBleed())
+ {
shared.AddButton(ContextMenuQuadrant.Brakes, "Bleed", SpriteName.Bleed, car.SetBleed);
}
shared.AddButton(ContextMenuQuadrant.Brakes, car.air.handbrakeApplied ? "Release Handbrake" : "Apply Handbrake", SpriteName.Handbrake, delegate
@@ -75,7 +75,9 @@ internal class CarPickable_HandleShowContextMenu_Patch
CameraSelector.shared.FollowCar(car);
});
- shared.Show(car.DisplayName);
+ 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}");
shared.BuildItemAngles();
shared.StartCoroutine(shared.AnimateButtonsShown());
return false;
diff --git a/TweaksAndThings/Patches/ContextMenu_Show_Patch.cs b/TweaksAndThings/Patches/ContextMenu_Show_Patch.cs
deleted file mode 100644
index 007c018..0000000
--- a/TweaksAndThings/Patches/ContextMenu_Show_Patch.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using HarmonyLib;
-using Helpers;
-using Railloader;
-using Serilog;
-using System;
-using System.Collections.Generic;
-using UI;
-using UI.ContextMenu;
-using UnityEngine;
-using UnityEngine.UI;
-
-namespace RMROC451.TweaksAndThings.Patches;
-
-[HarmonyPatch(typeof(UI.ContextMenu.ContextMenu))]
-[HarmonyPatch(nameof(UI.ContextMenu.ContextMenu.Show), typeof(string))]
-[HarmonyPatchCategory("RMROC451TweaksAndThings")]
-internal class ContextMenu_Show_Patch
-{
- static bool Prefix(UI.ContextMenu.ContextMenu __instance, string centerText)
- {
- TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared;
- if (!tweaksAndThings.IsEnabled()) return true;
-
- if (!__instance.GetRootCanvas(out var rootCanvas))
- {
- Log.Warning("Couldn't get root canvas");
- return true;
- }
- __instance.CancelHideCoroutine();
- if (__instance.contentRectTransform.childCount >= 1) __instance.contentRectTransform.GetChild(0).DestroyAllChildren(); //YOINK DEM CIRCLES!
- __instance.SetupTemplate(rootCanvas);
- __instance.centerLabel.text = centerText;
- Canvas componentInParent = ((Component)__instance.contentRectTransform).GetComponentInParent