This commit is contained in:
2025-07-19 13:36:26 -05:00
parent 3f2e3dee80
commit 26a3bdaaa1
10 changed files with 296 additions and 34 deletions

View File

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

View File

@@ -2,12 +2,14 @@
using Game.State;
using Helpers;
using Model;
using Model.Definition;
using Model.Definition.Data;
using Model.Ops;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using UnityEngine;
namespace RMROC451.TweaksAndThings.Extensions;
@@ -62,10 +64,16 @@ public static class Car_Extensions
public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose;
public static bool IsCabooseAndStoppedForLoadRefresh(this Car car) => car.IsCaboose() && car.IsStopped(30f);
public static bool IsCabooseAndStoppedForLoadRefresh(this Car car, bool isFull) => car.IsCaboose() && car.IsStopped(30f) && !isFull;
public static Car? CabooseInConsist(this IEnumerable<Car> input) => input.FirstOrDefault(c => c.IsCaboose());
public static bool ConsistNoFreight(this IEnumerable<Car> input) =>
input.Where(c => !c.IsLocomotive).Any() &&
input
.Where(c => !c.IsLocomotive)
.All(c => !c.Archetype.IsFreight());
public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, HashSet<string> carIdsCheckedAlready, bool decrement = false)
{
Car? output = null;

View File

@@ -0,0 +1,34 @@
using RMROC451.TweaksAndThings.Patches;
using Serilog;
using UnityEngine;
using ILogger = Serilog.ILogger;
namespace RMROC451.TweaksAndThings.Extensions;
public static class TextSprite_Extensions
{
public static string TriColorPiePercent(this float quantity, float capacity)
{
int num;
if (capacity <= 0f)
{
num = 0;
}
else
{
float num2 = Mathf.Clamp01(quantity / capacity);
int num3 = ((!(num2 < 0.01f)) ? ((!(num2 > 0.99f)) ? (Mathf.FloorToInt(num2 * 15f) + 1) : 16) : 0);
num = num3;
}
string color = "#219106"; //Green
if (num > 5 && num <= 10)
{
color = "#CE8326"; //orange
} else if (num <= 5)
{
color = "#D53427"; //Red
}
return $"<sprite tint=1 color={color} name=Pie{num:D2}>";
}
}

View File

@@ -0,0 +1,52 @@
using HarmonyLib;
using Railloader;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UI.Builder;
using UI.PreferencesWindow;
using UnityEngine.InputSystem;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(BindingsWindow))]
[HarmonyPatch(nameof(BindingsWindow.Build), typeof(UIPanelBuilder))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class BindingsWindow_Build_Patch
{
public static bool Prefix(BindingsWindow __instance, UIPanelBuilder builder)
{
return true;
//TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
//if (!tweaksAndThings.IsEnabled) return true;
//(string title, InputAction[] actions)[] rebindableActions = BindingsWindow.RebindableActions;
//HashSet<InputAction> conflicts = BindingsWindow.FindConflicts(rebindableActions.SelectMany(((string title, InputAction[] actions) t) => t.actions));
//__instance._conflicts = conflicts;
//__instance._builder = builder;
//builder.AddTabbedPanels(__instance._selectedTabState, delegate (UITabbedPanelBuilder uITabbedPanelBuilder)
//{
// (string, InputAction[])[] array = rebindableActions;
// for (int i = 0; i < array.Length; i++)
// {
// var (text, actions) = array[i];
// uITabbedPanelBuilder.AddTab(text, text, delegate (UIPanelBuilder uIPanelBuilder)
// {
// uIPanelBuilder.VScrollView(delegate (UIPanelBuilder uIPanelBuilder2)
// {
// InputAction[] array2 = actions;
// foreach (InputAction val in array2)
// {
// //uIPanelBuilder2.AddInputBindingControl(val, conflicts.Contains(val), DidRebind);
// }
// });
// });
// }
//});
//return false;
}
}

View File

@@ -173,20 +173,7 @@ internal class CarInspector_PopulateCarPanel_Patch
case MrocHelperType.GladhandAndAnglecock:
consist.Do(c =>
ends.Do(end =>
{
EndGear endGear = c[end];
StateManager.ApplyLocal(
new PropertyChange(
c.id,
KeyValueKeyFor(EndGearStateKey.Anglecock, c.LogicalToEnd(end)),
new FloatPropertyValue(endGear.IsCoupled ? 1f : 0f)
)
);
if (c.TryGetAdjacentCar(end, out Model.Car c2)) StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true));
})
CarEndAirUpdate(c)
);
break;
@@ -201,7 +188,25 @@ internal class CarInspector_PopulateCarPanel_Patch
}
}
private static void CalculateCostIfEnabled(Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost, IEnumerable<Car> consist)
internal static void CarEndAirUpdate(Car c)
{
ends.Do(end =>
{
EndGear endGear = c[end];
StateManager.ApplyLocal(
new PropertyChange(
c.id,
KeyValueKeyFor(EndGearStateKey.Anglecock, c.LogicalToEnd(end)),
new FloatPropertyValue(endGear.IsCoupled ? 1f : 0f)
)
);
if (c.TryGetAdjacentCar(end, out Model.Car c2)) StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true));
});
}
internal static void CalculateCostIfEnabled(Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost, IEnumerable<Car> consist)
{
if (buttonsHaveCost)
{

View File

@@ -1,16 +1,34 @@
using HarmonyLib;
using Core;
using HarmonyLib;
using Model;
using Network;
using Railloader;
using RMROC451.TweaksAndThings.Enums;
using RMROC451.TweaksAndThings.Extensions;
using RollingStock;
using Serilog;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using UI;
using UnityEngine;
namespace RMROC451.TweaksAndThings.Patches;
enum MrocAction
{
Follow,
Inspect,
ConnectConsistAir,
ToggleConsistBrakes
}
[HarmonyPatch(typeof(CarPickable))]
[HarmonyPatch(nameof(CarPickable.Activate), typeof(PickableActivateEvent))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class CarPickable_Activate_Patch
{
private static Serilog.ILogger _log => Log.ForContext<CarPickable_Activate_Patch>();
static float clicked = 0;
static float clicktime = 0;
@@ -20,20 +38,111 @@ internal class CarPickable_Activate_Patch
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled) return true;
bool bCtrlAltHeld = GameInput.IsControlDown && GameInput.IsAltDown;
if (OnPointerDown(evt))
_log.ForContext("car", __instance.car).Information($"{GameInput.IsShiftDown} {GameInput.IsControlDown} {GameInput.IsAltDown} {bCtrlAltHeld} ");
//On Double click of a car: follow
if (OnPointerDown(evt, PickableActivation.Primary))
{
CameraSelector.shared.FollowCar(__instance.car);
_log.ForContext("car", __instance.car).Information("just click!");
return false;
}
//single click with keys pressed:
else if (evt.Activation == PickableActivation.Primary)
{
bool output = true;
var consist = __instance.car.EnumerateCoupled();
bool handbrakesApplied = consist.Any(c => c.HandbrakeApplied());
bool airSystemIssues = consist.Any(c => c.EndAirSystemIssue());
bool needsOiling = GameInput.IsShiftDown && consist.All(c => c.IsStopped()) && consist.Any(c => c.NeedsOiling || c.HasHotbox) && (consist.CabooseInConsist() || !tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter());
var chargeIt = handbrakesApplied || airSystemIssues || needsOiling;
//CTRL + ALT + SHIFT : BrakesAngleCocksAndOiling
//CTRL + ALT : Release Consist Brakes and Check AngleCocks
//CTRL + SHIFT : Toggle Consist Brakes
//CTRL : Toggle Car Brakes & Airup Car
//ALT + SHIFT : Check Consist AngleCocks
//ALT : Open Inspector to Car
if (bCtrlAltHeld)
{
BrakesAngleCocksAndOiling(__instance, tweaksAndThings, GameInput.IsShiftDown, consist, handbrakesApplied, airSystemIssues, needsOiling, chargeIt);
_log.ForContext("car", __instance.car).Information($"ctrlAlt{(GameInput.IsShiftDown ? "shift" : string.Empty)}Held!");
output = false;
}
else if (GameInput.IsControlDown && GameInput.IsShiftDown)
{
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment());
_log.ForContext("car", __instance.car).Information("ctrlShiftHeld!");
output = false;
}
else if (GameInput.IsAltDown && GameInput.IsShiftDown)
{
if (airSystemIssues)
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment());
_log.ForContext("car", __instance.car).Information("altShiftHeld!");
output = false;
}
else if (GameInput.IsAltDown)
{
UI.CarInspector.CarInspector.Show(__instance.car);
_log.ForContext("car", __instance.car).Information("altHeld!");
output = false;
}
else if (GameInput.IsControlDown)
{
__instance.car.SetHandbrake(!__instance.car.HandbrakeApplied());
CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(__instance.car);
_log.ForContext("car", __instance.car).Information("ctrlHeld!");
output = false;
}
return output;
//else if (ctrlHeld && shiftHeld)
//{
// var selected = UI.CarInspector.CarInspector._instance._selectedTabState.Value;
// UI.CarInspector.CarInspector.Show(__instance.car);
// UI.CarInspector.CarInspector._instance._selectedTabState.Value = selected;
//}
}
return true;
}
public static bool OnPointerDown(PickableActivateEvent evt)
private static void BrakesAngleCocksAndOiling(CarPickable __instance, TweaksAndThingsPlugin tweaksAndThings, bool shiftHeld, System.Collections.Generic.IEnumerable<Car> consist, bool handbrakesApplied, bool airSystemIssues, bool needsOiling, bool chargeIt)
{
int hbFix = 0;
if (handbrakesApplied)
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Handbrake, false);
if (airSystemIssues)
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, false);
if (needsOiling)
{
foreach (var car in consist.Where(c => c.NeedsOiling || c.HasHotbox))
{
float num2 = 1f - car.Oiled;
car.OffsetOiled(num2);
hbFix += car.HasHotbox ? 1 : 0;
if (car.HasHotbox) car.AdjustHotboxValue(0f);
}
if (hbFix > 0)
Multiplayer.Broadcast($"Near {Hyperlink.To(__instance.car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\"");
}
if (chargeIt)
CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist);
}
public static bool OnPointerDown(
PickableActivateEvent evt,
PickableActivation btn = PickableActivation.Primary
)
{
bool output = false;
if (evt.Activation == PickableActivation.Primary)
if (evt.Activation == btn)
{
clicked++;
if (clicked == 1) clicktime = Time.time;

View File

@@ -14,6 +14,7 @@ using UI;
using UI.EngineRoster;
using UI.Tooltips;
using UnityEngine;
using Game.State;
namespace RMROC451.TweaksAndThings.Patches;
@@ -28,6 +29,9 @@ internal class EngineRosterRow_Refresh_Patch
TweaksAndThingsPlugin? tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
RosterFuelColumnSettings? rosterFuelColumnSettings = tweaksAndThings?.settings?.EngineRosterFuelColumnSettings;
string fuelInfoText = string.Empty;
string fuelInfoTooltip = string.Empty;
if (tweaksAndThings == null ||
rosterFuelColumnSettings == null ||
!tweaksAndThings.IsEnabled ||
@@ -38,8 +42,9 @@ internal class EngineRosterRow_Refresh_Patch
try
{
string fuelInfoText = string.Empty;
string fuelInfoTooltip = string.Empty;
IEnumerable<Car> consist = __instance._engine.EnumerateCoupled().Where(c => c.EnableOiling);
bool cabooseRequirementFulfilled = consist.CabooseInConsist() || !tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || consist.ConsistNoFreight();
float offendingPercentage = 0;
Car engineOrTender = __instance._engine;
List<LoadSlot> loadSlots = __instance._engine.Definition.LoadSlots;
if (!loadSlots.Any())
@@ -57,12 +62,42 @@ internal class EngineRosterRow_Refresh_Patch
{
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
var fuelLevel = FuelLevel(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
offendingPercentage = CalcPercentLoad(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
fuelInfoText += loadSlots[i].RequiredLoadIdentifier == offender ? fuelLevel + " " : string.Empty;
//fuelInfoText += TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity) + " ";
fuelInfoTooltip += $"{TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity)} {valueOrDefault.LoadString(CarPrototypeLibrary.instance.LoadForId(valueOrDefault.LoadId))}\n";
}
}
try
{
if (cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature)
{
float lowestOilLevel = consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled;
var oilLevel = FuelLevel(lowestOilLevel, 1);
fuelInfoTooltip += $"{lowestOilLevel.TriColorPiePercent(1)} {oilLevel} Consist Oil Lowest Level\n";
if (CalcPercentLoad(lowestOilLevel, 1) < offendingPercentage)
{
fuelInfoText = $"{oilLevel} ";
}
if (consist.Any(c => c.HasHotbox))
{
fuelInfoText = $"{TextSprites.Hotbox} ";
fuelInfoTooltip = $"{TextSprites.Hotbox} Hotbox detected!\n{fuelInfoTooltip}";
}
}
else if (!cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature)
{
fuelInfoTooltip += $"Add Caboose To Consist For Consist Oil Level Reporting\n";
}
}
catch (Exception ex)
{
Log.Error(ex, "Error Detecting oiling consist status for engine roster");
}
switch (rosterFuelColumnSettings.EngineRosterFuelStatusColumn)
{
case EngineRosterFuelDisplayColumn.Engine:
@@ -77,7 +112,6 @@ internal class EngineRosterRow_Refresh_Patch
default:
break;
}
} catch (Exception ex)
{
rosterFuelColumnSettings.EngineRosterFuelStatusColumn = EngineRosterFuelDisplayColumn.None;
@@ -91,10 +125,15 @@ internal class EngineRosterRow_Refresh_Patch
tooltip.TooltipInfo = new TooltipInfo(tooltip.tooltipTitle, fuelInfoTooltip);
}
public static string FuelLevel(float quantity, float capacity)
public static float CalcPercentLoad(float quantity, float capacity)
{
float num = capacity <= 0f ? 0 : Mathf.Clamp(quantity / capacity * 100, 0, 100);
return $"{Mathf.FloorToInt(num):D2}%";
return num;
}
public static string FuelLevel(float quantity, float capacity)
{
return $"{Mathf.FloorToInt(CalcPercentLoad(quantity, capacity)):D2}%";
}
}

View File

@@ -85,12 +85,13 @@ internal class OpsController_AnnounceCoalescedPayments_Patch
float rate2 = 24 * 2 * 8;// carLoadRate = 8 crew hours in 30 min loading; (24h * 2 to get half hour chunks * 8 hours to load in those chunks)
float num2 = 99999999; //QuantityInStorage for crew-hours (infinite where crew can be shuffling about)
float quantityToLoad = Mathf.Min(num2, IndustryComponent.RateToValue(rate2, deltaTime));
OpsCarAdapter? oca = car.IsCaboose() ? new OpsCarAdapter(car, OpsController.Shared) : null;
bool isFull = !car.IsCaboose() ? true : (oca?.IsFull(CrewHoursLoad()) ?? true);
if (car.IsCaboose() && !CrewCarStatus(car).spotted)
{
CrewCarDict[car.id] = (true, CrewCarDict[car.id].filling);
CrewCarDict[car.id] = (true, !isFull);
}
if (car.IsCabooseAndStoppedForLoadRefresh())
if (car.IsCabooseAndStoppedForLoadRefresh(isFull))
{
if (!CrewCarDict[car.id].filling) Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Topping off caboose crew.\"");
CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, true);
@@ -100,7 +101,7 @@ internal class OpsController_AnnounceCoalescedPayments_Patch
Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\"");
CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, false);
}
new OpsCarAdapter(car, OpsController.Shared).Load(CrewHoursLoad(), quantityToLoad);
(oca ?? new OpsCarAdapter(car, OpsController.Shared)).Load(CrewHoursLoad(), quantityToLoad);
}
}

