From fc43d54815c8f00041231775c0dd278be17bc359 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Fri, 21 Jun 2024 00:23:45 -0500 Subject: [PATCH 1/2] initial work for 13 --- .gitignore | 1 - TweaksAndThings.sln | 1 + TweaksAndThings/Commands/CrewUpdateCommand.cs | 44 +++++++++++++++++++ TweaksAndThings/Extensions/Car_Extensions.cs | 1 + TweaksAndThings/TweaksAndThings.cs | 3 +- 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 TweaksAndThings/Commands/CrewUpdateCommand.cs diff --git a/.gitignore b/.gitignore index 5ce7f30..8a30d25 100644 --- a/.gitignore +++ b/.gitignore @@ -396,4 +396,3 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml -/TweaksAndThings/Commands/EchoCommand.cs diff --git a/TweaksAndThings.sln b/TweaksAndThings.sln index 3176b51..da34e15 100644 --- a/TweaksAndThings.sln +++ b/TweaksAndThings.sln @@ -5,6 +5,7 @@ VisualStudioVersion = 17.4.33122.133 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{452A23A6-81C8-49C6-A7EE-95FD9377F896}" ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Paths.user = Paths.user diff --git a/TweaksAndThings/Commands/CrewUpdateCommand.cs b/TweaksAndThings/Commands/CrewUpdateCommand.cs new file mode 100644 index 0000000..d204df2 --- /dev/null +++ b/TweaksAndThings/Commands/CrewUpdateCommand.cs @@ -0,0 +1,44 @@ +using Game.State; +using Helpers; +using Model.OpsNew; +using Network; +using RMROC451.TweaksAndThings.Extensions; +using System.Linq; +using UI.Console; + +namespace RMROC451.TweaksAndThings.Commands; + +[ConsoleCommand("/cu", "generate a formatted message about a locomotive's status.")] +public class EchoCommand : IConsoleCommand +{ + public string Execute(string[] comps) + { + if (comps.Length < 4) + { + return "Usage: /cu |. +|- "; + } + + string query = comps[1]; + Model.Car car = query == "." ? TrainController.Shared.SelectedLocomotive : TrainController.Shared.CarForString(query); + + if (car.DetermineFuelCar() == null) + { + return "Car not found."; + } + string message = string.Join(" ", comps.Skip(3)).Truncate(512); + + switch (comps[2]) + { + case "+": + break; + case "-": + break; + default: + return "+ to include area or - to leave it out"; + } + + if (comps[2] == "+") message += $" {OpsController.Shared.ClosestArea(car)?.name ?? "???"}"; + Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {Hyperlink.To(car)}: \"{message}\""); + return string.Empty; + } +} \ No newline at end of file diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs index 6415c92..00e56ea 100644 --- a/TweaksAndThings/Extensions/Car_Extensions.cs +++ b/TweaksAndThings/Extensions/Car_Extensions.cs @@ -24,6 +24,7 @@ namespace RMROC451.TweaksAndThings.Extensions public static Car? DetermineFuelCar(this Car engine, bool returnEngineIfNull = false) { + if (engine == null) return null; Car? car; if (engine is SteamLocomotive steamLocomotive && new Func(steamLocomotive.FuelCar) != null) { diff --git a/TweaksAndThings/TweaksAndThings.cs b/TweaksAndThings/TweaksAndThings.cs index fe59771..b76c517 100644 --- a/TweaksAndThings/TweaksAndThings.cs +++ b/TweaksAndThings/TweaksAndThings.cs @@ -11,6 +11,7 @@ using System.Net.Http; using UI.Builder; using RMROC451.TweaksAndThings.Enums; +using RMROC451.TweaksAndThings.Commands; namespace RMROC451.TweaksAndThings; @@ -45,7 +46,7 @@ public class TweaksAndThings : SingletonPluginBase, IUpdateHand logger.Information("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version); - //moddingContext.RegisterConsoleCommand(new EchoCommand()); + moddingContext.RegisterConsoleCommand(new EchoCommand()); settings = moddingContext.LoadSettingsData(self.Id); } From 8dc87f312d32efea4a81c297aba38b513784e885 Mon Sep 17 00:00:00 2001 From: RMROC451 Date: Thu, 27 Jun 2024 00:09:40 -0500 Subject: [PATCH 2/2] #5 adding Crew as a resource to cabeese, and having the new buttons added by this mod, cost resources if opting in. --- Directory.Build.targets | 2 +- TweaksAndThings/Definition.json | 10 +- TweaksAndThings/Extensions/Car_Extensions.cs | 131 +++++++++++++---- TweaksAndThings/Extensions/Load_Extensions.cs | 28 ++++ .../CarInspector_PopulateCarPanel_Patch.cs | 89 ++++++++--- .../Patches/EngineRosterRow_Refresh_Patch.cs | 2 +- .../Patches/ExpandedConsole_Patch.cs | 2 +- .../IndustryComponent_Service_Patch.cs | 139 ++++++++++++++++++ .../StateManager_OnDayDidChange_Patch.cs | 50 +++++++ .../Patches/TagController_UpdateTag_Patch.cs | 3 +- .../RMROC451.TweaksAndThings.csproj | 10 ++ TweaksAndThings/Settings/Settings.cs | 5 +- TweaksAndThings/TweaksAndThingsPlugin.cs | 31 ++-- TweaksAndThings/mroc-cabeese.json | 23 +++ 14 files changed, 455 insertions(+), 70 deletions(-) create mode 100644 TweaksAndThings/Extensions/Load_Extensions.cs create mode 100644 TweaksAndThings/Patches/IndustryComponent_Service_Patch.cs create mode 100644 TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs create mode 100644 TweaksAndThings/mroc-cabeese.json diff --git a/Directory.Build.targets b/Directory.Build.targets index ce06d73..6546fe1 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -12,7 +12,7 @@ 0 1 - 5 + 6 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(AssemblyVersion) $(AssemblyVersion) diff --git a/TweaksAndThings/Definition.json b/TweaksAndThings/Definition.json index 092b2f5..5d66ba7 100644 --- a/TweaksAndThings/Definition.json +++ b/TweaksAndThings/Definition.json @@ -7,7 +7,13 @@ { "id": "railloader", "notBefore": "1.8.1" - } + }, + "Zamu.StrangeCustoms" ], - "assemblies": [ "RMROC451.TweaksAndThings" ] + "assemblies": [ "RMROC451.TweaksAndThings" ], + "mixintos": { + "container:ne-caboose01": "file(mroc-cabeese.json)", + "container:ne-caboose02": "file(mroc-cabeese.json)", + "container:ne-caboose03": "file(mroc-cabeese.json)" + } } \ No newline at end of file diff --git a/TweaksAndThings/Extensions/Car_Extensions.cs b/TweaksAndThings/Extensions/Car_Extensions.cs index f4f81a2..e5db62e 100644 --- a/TweaksAndThings/Extensions/Car_Extensions.cs +++ b/TweaksAndThings/Extensions/Car_Extensions.cs @@ -1,43 +1,110 @@ -using Model; +using Helpers; +using Model; +using Model.Definition.Data; +using Model.OpsNew; +using Serilog; using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; -namespace RMROC451.TweaksAndThings.Extensions +namespace RMROC451.TweaksAndThings.Extensions; + +public static class Car_Extensions { - public static class Car_Extensions + public static bool EndAirSystemIssue(this Car car) { - public static bool EndAirSystemIssue(this Car car) + bool AEndAirSystemIssue = car[Car.LogicalEnd.A].IsCoupled && !car[Car.LogicalEnd.A].IsAirConnectedAndOpen; + bool BEndAirSystemIssue = car[Car.LogicalEnd.B].IsCoupled && !car[Car.LogicalEnd.B].IsAirConnectedAndOpen; + bool EndAirSystemIssue = AEndAirSystemIssue || BEndAirSystemIssue; + return EndAirSystemIssue; + } + + public static bool HandbrakeApplied(this Model.Car car) => + car.air.handbrakeApplied; + + public static bool CarOrEndGearIssue(this Model.Car car) => + car.EndAirSystemIssue() || car.HandbrakeApplied(); + + public static bool CarAndEndGearIssue(this Model.Car car) => + car.EndAirSystemIssue() && car.HandbrakeApplied(); + + public static Car? DetermineFuelCar(this Car engine, bool returnEngineIfNull = false) + { + if (engine == null) return null; + Car? car; + if (engine is SteamLocomotive steamLocomotive && new Func(steamLocomotive.FuelCar) != null) { - bool AEndAirSystemIssue = car[Car.LogicalEnd.A].IsCoupled && !car[Car.LogicalEnd.A].IsAirConnectedAndOpen; - bool BEndAirSystemIssue = car[Car.LogicalEnd.B].IsCoupled && !car[Car.LogicalEnd.B].IsAirConnectedAndOpen; - bool EndAirSystemIssue = AEndAirSystemIssue || BEndAirSystemIssue; - return EndAirSystemIssue; + car = steamLocomotive.FuelCar(); } - - public static bool HandbrakeApplied(this Model.Car car) => - car.air.handbrakeApplied; - - public static bool CarOrEndGearIssue(this Model.Car car) => - car.EndAirSystemIssue() || car.HandbrakeApplied(); - - public static bool CarAndEndGearIssue(this Model.Car car) => - car.EndAirSystemIssue() && car.HandbrakeApplied(); - - public static Car? DetermineFuelCar(this Car engine, bool returnEngineIfNull = false) + else { - if (engine == null) return null; - Car? car; - if (engine is SteamLocomotive steamLocomotive && new Func(steamLocomotive.FuelCar) != null) - { - car = steamLocomotive.FuelCar(); - } - else - { - car = engine is DieselLocomotive ? engine : null; - if (returnEngineIfNull && car == null) car = engine; - } - return car; + car = engine is DieselLocomotive ? engine : null; + if (returnEngineIfNull && car == null) car = engine; } + return car; + } - public static bool NotMotivePower(this Car car) => car is not BaseLocomotive && car.Archetype != Model.Definition.CarArchetype.Tender; + public static bool NotMotivePower(this Car car) => car is not BaseLocomotive && car.Archetype != Model.Definition.CarArchetype.Tender; + + /// + /// For every car in the consist, cost 1 minute of AI Engineer time. + /// + /// + public static int CalculateCostForAutoEngineerEndGearSetting(this IEnumerable consist) => + consist.Count() * 60; + + public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose; + + public static bool CabooseInConsist(this IEnumerable input) => input.FirstOrDefault(c => c.IsCaboose()); + + public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool decrement = false) + { + Car? output = null; + if (!car.IsCaboose()) return null; + + List loadSlots = car.Definition.LoadSlots; + for (int i = 0; i < loadSlots.Count; i++) + { + CarLoadInfo? loadInfo = car.GetLoadInfo(i); + if (loadInfo.HasValue) + { + CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault(); + output = valueOrDefault.Quantity >= timeNeeded ? car : null; + if (decrement && output != null) output.SetLoadInfo(i, valueOrDefault with { Quantity = valueOrDefault.Quantity - timeNeeded }); + break; + } + } + return output; + } + + public static Car? HuntingForCabeeseNearCar(this Car car, float timeNeeded, TrainController tc, bool decrement = false) + { + Vector3 position = car.GetMotionSnapshot().Position; + Vector3 center = WorldTransformer.WorldToGame(position); + Rect rect = new Rect(new Vector2(center.x - 30f, center.z - 30f), Vector2.one * 30f * 2f); + var cars = tc.CarIdsInRect(rect); + Log.Information($"{nameof(HuntingForCabeeseNearCar)} => {cars.Count()}"); + bool decrementedAlready = false; + List<(string carId, float distance)> source = cars.Select(carId => + { + Car car = tc.CarForId(carId); + if (car == null || !car.CabooseWithSufficientCrewHours(timeNeeded, decrement && !decrementedAlready)) + { + return (carId: carId, distance: 1000f); + } + decrementedAlready = true; + Vector3 a = WorldTransformer.WorldToGame(car.GetMotionSnapshot().Position); + return (carId: carId, distance: Vector3.Distance(a, center)); + }).ToList(); + + Car? output = + (from t in source + where t.distance < 21f + orderby t.distance ascending + select tc.CarForId(t.carId) + ).FirstOrDefault(); + + return output; } } diff --git a/TweaksAndThings/Extensions/Load_Extensions.cs b/TweaksAndThings/Extensions/Load_Extensions.cs new file mode 100644 index 0000000..e638160 --- /dev/null +++ b/TweaksAndThings/Extensions/Load_Extensions.cs @@ -0,0 +1,28 @@ +using Core; +using System; + +namespace RMROC451.TweaksAndThings.Extensions; + +public static class Load_Extensions +{ + //https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings?redirectedfrom=MSDN#the--section-separator + //https://dotnetfiddle.net/iHVevM + public static string FormatCrewHours(this float quantity, string description) + { + var ts = TimeSpan.FromHours(quantity); + float minutes = ts.Minutes - (ts.Minutes % 15); + + string output = string.Format("{0:;;No}{1:##}{2:\\.##;\\.##;.} {3} {4}", + ts.Hours + minutes, + ts.Hours, (minutes / 60.0f) * 100, + description, + "Hour".Pluralize(quantity == 1 ? 1 : 0) + ).Trim(); + + if (ts.Hours < 1) + { + output = string.Format("{0} {1} Minutes", ts.Minutes, description).Trim(); + } + return output; + } +} diff --git a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs index efd1e34..b88547b 100644 --- a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs +++ b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs @@ -4,6 +4,7 @@ using HarmonyLib; using KeyValue.Runtime; using Model; using Model.OpsNew; +using Network; using Railloader; using RMROC451.TweaksAndThings.Enums; using RMROC451.TweaksAndThings.Extensions; @@ -21,7 +22,7 @@ namespace RMROC451.TweaksAndThings.Patches; [HarmonyPatch(typeof(CarInspector))] [HarmonyPatch(nameof(CarInspector.PopulateCarPanel), typeof(UIPanelBuilder))] [HarmonyPatchCategory("RMROC451TweaksAndThings")] -public class CarInspector_PopulateCarPanel_Patch +internal class CarInspector_PopulateCarPanel_Patch { private static IEnumerable ends = Enum.GetValues(typeof(LogicalEnd)).Cast(); @@ -36,6 +37,7 @@ public class CarInspector_PopulateCarPanel_Patch TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; if (!tweaksAndThings.IsEnabled) return true; + bool buttonsHaveCost = tweaksAndThings?.settings?.EndGearHelpersRequirePayment ?? false; var consist = __instance._car.EnumerateCoupled(LogicalEnd.A); builder = AddCarConsistRebuildObservers(builder, consist); @@ -43,24 +45,21 @@ public class CarInspector_PopulateCarPanel_Patch builder.HStack(delegate (UIPanelBuilder hstack) { var buttonName = $"{(consist.Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} {TextSprites.HandbrakeWheel}"; - hstack.AddButtonCompact(buttonName, delegate - { - MrocConsistHelper(__instance._car, MrocHelperType.Handbrake); + hstack.AddButtonCompact(buttonName, delegate { + MrocConsistHelper(__instance._car, MrocHelperType.Handbrake, buttonsHaveCost); hstack.Rebuild(); }).Tooltip(buttonName, $"Iterates over cars in this consist and {(consist.Any(c => c.HandbrakeApplied()) ? "releases" : "sets")} {TextSprites.HandbrakeWheel}."); if (consist.Any(c => c.EndAirSystemIssue())) { - hstack.AddButtonCompact("Connect Air", delegate - { - MrocConsistHelper(__instance._car, MrocHelperType.GladhandAndAnglecock); + hstack.AddButtonCompact("Connect Air", delegate { + MrocConsistHelper(__instance._car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost); hstack.Rebuild(); }).Tooltip("Connect Consist Air", "Iterates over each car in this consist and connects gladhands and opens anglecocks."); } - hstack.AddButtonCompact("Bleed Consist", delegate - { - MrocConsistHelper(__instance._car, MrocHelperType.BleedAirSystem); + hstack.AddButtonCompact("Bleed Consist", delegate { + MrocConsistHelper(__instance._car, MrocHelperType.BleedAirSystem, buttonsHaveCost); hstack.Rebuild(); }).Tooltip("Bleed Air Lines", "Iterates over each car in this consist and bleeds the air out of the lines."); }); @@ -92,8 +91,15 @@ public class CarInspector_PopulateCarPanel_Patch key, delegate (Value value) { - tagController.UpdateTag(car, car.TagCallout, OpsController.Shared); - builder.Rebuild(); + try + { + tagController.UpdateTag(car, car.TagCallout, OpsController.Shared); + builder.Rebuild(); + } + catch(Exception ex) + { + Log.Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}"); + } }, false ) @@ -112,12 +118,14 @@ public class CarInspector_PopulateCarPanel_Patch // } //} - public static void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType) + public static void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost) { - IEnumerable consist = car.EnumerateCoupled(LogicalEnd.A); - - Log.Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}"); TrainController tc = UnityEngine.Object.FindObjectOfType(); + IEnumerable consist = car.EnumerateCoupled(LogicalEnd.A); + //Log.Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}"); + + CalculateCostIfEnabled(car, mrocHelperType, buttonsHaveCost, consist); + switch (mrocHelperType) { case MrocHelperType.Handbrake: @@ -148,11 +156,7 @@ public class CarInspector_PopulateCarPanel_Patch ) ); - if (c.TryGetAdjacentCar(end, out Model.Car c2)) - { - StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true)); - } - + if (c.TryGetAdjacentCar(end, out Model.Car c2)) StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true)); }) ); break; @@ -167,4 +171,47 @@ public class CarInspector_PopulateCarPanel_Patch break; } } + + private static void CalculateCostIfEnabled(Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost, IEnumerable consist) + { + if (buttonsHaveCost) + { + float originalTimeCost = consist.CalculateCostForAutoEngineerEndGearSetting(); + float timeCost = originalTimeCost; + float crewCost = timeCost / 3600; //hours of time deducted from caboose. + var tsString = crewCost.FormatCrewHours(IndustryComponent_Service_Patch.CrewHoursLoad().description); + Car? cabooseWithAvailCrew = NearbyCabooseWithAvailableCrew(car, crewCost, buttonsHaveCost); + if (cabooseWithAvailCrew == null) timeCost *= 1.5f; + var cabooseFoundDisplay = cabooseWithAvailCrew?.DisplayName ?? "No caboose"; + + Log.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)"); + + + Multiplayer.SendError(StateManager.Shared._playersManager.LocalPlayer, $"{(cabooseWithAvailCrew != null ? $"{cabooseWithAvailCrew.DisplayName} Hours Adjusted: ({tsString})\n" : string.Empty)}Wages: ~(${Math.Ceiling((decimal)(timeCost / 3600) * 5)})"); + + if (buttonsHaveCost) StateManager_OnDayDidChange_Patch.UnbilledAutoBrakeCrewRunDuration += timeCost; + } + } + + public static Car? NearbyCabooseWithAvailableCrew(Car car, float timeNeeded, bool decrement = false) + { + //check current car. + Car? output = car.CabooseWithSufficientCrewHours(timeNeeded, decrement); + if (output != null) return output; //short out if we are good + + //check consist, for cabeese + IEnumerable consist = car.EnumerateCoupled(LogicalEnd.A); + output = consist.FirstOrDefault(c => c.CabooseWithSufficientCrewHours(timeNeeded, decrement)); + if (output != null) return output; //short out if we are good + + //then check near consist cars for cabeese + TrainController tc = UnityEngine.Object.FindObjectOfType(); + foreach (var c in consist) + { + output = c.HuntingForCabeeseNearCar(timeNeeded, tc, decrement); + if (output != null) return output; //short out if we are good + } + + return output; + } } diff --git a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs index d29cb5b..4b14fb2 100644 --- a/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs +++ b/TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs @@ -21,7 +21,7 @@ namespace RMROC451.TweaksAndThings.Patches; [HarmonyPatch(typeof(EngineRosterRow))] [HarmonyPatch(nameof(EngineRosterRow.Refresh))] [HarmonyPatchCategory("RMROC451TweaksAndThings")] -public class EngineRosterRow_Refresh_Patch +internal class EngineRosterRow_Refresh_Patch { public static void Postfix(EngineRosterRow __instance) { diff --git a/TweaksAndThings/Patches/ExpandedConsole_Patch.cs b/TweaksAndThings/Patches/ExpandedConsole_Patch.cs index 6c0f385..d651960 100644 --- a/TweaksAndThings/Patches/ExpandedConsole_Patch.cs +++ b/TweaksAndThings/Patches/ExpandedConsole_Patch.cs @@ -19,7 +19,7 @@ namespace RMROC451.TweaksAndThings.Patches; [HarmonyPatch(typeof(ExpandedConsole))] [HarmonyPatch(nameof(ExpandedConsole.Add))] [HarmonyPatchCategory("RMROC451TweaksAndThings")] -public class ExpandedConsole_Add_Patch +internal class ExpandedConsole_Add_Patch { private static void Prefix(ref UI.Console.Console.Entry entry) { diff --git a/TweaksAndThings/Patches/IndustryComponent_Service_Patch.cs b/TweaksAndThings/Patches/IndustryComponent_Service_Patch.cs new file mode 100644 index 0000000..4e3af99 --- /dev/null +++ b/TweaksAndThings/Patches/IndustryComponent_Service_Patch.cs @@ -0,0 +1,139 @@ +using Game.State; +using HarmonyLib; +using Model; +using Model.Definition.Data; +using Model.Ops.Definition; +using Model.OpsNew; +using Railloader; +using RMROC451.TweaksAndThings.Extensions; +using Serilog; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace RMROC451.TweaksAndThings.Patches; + +[HarmonyPatch(typeof(CarExtensions))] +[HarmonyPatch(nameof(CarExtensions.LoadString), typeof(CarLoadInfo), typeof(Load))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class CarExtensions_LoadString_Patch +{ + public static bool Prefix(CarLoadInfo info, Load load, ref string __result) + { + bool output = load.id == IndustryComponent_Service_Patch.CrewHoursLoad().id; + if (output) __result = info.Quantity.FormatCrewHours(load.description); + + return !output; + } +} + +[HarmonyPatch(typeof(CarPrototypeLibrary))] +[HarmonyPatch(nameof(CarPrototypeLibrary.LoadForId), typeof(string))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class CarPrototypeLibrary_LoadForId_Patch +{ + public static bool Prefix(string loadId, ref Load __result) + { + Load load = IndustryComponent_Service_Patch.CrewHoursLoad(); + if (loadId == load.id) __result = load; + + return __result == null; + } +} + +[HarmonyPatch(typeof(TeamTrack))] +[HarmonyPatch(nameof(TeamTrack.Service), typeof(IIndustryContext))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class TeamTrack_Service_Patch +{ + public static bool Prefix(IndustryComponent __instance, IIndustryContext ctx) + { + //Log.Information($"{nameof(SimplePassengerStop_Service_Patch)} => {((IndustryContext)ctx)._industry.name}"); + return IndustryComponent_Service_Patch.Prefix(__instance, ctx); + } +} + +[HarmonyPatch(typeof(RepairTrack))] +[HarmonyPatch(nameof(RepairTrack.Service), typeof(IIndustryContext))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class RepairTrack_Service_Patch +{ + public static bool Prefix(IndustryComponent __instance, IIndustryContext ctx) + { + //Log.Information($"{nameof(SimplePassengerStop_Service_Patch)} => {((IndustryContext)ctx)._industry.name}"); + return IndustryComponent_Service_Patch.Prefix(__instance, ctx); + } +} + +[HarmonyPatch(typeof(SimplePassengerStop))] +[HarmonyPatch(nameof(SimplePassengerStop.Service), typeof(IIndustryContext))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class SimplePassengerStop_Service_Patch +{ + public static bool Prefix(IndustryComponent __instance, IIndustryContext ctx) + { + Log.Information($"{nameof(SimplePassengerStop_Service_Patch)} => {((IndustryContext)ctx)._industry.name}"); + return IndustryComponent_Service_Patch.Prefix(__instance, ctx); + } +} + +internal static class IndustryComponent_Service_Patch +{ + public static Load CrewHoursLoad() + { + Load load = (Load)ScriptableObject.CreateInstance(typeof(Load)); + load.name = "crew-hours"; + load.description = "Crew"; + load.units = LoadUnits.Quantity; + + return load; + } + + public static bool Prefix(IndustryComponent __instance, IIndustryContext ctx) + { + TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + if (!StateManager.IsHost || !tweaksAndThings.IsEnabled || !(tweaksAndThings?.settings?.EndGearHelpersRequirePayment ?? false)) return true; + + Load load = CrewHoursLoad(); + + 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, ctx.DeltaTime)); + + var carsAtPosition = ctx.CarsAtPosition(); + + var cabeese = from car in carsAtPosition.Where(c => c.CarType == "NE") + where car.IsEmptyOrContains(load) + orderby car.QuantityOfLoad(load).quantity descending + select car; + + foreach (IOpsCar item in cabeese) + { + TrainController tc = UnityEngine.Object.FindAnyObjectByType(); + if (tc.TryGetCarForId(item.Id, out Car car)) + { + List loadSlots = car.Definition.LoadSlots; + float quantity = 0f; + float max = 0f; + for (int i = 0; i < loadSlots.Count; i++) + { + LoadSlot loadSlot = loadSlots[i]; + if (loadSlot.LoadRequirementsMatch(load) && loadSlot.LoadUnits == load.units) + { + CarLoadInfo? loadInfo = car.GetLoadInfo(i); + + quantity = loadInfo.HasValue ? loadInfo.Value.Quantity : 0f; + max = loadSlots[i].MaximumCapacity; + break; + } + } + //Log.Information($"{nameof(IndustryComponent_Service_Patch)} {car} => {car.StoppedDuration} => {quantityToLoad} => {quantity}/{max}"); + if (car.StoppedDuration > 30) item.Load(load, quantityToLoad); + } + + //todo:crew refresh message? + } + + return true; + } +} \ No newline at end of file diff --git a/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs b/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs new file mode 100644 index 0000000..1e0ad3f --- /dev/null +++ b/TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs @@ -0,0 +1,50 @@ +using Game.Events; +using Game.State; +using HarmonyLib; +using KeyValue.Runtime; +using Network; +using Railloader; +using UnityEngine; + +namespace RMROC451.TweaksAndThings.Patches; + +[HarmonyPatch(typeof(StateManager))] +[HarmonyPatch(nameof(StateManager.OnDayDidChange), typeof(TimeDayDidChange))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +internal class StateManager_OnDayDidChange_Patch +{ + private const string unbilledBrakeCrewDuration = "unbilledBrakeCrewDuration"; + + private static void Postfix(StateManager __instance) + { + TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase.Shared; + if (!tweaksAndThings.IsEnabled || !(tweaksAndThings?.settings?.EndGearHelpersRequirePayment ?? false)) return; + if (StateManager.IsHost) PayAutoBrakeCrewWages(__instance); + } + + private static void PayAutoBrakeCrewWages(StateManager __instance) + { + float unbilledRunDuration = UnbilledAutoBrakeCrewRunDuration; + int num = Mathf.FloorToInt(unbilledRunDuration / 3600f * 5f); + float num2 = (float)num / 5f; + float unbilledAutoEngineerRunDuration2 = unbilledRunDuration - num2 * 3600f; + if (num > 0) + { + __instance.ApplyToBalance(-num, Ledger.Category.WagesAI, null, memo: "AI Brake Crew"); + Multiplayer.Broadcast($"Paid {num:C0} for {num2:F1} hours of Brake Crew services."); + UnbilledAutoBrakeCrewRunDuration = unbilledAutoEngineerRunDuration2; + } + } + + public static float UnbilledAutoBrakeCrewRunDuration + { + get + { + return StateManager.Shared._storage._gameKeyValueObject[unbilledBrakeCrewDuration].FloatValue; + } + set + { + StateManager.Shared._storage._gameKeyValueObject[unbilledBrakeCrewDuration] = Value.Float(value); + } + } +} diff --git a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs index 495cd94..825f595 100644 --- a/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs +++ b/TweaksAndThings/Patches/TagController_UpdateTag_Patch.cs @@ -2,7 +2,6 @@ using Model; using Model.OpsNew; using Railloader; -using RMROC451.TweaksAndThings; using RMROC451.TweaksAndThings.Extensions; using UI; using UI.Tags; @@ -13,7 +12,7 @@ namespace RMROC451.TweaksAndThings.Patches; [HarmonyPatch(typeof(TagController))] [HarmonyPatch(nameof(TagController.UpdateTag), typeof(Car), typeof(TagCallout), typeof(OpsController))] [HarmonyPatchCategory("RMROC451TweaksAndThings")] -public class TagController_UpdateTag_Patch +internal class TagController_UpdateTag_Patch { private const string tagTitleAndIconDelimeter = "\n"; private const string tagTitleFormat = "{1}"; diff --git a/TweaksAndThings/RMROC451.TweaksAndThings.csproj b/TweaksAndThings/RMROC451.TweaksAndThings.csproj index ccf421d..c399853 100644 --- a/TweaksAndThings/RMROC451.TweaksAndThings.csproj +++ b/TweaksAndThings/RMROC451.TweaksAndThings.csproj @@ -4,6 +4,14 @@ + + + + + + Always + + @@ -12,12 +20,14 @@ + + diff --git a/TweaksAndThings/Settings/Settings.cs b/TweaksAndThings/Settings/Settings.cs index f90685f..fbf5e5c 100644 --- a/TweaksAndThings/Settings/Settings.cs +++ b/TweaksAndThings/Settings/Settings.cs @@ -17,17 +17,20 @@ public class Settings public Settings( List webhookSettingsList, bool handBrakeAndAirTagModifiers, - RosterFuelColumnSettings engineRosterFuelColumnSettings + RosterFuelColumnSettings engineRosterFuelColumnSettings, + bool endGearHelpersRequirePayment ) { WebhookSettingsList = webhookSettingsList; HandBrakeAndAirTagModifiers = handBrakeAndAirTagModifiers; EngineRosterFuelColumnSettings = engineRosterFuelColumnSettings; + EndGearHelpersRequirePayment = endGearHelpersRequirePayment; } public List? WebhookSettingsList; public bool HandBrakeAndAirTagModifiers; public RosterFuelColumnSettings? EngineRosterFuelColumnSettings; + public bool EndGearHelpersRequirePayment; internal void AddAnotherRow() { diff --git a/TweaksAndThings/TweaksAndThingsPlugin.cs b/TweaksAndThings/TweaksAndThingsPlugin.cs index e4c18fe..127dc94 100644 --- a/TweaksAndThings/TweaksAndThingsPlugin.cs +++ b/TweaksAndThings/TweaksAndThingsPlugin.cs @@ -41,7 +41,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self) { this.modDefinition = self; - + this.moddingContext = moddingContext; logger.Information("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version); @@ -55,7 +55,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, { logger.Information("OnEnable() was called!"); var harmony = new Harmony(modDefinition.Id); - harmony.PatchCategory(modDefinition.Id.Replace(".",string.Empty)); + harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty)); } public override void OnDisable() @@ -97,12 +97,12 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, builder.AddDropdown(columns, (int)(settings?.EngineRosterFuelColumnSettings?.EngineRosterFuelStatusColumn ?? EngineRosterFuelDisplayColumn.None), delegate (int column) { - if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList(), EngineRosterFuelColumnSettings = new() }; + if (settings == null) settings = new(); settings.EngineRosterFuelColumnSettings.EngineRosterFuelStatusColumn = (EngineRosterFuelDisplayColumn)column; 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 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)))}"); builder.AddField( "Always Visible?", @@ -110,7 +110,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, () => settings?.EngineRosterFuelColumnSettings?.EngineRosterShowsFuelStatusAlways ?? false, delegate (bool enabled) { - if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList(), EngineRosterFuelColumnSettings = new() }; + if (settings == null) settings = new(); settings.EngineRosterFuelColumnSettings.EngineRosterShowsFuelStatusAlways = enabled; builder.Rebuild(); } @@ -129,12 +129,25 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, () => settings?.HandBrakeAndAirTagModifiers ?? false, delegate (bool enabled) { - if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() }; + if (settings == null) settings = new(); settings.HandBrakeAndAirTagModifiers = enabled; builder.Rebuild(); } ) ).Tooltip("Enable Tag Updates", $"Will add {TextSprites.CycleWaybills} to the car tag title having Air System issues. Also prepends {TextSprites.HandbrakeWheel} if there is a handbrake set.\n\nHolding Left Alt while tags are displayed only shows tag titles that have issues."); + + builder.AddField( + "Caboose Use", + builder.AddToggle( + () => settings?.EndGearHelpersRequirePayment ?? false, + delegate (bool enabled) + { + if (settings == null) settings = new(); + settings.EndGearHelpersRequirePayment = enabled; + builder.Rebuild(); + } + ) + ).Tooltip("Enable End Gear Helper Cost", $"Will cost 1 minute of AI Brake Crew & Caboose Crew time per car in the consist when the new inspector buttons are utilized.\n\n1.5x multiplier penalty to AI Brake Crew cost if no sufficiently crewed caboose nearby.\n\nCaboose starts reloading `Crew Hours` at any Team or Repair track (no waybill), after being stationary for 30 seconds."); }); } @@ -153,7 +166,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, () => settings?.WebhookSettingsList[z]?.WebhookEnabled ?? false, delegate (bool enabled) { - if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() }; + if (settings == null) settings = new(); settings.WebhookSettingsList[z].WebhookEnabled = enabled; settings.AddAnotherRow(); builder.Rebuild(); @@ -169,7 +182,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, settings?.WebhookSettingsList[z]?.RailroadMark, delegate (string railroadMark) { - if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() }; + if (settings == null) settings = new(); settings.WebhookSettingsList[z].RailroadMark = railroadMark; settings.AddAnotherRow(); builder.Rebuild(); @@ -185,7 +198,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase, settings?.WebhookSettingsList[z]?.WebhookUrl, delegate (string webhookUrl) { - if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() }; + if (settings == null) settings = new(); settings.WebhookSettingsList[z].WebhookUrl = webhookUrl; settings.AddAnotherRow(); builder.Rebuild(); diff --git a/TweaksAndThings/mroc-cabeese.json b/TweaksAndThings/mroc-cabeese.json new file mode 100644 index 0000000..e65dc55 --- /dev/null +++ b/TweaksAndThings/mroc-cabeese.json @@ -0,0 +1,23 @@ +{ + "objects": [ + { + "$find": [ + { + "path": "definition.archetype", + "value": "Caboose" + } + ], + "definition": { + "loadSlots": [ + { + "$add": { + "maximumCapacity": 8, + "loadUnits": "Quantity", + "requiredLoadIdentifier": "crew-hours" + } + } + ] + } + } + ] +} \ No newline at end of file