diff --git a/Assembly.version b/Assembly.version index 691d1be..881bd75 100644 --- a/Assembly.version +++ b/Assembly.version @@ -2,6 +2,6 @@ 1 2 - 6 + 7 \ No newline at end of file diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs index 2fb2a63..bce43ce 100644 --- a/TweaksAndThings/Extensions/Car_Extensions.cs +++ b/TweaksAndThings/Extensions/Car_Extensions.cs @@ -2,12 +2,14 @@ using Game.State; using Helpers; using Model; +using Model.Definition; using Model.Definition.Data; using Model.Ops; using Serilog; using System; using System.Collections.Generic; using System.Linq; +using System.Security.AccessControl; using UnityEngine; namespace RMROC451.TweaksAndThings.Extensions; @@ -62,10 +64,16 @@ public static class Car_Extensions public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose; - public static bool IsCabooseAndStoppedForLoadRefresh(this Car car) => car.IsCaboose() && car.IsStopped(30f); + public static bool IsCabooseAndStoppedForLoadRefresh(this Car car, bool isFull) => car.IsCaboose() && car.IsStopped(30f) && !isFull; public static Car? CabooseInConsist(this IEnumerable input) => input.FirstOrDefault(c => c.IsCaboose()); + public static bool ConsistNoFreight(this IEnumerable input) => + input.Where(c => !c.IsLocomotive).Any() && + input + .Where(c => !c.IsLocomotive) + .All(c => !c.Archetype.IsFreight()); + public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, HashSet carIdsCheckedAlready, bool decrement = false) { Car? output = null; diff --git a/TweaksAndThings/Extensions/TextSprite_Extensions.cs b/TweaksAndThings/Extensions/TextSprite_Extensions.cs new file mode 100644 index 0000000..7e58bc0 --- /dev/null +++ b/TweaksAndThings/Extensions/TextSprite_Extensions.cs @@ -0,0 +1,34 @@ +using RMROC451.TweaksAndThings.Patches; +using Serilog; +using UnityEngine; +using ILogger = Serilog.ILogger; + +namespace RMROC451.TweaksAndThings.Extensions; + +public static class TextSprite_Extensions +{ + public static string TriColorPiePercent(this float quantity, float capacity) + { + int num; + if (capacity <= 0f) + { + num = 0; + } + else + { + float num2 = Mathf.Clamp01(quantity / capacity); + int num3 = ((!(num2 < 0.01f)) ? ((!(num2 > 0.99f)) ? (Mathf.FloorToInt(num2 * 15f) + 1) : 16) : 0); + num = num3; + } + string color = "#219106"; //Green + if (num > 5 && num <= 10) + { + color = "#CE8326"; //orange + } else if (num <= 5) + { + color = "#D53427"; //Red + } + + return $""; + } +} diff --git a/TweaksAndThings/Patches/BindingsWindow_Patches.cs b/TweaksAndThings/Patches/BindingsWindow_Patches.cs new file mode 100644 index 0000000..dbc28c7 --- /dev/null +++ b/TweaksAndThings/Patches/BindingsWindow_Patches.cs @@ -0,0 +1,52 @@ +using HarmonyLib; +using Railloader; +using System.Collections.Generic; +using System.Linq; +using TMPro; +using UI.Builder; +using UI.PreferencesWindow; +using UnityEngine.InputSystem; + + +namespace RMROC451.TweaksAndThings.Patches; + +[HarmonyPatch(typeof(BindingsWindow))] +[HarmonyPatch(nameof(BindingsWindow.Build), typeof(UIPanelBuilder))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class BindingsWindow_Build_Patch +{ + public static bool Prefix(BindingsWindow __instance, UIPanelBuilder builder) + { + return true; + //TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + //if (!tweaksAndThings.IsEnabled) return true; + + //(string title, InputAction[] actions)[] rebindableActions = BindingsWindow.RebindableActions; + + + + //HashSet conflicts = BindingsWindow.FindConflicts(rebindableActions.SelectMany(((string title, InputAction[] actions) t) => t.actions)); + //__instance._conflicts = conflicts; + //__instance._builder = builder; + //builder.AddTabbedPanels(__instance._selectedTabState, delegate (UITabbedPanelBuilder uITabbedPanelBuilder) + //{ + // (string, InputAction[])[] array = rebindableActions; + // for (int i = 0; i < array.Length; i++) + // { + // var (text, actions) = array[i]; + // uITabbedPanelBuilder.AddTab(text, text, delegate (UIPanelBuilder uIPanelBuilder) + // { + // uIPanelBuilder.VScrollView(delegate (UIPanelBuilder uIPanelBuilder2) + // { + // InputAction[] array2 = actions; + // foreach (InputAction val in array2) + // { + // //uIPanelBuilder2.AddInputBindingControl(val, conflicts.Contains(val), DidRebind); + // } + // }); + // }); + // } + //}); + //return false; + } +} diff --git a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs index 87512e8..341a70a 100644 --- a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs +++ b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs @@ -173,20 +173,7 @@ internal class CarInspector_PopulateCarPanel_Patch case MrocHelperType.GladhandAndAnglecock: consist.Do(c => - ends.Do(end => - { - EndGear endGear = c[end]; - - StateManager.ApplyLocal( - new PropertyChange( - c.id, - KeyValueKeyFor(EndGearStateKey.Anglecock, c.LogicalToEnd(end)), - new FloatPropertyValue(endGear.IsCoupled ? 1f : 0f) - ) - ); - - if (c.TryGetAdjacentCar(end, out Model.Car c2)) StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true)); - }) + CarEndAirUpdate(c) ); break; @@ -201,7 +188,25 @@ internal class CarInspector_PopulateCarPanel_Patch } } - private static void CalculateCostIfEnabled(Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost, IEnumerable consist) + internal static void CarEndAirUpdate(Car c) + { + ends.Do(end => + { + EndGear endGear = c[end]; + + StateManager.ApplyLocal( + new PropertyChange( + c.id, + KeyValueKeyFor(EndGearStateKey.Anglecock, c.LogicalToEnd(end)), + new FloatPropertyValue(endGear.IsCoupled ? 1f : 0f) + ) + ); + + if (c.TryGetAdjacentCar(end, out Model.Car c2)) StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true)); + }); + } + + internal static void CalculateCostIfEnabled(Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost, IEnumerable consist) { if (buttonsHaveCost) { diff --git a/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs b/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs index 9d3922f..192e93f 100644 --- a/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs +++ b/TweaksAndThings/Patches/CarPickable_Activate_Patch.cs @@ -1,16 +1,34 @@ -using HarmonyLib; +using Core; +using HarmonyLib; +using Model; +using Network; using Railloader; +using RMROC451.TweaksAndThings.Enums; +using RMROC451.TweaksAndThings.Extensions; using RollingStock; +using Serilog; +using System.Linq; +using System.Runtime.Remoting.Messaging; +using UI; using UnityEngine; namespace RMROC451.TweaksAndThings.Patches; +enum MrocAction +{ + Follow, + Inspect, + ConnectConsistAir, + ToggleConsistBrakes +} + [HarmonyPatch(typeof(CarPickable))] [HarmonyPatch(nameof(CarPickable.Activate), typeof(PickableActivateEvent))] [HarmonyPatchCategory("RMROC451TweaksAndThings")] internal class CarPickable_Activate_Patch { + private static Serilog.ILogger _log => Log.ForContext(); static float clicked = 0; static float clicktime = 0; @@ -20,20 +38,111 @@ internal class CarPickable_Activate_Patch { TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; if (!tweaksAndThings.IsEnabled) return true; + bool bCtrlAltHeld = GameInput.IsControlDown && GameInput.IsAltDown; - if (OnPointerDown(evt)) + _log.ForContext("car", __instance.car).Information($"{GameInput.IsShiftDown} {GameInput.IsControlDown} {GameInput.IsAltDown} {bCtrlAltHeld} "); + + //On Double click of a car: follow + if (OnPointerDown(evt, PickableActivation.Primary)) { CameraSelector.shared.FollowCar(__instance.car); + _log.ForContext("car", __instance.car).Information("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()); + bool needsOiling = GameInput.IsShiftDown && consist.All(c => c.IsStopped()) && consist.Any(c => c.NeedsOiling || c.HasHotbox) && (consist.CabooseInConsist() || !tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()); + var chargeIt = handbrakesApplied || airSystemIssues || needsOiling; + //CTRL + ALT + SHIFT : BrakesAngleCocksAndOiling + //CTRL + ALT : Release Consist Brakes and Check AngleCocks + //CTRL + SHIFT : Toggle Consist Brakes + //CTRL : Toggle Car Brakes & Airup Car + //ALT + SHIFT : Check Consist AngleCocks + //ALT : Open Inspector to Car + 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!"); + output = false; + } + else if (GameInput.IsControlDown && GameInput.IsShiftDown) + { + CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment()); + _log.ForContext("car", __instance.car).Information("ctrlShiftHeld!"); + output = false; + } + else if (GameInput.IsAltDown && GameInput.IsShiftDown) + { + if (airSystemIssues) + CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment()); + _log.ForContext("car", __instance.car).Information("altShiftHeld!"); + output = false; + } + else if (GameInput.IsAltDown) + { + UI.CarInspector.CarInspector.Show(__instance.car); + _log.ForContext("car", __instance.car).Information("altHeld!"); + output = false; + } + else if (GameInput.IsControlDown) + { + __instance.car.SetHandbrake(!__instance.car.HandbrakeApplied()); + CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(__instance.car); + _log.ForContext("car", __instance.car).Information("ctrlHeld!"); + 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; + + //} } return true; } - public static bool OnPointerDown(PickableActivateEvent evt) + private static void BrakesAngleCocksAndOiling(CarPickable __instance, 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); + if (airSystemIssues) + CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, false); + if (needsOiling) + { + foreach (var car in consist.Where(c => c.NeedsOiling || c.HasHotbox)) + { + float num2 = 1f - car.Oiled; + car.OffsetOiled(num2); + hbFix += car.HasHotbox ? 1 : 0; + if (car.HasHotbox) car.AdjustHotboxValue(0f); + } + if (hbFix > 0) + Multiplayer.Broadcast($"Near {Hyperlink.To(__instance.car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\""); + } + + if (chargeIt) + CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist); + } + + public static bool OnPointerDown( + PickableActivateEvent evt, + PickableActivation btn = PickableActivation.Primary + ) { bool output = false; - if (evt.Activation == PickableActivation.Primary) + if (evt.Activation == btn) { clicked++; if (clicked == 1) clicktime = Time.time; diff --git a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs index 7eacd0b..7c629ec 100644 --- a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs +++ b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs @@ -14,6 +14,7 @@ using UI; using UI.EngineRoster; using UI.Tooltips; using UnityEngine; +using Game.State; namespace RMROC451.TweaksAndThings.Patches; @@ -28,6 +29,9 @@ internal class EngineRosterRow_Refresh_Patch TweaksAndThingsPlugin? tweaksAndThings = SingletonPluginBase.Shared; RosterFuelColumnSettings? rosterFuelColumnSettings = tweaksAndThings?.settings?.EngineRosterFuelColumnSettings; + string fuelInfoText = string.Empty; + string fuelInfoTooltip = string.Empty; + if (tweaksAndThings == null || rosterFuelColumnSettings == null || !tweaksAndThings.IsEnabled || @@ -38,8 +42,9 @@ internal class EngineRosterRow_Refresh_Patch try { - string fuelInfoText = string.Empty; - string fuelInfoTooltip = string.Empty; + IEnumerable consist = __instance._engine.EnumerateCoupled().Where(c => c.EnableOiling); + bool cabooseRequirementFulfilled = consist.CabooseInConsist() || !tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || consist.ConsistNoFreight(); + float offendingPercentage = 0; Car engineOrTender = __instance._engine; List loadSlots = __instance._engine.Definition.LoadSlots; if (!loadSlots.Any()) @@ -57,12 +62,42 @@ internal class EngineRosterRow_Refresh_Patch { CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault(); var fuelLevel = FuelLevel(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity); + offendingPercentage = CalcPercentLoad(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity); fuelInfoText += loadSlots[i].RequiredLoadIdentifier == offender ? fuelLevel + " " : string.Empty; //fuelInfoText += TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity) + " "; fuelInfoTooltip += $"{TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity)} {valueOrDefault.LoadString(CarPrototypeLibrary.instance.LoadForId(valueOrDefault.LoadId))}\n"; } } + try + { + if (cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature) + { + float lowestOilLevel = consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled; + var oilLevel = FuelLevel(lowestOilLevel, 1); + fuelInfoTooltip += $"{lowestOilLevel.TriColorPiePercent(1)} {oilLevel} Consist Oil Lowest Level\n"; + if (CalcPercentLoad(lowestOilLevel, 1) < offendingPercentage) + { + fuelInfoText = $"{oilLevel} "; + } + + if (consist.Any(c => c.HasHotbox)) + { + fuelInfoText = $"{TextSprites.Hotbox} "; + fuelInfoTooltip = $"{TextSprites.Hotbox} Hotbox detected!\n{fuelInfoTooltip}"; + } + } + else if (!cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature) + { + fuelInfoTooltip += $"Add Caboose To Consist For Consist Oil Level Reporting\n"; + } + + } + catch (Exception ex) + { + Log.Error(ex, "Error Detecting oiling consist status for engine roster"); + } + switch (rosterFuelColumnSettings.EngineRosterFuelStatusColumn) { case EngineRosterFuelDisplayColumn.Engine: @@ -77,7 +112,6 @@ internal class EngineRosterRow_Refresh_Patch default: break; } - } catch (Exception ex) { rosterFuelColumnSettings.EngineRosterFuelStatusColumn = EngineRosterFuelDisplayColumn.None; @@ -91,10 +125,15 @@ internal class EngineRosterRow_Refresh_Patch tooltip.TooltipInfo = new TooltipInfo(tooltip.tooltipTitle, fuelInfoTooltip); } - public static string FuelLevel(float quantity, float capacity) + public static float CalcPercentLoad(float quantity, float capacity) { float num = capacity <= 0f ? 0 : Mathf.Clamp(quantity / capacity * 100, 0, 100); - return $"{Mathf.FloorToInt(num):D2}%"; + return num; + } + + public static string FuelLevel(float quantity, float capacity) + { + return $"{Mathf.FloorToInt(CalcPercentLoad(quantity, capacity)):D2}%"; } } diff --git a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs index e35bca4..ff2c016 100644 --- a/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs +++ b/TweaksAndThings/Patches/OpsController_AnnounceCoalescedPayments_Patch.cs @@ -85,12 +85,13 @@ internal class OpsController_AnnounceCoalescedPayments_Patch float rate2 = 24 * 2 * 8;// carLoadRate = 8 crew hours in 30 min loading; (24h * 2 to get half hour chunks * 8 hours to load in those chunks) float num2 = 99999999; //QuantityInStorage for crew-hours (infinite where crew can be shuffling about) float quantityToLoad = Mathf.Min(num2, IndustryComponent.RateToValue(rate2, deltaTime)); - + OpsCarAdapter? oca = car.IsCaboose() ? new OpsCarAdapter(car, OpsController.Shared) : null; + bool isFull = !car.IsCaboose() ? true : (oca?.IsFull(CrewHoursLoad()) ?? true); if (car.IsCaboose() && !CrewCarStatus(car).spotted) { - CrewCarDict[car.id] = (true, CrewCarDict[car.id].filling); + CrewCarDict[car.id] = (true, !isFull); } - if (car.IsCabooseAndStoppedForLoadRefresh()) + if (car.IsCabooseAndStoppedForLoadRefresh(isFull)) { if (!CrewCarDict[car.id].filling) Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Topping off caboose crew.\""); CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, true); @@ -100,7 +101,7 @@ internal class OpsController_AnnounceCoalescedPayments_Patch Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\""); CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, false); } - new OpsCarAdapter(car, OpsController.Shared).Load(CrewHoursLoad(), quantityToLoad); + (oca ?? new OpsCarAdapter(car, OpsController.Shared)).Load(CrewHoursLoad(), quantityToLoad); } } diff --git a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs index 9e9ca89..f4fdd0b 100644 --- a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs +++ b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs @@ -1,8 +1,10 @@ -using HarmonyLib; +using Game.State; +using HarmonyLib; using Model; using Model.Ops; using Railloader; using RMROC451.TweaksAndThings.Extensions; +using Serilog; using System.Collections.Generic; using System.Linq; using UI.Tags; @@ -26,18 +28,23 @@ internal class TagController_UpdateTag_Patch return; } - ProceedWithPostFix(car, tagCallout); + ProceedWithPostFix(car, tagCallout, tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()); return; } - private static void ProceedWithPostFix(Car car, TagCallout tagCallout) + private static void ProceedWithPostFix(Car car, TagCallout tagCallout, bool cabooseRequired) { tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", car.DisplayName); List tags = new(); if (OpsController_AnnounceCoalescedPayments_Patch.CrewCarStatus(car).spotted) tags.Add("+"); - if (car.HasHotbox) tags.Add(TextSprites.Hotbox); + //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)); + IEnumerable consist = car.EnumerateCoupled().Where(c => c.EnableOiling); + bool cabooseRequirementFulfilled = consist.CabooseInConsist() || !cabooseRequired || consist.ConsistNoFreight(); + if (cabooseRequirementFulfilled && car.IsLocomotive && !car.NeedsOiling && StateManager.Shared.Storage.OilFeature && consist.Any(c => c.NeedsOiling)) + tags.Add(consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled.TriColorPiePercent(1)); if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills); if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel); diff --git a/TweaksAndThings/RMROC451.TweaksAndThings.csproj b/TweaksAndThings/RMROC451.TweaksAndThings.csproj index c399853..ca09fd7 100644 --- a/TweaksAndThings/RMROC451.TweaksAndThings.csproj +++ b/TweaksAndThings/RMROC451.TweaksAndThings.csproj @@ -4,6 +4,9 @@ + + + @@ -22,8 +25,12 @@ + + + +