Compare commits

...

6 Commits

Author SHA1 Message Date
c0e75c3c39 version++ 2025-08-31 18:33:06 -05:00
bd215d0dcc coroutine vs observers 2025-08-31 18:32:48 -05:00
554ea11790 2.1.5 updates
passenger car load info in tags; refactor cog logic again; formatting updates for webhook messages
2025-08-30 22:38:31 -05:00
c114aec3b3 fixes #70; forgot to pass boolean after refactor. 2025-08-29 09:17:24 -05:00
38a43276f7 fixes #69; track loading wasn't fully topping off. 2025-08-29 09:15:36 -05:00
afd18b9d27 fixes #68; bugged 120% penalty adjusted to intended 20% 2025-08-28 11:12:07 -05:00
10 changed files with 302 additions and 211 deletions

View File

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

View File

@@ -2,6 +2,7 @@
using Game.State;
using Helpers;
using Model.Ops;
using Model.Ops.Timetable;
using Network;
using RMROC451.TweaksAndThings.Extensions;
using RMROC451.TweaksAndThings.Patches;
@@ -46,9 +47,16 @@ public class EchoCommand : IConsoleCommand
EntityReference loco = new EntityReference(EntityType.Car, car.id);
if (comps[2] == "+") message = new Hyperlink(entityReference.URI(), string.Format(message, OpsController.Shared.ClosestArea(car)?.name ?? "???"));
if (StateManager.IsHost) car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}");
ExpandedConsole_Add_Patch.SendMs(null, $"{Hyperlink.To(car)} {message}");
if (!StateManager.IsHost) Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {Hyperlink.To(car)}: \"{message}\"");
string hlt = Hyperlink.To(car);
hlt = car.TryGetTimetableTrain(out Timetable.Train t) ? hlt.Replace(car.DisplayName, t.DisplayStringLong) : hlt;
if (StateManager.IsHost)
{
car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}");
ExpandedConsole_Add_Patch.SendMs(null, $"{StateManager.Shared._playersManager.LocalPlayer} {hlt} {message}");
}
if (!StateManager.IsHost) Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {hlt}: \"{message}\"");
return string.Empty;
}

View File

@@ -103,7 +103,7 @@ public static class Car_Extensions
FindMyCaboose(car, 0f, decrement: false, requireLoad: false);
public static Car? FindMyCabooseWithLoadRequirement(this Car car, float timeNeeded, bool decrement) =>
FindMyCaboose(car, timeNeeded, decrement, requireLoad: false);
FindMyCaboose(car, timeNeeded, decrement, requireLoad: true);
private static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false, bool requireLoad = true) =>
(

View File

@@ -86,10 +86,10 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
noCaboose();
logMessage += $"\nGovern AE? {output}";
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Verbose))
logMessage += $"\n{Environment.StackTrace}";
_log.Information(logMessage);
_log.Debug(logMessage);
return output;
}

View File

