mirror of
https://github.com/rmroc451/TweaksAndThings.git
synced 2025-12-16 09:19:37 -06:00
Compare commits
12 Commits
v2.0.1
...
V2.1.4-bet
| Author | SHA1 | Date | |
|---|---|---|---|
| 52e9fe1cab | |||
| ec9aadd6e2 | |||
| af8e1bd6ca | |||
| a9943b6c0c | |||
| 2713247895 | |||
| 22c6fb8e5e | |||
| fa7e7dfcd2 | |||
| f1ba2ed6f1 | |||
| 11dba3ff78 | |||
| 3d0a57c56e | |||
| b3780b8370 | |||
| 3741995d3a |
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MajorVersion>2</MajorVersion>
|
||||
<MinorVersion>0</MinorVersion>
|
||||
<PatchVersion>1</PatchVersion>
|
||||
<MinorVersion>1</MinorVersion>
|
||||
<PatchVersion>4</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.*
|
||||
|
||||
@@ -46,9 +46,9 @@ 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 ?? "???"));
|
||||
|
||||
car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}");
|
||||
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}\"");
|
||||
if (!StateManager.IsHost) Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {Hyperlink.To(car)}: \"{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: false);
|
||||
|
||||
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();
|
||||
|
||||
@@ -38,7 +38,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
|
||||
|
||||
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;");
|
||||
_log.Debug($"{Enum.GetName(typeof(AutoEngineerMode), mode)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] {nameof(AutoEngineerOrdersExtensions.MaxSpeedMph)} limited to {limitedSpeed} from {orig}; No Caboose in Consist;");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,9 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
|
||||
{
|
||||
var _persistence = new AutoEngineerPersistence(TrainController.Shared.SelectedLocomotive.KeyValueObject);
|
||||
var OrdersHelper = new AutoEngineerOrdersHelper(TrainController.Shared.SelectedLocomotive, _persistence);
|
||||
|
||||
|
||||
if (TrainController.Shared.SelectedLocomotive.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)}[{TrainController.Shared.SelectedLocomotive.DisplayName}] ";
|
||||
Func<bool> firstClass = () =>
|
||||
@@ -70,7 +72,7 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
|
||||
|
||||
Func<bool> noCaboose = () =>
|
||||
{
|
||||
var output = TrainController.Shared.SelectedLocomotive.FindMyCaboose(0.0f, false) == null;
|
||||
bool output = TrainController.Shared.SelectedLocomotive.FindMyCabooseSansLoadRequirement() == null;
|
||||
logMessage += $"\ncaboose? {!output}";
|
||||
return output;
|
||||
};
|
||||
@@ -84,6 +86,8 @@ internal class AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch
|
||||
noCaboose();
|
||||
|
||||
logMessage += $"\nGovern AE? {output}";
|
||||
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Debug))
|
||||
logMessage += $"\n{Environment.StackTrace}";
|
||||
|
||||
_log.Information(logMessage);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Game.Notices;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Network;
|
||||
using Serilog;
|
||||
using UI.EngineControls;
|
||||
using UnityEngine;
|
||||
@@ -16,11 +18,14 @@ 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"));
|
||||
if (StateManager.IsHost)
|
||||
{
|
||||
_log.Debug($"start setWP");
|
||||
Car selectedLoco = __instance._locomotive;
|
||||
_log.Debug($"{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,344 @@
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using KeyValue.Runtime;
|
||||
using Model;
|
||||
using Model.AI;
|
||||
using Model.Ops;
|
||||
using Model.Physics;
|
||||
using Network;
|
||||
using Network.Messages;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Track;
|
||||
using Track.Search;
|
||||
using UI;
|
||||
using UI.EngineControls;
|
||||
using static Track.Search.RouteSearch;
|
||||
using Location = Track.Location;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerWaypointControls))]
|
||||
[HarmonyPatch(nameof(AutoEngineerWaypointControls.ConfigureOptionsDropdown))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch>();
|
||||
|
||||
private static readonly HashSet<IDisposable> _keyChangeObservers = new();
|
||||
private static readonly Dictionary<string, Dictionary<string, OpsCarPosition>> locoConsistDestinations = new();
|
||||
private static string _lastSelectedLoco = string.Empty;
|
||||
|
||||
static string getDictKey(Car car) => car.DisplayName;
|
||||
|
||||
static void Postfix(AutoEngineerWaypointControls __instance, ref OptionsDropdownConfiguration __result)
|
||||
{
|
||||
PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars);
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled() || (locoConsistDestinations.TryGetValue(getDictKey(selectedLoco), out Dictionary<string, OpsCarPosition> cars) && cars != null && _lastSelectedLoco == getDictKey(selectedLoco))) return;
|
||||
|
||||
_lastSelectedLoco = getDictKey(selectedLoco);
|
||||
|
||||
IterateCarsDetectDestinations(
|
||||
__instance,
|
||||
__result,
|
||||
selectedLoco,
|
||||
numberOfCars,
|
||||
out List<DropdownMenu.RowData> rowDatas,
|
||||
out Action<int> func,
|
||||
out int origCount,
|
||||
out int maxRowOrig,
|
||||
out AutoEngineerOrdersHelper aeoh
|
||||
);
|
||||
|
||||
List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco);
|
||||
|
||||
__result = WireUpJumpTosToSettingMenu(
|
||||
__instance,
|
||||
selectedLoco,
|
||||
rowDatas,
|
||||
func,
|
||||
origCount,
|
||||
maxRowOrig,
|
||||
aeoh,
|
||||
ref jumpTos
|
||||
);
|
||||
}
|
||||
|
||||
private static OptionsDropdownConfiguration WireUpJumpTosToSettingMenu(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco, List<DropdownMenu.RowData> rowDatas, Action<int> func, int origCount, int maxRowOrig, AutoEngineerOrdersHelper aeoh, ref List<(string destinationId, string destination, float? distance, Location? location)> jumpTos)
|
||||
{
|
||||
OptionsDropdownConfiguration __result;
|
||||
jumpTos = jumpTos?.OrderBy(c => c.distance ?? float.MaxValue)?.ToList() ?? [];
|
||||
var localJumpTos = jumpTos.ToList();
|
||||
var safetyFirst = AutoEngineerOrdersHelper_SendAutoEngineerCommand_Patch.SafetyFirstGoverningApplies() && 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($"{TrainController.Shared.SelectedLocomotive.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, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco)
|
||||
{
|
||||
List<(string destinationId, string destination, float? distance, Location? location)> jumpTos = new();
|
||||
foreach (var ocp in locoConsistDestinations[getDictKey(selectedLoco)].Values.Distinct())
|
||||
{
|
||||
string destName = ocp.DisplayName;
|
||||
string destId = ocp.Identifier;
|
||||
float? distance = null;
|
||||
|
||||
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;
|
||||
};
|
||||
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
|
||||
jumpTos.Add((
|
||||
destinationId: destId,
|
||||
destination: $"WP> {destName}"
|
||||
, distance: distance
|
||||
, location: (Location?)destLoc
|
||||
));
|
||||
}
|
||||
|
||||
return jumpTos;
|
||||
}
|
||||
|
||||
private static void IterateCarsDetectDestinations(AutoEngineerWaypointControls __instance, OptionsDropdownConfiguration __result, BaseLocomotive selectedLoco, int numberOfCars, out List<DropdownMenu.RowData> rowDatas, out Action<int> func, out int origCount, out int maxRowOrig, out AutoEngineerOrdersHelper aeoh)
|
||||
{
|
||||
rowDatas = __result.Rows.ToList();
|
||||
func = __result.OnRowSelected;
|
||||
origCount = rowDatas.Count;
|
||||
maxRowOrig = origCount - 1;
|
||||
if (!locoConsistDestinations.ContainsKey(getDictKey(selectedLoco))) locoConsistDestinations.Add(getDictKey(selectedLoco), new());
|
||||
|
||||
Dictionary<string, OpsCarPosition> seen = new();
|
||||
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
|
||||
Car.LogicalEnd logicalEnd = ((selectedLoco.set.IndexOfCar(selectedLoco).GetValueOrDefault(0) >= numberOfCars / 2) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
|
||||
Car.LogicalEnd end = ((logicalEnd == Car.LogicalEnd.A) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
|
||||
bool stop = false;
|
||||
int carIndex = selectedLoco.set.StartIndexForConnected(selectedLoco, logicalEnd, IntegrationSet.EnumerationCondition.Coupled);
|
||||
Car car;
|
||||
while (!stop && (car = selectedLoco.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null)
|
||||
{
|
||||
AddObserversToCar(__instance, car);
|
||||
var ocp = GetCarDestinationIdentifier(car);
|
||||
_log.Debug($"{getDictKey(selectedLoco)} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}");
|
||||
|
||||
if (ocp.HasValue)
|
||||
{
|
||||
seen.Add(getDictKey(car), ocp.Value);
|
||||
if (locoConsistDestinations[getDictKey(selectedLoco)].TryGetValue(getDictKey(car), out _))
|
||||
locoConsistDestinations[getDictKey(selectedLoco)][getDictKey(car)] = ocp.Value;
|
||||
else
|
||||
locoConsistDestinations[getDictKey(selectedLoco)].Add(getDictKey(car), ocp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
_log.Debug($"{getDictKey(selectedLoco)} --> [{seen.Keys.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}");
|
||||
_log.Debug($"{getDictKey(selectedLoco)} --> [{locoConsistDestinations[getDictKey(selectedLoco)].Keys.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations[getDictKey(selectedLoco)].Select(k => $"{k.Key}:{k.Value.DisplayName}"))}");
|
||||
|
||||
var dropped =
|
||||
locoConsistDestinations[getDictKey(selectedLoco)].Keys.Except(seen.Keys).ToDictionary(
|
||||
t => t,
|
||||
t => locoConsistDestinations[getDictKey(selectedLoco)][t]
|
||||
); //are no longer here
|
||||
|
||||
_log.Debug($"{getDictKey(selectedLoco)} --> [{dropped.Keys.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => $"{k.Key}:{k.Value.DisplayName}"))}");
|
||||
|
||||
locoConsistDestinations[getDictKey(selectedLoco)] =
|
||||
locoConsistDestinations[getDictKey(selectedLoco)].Keys.Intersect(seen.Keys).ToDictionary(
|
||||
t => t,
|
||||
t => locoConsistDestinations[getDictKey(selectedLoco)][t]
|
||||
); //remove ones that are no longer here
|
||||
seen.Clear();
|
||||
}
|
||||
|
||||
private static void PrepLocoUsage(out BaseLocomotive selectedLoco, out int numberOfCars)
|
||||
{
|
||||
//wire up that loco
|
||||
selectedLoco = TrainController.Shared.SelectedLocomotive;
|
||||
numberOfCars = selectedLoco.set.NumberOfCars;
|
||||
_log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]");
|
||||
foreach (var o in _keyChangeObservers) o.Dispose();
|
||||
_keyChangeObservers.Clear();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static void AddObserversToCar(AutoEngineerWaypointControls __instance, Car c)
|
||||
{
|
||||
AddObserver(__instance, c, Car.KeyOpsWaybill);
|
||||
AddObserver(__instance, c, Car.KeyOpsRepairDestination);
|
||||
foreach (Car.LogicalEnd logicalEnd in CarInspector_PopulateCarPanel_Patch.ends)
|
||||
{
|
||||
AddObserver(__instance, c, Car.KeyValueKeyFor(Car.EndGearStateKey.IsCoupled, c.LogicalToEnd(logicalEnd)), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddObserver(AutoEngineerWaypointControls __instance, Model.Car car, string key, bool clearCarCache = false)
|
||||
{
|
||||
_keyChangeObservers.Add(
|
||||
car.KeyValueObject.Observe(
|
||||
key,
|
||||
delegate (Value value)
|
||||
{
|
||||
|
||||
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
|
||||
|
||||
if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out Dictionary<string, OpsCarPosition> cars) && cars == null) return;
|
||||
|
||||
bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key);
|
||||
string? destId =
|
||||
waybillChng ?
|
||||
GetCarDestinationIdentifier(car)?.Identifier ?? null :
|
||||
null;
|
||||
if (waybillChng)
|
||||
{
|
||||
if (cars.TryGetValue(getDictKey(car), out OpsCarPosition pos)
|
||||
&& pos.Identifier == destId)
|
||||
{
|
||||
return;
|
||||
} else
|
||||
{
|
||||
_log.Debug($"{getDictKey(car)} OBSV {key}: destNew; {destId}; reload");
|
||||
}
|
||||
} else
|
||||
{
|
||||
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
foreach(var o in _keyChangeObservers)
|
||||
{
|
||||
o.Dispose();
|
||||
}
|
||||
var loco = TrainController.Shared.SelectedLocomotive;
|
||||
if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null;
|
||||
_keyChangeObservers.Clear();
|
||||
if (locoConsistDestinations.TryGetValue(getDictKey(TrainController.Shared.SelectedLocomotive), out _)) locoConsistDestinations[getDictKey(TrainController.Shared.SelectedLocomotive)] = null;
|
||||
TrainController.Shared.SelectedCar = loco;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}");
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static Waybill? GetWaybill(Car car, Value waybillValue)
|
||||
{
|
||||
Waybill? _waybill = null;
|
||||
try
|
||||
{
|
||||
_waybill = Model.Ops.Waybill.FromPropertyValue(waybillValue, OpsController.Shared);
|
||||
}
|
||||
catch (OpsController.InvalidOpsCarPositionException ex)
|
||||
{
|
||||
Log.Error(ex, "Waybill for car {car} contains an invalid ops position: {pos}", car, ex.Identifier);
|
||||
_waybill = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Warning(exception, "{car} Exception in Waybill.FromPropertyValue", car);
|
||||
_waybill = null;
|
||||
}
|
||||
return _waybill;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,92 +42,179 @@ 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)
|
||||
{
|
||||
bool output = true;
|
||||
var consist = __instance.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);
|
||||
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
|
||||
//CTRL + ALT : Release Consist Brakes and Check AngleCocks
|
||||
//ALT + SHIFT : toggle consist brakes
|
||||
//CTRL + SHIFT : Check Consist Angle Cocks
|
||||
//ALT : Toggle car brakes and & air up cars
|
||||
//CTRL : NOTHING; BASE CAR INSPECTOR
|
||||
}
|
||||
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 = car.EnumerateCoupled();
|
||||
bool handbrakesApplied = consist.Any(c => c.HandbrakeApplied());
|
||||
bool airSystemIssues = consist.Any(c => c.EndAirSystemIssue());
|
||||
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
|
||||
//CTRL + ALT : Release Consist Brakes and Check AngleCocks
|
||||
//ALT + SHIFT : toggle consist brakes
|
||||
//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;
|
||||
}
|
||||
return output;
|
||||
|
||||
|
||||
//else if (ctrlHeld && shiftHeld)
|
||||
//{
|
||||
// var selected = UI.CarInspector.CarInspector._instance._selectedTabState.Value;
|
||||
// UI.CarInspector.CarInspector.Show(__instance.car);
|
||||
// UI.CarInspector.CarInspector._instance._selectedTabState.Value = selected;
|
||||
|
||||
//}
|
||||
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 true;
|
||||
return output;
|
||||
}
|
||||
|
||||
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 AltClickageMyBrosif(Car car, PickableActivation activation)
|
||||
{
|
||||
//if (GameInput.IsControlDown)
|
||||
//{
|
||||
|
||||
//}
|
||||
//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;
|
||||
//}
|
||||
}
|
||||
|
||||
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(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
|
||||
@@ -44,23 +43,24 @@ internal class CarPickable_HandleShowContextMenu_Patch
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.BleedAirSystem, buttonsHaveCost);
|
||||
});
|
||||
}
|
||||
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, $"{(car.EnumerateCoupled().Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} Consist", SpriteName.Handbrake, delegate
|
||||
{
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost);
|
||||
});
|
||||
|
||||
if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.Unused2, $"Air Up Consist", SpriteName.Select, delegate
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, $"{(car.EnumerateCoupled().Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} Consist", SpriteName.Handbrake, delegate
|
||||
{
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Handbrake, buttonsHaveCost);
|
||||
});
|
||||
|
||||
if (car.EnumerateCoupled().Any(c => c.EndAirSystemIssue()))
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.General, $"Air Up Consist", SpriteName.Select, delegate
|
||||
{
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (car.SupportsBleed())
|
||||
else
|
||||
{
|
||||
if (car.SupportsBleed())
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, "Bleed", SpriteName.Bleed, car.SetBleed);
|
||||
}
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, car.air.handbrakeApplied ? "Release Handbrake" : "Apply Handbrake", SpriteName.Handbrake, delegate
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,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)
|
||||
|
||||
@@ -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";
|
||||
@@ -113,18 +115,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ internal class TagController_UpdateTag_Patch
|
||||
return;
|
||||
}
|
||||
|
||||
ProceedWithPostFix(car, tagCallout, tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() || !tweaksAndThings.CabooseRequiredForLocoOilIndicator());
|
||||
ProceedWithPostFix(car, tagCallout, tweaksAndThings.CabooseRequiredForLocoOilIndicator());
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -43,7 +43,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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -29,7 +29,8 @@ public class Settings
|
||||
bool servicingFundPenalty,
|
||||
bool safetyFirst,
|
||||
CrewHourLoadMethod loadCrewHoursMethod,
|
||||
float cabeeseSearchRadiusFtInMeters
|
||||
float cabeeseSearchRadiusFtInMeters,
|
||||
bool trainBrakeDisplayShowsColorsInCalloutMode
|
||||
)
|
||||
{
|
||||
WebhookSettingsList = webhookSettingsList;
|
||||
@@ -43,6 +44,7 @@ public class Settings
|
||||
SafetyFirst = safetyFirst;
|
||||
LoadCrewHoursMethod = loadCrewHoursMethod;
|
||||
CabeeseSearchRadiusFtInMeters = cabeeseSearchRadiusFtInMeters;
|
||||
TrainBrakeDisplayShowsColorsInCalloutMode = trainBrakeDisplayShowsColorsInCalloutMode;
|
||||
}
|
||||
|
||||
public readonly UIState<string> _selectedTabState = new UIState<string>(null);
|
||||
@@ -57,6 +59,7 @@ public class Settings
|
||||
public bool SafetyFirst;
|
||||
public CrewHourLoadMethod LoadCrewHoursMethod;
|
||||
public float CabeeseSearchRadiusFtInMeters;
|
||||
public bool TrainBrakeDisplayShowsColorsInCalloutMode;
|
||||
|
||||
internal void AddAnotherRow()
|
||||
{
|
||||
@@ -64,7 +67,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,7 +129,7 @@ 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) =>
|
||||
@@ -135,7 +138,7 @@ public static class SettingsExtensions
|
||||
input?.settings?.SafetyFirst ?? false;
|
||||
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,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
|
||||
static TweaksAndThingsPlugin()
|
||||
{
|
||||
Log.Information("Hello! Static Constructor was called!");
|
||||
Log.Debug("Hello! Static Constructor was called!");
|
||||
}
|
||||
|
||||
public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self)
|
||||
@@ -49,7 +49,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 +58,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
logger.Information("OnEnable() was called!");
|
||||
logger.Debug("OnEnable() was called!");
|
||||
var harmony = new Harmony(modDefinition.Id);
|
||||
harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty));
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
|
||||
|
||||
public void ModTabDidOpen(UIPanelBuilder builder)
|
||||
{
|
||||
logger.Information("Daytime!");
|
||||
logger.Debug("Daytime!");
|
||||
|
||||
if (settings == null) settings = new();
|
||||
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
|
||||
@@ -88,7 +88,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)
|
||||
@@ -236,6 +238,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 +270,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 +347,7 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
|
||||
|
||||
public void ModTabDidClose()
|
||||
{
|
||||
logger.Information("Nighttime...");
|
||||
logger.Debug("Nighttime...");
|
||||
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user