mirror of
https://github.com/rmroc451/TweaksAndThings.git
synced 2025-12-16 01:09:38 -06:00
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MajorVersion>2</MajorVersion>
|
||||
<MinorVersion>0</MinorVersion>
|
||||
<PatchVersion>1</PatchVersion>
|
||||
<MinorVersion>1</MinorVersion>
|
||||
<PatchVersion>7</PatchVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
193
README.md
193
README.md
@@ -28,22 +28,191 @@ This mod requires Railloader by Zamu.
|
||||
### What does this mod do?
|
||||
**PLEASE READ AS THE WAY THIS MOD FUNCTIONS HAS CHANGED FROM PRIOR VERSIONS**
|
||||
|
||||
1. Car Inspector : Handbrake & Air Line Helper
|
||||
* Gives two buttons that will scan the current car's connections, including the whole consist, and automatically release hand brakes, set anglecocks and connect glad hands.
|
||||
2. Car Tag Updates
|
||||
* Shows an indication of which cars in the FOV have Air System or Handbrake issues.
|
||||
* **hold SHIFT** to only show the tags in the FOV for cars with an issue!
|
||||
3. Discord Webhooks
|
||||
* Allows the console messages to post to a discord webhook. useful for those wanting to keep an eye on 24/7 hosted saves.
|
||||
* Locomotive messages grab the locomotive `Ident.RoadNumber` and check the `CTC Panel Markers` if they exist. If found, they will use the red/green color and embed the locomotive as an image in the message. If no marker is found, it defaults to blue.
|
||||
* Currently, One person per server should have this per discord webhook, otherwise you will get duplicate messages to the webhook.
|
||||
* **Multiple hooks**: Allows for many different webhooks per client to be setup, and filtered to the `Ident.ReportingMark` so you can get messages to different hooks based on what save/server you are playing on.
|
||||
* **Customizable** from the in-game Railloader settings, find `RMROC451.TweaksAndThings`
|
||||
Basically, this mod has a couple zones of focus. Caboose tweaks and other QOL things. Some of those QOL things, I added the option for the cabeese to be required & charge you a "crew salary" to utilize, or pay a monetary penalty.
|
||||
|
||||
I was disappointed the vanilla cabeese were largely for show, didn't provide any real reason to have them except for role playing.
|
||||
|
||||
Enter Tweaks and Things.
|
||||
|
||||
### QOL & Cabeese Modifications:
|
||||
<ul>
|
||||
<li><b>A:</b> Car Level Updates:
|
||||
<ul>
|
||||
<li><b>A1:</b> If a car is a participant in a disconnected air hose (currently uses the copy waybill icon)</li>
|
||||
<li><b>A2:</b> If a car's hand brake is set (currently uses the handbrake icon)</li>
|
||||
<li><b>A3 (🟢 NEW v2.0.0):</b> Oiling Level/Hotbox Indication : pie chart or 🔥 icon
|
||||
<ul>
|
||||
<li><b>A3a:</b> <u>On Rolling Stock:</u> Indicates the car's oiling level, if oiling feature is enabled</li>
|
||||
<li><b>A3b:</b> <u>On Locomotive:</u> Indicates the worst oiling level of a car from the connected consist (see <b>S1</b>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>A4:</b> Adds a "+" on cabeese tags when on a track span that reloads their crew hours load (see <b>C</b>)</li>
|
||||
<li><b>A5 (🟢 NEW v2.0.0):</b> Car Click Hotkey Modifiers
|
||||
<ul>
|
||||
<li><b>A5a:</b> `alt left click` : toggle car hand brake and connect glad hands on both ends
|
||||
<ul>
|
||||
<li>[ ] https://github.com/rmroc451/TweaksAndThings/issues/43 : Add setting to dump air vs connecting when used (requested by CD WEISS)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>A5b:</b> `alt shift click` : toggle consists brakes and connect all glad hands</li>
|
||||
<li><b>A5c:</b> `ctrl alt click` : drop all brakes and connect all glad hands in the consist</li>
|
||||
<li><b>A5d:</b> `ctrl alt shift click` : same as above but will auto oil the entire consist!</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>B:</b> Car Context Menu Updates
|
||||
<ul>
|
||||
<li><b>B1 (🔵 MODIFIED v2.0.0):</b> Context Menu (right click car) Updates<br/>
|
||||
When right clicking on a car you get some new individual car options:
|
||||
<ul>
|
||||
<li><b>B1a:</b> Bleed<br/>
|
||||
Dumps all of the air in the car's air system</li>
|
||||
<li><b>B1b:</b> Apply/Release Handbrake<br/>
|
||||
Toggles the individual car's handbrake</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>B2 (🟢 NEW v2.0.0):</b> SHIFT Context Menu (right click car) Updates<br/>
|
||||
When right clicking on a car and holding SHIFT you get some new consist level options:
|
||||
<ul>
|
||||
<li><b>B2a:</b> Bleed Consist<br/>
|
||||
Dumps all of the air in all the consist car air systems</li>
|
||||
<li><b>B2b:</b> Set/Release Consist Handbrakes<br/>
|
||||
If handbrakes are detected on, it knocks them all off.<br/>
|
||||
If no handbrakes are detected in the consist, it utilizes the RailRoader base game handbrake detection for when cuts of cars are spawned.</li>
|
||||
<li><b>B2c:</b> Air Up Consist<br/>
|
||||
Connects all gladhands and opens angle cocks for the consist.
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>C:</b> Cabeese Modifications
|
||||
<ul>
|
||||
<li><b>C1:</b> Adding `crew-hours` load to caboose type cars
|
||||
<ul>
|
||||
<li><b>C1a:</b> Gives the cabeese a depletable resource that is used to simulate the crew that resided in the caboose.</li>
|
||||
<li><b>C1b:</b> When certain actions are utilized at a consist level, the depletion of this resource is used to simulate a crew's stamina for the day.</li>
|
||||
<li><b>C1c:</b> When a request to adjust the <b>crew-hours</b> below the remaining quantity, things start costing over time, if <b>S1b</b> is enabled (1.5x modifier).</li>
|
||||
<li><b>C1d:</b> This load/resource is utilized when <b>S1b</b> is enabled and for <b>S1e</b> integration.</li>
|
||||
<li><b>C1e:</b> See <b>S1b1/S1b2/S1b3</b> for what this is used for.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>C2 (🔵 MODIFIED v2.0.0):</b> Proximity Detection<br/>
|
||||
Order of detection when requesting to adjust <b>crew-hours</b> for an action:
|
||||
<ul>
|
||||
<li><b>C2a:</b> Car initiating the action is a caboose, if so use this caboose</li>
|
||||
<li><b>C2b:</b> Cabeese near the car that initiated the request:</li>
|
||||
<ul>
|
||||
<li><b>C2a:</b> Gather from consist of the requesting car.</li>
|
||||
<li><b>C2b:</b> Gather from <b>OpsController.Shared.ClosestArea</b> for cars that are in the same <b>Area</b>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>C2c:</b> Sort the found cars by:
|
||||
<ul>
|
||||
<li><b>C2c1:</b> Preference for cars with same <b>crew-id</b> as selected locomotive (engine controls, bottom left), still order by <b>C2c2</b></li>
|
||||
<li><b>C2c2:</b> Distance from the requesting car, ascending (pick closest one)</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>D:</b> Discord Webhooks
|
||||
<ul>
|
||||
<li><b>D1:</b> Allows the console messages to post to a discord webhook. useful for those wanting to keep an eye on 24/7 hosted saves.</li>
|
||||
<li><b>D2:</b> Locomotive messages grab the locomotive `Ident.RoadNumber` and check the `CTC Panel Markers` if they exist. If found, they will use the red/green color and embed the locomotive as an image in the message. If no marker is found, it defaults to blue.</li>
|
||||
<li><b>D3:</b> Currently, One person per server should have this per discord webhook, otherwise you will get duplicate messages to the webhook.</li>
|
||||
<li><b>D4: Multiple hooks</b>: Allows for many different webhooks per client to be setup, and filtered to the `Ident.ReportingMark` so you can get messages to different hooks based on what save/server you are playing on.</li>
|
||||
<li><b>D5: Customizable</b> from the in-game Railloader settings, find <b>RMROC451.TweaksAndThings</b> (see <b>S3</b>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>M:</b> Miscellaneous
|
||||
<ul>
|
||||
<li><b>M1 (🟢 NEW v2.0.0):</b> Repair tracks now require cars to be waybilled, or they will not be serviced/overhauled.<br/>They will report on the company window's location section as <b>'No Work Order Assigned'</b>.</li>
|
||||
<li><b>M2:</b> Engine Roster Tweaks<br/>
|
||||
<ul>
|
||||
<li><b>M2a (🟢 NEW v2.0.0):</b> MU'd locomotives will automatically be hidden unless they are <b>SELECTED</b> or <b>FAVORITED</b>.</li>
|
||||
<li><b>M2b:</b> Fuel Display in Engine Roster<br/>
|
||||
Will add reamaing fuel indication to Engine Roster (with details in roster row tool tip (see <b>S2c</b>))</li>
|
||||
<ul>
|
||||
<li><b>M2b1:</b> MU'd locomotives fuel information will combine with MU primary (see <b>S2c</b>).</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>M3 (🟢 NEW v2.0.1):</b> MU Adjacency Restriction Removal <h3 style="color:red; display:inline">(USE AT OWN RISK)</h3></br>
|
||||
Engines no longer are required to be adjacent to eachother to contribute to MU. They can be dispersed throughout the train.<br/>
|
||||
The primary MU engine still acts as the main air reservoir, meaning train braking emits from that engine at this time.
|
||||
</li>
|
||||
<li><b>M4 (🟢 NEW v2.0.0):</b> `ctrl alt click` on a track in the map, sets the selected locomotives waypoint there when in waypoint mode.<br/>
|
||||
If you have mapenhancer with cars displayed, if you keycombo click on a car icon, it will set the auto couple attempt.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>S:</b> Settings
|
||||
<ul>
|
||||
<li><b>S1:</b> Caboose Mods</li>
|
||||
<ul>
|
||||
<li><b>S1a:</b> Consist Oil Indication<br/>A caboose is required in the consist to report the lowest oil level in the consist in the locomotive's tag(see <b>A3b</b>) & roster entry(see <b>M2</b>).</li>
|
||||
<li><b>S1b:</b> Caboose Use / Enable End Gear Helper Cost
|
||||
<ul>
|
||||
<li><b>S1b1:</b> Will cost 1 minute of AI Brake Crew & Caboose Crew time per car in the consist when the new <b>inspector</b> or <b>shift context wheel</b> buttons are utilized.</li>
|
||||
<li><b>S1b2:</b> 1.5x multiplier penalty to AI Brake Crew cost if no sufficiently crewed caboose nearby (see <b>C2</b>).</li>
|
||||
<li><b>S1b3:</b> Caboose starts reloading `Crew Hours` at any Team or Repair track (no waybill), after being stationary for 30 seconds.</li>
|
||||
<li><b>S1b4:</b> <b>AutoOiler Update:</b> 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).
|
||||
<li><b>S1b5:</b> <b>AutoOiler Update:</b> if <b>S1b</b> & <b>S1d</b> checked, when a caboose is present (see <b>C2</b>), the AutoOiler will repair hotboxes afer oiling them to 100%.
|
||||
<li><b>S1b6:</b> <b>AutoHotboxSpotter Update:</b> decrease the random wait from 30 - 300 seconds to 15 - 30 seconds (Safety Is Everyone's Job)</li>
|
||||
<li><b>S1b6:</b> <b>Costs from S1B1/S1B2:</b> added to financials at end of day with an entry of <b>AI Brake Crew</b>.</li>
|
||||
</ul>
|
||||
<li><b>S1c (🟢 NEW v2.0.0):</b> Refill / Crew Hours Load Option<br/>Select whether you want to manually reload cabeese via:
|
||||
<ul>
|
||||
<li><b>S1c1:</b> track method - (team/repair/passenger stop)</li>
|
||||
<li><b>S1c2:</b> daily caboose top off - refill to 8h at new day</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>S1d:</b> AutoAI Requirement (AI Hotbox\Oiler Requires Caboose)<br/>A caboose is required in the consist to check for Hotboxes and perform Auto Oiler, if checked.</li>
|
||||
<li><b>S1e (🟢 NEW v2.0.0):</b> Safety First!<br/>On non-express timetabled freight consists, a caboose with some crew-hours (see <b>C1</b>) is required in the consist to increase AE max speed > 20 in <b>ROAD</b>/<b>WAYPOINT</b> modes.</li>
|
||||
</ul>
|
||||
<li><b>S2:</b> UI
|
||||
<ul>
|
||||
<li><b>S2a:</b> Enable Tag Updates<br/>
|
||||
Allows all tag updates from <b>A</b> to display.</li>
|
||||
<li><b>S2b (🟢 NEW v2.0.0):</b> Debt Allowance<br/>
|
||||
Will allow interchange service and repair shops to still function when you are insolvent, at a 20% overdraft fee.</li>
|
||||
<li><b>S2c:</b> Engine Roster Fuel/Info
|
||||
<ul>
|
||||
<li><b>S2c1:</b> Enable Fuel Display in Engine Roster<br/>
|
||||
Will add reamaing fuel indication to Engine Roster (with details in roster row tool tip). <br/>
|
||||
Select where to display:
|
||||
<ul>
|
||||
<li><b>S2c1a</b> None/Off</li>
|
||||
<li><b>S2c1b</b> Engine Column</li>
|
||||
<li><b>S2c1c</b> Crew Column</li>
|
||||
<li><b>S2c1d</b> Status Column</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>S2c2:</b> Always Visible?<br/>
|
||||
Always displayed, if you want it hidden and only shown when you care to see, uncheck this, and then you can press ALT for it to populate on the next UI refresh cycle.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>S3:</b> Webhooks
|
||||
<ul>
|
||||
<li><b>S3a:</b> Webhook Enabled<br/>Will parse the console messages and transmit to a Discord webhook.</li>
|
||||
<li><b>S3b:</b> Reporting Mark<br/>Reporting mark of the company this Discord webhook applies to.</li>
|
||||
<li><b>S3c:</b> Webhook Url<br/>Url of Discord webhook to publish messages to.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
### Does this work in Multiplayer?
|
||||
Yes, these are client side mods. Host doesn't need to have them.
|
||||
|
||||
### What version of Railroader does this mod work with?
|
||||
2024.4.4
|
||||
2024.6 -> [Full Requirements](./TweaksAndThings/Definition.json)
|
||||
|
||||
*Special thanks and credit to Zamu for creating Railloader and for help with making the mod a bit more robust.*
|
||||
|
||||
@@ -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 ?? "???"));
|
||||
|
||||
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, $"{Hyperlink.To(car)} {message}");
|
||||
Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {Hyperlink.To(car)}: \"{message}\"");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
public static IEnumerator MrocAutoOilerLoop(this AutoOiler oiler, Serilog.ILogger _log, bool cabooseRequired)
|
||||
{
|
||||
int originIndex = oiler.FindOriginIndex();
|
||||
Model.Car? foundCaboose = oiler._originCar.FindMyCaboose(0.0f, false);
|
||||
Model.Car? foundCaboose = oiler._originCar.FindMyCabooseSansLoadRequirement();
|
||||
if (originIndex < 0)
|
||||
{
|
||||
_log.Error("Couldn't find origin car {car}", oiler._originCar);
|
||||
@@ -30,7 +30,7 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
yield break;
|
||||
}
|
||||
oiler._reverse = originIndex > oiler._cars.Count - originIndex;
|
||||
_log.Information(
|
||||
_log.Debug(
|
||||
"AutoOiler {name} starting, rev = {reverse}, caboose required = {req}, caboose halving adjustment = {hasCaboose}, oil limit = {limit}",
|
||||
oiler.name,
|
||||
oiler._reverse,
|
||||
@@ -41,7 +41,7 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(AutoOiler.StartDelay.CabooseHalvedFloat(foundCaboose));
|
||||
foundCaboose = oiler._originCar.FindMyCaboose(0.0f,false);
|
||||
foundCaboose = oiler._originCar.FindMyCabooseSansLoadRequirement();
|
||||
int carIndex = originIndex;
|
||||
float adjustedTimeToWalk = AutoOiler.TimeToWalkCar.CabooseHalvedFloat(foundCaboose);
|
||||
do
|
||||
@@ -58,11 +58,11 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
num += num3;
|
||||
oiler._pendingRunDuration += num3;
|
||||
oiler._oiledCount++;
|
||||
_log.Information("AutoOiler {name}: oiled {car} from {orig} => {new}", oiler.name, car, origOil, car.Oiled);
|
||||
_log.Debug("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);
|
||||
_log.Debug("AutoOiler {name}: {foundCaboose} repaired hotbox {car}", oiler.name, foundCaboose, car);
|
||||
Multiplayer.Broadcast($"{Hyperlink.To(oiler._originCar)}: \"{Hyperlink.To(car)} hotbox repaired!\"");
|
||||
car.SendPropertyChange(PropertyChange.Control.Hotbox, false);
|
||||
}
|
||||
@@ -80,7 +80,7 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
|
||||
public static IEnumerator MrocAutoHotboxSpotterLoop(this AutoHotboxSpotter spotter, Serilog.ILogger _log, bool cabooseRequired)
|
||||
{
|
||||
Func<Model.Car?> foundCaboose = () => spotter._locomotive.FindMyCaboose(0.0f, false);
|
||||
Func<Model.Car?> foundCaboose = () => spotter._locomotive.FindMyCabooseSansLoadRequirement();
|
||||
while (true)
|
||||
{
|
||||
if (!spotter.HasCars)
|
||||
@@ -89,7 +89,7 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
continue;
|
||||
}
|
||||
var fc = foundCaboose();
|
||||
_log.Information("AutoHotboxSpotter {name}: Hotbox Spotter Running, Found Caboose => {hasCaboose}; Has Cars {hasCars}; Requires Caboose {requiresCaboose}",
|
||||
_log.Debug("AutoHotboxSpotter {name}: Hotbox Spotter Running, Found Caboose => {hasCaboose}; Has Cars {hasCars}; Requires Caboose {requiresCaboose}",
|
||||
spotter.name, fc, spotter.HasCars, cabooseRequired);
|
||||
if (CabooseRequirementChecker(string.Format("{0} {1}", spotter.GetType().Name, spotter.name), cabooseRequired, fc, _log))
|
||||
{
|
||||
@@ -104,7 +104,7 @@ namespace RMROC451.TweaksAndThings.Extensions
|
||||
{
|
||||
var numOrig = num;
|
||||
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, fc, cabooseRequired);
|
||||
_log.Debug("AutoHotboxSpotter {name}: Next check went from num(60,300) => {numOrig}; to num(15,30) => {hasCaboose}; Requires Caboose {requiresCaboose}", spotter.name, numOrig, num, fc, cabooseRequired);
|
||||
}
|
||||
yield return new WaitForSeconds(num);
|
||||
spotter.CheckForHotbox();
|
||||
|
||||
@@ -7,6 +7,7 @@ using Model.Definition.Data;
|
||||
using Model.Ops;
|
||||
using Model.Ops.Timetable;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Patches;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -98,21 +99,28 @@ public static class Car_Extensions
|
||||
input.SelectedLocomotive.TryGetTimetableTrain(out Timetable.Train t) &&
|
||||
t.TrainClass == Timetable.TrainClass.First;
|
||||
|
||||
public static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false) =>
|
||||
public static Car? FindMyCabooseSansLoadRequirement(this Car car) =>
|
||||
FindMyCaboose(car, 0f, decrement: false, requireLoad: false);
|
||||
|
||||
public static Car? FindMyCabooseWithLoadRequirement(this Car car, float timeNeeded, bool decrement) =>
|
||||
FindMyCaboose(car, timeNeeded, decrement, requireLoad: true);
|
||||
|
||||
private static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false, bool requireLoad = true) =>
|
||||
(
|
||||
car.CarCaboose() ?? car.CarsNearCurrentCar(timeNeeded, decrement).FindNearestCabooseFromNearbyCars()
|
||||
)?.CabooseWithSufficientCrewHours(timeNeeded, decrement);
|
||||
)?.CabooseWithSufficientCrewHours(timeNeeded: timeNeeded, requireLoad:requireLoad, decrement: decrement);
|
||||
|
||||
public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool decrement = false)
|
||||
public static Car? CabooseWithSufficientCrewHours(this Car car, float timeNeeded, bool requireLoad, bool decrement = false)
|
||||
{
|
||||
Car? output = null;
|
||||
if (car is null || !car.IsCaboose()) return null;
|
||||
if (!requireLoad) return car;
|
||||
|
||||
List<LoadSlot> loadSlots = car.Definition.LoadSlots;
|
||||
for (int i = 0; i < loadSlots.Count; i++)
|
||||
{
|
||||
CarLoadInfo? loadInfo = car.GetLoadInfo(i);
|
||||
if (loadInfo.HasValue)
|
||||
if (loadInfo.HasValue && loadInfo.Value.LoadId == OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.id)
|
||||
{
|
||||
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
|
||||
output = valueOrDefault.Quantity >= timeNeeded ? car : null;
|
||||
@@ -140,7 +148,7 @@ public static class Car_Extensions
|
||||
.Union(car.EnumerateCoupled())
|
||||
.Where(c => c.IsCaboose());
|
||||
|
||||
//if (cabeese?.Any() ?? false) Log.Information($"{nameof(CarsNearCurrentCar)}[{car.DisplayName}] => {cabeese.Count()}");
|
||||
if (cabeese?.Any() ?? false) Log.Debug($"{nameof(CarsNearCurrentCar)}[{car.DisplayName}] => {cabeese.Count()}");
|
||||
|
||||
List<(Car car, bool crewCar, float distance)> source =
|
||||
cabeese.Select(c => (car: c, crewCar: c.IsCrewCar(), distance: car.Distance(c))).ToList();
|
||||
|
||||
@@ -13,7 +13,7 @@ internal class AutoEngineerControlSetBase_UpdateStatusLabel_Patch
|
||||
static void Postfix(AutoEngineerControlSetBase __instance)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled() || !AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies()) return;
|
||||
if (!tweaksAndThings.IsEnabled() || !AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(__instance.Locomotive)) return;
|
||||
|
||||
string orig = __instance.statusLabel.text;
|
||||
__instance.statusLabel.text = $"{orig}; <b>Safety</b>";
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using HarmonyLib;
|
||||
using Helpers;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System.Collections;
|
||||
using Track;
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
using static UI.AutoEngineerDestinationPicker;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerDestinationPicker))]
|
||||
[HarmonyPatch(nameof(AutoEngineerDestinationPicker.Loop))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerDestinationPicker_Loop_Patch
|
||||
{
|
||||
static bool Prefix(AutoEngineerDestinationPicker __instance, ref IEnumerator __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
__result = Loop(__instance);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerator Loop(AutoEngineerDestinationPicker __instance)
|
||||
{
|
||||
Hit valueOrDefault;
|
||||
Location location;
|
||||
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(1/60);
|
||||
while (true)
|
||||
{
|
||||
Location? currentOrdersGotoLocation = __instance.GetCurrentOrdersGotoLocation();
|
||||
Hit? hit = __instance.HitLocation();
|
||||
if (hit.HasValue)
|
||||
{
|
||||
valueOrDefault = hit.GetValueOrDefault();
|
||||
location = valueOrDefault.Location;
|
||||
Graph.PositionRotation positionRotation = __instance._graph.GetPositionRotation(location);
|
||||
__instance.destinationMarker.position = WorldTransformer.GameToWorld(positionRotation.Position);
|
||||
__instance.destinationMarker.rotation = positionRotation.Rotation;
|
||||
__instance.destinationMarker.gameObject.SetActive(value: true);
|
||||
if (!currentOrdersGotoLocation.Equals(location) && __instance.MouseClicked)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.destinationMarker.gameObject.SetActive(value: false);
|
||||
}
|
||||
yield return wait;
|
||||
}
|
||||
Log.Debug("DestinationPicker Hit: {hit} {car} {end}", valueOrDefault.Location, valueOrDefault.CarInfo?.car, valueOrDefault.CarInfo?.end);
|
||||
__instance._ordersHelper.SetWaypoint(location, valueOrDefault.CarInfo?.car.id);
|
||||
__instance.StopLoop();
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using Game.Messages;
|
||||
using HarmonyLib;
|
||||
using Model.AI;
|
||||
using Model.Definition;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UI.EngineControls;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerOrdersHelper))]
|
||||
[HarmonyPatch(nameof(AutoEngineerOrdersHelper.SendAutoEngineerCommand), typeof(AutoEngineerMode), typeof(bool), typeof(int), typeof(float), typeof(OrderWaypoint))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
|
||||
{
|
||||
|
||||
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch>();
|
||||
|
||||
static bool Prefix(AutoEngineerMode mode, ref int maxSpeedMph)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst()) return true;
|
||||
|
||||
if (SafetyFirstGoverningApplies())
|
||||
{
|
||||
int orig = maxSpeedMph;
|
||||
int limitedSpeed = Math.Min(maxSpeedMph, 20);
|
||||
maxSpeedMph = mode switch
|
||||
{
|
||||
AutoEngineerMode.Road => limitedSpeed,
|
||||
AutoEngineerMode.Waypoint => limitedSpeed,
|
||||
_ => maxSpeedMph,
|
||||
};
|
||||
|
||||
if (orig != maxSpeedMph)
|
||||
{
|
||||
_log.Information($"{Enum.GetName(typeof(AutoEngineerMode), mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] {nameof(AutoEngineerOrdersExtensions.MaxSpeedMph)} limited to {limitedSpeed} from {orig}; No Caboose in Consist;");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
internal static bool SafetyFirstGoverningApplies()
|
||||
{
|
||||
var _persistence = new AutoEngineerPersistence(TrainController.Shared.SelectedLocomotive.KeyValueObject);
|
||||
var OrdersHelper = new AutoEngineerOrdersHelper(TrainController.Shared.SelectedLocomotive, _persistence);
|
||||
|
||||
bool cabooseReq = SingletonPluginBase<TweaksAndThingsPlugin>.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
|
||||
string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] ";
|
||||
Func<bool> firstClass = () =>
|
||||
{
|
||||
var output = TrainController.Shared.SelectedEngineExpress();
|
||||
logMessage += $"\nfirst class {output}";
|
||||
return output;
|
||||
};
|
||||
|
||||
Func<bool> FreightConsist = () =>
|
||||
{
|
||||
bool output = !TrainController.Shared.SelectedLocomotive.EnumerateCoupled().ConsistNoFreight();
|
||||
logMessage += $"\nFreightConsist? {output}";
|
||||
logMessage += " " + string.Join(" / ", TrainController.Shared.SelectedLocomotive.EnumerateCoupled().Where(c => !c.MotivePower()).Select(c => $"{c.id} {Enum.GetName(typeof(CarArchetype), c.Archetype)}"));
|
||||
return output;
|
||||
};
|
||||
|
||||
Func<bool> noCaboose = () =>
|
||||
{
|
||||
var output = TrainController.Shared.SelectedLocomotive.FindMyCaboose(0.0f, false) == null;
|
||||
logMessage += $"\ncaboose? {!output}";
|
||||
return output;
|
||||
};
|
||||
|
||||
logMessage += $"\nCaboose Required {cabooseReq}";
|
||||
|
||||
bool output =
|
||||
cabooseReq &&
|
||||
!firstClass() &&
|
||||
FreightConsist() &&
|
||||
noCaboose();
|
||||
|
||||
logMessage += $"\nGovern AE? {output}";
|
||||
|
||||
_log.Information(logMessage);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Game.Notices;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Serilog;
|
||||
using UI.EngineControls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerOrdersHelper))]
|
||||
[HarmonyPatch(nameof(AutoEngineerOrdersHelper.SetWaypoint), typeof(Track.Location), typeof(string))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerOrdersHelper_SetWaypoint_patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerOrdersHelper_SetWaypoint_patch>();
|
||||
static void Postfix(AutoEngineerOrdersHelper __instance, Track.Location location, string coupleToCarId)
|
||||
{
|
||||
_log.Information($"start setWP");
|
||||
Car selectedLoco = __instance._locomotive;
|
||||
_log.Information($"{selectedLoco?.DisplayName ?? ""} set WP");
|
||||
Vector3 gamePoint = location.GetPosition();
|
||||
EntityReference entityReference = new EntityReference(EntityType.Position, new Vector4(gamePoint.x, gamePoint.y, gamePoint.z, 0));
|
||||
selectedLoco.PostNotice("ai-wpt-rmroc451", new Hyperlink(entityReference.URI(), $"WP SET"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using Game;
|
||||
using Game.Messages;
|
||||
using Game.Notices;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.AI;
|
||||
using Model.Definition;
|
||||
using Network;
|
||||
using Network.Messages;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Track;
|
||||
using UI.EngineControls;
|
||||
using UI.EngineRoster;
|
||||
using UnityEngine;
|
||||
using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics;
|
||||
using static UnityEngine.InputSystem.InputRemoting;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerPlanner))]
|
||||
[HarmonyPatch(nameof(AutoEngineerPlanner.HandleCommand))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerPlanner_HandleCommand_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerPlanner_HandleCommand_Patch>();
|
||||
private static int governedSpeed = 20;
|
||||
|
||||
static bool Prefix(AutoEngineerPlanner __instance, ref AutoEngineerCommand command, ref IPlayer sender)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
LocoNoticeWPSet(__instance, command, sender);
|
||||
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst() || (sender.IsRemote && !tweaksAndThings.SafetyFirstClientEnforce()) || command.MaxSpeedMph <= governedSpeed) return true;
|
||||
BaseLocomotive loco = __instance._locomotive;
|
||||
|
||||
if (SafetyFirstGoverningApplies(loco))
|
||||
{
|
||||
int orig = command.MaxSpeedMph;
|
||||
int limitedSpeed = Math.Min(command.MaxSpeedMph, governedSpeed);
|
||||
command.MaxSpeedMph = command.Mode switch
|
||||
{
|
||||
AutoEngineerMode.Road => limitedSpeed,
|
||||
AutoEngineerMode.Waypoint => limitedSpeed,
|
||||
_ => command.MaxSpeedMph,
|
||||
};
|
||||
|
||||
string message = $"{Enum.GetName(typeof(AutoEngineerMode), command.Mode)}[{loco.DisplayName}] governed{{0}}due to Safety First rules.";
|
||||
if (orig != command.MaxSpeedMph)
|
||||
{
|
||||
message = string.Format(message, $" from {orig} to {command.MaxSpeedMph} MPH ");
|
||||
}
|
||||
else
|
||||
{
|
||||
message = string.Format(message, " ");
|
||||
}
|
||||
_log.Debug(message);
|
||||
Multiplayer.SendError(sender, message, AlertLevel.Info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void LocoNoticeWPSet(AutoEngineerPlanner __instance, AutoEngineerCommand command, IPlayer sender)
|
||||
{
|
||||
OrderWaypoint? wp =
|
||||
string.IsNullOrEmpty(command.WaypointLocationString) ?
|
||||
null :
|
||||
new OrderWaypoint?(new OrderWaypoint(command.WaypointLocationString, command.WaypointCoupleToCarId));
|
||||
|
||||
if (
|
||||
wp.HasValue &&
|
||||
!string.IsNullOrEmpty(wp.Value.LocationString) &&
|
||||
!__instance._orders.Waypoint.Equals(wp)
|
||||
)
|
||||
{
|
||||
_log.Debug($"start setWP");
|
||||
Car selectedLoco = __instance._locomotive;
|
||||
_log.Debug($"{selectedLoco?.DisplayName ?? ""} set WP");
|
||||
if (LocationPositionFromString(wp.Value, out Vector3 gamePoint))
|
||||
{
|
||||
EntityReference entityReference = new EntityReference(EntityType.Position, new Vector4(gamePoint.x, gamePoint.y, gamePoint.z, 0));
|
||||
selectedLoco.PostNotice("ai-wpt-rmroc451", new Hyperlink(entityReference.URI(), $"WP SET [{sender.Name}]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool LocationPositionFromString(OrderWaypoint waypoint, out Vector3 position)
|
||||
{
|
||||
Location location;
|
||||
position = default;
|
||||
try
|
||||
{
|
||||
location = Graph.Shared.ResolveLocationString(waypoint.LocationString);
|
||||
position = location.GetPosition();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Couldn't get location from waypoint: {locStr}", waypoint.LocationString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool SafetyFirstGoverningApplies(BaseLocomotive loco)
|
||||
{
|
||||
var _persistence = new AutoEngineerPersistence(loco.KeyValueObject);
|
||||
var OrdersHelper = new AutoEngineerOrdersHelper(loco, _persistence);
|
||||
|
||||
if (loco.EnumerateCoupled().All(c => c.IsCaboose() || c.MotivePower())) return false;
|
||||
|
||||
bool cabooseReq = SingletonPluginBase<TweaksAndThingsPlugin>.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
|
||||
string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{loco.DisplayName}] ";
|
||||
Func<bool> firstClass = () =>
|
||||
{
|
||||
var output = TrainController.Shared.SelectedEngineExpress();
|
||||
logMessage += $"\nfirst class {output}";
|
||||
return output;
|
||||
};
|
||||
|
||||
Func<bool> FreightConsist = () =>
|
||||
{
|
||||
bool output = !loco.EnumerateCoupled().ConsistNoFreight();
|
||||
logMessage += $"\nFreightConsist? {output}";
|
||||
logMessage += " " + string.Join(" / ", loco.EnumerateCoupled().Where(c => !c.MotivePower()).Select(c => $"{c.id} {Enum.GetName(typeof(CarArchetype), c.Archetype)}"));
|
||||
return output;
|
||||
};
|
||||
|
||||
Func<bool> noCaboose = () =>
|
||||
{
|
||||
bool output = loco.FindMyCabooseSansLoadRequirement() == null;
|
||||
logMessage += $"\ncaboose? {!output}";
|
||||
return output;
|
||||
};
|
||||
|
||||
logMessage += $"\nCaboose Required {cabooseReq}";
|
||||
|
||||
bool output =
|
||||
cabooseReq &&
|
||||
!firstClass() &&
|
||||
FreightConsist() &&
|
||||
noCaboose();
|
||||
|
||||
logMessage += $"\nGovern AE? {output}";
|
||||
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Verbose))
|
||||
logMessage += $"\n{Environment.StackTrace}";
|
||||
|
||||
_log.Debug(logMessage);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
using Game.Messages;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.AI;
|
||||
using Model.Ops;
|
||||
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(LocomotiveControlsUIAdapter))]
|
||||
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
|
||||
{
|
||||
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(LocomotiveControlsUIAdapter __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (lastLocoSeenCarId != null && lastLocoSeenCarId.Equals(TrainController.Shared?.SelectedLocomotive.id) && watchyWatchy != null) return;
|
||||
if (watchyWatchy != null) ((MonoBehaviour)__instance).StopCoroutine(watchyWatchy);
|
||||
watchyWatchy = null;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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 || ((AutoEngineerWaypointControls)__instance.aiWaypointControls).Locomotive == null) 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.sortDistance)?.ToList() ?? default;
|
||||
var localJumpTos = jumpTos.ToList();
|
||||
var safetyFirst = AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(selectedLoco) && jumpTos.Any();
|
||||
|
||||
rowDatas.AddRange(jumpTos.Select(j =>
|
||||
new DropdownMenu.RowData(
|
||||
$"{j.destination} <b>({(j.distance.HasValue ? Units.DistanceText(j.distance.Value) : "N/A")})</b>",
|
||||
!safetyFirst ? null : "<i>Disabled; Safety First!</i>"
|
||||
)
|
||||
));
|
||||
|
||||
__result = new OptionsDropdownConfiguration(
|
||||
rowDatas
|
||||
, delegate (int row)
|
||||
{
|
||||
_log.Debug($"{__instance.Locomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}");
|
||||
if (row <= maxRowOrig)
|
||||
{
|
||||
func(row);
|
||||
}
|
||||
if (row > maxRowOrig && localJumpTos[row - origCount].location.HasValue)
|
||||
{
|
||||
if (safetyFirst)
|
||||
{
|
||||
Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, "Safety First, find yourself a caboose!", AlertLevel.Error);
|
||||
return;
|
||||
}
|
||||
float trainMomentum = 0f;
|
||||
Location end = localJumpTos[row - origCount].location.Value;
|
||||
Location start = RouteStartLocation(__instance, selectedLoco);
|
||||
HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
|
||||
List<RouteSearch.Step> list = new List<RouteSearch.Step>();
|
||||
|
||||
var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
|
||||
if (!Graph.Shared.FindRoute(start, end, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum))
|
||||
{
|
||||
RouteSearch.Metrics metrics2;
|
||||
bool flag = Graph.Shared.FindRoute(start, end, autoEngineer, null, out metrics2);
|
||||
Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, flag ? (getDictKey(selectedLoco) + " Train too long to navigate to waypoint.") : (getDictKey(selectedLoco) + " Unable to find a path to waypoint."), AlertLevel.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
var mw = (location: end, carId: string.Empty);
|
||||
aeoh.SetWaypoint(mw.location, mw.carId);
|
||||
aeoh.SetOrdersValue(maybeWaypoint: mw);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return __result;
|
||||
}
|
||||
|
||||
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, 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(),
|
||||
ocp.Spans?.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: $"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,
|
||||
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;
|
||||
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
|
||||
string locoKey = getDictKey(selectedLoco);
|
||||
|
||||
var dropped =
|
||||
locoConsistDestinations.Except(destinations).ToHashSet(); //are no longer here
|
||||
|
||||
_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(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars)
|
||||
{
|
||||
//wire up that loco
|
||||
selectedLoco = __instance.Locomotive;
|
||||
numberOfCars = selectedLoco?.set.NumberOfCars ?? -1;
|
||||
_log.Debug($"{selectedLoco?.id} --> HI BOB[{numberOfCars}]");
|
||||
}
|
||||
|
||||
private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
|
||||
{
|
||||
OpsCarPosition? destination = null;
|
||||
if (c.TryGetOverrideDestination(OverrideDestination.Repair, OpsController.Shared, out (OpsCarPosition, string)? result))
|
||||
destination = result.Value.Item1;
|
||||
|
||||
if (!destination.HasValue && c.Waybill.HasValue && !c.Waybill.Value.Completed)
|
||||
destination = c.Waybill.Value.Destination;
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
|
||||
{
|
||||
bool num = _locomotive.IsStopped();
|
||||
bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
|
||||
|
||||
bool flag2 = flag ?? __instance.OrdersHelper.Orders.Forward;
|
||||
if (_locomotive.EndToLogical((!flag2) ? Car.End.R : Car.End.F) == Car.LogicalEnd.A)
|
||||
{
|
||||
return _locomotive.EnumerateCoupled().First().WheelBoundsA;
|
||||
}
|
||||
|
||||
return _locomotive.EnumerateCoupled(Car.LogicalEnd.B).First().WheelBoundsB.Flipped();
|
||||
}
|
||||
|
||||
private static float CalculateTotalLength(BaseLocomotive selectedLoco)
|
||||
{
|
||||
List<Car> coupledCarsCached = selectedLoco.EnumerateCoupled().ToList();
|
||||
float num = 0f;
|
||||
foreach (Car item in coupledCarsCached)
|
||||
{
|
||||
num += item.carLength;
|
||||
}
|
||||
|
||||
return num + 1.04f * (float)(coupledCarsCached.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
|
||||
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateOptionsDropdown))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class LocomotiveControlsUIAdapter_UpdateOptionsDropdown_Prefix
|
||||
{
|
||||
static bool Prefix(LocomotiveControlsUIAdapter __instance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -43,3 +43,89 @@ internal class BaseLocomotive_FindSourceLocomotive_Patch
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//[HarmonyPatch(typeof(LocomotiveAirSystem))]
|
||||
//[HarmonyPatch(nameof(LocomotiveAirSystem._ShouldDeferToLocomotiveAir))]
|
||||
//[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
//internal class LocomotiveAirSystem__ShouldDeferToLocomotiveAir_Patch
|
||||
//{
|
||||
// private static void Postfix(LocomotiveAirSystem __instance, ref LocomotiveAirSystem locomotiveAirSystem, ref bool __result)
|
||||
// {
|
||||
// TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
// if (!tweaksAndThings.IsEnabled()) return;
|
||||
|
||||
// __result = _ShouldDeferToLocomotiveAir(__instance, out locomotiveAirSystem);
|
||||
// }
|
||||
// public static bool _ShouldDeferToLocomotiveAir(LocomotiveAirSystem __instance, out LocomotiveAirSystem locomotiveAirSystem)
|
||||
// {
|
||||
// locomotiveAirSystem = null;
|
||||
// if (__instance.car.set == null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (!(__instance.car.air is LocomotiveAirSystem locomotiveAirSystem2) || !(__instance.car is BaseLocomotive baseLocomotive))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (!locomotiveAirSystem2.IsCutOut || locomotiveAirSystem2.IsMuEnabled)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// BaseLocomotive baseLocomotive2 = baseLocomotive.FindMuSourceLocomotive();
|
||||
// if (baseLocomotive2 == null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (!(baseLocomotive2.air is LocomotiveAirSystem locomotiveAirSystem3))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// locomotiveAirSystem = locomotiveAirSystem3;
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
|
||||
//[HarmonyPatch(typeof(CarAirSystem))]
|
||||
//[HarmonyPatch(nameof(CarAirSystem.ShouldDeferToLocomotiveAir))]
|
||||
//[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
//internal class CarAirSystem_ShouldDeferToLocomotiveAir_Patch
|
||||
//{
|
||||
// private static void Postfix(CarAirSystem __instance, ref LocomotiveAirSystem locomotiveAirSystem, ref bool __result)
|
||||
// {
|
||||
// TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
// if (!tweaksAndThings.IsEnabled()) return;
|
||||
|
||||
// __result = ShouldDeferToLocomotiveAir(__instance, out locomotiveAirSystem);
|
||||
// }
|
||||
|
||||
// public static bool ShouldDeferToLocomotiveAir(CarAirSystem __instance, out LocomotiveAirSystem locomotiveAirSystem)
|
||||
// {
|
||||
// locomotiveAirSystem = null;
|
||||
// if (__instance.car.set == null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (__instance.car.Archetype != CarArchetype.Tender)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (!__instance.car.TryGetAdjacentCar(__instance.car.EndToLogical(Car.End.F), out var adjacent) || !adjacent.IsLocomotive)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (!(adjacent.air is LocomotiveAirSystem locomotiveAirSystem2))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// locomotiveAirSystem = locomotiveAirSystem2;
|
||||
// if (locomotiveAirSystem.IsMuEnabled)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// if (!locomotiveAirSystem.IsCutOut)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// return locomotiveAirSystem.ShouldDeferToLocomotiveAir(out locomotiveAirSystem);
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace RMROC451.TweaksAndThings.Patches;
|
||||
internal class CarInspector_PopulateCarPanel_Patch
|
||||
{
|
||||
private static ILogger _log => Log.ForContext<CarInspector_PopulateCarPanel_Patch>();
|
||||
private static IEnumerable<LogicalEnd> ends = Enum.GetValues(typeof(LogicalEnd)).Cast<LogicalEnd>();
|
||||
internal static IEnumerable<LogicalEnd> ends = Enum.GetValues(typeof(LogicalEnd)).Cast<LogicalEnd>();
|
||||
|
||||
/// <summary>
|
||||
/// If a caboose inspector is opened, it will auto set Anglecocks, gladhands and hand brakes
|
||||
@@ -88,7 +88,7 @@ internal class CarInspector_PopulateCarPanel_Patch
|
||||
int consistLength = consist.Count();
|
||||
int tonnage = LocomotiveControlsHoverArea.CalculateTonnage(consist);
|
||||
int lengthInMeters = UnityEngine.Mathf.CeilToInt(LocomotiveControlsHoverArea.CalculateLengthInMeters(consist.ToList()) * 3.28084f);
|
||||
var newSubTitle = () => string.Format("{0}, {1:N0}T, {2:N0}ft, {3:0.0} mph", consistLength.Pluralize("car"), tonnage, lengthInMeters, __instance._car.VelocityMphAbs);
|
||||
var newSubTitle = () => string.Format("{0}, {1:N0}T, {2:N0}ft, {3:N0} mph", consistLength.Pluralize("car"), tonnage, lengthInMeters, __instance._car.VelocityMphAbs);
|
||||
|
||||
field.AddLabel(() => newSubTitle(), UIPanelBuilder.Frequency.Fast)
|
||||
.Tooltip("Consist Info", "Reflects info about consist.").FlexibleWidth();
|
||||
@@ -181,7 +181,7 @@ internal class CarInspector_PopulateCarPanel_Patch
|
||||
|
||||
case MrocHelperType.BleedAirSystem:
|
||||
consist = consist.Where(c => !c.MotivePower());
|
||||
_log.ForContext("car", car).Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
|
||||
_log.ForContext("car", car).Debug($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
|
||||
foreach (Model.Car bleed in consist)
|
||||
{
|
||||
StateManager.ApplyLocal(new PropertyChange(bleed.id, PropertyChange.Control.Bleed, 1));
|
||||
@@ -190,7 +190,7 @@ internal class CarInspector_PopulateCarPanel_Patch
|
||||
|
||||
case MrocHelperType.Oil:
|
||||
consist = consist.Where(c => c.NeedsOiling || c.HasHotbox);
|
||||
_log.ForContext("car", car).Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
|
||||
_log.ForContext("car", car).Debug($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
|
||||
foreach (Model.Car oil in consist)
|
||||
{
|
||||
StateManager.ApplyLocal(new PropertyChange(oil.id, nameof(Car.Oiled).ToLower(), new FloatPropertyValue(1)));
|
||||
@@ -227,12 +227,12 @@ internal class CarInspector_PopulateCarPanel_Patch
|
||||
float originalTimeCost = consist.CalculateCostForAutoEngineerEndGearSetting();
|
||||
float timeCost = originalTimeCost;
|
||||
float crewCost = timeCost / 3600; //hours of time deducted from caboose.
|
||||
var tsString = crewCost.FormatCrewHours(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad().description);
|
||||
Car? cabooseWithAvailCrew = car.FindMyCaboose(crewCost, buttonsHaveCost);
|
||||
var tsString = crewCost.FormatCrewHours(OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.description);
|
||||
Car? cabooseWithAvailCrew = car.FindMyCabooseWithLoadRequirement(crewCost, buttonsHaveCost);
|
||||
if (cabooseWithAvailCrew == null) timeCost *= 1.5f;
|
||||
var cabooseFoundDisplay = cabooseWithAvailCrew?.DisplayName ?? "No caboose";
|
||||
|
||||
_log.ForContext("car", car).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)");
|
||||
_log.ForContext("car", car).Debug($"{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)})");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.Ops;
|
||||
using Model.Physics;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
@@ -9,6 +10,7 @@ using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
using UI.Tags;
|
||||
@@ -40,25 +42,27 @@ internal class CarPickable_Activate_Patch
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
bool bCtrlAltHeld = GameInput.IsControlDown && GameInput.IsAltDown;
|
||||
|
||||
_log.ForContext("car", __instance.car).Information($"{GameInput.IsShiftDown} {GameInput.IsControlDown} {GameInput.IsAltDown} {bCtrlAltHeld} ");
|
||||
_log.ForContext("car", __instance.car).Debug($"{GameInput.IsShiftDown} {GameInput.IsControlDown} {GameInput.IsAltDown}");
|
||||
|
||||
if (OnPointerDown(evt, PickableActivation.Primary))
|
||||
{
|
||||
CameraSelector.shared.FollowCar(__instance.car);
|
||||
_log.ForContext("car", __instance.car).Information("just click!");
|
||||
_log.ForContext("car", __instance.car).Debug("just click!");
|
||||
return false;
|
||||
|
||||
}
|
||||
//single click with keys pressed:
|
||||
else if (evt.Activation == PickableActivation.Primary)
|
||||
return HandleCarOrTrainBrakeDisplayClick(__instance.car, tweaksAndThings, evt.Activation);
|
||||
}
|
||||
|
||||
internal static bool HandleCarOrTrainBrakeDisplayClick(Car car, TweaksAndThingsPlugin tweaksAndThings, PickableActivation activation)
|
||||
{
|
||||
bool bCtrlAltHeld = GameInput.IsControlDown && GameInput.IsAltDown;
|
||||
bool output = true;
|
||||
var consist = __instance.car.EnumerateCoupled();
|
||||
var consist = car.EnumerateCoupled();
|
||||
bool handbrakesApplied = consist.Any(c => c.HandbrakeApplied());
|
||||
bool airSystemIssues = consist.Any(c => c.EndAirSystemIssue());
|
||||
Func<bool> cabooseNear = () => (bool)__instance.car.FindMyCaboose(0.0f, false);
|
||||
Func<bool> cabooseNear = () => (bool)car.FindMyCabooseSansLoadRequirement();
|
||||
bool needsOiling = GameInput.IsShiftDown && consist.All(c => c.IsStopped()) && consist.Any(c => c.NeedsOiling || c.HasHotbox) && (!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || cabooseNear());
|
||||
var chargeIt = handbrakesApplied || airSystemIssues || needsOiling;
|
||||
//CTRL + ALT + SHIFT : BrakesAngleCocksAndOiling
|
||||
@@ -67,65 +71,150 @@ internal class CarPickable_Activate_Patch
|
||||
//CTRL + SHIFT : Check Consist Angle Cocks
|
||||
//ALT : Toggle car brakes and & air up cars
|
||||
//CTRL : NOTHING; BASE CAR INSPECTOR
|
||||
//SHIFT : FOLLOW
|
||||
|
||||
if (activation == PickableActivation.Primary)
|
||||
{
|
||||
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!");
|
||||
BrakesAngleCocksAndOiling(car, tweaksAndThings, GameInput.IsShiftDown, consist, handbrakesApplied, airSystemIssues, needsOiling, chargeIt);
|
||||
_log.ForContext("car", car).Debug($"ctrlAlt{(GameInput.IsShiftDown ? "shift" : string.Empty)}Held!");
|
||||
output = false;
|
||||
}
|
||||
else if (GameInput.IsAltDown && GameInput.IsShiftDown)
|
||||
{
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment());
|
||||
_log.ForContext("car", __instance.car).Information("ctrlShiftHeld!");
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment());
|
||||
_log.ForContext("car", car).Debug("ctrlShiftHeld!");
|
||||
output = false;
|
||||
}
|
||||
else if (GameInput.IsControlDown && GameInput.IsShiftDown)
|
||||
{
|
||||
if (airSystemIssues)
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment());
|
||||
_log.ForContext("car", __instance.car).Information("altShiftHeld!");
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment());
|
||||
_log.ForContext("car", car).Debug("altShiftHeld!");
|
||||
output = false;
|
||||
}
|
||||
else if (GameInput.IsAltDown)
|
||||
{
|
||||
__instance.car.SetHandbrake(!__instance.car.HandbrakeApplied());
|
||||
CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(__instance.car);
|
||||
if (__instance.car.TryGetAdjacentCar(Car.LogicalEnd.A, out Model.Car cA)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cA);
|
||||
if (__instance.car.TryGetAdjacentCar(Car.LogicalEnd.B, out Model.Car cB)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cB);
|
||||
_log.ForContext("car", __instance.car).Information("ctrlHeld!");
|
||||
TagController.Shared.UpdateTag(__instance.car, __instance.car.TagCallout, OpsController.Shared);
|
||||
__instance.car.TagCallout.Update();
|
||||
car.SetHandbrake(!car.HandbrakeApplied());
|
||||
CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(car);
|
||||
if (car.TryGetAdjacentCar(Car.LogicalEnd.A, out Model.Car cA)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cA);
|
||||
if (car.TryGetAdjacentCar(Car.LogicalEnd.B, out Model.Car cB)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cB);
|
||||
_log.ForContext("car", car).Debug("ctrlHeld!");
|
||||
TagController.Shared.UpdateTag(car, car.TagCallout, OpsController.Shared);
|
||||
car.TagCallout.Update();
|
||||
output = false;
|
||||
}
|
||||
else if (GameInput.IsShiftDown)
|
||||
{
|
||||
CameraSelector.shared.FollowCar(car);
|
||||
output = false;
|
||||
}
|
||||
}
|
||||
else if ((GameInput.IsControlDown || GameInput.IsAltDown) && activation == PickableActivation.Secondary)
|
||||
{
|
||||
AltClickageMyBrosif(car, activation);
|
||||
output = false;
|
||||
}
|
||||
else if (activation == PickableActivation.Secondary)
|
||||
{
|
||||
//AltClickageMyBrosif(car, activation);
|
||||
CarPickable.HandleShowContextMenu(car);
|
||||
output = false;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
//else if (ctrlHeld && shiftHeld)
|
||||
private static void AltClickageMyBrosif(Car car, PickableActivation activation)
|
||||
{
|
||||
//if (GameInput.IsControlDown)
|
||||
//{
|
||||
// var selected = UI.CarInspector.CarInspector._instance._selectedTabState.Value;
|
||||
// UI.CarInspector.CarInspector.Show(__instance.car);
|
||||
// UI.CarInspector.CarInspector._instance._selectedTabState.Value = selected;
|
||||
|
||||
//}
|
||||
//else if (GameInput.IsAltDown)
|
||||
//{
|
||||
// logSet(car);
|
||||
// //car.TryGetAdjacentCar(Car.LogicalEnd.A, out var OutCarA);
|
||||
// //car.TryGetAdjacentCar(Car.LogicalEnd.B, out var OutCarB);
|
||||
// output = false;
|
||||
//}
|
||||
List<Car> cars = TrainController.Shared.SelectedLocomotive.EnumerateCoupled().ToList();
|
||||
logSet(car);
|
||||
//car.TryGetAdjacentCar(Car.LogicalEnd.A, out var OutCarA);
|
||||
//car.TryGetAdjacentCar(Car.LogicalEnd.B, out var OutCarB);
|
||||
//bool b4 = false;
|
||||
//if ((Object)(object)OutCarA != (Object)null && OutCarA.id == cars[i - 1].id)
|
||||
//{
|
||||
// cars[i - 1].ApplyEndGearChange(Car.LogicalEnd.B, Car.EndGearStateKey.Anglecock, 0f);
|
||||
// ccar.ApplyEndGearChange(Car.LogicalEnd.A, Car.EndGearStateKey.Anglecock, 0f);
|
||||
// ccar.HandleCouplerClick(ccar.EndGearA.Coupler);
|
||||
// logger.Information("{indName}: Decoupled {ccarName} from {cars1Name} at NOA{FR}", new object[4]
|
||||
// {
|
||||
// ((Object)base.Industry).name,
|
||||
// ccar.DisplayName,
|
||||
// cars[i - 1].DisplayName,
|
||||
// (ccar.LogicalToEnd(Car.LogicalEnd.A) == Car.End.F) ? "F" : "R"
|
||||
// });
|
||||
// Storage.LastCarEnd = ccar.LogicalToEnd(Car.LogicalEnd.A);
|
||||
// b4 = true;
|
||||
//}
|
||||
//if ((Object)(object)OutCarB != (Object)null && !b4 && OutCarB.id == cars[i - 1].id)
|
||||
//{
|
||||
// cars[i - 1].ApplyEndGearChange(Car.LogicalEnd.A, Car.EndGearStateKey.Anglecock, 0f);
|
||||
// ccar.ApplyEndGearChange(Car.LogicalEnd.B, Car.EndGearStateKey.Anglecock, 0f);
|
||||
// ccar.HandleCouplerClick(ccar.EndGearB.Coupler);
|
||||
// logger.Information("{indName}: Decoupled {ccarName} from {carsm1Name} at NOB{FR}", new object[4]
|
||||
// {
|
||||
// ((Object)base.Industry).name,
|
||||
// ccar.DisplayName,
|
||||
// cars[i - 1].DisplayName,
|
||||
// (ccar.LogicalToEnd(Car.LogicalEnd.A) == Car.End.F) ? "F" : "R"
|
||||
// });
|
||||
// Storage.LastCarEnd = ccar.LogicalToEnd(Car.LogicalEnd.B);
|
||||
// b4 = true;
|
||||
//}
|
||||
//if (!b4)
|
||||
//{
|
||||
// logger.Information("{IndName}: ERROR: NLC {ccarName} can't find LCC {carsm1Name} ({OutCarAName},{OutCarBName})", new object[5]
|
||||
// {
|
||||
// ((Object)base.Industry).name,
|
||||
// ccar.DisplayName,
|
||||
// cars[i - 1].DisplayName,
|
||||
// ((Object)(object)OutCarA != (Object)null) ? OutCarA.DisplayName : "no Car",
|
||||
// ((Object)(object)OutCarB != (Object)null) ? OutCarB.DisplayName : "no Car"
|
||||
// });
|
||||
// return;
|
||||
//}
|
||||
}
|
||||
|
||||
return true;
|
||||
private static void logSet(Car refCar)
|
||||
{
|
||||
IntegrationSet set = refCar.set;
|
||||
foreach (Car car in set.Cars)
|
||||
{
|
||||
Car acar;
|
||||
string endA = (car.TryGetAdjacentCar(Car.LogicalEnd.A, out acar) ? acar.DisplayName : "none");
|
||||
Car bcar;
|
||||
string endB = (car.TryGetAdjacentCar(Car.LogicalEnd.B, out bcar) ? bcar.DisplayName : "none");
|
||||
_log.Information("[Car: {id}:(EndA:{carA}),(EndB:{carB}),(FisA:{fisA})]", new object[4] { car.DisplayName, endA, endB, car.FrontIsA });
|
||||
}
|
||||
}
|
||||
|
||||
private static void BrakesAngleCocksAndOiling(CarPickable __instance, TweaksAndThingsPlugin tweaksAndThings, bool shiftHeld, System.Collections.Generic.IEnumerable<Car> consist, bool handbrakesApplied, bool airSystemIssues, bool needsOiling, bool chargeIt)
|
||||
|
||||
private static void BrakesAngleCocksAndOiling(Car car, 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);
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, false);
|
||||
if (airSystemIssues)
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.GladhandAndAnglecock, false);
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, false);
|
||||
if (needsOiling)
|
||||
hbFix = CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(__instance.car, MrocHelperType.Oil, false);
|
||||
hbFix = CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Oil, false);
|
||||
if (hbFix > 0)
|
||||
Multiplayer.Broadcast($"Near {Hyperlink.To(__instance.car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\"");
|
||||
Multiplayer.Broadcast($"Near {Hyperlink.To(car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\"");
|
||||
if (chargeIt)
|
||||
CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(__instance.car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist);
|
||||
CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist);
|
||||
}
|
||||
|
||||
public static bool OnPointerDown(
|
||||
|
||||
@@ -4,6 +4,7 @@ using Railloader;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
using UI.ContextMenu;
|
||||
@@ -35,8 +36,6 @@ internal class CarPickable_HandleShowContextMenu_Patch
|
||||
});
|
||||
if (GameInput.IsShiftDown)
|
||||
{
|
||||
|
||||
|
||||
if (!car.EnumerateCoupled().Any(c => !c.SupportsBleed()))
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, $"Bleed Consist", SpriteName.Bleed, delegate
|
||||
@@ -52,13 +51,14 @@ internal class CarPickable_HandleShowContextMenu_Patch
|
||||
|
||||
if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.Unused2, $"Air Up Consist", SpriteName.Select, delegate
|
||||
shared.AddButton(ContextMenuQuadrant.General, $"Air Up Consist", SpriteName.Select, delegate
|
||||
{
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (car.SupportsBleed())
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, "Bleed", SpriteName.Bleed, car.SetBleed);
|
||||
@@ -75,7 +75,9 @@ internal class CarPickable_HandleShowContextMenu_Patch
|
||||
CameraSelector.shared.FollowCar(car);
|
||||
});
|
||||
|
||||
shared.Show(car.DisplayName);
|
||||
string secondaryLine = car.Waybill.HasValue ? $"{Environment.NewLine}{car.Waybill.Value.Destination.DisplayName}" : string.Empty;
|
||||
secondaryLine = secondaryLine.Length > 10 + Environment.NewLine.Length ? $"{secondaryLine.Substring(0, 7+ Environment.NewLine.Length)}..." : secondaryLine;
|
||||
shared.Show($"{car.DisplayName}{secondaryLine}");
|
||||
shared.BuildItemAngles();
|
||||
shared.StartCoroutine(shared.AnimateButtonsShown());
|
||||
return false;
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
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();
|
||||
if (__instance.contentRectTransform.childCount >= 1) __instance.contentRectTransform.GetChild(0).DestroyAllChildren(); //YOINK DEM CIRCLES!
|
||||
__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;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ internal class EngineRosterPanel_Populate_Patch
|
||||
__instance._window.Title = __instance._window.Title.Split(':')[0].Trim();
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
var hiddenEntries = rows.Where(r => r.Engine.IsMuEnabled && !r.IsSelected && !r.IsFavorite).Select(r => r.Engine.id) ?? Enumerable.Empty<string>();
|
||||
var hiddenEntries = rows.Where(r => r.Engine.locomotiveControl.air.IsCutOut && !r.IsSelected && !r.IsFavorite).Select(r => r.Engine.id) ?? Enumerable.Empty<string>();
|
||||
|
||||
if (hiddenEntries.Any()) __instance._window.Title =string.Format("{0} : {1}", __instance._window.Title, $"Hidden MU Count [{hiddenEntries.Count()}]");
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using UI.EngineRoster;
|
||||
using UI.Tooltips;
|
||||
using UnityEngine;
|
||||
using Game.State;
|
||||
using Game;
|
||||
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
@@ -34,6 +35,8 @@ internal class EngineRosterRow_Refresh_Patch
|
||||
string fuelInfoText = string.Empty;
|
||||
string fuelInfoTooltip = string.Empty;
|
||||
|
||||
TweakyTweakTweakers(__instance);
|
||||
|
||||
if (tweaksAndThings == null ||
|
||||
rosterFuelColumnSettings == null ||
|
||||
!tweaksAndThings.IsEnabled() ||
|
||||
@@ -52,7 +55,7 @@ internal class EngineRosterRow_Refresh_Patch
|
||||
bool cabooseRequirementFulfilled =
|
||||
!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()
|
||||
|| consist.ConsistNoFreight()
|
||||
|| (bool)engineOrTender.FindMyCaboose(0.0f, false);
|
||||
|| (bool)engineOrTender.FindMyCabooseSansLoadRequirement();
|
||||
float offendingPercentage = 100f;
|
||||
|
||||
foreach (Car loco in locos)
|
||||
@@ -131,13 +134,54 @@ internal class EngineRosterRow_Refresh_Patch
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rosterFuelColumnSettings.EngineRosterFuelStatusColumn = EngineRosterFuelDisplayColumn.None;
|
||||
Log.Error(ex, "Error Detecting fuel status for engine roster");
|
||||
}
|
||||
}
|
||||
|
||||
private static void TweakyTweakTweakers(EngineRosterRow __instance)
|
||||
{
|
||||
var helperData = EngineTextHelper(__instance._engine);
|
||||
|
||||
if (helperData.HasValue)
|
||||
{
|
||||
__instance.nameLabel.text = helperData.Value.nameLabel;
|
||||
__instance.nameTooltip.tooltipText += helperData.Value.nameTooltip;
|
||||
}
|
||||
|
||||
__instance.crewLabel.text = string.Empty;
|
||||
for (int i = __instance._crewComponents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
string str = __instance._crewComponents[i];
|
||||
if ((new[] { "MU", "AE" }).Contains(str))
|
||||
str = $"<sup>{str} </sup>";
|
||||
__instance.crewLabel.text = $"{str}{__instance.crewLabel.text}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static (string nameLabel, string nameTooltip, int selectedCount)? EngineTextHelper(Car loco, bool mapIcon = false)
|
||||
{
|
||||
(string nameLabel, string nameTooltip, int selectedCount)? output = null;
|
||||
int selectedCount = 0;
|
||||
Dictionary<PlayerId, IPlayer> dictionary = StateManager.Shared.PlayersManager.AllPlayers.ToDictionary((IPlayer p) => p.PlayerId, (IPlayer p) => p);
|
||||
List<string> usersSelected = new();
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
if (new PlayerProperties(PlayerPropertiesManager.Shared._object[kvp.Key.ToString()]).SelectedCarId == loco.id)
|
||||
{
|
||||
usersSelected.Add(kvp.Value.Name);
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCount > 0 && dictionary.Count > 1)
|
||||
output = ($"{(mapIcon && loco is BaseLocomotive ? loco.Ident.RoadNumber : loco.DisplayName)}<sub>{selectedCount}</sub>", $"{Environment.NewLine}Selected by: {string.Join(", ", usersSelected)}", selectedCount);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static void SetLabelAndTooltip(ref TMP_Text label, ref UITooltipProvider tooltip, string fuelInfoText, string fuelInfoTooltip)
|
||||
{
|
||||
label.text = $" {fuelInfoText} {label.text}";
|
||||
|
||||
@@ -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
|
||||
[2;40m[0m[2;40m[2;34m■[0m[2;40m[0m[2;34m[2;40m[0m[2;34m[0m[2;40m[1;2m[1;34m{loco}[0m[1;40m[0m[2;40m[0m[2;40m[1;2m[1;34m[0m[1;40m[0m[2;40m[0m[2;40m[2;34m■[0m[2;40m[0m[2;34m[2;40m[0m[2;34m[0m {msg}
|
||||
```";
|
||||
|
||||
private static string west => @"```ansi
|
||||
[2;40m[0m[2;31m[2;40m◀{loco}[0m[2;31m[0m[2;40m[2;31m [0m[2;40m[0m[2;40m[0m {msg}
|
||||
```";
|
||||
|
||||
private static string east => @"```ansi
|
||||
[2;40m[0m[2;32m[0m[2;40m [0m[2;36m[0m[2;40m[0m[2;36m[2;40m{loco}▶[0m[2;36m[0m {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;
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -39,7 +39,7 @@ internal class MapWindow_OnClick_Patch
|
||||
texture2D.wrapMode = TextureWrapMode.Clamp;
|
||||
if (!ImageConversion.LoadImage(texture2D, File.ReadAllBytes(path)))
|
||||
{
|
||||
_log.Information($"Unable to load {name} icon!");
|
||||
_log.Debug($"Unable to load {name} icon!");
|
||||
return null;
|
||||
}
|
||||
Sprite sprite = Sprite.Create(texture2D, new Rect(0f, 0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Game.Notices;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Serilog;
|
||||
@@ -14,10 +15,11 @@ internal class NoticeExtensions_PostNotice_Patch
|
||||
private static ILogger _log => Log.ForContext<NoticeExtensions_PostNotice_Patch>();
|
||||
static void Postfix(Car car, string key, string content)
|
||||
{
|
||||
if (!StateManager.IsHost) return;
|
||||
try
|
||||
{
|
||||
|
||||
//Log.Information($"{car.DisplayName} patch PostNotice");
|
||||
//Log.Debug($"{car.DisplayName} patch PostNotice");
|
||||
if (!string.IsNullOrEmpty(content) &&
|
||||
key.Equals("ai-wpt") &&
|
||||
content.ToLower().Contains("Arrived at Waypoint".ToLower())
|
||||
|
||||
@@ -24,7 +24,7 @@ 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;
|
||||
bool output = load.id == OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.id;
|
||||
if (output) __result = info.Quantity.FormatCrewHours(load.description);
|
||||
|
||||
return !output;
|
||||
@@ -38,7 +38,7 @@ internal class CarPrototypeLibrary_LoadForId_Patch
|
||||
{
|
||||
public static bool Prefix(string loadId, ref Load __result)
|
||||
{
|
||||
Load load = OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad();
|
||||
Load load = OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours;
|
||||
if (loadId == load.id) __result = load;
|
||||
|
||||
return __result == null;
|
||||
@@ -70,7 +70,9 @@ internal class OpsController_AnnounceCoalescedPayments_Patch
|
||||
typeof(RepairTrack)
|
||||
};
|
||||
|
||||
public static Load CrewHoursLoad()
|
||||
private static Load _crewLoadHours;
|
||||
internal static Load CrewLoadHours => _crewLoadHours ?? CrewHoursLoad();
|
||||
private static Load CrewHoursLoad()
|
||||
{
|
||||
Load load = (Load)ScriptableObject.CreateInstance(typeof(Load));
|
||||
load.name = "crew-hours";
|
||||
@@ -96,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);
|
||||
}
|
||||
@@ -113,18 +116,18 @@ internal class OpsController_AnnounceCoalescedPayments_Patch
|
||||
|
||||
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)}");
|
||||
//Log.Debug($"{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}") ?? [])}");
|
||||
//Log.Debug($"{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}");
|
||||
//Log.Debug($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper ({deltaTime}) => {caboose.t} : {caboose.c2}");
|
||||
CarLoadCrewHelper(caboose.c2, deltaTime);
|
||||
}
|
||||
dateTime = TimeWeather.Now;
|
||||
|
||||
@@ -36,11 +36,11 @@ internal class StateManager_OnDayDidChange_Patch
|
||||
{
|
||||
foreach (var car in TrainController.Shared.Cars.Where(Car_Extensions.IsCaboose))
|
||||
{
|
||||
var data = car.QuantityCapacityOfLoad(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad());
|
||||
var data = car.QuantityCapacityOfLoad(OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours);
|
||||
if (data.quantity < data.capacity)
|
||||
{
|
||||
Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\"");
|
||||
new OpsCarAdapter(car, OpsController.Shared).Load(OpsController_AnnounceCoalescedPayments_Patch.CrewHoursLoad(), data.capacity - data.quantity);
|
||||
new OpsCarAdapter(car, OpsController.Shared).Load(OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours, data.capacity - data.quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -28,7 +32,7 @@ internal class TagController_UpdateTag_Patch
|
||||
return;
|
||||
}
|
||||
|
||||
ProceedWithPostFix(car, tagCallout, tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || !tweaksAndThings.CabooseRequiredForLocoOilIndicator());
|
||||
ProceedWithPostFix(car, tagCallout, tweaksAndThings.CabooseRequiredForLocoOilIndicator());
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -43,7 +47,7 @@ internal class TagController_UpdateTag_Patch
|
||||
//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, oilSpriteName));
|
||||
IEnumerable<Car> consist = car.EnumerateCoupled().Where(c => c.EnableOiling);
|
||||
Func<bool> cabooseRequirementFulfilled = () => (!cabooseRequired || consist.ConsistNoFreight() || car.FindMyCaboose(0.0f, false));
|
||||
Func<bool> cabooseRequirementFulfilled = () => (!cabooseRequired || consist.ConsistNoFreight() || (bool)car.FindMyCabooseSansLoadRequirement());
|
||||
if (StateManager.Shared.Storage.OilFeature
|
||||
&& car.IsLocomotive
|
||||
&& !car.NeedsOiling
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Railloader;
|
||||
using UI;
|
||||
using UI.Tags;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(TrainBrakeDisplay))]
|
||||
[HarmonyPatch(nameof(TrainBrakeDisplay.Update))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class TrainBrakeDisplay_Update_Patch()
|
||||
{
|
||||
private static bool Prefix(TrainBrakeDisplay __instance)
|
||||
{
|
||||
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
TweakedOriginalMethods.TrainBrakeDisplay.Update(__instance);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(TrainBrakeDisplay))]
|
||||
[HarmonyPatch(nameof(TrainBrakeDisplay.ColorForCar))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class TrainBrakeDisplay_ColorForCar_Patch
|
||||
{
|
||||
private static bool Prefix(TrainBrakeDisplay __instance, Car car, ref Color __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.TrainBrakeDisplayShowsColorsInCalloutMode() || !TagController.Shared.TagsVisible) return true;
|
||||
|
||||
TweakedOriginalMethods.TrainBrakeDisplay.ColorForCar(__instance, car, ref __result);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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.
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><!-- Optionally, set a few things to your liking -->
|
||||
<PropertyGroup>
|
||||
<!-- Optionally, set a few things to your liking -->
|
||||
<!-- <MajorVersion>1</MajorVersion> -->
|
||||
<!-- <MinorVersion>0</MinorVersion> -->
|
||||
|
||||
<SignAssembly>False</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<Optimize>False</Optimize>
|
||||
|
||||
@@ -28,8 +28,10 @@ public class Settings
|
||||
bool cabooseRequiredForLocoTagOilIndication,
|
||||
bool servicingFundPenalty,
|
||||
bool safetyFirst,
|
||||
bool safetyFirstClientEnforce,
|
||||
CrewHourLoadMethod loadCrewHoursMethod,
|
||||
float cabeeseSearchRadiusFtInMeters
|
||||
float cabeeseSearchRadiusFtInMeters,
|
||||
bool trainBrakeDisplayShowsColorsInCalloutMode
|
||||
)
|
||||
{
|
||||
WebhookSettingsList = webhookSettingsList;
|
||||
@@ -41,8 +43,10 @@ public class Settings
|
||||
CabooseRequiredForLocoTagOilIndication = cabooseRequiredForLocoTagOilIndication;
|
||||
ServicingFundPenalty = servicingFundPenalty;
|
||||
SafetyFirst = safetyFirst;
|
||||
SafetyFirstClientEnforce = safetyFirstClientEnforce;
|
||||
LoadCrewHoursMethod = loadCrewHoursMethod;
|
||||
CabeeseSearchRadiusFtInMeters = cabeeseSearchRadiusFtInMeters;
|
||||
TrainBrakeDisplayShowsColorsInCalloutMode = trainBrakeDisplayShowsColorsInCalloutMode;
|
||||
}
|
||||
|
||||
public readonly UIState<string> _selectedTabState = new UIState<string>(null);
|
||||
@@ -55,8 +59,10 @@ public class Settings
|
||||
public bool CabooseRequiredForLocoTagOilIndication;
|
||||
public bool ServicingFundPenalty;
|
||||
public bool SafetyFirst;
|
||||
public bool SafetyFirstClientEnforce;
|
||||
public CrewHourLoadMethod LoadCrewHoursMethod;
|
||||
public float CabeeseSearchRadiusFtInMeters;
|
||||
public bool TrainBrakeDisplayShowsColorsInCalloutMode;
|
||||
|
||||
internal void AddAnotherRow()
|
||||
{
|
||||
@@ -64,7 +70,7 @@ public class Settings
|
||||
if (!string.IsNullOrEmpty(WebhookSettingsList.OrderByDescending(wsl => wsl.WebhookUrl).Last().WebhookUrl))
|
||||
{
|
||||
WebhookSettingsList.Add(new());
|
||||
Log.Information($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in");
|
||||
Log.Debug($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,16 +132,18 @@ public static class SettingsExtensions
|
||||
public static bool RequireConsistCabooseForOilerAndHotboxSpotter(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.RequireConsistCabooseForOilerAndHotboxSpotter ?? false;
|
||||
public static bool CabooseNonMotiveAllowedSetting(this TweaksAndThingsPlugin input, Car car) =>
|
||||
input.EndGearHelpersRequirePayment() && !car.MotivePower() && (bool)car.FindMyCaboose(0.0f, false);
|
||||
input.EndGearHelpersRequirePayment() && !car.MotivePower() && (bool)car.FindMyCabooseSansLoadRequirement();
|
||||
public static bool CabooseRequiredForLocoOilIndicator(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.CabooseRequiredForLocoTagOilIndication ?? false;
|
||||
public static bool ServiceFundPenalties(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.ServicingFundPenalty ?? false;
|
||||
public static bool SafetyFirst(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.SafetyFirst ?? false;
|
||||
public static bool SafetyFirstClientEnforce(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.SafetyFirstClientEnforce ?? true;
|
||||
public static bool DayLoadCrewHours(this TweaksAndThingsPlugin input) =>
|
||||
(input?.settings?.LoadCrewHoursMethod ?? CrewHourLoadMethod.Tracks) == CrewHourLoadMethod.Daily;
|
||||
public static float CabeeseSearchRadiusInMeters(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.CabeeseSearchRadiusFtInMeters ?? 21f;
|
||||
public static bool TrainBrakeDisplayShowsColorsInCalloutMode(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.TrainBrakeDisplayShowsColorsInCalloutMode ?? false;
|
||||
|
||||
}
|
||||
177
TweaksAndThings/TweakedOriginalMethods/TrainBrakeDisplay.cs
Normal file
177
TweaksAndThings/TweakedOriginalMethods/TrainBrakeDisplay.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using Model;
|
||||
using Model.Ops;
|
||||
using Model.Physics;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Patches;
|
||||
using UI;
|
||||
using UI.CarInspector;
|
||||
using UI.Common;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using static UnityEngine.UI.Image;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.TweakedOriginalMethods
|
||||
{
|
||||
internal static class TrainBrakeDisplay
|
||||
{
|
||||
internal static void ColorForCar(UI.TrainBrakeDisplay __instance, Car car, ref Color __result)
|
||||
{
|
||||
__result = Color.gray;
|
||||
|
||||
if (car.IsDerailed)
|
||||
{
|
||||
__result = __instance.derailedColor;
|
||||
return;
|
||||
}
|
||||
CarAirSystem air = car.air;
|
||||
OpsController opsController = OpsController.Shared;
|
||||
if (air.handbrakeApplied)
|
||||
{
|
||||
__result = __instance.handbrakeAppliedColor;
|
||||
return;
|
||||
}
|
||||
else if (
|
||||
(Object)(object)opsController != (Object)null &&
|
||||
opsController.TryGetDestinationInfo(car, out var destinationName, out var isAtDestination, out var destinationPosition, out var destination)
|
||||
)
|
||||
{
|
||||
Area area = opsController.AreaForCarPosition(destination);
|
||||
if ((Object)(object)area != (Object)null)
|
||||
{
|
||||
__result = area.tagColor;
|
||||
}
|
||||
if (!isAtDestination)
|
||||
{
|
||||
float num = 1f / __result.maxColorComponent;
|
||||
__result *= num;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mathf.InverseLerp(0f, 72f, air.BrakeCylinder.Pressure) >= 10f) __result = (__result + __instance.ColorForPsi(air.BrakeCylinder.Pressure)) / 2f;
|
||||
}
|
||||
|
||||
internal static Image GetCarImage(UI.TrainBrakeDisplay __instance, int index, float xCyl, float yCyl, Car car)
|
||||
{
|
||||
Image output = null;
|
||||
if (__instance._carImages.Count - 1 < index)
|
||||
{
|
||||
var tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
GameObject val = new GameObject();
|
||||
val.transform.SetParent(((Component)__instance).transform, false);
|
||||
((Object)val).name = $"Car {index}";
|
||||
float height = 12f;
|
||||
val.AddComponent<RectTransform>().SetFrame(xCyl, yCyl, __instance._imageWidth, height);
|
||||
EventTrigger trigger = val.AddComponent<EventTrigger>();
|
||||
EventTrigger.Entry entry = new EventTrigger.Entry();
|
||||
entry.eventID = EventTriggerType.PointerClick;
|
||||
entry.callback.AddListener((eventData) => {
|
||||
PointerEventData ped = (PointerEventData)eventData;
|
||||
PickableActivation pa = PickableActivation.Primary;
|
||||
if (ped.button == PointerEventData.InputButton.Right) pa = PickableActivation.Secondary;
|
||||
|
||||
bool checkFurther = ped.button == PointerEventData.InputButton.Middle ? true : CarPickable_Activate_Patch.HandleCarOrTrainBrakeDisplayClick(car, tweaksAndThings, pa);
|
||||
if (checkFurther & GameInput.IsControlDown) CarInspector.Show(car);
|
||||
});
|
||||
trigger.triggers.Add(entry);
|
||||
output = val.AddComponent<Image>();
|
||||
|
||||
output.type = (Type)1;
|
||||
__instance._carImages.Add(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
output = __instance._carImages[index];
|
||||
((Component)output).gameObject.SetActive(true);
|
||||
}
|
||||
output.sprite = (car.IsLocomotive ? __instance.locomotiveTile : __instance.carTile);
|
||||
return output;
|
||||
}
|
||||
|
||||
internal static void Update(UI.TrainBrakeDisplay __instance)
|
||||
{
|
||||
Car selectedCar = __instance._trainController.SelectedCar;
|
||||
if (selectedCar == null || selectedCar.set == null) return;
|
||||
|
||||
int numberOfCars = selectedCar.set.NumberOfCars;
|
||||
if (numberOfCars != __instance._lastNumCars)
|
||||
{
|
||||
__instance.RemoveAllImages();
|
||||
int num = Mathf.Clamp(numberOfCars, 0, 100);
|
||||
float num2 = (float)num * 8f + (float)(num - 1) * 1f;
|
||||
if (num2 > __instance._rectTransform.rect.width)
|
||||
{
|
||||
float num3 = __instance._rectTransform.rect.width / num2;
|
||||
__instance._imageWidth = 8f * num3;
|
||||
__instance._spacing = 1f * num3;
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance._imageWidth = 8f;
|
||||
__instance._spacing = 1f;
|
||||
}
|
||||
|
||||
__instance._lastNumCars = numberOfCars;
|
||||
}
|
||||
|
||||
int num4 = 0;
|
||||
float num5 = __instance._imageWidth / 2f;
|
||||
float num6 = 0f;
|
||||
float y = 12f - 2f * __instance._spacing;
|
||||
Car.LogicalEnd logicalEnd = ((selectedCar.set.IndexOfCar(selectedCar).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 = selectedCar.set.StartIndexForConnected(selectedCar, logicalEnd, IntegrationSet.EnumerationCondition.Coupled);
|
||||
Car car;
|
||||
while (!stop && (car = selectedCar.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null && !(num5 > __instance._rectTransform.rect.width))
|
||||
{
|
||||
Image carImage = TweakedOriginalMethods.TrainBrakeDisplay.GetCarImage(__instance, num4, num5, 0f, car);
|
||||
num5 += __instance._imageWidth + __instance._spacing;
|
||||
carImage.color = __instance.ColorForCar(car);
|
||||
Image airImage = __instance.GetAirImage(num4, num6, y);
|
||||
num6 += __instance._imageWidth + __instance._spacing;
|
||||
Color color;
|
||||
if (!car[logicalEnd].IsCoupled)
|
||||
{
|
||||
color = ColorForOuterAnglecock(car[logicalEnd].AnglecockSetting);
|
||||
}
|
||||
else
|
||||
{
|
||||
Car car2 = car.CoupledTo(logicalEnd);
|
||||
bool num7 = car2 != null && car2[end].AnglecockSetting > 0.9f;
|
||||
bool flag = car[logicalEnd].AnglecockSetting > 0.9f;
|
||||
color = ((num7 && flag && car[logicalEnd].IsAirConnected) ? Color.clear : Color.white);
|
||||
}
|
||||
|
||||
airImage.color = color;
|
||||
if (!car[end].IsCoupled)
|
||||
{
|
||||
__instance.GetAirImage(num4 + 1, num6, y).color = ColorForOuterAnglecock(car[end].AnglecockSetting);
|
||||
}
|
||||
|
||||
num4++;
|
||||
}
|
||||
|
||||
for (int i = num4; i < __instance._carImages.Count; i++)
|
||||
{
|
||||
__instance._carImages[i].gameObject.SetActive(value: false);
|
||||
}
|
||||
|
||||
for (int j = num4 + 1; j < __instance._airImages.Count; j++)
|
||||
{
|
||||
__instance._airImages[j].gameObject.SetActive(value: false);
|
||||
}
|
||||
|
||||
static Color ColorForOuterAnglecock(float value)
|
||||
{
|
||||
if (!((double)value < 0.01))
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
return Color.clear;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
|
||||
static TweaksAndThingsPlugin()
|
||||
{
|
||||
Log.Information("Hello! Static Constructor was called!");
|
||||
}
|
||||
|
||||
public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self)
|
||||
@@ -49,7 +48,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
|
||||
this.moddingContext = moddingContext;
|
||||
|
||||
logger.Information("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version);
|
||||
logger.Debug("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version);
|
||||
|
||||
moddingContext.RegisterConsoleCommand(new EchoCommand());
|
||||
|
||||
@@ -58,7 +57,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
logger.Information("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.Information("Daytime!");
|
||||
|
||||
if (settings == null) settings = new();
|
||||
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
|
||||
if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new();
|
||||
@@ -88,7 +83,9 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
settings?.WebhookSettingsList.SanitizeEmptySettings();
|
||||
|
||||
builder.AddSection("Adjustments To Base Game", (UIPanelBuilder builder) => {
|
||||
builder.AddLabel("Repair tracks now require cars to be waybilled, or they will not be serviced/overhauled.\nThey will report on the company window's location section as 'No Work Order Assigned'.");
|
||||
builder.AddLabel("1) Repair tracks now require cars to be waybilled, or they will not be serviced/overhauled.\nThey will report on the company window's location section as 'No Work Order Assigned'.");
|
||||
builder.Spacer(spacing * spacing);
|
||||
builder.AddLabel("2) You now have the same click options on the little car icons in the lower left engine controls ui, as you do with cars in the game. Ctrl click -> open car inspector, etc.");
|
||||
});
|
||||
builder.Spacer(spacing * spacing);
|
||||
builder.AddTabbedPanels(settings._selectedTabState, delegate (UITabbedPanelBuilder tabBuilder)
|
||||
@@ -206,6 +203,23 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
|
||||
).Tooltip("Safety First", $@"On non-express timetabled consists, a caboose is required in the consist increase AE max speed > 20 in {Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Road)}/{Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Waypoint)} mode.");
|
||||
#endregion
|
||||
|
||||
#region SafetyFirstClient
|
||||
if (settings?.SafetyFirst ?? false)
|
||||
{
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
"Safety First! (Enforce Client Speed Restrictions)",
|
||||
() => settings?.SafetyFirstClientEnforce ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.SafetyFirstClientEnforce = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Safety First! (Enforce Client Speed Restrictions)", $@"Enforce cabeese dominance on clients; uncheck to allow clients to override the 20mph restriction.");
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
private void UiUpdates(UIPanelBuilder builder)
|
||||
@@ -236,6 +250,18 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
|
||||
}
|
||||
).Tooltip("Allow Insufficient Funds", $@"Will allow interchange service and repair shops to still function when you are insolvent, at a 20% overdraft fee.");
|
||||
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
"Train Brake Color Mode",
|
||||
() => this.TrainBrakeDisplayShowsColorsInCalloutMode(),
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.TrainBrakeDisplayShowsColorsInCalloutMode = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Train Brake Color Mode", $@"When enabled/checked and car tag callout mode is enabled (showing car tags hovering over them), the train brake display of the selected locomotive will change the cars/engines to their destination area's color to help you visualize sets of cars at a glance.");
|
||||
|
||||
builder.Spacer(spacing);
|
||||
EngineRosterShowsFuelStatusUISection(builder);
|
||||
}
|
||||
@@ -256,7 +282,7 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
|
||||
builder.Rebuild();
|
||||
}
|
||||
)
|
||||
).Tooltip("Enable Fuel Display in Engine Roster", $"Will add reaming fuel indication to Engine Roster (with details in roster row tool tip), Examples : {string.Join(" ", Enumerable.Range(0, 4).Select(i => TextSprites.PiePercent(i, 4)))}");
|
||||
).Tooltip("Enable Fuel Display in Engine Roster", $"Will add reamaing fuel indication to Engine Roster (with details in roster row tool tip), Examples : {string.Join(" ", Enumerable.Range(0, 4).Select(i => TextSprites.PiePercent(i, 4)))}");
|
||||
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
@@ -333,7 +359,6 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
|
||||
|
||||
public void ModTabDidClose()
|
||||
{
|
||||
logger.Information("Nighttime...");
|
||||
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user