Compare commits

...

14 Commits

Author SHA1 Message Date
80f0847587 reverting updated context menu visualizations, and allowing the even spacing of items vs quadrant concept. Pesky double circle context menu borders still remain, but I'm working on it. 2025-03-02 12:43:08 -06:00
7552422b6e disabling context menu dividers and expanding the context menu radius to 200f to help spread things out. 2025-02-22 23:43:00 -06:00
58dc7efac0 adjusting CarIdsInRect -> CarIdsInRadius 2025-02-16 09:41:58 -06:00
7ebd14db2a adjusting railroader usage of namespace Model.OpsNew -> Model.Ops and some adjustments to the version files. 2024-12-24 22:11:22 -06:00
ca0e78b971 hotfix for car inspector rebuild 2024-08-03 00:55:43 -05:00
9f210b0b8a increasing version number. 2024-08-01 15:55:29 -05:00
840f35cf62 Merge pull request #38 from rmroc451/37-passenger-stops-not-filling-crew-hours-on-cabeese
#37 refactored caboose crew hours load refill logic, condensing and a…
2024-07-28 12:11:16 -05:00
786db49b68 #37 refactored caboose crew hours load refill logic, condensing and also allowing passenger stops to work as they were expected to. 2024-07-28 08:13:21 -05:00
108900b026 Merge pull request #36 from rmroc451/33-double-click-to-follow-car
#33 double primary click any car/loco to follow
2024-07-27 22:33:18 -05:00
9e8c38e6f4 Merge pull request #35 from rmroc451/26-caboose-auto-heal-hotbox-when-fully-oiled
#26 auto heal hotbox when found if caboose use is enabled and caboose…
2024-07-27 22:32:49 -05:00
89f2490f14 Merge pull request #34 from rmroc451/32-buttons-dont-adjust-when-decoupling-as-expected
#32 swap from integration set that was giving inconsistent results, a…
2024-07-27 22:32:20 -05:00
fbd08b007c #33 double primary click any car/loco to follow 2024-07-27 22:31:33 -05:00
27d3432cbf #26 auto heal hotbox when found if caboose use is enabled and caboose is present in consist. 2024-07-27 22:30:40 -05:00
cf1d5e32e5 #32 swap from integration set that was giving inconsistent results, and adding in a panel.reubuildOnInterval when an observed value changes. 2024-07-27 22:25:23 -05:00
20 changed files with 457 additions and 186 deletions

7
Assembly.version Normal file
View File

@@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<MajorVersion>1</MajorVersion>
<MinorVersion>2</MinorVersion>
<PatchVersion>4</PatchVersion>
</PropertyGroup>
</Project>

View File

@@ -1,5 +1,6 @@
<Project> <Project>
<Import Project="Paths.user" Condition="Exists('Paths.user')" /> <Import Project="Paths.user" Condition="Exists('Paths.user')" />
<Import Project="Assembly.version" Condition="Exists('Assembly.version')" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>net48</TargetFramework> <TargetFramework>net48</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>

View File