@@ -1,77 +1,138 @@
using Game.State;
using Game.Messages;
using Game.State;
using HarmonyLib;
using KeyValue.Runtime;
using Model;
using Model.AI;
using Model.Ops;
using Model.Physics;
using Model.Ops.Timetable;
using Network;
using Network.Messages;
using Railloader;
using Serilog;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Track;
using Track.Search;
using UI;
using UI.EngineControls;
using UnityEngine;
using UnityEngine.UI;
using static Track.Search.RouteSearch;
using Location = Track.Location;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerWaypointControls))]
[HarmonyPatch(nameof(AutoEngineerWaypointControls.ConfigureOptionsDropdown))]
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
{
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch>();
private static readonly HashSet<IDisposable> _keyChangeObservers = new();
private static readonly Dictionary<string, Dictionary<string, OpsCarPosition>> locoConsistDestinations = new();
private static string _lastSelectedLoco = string.Empty;
private static Serilog.ILogger _log => Log.ForContext<LocomotiveControlsUIAdapter_UpdateCarText_Postfix>();
private static int lastSeenIntegrationSetCount = default;
private static string? lastLocoSeenCarId = default;
private static Coroutine? watchyWatchy = null;
private static HashSet<OpsCarPosition?> locoConsistDestinations = [];
private static Game.GameDateTime? timetableSaveTime = null;
static string getDictKey(Car car) => car.DisplayName;
static void Postfix(AutoEngineerWaypointControls __instance, ref OptionsDropdownConfiguration __result)
static void Postfix(LocomotiveControlsUIAdapter __instance)
{
PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars);
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled() || (locoConsistDestinations.TryGetValue(getDictKey(selectedLoco), out Dictionary<string, OpsCarPosition> cars) && cars != null && _lastSelectedLoco == getDictKey(selectedLoco))) return;
try
{
_lastSelectedLoco = getDictKey(selectedLoco);
if (lastLocoSeenCarId != null && lastLocoSeenCarId.Equals(TrainController.Shared?.SelectedLocomotive.id) && watchyWatchy != null) return;
if (watchyWatchy != null) ((MonoBehaviour)__instance).StopCoroutine(watchyWatchy);
watchyWatchy = null;
IterateCarsDetectDestinations(
__instance,
__result,
selectedLoco,
numberOfCars,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
out int maxRowOrig,
out AutoEngineerOrdersHelper aeoh
);
List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco);
__result = WireUpJumpTosToSettingMenu(
__instance,
selectedLoco,
rowDatas,
func,
origCount,
maxRowOrig,
aeoh,
ref jumpTos
);
if (__instance._persistence.Orders.Mode == AutoEngineerMode.Waypoint) watchyWatchy = ((MonoBehaviour)__instance).StartCoroutine(UpdateCogCoroutine(__instance));
}
catch (Exception ex)
{
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you.");
}
}
private static OptionsDropdownConfiguration WireUpJumpTosToSettingMenu(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco, List<DropdownMenu.RowData> rowDatas, Action<int> func, int origCount, int maxRowOrig, AutoEngineerOrdersHelper aeoh, ref List<(string destinationId, string destination, float? distance, Location? location)> jumpTos)
public static IEnumerator UpdateCogCoroutine(LocomotiveControlsUIAdapter __instance)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(3f);
while (true)
{
if (__instance._persistence.Orders.Mode != AutoEngineerMode.Waypoint) yield return wait;
PrepLocoUsage((AutoEngineerWaypointControls)__instance.aiWaypointControls, out BaseLocomotive selectedLoco, out int numberOfCars);
HashSet<OpsCarPosition?> destinations = [];
if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(__instance, selectedLoco, out destinations)) yield return wait;
timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified;
lastSeenIntegrationSetCount = selectedLoco.set.NumberOfCars;
IterateCarsDetectDestinations(
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
((AutoEngineerWaypointControls)__instance.aiWaypointControls).ConfigureOptionsDropdown(),
selectedLoco,
numberOfCars,
destinations: destinations,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
out int maxRowOrig,
out AutoEngineerOrdersHelper aeoh
);
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos =
BuildJumpToOptions((AutoEngineerWaypointControls)__instance.aiWaypointControls, selectedLoco);
var config = WireUpJumpTosToSettingMenu(
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
selectedLoco,
rowDatas,
func,
origCount,
maxRowOrig,
aeoh,
ref jumpTos
);
List<DropdownMenu.RowData> list = config.Rows;
Action<int> action = config.OnRowSelected;
__instance.optionsDropdown.Configure(list, action);
((Selectable)__instance.optionsDropdown).interactable = list.Count > 0;
yield return wait;
}
}
private static bool ShouldRecalc(LocomotiveControlsUIAdapter __instance, BaseLocomotive selectedLoco, out HashSet<OpsCarPosition?> destinations)
{
bool output = false;
string locoKey = getDictKey(selectedLoco);
List<Car> consist = new List<Car>();
consist = selectedLoco.EnumerateCoupled().ToList();
destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet();
//_log.Information($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
//_log.Information($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
output |= !locoConsistDestinations.SetEquals(destinations);
_log.Information($"{locoKey} 1-> {output}");
if (output) lastSeenIntegrationSetCount = default;
output |= lastSeenIntegrationSetCount != selectedLoco.set.NumberOfCars;
_log.Information($"{locoKey} 2-> {output}");
//output |= __instance.optionsDropdown.scrollRect.content.childCount != (destinations.Count + timetableDestinations.Count + 1); //+1 for the default "JumpTo" entry)
//_log.Information($"{locoKey} 2.5-> {output} {__instance.optionsDropdown.scrollRect.content.childCount} {(destinations.Count)} {timetableDestinations.Count}");
output |= selectedLoco.TryGetTimetableTrain(out _) && TimetableController.Shared.CurrentDocument.Modified != timetableSaveTime;
_log.Information($"{locoKey} 3-> {output}");
return output;
}
private static OptionsDropdownConfiguration WireUpJumpTosToSettingMenu(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco, List<DropdownMenu.RowData> rowDatas, Action<int> func, int origCount, int maxRowOrig, AutoEngineerOrdersHelper aeoh, ref List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos)
{
OptionsDropdownConfiguration __result;
jumpTos = jumpTos?.OrderBy(c => c.distance ?? float.MaxValue)?.ToList() ?? [];
jumpTos = jumpTos?.OrderBy(c => c.sortDistance)?.ToList() ?? default;
var localJumpTos = jumpTos.ToList();
var safetyFirst = AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies() && jumpTos.Any();
@@ -86,7 +147,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
rowDatas
, delegate (int row)
{
_log.Debug($"{TrainController.Shared.SelectedLocomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}");
_log.Debug($"{__instance.Locomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}");
if (row <= maxRowOrig)
{
func(row);
@@ -123,15 +184,15 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return __result;
}
private static List<(string destinationId, string destination, float? distance, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco)
private static List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco)
{
List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = new();
foreach (var ocp in locoConsistDestinations[getDictKey(selectedLoco)].Values.Distinct())
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos = new();
foreach (OpsCarPosition ocp in locoConsistDestinations)
{
string destName = ocp.DisplayName;
string destId = ocp.Identifier;
float? distance = null;
float sortdistance = 0f;
if (
Graph.Shared.TryGetLocationFromPoint(
ocp.Spans?.FirstOrDefault().GetSegments().FirstOrDefault(),
@@ -149,77 +210,113 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
distance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum)
? metrics.Distance
: null;
};
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
? metrics.Distance
: float.MaxValue;
}
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add((
destinationId: destId,
destination: $"WP> {destName}"
, distance: distance
, sortdistance: sortdistance
, location: (Location?)destLoc
));
}
if (selectedLoco.TryGetTimetableTrain(out Timetable.Train t))
{
_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong}");
foreach (var e in t.Entries)
{
var stp = TimetableController.Shared.GetAllStations().FirstOrDefault(ps => ps.code == e.Station);
_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong} -> {e.Station} {stp}");
if (stp != null)
{
try
{
string destName = t.TrainType == Timetable.TrainType.Passenger ? stp.passengerStop.DisplayName : stp.DisplayName;
string destId = t.TrainType == Timetable.TrainType.Passenger ? stp.passengerStop.identifier : stp.code;
float? distance = null;
float sortdistance = 0f;
if (
Graph.Shared.TryGetLocationFromPoint(
stp.passengerStop.TrackSpans?.FirstOrDefault().GetSegments().FirstOrDefault(),
stp.passengerStop.TrackSpans?.FirstOrDefault()?.GetCenterPoint() ?? default,
200f,
out Location destLoc
)
)
{
float trainMomentum = 0f;
Location start = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.RouteStartLocation(out trainMomentum) : RouteStartLocation(__instance, selectedLoco);
HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
List<RouteSearch.Step> list = new List<RouteSearch.Step>();
var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
distance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum)
? metrics.Distance
: null;
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
? metrics.Distance
: float.MaxValue;
}
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add((
destinationId: destId,
destination: $"{t.DisplayStringLong} > {destName}"
, distance: distance
, sortdistance: sortdistance
, location: (Location?)destLoc
));
}
catch (Exception ex)
{
_log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}");
}
}
}
}
return jumpTos;
}
private static void IterateCarsDetectDestinations(AutoEngineerWaypointControls __instance, OptionsDropdownConfiguration __result, BaseLocomotive selectedLoco, int numberOfCars, out List<DropdownMenu.RowData> rowDatas, out Action<int> func, out int origCount, out int maxRowOrig, out AutoEngineerOrdersHelper aeoh)
private static void IterateCarsDetectDestinations(
AutoEngineerWaypointControls __instance,
OptionsDropdownConfiguration __result,
BaseLocomotive selectedLoco,
int numberOfCars,
HashSet<OpsCarPosition?> destinations,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
out int maxRowOrig,
out AutoEngineerOrdersHelper aeoh
)
{
rowDatas = __result.Rows.ToList();
func = __result.OnRowSelected;
origCount = rowDatas.Count;
maxRowOrig = origCount - 1;
if (!locoConsistDestinations.ContainsKey(getDictKey(selectedLoco))) locoConsistDestinations.Add(getDictKey(selectedLoco), new());
Dictionary<string, OpsCarPosition> seen = new();
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
Car.LogicalEnd logicalEnd = ((selectedLoco.set.IndexOfCar(selectedLoco).GetValueOrDefault(0) >= numberOfCars / 2) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
Car.LogicalEnd end = ((logicalEnd == Car.LogicalEnd.A) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
bool stop = false;
int carIndex = selectedLoco.set.StartIndexForConnected(selectedLoco, logicalEnd, IntegrationSet.EnumerationCondition.Coupled);
Car car;
while (!stop && (car = selectedLoco.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null)
{
AddObserversToCar(__instance, car);
var ocp = GetCarDestinationIdentifier(car);
_log.Debug($"{getDictKey(selectedLoco)} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}");
if (ocp.HasValue)
{
seen.Add(getDictKey(car), ocp.Value);
if (locoConsistDestinations[getDictKey(selectedLoco)].TryGetValue(getDictKey(car), out _))
locoConsistDestinations[getDictKey(selectedLoco)][getDictKey(car)] = ocp.Value;
else
locoConsistDestinations[getDictKey(selectedLoco)].Add(getDictKey(car), ocp.Value);
}
}
_log.Debug($"{getDictKey(selectedLoco)} --> [{seen.Keys.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}");
_log.Debug($"{getDictKey(selectedLoco)} --> [{locoConsistDestinations[getDictKey(selectedLoco)].Keys.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations[getDictKey(selectedLoco)].Select(k => $"{k.Key}:{k.Value.DisplayName}"))}");
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
string locoKey = getDictKey(selectedLoco);
var dropped =
locoConsistDestinations[getDictKey(selectedLoco)].Keys.Except(seen.Keys).ToDictionary(
t => t,
t => locoConsistDestinations[getDictKey(selectedLoco)][t]
); //are no longer here
locoConsistDestinations.Except(destinations).ToHashSet(); //are no longer here
_log.Debug($"{getDictKey(selectedLoco)} --> [{dropped.Keys.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}");
locoConsistDestinations[getDictKey(selectedLoco)] =
locoConsistDestinations[getDictKey(selectedLoco)].Keys.Intersect(seen.Keys).ToDictionary(
t => t,
t => locoConsistDestinations[getDictKey(selectedLoco)][t]
); //remove ones that are no longer here
seen.Clear();
_log.Debug($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
_log.Debug($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
_log.Debug($"{locoKey} --> [{dropped.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => k.Value.DisplayName))}");
locoConsistDestinations = destinations.ToList().ToHashSet(); //remove ones that are no longer here
}
private static void PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars)
private static void PrepLocoUsage(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars)
{
//wire up that loco
selectedLoco = TrainController.Shared.SelectedLocomotive;
selectedLoco = __instance.Locomotive;
numberOfCars = selectedLoco.set.NumberOfCars;
_log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]");
foreach (var o in _keyChangeObservers) o.Dispose();
_keyChangeObservers.Clear();
_log.Debug($"{selectedLoco.id} --> HI BOB[{numberOfCars}]");
}
private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
@@ -233,7 +330,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return destination;
}
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive) {
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
{
bool num = _locomotive.IsStopped();
bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
@@ -256,89 +354,16 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
}
return num + 1.04f * (float)(coupledCarsCached.Count - 1);
}
}
}
private static void AddObserversToCar(AutoEngineerWaypointControls __instance, Car c)
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateOptionsDropdown))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class LocomotiveControlsUIAdapter_UpdateOptionsDropdown_Prefix
{
static bool Prefix(LocomotiveControlsUIAdapter __instance)
{
AddObserver(__instance, c, Car.KeyOpsWaybill);
AddObserver(__instance, c, Car.KeyOpsRepairDestination);
foreach (Car.LogicalEnd logicalEnd in CarInspector_PopulateCarPanel_Patch.ends)
{
AddObserver(__instance, c, Car.KeyValueKeyFor(Car.EndGearStateKey.IsCoupled, c.LogicalToEnd(logicalEnd)), true);
}
}
private static void AddObserver(AutoEngineerWaypointControls __instance, Model.Car car, string key, bool clearCarCache = false)
{
_keyChangeObservers.Add(
car.KeyValueObject.Observe(
key,
delegate (Value value)
{
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out Dictionary<string, OpsCarPosition> cars) && cars == null) return;
bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key);
string? destId =
waybillChng ?
GetCarDestinationIdentifier(car)?.Identifier ?? null :
null;
if (waybillChng)
{
if (cars.TryGetValue(getDictKey(car), out OpsCarPosition pos)
&& pos.Identifier == destId)
{
return;
} else
{
_log.Debug($"{getDictKey(car)} OBSV {key}: destNew; {destId}; reload");
}
} else
{
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
}
try
{
foreach(var o in _keyChangeObservers)
{
o.Dispose();
}
var loco = TrainController.Shared.SelectedLocomotive;
if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null;
_keyChangeObservers.Clear();
if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out _)) locoConsistDestinations[getDictKey(TrainController.Shared.SelectedLocomotive)] = null;
TrainController.Shared.SelectedCar = loco;
}
catch (Exception ex)
{
_log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}");
}
},
false
)
);
}
private static Waybill? GetWaybill(Car car, Value waybillValue)
{
Waybill? _waybill = null;
try
{
_waybill = Model.Ops.Waybill.FromPropertyValue(waybillValue, OpsController.Shared);
}
catch (OpsController.InvalidOpsCarPositionException ex)
{
Log.Error(ex, "Waybill for car {car} contains an invalid ops position: {pos}", car, ex.Identifier);
_waybill = null;
}
catch (Exception exception)
{
Log.Warning(exception, "{car} Exception in Waybill.FromPropertyValue", car);
_waybill = null;
}
return _waybill;
return false;
}
}

