From 3741995d3a699ce33f7ed796b54a596ff983c169 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Sun, 17 Aug 2025 10:39:15 -0500 Subject: [PATCH 01/26] Update README.md for 2.0.0 --- README.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 140 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8911242..1158997 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,146 @@ 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: + ### Does this work in Multiplayer? Yes, these are client side mods. Host doesn't need to have them. From b3780b8370e1a165cb49dce1ee04d58650b10788 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Sun, 17 Aug 2025 10:56:08 -0500 Subject: [PATCH 02/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1158997..bdd5ac2 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,6 @@ Enter Tweaks and Things. 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.* From 3d0a57c56e07d994eb7eb01aab9f04282af96a16 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Sun, 17 Aug 2025 13:58:28 -0500 Subject: [PATCH 03/26] Update README.md --- README.md | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bdd5ac2..5482088 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,52 @@ Enter Tweaks and Things. ### QOL & Cabeese Modifications:
    -
  • A: Adding a visual indication to the car's callout tag to indicate: +
  • 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: Oiling Level/Hotbox Indication : pie chart or 🔥 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
      • +
      • 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 @@ -60,7 +93,7 @@ Enter Tweaks and Things.
    • C1e: See S1b1/S1b2/S1b3 for what this is used for.
  • -
  • C2: Proximity Detection
    +
  • 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
    • @@ -91,10 +124,10 @@ Enter Tweaks and Things.
    • M: Miscellaneous
        -
      • M1: 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'.
      • +
      • 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: MU'd locomotives will automatically be hidden unless they are SELECTED or FAVORITED.
        • +
        • 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))
          • @@ -103,10 +136,13 @@ Enter Tweaks and Things.
          -
        • M3: MU Adjacency Restriction Removal

          (USE AT OWN RISK)


          +
        • 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 @@ -124,20 +160,20 @@ Enter Tweaks and Things.
      • 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: Refill / Crew Hours Load Option
      Select whether you want to manually reload cabeese via: +
    • 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: 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.
    • +
    • 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: Debt Allowance
      +
    • 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
        From 11dba3ff78c0cca2c8693410ce9d02345129abec Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Sun, 17 Aug 2025 14:05:52 -0500 Subject: [PATCH 04/26] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5482088..0255838 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,11 @@ Enter Tweaks and Things.
      • 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
        • +
        • 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!
        • From f1ba2ed6f1a67222a0d6fd7908112b1430b2eac6 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 24 Aug 2025 01:17:48 -0500 Subject: [PATCH 05/26] updates for TrainBrakeDisplay (allow color coding of brake display on engine control panel when in tag view mode, and be able to click them with my click modifier options to interact), AE waypoint engineer adding distinct "click to set" destinations in the gear menu, and allowing engine only consists to bypass safety first. --- Assembly.version | 4 +- TweaksAndThings/Commands/CrewUpdateCommand.cs | 4 +- TweaksAndThings/Definition.json | 3 +- .../Extensions/AutoEngineer_Extensions.cs | 10 +- TweaksAndThings/Extensions/Car_Extensions.cs | 2 +- ...ersHelper_SendAutoEngineerCommand_Patch.cs | 8 +- ...oEngineerOrdersHelper_SetWaypoint_patch.cs | 17 +- ...Controls_ConfigureOptionsDropdown_Patch.cs | 224 ++++++++++++++++++ ...seLocomotive_FindSourceLocomotive_Patch.cs | 86 +++++++ .../CarInspector_PopulateCarPanel_Patch.cs | 8 +- .../Patches/CarPickable_Activate_Patch.cs | 189 +++++++++++---- ...CarPickable_HandleShowContextMenu_Patch.cs | 21 +- .../Patches/MapWindow_OnClick_Patch.cs | 2 +- .../NoticeExtensions_PostNotice_Patch.cs | 4 +- ...troller_AnnounceCoalescedPayments_Patch.cs | 6 +- .../Patches/TagController_UpdateTag_Patch.cs | 2 +- .../TrainBrakeDisplay_GetCarImage_Patch.cs | 42 ++++ .../RMROC451.TweaksAndThings.csproj | 5 +- TweaksAndThings/Settings/Settings.cs | 11 +- .../TrainBrakeDisplay.cs | 177 ++++++++++++++ TweaksAndThings/TweaksAndThingsPlugin.cs | 28 ++- 21 files changed, 749 insertions(+), 104 deletions(-) create mode 100644 TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs create mode 100644 TweaksAndThings/Patches/TrainBrakeDisplay_GetCarImage_Patch.cs create mode 100644 TweaksAndThings/TweakedOriginalMethods/TrainBrakeDisplay.cs diff --git a/Assembly.version b/Assembly.version index 45f4823..a8136ea 100644 --- a/Assembly.version +++ b/Assembly.version @@ -1,7 +1,7 @@ 2 - 0 - 1 + 1 + 0 \ No newline at end of file diff --git a/TweaksAndThings/Commands/CrewUpdateCommand.cs b/TweaksAndThings/Commands/CrewUpdateCommand.cs index a949ac9..f2cffdc 100644 --- a/TweaksAndThings/Commands/CrewUpdateCommand.cs +++ b/TweaksAndThings/Commands/CrewUpdateCommand.cs @@ -46,9 +46,9 @@ 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}"); + if (StateManager.IsHost) 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}\""); + if (!StateManager.IsHost) Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {Hyperlink.To(car)}: \"{message}\""); return string.Empty; } diff --git a/TweaksAndThings/Definition.json b/TweaksAndThings/Definition.json index a29b876..31fd06d 100644 --- a/TweaksAndThings/Definition.json +++ b/TweaksAndThings/Definition.json @@ -21,6 +21,7 @@ "mixintos": { "container:ne-caboose01": "file(mroc-cabeese.json)", "container:ne-caboose02": "file(mroc-cabeese.json)", - "container:ne-caboose03": "file(mroc-cabeese.json)" + "container:ne-caboose03": "file(mroc-cabeese.json)", + "game-graph": "file(tweakedLoadIDS.json)" } } \ No newline at end of file diff --git a/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs b/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs index a71600c..e96a908 100644 --- a/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs +++ b/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs @@ -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, @@ -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); } @@ -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..67a6c2b 100644 --- a/TweaksAndThings/Extensions/Car_Extensions.cs +++ b/TweaksAndThings/Extensions/Car_Extensions.cs @@ -140,7 +140,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/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs index 623a66f..9869789 100644 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs @@ -38,7 +38,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch 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;"); + _log.Debug($"{Enum.GetName(typeof(AutoEngineerMode), mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] {nameof(AutoEngineerOrdersExtensions.MaxSpeedMph)} limited to {limitedSpeed} from {orig}; No Caboose in Consist;"); } } @@ -50,7 +50,9 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch { 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.Shared.RequireConsistCabooseForOilerAndHotboxSpotter(); string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] "; Func firstClass = () => @@ -85,7 +87,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch logMessage += $"\nGovern AE? {output}"; - _log.Information(logMessage); + _log.Debug(logMessage); return output; } diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs index a512189..e270df8 100644 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs @@ -1,6 +1,8 @@ using Game.Notices; +using Game.State; using HarmonyLib; using Model; +using Network; using Serilog; using UI.EngineControls; using UnityEngine; @@ -16,11 +18,14 @@ 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")); + 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")); + } } } diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs new file mode 100644 index 0000000..4b8bf7b --- /dev/null +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -0,0 +1,224 @@ +using Game.State; +using HarmonyLib; +using KeyValue.Runtime; +using Model; +using Model.AI; +using Model.Ops; +using Network; +using Network.Messages; +using Railloader; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using Track; +using Track.Search; +using UI; +using UI.EngineControls; +using UnityEngine; +using static Track.Search.RouteSearch; +using Location = Track.Location; + +namespace RMROC451.TweaksAndThings.Patches; + +[HarmonyPatch(typeof(AutoEngineerWaypointControls))] +[HarmonyPatch(nameof(AutoEngineerWaypointControls.ConfigureOptionsDropdown))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch +{ + private static Serilog.ILogger _log => Log.ForContext(); + + private static readonly HashSet _keyChangeObservers = new HashSet(); + private static readonly HashSet destinations = new HashSet(); + private static readonly HashSet consist = new HashSet(); + private static string _lastLocoCarId = string.Empty; + private static bool recalcing = false; + + static void Postfix(AutoEngineerWaypointControls __instance, ref OptionsDropdownConfiguration __result) + { + + _log.Information($"HI BOB"); + foreach(var o in _keyChangeObservers) o.Dispose(); + _keyChangeObservers.Clear(); + destinations.Clear(); + recalcing = false; + TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + if (!tweaksAndThings.IsEnabled()) return; + //_log.Information($"HI2"); + + + List rowDatas = __result.Rows.ToList(); + var func = __result.OnRowSelected; + int origCount = rowDatas.Count; + int maxRowOrig = origCount - 1; + + var selectedLoco = TrainController.Shared.SelectedLocomotive; + if (selectedLoco.id != _lastLocoCarId || !(consist?.Any() ?? false)) + { + _lastLocoCarId = selectedLoco.id; + consist.Clear(); + consist.UnionWith(selectedLoco.EnumerateCoupled()?.ToList() ?? Enumerable.Empty()); + } + var aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco); + List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = new(); + + foreach(var c in consist) + { + AddObserversToCar(__instance, c); + OpsCarPosition? destination = c.Waybill.HasValue && !c.Waybill.Value.Completed ? c.Waybill.Value.Destination : null; + bool completed = c.Waybill?.Completed ?? false; + if (!destination.HasValue && c.TryGetOverrideDestination(OverrideDestination.Repair, OpsController.Shared, out (OpsCarPosition, string)? result)) destination = result.Value.Item1; + _log.Information($"{c.DisplayName} -> {destination.HasValue}"); + + string destId = destination?.Identifier ?? string.Empty; + + if (destinations.Contains(destId)) continue; + + if (destination.HasValue && !completed) + { + string destName = destination.Value.DisplayName; + float? distance = null; + + if (Graph.Shared.TryGetLocationFromPoint(destination.Value.Spans?.FirstOrDefault().GetSegments().FirstOrDefault(), destination.Value.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; + }; + _log.Information($"{c.DisplayName} -> {destName} {destId} {distance?.ToString()}"); + if (distance.HasValue) + { + destinations.Add(destId); + jumpTos.Add(( + destinationId: destId, + destination: $"WP> {destName}" + , distance: distance + , location: (Location?)destLoc + )); + } + } + } + + jumpTos = jumpTos?.OrderBy(c => c.distance)?.ToList() ?? []; + var safetyFirst = AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies() && 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.Information($"{TrainController.Shared.SelectedLocomotive.DisplayName} row {row}/{jumpTos.Count}/{rowDatas.Count}"); + if (row <= maxRowOrig) + { + func(row); + } + if (row > maxRowOrig && jumpTos[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 = jumpTos[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 ? (selectedLoco.DisplayName + " Train too long to navigate to waypoint.") : (selectedLoco.DisplayName + " 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); + } + } + } + ); + + } + + 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); + } + + private static void AddObserversToCar(AutoEngineerWaypointControls __instance, Car c) + { + AddObserver(__instance, c, Car.KeyOpsWaybill); + AddObserver(__instance, c, Car.KeyOpsRepairDestination); + 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.Information($"{car.DisplayName} OBSV {key}: {value}; recalcing {recalcing}"); + if (recalcing) return; + try + { + foreach(var o in _keyChangeObservers) + { + o.Dispose(); + } + var loco = TrainController.Shared.SelectedLocomotive; + TrainController.Shared.SelectedCar = null; + _keyChangeObservers.Clear(); + recalcing = true; + new WaitForSeconds(0.25f); + TrainController.Shared.SelectedCar = loco; + } + catch (Exception ex) + { + _log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}"); + } + }, + 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..ed52750 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 @@ -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))); @@ -232,7 +232,7 @@ internal class CarInspector_PopulateCarPanel_Patch 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..32dfc7d 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.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 + //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..0b36b59 100644 --- a/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs +++ b/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs @@ -44,23 +44,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.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 { 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 diff --git a/TweaksAndThings/Patches/MapWindow_OnClick_Patch.cs b/TweaksAndThings/Patches/MapWindow_OnClick_Patch.cs index 5619df3..c1aef99 100644 --- a/TweaksAndThings/Patches/MapWindow_OnClick_Patch.cs +++ b/TweaksAndThings/Patches/MapWindow_OnClick_Patch.cs @@ -39,7 +39,7 @@ internal class MapWindow_OnClick_Patch texture2D.wrapMode = TextureWrapMode.Clamp; if (!ImageConversion.LoadImage(texture2D, File.ReadAllBytes(path))) { - _log.Information($"Unable to load {name} icon!"); + _log.Debug($"Unable to load {name} icon!"); return null; } Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f)); diff --git a/TweaksAndThings/Patches/NoticeExtensions_PostNotice_Patch.cs b/TweaksAndThings/Patches/NoticeExtensions_PostNotice_Patch.cs index 27fe48c..17f0d19 100644 --- a/TweaksAndThings/Patches/NoticeExtensions_PostNotice_Patch.cs +++ b/TweaksAndThings/Patches/NoticeExtensions_PostNotice_Patch.cs @@ -1,4 +1,5 @@ using Game.Notices; +using Game.State; using HarmonyLib; using Model; using Serilog; @@ -14,10 +15,11 @@ internal class NoticeExtensions_PostNotice_Patch private static ILogger _log => Log.ForContext(); static void Postfix(Car car, string key, string content) { + if (!StateManager.IsHost) return; try { - //Log.Information($"{car.DisplayName} patch PostNotice"); + //Log.Debug($"{car.DisplayName} patch PostNotice"); if (!string.IsNullOrEmpty(content) && key.Equals("ai-wpt") && content.ToLower().Contains("Arrived at Waypoint".ToLower()) diff --git a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs index 337045a..6692686 100644 --- a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs +++ b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs @@ -113,18 +113,18 @@ internal class OpsController_AnnounceCoalescedPayments_Patch var passengerStops = OpsController.Shared.AllIndustries .SelectMany(i => i.TrackDisplayables.Where(t => refillLocations.Contains(t.GetType()))); - //Log.Information($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper => PassengerStops => {string.Join(",", passengerStops)}"); + //Log.Debug($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper => PassengerStops => {string.Join(",", passengerStops)}"); var cabeese = passengerStops .SelectMany(t => t.TrackSpans?.Select(s => (tc.CarsOnSpan(s) ?? Enumerable.Empty()).Where(c => c.IsCaboose()))?.SelectMany(c => c?.Select(c2 => (t, c2)))); - //Log.Information($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper => PassengerStops Cabeese => {string.Join(",", cabeese?.Select(c => $"{c.t} : {c.c2}") ?? [])}"); + //Log.Debug($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper => PassengerStops Cabeese => {string.Join(",", cabeese?.Select(c => $"{c.t} : {c.c2}") ?? [])}"); CrewCarDict = CrewCarDict.Where(kvp => cabeese.Select(c => c.c2.id).Contains(kvp.Key)).ToDictionary(k => k.Key, v => v.Value); var deltaTime = (float)(TimeWeather.Now.TotalSeconds - dateTime.TotalSeconds); foreach (var caboose in cabeese) { - //Log.Information($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper ({deltaTime}) => {caboose.t} : {caboose.c2}"); + //Log.Debug($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper ({deltaTime}) => {caboose.t} : {caboose.c2}"); CarLoadCrewHelper(caboose.c2, deltaTime); } dateTime = TimeWeather.Now; diff --git a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs index 724e5b1..971dc0d 100644 --- a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs +++ b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs @@ -28,7 +28,7 @@ internal class TagController_UpdateTag_Patch return; } - ProceedWithPostFix(car, tagCallout, tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || !tweaksAndThings.CabooseRequiredForLocoOilIndicator()); + ProceedWithPostFix(car, tagCallout, tweaksAndThings.CabooseRequiredForLocoOilIndicator()); return; } diff --git a/TweaksAndThings/Patches/TrainBrakeDisplay_GetCarImage_Patch.cs b/TweaksAndThings/Patches/TrainBrakeDisplay_GetCarImage_Patch.cs new file mode 100644 index 0000000..05d80a4 --- /dev/null +++ b/TweaksAndThings/Patches/TrainBrakeDisplay_GetCarImage_Patch.cs @@ -0,0 +1,42 @@ +using HarmonyLib; +using Model; +using Railloader; +using UI; +using UI.Tags; +using UnityEngine; + +namespace RMROC451.TweaksAndThings.Patches; + +[HarmonyPatch(typeof(TrainBrakeDisplay))] +[HarmonyPatch(nameof(TrainBrakeDisplay.Update))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class TrainBrakeDisplay_Update_Patch() +{ + private static bool Prefix(TrainBrakeDisplay __instance) + { + + TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + if (!tweaksAndThings.IsEnabled()) return true; + + TweakedOriginalMethods.TrainBrakeDisplay.Update(__instance); + + return false; + } +} + +[HarmonyPatch(typeof(TrainBrakeDisplay))] +[HarmonyPatch(nameof(TrainBrakeDisplay.ColorForCar))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class TrainBrakeDisplay_ColorForCar_Patch +{ + private static bool Prefix(TrainBrakeDisplay __instance, Car car, ref Color __result) + { + TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.TrainBrakeDisplayShowsColorsInCalloutMode() || !TagController.Shared.TagsVisible) return true; + + TweakedOriginalMethods.TrainBrakeDisplay.ColorForCar(__instance, car, ref __result); + + return false; + } +} + diff --git a/TweaksAndThings/RMROC451.TweaksAndThings.csproj b/TweaksAndThings/RMROC451.TweaksAndThings.csproj index c4d70df..8069656 100644 --- a/TweaksAndThings/RMROC451.TweaksAndThings.csproj +++ b/TweaksAndThings/RMROC451.TweaksAndThings.csproj @@ -1,9 +1,8 @@  - + + - - False False diff --git a/TweaksAndThings/Settings/Settings.cs b/TweaksAndThings/Settings/Settings.cs index de4b2a1..26ff960 100644 --- a/TweaksAndThings/Settings/Settings.cs +++ b/TweaksAndThings/Settings/Settings.cs @@ -29,7 +29,8 @@ public class Settings bool servicingFundPenalty, bool safetyFirst, CrewHourLoadMethod loadCrewHoursMethod, - float cabeeseSearchRadiusFtInMeters + float cabeeseSearchRadiusFtInMeters, + bool trainBrakeDisplayShowsColorsInCalloutMode ) { WebhookSettingsList = webhookSettingsList; @@ -43,6 +44,7 @@ public class Settings SafetyFirst = safetyFirst; LoadCrewHoursMethod = loadCrewHoursMethod; CabeeseSearchRadiusFtInMeters = cabeeseSearchRadiusFtInMeters; + TrainBrakeDisplayShowsColorsInCalloutMode = trainBrakeDisplayShowsColorsInCalloutMode; } public readonly UIState _selectedTabState = new UIState(null); @@ -57,6 +59,7 @@ public class Settings public bool SafetyFirst; public CrewHourLoadMethod LoadCrewHoursMethod; public float CabeeseSearchRadiusFtInMeters; + public bool TrainBrakeDisplayShowsColorsInCalloutMode; internal void AddAnotherRow() { @@ -64,7 +67,7 @@ public class Settings if (!string.IsNullOrEmpty(WebhookSettingsList.OrderByDescending(wsl => wsl.WebhookUrl).Last().WebhookUrl)) { WebhookSettingsList.Add(new()); - Log.Information($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in"); + Log.Debug($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in"); } } } @@ -135,7 +138,7 @@ public static class SettingsExtensions input?.settings?.SafetyFirst ?? false; public static bool DayLoadCrewHours(this TweaksAndThingsPlugin input) => (input?.settings?.LoadCrewHoursMethod ?? CrewHourLoadMethod.Tracks) == CrewHourLoadMethod.Daily; - public static float CabeeseSearchRadiusInMeters(this TweaksAndThingsPlugin input) => - input?.settings?.CabeeseSearchRadiusFtInMeters ?? 21f; + public static bool TrainBrakeDisplayShowsColorsInCalloutMode(this TweaksAndThingsPlugin input) => + input?.settings?.TrainBrakeDisplayShowsColorsInCalloutMode ?? false; } \ No newline at end of file diff --git a/TweaksAndThings/TweakedOriginalMethods/TrainBrakeDisplay.cs b/TweaksAndThings/TweakedOriginalMethods/TrainBrakeDisplay.cs new file mode 100644 index 0000000..08ad136 --- /dev/null +++ b/TweaksAndThings/TweakedOriginalMethods/TrainBrakeDisplay.cs @@ -0,0 +1,177 @@ +using Model; +using Model.Ops; +using Model.Physics; +using Railloader; +using RMROC451.TweaksAndThings.Patches; +using UI; +using UI.CarInspector; +using UI.Common; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using static UnityEngine.UI.Image; +using Object = UnityEngine.Object; + +namespace RMROC451.TweaksAndThings.TweakedOriginalMethods +{ + internal static class TrainBrakeDisplay + { + internal static void ColorForCar(UI.TrainBrakeDisplay __instance, Car car, ref Color __result) + { + __result = Color.gray; + + if (car.IsDerailed) + { + __result = __instance.derailedColor; + return; + } + CarAirSystem air = car.air; + OpsController opsController = OpsController.Shared; + if (air.handbrakeApplied) + { + __result = __instance.handbrakeAppliedColor; + return; + } + else if ( + (Object)(object)opsController != (Object)null && + opsController.TryGetDestinationInfo(car, out var destinationName, out var isAtDestination, out var destinationPosition, out var destination) + ) + { + Area area = opsController.AreaForCarPosition(destination); + if ((Object)(object)area != (Object)null) + { + __result = area.tagColor; + } + if (!isAtDestination) + { + float num = 1f / __result.maxColorComponent; + __result *= num; + } + } + + if (Mathf.InverseLerp(0f, 72f, air.BrakeCylinder.Pressure) >= 10f) __result = (__result + __instance.ColorForPsi(air.BrakeCylinder.Pressure)) / 2f; + } + + internal static Image GetCarImage(UI.TrainBrakeDisplay __instance, int index, float xCyl, float yCyl, Car car) + { + Image output = null; + if (__instance._carImages.Count - 1 < index) + { + var tweaksAndThings = SingletonPluginBase.Shared; + GameObject val = new GameObject(); + val.transform.SetParent(((Component)__instance).transform, false); + ((Object)val).name = $"Car {index}"; + float height = 12f; + val.AddComponent().SetFrame(xCyl, yCyl, __instance._imageWidth, height); + EventTrigger trigger = val.AddComponent(); + EventTrigger.Entry entry = new EventTrigger.Entry(); + entry.eventID = EventTriggerType.PointerClick; + entry.callback.AddListener((eventData) => { + PointerEventData ped = (PointerEventData)eventData; + PickableActivation pa = PickableActivation.Primary; + if (ped.button == PointerEventData.InputButton.Right) pa = PickableActivation.Secondary; + + bool checkFurther = ped.button == PointerEventData.InputButton.Middle ? true : CarPickable_Activate_Patch.HandleCarOrTrainBrakeDisplayClick(car, tweaksAndThings, pa); + if (checkFurther & GameInput.IsControlDown) CarInspector.Show(car); + }); + trigger.triggers.Add(entry); + output = val.AddComponent(); + + output.type = (Type)1; + __instance._carImages.Add(output); + } + else + { + output = __instance._carImages[index]; + ((Component)output).gameObject.SetActive(true); + } + output.sprite = (car.IsLocomotive ? __instance.locomotiveTile : __instance.carTile); + return output; + } + + internal static void Update(UI.TrainBrakeDisplay __instance) + { + Car selectedCar = __instance._trainController.SelectedCar; + if (selectedCar == null || selectedCar.set == null) return; + + int numberOfCars = selectedCar.set.NumberOfCars; + if (numberOfCars != __instance._lastNumCars) + { + __instance.RemoveAllImages(); + int num = Mathf.Clamp(numberOfCars, 0, 100); + float num2 = (float)num * 8f + (float)(num - 1) * 1f; + if (num2 > __instance._rectTransform.rect.width) + { + float num3 = __instance._rectTransform.rect.width / num2; + __instance._imageWidth = 8f * num3; + __instance._spacing = 1f * num3; + } + else + { + __instance._imageWidth = 8f; + __instance._spacing = 1f; + } + + __instance._lastNumCars = numberOfCars; + } + + int num4 = 0; + float num5 = __instance._imageWidth / 2f; + float num6 = 0f; + float y = 12f - 2f * __instance._spacing; + Car.LogicalEnd logicalEnd = ((selectedCar.set.IndexOfCar(selectedCar).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 = selectedCar.set.StartIndexForConnected(selectedCar, logicalEnd, IntegrationSet.EnumerationCondition.Coupled); + Car car; + while (!stop && (car = selectedCar.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null && !(num5 > __instance._rectTransform.rect.width)) + { + Image carImage = TweakedOriginalMethods.TrainBrakeDisplay.GetCarImage(__instance, num4, num5, 0f, car); + num5 += __instance._imageWidth + __instance._spacing; + carImage.color = __instance.ColorForCar(car); + Image airImage = __instance.GetAirImage(num4, num6, y); + num6 += __instance._imageWidth + __instance._spacing; + Color color; + if (!car[logicalEnd].IsCoupled) + { + color = ColorForOuterAnglecock(car[logicalEnd].AnglecockSetting); + } + else + { + Car car2 = car.CoupledTo(logicalEnd); + bool num7 = car2 != null && car2[end].AnglecockSetting > 0.9f; + bool flag = car[logicalEnd].AnglecockSetting > 0.9f; + color = ((num7 && flag && car[logicalEnd].IsAirConnected) ? Color.clear : Color.white); + } + + airImage.color = color; + if (!car[end].IsCoupled) + { + __instance.GetAirImage(num4 + 1, num6, y).color = ColorForOuterAnglecock(car[end].AnglecockSetting); + } + + num4++; + } + + for (int i = num4; i < __instance._carImages.Count; i++) + { + __instance._carImages[i].gameObject.SetActive(value: false); + } + + for (int j = num4 + 1; j < __instance._airImages.Count; j++) + { + __instance._airImages[j].gameObject.SetActive(value: false); + } + + static Color ColorForOuterAnglecock(float value) + { + if (!((double)value < 0.01)) + { + return Color.white; + } + + return Color.clear; + } + } + } +} diff --git a/TweaksAndThings/TweaksAndThingsPlugin.cs b/TweaksAndThings/TweaksAndThingsPlugin.cs index 7a623b2..1cf6011 100644 --- a/TweaksAndThings/TweaksAndThingsPlugin.cs +++ b/TweaksAndThings/TweaksAndThingsPlugin.cs @@ -40,7 +40,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, static TweaksAndThingsPlugin() { - Log.Information("Hello! Static Constructor was called!"); + Log.Debug("Hello! Static Constructor was called!"); } public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self) @@ -49,7 +49,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, this.moddingContext = moddingContext; - logger.Information("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version); + logger.Debug("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version); moddingContext.RegisterConsoleCommand(new EchoCommand()); @@ -58,7 +58,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, public override void OnEnable() { - logger.Information("OnEnable() was called!"); + logger.Debug("OnEnable() was called!"); var harmony = new Harmony(modDefinition.Id); harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty)); } @@ -77,7 +77,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, public void ModTabDidOpen(UIPanelBuilder builder) { - logger.Information("Daytime!"); + logger.Debug("Daytime!"); if (settings == null) settings = new(); if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList(); @@ -88,7 +88,9 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, settings?.WebhookSettingsList.SanitizeEmptySettings(); builder.AddSection("Adjustments To Base Game", (UIPanelBuilder builder) => { - builder.AddLabel("Repair tracks now require cars to be waybilled, or they will not be serviced/overhauled.\nThey will report on the company window's location section as 'No Work Order Assigned'."); + builder.AddLabel("1) Repair tracks now require cars to be waybilled, or they will not be serviced/overhauled.\nThey will report on the company window's location section as 'No Work Order Assigned'."); + builder.Spacer(spacing * spacing); + builder.AddLabel("2) You now have the same click options on the little car icons in the lower left engine controls ui, as you do with cars in the game. Ctrl click -> open car inspector, etc."); }); builder.Spacer(spacing * spacing); builder.AddTabbedPanels(settings._selectedTabState, delegate (UITabbedPanelBuilder tabBuilder) @@ -236,6 +238,18 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - } ).Tooltip("Allow Insufficient Funds", $@"Will allow interchange service and repair shops to still function when you are insolvent, at a 20% overdraft fee."); + builder.Spacer(spacing); + builder.AddFieldToggle( + "Train Brake Color Mode", + () => this.TrainBrakeDisplayShowsColorsInCalloutMode(), + delegate (bool enabled) + { + if (settings == null) settings = new(); + settings.TrainBrakeDisplayShowsColorsInCalloutMode = enabled; + builder.Rebuild(); + } + ).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); EngineRosterShowsFuelStatusUISection(builder); } @@ -256,7 +270,7 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - builder.Rebuild(); } ) - ).Tooltip("Enable Fuel Display in Engine Roster", $"Will add reaming fuel indication to Engine Roster (with details in roster row tool tip), Examples : {string.Join(" ", Enumerable.Range(0, 4).Select(i => TextSprites.PiePercent(i, 4)))}"); + ).Tooltip("Enable Fuel Display in Engine Roster", $"Will add reamaing fuel indication to Engine Roster (with details in roster row tool tip), Examples : {string.Join(" ", Enumerable.Range(0, 4).Select(i => TextSprites.PiePercent(i, 4)))}"); builder.Spacer(spacing); builder.AddFieldToggle( @@ -333,7 +347,7 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - public void ModTabDidClose() { - logger.Information("Nighttime..."); + logger.Debug("Nighttime..."); this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new()); } } From fa7e7dfcd2c02bf038deeedaf0d4d1b01411d391 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 24 Aug 2025 09:57:45 -0500 Subject: [PATCH 06/26] fixes #46; only care about caboose crew hours on certain actions, it's presence is enough in other cases. --- TweaksAndThings/Definition.json | 3 +-- .../Extensions/AutoEngineer_Extensions.cs | 6 +++--- TweaksAndThings/Extensions/Car_Extensions.cs | 14 +++++++++++--- ...erOrdersHelper_SendAutoEngineerCommand_Patch.cs | 2 +- .../Patches/CarInspector_PopulateCarPanel_Patch.cs | 4 ++-- .../Patches/CarPickable_Activate_Patch.cs | 2 +- .../Patches/EngineRosterRow_Refresh_Patch.cs | 2 +- ...psController_AnnounceCoalescedPayments_Patch.cs | 8 +++++--- .../Patches/StateManager_OnDayDidChange_Patch.cs | 4 ++-- .../Patches/TagController_UpdateTag_Patch.cs | 2 +- TweaksAndThings/Settings/Settings.cs | 2 +- 11 files changed, 29 insertions(+), 20 deletions(-) diff --git a/TweaksAndThings/Definition.json b/TweaksAndThings/Definition.json index 31fd06d..a29b876 100644 --- a/TweaksAndThings/Definition.json +++ b/TweaksAndThings/Definition.json @@ -21,7 +21,6 @@ "mixintos": { "container:ne-caboose01": "file(mroc-cabeese.json)", "container:ne-caboose02": "file(mroc-cabeese.json)", - "container:ne-caboose03": "file(mroc-cabeese.json)", - "game-graph": "file(tweakedLoadIDS.json)" + "container:ne-caboose03": "file(mroc-cabeese.json)" } } \ No newline at end of file diff --git a/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs b/TweaksAndThings/Extensions/AutoEngineer_Extensions.cs index e96a908..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); @@ -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 @@ -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) diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs index 67a6c2b..21fc3e8 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: false); + + 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); - 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; diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs index 9869789..0f5ac19 100644 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs @@ -72,7 +72,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch Func noCaboose = () => { - var output = TrainController.Shared.SelectedLocomotive.FindMyCaboose(0.0f, false) == null; + bool output = (bool)TrainController.Shared.SelectedLocomotive.FindMyCabooseSansLoadRequirement(); logMessage += $"\ncaboose? {!output}"; return output; }; diff --git a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs index ed52750..7bd4199 100644 --- a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs +++ b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs @@ -227,8 +227,8 @@ 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"; diff --git a/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs b/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs index 32dfc7d..ae1678c 100644 --- a/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs +++ b/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs @@ -62,7 +62,7 @@ internal class CarPickable_Activate_Patch var consist = car.EnumerateCoupled(); bool handbrakesApplied = consist.Any(c => c.HandbrakeApplied()); bool airSystemIssues = consist.Any(c => c.EndAirSystemIssue()); - Func cabooseNear = () => (bool)car.FindMyCaboose(0.0f, false); + 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 diff --git a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs index b49e4b5..4a9883d 100644 --- a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs +++ b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs @@ -52,7 +52,7 @@ internal class EngineRosterRow_Refresh_Patch bool cabooseRequirementFulfilled = !tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || consist.ConsistNoFreight() - || (bool)engineOrTender.FindMyCaboose(0.0f, false); + || (bool)engineOrTender.FindMyCabooseSansLoadRequirement(); float offendingPercentage = 100f; foreach (Car loco in locos) diff --git a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs index 6692686..6243673 100644 --- a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs +++ b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs @@ -24,7 +24,7 @@ internal class CarExtensions_LoadString_Patch { public static bool Prefix(CarLoadInfo info, Load load, ref string __result) { - bool output = load.id == OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad().id; + bool output = load.id == OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.id; if (output) __result = info.Quantity.FormatCrewHours(load.description); return !output; @@ -38,7 +38,7 @@ internal class CarPrototypeLibrary_LoadForId_Patch { public static bool Prefix(string loadId, ref Load __result) { - Load load = OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad(); + Load load = OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours; if (loadId == load.id) __result = load; return __result == null; @@ -70,7 +70,9 @@ internal class OpsController_AnnounceCoalescedPayments_Patch typeof(RepairTrack) }; - public static Load CrewHoursLoad() + private static Load _crewLoadHours; + internal static Load CrewLoadHours => _crewLoadHours ?? CrewHoursLoad(); + private static Load CrewHoursLoad() { Load load = (Load)ScriptableObject.CreateInstance(typeof(Load)); load.name = "crew-hours"; diff --git a/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs b/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs index a760448..ccd0406 100644 --- a/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs +++ b/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs @@ -36,11 +36,11 @@ internal class StateManager_OnDayDidChange_Patch { foreach (var car in TrainController.Shared.Cars.Where(Car_Extensions.IsCaboose)) { - var data = car.QuantityCapacityOfLoad(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad()); + var data = car.QuantityCapacityOfLoad(OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours); if (data.quantity < data.capacity) { Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\""); - new OpsCarAdapter(car, OpsController.Shared).Load(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad(), data.capacity - data.quantity); + new OpsCarAdapter(car, OpsController.Shared).Load(OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours, data.capacity - data.quantity); } } } diff --git a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs index 971dc0d..c349e2b 100644 --- a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs +++ b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs @@ -43,7 +43,7 @@ internal class TagController_UpdateTag_Patch //if (car.EnableOiling) tags.Add(car.HasHotbox ? TextSprites.Hotbox : $"{TextSprites.Warning}{car.Oiled.TriColorPiePercent(1)}"); if (car.EnableOiling) tags.Add(car.HasHotbox ? TextSprites.Hotbox : car.Oiled.TriColorPiePercent(1, oilSpriteName)); IEnumerable consist = car.EnumerateCoupled().Where(c => c.EnableOiling); - Func cabooseRequirementFulfilled = () => (!cabooseRequired || consist.ConsistNoFreight() || car.FindMyCaboose(0.0f, false)); + Func cabooseRequirementFulfilled = () => (!cabooseRequired || consist.ConsistNoFreight() || (bool)car.FindMyCabooseSansLoadRequirement()); if (StateManager.Shared.Storage.OilFeature && car.IsLocomotive && !car.NeedsOiling diff --git a/TweaksAndThings/Settings/Settings.cs b/TweaksAndThings/Settings/Settings.cs index 26ff960..79eae63 100644 --- a/TweaksAndThings/Settings/Settings.cs +++ b/TweaksAndThings/Settings/Settings.cs @@ -129,7 +129,7 @@ public static class SettingsExtensions public static bool RequireConsistCabooseForOilerAndHotboxSpotter(this TweaksAndThingsPlugin input) => input?.settings?.RequireConsistCabooseForOilerAndHotboxSpotter ?? false; public static bool CabooseNonMotiveAllowedSetting(this TweaksAndThingsPlugin input, Car car) => - input.EndGearHelpersRequirePayment() && !car.MotivePower() && (bool)car.FindMyCaboose(0.0f, false); + input.EndGearHelpersRequirePayment() && !car.MotivePower() && (bool)car.FindMyCabooseSansLoadRequirement(); public static bool CabooseRequiredForLocoOilIndicator(this TweaksAndThingsPlugin input) => input?.settings?.CabooseRequiredForLocoTagOilIndication ?? false; public static bool ServiceFundPenalties(this TweaksAndThingsPlugin input) => From 22c6fb8e5eae5ef26e5d829b967c0e0e5bab8367 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 24 Aug 2025 17:35:18 -0500 Subject: [PATCH 07/26] fixes #64; try to recall locomotive vs straight to null, only toggle to null if no prior. --- ...toEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs index 4b8bf7b..560f74b 100644 --- a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -206,7 +206,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch o.Dispose(); } var loco = TrainController.Shared.SelectedLocomotive; - TrainController.Shared.SelectedCar = null; + if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null; _keyChangeObservers.Clear(); recalcing = true; new WaitForSeconds(0.25f); From 2713247895fb4dd73895961eb74d1d0b91aa15e2 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 24 Aug 2025 17:35:55 -0500 Subject: [PATCH 08/26] fixes #48; resurrect the context menu backgrounds! --- ...CarPickable_HandleShowContextMenu_Patch.cs | 9 +- .../Patches/ContextMenu_Show_Patch.cs | 128 ------------------ .../Patches/WedgeImage_OnPopulateMesh.cs | 20 --- 3 files changed, 5 insertions(+), 152 deletions(-) delete mode 100644 TweaksAndThings/Patches/ContextMenu_Show_Patch.cs delete mode 100644 TweaksAndThings/Patches/WedgeImage_OnPopulateMesh.cs diff --git a/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs b/TweaksAndThings/Patches/CarPickable_HandleShowContextMenu_Patch.cs index 0b36b59..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 @@ -52,7 +51,7 @@ internal class CarPickable_HandleShowContextMenu_Patch if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue())) { - shared.AddButton(ContextMenuQuadrant.Unused2, $"Air Up Consist", SpriteName.Select, delegate + shared.AddButton(ContextMenuQuadrant.General, $"Air Up Consist", SpriteName.Select, delegate { CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost); }); @@ -76,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(); - Vector3 mousePosition = Input.mousePosition; - Vector2 val = componentInParent.ScreenToCanvasPosition(mousePosition).XY(); - Vector2 renderingDisplaySize = rootCanvas.renderingDisplaySize; - float num = __instance.radius + 50f; - if (val.x < num) - { - val.x = num; - } - if (val.x > renderingDisplaySize.x - num) - { - val.x = renderingDisplaySize.x - num; - } - if (val.y < num) - { - val.y = num; - } - if (val.y > renderingDisplaySize.y - num) - { - val.y = renderingDisplaySize.y - num; - } - __instance.contentRectTransform.anchoredPosition = val; - __instance.BuildItemAngles(); - - ((MonoBehaviour)__instance).StartCoroutine(__instance.AnimateButtonsShown()); - ((Component)__instance.contentRectTransform).gameObject.SetActive(true); - UI.ContextMenu.ContextMenu.IsShown = true; - __instance._blocker = __instance.CreateBlocker(rootCanvas); - GameInput.RegisterEscapeHandler(GameInput.EscapeHandler.Transient, delegate - { - __instance.Hide(); - return true; - }); - - return false; - } -} - - -[HarmonyPatch(typeof(UI.ContextMenu.ContextMenu))] -[HarmonyPatch(nameof(UI.ContextMenu.ContextMenu.DefaultAngleForItem), typeof(ContextMenuQuadrant), typeof(int), typeof(int))] -[HarmonyPatchCategory("RMROC451TweaksAndThings")] -internal class ContextMenu_DefaultAngleForItem_Patch -{ - static bool Prefix(UI.ContextMenu.ContextMenu __instance, ref float __result, ContextMenuQuadrant quadrant, int index, int quadrantItemCount) - { - TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; - if (!tweaksAndThings.IsEnabled()) return true; - - - int num = quadrant switch - { - ContextMenuQuadrant.General => 0, - ContextMenuQuadrant.Unused1 => 90, - ContextMenuQuadrant.Brakes => 180, - ContextMenuQuadrant.Unused2 => -90, - _ => throw new ArgumentOutOfRangeException("quadrant", quadrant, null), - }; - if (quadrantItemCount <= 1) - { - __result = num; - return false; - } - int num2 = ((quadrantItemCount <= 3) ? 30 : (90 / (quadrantItemCount - 1))); - __result = (float)num + -0.5f * (float)((quadrantItemCount - 1) * num2) + (float)(num2 * index); - return false; - } -} - - -[HarmonyPatch(typeof(UI.ContextMenu.ContextMenu))] -[HarmonyPatch(nameof(UI.ContextMenu.ContextMenu.AddButton), typeof(ContextMenuQuadrant), typeof(string), typeof(Sprite), typeof(Action))] -[HarmonyPatchCategory("RMROC451TweaksAndThings")] -internal class ContextMenu_AddButton_Patch -{ - static bool Prefix(UI.ContextMenu.ContextMenu __instance, ContextMenuQuadrant quadrant, string title, Sprite sprite, Action action) - { - TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; - if (!tweaksAndThings.IsEnabled()) return true; - - - List list = __instance._quadrants[(int)quadrant]; - int index = list.Count; - ContextMenuItem contextMenuItem = UnityEngine.Object.Instantiate(__instance.itemPrefab, (Transform)(object)__instance.contentRectTransform); - contextMenuItem.image.sprite = sprite; - contextMenuItem.label.text = title; - contextMenuItem.OnClick = delegate - { - action(); - __instance.Hide((quadrant, index)); - }; - ((Component)contextMenuItem).gameObject.AddComponent().preferredHeight = 30f; - list.Add(contextMenuItem); - return false; - } -} \ No newline at end of file diff --git a/TweaksAndThings/Patches/WedgeImage_OnPopulateMesh.cs b/TweaksAndThings/Patches/WedgeImage_OnPopulateMesh.cs deleted file mode 100644 index dcc7121..0000000 --- a/TweaksAndThings/Patches/WedgeImage_OnPopulateMesh.cs +++ /dev/null @@ -1,20 +0,0 @@ -using HarmonyLib; -using Railloader; -using UI.ContextMenu; -using UnityEngine.UI; - -namespace RMROC451.TweaksAndThings.Patches; - -[HarmonyPatch(typeof(WedgeImage))] -[HarmonyPatch(nameof(WedgeImage.OnPopulateMesh), typeof(VertexHelper))] -[HarmonyPatchCategory("RMROC451TweaksAndThings")] -internal class WedgeImage_OnPopulateMesh_Patch -{ - private static void Postfix(VertexHelper vh) - { - TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; - if (!tweaksAndThings.IsEnabled()) return; - - vh.Clear(); //clear the image backgrounds for now. - } -} From a9943b6c0c36b8b525cc0e42cf9c01a6ee5279bb Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 24 Aug 2025 17:39:17 -0500 Subject: [PATCH 09/26] incrementing version --- Assembly.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assembly.version b/Assembly.version index a8136ea..5953cd0 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 2 1 - 0 + 1 \ No newline at end of file From af8e1bd6ca2f350123924fdf6c0e88a30b3ddb67 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 24 Aug 2025 20:25:44 -0500 Subject: [PATCH 10/26] Fix issue with inverted boolean and params out of order; version increment --- Assembly.version | 2 +- TweaksAndThings/Extensions/Car_Extensions.cs | 2 +- .../AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Assembly.version b/Assembly.version index 5953cd0..df0e54f 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 2 1 - 1 + 2 \ No newline at end of file diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs index 21fc3e8..4865af5 100644 --- a/TweaksAndThings/Extensions/Car_Extensions.cs +++ b/TweaksAndThings/Extensions/Car_Extensions.cs @@ -108,7 +108,7 @@ public static class Car_Extensions 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 requireLoad, bool decrement = false) { diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs index 0f5ac19..9f84153 100644 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs @@ -72,7 +72,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch Func noCaboose = () => { - bool output = (bool)TrainController.Shared.SelectedLocomotive.FindMyCabooseSansLoadRequirement(); + bool output = TrainController.Shared.SelectedLocomotive.FindMyCabooseSansLoadRequirement() == null; logMessage += $"\ncaboose? {!output}"; return output; }; @@ -87,7 +87,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch logMessage += $"\nGovern AE? {output}"; - _log.Debug(logMessage); + _log.Information(logMessage); return output; } From ec9aadd6e2e97280066128e6fd4a244548582d64 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 25 Aug 2025 08:55:19 -0500 Subject: [PATCH 11/26] refactor for #56; improved caching, observer detection of waybill/repair destination changing debouncing, and coupled car detection fixes. increment version. --- Assembly.version | 2 +- ...Controls_ConfigureOptionsDropdown_Patch.cs | 268 +++++++++++++----- 2 files changed, 194 insertions(+), 76 deletions(-) diff --git a/Assembly.version b/Assembly.version index df0e54f..b9ef449 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 2 1 - 2 + 3 \ No newline at end of file diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs index 560f74b..6833a81 100644 --- a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -4,6 +4,7 @@ using KeyValue.Runtime; using Model; using Model.AI; using Model.Ops; +using Model.Physics; using Network; using Network.Messages; using Railloader; @@ -15,7 +16,6 @@ using Track; using Track.Search; using UI; using UI.EngineControls; -using UnityEngine; using static Track.Search.RouteSearch; using Location = Track.Location; @@ -28,84 +28,51 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch { private static Serilog.ILogger _log => Log.ForContext(); - private static readonly HashSet _keyChangeObservers = new HashSet(); - private static readonly HashSet destinations = new HashSet(); - private static readonly HashSet consist = new HashSet(); + private static readonly HashSet _keyChangeObservers = new(); + private static readonly Dictionary> locoConsistDestinations = new(); + private static readonly HashSet consist = new(); private static string _lastLocoCarId = string.Empty; private static bool recalcing = false; + static string getDictKey(Car car) => car.DisplayName; + static void Postfix(AutoEngineerWaypointControls __instance, ref OptionsDropdownConfiguration __result) { - - _log.Information($"HI BOB"); - foreach(var o in _keyChangeObservers) o.Dispose(); - _keyChangeObservers.Clear(); - destinations.Clear(); - recalcing = false; + PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars); TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; if (!tweaksAndThings.IsEnabled()) return; - //_log.Information($"HI2"); + IterateCarsDetectDestinations( + __instance, + __result, + selectedLoco, + numberOfCars, + out List rowDatas, + out Action func, + out int origCount, + out int maxRowOrig, + out AutoEngineerOrdersHelper aeoh + ); - List rowDatas = __result.Rows.ToList(); - var func = __result.OnRowSelected; - int origCount = rowDatas.Count; - int maxRowOrig = origCount - 1; + List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco); - var selectedLoco = TrainController.Shared.SelectedLocomotive; - if (selectedLoco.id != _lastLocoCarId || !(consist?.Any() ?? false)) - { - _lastLocoCarId = selectedLoco.id; - consist.Clear(); - consist.UnionWith(selectedLoco.EnumerateCoupled()?.ToList() ?? Enumerable.Empty()); - } - var aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco); - List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = new(); + __result = WireUpJumpTosToSettingMenu( + __instance, + selectedLoco, + rowDatas, + func, + origCount, + maxRowOrig, + aeoh, + ref jumpTos + ); + } - foreach(var c in consist) - { - AddObserversToCar(__instance, c); - OpsCarPosition? destination = c.Waybill.HasValue && !c.Waybill.Value.Completed ? c.Waybill.Value.Destination : null; - bool completed = c.Waybill?.Completed ?? false; - if (!destination.HasValue && c.TryGetOverrideDestination(OverrideDestination.Repair, OpsController.Shared, out (OpsCarPosition, string)? result)) destination = result.Value.Item1; - _log.Information($"{c.DisplayName} -> {destination.HasValue}"); - - string destId = destination?.Identifier ?? string.Empty; - - if (destinations.Contains(destId)) continue; - - if (destination.HasValue && !completed) - { - string destName = destination.Value.DisplayName; - float? distance = null; - - if (Graph.Shared.TryGetLocationFromPoint(destination.Value.Spans?.FirstOrDefault().GetSegments().FirstOrDefault(), destination.Value.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; - }; - _log.Information($"{c.DisplayName} -> {destName} {destId} {distance?.ToString()}"); - if (distance.HasValue) - { - destinations.Add(destId); - jumpTos.Add(( - destinationId: destId, - destination: $"WP> {destName}" - , distance: distance - , location: (Location?)destLoc - )); - } - } - } - - jumpTos = jumpTos?.OrderBy(c => c.distance)?.ToList() ?? []; + 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, Location? location)> jumpTos) + { + OptionsDropdownConfiguration __result; + jumpTos = jumpTos?.OrderBy(c => c.distance ?? float.MaxValue)?.ToList() ?? []; + var localJumpTos = jumpTos.ToList(); var safetyFirst = AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies() && jumpTos.Any(); rowDatas.AddRange(jumpTos.Select(j => @@ -119,12 +86,12 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch rowDatas , delegate (int row) { - _log.Information($"{TrainController.Shared.SelectedLocomotive.DisplayName} row {row}/{jumpTos.Count}/{rowDatas.Count}"); + _log.Debug($"{TrainController.Shared.SelectedLocomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}"); if (row <= maxRowOrig) { func(row); } - if (row > maxRowOrig && jumpTos[row - origCount].location.HasValue) + if (row > maxRowOrig && localJumpTos[row - origCount].location.HasValue) { if (safetyFirst) { @@ -132,7 +99,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch return; } float trainMomentum = 0f; - Location end = jumpTos[row - origCount].location.Value; + Location end = localJumpTos[row - origCount].location.Value; Location start = RouteStartLocation(__instance, selectedLoco); HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer; List list = new List(); @@ -142,8 +109,9 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch { RouteSearch.Metrics metrics2; bool flag = Graph.Shared.FindRoute(start, end, autoEngineer, null, out metrics2); - Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, flag ? (selectedLoco.DisplayName + " Train too long to navigate to waypoint.") : (selectedLoco.DisplayName + " Unable to find a path to waypoint."), AlertLevel.Error); - } else + 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); @@ -152,7 +120,118 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch } } ); + return __result; + } + private static List<(string destinationId, string destination, float? distance, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco) + { + List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = new(); + foreach (var ocp in locoConsistDestinations[getDictKey(selectedLoco)].Values.Distinct()) + { + string destName = ocp.DisplayName; + string destId = ocp.Identifier; + float? distance = null; + + 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; + }; + _log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}"); + jumpTos.Add(( + destinationId: destId, + destination: $"WP> {destName}" + , distance: distance + , location: (Location?)destLoc + )); + } + + return jumpTos; + } + + private static void IterateCarsDetectDestinations(AutoEngineerWaypointControls __instance, OptionsDropdownConfiguration __result, BaseLocomotive selectedLoco, int numberOfCars, 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; + if (!locoConsistDestinations.ContainsKey(getDictKey(selectedLoco))) locoConsistDestinations.Add(getDictKey(selectedLoco), new()); + + Dictionary seen = new(); + 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; + while (!stop && (car = selectedLoco.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null) + { + AddObserversToCar(__instance, car); + var ocp = GetCarDestinationIdentifier(car); + _log.Debug($"{getDictKey(selectedLoco)} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}"); + + if (ocp.HasValue) + { + seen.Add(getDictKey(car), ocp.Value); + if (locoConsistDestinations[getDictKey(selectedLoco)].TryGetValue(getDictKey(car), out _)) + locoConsistDestinations[getDictKey(selectedLoco)][getDictKey(car)] = ocp.Value; + else + locoConsistDestinations[getDictKey(selectedLoco)].Add(getDictKey(car), ocp.Value); + } + } + + _log.Debug($"{getDictKey(selectedLoco)} --> [{seen.Keys.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}"); + _log.Debug($"{getDictKey(selectedLoco)} --> [{locoConsistDestinations[getDictKey(selectedLoco)].Keys.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations[getDictKey(selectedLoco)].Select(k => $"{k.Key}:{k.Value.DisplayName}"))}"); + + var dropped = + locoConsistDestinations[getDictKey(selectedLoco)].Keys.Except(seen.Keys).ToDictionary( + t => t, + t => locoConsistDestinations[getDictKey(selectedLoco)][t] + ); //are no longer here + + _log.Debug($"{getDictKey(selectedLoco)} --> [{dropped.Keys.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}"); + + locoConsistDestinations[getDictKey(selectedLoco)] = + locoConsistDestinations[getDictKey(selectedLoco)].Keys.Intersect(seen.Keys).ToDictionary( + t => t, + t => locoConsistDestinations[getDictKey(selectedLoco)][t] + ); //remove ones that are no longer here + seen.Clear(); + } + + private static void PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars) + { + //wire up that loco + selectedLoco = TrainController.Shared.SelectedLocomotive; + numberOfCars = selectedLoco.set.NumberOfCars; + _log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]"); + foreach (var o in _keyChangeObservers) o.Dispose(); + _keyChangeObservers.Clear(); + recalcing = false; + } + + 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) { @@ -197,8 +276,28 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch key, delegate (Value value) { - _log.Information($"{car.DisplayName} OBSV {key}: {value}; recalcing {recalcing}"); if (recalcing) return; + bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key); + string? destId = + waybillChng ? + GetCarDestinationIdentifier(car)?.Identifier ?? null : + null; + if (waybillChng) + { + if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out Dictionary cars) + && cars.TryGetValue(getDictKey(car), out OpsCarPosition pos) + && pos.Identifier == destId) + { + return; + } else + { + _log.Debug($"{getDictKey(car)} OBSV {key}: destNew; {destId}; reload"); + } + } else + { + _log.Debug($"{getDictKey(car)} OBSV {key}: {value}"); + } + try { foreach(var o in _keyChangeObservers) @@ -209,7 +308,6 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null; _keyChangeObservers.Clear(); recalcing = true; - new WaitForSeconds(0.25f); TrainController.Shared.SelectedCar = loco; } catch (Exception ex) @@ -221,4 +319,24 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch ) ); } + + private static Waybill? GetWaybill(Car car, Value waybillValue) + { + Waybill? _waybill = null; + try + { + _waybill = Model.Ops.Waybill.FromPropertyValue(waybillValue, OpsController.Shared); + } + catch (OpsController.InvalidOpsCarPositionException ex) + { + Log.Error(ex, "Waybill for car {car} contains an invalid ops position: {pos}", car, ex.Identifier); + _waybill = null; + } + catch (Exception exception) + { + Log.Warning(exception, "{car} Exception in Waybill.FromPropertyValue", car); + _waybill = null; + } + return _waybill; + } } From 52e9fe1cabbe35f0a435a64dee6942ade2dc3c96 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 25 Aug 2025 17:08:18 -0500 Subject: [PATCH 12/26] additional refactor for #56; fixing AEPlanner spamming recalc of gear when coupling in waypoint mode and reporting stationary endlessly. --- Assembly.version | 2 +- ...ersHelper_SendAutoEngineerCommand_Patch.cs | 2 ++ ...Controls_ConfigureOptionsDropdown_Patch.cs | 20 ++++++++++--------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Assembly.version b/Assembly.version index b9ef449..a99e644 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 2 1 - 3 + 4 \ No newline at end of file diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs index 9f84153..d398bed 100644 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs @@ -86,6 +86,8 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch noCaboose(); logMessage += $"\nGovern AE? {output}"; + if (_log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) + logMessage += $"\n{Environment.StackTrace}"; _log.Information(logMessage); diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs index 6833a81..bb1d42b 100644 --- a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -30,9 +30,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch private static readonly HashSet _keyChangeObservers = new(); private static readonly Dictionary> locoConsistDestinations = new(); - private static readonly HashSet consist = new(); - private static string _lastLocoCarId = string.Empty; - private static bool recalcing = false; + private static string _lastSelectedLoco = string.Empty; static string getDictKey(Car car) => car.DisplayName; @@ -40,7 +38,9 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch { PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars); TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; - if (!tweaksAndThings.IsEnabled()) return; + if (!tweaksAndThings.IsEnabled() || (locoConsistDestinations.TryGetValue(getDictKey(selectedLoco), out Dictionary cars) && cars != null && _lastSelectedLoco == getDictKey(selectedLoco))) return; + + _lastSelectedLoco = getDictKey(selectedLoco); IterateCarsDetectDestinations( __instance, @@ -220,7 +220,6 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch _log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]"); foreach (var o in _keyChangeObservers) o.Dispose(); _keyChangeObservers.Clear(); - recalcing = false; } private static OpsCarPosition? GetCarDestinationIdentifier(Car c) @@ -276,7 +275,11 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch key, delegate (Value value) { - if (recalcing) return; + + _log.Debug($"{getDictKey(car)} OBSV {key}: {value}"); + + if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out Dictionary cars) && cars == null) return; + bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key); string? destId = waybillChng ? @@ -284,8 +287,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch null; if (waybillChng) { - if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out Dictionary cars) - && cars.TryGetValue(getDictKey(car), out OpsCarPosition pos) + if (cars.TryGetValue(getDictKey(car), out OpsCarPosition pos) && pos.Identifier == destId) { return; @@ -307,7 +309,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch var loco = TrainController.Shared.SelectedLocomotive; if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null; _keyChangeObservers.Clear(); - recalcing = true; + if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out _)) locoConsistDestinations[getDictKey(TrainController.Shared.SelectedLocomotive)] = null; TrainController.Shared.SelectedCar = loco; } catch (Exception ex) From afd18b9d27c95b31e81be0b5c0a28cf498d0442e Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Thu, 28 Aug 2025 11:12:07 -0500 Subject: [PATCH 13/26] fixes #68; bugged 120% penalty adjusted to intended 20% --- .../InterchangedIndustryLoader_ServiceInterchange_Patch.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TweaksAndThings/Patches/InterchangedIndustryLoader_ServiceInterchange_Patch.cs b/TweaksAndThings/Patches/InterchangedIndustryLoader_ServiceInterchange_Patch.cs index 14660a5..7c5e0d3 100644 --- a/TweaksAndThings/Patches/InterchangedIndustryLoader_ServiceInterchange_Patch.cs +++ b/TweaksAndThings/Patches/InterchangedIndustryLoader_ServiceInterchange_Patch.cs @@ -48,8 +48,7 @@ internal class InterchangedIndustryLoader_ServiceInterchange_Patch if (num4 > 0) { var canAfford = shared.CanAfford(num4); - penalty = Mathf.CeilToInt(!canAfford ? num4 * 0.2f : 0); - __instance.Industry.ApplyToBalance(-num4, __instance.ledgerCategory, null, 0, quiet: true); + penalty += Mathf.CeilToInt(!canAfford ? num4 * 0.2f : 0); num2 += num4; num3++; From 38a43276f794ce48ce846f8590b1c1eb3ddb3133 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Fri, 29 Aug 2025 09:15:36 -0500 Subject: [PATCH 14/26] fixes #69; track loading wasn't fully topping off. --- .../Patches/OpsController_AnnounceCoalescedPayments_Patch.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs index 6243673..8ac62fd 100644 --- a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs +++ b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs @@ -98,6 +98,7 @@ internal class OpsController_AnnounceCoalescedPayments_Patch var data = car.QuantityCapacityOfLoad(CrewHoursLoad()); if ((data.quantity + quantityToLoad > data.capacity) && data.quantity < data.capacity) { + quantityToLoad = data.capacity; //ensure topping off Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\""); CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, false); } From c114aec3b3979ec2b72811730a533f2a76cbb8c7 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Fri, 29 Aug 2025 09:17:24 -0500 Subject: [PATCH 15/26] fixes #70; forgot to pass boolean after refactor. --- TweaksAndThings/Extensions/Car_Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs index 4865af5..05aa655 100644 --- a/TweaksAndThings/Extensions/Car_Extensions.cs +++ b/TweaksAndThings/Extensions/Car_Extensions.cs @@ -103,7 +103,7 @@ public static class Car_Extensions FindMyCaboose(car, 0f, decrement: false, requireLoad: false); public static Car? FindMyCabooseWithLoadRequirement(this Car car, float timeNeeded, bool decrement) => - FindMyCaboose(car, timeNeeded, decrement, requireLoad: false); + FindMyCaboose(car, timeNeeded, decrement, requireLoad: true); private static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false, bool requireLoad = true) => ( From 554ea117907bdb53f9f537672cbed04d6f3b539a Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sat, 30 Aug 2025 22:38:31 -0500 Subject: [PATCH 16/26] 2.1.5 updates passenger car load info in tags; refactor cog logic again; formatting updates for webhook messages --- TweaksAndThings/Commands/CrewUpdateCommand.cs | 14 +- ...ersHelper_SendAutoEngineerCommand_Patch.cs | 4 +- ...Controls_ConfigureOptionsDropdown_Patch.cs | 277 +++++++++++------- .../Patches/ExpandedConsole_Patch.cs | 86 ++++-- .../Patches/TagController_UpdateTag_Patch.cs | 16 + 5 files changed, 273 insertions(+), 124 deletions(-) diff --git a/TweaksAndThings/Commands/CrewUpdateCommand.cs b/TweaksAndThings/Commands/CrewUpdateCommand.cs index f2cffdc..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 ?? "???")); - if (StateManager.IsHost) car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}"); - ExpandedConsole_Add_Patch.SendMs(null, $"{Hyperlink.To(car)} {message}"); - if (!StateManager.IsHost) 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/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs index d398bed..ec56c13 100644 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs @@ -86,10 +86,10 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch noCaboose(); logMessage += $"\nGovern AE? {output}"; - if (_log.IsEnabled(Serilog.Events.LogEventLevel.Debug)) + if (_log.IsEnabled(Serilog.Events.LogEventLevel.Verbose)) logMessage += $"\n{Environment.StackTrace}"; - _log.Information(logMessage); + _log.Debug(logMessage); return output; } diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs index bb1d42b..f675d37 100644 --- a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -1,9 +1,13 @@ -using Game.State; +using GalaSoft.MvvmLight.Messaging; +using Game.Events; +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; @@ -27,51 +31,93 @@ namespace RMROC451.TweaksAndThings.Patches; internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch { private static Serilog.ILogger _log => Log.ForContext(); - - private static readonly HashSet _keyChangeObservers = new(); - private static readonly Dictionary> locoConsistDestinations = new(); - private static string _lastSelectedLoco = string.Empty; - + private static readonly HashSet _keyChangeObservers = []; + private static HashSet 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) { - PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars); - TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; - if (!tweaksAndThings.IsEnabled() || (locoConsistDestinations.TryGetValue(getDictKey(selectedLoco), out Dictionary cars) && cars != null && _lastSelectedLoco == getDictKey(selectedLoco))) return; + try + { + PrepLocoUsage(__instance, out BaseLocomotive selectedLoco, out int numberOfCars); + TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(selectedLoco)) return; - _lastSelectedLoco = getDictKey(selectedLoco); + timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified; + Messenger.Default.Unregister(placeholder); - IterateCarsDetectDestinations( - __instance, - __result, - selectedLoco, - numberOfCars, - out List rowDatas, - out Action func, - out int origCount, - out int maxRowOrig, - out AutoEngineerOrdersHelper aeoh - ); + 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); + }); - List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco); + IterateCarsDetectDestinations( + __instance, + __result, + selectedLoco, + numberOfCars, + out List rowDatas, + out Action func, + out int origCount, + out int maxRowOrig, + out AutoEngineerOrdersHelper aeoh + ); - __result = WireUpJumpTosToSettingMenu( - __instance, - selectedLoco, - rowDatas, - func, - origCount, - maxRowOrig, - aeoh, - ref jumpTos - ); + List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco); + + __result = WireUpJumpTosToSettingMenu( + __instance, + selectedLoco, + rowDatas, + func, + origCount, + maxRowOrig, + 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."); + } } - 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, Location? location)> jumpTos) + private static bool ShouldRecalc(BaseLocomotive selectedLoco) + { + bool output = false; + string locoKey = getDictKey(selectedLoco); + List consist = new List(); + consist = selectedLoco.EnumerateCoupled().ToList(); + HashSet destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet(); + + output |= !locoConsistDestinations.Equals(destinations); + output |= !(_keyChangeObservers?.Any() ?? false); + output |= selectedLoco.TryGetTimetableTrain(out _) && TimetableController.Shared.CurrentDocument.Modified != timetableSaveTime; + + 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.distance ?? float.MaxValue)?.ToList() ?? []; + jumpTos = jumpTos?.OrderBy(c => c.sortDistance)?.ToList() ?? default; var localJumpTos = jumpTos.ToList(); var safetyFirst = AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies() && jumpTos.Any(); @@ -86,7 +132,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch rowDatas , delegate (int row) { - _log.Debug($"{TrainController.Shared.SelectedLocomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}"); + _log.Debug($"{__instance.Locomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}"); if (row <= maxRowOrig) { func(row); @@ -123,15 +169,15 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch return __result; } - private static List<(string destinationId, string destination, float? distance, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco) + 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, Location? location)> jumpTos = new(); - foreach (var ocp in locoConsistDestinations[getDictKey(selectedLoco)].Values.Distinct()) + 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(), @@ -149,16 +195,72 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch 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; } @@ -168,54 +270,42 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch func = __result.OnRowSelected; origCount = rowDatas.Count; maxRowOrig = origCount - 1; - if (!locoConsistDestinations.ContainsKey(getDictKey(selectedLoco))) locoConsistDestinations.Add(getDictKey(selectedLoco), new()); - - Dictionary seen = new(); + locoConsistDestinations = []; + HashSet 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; + 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($"{getDictKey(selectedLoco)} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}"); + _log.Debug($"{locoKey} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}"); if (ocp.HasValue) { - seen.Add(getDictKey(car), ocp.Value); - if (locoConsistDestinations[getDictKey(selectedLoco)].TryGetValue(getDictKey(car), out _)) - locoConsistDestinations[getDictKey(selectedLoco)][getDictKey(car)] = ocp.Value; - else - locoConsistDestinations[getDictKey(selectedLoco)].Add(getDictKey(car), ocp.Value); + seen.Add(ocp.Value); + locoConsistDestinations.Add(ocp.Value); } } - _log.Debug($"{getDictKey(selectedLoco)} --> [{seen.Keys.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}"); - _log.Debug($"{getDictKey(selectedLoco)} --> [{locoConsistDestinations[getDictKey(selectedLoco)].Keys.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations[getDictKey(selectedLoco)].Select(k => $"{k.Key}:{k.Value.DisplayName}"))}"); - var dropped = - locoConsistDestinations[getDictKey(selectedLoco)].Keys.Except(seen.Keys).ToDictionary( - t => t, - t => locoConsistDestinations[getDictKey(selectedLoco)][t] - ); //are no longer here + locoConsistDestinations.Except(seen).ToHashSet(); //are no longer here - _log.Debug($"{getDictKey(selectedLoco)} --> [{dropped.Keys.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}"); - - locoConsistDestinations[getDictKey(selectedLoco)] = - locoConsistDestinations[getDictKey(selectedLoco)].Keys.Intersect(seen.Keys).ToDictionary( - t => t, - t => locoConsistDestinations[getDictKey(selectedLoco)][t] - ); //remove ones that are no longer here + _log.Debug($"{locoKey} --> [{seen.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.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(); } - private static void PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars) + private static void PrepLocoUsage(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars) { //wire up that loco - selectedLoco = TrainController.Shared.SelectedLocomotive; + selectedLoco = __instance.Locomotive; numberOfCars = selectedLoco.set.NumberOfCars; _log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]"); foreach (var o in _keyChangeObservers) o.Dispose(); @@ -262,6 +352,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch { 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); @@ -278,67 +369,53 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch _log.Debug($"{getDictKey(car)} OBSV {key}: {value}"); - if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out Dictionary cars) && cars == null) return; + if (!(locoConsistDestinations?.Any() ?? false)) return; bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key); - string? destId = - waybillChng ? - GetCarDestinationIdentifier(car)?.Identifier ?? null : + string? destId = + waybillChng ? + GetCarDestinationIdentifier(car)?.Identifier ?? null : null; if (waybillChng) { - if (cars.TryGetValue(getDictKey(car), out OpsCarPosition pos) - && pos.Identifier == destId) + if (locoConsistDestinations.Contains(GetCarDestinationIdentifier(car))) { return; - } else + } + else { _log.Debug($"{getDictKey(car)} OBSV {key}: destNew; {destId}; reload"); } - } else + } + else { _log.Debug($"{getDictKey(car)} OBSV {key}: {value}"); } - try - { - foreach(var o in _keyChangeObservers) - { - o.Dispose(); - } - var loco = TrainController.Shared.SelectedLocomotive; - if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null; - _keyChangeObservers.Clear(); - if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out _)) locoConsistDestinations[getDictKey(TrainController.Shared.SelectedLocomotive)] = null; - TrainController.Shared.SelectedCar = loco; - } - catch (Exception ex) - { - _log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}"); - } + Rebuild(__instance, car, key); }, false ) ); } - private static Waybill? GetWaybill(Car car, Value waybillValue) + private static void Rebuild(AutoEngineerWaypointControls __instance, Car car, string key) { - Waybill? _waybill = null; try { - _waybill = Model.Ops.Waybill.FromPropertyValue(waybillValue, OpsController.Shared); + 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 (OpsController.InvalidOpsCarPositionException ex) + catch (Exception ex) { - Log.Error(ex, "Waybill for car {car} contains an invalid ops position: {pos}", car, ex.Identifier); - _waybill = null; + _log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}"); } - catch (Exception exception) - { - Log.Warning(exception, "{car} Exception in Waybill.FromPropertyValue", car); - _waybill = null; - } - return _waybill; } } diff --git a/TweaksAndThings/Patches/ExpandedConsole_Patch.cs b/TweaksAndThings/Patches/ExpandedConsole_Patch.cs index 50b2461..19fa469 100644 --- a/TweaksAndThings/Patches/ExpandedConsole_Patch.cs +++ b/TweaksAndThings/Patches/ExpandedConsole_Patch.cs @@ -1,7 +1,8 @@ using Game; using Game.State; using HarmonyLib; -using Helpers; +using Model; +using Model.Ops.Timetable; using Newtonsoft.Json; using Railloader; using Serilog; @@ -21,6 +22,7 @@ namespace RMROC451.TweaksAndThings.Patches; [HarmonyPatchCategory("RMROC451TweaksAndThings")] internal class ExpandedConsole_Add_Patch { + private static Serilog.ILogger _log => Log.ForContext(); private static void Prefix(ref UI.Console.Console.Entry entry) { entry.Text = $"{entry.Timestamp} : {entry.Text}"; @@ -28,12 +30,26 @@ internal class ExpandedConsole_Add_Patch SendMs((UI.Console.Console.Entry?)entry); } + + private static string hold => @"```ansi +■{loco}■ {msg} +```"; + + private static string west => @"```ansi +◀{loco}  {msg} +```"; + + private static string east => @"```ansi + {loco}▶ {msg} +```"; + internal static void SendMs(UI.Console.Console.Entry? entry, string? text = null) { try { if (entry is null && !String.IsNullOrEmpty(text)) entry = new() { Text = text }; var msgText = entry?.Text ?? string.Empty; + if (msgText.StartsWith("Usage:")) return; TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; StateManager shared = StateManager.Shared; GameStorage gameStorage = shared.Storage; @@ -50,31 +66,45 @@ internal class ExpandedConsole_Add_Patch var carId = t.IsMatch(msgText) ? Regex.Match(msgText, "car:(.*?)\"").Groups[1].Captures[0].ToString() : string.Empty; Model.Car? car = TrainController.Shared.CarForString(carId); + var data = UpdateCarText(car); bool engineInMessage = car?.IsLocomotive ?? false; - var image = engineInMessage ? - new - { - url = string.Empty - } : - null; + string msgToSend = string.Empty; + string desc = Regex.Replace(msgText, "<.*?>", ""); + desc = !!car ? desc.Replace(car?.DisplayName ?? string.Empty, string.Empty) : desc; + desc = desc.Trim();//.Replace(": ", "\n"); - if (engineInMessage) + if (!!car && engineInMessage) { CTCPanelMarkerManager cTCPanelMarkerManager = UnityEngine.Object.FindObjectOfType(); CTCPanelMarker marker = cTCPanelMarkerManager?._markers?.Values?.FirstOrDefault(v => v.TooltipInfo.Text.Contains(car.Ident.RoadNumber)); - - string color = CTCPanelMarker.InferColorFromText(car?.DisplayName).HexString().Replace("#", string.Empty); - if (marker != null) + + string markerText = marker?.TooltipInfo.Text ?? string.Empty; + if (markerText.StartsWith(">") || markerText.EndsWith(">") || data.Item2 == Timetable.Direction.East || msgText.Contains("*-")) { - color = CTCPanelMarker.InferColorFromText(marker.TooltipInfo.Text).HexString().Replace("#", string.Empty); + msgToSend = east.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc); } - image = new + else if (markerText.StartsWith("<") || markerText.EndsWith("<") || data.Item2 == Timetable.Direction.West || msgText.Contains("-*")) { - url = $"https://img.shields.io/badge/{car.DisplayName.Replace(" ", "%20")}-%20-{color}.png" - }; - + msgToSend = west.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc); + } + else + { + msgToSend = hold.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc); + } } + else + { + msgToSend = desc; + } + msgToSend = msgToSend + .Replace(" , ", ", ") + .Replace(", :", " :") + .Replace(" ; ", "; ") + .Replace("*-", string.Empty) + .Replace("#", string.Empty) + .Replace("-*", string.Empty) + .Trim(); var SuccessWebHook = new { @@ -83,9 +113,7 @@ internal class ExpandedConsole_Add_Patch { new { - description= Regex.Replace(msgText, "<.*?>", "").Replace(": ", "\n"), - timestamp=DateTime.UtcNow, - image + description= msgToSend }, } }; @@ -103,6 +131,26 @@ internal class ExpandedConsole_Add_Patch } } + public static (string , Timetable.Direction?) UpdateCarText(Car car) + { + string output = string.Empty; + Timetable.Direction? dir = null; + if (car?.IsLocomotive ?? false) + { + if (StateManager.Shared.PlayersManager.TrainCrewForId(car.trainCrewId, out var trainCrew)) + { + output = trainCrew.Name; + if (TimetableController.Shared.TryGetTrainForTrainCrew(trainCrew, out Timetable.Train timetableTrain)) + { + dir = timetableTrain.Direction; + output += " (Train " + timetableTrain.DisplayStringShort + ")"; + } + } + } + return (output, dir); + } + + public static GameDateTime RealNow() { var now = DateTime.Now; diff --git a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs index c349e2b..dc46cd5 100644 --- a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs +++ b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs @@ -1,13 +1,17 @@ using Game.State; using HarmonyLib; +using KeyValue.Runtime; using Model; using Model.Ops; using Railloader; using RMROC451.TweaksAndThings.Extensions; +using RollingStock; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using UI.Tags; +using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics; namespace RMROC451.TweaksAndThings.Patches; @@ -54,6 +58,18 @@ internal class TagController_UpdateTag_Patch if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills); if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel); + if (car.IsPassengerCar()) + { + PassengerMarker? passengerMarker = car.GetPassengerMarker(); + if (passengerMarker.HasValue) + { + IEnumerable loadInfo = car.PassengerCountString(passengerMarker).Split('/'); + //string item4 = CarPickable.PassengerString(car, passengerMarker.Value); + string val = TextSprites.PiePercent(float.Parse(loadInfo.First()), float.Parse(loadInfo.Last())) + $" {car.PassengerCountString(passengerMarker)} Passengers"; + tagCallout.callout.Text = tagCallout.callout.Text.Contains("Empty") ? tagCallout.callout.Text.Replace("Empty", val) : tagCallout.callout.Text + $"\n{val}"; + } + } + tagCallout.callout.Title = tags.Any() switch { From bd215d0dcc696cebeac790192b7085522eb6ef7b Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 31 Aug 2025 18:32:48 -0500 Subject: [PATCH 17/26] coroutine vs observers --- ...Controls_ConfigureOptionsDropdown_Patch.cs | 250 +++++++----------- TweaksAndThings/TweaksAndThingsPlugin.cs | 6 - 2 files changed, 99 insertions(+), 157 deletions(-) diff --git a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs index f675d37..8dd2ec8 100644 --- a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -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(); - private static readonly HashSet _keyChangeObservers = []; + 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 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.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.Shared; + WaitForSecondsRealtime wait = new WaitForSecondsRealtime(3f); + + while (true) + { + if (__instance._persistence.Orders.Mode != AutoEngineerMode.Waypoint) 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; - 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 rowDatas, out Action 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 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(BaseLocomotive selectedLoco) + 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(); - HashSet 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; } @@ -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, @@ -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,42 +282,33 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch return jumpTos; } - private static void IterateCarsDetectDestinations(AutoEngineerWaypointControls __instance, OptionsDropdownConfiguration __result, BaseLocomotive selectedLoco, int numberOfCars, out List rowDatas, out Action func, out int origCount, out int maxRowOrig, out AutoEngineerOrdersHelper aeoh) + 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; - locoConsistDestinations = []; - HashSet 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) @@ -307,9 +316,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch //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(); + _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; } } diff --git a/TweaksAndThings/TweaksAndThingsPlugin.cs b/TweaksAndThings/TweaksAndThingsPlugin.cs index 1cf6011..c5b9363 100644 --- a/TweaksAndThings/TweaksAndThingsPlugin.cs +++ b/TweaksAndThings/TweaksAndThingsPlugin.cs @@ -40,7 +40,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, static TweaksAndThingsPlugin() { - Log.Debug("Hello! Static Constructor was called!"); } public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self) @@ -58,7 +57,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, 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, 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(); @@ -347,7 +342,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()); } } From c0e75c3c3914a148d3c39cbf837522cb55122eef Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Sun, 31 Aug 2025 18:33:06 -0500 Subject: [PATCH 18/26] version++ --- Assembly.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assembly.version b/Assembly.version index a99e644..6295c4d 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 2 1 - 4 + 6 \ No newline at end of file From 1032edb7efeeda448eb094e7419d7e8e4075f199 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 1 Sep 2025 12:50:50 -0500 Subject: [PATCH 19/26] switch roster MU hiding to be based on cutout not MU --- TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs b/TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs index ccb5389..d9bc633 100644 --- a/TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs +++ b/TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs @@ -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(); + var hiddenEntries = rows.Where(r => r.Engine.locomotiveControl.air.IsCutOut && !r.IsSelected && !r.IsFavorite).Select(r => r.Engine.id) ?? Enumerable.Empty(); if (hiddenEntries.Any()) __instance._window.Title =string.Format("{0} : {1}", __instance._window.Title, $"Hidden MU Count [{hiddenEntries.Count()}]"); From 1dadb04cbee8ee857629f6a513f13ac7682a93a3 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 1 Sep 2025 12:51:29 -0500 Subject: [PATCH 20/26] round to whole number vs showing partial full mph in car inspector consist info (when caboose is nearby) --- TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs index 7bd4199..235dd2f 100644 --- a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs +++ b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs @@ -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(); From cc18579507b3b839476e3e688fc394035275a719 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 1 Sep 2025 12:53:52 -0500 Subject: [PATCH 21/26] host now can assert safety first; setting added to allow clients to override. Client does not require mod for this to be enforced! --- ...rControlSetBase_UpdateStatusLabel_Patch.cs | 2 +- ...ersHelper_SendAutoEngineerCommand_Patch.cs | 96 --------------- ...AutoEngineerPlanner_HandleCommand_Patch.cs | 114 ++++++++++++++++++ ...Controls_ConfigureOptionsDropdown_Patch.cs | 18 +-- TweaksAndThings/Settings/Settings.cs | 5 + TweaksAndThings/TweaksAndThingsPlugin.cs | 17 +++ 6 files changed, 146 insertions(+), 106 deletions(-) delete mode 100644 TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs create mode 100644 TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs 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/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs deleted file mode 100644 index ec56c13..0000000 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.cs +++ /dev/null @@ -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(); - - 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.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.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 = () => - { - 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; - } -} diff --git a/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs new file mode 100644 index 0000000..a750b54 --- /dev/null +++ b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs @@ -0,0 +1,114 @@ +using Game; +using Game.Messages; +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 UI.EngineControls; +using UI.EngineRoster; +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; + if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst() || (sender.IsRemote && !tweaksAndThings.SafetyFirstClientEnforce()) || command.MaxSpeedMph <= governedSpeed) return true; + BaseLocomotive loco = TrainController.Shared.SelectedLocomotive; + if (TrainController.Shared.TryGetCarForId(command.LocomotiveId, out Car c)) loco = (BaseLocomotive)c; + + 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; + } + + 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 index 8dd2ec8..6737d87 100644 --- a/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch.cs @@ -61,7 +61,7 @@ internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix() while (true) { - if (__instance._persistence.Orders.Mode != AutoEngineerMode.Waypoint) yield return wait; + 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 = []; @@ -117,14 +117,14 @@ internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix() //_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}"); + //_log.Information($"{locoKey} 1-> {output}"); if (output) lastSeenIntegrationSetCount = default; output |= lastSeenIntegrationSetCount != selectedLoco.set.NumberOfCars; - _log.Information($"{locoKey} 2-> {output}"); + //_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}"); + //_log.Information($"{locoKey} 3-> {output}"); return output; } @@ -134,7 +134,7 @@ internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix() 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( @@ -227,11 +227,11 @@ internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix() 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 @@ -315,8 +315,8 @@ internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix() { //wire up that loco selectedLoco = __instance.Locomotive; - numberOfCars = selectedLoco.set.NumberOfCars; - _log.Debug($"{selectedLoco.id} --> HI BOB[{numberOfCars}]"); + numberOfCars = selectedLoco?.set.NumberOfCars ?? -1; + _log.Debug($"{selectedLoco?.id} --> HI BOB[{numberOfCars}]"); } private static OpsCarPosition? GetCarDestinationIdentifier(Car c) diff --git a/TweaksAndThings/Settings/Settings.cs b/TweaksAndThings/Settings/Settings.cs index 79eae63..c8e93af 100644 --- a/TweaksAndThings/Settings/Settings.cs +++ b/TweaksAndThings/Settings/Settings.cs @@ -28,6 +28,7 @@ public class Settings bool cabooseRequiredForLocoTagOilIndication, bool servicingFundPenalty, bool safetyFirst, + bool safetyFirstClientEnforce, CrewHourLoadMethod loadCrewHoursMethod, float cabeeseSearchRadiusFtInMeters, bool trainBrakeDisplayShowsColorsInCalloutMode @@ -42,6 +43,7 @@ public class Settings CabooseRequiredForLocoTagOilIndication = cabooseRequiredForLocoTagOilIndication; ServicingFundPenalty = servicingFundPenalty; SafetyFirst = safetyFirst; + SafetyFirstClientEnforce = safetyFirstClientEnforce; LoadCrewHoursMethod = loadCrewHoursMethod; CabeeseSearchRadiusFtInMeters = cabeeseSearchRadiusFtInMeters; TrainBrakeDisplayShowsColorsInCalloutMode = trainBrakeDisplayShowsColorsInCalloutMode; @@ -57,6 +59,7 @@ public class Settings public bool CabooseRequiredForLocoTagOilIndication; public bool ServicingFundPenalty; public bool SafetyFirst; + public bool SafetyFirstClientEnforce; public CrewHourLoadMethod LoadCrewHoursMethod; public float CabeeseSearchRadiusFtInMeters; public bool TrainBrakeDisplayShowsColorsInCalloutMode; @@ -136,6 +139,8 @@ 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) => diff --git a/TweaksAndThings/TweaksAndThingsPlugin.cs b/TweaksAndThings/TweaksAndThingsPlugin.cs index c5b9363..e7382f5 100644 --- a/TweaksAndThings/TweaksAndThingsPlugin.cs +++ b/TweaksAndThings/TweaksAndThingsPlugin.cs @@ -203,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) From b911ee6f6f68a4811f20747c37c84c7e4f82684a Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 1 Sep 2025 12:54:16 -0500 Subject: [PATCH 22/26] version -> 2.1.7 --- Assembly.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assembly.version b/Assembly.version index 6295c4d..8829fb5 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 2 1 - 6 + 7 \ No newline at end of file From f1b68d28276b84d9b961e3610723be235eb01917 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Mon, 1 Sep 2025 23:53:23 -0500 Subject: [PATCH 23/26] one more of the ill fated traincontroller shared usages while on host processing. --- .../Patches/AutoEngineerPlanner_HandleCommand_Patch.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs index a750b54..8d2f982 100644 --- a/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs @@ -34,8 +34,7 @@ internal class AutoEngineerPlanner_HandleCommand_Patch { TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst() || (sender.IsRemote && !tweaksAndThings.SafetyFirstClientEnforce()) || command.MaxSpeedMph <= governedSpeed) return true; - BaseLocomotive loco = TrainController.Shared.SelectedLocomotive; - if (TrainController.Shared.TryGetCarForId(command.LocomotiveId, out Car c)) loco = (BaseLocomotive)c; + BaseLocomotive loco = __instance._locomotive; if (SafetyFirstGoverningApplies(loco)) { From b562424f8b6875cbdf221126eb47e4a88dd25e49 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Tue, 2 Sep 2025 09:42:14 -0500 Subject: [PATCH 24/26] move loco post notice about WP set to host, so clients get them --- ...oEngineerOrdersHelper_SetWaypoint_patch.cs | 31 ------------ ...AutoEngineerPlanner_HandleCommand_Patch.cs | 50 ++++++++++++++++++- 2 files changed, 48 insertions(+), 33 deletions(-) delete mode 100644 TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs diff --git a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs b/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs deleted file mode 100644 index e270df8..0000000 --- a/TweaksAndThings/Patches/AutoEngineerOrdersHelper_SetWaypoint_patch.cs +++ /dev/null @@ -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(); - 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")); - } - } -} diff --git a/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs index 8d2f982..fa3edbc 100644 --- a/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerPlanner_HandleCommand_Patch.cs @@ -1,5 +1,6 @@ using Game; using Game.Messages; +using Game.Notices; using Game.State; using HarmonyLib; using Model; @@ -15,8 +16,10 @@ 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; @@ -33,6 +36,7 @@ internal class AutoEngineerPlanner_HandleCommand_Patch 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; @@ -49,9 +53,10 @@ internal class AutoEngineerPlanner_HandleCommand_Patch 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 + } + else { message = string.Format(message, " "); } @@ -62,6 +67,47 @@ internal class AutoEngineerPlanner_HandleCommand_Patch 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); From a7ae1d479cc45609e54ea32a001d9a5ee5410841 Mon Sep 17 00:00:00 2001 From: Ryan Mroczenski Date: Tue, 2 Sep 2025 10:04:58 -0500 Subject: [PATCH 25/26] 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). --- ...utoEngineerDestinationPicker_Loop_Patch.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs diff --git a/TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs b/TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs new file mode 100644 index 0000000..1594772 --- /dev/null +++ b/TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs @@ -0,0 +1,62 @@ +using HarmonyLib; +using Helpers; +using Model.AI; +using Railloader; +using Serilog; +using System.Collections; +using Track; +using UI; +using UnityEngine; +using static Game.Messages.RequestOps; +using static UI.AutoEngineerDestinationPicker; + +namespace RMROC451.TweaksAndThings; + +[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(.1f); + 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(); + } +} From 32f3e8b2db52c1867de3dfac6e958e5e56a4df48 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Tue, 2 Sep 2025 22:19:41 -0500 Subject: [PATCH 26/26] tweaks to engine roster to show who has engines selected and throttling on AE Waypoint picker to 60fps. --- ...utoEngineerDestinationPicker_Loop_Patch.cs | 6 +-- .../Patches/EngineRosterRow_Refresh_Patch.cs | 48 ++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) rename TweaksAndThings/{ => Patches}/AutoEngineerDestinationPicker_Loop_Patch.cs (95%) diff --git a/TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs b/TweaksAndThings/Patches/AutoEngineerDestinationPicker_Loop_Patch.cs similarity index 95% rename from TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs rename to TweaksAndThings/Patches/AutoEngineerDestinationPicker_Loop_Patch.cs index 1594772..8b260f1 100644 --- a/TweaksAndThings/AutoEngineerDestinationPicker_Loop_Patch.cs +++ b/TweaksAndThings/Patches/AutoEngineerDestinationPicker_Loop_Patch.cs @@ -1,16 +1,14 @@ using HarmonyLib; using Helpers; -using Model.AI; using Railloader; using Serilog; using System.Collections; using Track; using UI; using UnityEngine; -using static Game.Messages.RequestOps; using static UI.AutoEngineerDestinationPicker; -namespace RMROC451.TweaksAndThings; +namespace RMROC451.TweaksAndThings.Patches; [HarmonyPatch(typeof(AutoEngineerDestinationPicker))] [HarmonyPatch(nameof(AutoEngineerDestinationPicker.Loop))] @@ -31,7 +29,7 @@ internal class AutoEngineerDestinationPicker_Loop_Patch { Hit valueOrDefault; Location location; - WaitForSecondsRealtime wait = new WaitForSecondsRealtime(.1f); + WaitForSecondsRealtime wait = new WaitForSecondsRealtime(1/60); while (true) { Location? currentOrdersGotoLocation = __instance.GetCurrentOrdersGotoLocation(); diff --git a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs index 4a9883d..3e48c14 100644 --- a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs +++ b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs @@ -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 = $"{str} "; + __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 dictionary = StateManager.Shared.PlayersManager.AllPlayers.ToDictionary((IPlayer p) => p.PlayerId, (IPlayer p) => p); + List 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)}{selectedCount}", $"{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}";