@@ -12,8 +12,8 @@
<!-- Replace the default version if something was set for it --> <!-- Replace the default version if something was set for it -->
<PropertyGroup Condition="'$(AssemblyVersion)' == '' OR '$(MajorVersion)' != '' OR '$(MinorVersion)' != ''"> <PropertyGroup Condition="'$(AssemblyVersion)' == '' OR '$(MajorVersion)' != '' OR '$(MinorVersion)' != ''">
<MajorVersion Condition="'$(MajorVersion)' == ''">1</MajorVersion> <MajorVersion Condition="'$(MajorVersion)' == ''">1</MajorVersion>
<MinorVersion Condition="'$(MinorVersion)' == ''">0</MinorVersion> <MinorVersion Condition="'$(MinorVersion)' == ''">1</MinorVersion>
<PatchVersion Condition="'$(PatchVersion)' == ''">0</PatchVersion> <PatchVersion Condition="'$(PatchVersion)' == ''">1</PatchVersion>
<AssemblyVersion>$(MajorVersion).$(MinorVersion).$(PatchVersion)</AssemblyVersion> <AssemblyVersion>$(MajorVersion).$(MinorVersion).$(PatchVersion)</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion> <FileVersion>$(AssemblyVersion)</FileVersion>
<ProductVersion>$(AssemblyVersion)</ProductVersion> <ProductVersion>$(AssemblyVersion)</ProductVersion>
@@ -39,7 +39,7 @@
<!-- Folder we'll put the published zips into --> <!-- Folder we'll put the published zips into -->
<PublishPath>../bin</PublishPath> <PublishPath>../bin</PublishPath>
<ZipName>$(PublishPath)/$(AssemblyName)_$(AssemblyVersion).zip</ZipName> <ZipName>$(PublishPath)/$(AssemblyName)_$(AssemblyVersion)_Experimental.zip</ZipName>
</PropertyGroup> </PropertyGroup>
<!-- Assure the output path exists --> <!-- Assure the output path exists -->

6
Paths.user.example Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<!-- Directory that the game (Railroader.exe) is in -->
<GameDir></GameDir>
</PropertyGroup>
</Project>

View File

@@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{452A23A6-81C8-49C6-A7EE-95FD9377F896}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{452A23A6-81C8-49C6-A7EE-95FD9377F896}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore .gitignore = .gitignore
Assembly.version = Assembly.version
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets Directory.Build.targets = Directory.Build.targets
Paths.user = Paths.user Paths.user = Paths.user

View File

@@ -1,6 +1,6 @@
using Game.State; using Game.State;
using Helpers; using Helpers;
using Model.OpsNew; using Model.Ops;
using Network; using Network;
using RMROC451.TweaksAndThings.Extensions; using RMROC451.TweaksAndThings.Extensions;
using System.Linq; using System.Linq;

View File

