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/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.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/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 d0ae00d..e5db62e 100644
--- a/TweaksAndThings/Extensions/Car_Extensions.cs
+++ b/TweaksAndThings/Extensions/Car_Extensions.cs
@@ -1,42 +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
{
- 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 e0e6970..127dc94 100644
--- a/TweaksAndThings/TweaksAndThingsPlugin.cs
+++ b/TweaksAndThings/TweaksAndThingsPlugin.cs
@@ -11,6 +11,7 @@ using System.Net.Http;
using UI.Builder;
using RMROC451.TweaksAndThings.Enums;
+using RMROC451.TweaksAndThings.Commands;
namespace RMROC451.TweaksAndThings;
@@ -40,12 +41,12 @@ 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);
- //moddingContext.RegisterConsoleCommand(new EchoCommand());
+ moddingContext.RegisterConsoleCommand(new EchoCommand());
settings = moddingContext.LoadSettingsData(self.Id);
}
@@ -54,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()
@@ -96,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?",
@@ -109,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();
}
@@ -128,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.");
});
}
@@ -152,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();
@@ -168,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();
@@ -184,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