View File

@@ -1,7 +1,8 @@
using Game;
using Game.State;
using HarmonyLib;
using Helpers;
using Model;
using Model.Ops.Timetable;
using Newtonsoft.Json;
using Railloader;
using Serilog;
@@ -21,6 +22,7 @@ namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class ExpandedConsole_Add_Patch
{
private static Serilog.ILogger _log => Log.ForContext<ExpandedConsole_Add_Patch>();
private static void Prefix(ref UI.Console.Console.Entry entry)
{
entry.Text = $"{entry.Timestamp} : {entry.Text}";
@@ -28,12 +30,26 @@ internal class ExpandedConsole_Add_Patch
SendMs((UI.Console.Console.Entry?)entry);
}
private static string hold => @"```ansi
■{loco}■ {msg}
```";
private static string west => @"```ansi
◀{loco}  {msg}
```";
private static string east => @"```ansi
 {loco}▶ {msg}
```";
internal static void SendMs(UI.Console.Console.Entry? entry, string? text = null)
{
try
{
if (entry is null && !String.IsNullOrEmpty(text)) entry = new() { Text = text };
var msgText = entry?.Text ?? string.Empty;
if (msgText.StartsWith("Usage:")) return;
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
StateManager shared = StateManager.Shared;
GameStorage gameStorage = shared.Storage;
@@ -50,31 +66,45 @@ internal class ExpandedConsole_Add_Patch
var carId = t.IsMatch(msgText) ? Regex.Match(msgText, "car:(.*?)\"").Groups[1].Captures[0].ToString() : string.Empty;
Model.Car? car = TrainController.Shared.CarForString(carId);
var data = UpdateCarText(car);
bool engineInMessage = car?.IsLocomotive ?? false;
var image = engineInMessage ?
new
{
url = string.Empty
} :
null;
string msgToSend = string.Empty;
string desc = Regex.Replace(msgText, "<.*?>", "");
desc = !!car ? desc.Replace(car?.DisplayName ?? string.Empty, string.Empty) : desc;
desc = desc.Trim();//.Replace(": ", "\n");
if (engineInMessage)
if (!!car && engineInMessage)
{
CTCPanelMarkerManager cTCPanelMarkerManager = UnityEngine.Object.FindObjectOfType<CTCPanelMarkerManager>();
CTCPanelMarker marker = cTCPanelMarkerManager?._markers?.Values?.FirstOrDefault(v => v.TooltipInfo.Text.Contains(car.Ident.RoadNumber));
string color = CTCPanelMarker.InferColorFromText(car?.DisplayName).HexString().Replace("#", string.Empty);
if (marker != null)
string markerText = marker?.TooltipInfo.Text ?? string.Empty;
if (markerText.StartsWith(">") || markerText.EndsWith(">") || data.Item2 == Timetable.Direction.East || msgText.Contains("*-"))
{
color = CTCPanelMarker.InferColorFromText(marker.TooltipInfo.Text).HexString().Replace("#", string.Empty);
msgToSend = east.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc);
}
image = new
else if (markerText.StartsWith("<") || markerText.EndsWith("<") || data.Item2 == Timetable.Direction.West || msgText.Contains("-*"))
{
url = $"https://img.shields.io/badge/{car.DisplayName.Replace(" ", "%20")}-%20-{color}.png"
};
msgToSend = west.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc);
}
else
{
msgToSend = hold.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc);
}
}
else
{
msgToSend = desc;
}
msgToSend = msgToSend
.Replace(" , ", ", ")
.Replace(", :", " :")
.Replace(" ; ", "; ")
.Replace("*-", string.Empty)
.Replace("#", string.Empty)
.Replace("-*", string.Empty)
.Trim();
var SuccessWebHook = new
{
@@ -83,9 +113,7 @@ internal class ExpandedConsole_Add_Patch
{
new
{
description= Regex.Replace(msgText, "<.*?>", "").Replace(": ", "\n"),
timestamp=DateTime.UtcNow,
image
description= msgToSend
},
}
};
@@ -103,6 +131,26 @@ internal class ExpandedConsole_Add_Patch
}
}
public static (string , Timetable.Direction?) UpdateCarText(Car car)
{
string output = string.Empty;
Timetable.Direction? dir = null;
if (car?.IsLocomotive ?? false)
{
if (StateManager.Shared.PlayersManager.TrainCrewForId(car.trainCrewId, out var trainCrew))
{
output = trainCrew.Name;
if (TimetableController.Shared.TryGetTrainForTrainCrew(trainCrew, out Timetable.Train timetableTrain))
{
dir = timetableTrain.Direction;
output += " (Train " + timetableTrain.DisplayStringShort + ")";
}
}
}
return (output, dir);
}
public static GameDateTime RealNow()
{
var now = DateTime.Now;

View File

@@ -48,8 +48,7 @@ internal class InterchangedIndustryLoader_ServiceInterchange_Patch
if (num4 > 0)
{
var canAfford = shared.CanAfford(num4);
penalty = Mathf.CeilToInt(!canAfford ? num4 * 0.2f : 0);
__instance.Industry.ApplyToBalance(-num4, __instance.ledgerCategory, null, 0, quiet: true);
penalty += Mathf.CeilToInt(!canAfford ? num4 * 0.2f : 0);
num2 += num4;
num3++;

View File

@@ -98,6 +98,7 @@ internal class OpsController_AnnounceCoalescedPayments_Patch
var data = car.QuantityCapacityOfLoad(CrewHoursLoad());
if ((data.quantity + quantityToLoad > data.capacity) && data.quantity < data.capacity)
{
quantityToLoad = data.capacity; //ensure topping off
Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\"");
CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, false);
}

View File

@@ -1,13 +1,17 @@
using Game.State;
using HarmonyLib;
using KeyValue.Runtime;
using Model;
using Model.Ops;
using Railloader;
using RMROC451.TweaksAndThings.Extensions;
using RollingStock;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UI.Tags;
using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics;
namespace RMROC451.TweaksAndThings.Patches;
@@ -54,6 +58,18 @@ internal class TagController_UpdateTag_Patch
if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills);
if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel);
if (car.IsPassengerCar())
{
PassengerMarker? passengerMarker = car.GetPassengerMarker();
if (passengerMarker.HasValue)
{
IEnumerable<string> loadInfo = car.PassengerCountString(passengerMarker).Split('/');
//string item4 = CarPickable.PassengerString(car, passengerMarker.Value);
string val = TextSprites.PiePercent(float.Parse(loadInfo.First()), float.Parse(loadInfo.Last())) + $" {car.PassengerCountString(passengerMarker)} Passengers";
tagCallout.callout.Text = tagCallout.callout.Text.Contains("Empty") ? tagCallout.callout.Text.Replace("Empty", val) : tagCallout.callout.Text + $"\n{val}";
}
}
tagCallout.callout.Title =
tags.Any() switch
{

View File

@@ -40,7 +40,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
static TweaksAndThingsPlugin()
{
Log.Debug("Hello! Static Constructor was called!");
}
public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self)
@@ -58,7 +57,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
public override void OnEnable()
{
logger.Debug("OnEnable() was called!");
var harmony = new Harmony(modDefinition.Id);
harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty));
}
@@ -72,13 +70,10 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
public void Update()
{
logger.Verbose("UPDATE()");
}
public void ModTabDidOpen(UIPanelBuilder builder)
{
logger.Debug("Daytime!");
if (settings == null) settings = new();
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new();
@@ -347,7 +342,6 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
public void ModTabDidClose()
{
logger.Debug("Nighttime...");
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
}
}