View File

@@ -1,8 +1,10 @@
using HarmonyLib;
using Game.State;
using HarmonyLib;
using Model;
using Model.Ops;
using Railloader;
using RMROC451.TweaksAndThings.Extensions;
using Serilog;
using System.Collections.Generic;
using System.Linq;
using UI.Tags;
@@ -26,18 +28,23 @@ internal class TagController_UpdateTag_Patch
return;
}
ProceedWithPostFix(car, tagCallout);
ProceedWithPostFix(car, tagCallout, tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter());
return;
}
private static void ProceedWithPostFix(Car car, TagCallout tagCallout)
private static void ProceedWithPostFix(Car car, TagCallout tagCallout, bool cabooseRequired)
{
tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", car.DisplayName);
List<string> tags = new();
if (OpsController_AnnounceCoalescedPayments_Patch.CrewCarStatus(car).spotted) tags.Add("+");
if (car.HasHotbox) tags.Add(TextSprites.Hotbox);
//if (car.EnableOiling) tags.Add(car.HasHotbox ? TextSprites.Hotbox : $"<cspace=-1em>{TextSprites.Warning}{car.Oiled.TriColorPiePercent(1)}</cspace>");
if (car.EnableOiling) tags.Add(car.HasHotbox ? TextSprites.Hotbox : car.Oiled.TriColorPiePercent(1));
IEnumerable<Car> consist = car.EnumerateCoupled().Where(c => c.EnableOiling);
bool cabooseRequirementFulfilled = consist.CabooseInConsist() || !cabooseRequired || consist.ConsistNoFreight();
if (cabooseRequirementFulfilled && car.IsLocomotive && !car.NeedsOiling && StateManager.Shared.Storage.OilFeature && consist.Any(c => c.NeedsOiling))
tags.Add(consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled.TriColorPiePercent(1));
if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills);
if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel);

View File

@@ -4,6 +4,9 @@
<!-- <MajorVersion>1</MajorVersion> -->
<!-- <MinorVersion>0</MinorVersion> -->
</PropertyGroup>
<ItemGroup>
<Compile Remove="Patches\BindingsWindow_Patches.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="mroc-cabeese.json" />
</ItemGroup>
@@ -22,8 +25,12 @@
<GameAssembly Include="Ops" />
<GameAssembly Include="StrangeCustoms" />
<GameAssembly Include="Unity.InputSystem" />
<GameAssembly Include="UnityEngine.CoreModule" />
<GameAssembly Include="UnityEngine.UI" />
<GameAssembly Include="UnityEngine.UIModule" />
<GameAssembly Include="UnityEngine.InputLegacyModule" />
<GameAssembly Include="UnityEngine.InputModule" />
<GameAssembly Include="Unity.TextMeshPro" />
<GameAssembly Include="System.Net.Http" />