@@ -5,10 +5,17 @@
"version": "$(AssemblyVersion)", "version": "$(AssemblyVersion)",
"requires": [ "requires": [
{ {
"id": "railloader", "id": "railroader",
"notBefore": "1.8.1" "notBefore": "2024.6"
}, },
"Zamu.StrangeCustoms" {
"id": "railloader",
"notBefore": "1.9.6.14"
},
{
"id": "Zamu.StrangeCustoms",
"notBefore": "1.10.25017.313"
}
], ],
"assemblies": [ "RMROC451.TweaksAndThings" ], "assemblies": [ "RMROC451.TweaksAndThings" ],
"mixintos": { "mixintos": {

View File

@@ -6,22 +6,22 @@ namespace RMROC451.TweaksAndThings.Extensions
{ {
internal static class AutoEngineer_Extensions internal static class AutoEngineer_Extensions
{ {
private static float CabooseHalvedFloat(this float input, bool hasCaboose) => private static float CabooseHalvedFloat(this float input, Model.Car? hasCaboose) =>
hasCaboose ? input / 2 : input; hasCaboose ? input / 2 : input;
private static float CabooseAutoOilerLimit(this bool hasCaboose) => private static float CabooseAutoOilerLimit(this Model.Car? caboose) =>
hasCaboose ? 0.99f : AutoOiler.OilIfBelow; caboose ? 0.99f : AutoOiler.OilIfBelow;
public static IEnumerator MrocAutoOilerLoop(this AutoOiler oiler, Serilog.ILogger _log, bool cabooseRequired) public static IEnumerator MrocAutoOilerLoop(this AutoOiler oiler, Serilog.ILogger _log, bool cabooseRequired)
{ {
int originIndex = oiler.FindOriginIndex(); int originIndex = oiler.FindOriginIndex();
bool hasCaboose = oiler._cars.CabooseInConsist(); Model.Car? foundCaboose = oiler._cars.CabooseInConsist();
if (originIndex < 0) if (originIndex < 0)
{ {
_log.Error("Couldn't find origin car {car}", oiler._originCar); _log.Error("Couldn't find origin car {car}", oiler._originCar);
oiler._coroutine = null; oiler._coroutine = null;
yield break; yield break;
} else if (CabooseRequirementChecker(string.Format("{0} {1}", oiler.GetType().Name, oiler.name), cabooseRequired, hasCaboose, _log)) } else if (CabooseRequirementChecker(string.Format("{0} {1}", oiler.GetType().Name, oiler.name), cabooseRequired, foundCaboose, _log))
{ {
yield break; yield break;
} }
@@ -31,30 +31,35 @@ namespace RMROC451.TweaksAndThings.Extensions
oiler.name, oiler.name,
oiler._reverse, oiler._reverse,
cabooseRequired, cabooseRequired,
hasCaboose, foundCaboose,
hasCaboose.CabooseAutoOilerLimit() foundCaboose.CabooseAutoOilerLimit()
); );
while (true) while (true)
{ {
yield return new WaitForSeconds(AutoOiler.StartDelay.CabooseHalvedFloat(hasCaboose)); yield return new WaitForSeconds(AutoOiler.StartDelay.CabooseHalvedFloat(foundCaboose));
int carIndex = originIndex; int carIndex = originIndex;
float adjustedTimeToWalk = AutoOiler.TimeToWalkCar.CabooseHalvedFloat(hasCaboose); float adjustedTimeToWalk = AutoOiler.TimeToWalkCar.CabooseHalvedFloat(foundCaboose);
do do
{ {
if (oiler.TryGetCar(carIndex, out var car)) if (oiler.TryGetCar(carIndex, out var car))
{ {
float num = 0f; float num = 0f;
float origOil = car.Oiled; float origOil = car.Oiled;
if (car.NeedsOiling && car.Oiled < hasCaboose.CabooseAutoOilerLimit()) if (car.NeedsOiling && car.Oiled < foundCaboose.CabooseAutoOilerLimit())
{ {
float num2 = 1f - car.Oiled; float num2 = 1f - car.Oiled;
car.OffsetOiled(num2); car.OffsetOiled(num2);
float num3 = num2 * AutoOiler.TimeToFullyOil.CabooseHalvedFloat(hasCaboose); float num3 = num2 * AutoOiler.TimeToFullyOil.CabooseHalvedFloat(foundCaboose);
num += num3; num += num3;
oiler._pendingRunDuration += num3; oiler._pendingRunDuration += num3;
oiler._oiledCount++; oiler._oiledCount++;
_log.Information("AutoOiler {name}: oiled {car} from {orig} => {new}", oiler.name, car, origOil, car.Oiled); _log.Information("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);
car.AdjustHotboxValue(0f);
}
num += adjustedTimeToWalk; num += adjustedTimeToWalk;
oiler._pendingRunDuration += adjustedTimeToWalk; oiler._pendingRunDuration += adjustedTimeToWalk;
yield return new WaitForSeconds(num); yield return new WaitForSeconds(num);
@@ -71,14 +76,16 @@ namespace RMROC451.TweaksAndThings.Extensions
{ {
while (true) while (true)
{ {
bool hasCaboose = spotter._cars.CabooseInConsist(); Model.Car? foundCaboose = spotter._cars.CabooseInConsist();
if (!spotter.HasCars) if (!spotter.HasCars)
{ {
yield return new WaitForSeconds(1f); yield return new WaitForSeconds(1f);
continue; continue;
} }
_log.Information("AutoHotboxSpotter {name}: Hotbox Spotter Running, Has Caboose => {hasCaboose}; Has Cars {hasCars}; Requires Caboose {requiresCaboose}", spotter.name, hasCaboose, spotter.HasCars, cabooseRequired); _log.Information("AutoHotboxSpotter {name}: Hotbox Spotter Running, Found Caboose => {hasCaboose}; Has Cars {hasCars}; Requires Caboose {requiresCaboose}",
if (CabooseRequirementChecker(string.Format("{0} {1}", spotter.GetType().Name, spotter.name), cabooseRequired, hasCaboose, _log)) spotter.name, foundCaboose, spotter.HasCars, cabooseRequired);
foundCaboose = spotter._cars.CabooseInConsist();
if (CabooseRequirementChecker(string.Format("{0} {1}", spotter.GetType().Name, spotter.name), cabooseRequired, foundCaboose, _log))
{ {
yield break; yield break;
} }
@@ -86,11 +93,12 @@ namespace RMROC451.TweaksAndThings.Extensions
while (spotter.HasCars) while (spotter.HasCars)
{ {
int num = Random.Range(60, 300); int num = Random.Range(60, 300);
if (hasCaboose) foundCaboose = spotter._cars.CabooseInConsist();
if (foundCaboose)
{ {
var numOrig = num; var numOrig = num;
num = Random.Range(15, 30); 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, hasCaboose, cabooseRequired); _log.Information("AutoHotboxSpotter {name}: Next check went from num(60,300) => {numOrig}; to num(15,30) => {hasCaboose}; Requires Caboose {requiresCaboose}", spotter.name, numOrig, num, foundCaboose, cabooseRequired);
} }
yield return new WaitForSeconds(num); yield return new WaitForSeconds(num);
spotter.CheckForHotbox(); spotter.CheckForHotbox();
@@ -98,9 +106,9 @@ namespace RMROC451.TweaksAndThings.Extensions
} }
} }
private static bool CabooseRequirementChecker(string name, bool cabooseRequired, bool hasCaboose, Serilog.ILogger _log) private static bool CabooseRequirementChecker(string name, bool cabooseRequired, Model.Car? foundCaboose, Serilog.ILogger _log)
{ {
bool error = cabooseRequired && !hasCaboose; bool error = cabooseRequired && foundCaboose == null;
if (error) { if (error) {
_log.Debug("{name}: Couldn't find required caboose!", name); _log.Debug("{name}: Couldn't find required caboose!", name);
} }

View File

@@ -1,7 +1,9 @@
using Helpers; using Game.Messages;
using Game.State;
using Helpers;
using Model; using Model;
using Model.Definition.Data; using Model.Definition.Data;
using Model.OpsNew; using Model.Ops;
using Serilog; using Serilog;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -60,7 +62,9 @@ public static class Car_Extensions
public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose; public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose;
public static bool CabooseInConsist(this IEnumerable<Car> input) => input.FirstOrDefault(c => c.IsCaboose()); public static bool IsCabooseAndStoppedForLoadRefresh(this Car car) => car.IsCaboose() && car.IsStopped(30f);
public static Car? CabooseInConsist(this IEnumerable<Car> input) => input.FirstOrDefault(c => c.IsCaboose());
public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, HashSet<string> carIdsCheckedAlready, bool decrement = false) public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, HashSet<string> carIdsCheckedAlready, bool decrement = false)
{ {
@@ -105,8 +109,7 @@ public static class Car_Extensions
{ {
Vector3 position = car.GetMotionSnapshot().Position; Vector3 position = car.GetMotionSnapshot().Position;
Vector3 center = WorldTransformer.WorldToGame(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.CarIdsInRadius(center, 60f);
var cars = tc.CarIdsInRect(rect);
Log.Information($"{nameof(HuntingForCabeeseNearCar)} => {cars.Count()}"); Log.Information($"{nameof(HuntingForCabeeseNearCar)} => {cars.Count()}");
List<(string carId, float distance)> source = List<(string carId, float distance)> source =
cars cars
@@ -122,4 +125,12 @@ public static class Car_Extensions
}).ToList(); }).ToList();
return source; return source;
} }
public static void AdjustHotboxValue(this Car car, float hotboxValue) =>
StateManager.ApplyLocal(
new PropertyChange(
car.id, PropertyChange.KeyForControl(PropertyChange.Control.Hotbox),
new FloatPropertyValue(hotboxValue)
)
);
} }

View File

@@ -43,11 +43,11 @@ internal class CarInspector_PopulateCarPanel_Patch
if (!tweaksAndThings.IsEnabled) return true; if (!tweaksAndThings.IsEnabled) return true;
bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment(); bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment();
var consist = __instance._car._set.Cars; var consist = __instance._car.EnumerateCoupled();
builder = AddCarConsistRebuildObservers(builder, consist);
builder.HStack(delegate (UIPanelBuilder hstack) builder.HStack(delegate (UIPanelBuilder hstack)
{ {
hstack = AddCarConsistRebuildObservers(hstack, consist);
var buttonName = $"{(consist.Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} {TextSprites.HandbrakeWheel}"; var buttonName = $"{(consist.Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} {TextSprites.HandbrakeWheel}";
hstack.AddButtonCompact(buttonName, delegate hstack.AddButtonCompact(buttonName, delegate
{ {
@@ -123,7 +123,7 @@ internal class CarInspector_PopulateCarPanel_Patch
try try
{ {
builder.Rebuild(); builder.Rebuild();
if (car.TagCallout != null) tagController.UpdateTags(CameraSelector.shared._currentCamera.GroundPosition, true); //tagController.UpdateTag(car, car.TagCallout, OpsController.Shared); if (car.TagCallout != null) tagController.UpdateTags(CameraSelector.shared._currentCamera.GroundPosition, true);
if (ContextMenu.IsShown && ContextMenu.Shared.centerLabel.text == car.DisplayName) CarPickable.HandleShowContextMenu(car); if (ContextMenu.IsShown && ContextMenu.Shared.centerLabel.text == car.DisplayName) CarPickable.HandleShowContextMenu(car);
} }
catch (Exception ex) catch (Exception ex)
@@ -151,7 +151,7 @@ internal class CarInspector_PopulateCarPanel_Patch
public static void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost) public static void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost)
{ {
TrainController tc = UnityEngine.Object.FindObjectOfType<TrainController>(); TrainController tc = UnityEngine.Object.FindObjectOfType<TrainController>();
IEnumerable<Model.Car> consist = car._set.Cars; IEnumerable<Model.Car> consist = car.EnumerateCoupled();
_log.ForContext("car", car).Verbose($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}"); _log.ForContext("car", car).Verbose($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
CalculateCostIfEnabled(car, mrocHelperType, buttonsHaveCost, consist); CalculateCostIfEnabled(car, mrocHelperType, buttonsHaveCost, consist);
@@ -208,7 +208,7 @@ internal class CarInspector_PopulateCarPanel_Patch
float originalTimeCost = consist.CalculateCostForAutoEngineerEndGearSetting(); float originalTimeCost = consist.CalculateCostForAutoEngineerEndGearSetting();
float timeCost = originalTimeCost; float timeCost = originalTimeCost;
float crewCost = timeCost / 3600; //hours of time deducted from caboose. float crewCost = timeCost / 3600; //hours of time deducted from caboose.
var tsString = crewCost.FormatCrewHours(IndustryComponent_Service_Patch.CrewHoursLoad().description); var tsString = crewCost.FormatCrewHours(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad().description);
Car? cabooseWithAvailCrew = NearbyCabooseWithAvailableCrew(car, crewCost, buttonsHaveCost); Car? cabooseWithAvailCrew = NearbyCabooseWithAvailableCrew(car, crewCost, buttonsHaveCost);
if (cabooseWithAvailCrew == null) timeCost *= 1.5f; if (cabooseWithAvailCrew == null) timeCost *= 1.5f;
var cabooseFoundDisplay = cabooseWithAvailCrew?.DisplayName ?? "No caboose"; var cabooseFoundDisplay = cabooseWithAvailCrew?.DisplayName ?? "No caboose";
@@ -232,7 +232,7 @@ internal class CarInspector_PopulateCarPanel_Patch
carIdsCheckedAlready.Add(car.id); carIdsCheckedAlready.Add(car.id);
//check consist, for cabeese //check consist, for cabeese
IEnumerable<Car> consist = car._set.Cars; IEnumerable<Car> consist = car.EnumerateCoupled();
output = consist.FirstOrDefault(c => c.CabooseWithSufficientCrewHours(timeNeeded, carIdsCheckedAlready, decrement)); output = consist.FirstOrDefault(c => c.CabooseWithSufficientCrewHours(timeNeeded, carIdsCheckedAlready, decrement));
if (output != null) return output; //short out if we are good if (output != null) return output; //short out if we are good
carIdsCheckedAlready.UnionWith(consist.Select(c => c.id)); carIdsCheckedAlready.UnionWith(consist.Select(c => c.id));

View File

@@ -0,0 +1,52 @@
using HarmonyLib;
using Railloader;
using RollingStock;
using UnityEngine;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(CarPickable))]
[HarmonyPatch(nameof(CarPickable.Activate), typeof(PickableActivateEvent))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class CarPickable_Activate_Patch
{
static float clicked = 0;
static float clicktime = 0;
static float clickdelay = 0.5f;
private static bool Prefix(CarPickable __instance, PickableActivateEvent evt)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled) return true;
if (OnPointerDown(evt))
{
CameraSelector.shared.FollowCar(__instance.car);
return false;
}
return true;
}
public static bool OnPointerDown(PickableActivateEvent evt)
{
bool output = false;
if (evt.Activation == PickableActivation.Primary)
{
clicked++;
if (clicked == 1) clicktime = Time.time;
if (clicked > 1 && Time.time - clicktime < clickdelay)
{
clicked = 0;
clicktime = 0;
output = true;
}
else if (clicked > 2 || Time.time - clicktime > 1) clicked = 0;
}
return output;
}
}

View File

@@ -22,20 +22,20 @@ internal class CarPickable_HandleShowContextMenu_Patch
bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment(); bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment();
ContextMenu shared = ContextMenu.Shared; ContextMenu shared = ContextMenu.Shared;
shared.AddButton(ContextMenuQuadrant.Unused2, $"{(car._set.Cars.Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} Consist", SpriteName.Handbrake, delegate shared.AddButton(ContextMenuQuadrant.Unused1, $"{(car.EnumerateCoupled().Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} Consist", SpriteName.Handbrake, delegate
{ {
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost); CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost);
}); });
if (car._set.Cars.Any(c => c.EndAirSystemIssue())) if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
{ {
shared.AddButton(ContextMenuQuadrant.Unused2, $"Air Up Consist", SpriteName.Select, delegate shared.AddButton(ContextMenuQuadrant.Unused1, $"Air Up Consist", SpriteName.Select, delegate
{ {
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost); CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
}); });
} }
if (car._set.Cars.Any(c => c.SupportsBleed())) if (!car.EnumerateCoupled().Any(c => !c.SupportsBleed()))
{ {
shared.AddButton(ContextMenuQuadrant.Unused2, $"Bleed Consist", SpriteName.Bleed, delegate shared.AddButton(ContextMenuQuadrant.Unused2, $"Bleed Consist", SpriteName.Bleed, delegate
{ {
@@ -47,7 +47,6 @@ internal class CarPickable_HandleShowContextMenu_Patch
{ {
CameraSelector.shared.FollowCar(car); CameraSelector.shared.FollowCar(car);
}); });
shared.BuildItemAngles(); shared.BuildItemAngles();
shared.StartCoroutine(shared.AnimateButtonsShown()); shared.StartCoroutine(shared.AnimateButtonsShown());
} }

View File

@@ -0,0 +1,25 @@
using HarmonyLib;
using Railloader;
using System;
using UI.ContextMenu;
using UnityEngine;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(UI.ContextMenu.ContextMenu))]
//ContextMenuQuadrant quadrant, int index, float normalizedRadius = 1f
[HarmonyPatch(nameof(UI.ContextMenu.ContextMenu.PositionForItem), typeof(ContextMenuQuadrant), typeof(int), typeof(float))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class ContextMenu_PositionForItem_Patch
{
static void Postfix(UI.ContextMenu.ContextMenu __instance, ref Vector2 __result, ContextMenuQuadrant quadrant, int index, float normalizedRadius = 1f)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled) return;
float num = __instance._itemAngles[(quadrant, index)] * ((float)Math.PI / 180f);
float num2 = __instance.radius * normalizedRadius;
__result = new Vector2(1.3f * Mathf.Sin(num) * num2, Mathf.Cos(num) * num2);
}
}

View File

@@ -0,0 +1,127 @@
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<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled) return true;
if (!__instance.GetRootCanvas(out var rootCanvas))
{
Log.Warning("Couldn't get root canvas");
return true;
}
__instance.CancelHideCoroutine();
__instance.SetupTemplate(rootCanvas);
__instance.centerLabel.text = centerText;
Canvas componentInParent = ((Component)__instance.contentRectTransform).GetComponentInParent<Canvas>();
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<TweaksAndThingsPlugin>.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<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled) return true;
List<ContextMenuItem> list = __instance._quadrants[(int)quadrant];
int index = list.Count;
ContextMenuItem contextMenuItem = UnityEngine.Object.Instantiate<ContextMenuItem>(__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<LayoutElement>().preferredHeight = 30f;
list.Add(contextMenuItem);
return false;
}
}

View File

@@ -1,7 +1,7 @@
using HarmonyLib; using HarmonyLib;
using Model; using Model;
using Model.Definition.Data; using Model.Definition.Data;
using Model.OpsNew; using Model.Ops;
using Railloader; using Railloader;
using RMROC451.TweaksAndThings.Extensions; using RMROC451.TweaksAndThings.Extensions;
using Serilog; using Serilog;

View File

@@ -1,139 +0,0 @@
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<TweaksAndThingsPlugin>.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<TrainController>();
if (tc.TryGetCarForId(item.Id, out Car car))
{
List<LoadSlot> 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;
}
}

View File

@@ -0,0 +1,140 @@
using Game;
using Game.State;
using HarmonyLib;
using Model;
using Model.Definition.Data;
using Model.Ops.Definition;
using Model.Ops;
using Network;
using Railloader;
using RMROC451.TweaksAndThings.Extensions;
using RollingStock;
using Serilog;
using System;
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 == OpsController_AnnounceCoalescedPayments_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 = OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad();
if (loadId == load.id) __result = load;
return __result == null;
}
}
[HarmonyPatch(typeof(OpsController))]
[HarmonyPatch(nameof(OpsController.AnnounceCoalescedPayments))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class OpsController_AnnounceCoalescedPayments_Patch
{
static Dictionary<string, (bool spotted, bool filling)> CrewCarDict = new();
public static (bool spotted, bool filling) CrewCarStatus(Car car)
{
bool found = CrewCarDict.TryGetValue(car.id, out (bool spotted, bool filling) val);
if (!found) CrewCarDict.Add(car.id, (false, false));
return val;
}
static GameDateTime dateTime = TimeWeather.Now;
static readonly IEnumerable<Type> refillLocations =
new List<Type>() {
typeof(PassengerStop),
typeof(SimplePassengerStop),
typeof(TeamTrack),
typeof(RepairTrack)
};
public static Load CrewHoursLoad()
{
Load load = (Load)ScriptableObject.CreateInstance(typeof(Load));
load.name = "crew-hours";
load.description = "Crew";
load.units = LoadUnits.Quantity;
return load;
}
private static void CarLoadCrewHelper(Car car, float deltaTime)
{
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));
if (car.IsCaboose() && !CrewCarStatus(car).spotted)
{
CrewCarDict[car.id] = (true, CrewCarDict[car.id].filling);
}
if (car.IsCabooseAndStoppedForLoadRefresh())
{
if (!CrewCarDict[car.id].filling) Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Topping off caboose crew.\"");
CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, true);
var data = car.QuantityCapacityOfLoad(CrewHoursLoad());
if ((data.quantity + quantityToLoad > data.capacity) && data.quantity < data.capacity)
{
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);
}
}
public static bool Prefix(IndustryComponent __instance)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!StateManager.IsHost || !tweaksAndThings.IsEnabled || !(tweaksAndThings?.settings?.EndGearHelpersRequirePayment ?? false)) return true;
TrainController tc = UnityEngine.Object.FindAnyObjectByType<TrainController>();
try {
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)}");
var cabeese = passengerStops
.SelectMany(t => t.TrackSpans?.Select(s => (tc.CarsOnSpan(s) ?? Enumerable.Empty<Car>()).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}") ?? [])}");
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}");
CarLoadCrewHelper(caboose.c2, deltaTime);
}
dateTime = TimeWeather.Now;
} catch (System.Exception ex)
{
Log.Error(ex, "error with announce caboose helper");
}
return true;
}
}

