Merge pull request #18 from rmroc451/13-add-buttonconsole-command-to-initiate-a-console-message-logging-with-selected-engine

Engine based status messages & Caboose Crew time Resource
This commit is contained in:
2024-06-27 00:11:54 -05:00
committed by GitHub
17 changed files with 502 additions and 71 deletions

1
.gitignore vendored
View File

@@ -396,4 +396,3 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
/TweaksAndThings/Commands/EchoCommand.cs

View File

@@ -12,7 +12,7 @@
<PropertyGroup Condition="'$(AssemblyVersion)' == '' OR '$(MajorVersion)' != '' OR '$(MinorVersion)' != ''">
<MajorVersion Condition="'$(MajorVersion)' == ''">0</MajorVersion>
<MinorVersion Condition="'$(MinorVersion)' == ''">1</MinorVersion>
<PatchVersion Condition="'$(PatchVersion)' == ''">5</PatchVersion>
<PatchVersion Condition="'$(PatchVersion)' == ''">6</PatchVersion>
<AssemblyVersion>$(MajorVersion).$(MinorVersion).$(PatchVersion)</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<ProductVersion>$(AssemblyVersion)</ProductVersion>

View File

@@ -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

View File

@@ -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 <car>|. +|- <message>";
}
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;
}
}

View File

@@ -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)"
}
}

View File

@@ -1,8 +1,15 @@
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 bool EndAirSystemIssue(this Car car)
@@ -24,6 +31,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<Car>(steamLocomotive.FuelCar) != null)
{
@@ -38,5 +46,65 @@ namespace RMROC451.TweaksAndThings.Extensions
}
public static bool NotMotivePower(this Car car) => car is not BaseLocomotive && car.Archetype != Model.Definition.CarArchetype.Tender;
/// <summary>
/// For every car in the consist, cost 1 minute of AI Engineer time.
/// </summary>
/// <param name="consist"></param>
public static int CalculateCostForAutoEngineerEndGearSetting(this IEnumerable<Car> consist) =>
consist.Count() * 60;
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 Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool decrement = false)
{
Car? output = null;
if (!car.IsCaboose()) return null;
List<LoadSlot> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<LogicalEnd> ends = Enum.GetValues(typeof(LogicalEnd)).Cast<LogicalEnd>();
@@ -36,6 +37,7 @@ public class CarInspector_PopulateCarPanel_Patch
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.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.");
});
@@ -91,9 +90,16 @@ public class CarInspector_PopulateCarPanel_Patch
car.KeyValueObject.Observe(
key,
delegate (Value value)
{
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<Model.Car> consist = car.EnumerateCoupled(LogicalEnd.A);
Log.Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
TrainController tc = UnityEngine.Object.FindObjectOfType<TrainController>();
IEnumerable<Model.Car> 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<Car> 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<Car> 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<TrainController>();
foreach (var c in consist)
{
output = c.HuntingForCabeeseNearCar(timeNeeded, tc, decrement);
if (output != null) return output; //short out if we are good
}
return output;
}
}

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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<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,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<TweaksAndThingsPlugin>.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);
}
}
}

View File

@@ -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<width=100%><align=\"right\">";
private const string tagTitleFormat = "<align=left><margin-right={0}.5em>{1}</margin><line-height=0>";

View File

@@ -4,6 +4,14 @@
<!-- <MajorVersion>1</MajorVersion> -->
<!-- <MinorVersion>0</MinorVersion> -->
</PropertyGroup>
<ItemGroup>
<None Remove="mroc-cabeese.json" />
</ItemGroup>
<ItemGroup>
<Content Include="mroc-cabeese.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<GameAssembly Include="Assembly-CSharp" />
<GameAssembly Include="Railloader.Interchange" />
@@ -12,12 +20,14 @@
<GameAssembly Include="KeyValue.Runtime" />
<GameAssembly Include="Definition" />
<GameAssembly Include="Ops" />
<GameAssembly Include="StrangeCustoms" />
<GameAssembly Include="UnityEngine.CoreModule" />
<GameAssembly Include="UnityEngine.UI" />
<GameAssembly Include="Unity.TextMeshPro" />
<GameAssembly Include="System.Net.Http" />
<GameAssembly Include="Core" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Krafs.Publicizer" Version="2.2.1">

View File

@@ -17,17 +17,20 @@ public class Settings
public Settings(
List<WebhookSettings> webhookSettingsList,
bool handBrakeAndAirTagModifiers,
RosterFuelColumnSettings engineRosterFuelColumnSettings
RosterFuelColumnSettings engineRosterFuelColumnSettings,
bool endGearHelpersRequirePayment
)
{
WebhookSettingsList = webhookSettingsList;
HandBrakeAndAirTagModifiers = handBrakeAndAirTagModifiers;
EngineRosterFuelColumnSettings = engineRosterFuelColumnSettings;
EndGearHelpersRequirePayment = endGearHelpersRequirePayment;
}
public List<WebhookSettings>? WebhookSettingsList;
public bool HandBrakeAndAirTagModifiers;
public RosterFuelColumnSettings? EngineRosterFuelColumnSettings;
public bool EndGearHelpersRequirePayment;
internal void AddAnotherRow()
{

View File

@@ -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 TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
logger.Information("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version);
//moddingContext.RegisterConsoleCommand(new EchoCommand());
moddingContext.RegisterConsoleCommand(new EchoCommand());
settings = moddingContext.LoadSettingsData<Settings>(self.Id);
}
@@ -96,7 +97,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
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();
}
@@ -109,7 +110,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
() => 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<TweaksAndThingsPlugin>,
() => 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<TweaksAndThingsPlugin>,
() => 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<TweaksAndThingsPlugin>,
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<TweaksAndThingsPlugin>,
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();

View File

@@ -0,0 +1,23 @@
{
"objects": [
{
"$find": [
{
"path": "definition.archetype",
"value": "Caboose"
}
],
"definition": {
"loadSlots": [
{
"$add": {
"maximumCapacity": 8,
"loadUnits": "Quantity",
"requiredLoadIdentifier": "crew-hours"
}
}
]
}
}
]
}