View File

@@ -1,6 +1,6 @@
using HarmonyLib; using HarmonyLib;
using Model; using Model;
using Model.OpsNew; using Model.Ops;
using Railloader; using Railloader;
using RMROC451.TweaksAndThings.Extensions; using RMROC451.TweaksAndThings.Extensions;
using System.Collections.Generic; using System.Collections.Generic;
@@ -34,8 +34,9 @@ internal class TagController_UpdateTag_Patch
private static void ProceedWithPostFix(Car car, TagCallout tagCallout) private static void ProceedWithPostFix(Car car, TagCallout tagCallout)
{ {
tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", car.DisplayName); tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", car.DisplayName);
List<string> tags = []; List<string> tags = new();
if (OpsController_AnnounceCoalescedPayments_Patch.CrewCarStatus(car).spotted) tags.Add("+");
if (car.HasHotbox) tags.Add(TextSprites.Hotbox); if (car.HasHotbox) tags.Add(TextSprites.Hotbox);
if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills); if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills);
if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel); if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel);

View File

@@ -0,0 +1,20 @@
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<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled) return;
vh.Clear(); //clear the image backgrounds for now.
}
}

View File

@@ -90,10 +90,13 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
}); });
} }
private static string cabooseUse => "Caboose Use";
private static string autoAiRequirment => "AutoAI\nRequirement";
private void CabooseMods(UIPanelBuilder builder) private void CabooseMods(UIPanelBuilder builder)
{ {
builder.AddField( builder.AddField(
"Caboose Use", cabooseUse,
builder.AddToggle( builder.AddToggle(
() => settings?.EndGearHelpersRequirePayment ?? false, () => settings?.EndGearHelpersRequirePayment ?? false,
delegate (bool enabled) delegate (bool enabled)
@@ -109,12 +112,14 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
Caboose starts reloading `Crew Hours` at any Team or Repair track (no waybill), after being stationary for 30 seconds. Caboose starts reloading `Crew Hours` at any Team or Repair track (no waybill), after being stationary for 30 seconds.
AutoOiler Update: Increases limit that crew will oiling a car from 75% -> 99%, also halves the time it takes (simulating crew from lead end and caboose handling half the train) AutoOiler Update: Increases limit that crew will oiling a car from 75% -> 99%, also halves the time it takes (simulating crew from lead end and caboose handling half the train).
AutoOiler Update: if `{cabooseUse}` & `{autoAiRequirment.Replace("\n", " ")}` checked, then when a caboose is present, the AutoOiler will repair hotboxes afer oiling them to 100%.
AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - 30 seconds (Safety Is Everyone's Job)"); AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - 30 seconds (Safety Is Everyone's Job)");
builder.AddField( builder.AddField(
$"AutoAI\nRequirement", autoAiRequirment,
builder.AddToggle( builder.AddToggle(
() => settings?.RequireConsistCabooseForOilerAndHotboxSpotter ?? false, () => settings?.RequireConsistCabooseForOilerAndHotboxSpotter ?? false,
delegate (bool enabled) delegate (bool enabled)