mirror of
https://github.com/rmroc451/TweaksAndThings.git
synced 2025-12-16 09:19:37 -06:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbca69d66b | |||
| 32f3e8b2db | |||
| a7ae1d479c | |||
| b562424f8b | |||
| f1b68d2827 | |||
| b911ee6f6f | |||
| cc18579507 | |||
| 1dadb04cbe | |||
| 1032edb7ef | |||
| c0e75c3c39 | |||
| bd215d0dcc | |||
| 554ea11790 | |||
| c114aec3b3 | |||
| 38a43276f7 | |||
| afd18b9d27 | |||
| 52e9fe1cab | |||
| ec9aadd6e2 | |||
| af8e1bd6ca | |||
| a9943b6c0c | |||
| 2713247895 | |||
| 22c6fb8e5e | |||
| fa7e7dfcd2 | |||
| f1ba2ed6f1 | |||
| 11dba3ff78 | |||
| 3d0a57c56e | |||
| b3780b8370 | |||
| 3741995d3a | |||
| aa6deab8f5 | |||
| facb0b7d71 | |||
| 5affe27323 | |||
| d19a1a2995 | |||
| 7c8becd471 | |||
| 26a3bdaaa1 | |||
| 3f2e3dee80 | |||
| ed44ea829e | |||
| bce1d8bd58 | |||
| c25271ee2d | |||
| 80f0847587 | |||
| 7552422b6e | |||
| 58dc7efac0 | |||
| 7ebd14db2a | |||
| ca0e78b971 | |||
| 9f210b0b8a | |||
| 840f35cf62 | |||
| 786db49b68 | |||
| 108900b026 | |||
| 9e8c38e6f4 | |||
| 89f2490f14 | |||
| fbd08b007c | |||
| 27d3432cbf | |||
| cf1d5e32e5 | |||
| 92317b29b2 | |||
| 1f48bf04aa | |||
| 5774c7f04c | |||
| 5eed492b47 | |||
| d4d18d8c92 | |||
| d7e35828b8 | |||
| 0b444d6364 | |||
| d197ff8d8a | |||
| 89894890b4 | |||
| a13701c3d2 | |||
| b5be17703f | |||
| 35afa4520d | |||
| 80d064c950 | |||
| ec21effd30 | |||
| 2242aaacde | |||
| e26713688b | |||
| 8a228be5a8 | |||
| 2ed33465d9 | |||
| 67d39801de | |||
| 8dc87f312d | |||
| 9cb788d86a | |||
| 2e47536028 | |||
| b6edc93636 | |||
| 6693a762c9 | |||
| fc43d54815 | |||
| 0adcbfa787 | |||
| 715629394d | |||
| 1ea3d9917d | |||
| d780b075dc | |||
| d0e2c3f99e | |||
| 8ae319fbee |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -396,4 +396,3 @@ FodyWeavers.xsd
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
/TweaksAndThings/Commands/EchoCommand.cs
|
||||
|
||||
7
Assembly.version
Normal file
7
Assembly.version
Normal file
@@ -0,0 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MajorVersion>2</MajorVersion>
|
||||
<MinorVersion>1</MinorVersion>
|
||||
<PatchVersion>7</PatchVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,5 +1,6 @@
|
||||
<Project>
|
||||
<Import Project="Paths.user" Condition="Exists('Paths.user')" />
|
||||
<Import Project="Assembly.version" Condition="Exists('Assembly.version')" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
<PropertyGroup>
|
||||
<!-- Try to find the directory on our own -->
|
||||
<GameManagedDir Condition="'$(GameManagedDir)' == ''">$([System.IO.Directory]::GetDirectories(`$(GameDir)`, `*_Data`)[0])\Managed</GameManagedDir>
|
||||
|
||||
|
||||
<!-- Copy the mod to the game directory -->
|
||||
<GameModDir Condition="'$(GameModDir)' == ''">$(GameDir)/Mods/$(AssemblyName)</GameModDir>
|
||||
<OutDir Condition="'$(Configuration)' == 'Debug'">$(GameModDir)/</OutDir>
|
||||
<VersionTimestamp>$([System.DateTime]::UtcNow.ToString(`o`))</VersionTimestamp>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Replace the default version if something was set for it -->
|
||||
<PropertyGroup Condition="'$(AssemblyVersion)' == '' OR '$(MajorVersion)' != '' OR '$(MinorVersion)' != ''">
|
||||
<MajorVersion Condition="'$(MajorVersion)' == ''">0</MajorVersion>
|
||||
<MajorVersion Condition="'$(MajorVersion)' == ''">1</MajorVersion>
|
||||
<MinorVersion Condition="'$(MinorVersion)' == ''">1</MinorVersion>
|
||||
<PatchVersion Condition="'$(PatchVersion)' == ''">3</PatchVersion>
|
||||
<PatchVersion Condition="'$(PatchVersion)' == ''">1</PatchVersion>
|
||||
<AssemblyVersion>$(MajorVersion).$(MinorVersion).$(PatchVersion)</AssemblyVersion>
|
||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||
<ProductVersion>$(AssemblyVersion)</ProductVersion>
|
||||
@@ -30,7 +31,7 @@
|
||||
<!-- Publish the mod as a neat zip file -->
|
||||
<Target Name="PrepareForPublishing" AfterTargets="AfterBuild" Condition="'$(Configuration)' == 'Release'">
|
||||
<!-- Replace $(AssemblyVersion) with the actual version -->
|
||||
<Exec Command="powershell -Command "(Get-Content '$(OutputPath)Definition.json') -replace '\$\(AssemblyVersion\)', '$(AssemblyVersion)' | Set-Content '$(OutputPath)Definition.json'"" />
|
||||
<Exec Command="powershell -Command "(Get-Content '$(OutputPath)Definition.json') -replace '\$\(AssemblyVersion\)', '$(AssemblyVersion)_$(VersionTimestamp)' | Set-Content '$(OutputPath)Definition.json'"" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ModsDirectory>$(OutputPath)/Mods</ModsDirectory>
|
||||
@@ -43,15 +44,15 @@
|
||||
|
||||
<!-- Assure the output path exists -->
|
||||
<MakeDir Directories="$(PublishPath)" />
|
||||
|
||||
|
||||
<!-- Remove the old Mods directory, and Zips file if any is lying around -->
|
||||
<ItemGroup>
|
||||
<OldZips Include="$(PublishPath)/$(AssemblyName)_*.zip" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<RemoveDir Directories="$(ModsDirectory)" ContinueOnError="true" />
|
||||
<Delete Files="@(OldZips)" />
|
||||
|
||||
|
||||
<!-- Create the Mods directory again -->
|
||||
<MakeDir Directories="$(ModDirectory)" />
|
||||
|
||||
@@ -64,7 +65,7 @@
|
||||
|
||||
<!-- Zip it up -->
|
||||
<Exec Command="powershell -Command "Compress-Archive -Path '$(ModsDirectory)' -Destination '$(ZipName)'"" />
|
||||
|
||||
|
||||
<!-- Move them to the game directory -->
|
||||
<Copy SourceFiles="@(OutputFiles)" DestinationFolder="$(GameModDir)" />
|
||||
</Target>
|
||||
|
||||
6
Paths.user.example
Normal file
6
Paths.user.example
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Directory that the game (Railroader.exe) is in -->
|
||||
<GameDir></GameDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
195
README.md
195
README.md
@@ -20,7 +20,7 @@ This mod requires Railloader by Zamu.
|
||||
3. Run the game and enjoy all of the tweaks and things!
|
||||
|
||||
## Notes
|
||||
1. This mod currently supports Railroader verison 2024.4.4. This mod may break in future updates. I will do my best to continue to update this mod.
|
||||
1. This mod currently supports Railroader version 2024.4.4. This mod may break in future updates. I will do my best to continue to update this mod.
|
||||
2. It is possible that the developers of Railroader will implement their own fix for this issue. At such time this mod will be deprecated and no longer maintained.
|
||||
3. As the saying goes, use mods at your own risk.
|
||||
|
||||
@@ -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 handbrakes, 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 locmotive 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.*
|
||||
|
||||
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.33122.133
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{452A23A6-81C8-49C6-A7EE-95FD9377F896}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
Assembly.version = Assembly.version
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
Paths.user = Paths.user
|
||||
|
||||
63
TweaksAndThings/Commands/CrewUpdateCommand.cs
Normal file
63
TweaksAndThings/Commands/CrewUpdateCommand.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Game.Notices;
|
||||
using Game.State;
|
||||
using Helpers;
|
||||
using Model.Ops;
|
||||
using Model.Ops.Timetable;
|
||||
using Network;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RMROC451.TweaksAndThings.Patches;
|
||||
using System.Linq;
|
||||
using Track;
|
||||
using UI.Console;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Commands;
|
||||
|
||||
[ConsoleCommand("/cu", "generate a formatted message about a locomotive's status.")]
|
||||
public class EchoCommand : IConsoleCommand
|
||||
{
|
||||
public string Execute(string[] comps)
|
||||
{
|
||||
if (comps.Length < 4)
|
||||
{
|
||||
return "Usage: /cu <car>|. +|- <message>";
|
||||
}
|
||||
|
||||
string query = comps[1];
|
||||
Model.Car car = query == "." ? TrainController.Shared.SelectedLocomotive : TrainController.Shared.CarForString(query);
|
||||
|
||||
if (car.DetermineFuelCar() == null)
|
||||
{
|
||||
return "Car not found.";
|
||||
}
|
||||
string message = string.Join(" ", comps.Skip(3)).Truncate(512);
|
||||
|
||||
switch (comps[2])
|
||||
{
|
||||
case "+":
|
||||
break;
|
||||
case "-":
|
||||
break;
|
||||
default:
|
||||
return "+ to include area or - to leave it out";
|
||||
}
|
||||
|
||||
var gamePoint = car.GetCenterPosition(Graph._graph);
|
||||
EntityReference entityReference = new EntityReference(EntityType.Position, new Vector4( gamePoint.x, gamePoint.y, gamePoint.z, 0));
|
||||
EntityReference loco = new EntityReference(EntityType.Car, car.id);
|
||||
if (comps[2] == "+") message = new Hyperlink(entityReference.URI(), string.Format(message, OpsController.Shared.ClosestArea(car)?.name ?? "???"));
|
||||
|
||||
string hlt = Hyperlink.To(car);
|
||||
hlt = car.TryGetTimetableTrain(out Timetable.Train t) ? hlt.Replace(car.DisplayName, t.DisplayStringLong) : hlt;
|
||||
|
||||
if (StateManager.IsHost)
|
||||
{
|
||||
car.PostNotice(nameof(EchoCommand), $"{message} :{StateManager.Shared._playersManager.LocalPlayer}");
|
||||
|
||||
ExpandedConsole_Add_Patch.SendMs(null, $"{StateManager.Shared._playersManager.LocalPlayer} {hlt} {message}");
|
||||
}
|
||||
if (!StateManager.IsHost) Multiplayer.Broadcast($"{StateManager.Shared._playersManager.LocalPlayer} {hlt}: \"{message}\"");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,23 @@
|
||||
"name": "RMROC451's Tweaks and Things",
|
||||
"version": "$(AssemblyVersion)",
|
||||
"requires": [
|
||||
{
|
||||
"id": "railroader",
|
||||
"notBefore": "2024.6"
|
||||
},
|
||||
{
|
||||
"id": "railloader",
|
||||
"notBefore": "1.8.1"
|
||||
"notBefore": "1.9.6.14"
|
||||
},
|
||||
{
|
||||
"id": "Zamu.StrangeCustoms",
|
||||
"notBefore": "1.10.25017.313"
|
||||
}
|
||||
],
|
||||
"assemblies": [ "RMROC451.TweaksAndThings" ]
|
||||
"assemblies": [ "RMROC451.TweaksAndThings" ],
|
||||
"mixintos": {
|
||||
"container:ne-caboose01": "file(mroc-cabeese.json)",
|
||||
"container:ne-caboose02": "file(mroc-cabeese.json)",
|
||||
"container:ne-caboose03": "file(mroc-cabeese.json)"
|
||||
}
|
||||
}
|
||||
7
TweaksAndThings/Enums/CrewHourLoadMethod.cs
Normal file
7
TweaksAndThings/Enums/CrewHourLoadMethod.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RMROC451.TweaksAndThings.Enums;
|
||||
|
||||
public enum CrewHourLoadMethod
|
||||
{
|
||||
Tracks,
|
||||
Daily
|
||||
}
|
||||
9
TweaksAndThings/Enums/EngineRosterFuelDisplayColumn.cs
Normal file
9
TweaksAndThings/Enums/EngineRosterFuelDisplayColumn.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace RMROC451.TweaksAndThings.Enums;
|
||||
|
||||
public enum EngineRosterFuelDisplayColumn
|
||||
{
|
||||
None,
|
||||
Engine,
|
||||
Crew,
|
||||
Status
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace TweaksAndThings.Enums;
|
||||
namespace RMROC451.TweaksAndThings.Enums;
|
||||
|
||||
public enum MrocHelperType
|
||||
{
|
||||
Handbrake,
|
||||
GladhandAndAnglecock
|
||||
GladhandAndAnglecock,
|
||||
BleedAirSystem,
|
||||
Oil
|
||||
}
|
||||
|
||||
124
TweaksAndThings/Extensions/AutoEngineer_Extensions.cs
Normal file
124
TweaksAndThings/Extensions/AutoEngineer_Extensions.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Game.Messages;
|
||||
using Model.AI;
|
||||
using Network;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Extensions
|
||||
{
|
||||
internal static class AutoEngineer_Extensions
|
||||
{
|
||||
private static float CabooseHalvedFloat(this float input, Model.Car? hasCaboose) =>
|
||||
hasCaboose ? input / 2 : input;
|
||||
|
||||
private static float CabooseAutoOilerLimit(this Model.Car? caboose) =>
|
||||
caboose ? 0.99f : AutoOiler.OilIfBelow;
|
||||
|
||||
public static IEnumerator MrocAutoOilerLoop(this AutoOiler oiler, Serilog.ILogger _log, bool cabooseRequired)
|
||||
{
|
||||
int originIndex = oiler.FindOriginIndex();
|
||||
Model.Car? foundCaboose = oiler._originCar.FindMyCabooseSansLoadRequirement();
|
||||
if (originIndex < 0)
|
||||
{
|
||||
_log.Error("Couldn't find origin car {car}", oiler._originCar);
|
||||
oiler._coroutine = null;
|
||||
yield break;
|
||||
} else if (CabooseRequirementChecker(string.Format("{0} {1}", oiler.GetType().Name, oiler.name), cabooseRequired, foundCaboose, _log))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
oiler._reverse = originIndex > oiler._cars.Count - originIndex;
|
||||
_log.Debug(
|
||||
"AutoOiler {name} starting, rev = {reverse}, caboose required = {req}, caboose halving adjustment = {hasCaboose}, oil limit = {limit}",
|
||||
oiler.name,
|
||||
oiler._reverse,
|
||||
cabooseRequired,
|
||||
foundCaboose,
|
||||
foundCaboose.CabooseAutoOilerLimit()
|
||||
);
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(AutoOiler.StartDelay.CabooseHalvedFloat(foundCaboose));
|
||||
foundCaboose = oiler._originCar.FindMyCabooseSansLoadRequirement();
|
||||
int carIndex = originIndex;
|
||||
float adjustedTimeToWalk = AutoOiler.TimeToWalkCar.CabooseHalvedFloat(foundCaboose);
|
||||
do
|
||||
{
|
||||
if (oiler.TryGetCar(carIndex, out var car))
|
||||
{
|
||||
float num = 0f;
|
||||
float origOil = car.Oiled;
|
||||
if (car.NeedsOiling && car.Oiled < foundCaboose.CabooseAutoOilerLimit())
|
||||
{
|
||||
float num2 = 1f - car.Oiled;
|
||||
car.OffsetOiled(num2);
|
||||
float num3 = num2 * AutoOiler.TimeToFullyOil.CabooseHalvedFloat(foundCaboose);
|
||||
num += num3;
|
||||
oiler._pendingRunDuration += num3;
|
||||
oiler._oiledCount++;
|
||||
_log.Debug("AutoOiler {name}: oiled {car} from {orig} => {new}", oiler.name, car, origOil, car.Oiled);
|
||||
}
|
||||
if (car.HasHotbox && car.Oiled == 1f && cabooseRequired && foundCaboose)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
num += adjustedTimeToWalk;
|
||||
oiler._pendingRunDuration += adjustedTimeToWalk;
|
||||
yield return new WaitForSeconds(num);
|
||||
}
|
||||
carIndex = oiler.NextIndex(carIndex);
|
||||
}
|
||||
while (oiler.InBounds(carIndex));
|
||||
oiler._reverse = !oiler._reverse;
|
||||
oiler.PayWages();
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerator MrocAutoHotboxSpotterLoop(this AutoHotboxSpotter spotter, Serilog.ILogger _log, bool cabooseRequired)
|
||||
{
|
||||
Func<Model.Car?> foundCaboose = () => spotter._locomotive.FindMyCabooseSansLoadRequirement();
|
||||
while (true)
|
||||
{
|
||||
if (!spotter.HasCars)
|
||||
{
|
||||
yield return new WaitForSeconds(1f);
|
||||
continue;
|
||||
}
|
||||
var fc = foundCaboose();
|
||||
_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))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
spotter.CheckForHotbox();
|
||||
while (spotter.HasCars)
|
||||
{
|
||||
int num = Random.Range(60, 300);
|
||||
fc = foundCaboose();
|
||||
if (fc)
|
||||
{
|
||||
var numOrig = num;
|
||||
num = Random.Range(15, 30);
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CabooseRequirementChecker(string name, bool cabooseRequired, Model.Car? foundCaboose, Serilog.ILogger _log)
|
||||
{
|
||||
bool error = cabooseRequired && foundCaboose == null;
|
||||
if (error) {
|
||||
_log.Debug("{name}: Couldn't find required caboose!", name);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
181
TweaksAndThings/Extensions/Car_Extensions.cs
Normal file
181
TweaksAndThings/Extensions/Car_Extensions.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using Game.Messages;
|
||||
using Game.State;
|
||||
using Helpers;
|
||||
using Model;
|
||||
using Model.Definition;
|
||||
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;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Extensions;
|
||||
|
||||
public static class Car_Extensions
|
||||
{
|
||||
private static bool EndGearIssue(this Car car, Car.LogicalEnd end) =>
|
||||
(!car[end].IsCoupled && car[end].IsAnglecockOpen) ||
|
||||
(car[end].IsCoupled && !car[end].IsAirConnectedAndOpen);
|
||||
|
||||
public static bool EndAirSystemIssue(this Car car)
|
||||
{
|
||||
bool AEndAirSystemIssue = car.EndGearIssue(Car.LogicalEnd.A);
|
||||
bool BEndAirSystemIssue = car.EndGearIssue(Car.LogicalEnd.B);
|
||||
bool EndAirSystemIssue = AEndAirSystemIssue || BEndAirSystemIssue;
|
||||
return EndAirSystemIssue;
|
||||
}
|
||||
|
||||
public static bool HandbrakeApplied(this Model.Car car) =>
|
||||
car.air.handbrakeApplied;
|
||||
|
||||
public static bool CarOrEndGearIssue(this Model.Car car) =>
|
||||
car.EndAirSystemIssue() || car.HandbrakeApplied();
|
||||
|
||||
public static bool CarAndEndGearIssue(this Model.Car car) =>
|
||||
car.EndAirSystemIssue() && car.HandbrakeApplied();
|
||||
|
||||
public static Car? DetermineFuelCar(this Car engine, bool returnEngineIfNull = false)
|
||||
{
|
||||
if (engine == null) return null;
|
||||
Car? car;
|
||||
if (engine is SteamLocomotive steamLocomotive && new Func<Car>(steamLocomotive.FuelCar) != null)
|
||||
{
|
||||
car = steamLocomotive.FuelCar();
|
||||
}
|
||||
else
|
||||
{
|
||||
car = engine is DieselLocomotive ? engine : null;
|
||||
if (returnEngineIfNull && car == null) car = engine;
|
||||
}
|
||||
return car;
|
||||
}
|
||||
|
||||
public static bool MotivePower(this Car car) => car is BaseLocomotive || car.Archetype == Model.Definition.CarArchetype.Tender;
|
||||
|
||||
/// <summary>
|
||||
/// For every car in the consist, cost 1 minute of AI Engineer time.
|
||||
/// </summary>
|
||||
/// <param name="consist"></param>
|
||||
public static int CalculateCostForAutoEngineerEndGearSetting(this IEnumerable<Car> consist) =>
|
||||
consist.Count() * 60;
|
||||
|
||||
public static bool IsCaboose(this Car car) => car.Archetype == Model.Definition.CarArchetype.Caboose;
|
||||
|
||||
public static Car? CarCaboose(this Car car) => car.IsCaboose() ? car : null;
|
||||
|
||||
public static bool IsCabooseAndStoppedForLoadRefresh(this Car car, bool isFull) => car.IsCaboose() && car.IsStopped(30f) && !isFull;
|
||||
|
||||
public static Car? CabooseInConsist(this IEnumerable<Car> input) => input.FirstOrDefault(IsCaboose);
|
||||
|
||||
public static bool ConsistNoFreight(this IEnumerable<Car> input) =>
|
||||
input.Where(c => !c.MotivePower()).Any() &&
|
||||
input
|
||||
.Where(c => !c.MotivePower())
|
||||
.All(c => !c.MrocIsFreight());
|
||||
|
||||
public static bool ConsistFreight(this IEnumerable<Car> input) =>
|
||||
input.Where(c => !c.MotivePower()).Any() &&
|
||||
input
|
||||
.Where(c => !c.MotivePower())
|
||||
.All(c => c.MrocIsFreight());
|
||||
|
||||
public static bool MrocIsFreight(this Car c) =>
|
||||
c.Archetype.IsFreight() || c.Archetype switch
|
||||
{
|
||||
CarArchetype.LocomotiveDiesel => false,
|
||||
CarArchetype.LocomotiveSteam => false,
|
||||
CarArchetype.Coach => false,
|
||||
CarArchetype.Baggage => false,
|
||||
_ => true
|
||||
};
|
||||
|
||||
|
||||
public static bool SelectedEngineExpress(this TrainController input) =>
|
||||
input.SelectedLocomotive.TryGetTimetableTrain(out Timetable.Train t) &&
|
||||
t.TrainClass == Timetable.TrainClass.First;
|
||||
|
||||
public static Car? FindMyCabooseSansLoadRequirement(this Car car) =>
|
||||
FindMyCaboose(car, 0f, decrement: false, requireLoad: false);
|
||||
|
||||
public static Car? FindMyCabooseWithLoadRequirement(this Car car, float timeNeeded, bool decrement) =>
|
||||
FindMyCaboose(car, timeNeeded, decrement, requireLoad: true);
|
||||
|
||||
private static Car? FindMyCaboose(this Car car, float timeNeeded, bool decrement = false, bool requireLoad = true) =>
|
||||
(
|
||||
car.CarCaboose() ?? car.CarsNearCurrentCar(timeNeeded, decrement).FindNearestCabooseFromNearbyCars()
|
||||
)?.CabooseWithSufficientCrewHours(timeNeeded: timeNeeded, requireLoad:requireLoad, decrement: decrement);
|
||||
|
||||
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 && loadInfo.Value.LoadId == OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.id)
|
||||
{
|
||||
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
|
||||
output = valueOrDefault.Quantity >= timeNeeded ? car : null;
|
||||
if (decrement && output != null) output.SetLoadInfo(i, valueOrDefault with { Quantity = valueOrDefault.Quantity - timeNeeded });
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private static Car? FindNearestCabooseFromNearbyCars(this IEnumerable<(Car car, bool crewCar, float distance)> source) =>
|
||||
source
|
||||
?.OrderBy(c => c.crewCar ? 0 : 1)
|
||||
?.ThenBy(c => c.distance)
|
||||
?.Select(c => c.car)
|
||||
?.FirstOrDefault();
|
||||
|
||||
private static IEnumerable<(Car car, bool crewCar, float distance)> CarsNearCurrentCar(this Car car, float timeNeeded, bool decrement)
|
||||
{
|
||||
Area carArea = OpsController.Shared.ClosestArea(car);
|
||||
|
||||
var cabeese = OpsController.Shared
|
||||
.CarsInArea(carArea)
|
||||
.Select(c => TrainController.Shared.CarForId(c.Id))
|
||||
.Union(car.EnumerateCoupled())
|
||||
.Where(c => c.IsCaboose());
|
||||
|
||||
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();
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public static float Distance(this Car car1, Car car2)
|
||||
{
|
||||
Vector3 position = car1.GetMotionSnapshot().Position;
|
||||
Vector3 center = WorldTransformer.WorldToGame(position);
|
||||
Vector3 a = WorldTransformer.WorldToGame(car2.GetMotionSnapshot().Position);
|
||||
|
||||
return Vector3.Distance(a, center);
|
||||
}
|
||||
|
||||
public static bool IsCrewCar(this Car car) =>
|
||||
!string.IsNullOrEmpty(TrainController.Shared.SelectedLocomotive?.trainCrewId) &&
|
||||
car.trainCrewId == TrainController.Shared.SelectedLocomotive?.trainCrewId;
|
||||
|
||||
|
||||
//public static void AdjustHotboxValue(this Car car) => car.ControlProperties[PropertyChange.Control.Hotbox] = null;
|
||||
public static void AdjustHotboxValue(this Car car, float hotboxValue = 0f) =>
|
||||
StateManager.ApplyLocal(
|
||||
new PropertyChange(
|
||||
car.id, PropertyChange.KeyForControl(PropertyChange.Control.Hotbox),
|
||||
new FloatPropertyValue(hotboxValue)
|
||||
)
|
||||
);
|
||||
}
|
||||
28
TweaksAndThings/Extensions/Load_Extensions.cs
Normal file
28
TweaksAndThings/Extensions/Load_Extensions.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Core;
|
||||
using System;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Extensions;
|
||||
|
||||
public static class Load_Extensions
|
||||
{
|
||||
//https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings?redirectedfrom=MSDN#the--section-separator
|
||||
//https://dotnetfiddle.net/iHVevM
|
||||
public static string FormatCrewHours(this float quantity, string description)
|
||||
{
|
||||
var ts = TimeSpan.FromHours(quantity);
|
||||
float minutes = ts.Minutes - (ts.Minutes % 15);
|
||||
|
||||
string output = string.Format("{0:;;No}{1:##}{2:\\.##;\\.##;.} {3} {4}",
|
||||
ts.Hours + minutes,
|
||||
ts.Hours, (minutes / 60.0f) * 100,
|
||||
description,
|
||||
"Hour".Pluralize(quantity == 1 ? 1 : 0)
|
||||
).Trim();
|
||||
|
||||
if (ts.Hours < 1)
|
||||
{
|
||||
output = string.Format("{0} {1} Minutes", ts.Minutes, description).Trim();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
36
TweaksAndThings/Extensions/TextSprite_Extensions.cs
Normal file
36
TweaksAndThings/Extensions/TextSprite_Extensions.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using RMROC451.TweaksAndThings.Patches;
|
||||
using Serilog;
|
||||
using UnityEngine;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Extensions;
|
||||
|
||||
public static class TextSprite_Extensions
|
||||
{
|
||||
public static string TriColorPiePercent(this float quantity, float capacity, string name = "")
|
||||
{
|
||||
int num;
|
||||
if (capacity <= 0f)
|
||||
{
|
||||
num = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float num2 = Mathf.Clamp01(quantity / capacity);
|
||||
int num3 = ((!(num2 < 0.01f)) ? ((!(num2 > 0.99f)) ? (Mathf.FloorToInt(num2 * 15f) + 1) : 16) : 0);
|
||||
num = num3;
|
||||
}
|
||||
string color = "#219106"; //Green
|
||||
if (num > 5 && num <= 10)
|
||||
{
|
||||
color = "#CE8326"; //orange
|
||||
} else if (num <= 5)
|
||||
{
|
||||
color = "#D53427"; //Red
|
||||
}
|
||||
|
||||
string sprite = string.IsNullOrEmpty(name) ? $"Pie{num:D2}" : name;
|
||||
|
||||
return $"<sprite tint=1 color={color} name={sprite}>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using HarmonyLib;
|
||||
using Railloader;
|
||||
using UI.Builder;
|
||||
using UI.EngineControls;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerControlSetBase))]
|
||||
[HarmonyPatch(nameof(AutoEngineerControlSetBase.UpdateStatusLabel))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerControlSetBase_UpdateStatusLabel_Patch
|
||||
{
|
||||
static void Postfix(AutoEngineerControlSetBase __instance)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled() || !AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(__instance.Locomotive)) return;
|
||||
|
||||
string orig = __instance.statusLabel.text;
|
||||
__instance.statusLabel.text = $"{orig}; <b>Safety</b>";
|
||||
__instance.statusLabel.rectTransform.Tooltip("Status", $"<b>Safety First, Speed Limited</b>\n\n{orig}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using HarmonyLib;
|
||||
using Helpers;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System.Collections;
|
||||
using Track;
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
using static UI.AutoEngineerDestinationPicker;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerDestinationPicker))]
|
||||
[HarmonyPatch(nameof(AutoEngineerDestinationPicker.Loop))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerDestinationPicker_Loop_Patch
|
||||
{
|
||||
static bool Prefix(AutoEngineerDestinationPicker __instance, ref IEnumerator __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
__result = Loop(__instance);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerator Loop(AutoEngineerDestinationPicker __instance)
|
||||
{
|
||||
Hit valueOrDefault;
|
||||
Location location;
|
||||
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(1/60);
|
||||
while (true)
|
||||
{
|
||||
Location? currentOrdersGotoLocation = __instance.GetCurrentOrdersGotoLocation();
|
||||
Hit? hit = __instance.HitLocation();
|
||||
if (hit.HasValue)
|
||||
{
|
||||
valueOrDefault = hit.GetValueOrDefault();
|
||||
location = valueOrDefault.Location;
|
||||
Graph.PositionRotation positionRotation = __instance._graph.GetPositionRotation(location);
|
||||
__instance.destinationMarker.position = WorldTransformer.GameToWorld(positionRotation.Position);
|
||||
__instance.destinationMarker.rotation = positionRotation.Rotation;
|
||||
__instance.destinationMarker.gameObject.SetActive(value: true);
|
||||
if (!currentOrdersGotoLocation.Equals(location) && __instance.MouseClicked)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.destinationMarker.gameObject.SetActive(value: false);
|
||||
}
|
||||
yield return wait;
|
||||
}
|
||||
Log.Debug("DestinationPicker Hit: {hit} {car} {end}", valueOrDefault.Location, valueOrDefault.CarInfo?.car, valueOrDefault.CarInfo?.end);
|
||||
__instance._ordersHelper.SetWaypoint(location, valueOrDefault.CarInfo?.car.id);
|
||||
__instance.StopLoop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using Game;
|
||||
using Game.Messages;
|
||||
using Game.Notices;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.AI;
|
||||
using Model.Definition;
|
||||
using Network;
|
||||
using Network.Messages;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Track;
|
||||
using UI.EngineControls;
|
||||
using UI.EngineRoster;
|
||||
using UnityEngine;
|
||||
using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics;
|
||||
using static UnityEngine.InputSystem.InputRemoting;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoEngineerPlanner))]
|
||||
[HarmonyPatch(nameof(AutoEngineerPlanner.HandleCommand))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoEngineerPlanner_HandleCommand_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerPlanner_HandleCommand_Patch>();
|
||||
private static int governedSpeed = 20;
|
||||
|
||||
static bool Prefix(AutoEngineerPlanner __instance, ref AutoEngineerCommand command, ref IPlayer sender)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
LocoNoticeWPSet(__instance, command, sender);
|
||||
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.SafetyFirst() || (sender.IsRemote && !tweaksAndThings.SafetyFirstClientEnforce()) || command.MaxSpeedMph <= governedSpeed) return true;
|
||||
BaseLocomotive loco = __instance._locomotive;
|
||||
|
||||
if (SafetyFirstGoverningApplies(loco))
|
||||
{
|
||||
int orig = command.MaxSpeedMph;
|
||||
int limitedSpeed = Math.Min(command.MaxSpeedMph, governedSpeed);
|
||||
command.MaxSpeedMph = command.Mode switch
|
||||
{
|
||||
AutoEngineerMode.Road => limitedSpeed,
|
||||
AutoEngineerMode.Waypoint => limitedSpeed,
|
||||
_ => command.MaxSpeedMph,
|
||||
};
|
||||
|
||||
string message = $"{Enum.GetName(typeof(AutoEngineerMode), command.Mode)}[{loco.DisplayName}] governed{{0}}due to Safety First rules.";
|
||||
if (orig != command.MaxSpeedMph)
|
||||
{
|
||||
message = string.Format(message, $" from {orig} to {command.MaxSpeedMph} MPH ");
|
||||
}
|
||||
else
|
||||
{
|
||||
message = string.Format(message, " ");
|
||||
}
|
||||
_log.Debug(message);
|
||||
Multiplayer.SendError(sender, message, AlertLevel.Info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void LocoNoticeWPSet(AutoEngineerPlanner __instance, AutoEngineerCommand command, IPlayer sender)
|
||||
{
|
||||
OrderWaypoint? wp =
|
||||
string.IsNullOrEmpty(command.WaypointLocationString) ?
|
||||
null :
|
||||
new OrderWaypoint?(new OrderWaypoint(command.WaypointLocationString, command.WaypointCoupleToCarId));
|
||||
|
||||
if (
|
||||
wp.HasValue &&
|
||||
!string.IsNullOrEmpty(wp.Value.LocationString) &&
|
||||
!__instance._orders.Waypoint.Equals(wp)
|
||||
)
|
||||
{
|
||||
_log.Debug($"start setWP");
|
||||
Car selectedLoco = __instance._locomotive;
|
||||
_log.Debug($"{selectedLoco?.DisplayName ?? ""} set WP");
|
||||
if (LocationPositionFromString(wp.Value, out Vector3 gamePoint))
|
||||
{
|
||||
EntityReference entityReference = new EntityReference(EntityType.Position, new Vector4(gamePoint.x, gamePoint.y, gamePoint.z, 0));
|
||||
selectedLoco.PostNotice("ai-wpt-rmroc451", new Hyperlink(entityReference.URI(), $"WP SET [{sender.Name}]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool LocationPositionFromString(OrderWaypoint waypoint, out Vector3 position)
|
||||
{
|
||||
Location location;
|
||||
position = default;
|
||||
try
|
||||
{
|
||||
location = Graph.Shared.ResolveLocationString(waypoint.LocationString);
|
||||
position = location.GetPosition();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Couldn't get location from waypoint: {locStr}", waypoint.LocationString);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool SafetyFirstGoverningApplies(BaseLocomotive loco)
|
||||
{
|
||||
var _persistence = new AutoEngineerPersistence(loco.KeyValueObject);
|
||||
var OrdersHelper = new AutoEngineerOrdersHelper(loco, _persistence);
|
||||
|
||||
if (loco.EnumerateCoupled().All(c => c.IsCaboose() || c.MotivePower())) return false;
|
||||
|
||||
bool cabooseReq = SingletonPluginBase<TweaksAndThingsPlugin>.Shared.RequireConsistCabooseForOilerAndHotboxSpotter();
|
||||
string logMessage = $"\n{nameof(SafetyFirstGoverningApplies)}:{Enum.GetName(typeof(AutoEngineerMode), OrdersHelper.Mode)}[{loco.DisplayName}] ";
|
||||
Func<bool> firstClass = () =>
|
||||
{
|
||||
var output = TrainController.Shared.SelectedEngineExpress();
|
||||
logMessage += $"\nfirst class {output}";
|
||||
return output;
|
||||
};
|
||||
|
||||
Func<bool> FreightConsist = () =>
|
||||
{
|
||||
bool output = !loco.EnumerateCoupled().ConsistNoFreight();
|
||||
logMessage += $"\nFreightConsist? {output}";
|
||||
logMessage += " " + string.Join(" / ", loco.EnumerateCoupled().Where(c => !c.MotivePower()).Select(c => $"{c.id} {Enum.GetName(typeof(CarArchetype), c.Archetype)}"));
|
||||
return output;
|
||||
};
|
||||
|
||||
Func<bool> noCaboose = () =>
|
||||
{
|
||||
bool output = loco.FindMyCabooseSansLoadRequirement() == null;
|
||||
logMessage += $"\ncaboose? {!output}";
|
||||
return output;
|
||||
};
|
||||
|
||||
logMessage += $"\nCaboose Required {cabooseReq}";
|
||||
|
||||
bool output =
|
||||
cabooseReq &&
|
||||
!firstClass() &&
|
||||
FreightConsist() &&
|
||||
noCaboose();
|
||||
|
||||
logMessage += $"\nGovern AE? {output}";
|
||||
if (_log.IsEnabled(Serilog.Events.LogEventLevel.Verbose))
|
||||
logMessage += $"\n{Environment.StackTrace}";
|
||||
|
||||
_log.Debug(logMessage);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
using Game.Messages;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.AI;
|
||||
using Model.Ops;
|
||||
using Model.Ops.Timetable;
|
||||
using Network;
|
||||
using Network.Messages;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Track;
|
||||
using Track.Search;
|
||||
using UI;
|
||||
using UI.EngineControls;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using static Track.Search.RouteSearch;
|
||||
using Location = Track.Location;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
|
||||
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<LocomotiveControlsUIAdapter_UpdateCarText_Postfix>();
|
||||
private static int lastSeenIntegrationSetCount = default;
|
||||
private static string? lastLocoSeenCarId = default;
|
||||
private static Coroutine? watchyWatchy = null;
|
||||
private static HashSet<OpsCarPosition?> locoConsistDestinations = [];
|
||||
private static Game.GameDateTime? timetableSaveTime = null;
|
||||
static string getDictKey(Car car) => car.DisplayName;
|
||||
|
||||
static void Postfix(LocomotiveControlsUIAdapter __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (lastLocoSeenCarId != null && lastLocoSeenCarId.Equals(TrainController.Shared?.SelectedLocomotive.id) && watchyWatchy != null) return;
|
||||
if (watchyWatchy != null) ((MonoBehaviour)__instance).StopCoroutine(watchyWatchy);
|
||||
watchyWatchy = null;
|
||||
|
||||
if (__instance._persistence.Orders.Mode == AutoEngineerMode.Waypoint) watchyWatchy = ((MonoBehaviour)__instance).StartCoroutine(UpdateCogCoroutine(__instance));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you.");
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerator UpdateCogCoroutine(LocomotiveControlsUIAdapter __instance)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(3f);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (__instance._persistence.Orders.Mode != AutoEngineerMode.Waypoint || ((AutoEngineerWaypointControls)__instance.aiWaypointControls).Locomotive == null) yield return wait;
|
||||
|
||||
PrepLocoUsage((AutoEngineerWaypointControls)__instance.aiWaypointControls, out BaseLocomotive selectedLoco, out int numberOfCars);
|
||||
HashSet<OpsCarPosition?> destinations = [];
|
||||
if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(__instance, selectedLoco, out destinations)) yield return wait;
|
||||
timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified;
|
||||
lastSeenIntegrationSetCount = selectedLoco.set.NumberOfCars;
|
||||
|
||||
IterateCarsDetectDestinations(
|
||||
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
|
||||
((AutoEngineerWaypointControls)__instance.aiWaypointControls).ConfigureOptionsDropdown(),
|
||||
selectedLoco,
|
||||
numberOfCars,
|
||||
destinations: destinations,
|
||||
out List<DropdownMenu.RowData> rowDatas,
|
||||
out Action<int> func,
|
||||
out int origCount,
|
||||
out int maxRowOrig,
|
||||
out AutoEngineerOrdersHelper aeoh
|
||||
);
|
||||
|
||||
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos =
|
||||
BuildJumpToOptions((AutoEngineerWaypointControls)__instance.aiWaypointControls, selectedLoco);
|
||||
|
||||
var config = WireUpJumpTosToSettingMenu(
|
||||
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
|
||||
selectedLoco,
|
||||
rowDatas,
|
||||
func,
|
||||
origCount,
|
||||
maxRowOrig,
|
||||
aeoh,
|
||||
ref jumpTos
|
||||
);
|
||||
|
||||
List<DropdownMenu.RowData> list = config.Rows;
|
||||
Action<int> action = config.OnRowSelected;
|
||||
|
||||
__instance.optionsDropdown.Configure(list, action);
|
||||
((Selectable)__instance.optionsDropdown).interactable = list.Count > 0;
|
||||
yield return wait;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldRecalc(LocomotiveControlsUIAdapter __instance, BaseLocomotive selectedLoco, out HashSet<OpsCarPosition?> destinations)
|
||||
{
|
||||
bool output = false;
|
||||
string locoKey = getDictKey(selectedLoco);
|
||||
List<Car> consist = new List<Car>();
|
||||
consist = selectedLoco.EnumerateCoupled().ToList();
|
||||
destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet();
|
||||
|
||||
//_log.Information($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
|
||||
//_log.Information($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
|
||||
|
||||
output |= !locoConsistDestinations.SetEquals(destinations);
|
||||
//_log.Information($"{locoKey} 1-> {output}");
|
||||
if (output) lastSeenIntegrationSetCount = default;
|
||||
output |= lastSeenIntegrationSetCount != selectedLoco.set.NumberOfCars;
|
||||
//_log.Information($"{locoKey} 2-> {output}");
|
||||
//output |= __instance.optionsDropdown.scrollRect.content.childCount != (destinations.Count + timetableDestinations.Count + 1); //+1 for the default "JumpTo" entry)
|
||||
//_log.Information($"{locoKey} 2.5-> {output} {__instance.optionsDropdown.scrollRect.content.childCount} {(destinations.Count)} {timetableDestinations.Count}");
|
||||
output |= selectedLoco.TryGetTimetableTrain(out _) && TimetableController.Shared.CurrentDocument.Modified != timetableSaveTime;
|
||||
//_log.Information($"{locoKey} 3-> {output}");
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static OptionsDropdownConfiguration WireUpJumpTosToSettingMenu(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco, List<DropdownMenu.RowData> rowDatas, Action<int> func, int origCount, int maxRowOrig, AutoEngineerOrdersHelper aeoh, ref List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos)
|
||||
{
|
||||
OptionsDropdownConfiguration __result;
|
||||
jumpTos = jumpTos?.OrderBy(c => c.sortDistance)?.ToList() ?? default;
|
||||
var localJumpTos = jumpTos.ToList();
|
||||
var safetyFirst = AutoEngineerPlanner_HandleCommand_Patch.SafetyFirstGoverningApplies(selectedLoco) && jumpTos.Any();
|
||||
|
||||
rowDatas.AddRange(jumpTos.Select(j =>
|
||||
new DropdownMenu.RowData(
|
||||
$"{j.destination} <b>({(j.distance.HasValue ? Units.DistanceText(j.distance.Value) : "N/A")})</b>",
|
||||
!safetyFirst ? null : "<i>Disabled; Safety First!</i>"
|
||||
)
|
||||
));
|
||||
|
||||
__result = new OptionsDropdownConfiguration(
|
||||
rowDatas
|
||||
, delegate (int row)
|
||||
{
|
||||
_log.Debug($"{__instance.Locomotive.DisplayName} row {row}/{localJumpTos.Count}/{rowDatas.Count}");
|
||||
if (row <= maxRowOrig)
|
||||
{
|
||||
func(row);
|
||||
}
|
||||
if (row > maxRowOrig && localJumpTos[row - origCount].location.HasValue)
|
||||
{
|
||||
if (safetyFirst)
|
||||
{
|
||||
Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, "Safety First, find yourself a caboose!", AlertLevel.Error);
|
||||
return;
|
||||
}
|
||||
float trainMomentum = 0f;
|
||||
Location end = localJumpTos[row - origCount].location.Value;
|
||||
Location start = RouteStartLocation(__instance, selectedLoco);
|
||||
HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
|
||||
List<RouteSearch.Step> list = new List<RouteSearch.Step>();
|
||||
|
||||
var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
|
||||
if (!Graph.Shared.FindRoute(start, end, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum))
|
||||
{
|
||||
RouteSearch.Metrics metrics2;
|
||||
bool flag = Graph.Shared.FindRoute(start, end, autoEngineer, null, out metrics2);
|
||||
Multiplayer.SendError(StateManager.Shared.PlayersManager.LocalPlayer, flag ? (getDictKey(selectedLoco) + " Train too long to navigate to waypoint.") : (getDictKey(selectedLoco) + " Unable to find a path to waypoint."), AlertLevel.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
var mw = (location: end, carId: string.Empty);
|
||||
aeoh.SetWaypoint(mw.location, mw.carId);
|
||||
aeoh.SetOrdersValue(maybeWaypoint: mw);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return __result;
|
||||
}
|
||||
|
||||
private static List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> BuildJumpToOptions(AutoEngineerWaypointControls __instance, BaseLocomotive selectedLoco)
|
||||
{
|
||||
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos = new();
|
||||
foreach (OpsCarPosition ocp in locoConsistDestinations)
|
||||
{
|
||||
string destName = ocp.DisplayName;
|
||||
string destId = ocp.Identifier;
|
||||
float? distance = null;
|
||||
float sortdistance = 0f;
|
||||
if (
|
||||
Graph.Shared.TryGetLocationFromPoint(
|
||||
ocp.Spans?.FirstOrDefault().GetSegments().FirstOrDefault(),
|
||||
ocp.Spans?.FirstOrDefault()?.GetCenterPoint() ?? default,
|
||||
200f,
|
||||
out Location destLoc
|
||||
)
|
||||
)
|
||||
{
|
||||
float trainMomentum = 0f;
|
||||
Location start = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.RouteStartLocation(out trainMomentum) : RouteStartLocation(__instance, selectedLoco);
|
||||
HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
|
||||
List<RouteSearch.Step> list = new List<RouteSearch.Step>();
|
||||
var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
|
||||
distance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum)
|
||||
? metrics.Distance
|
||||
: null;
|
||||
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
|
||||
? metrics.Distance
|
||||
: float.MaxValue;
|
||||
}
|
||||
;
|
||||
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
|
||||
jumpTos.Add((
|
||||
destinationId: destId,
|
||||
destination: $"WP> {destName}"
|
||||
, distance: distance
|
||||
, sortdistance: sortdistance
|
||||
, location: (Location?)destLoc
|
||||
));
|
||||
}
|
||||
|
||||
if (selectedLoco.TryGetTimetableTrain(out Timetable.Train t))
|
||||
{
|
||||
//_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong}");
|
||||
foreach (var e in t.Entries)
|
||||
{
|
||||
var stp = TimetableController.Shared.GetAllStations().FirstOrDefault(ps => ps.code == e.Station);
|
||||
//_log.Information($"{getDictKey(selectedLoco)} -> {t.DisplayStringLong} -> {e.Station} {stp}");
|
||||
if (stp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string destName = t.TrainType == Timetable.TrainType.Passenger ? stp.passengerStop.DisplayName : stp.DisplayName;
|
||||
string destId = t.TrainType == Timetable.TrainType.Passenger ? stp.passengerStop.identifier : stp.code;
|
||||
float? distance = null;
|
||||
float sortdistance = 0f;
|
||||
if (
|
||||
Graph.Shared.TryGetLocationFromPoint(
|
||||
stp.passengerStop.TrackSpans?.FirstOrDefault().GetSegments().FirstOrDefault(),
|
||||
stp.passengerStop.TrackSpans?.FirstOrDefault()?.GetCenterPoint() ?? default,
|
||||
200f,
|
||||
out Location destLoc
|
||||
)
|
||||
)
|
||||
{
|
||||
float trainMomentum = 0f;
|
||||
Location start = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.RouteStartLocation(out trainMomentum) : RouteStartLocation(__instance, selectedLoco);
|
||||
HeuristicCosts autoEngineer = HeuristicCosts.AutoEngineer;
|
||||
List<RouteSearch.Step> list = new List<RouteSearch.Step>();
|
||||
var totLen = StateManager.IsHost ? selectedLoco.AutoEngineerPlanner.CalculateTotalLength() : CalculateTotalLength(selectedLoco);
|
||||
distance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out var metrics, checkForCars: false, totLen, trainMomentum)
|
||||
? metrics.Distance
|
||||
: null;
|
||||
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
|
||||
? metrics.Distance
|
||||
: float.MaxValue;
|
||||
}
|
||||
;
|
||||
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
|
||||
jumpTos.Add((
|
||||
destinationId: destId,
|
||||
destination: $"{t.DisplayStringLong} > {destName}"
|
||||
, distance: distance
|
||||
, sortdistance: sortdistance
|
||||
, location: (Location?)destLoc
|
||||
));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jumpTos;
|
||||
}
|
||||
|
||||
private static void IterateCarsDetectDestinations(
|
||||
AutoEngineerWaypointControls __instance,
|
||||
OptionsDropdownConfiguration __result,
|
||||
BaseLocomotive selectedLoco,
|
||||
int numberOfCars,
|
||||
HashSet<OpsCarPosition?> destinations,
|
||||
out List<DropdownMenu.RowData> rowDatas,
|
||||
out Action<int> func,
|
||||
out int origCount,
|
||||
out int maxRowOrig,
|
||||
out AutoEngineerOrdersHelper aeoh
|
||||
)
|
||||
{
|
||||
rowDatas = __result.Rows.ToList();
|
||||
func = __result.OnRowSelected;
|
||||
origCount = rowDatas.Count;
|
||||
maxRowOrig = origCount - 1;
|
||||
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
|
||||
string locoKey = getDictKey(selectedLoco);
|
||||
|
||||
var dropped =
|
||||
locoConsistDestinations.Except(destinations).ToHashSet(); //are no longer here
|
||||
|
||||
_log.Debug($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
|
||||
_log.Debug($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
|
||||
_log.Debug($"{locoKey} --> [{dropped.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => k.Value.DisplayName))}");
|
||||
locoConsistDestinations = destinations.ToList().ToHashSet(); //remove ones that are no longer here
|
||||
}
|
||||
|
||||
private static void PrepLocoUsage(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars)
|
||||
{
|
||||
//wire up that loco
|
||||
selectedLoco = __instance.Locomotive;
|
||||
numberOfCars = selectedLoco?.set.NumberOfCars ?? -1;
|
||||
_log.Debug($"{selectedLoco?.id} --> HI BOB[{numberOfCars}]");
|
||||
}
|
||||
|
||||
private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
|
||||
{
|
||||
OpsCarPosition? destination = null;
|
||||
if (c.TryGetOverrideDestination(OverrideDestination.Repair, OpsController.Shared, out (OpsCarPosition, string)? result))
|
||||
destination = result.Value.Item1;
|
||||
|
||||
if (!destination.HasValue && c.Waybill.HasValue && !c.Waybill.Value.Completed)
|
||||
destination = c.Waybill.Value.Destination;
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
|
||||
{
|
||||
bool num = _locomotive.IsStopped();
|
||||
bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
|
||||
|
||||
bool flag2 = flag ?? __instance.OrdersHelper.Orders.Forward;
|
||||
if (_locomotive.EndToLogical((!flag2) ? Car.End.R : Car.End.F) == Car.LogicalEnd.A)
|
||||
{
|
||||
return _locomotive.EnumerateCoupled().First().WheelBoundsA;
|
||||
}
|
||||
|
||||
return _locomotive.EnumerateCoupled(Car.LogicalEnd.B).First().WheelBoundsB.Flipped();
|
||||
}
|
||||
|
||||
private static float CalculateTotalLength(BaseLocomotive selectedLoco)
|
||||
{
|
||||
List<Car> coupledCarsCached = selectedLoco.EnumerateCoupled().ToList();
|
||||
float num = 0f;
|
||||
foreach (Car item in coupledCarsCached)
|
||||
{
|
||||
num += item.carLength;
|
||||
}
|
||||
|
||||
return num + 1.04f * (float)(coupledCarsCached.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
|
||||
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateOptionsDropdown))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class LocomotiveControlsUIAdapter_UpdateOptionsDropdown_Prefix
|
||||
{
|
||||
static bool Prefix(LocomotiveControlsUIAdapter __instance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using HarmonyLib;
|
||||
using Model.AI;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using Serilog;
|
||||
using System.Collections;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoHotboxSpotter))]
|
||||
[HarmonyPatch(nameof(AutoHotboxSpotter.SpotterLoop))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoHotboxSpotter_SpotterLoop_Patch
|
||||
{
|
||||
private static ILogger _log => Log.ForContext<AutoHotboxSpotter_SpotterLoop_Patch>();
|
||||
|
||||
public static bool Prefix(AutoHotboxSpotter __instance, ref IEnumerator __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment();
|
||||
bool cabooseRequired = tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter();
|
||||
|
||||
if (buttonsHaveCost) __result = __instance.MrocAutoHotboxSpotterLoop(_log, cabooseRequired);
|
||||
return !buttonsHaveCost; //only hit this if !buttonsHaveCost, since Loop is a coroutine
|
||||
}
|
||||
}
|
||||
27
TweaksAndThings/Patches/AutoOiler_Loop_Patch.cs
Normal file
27
TweaksAndThings/Patches/AutoOiler_Loop_Patch.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using HarmonyLib;
|
||||
using Model.AI;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using Serilog;
|
||||
using System.Collections;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(AutoOiler))]
|
||||
[HarmonyPatch(nameof(AutoOiler.Loop))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class AutoOiler_Loop_Patch
|
||||
{
|
||||
private static ILogger _log => Log.ForContext<AutoOiler_Loop_Patch>();
|
||||
|
||||
public static bool Prefix(AutoOiler __instance, ref IEnumerator __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment();
|
||||
bool cabooseRequired = tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter() && !__instance._cars.ConsistNoFreight();
|
||||
|
||||
if (buttonsHaveCost) __result = __instance.MrocAutoOilerLoop(_log, cabooseRequired);
|
||||
return !buttonsHaveCost; //only hit this if !buttonsHaveCost, since Loop is a coroutine
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.Definition;
|
||||
using Model.Physics;
|
||||
using Railloader;
|
||||
using System;
|
||||
using static Model.Car;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(BaseLocomotive))]
|
||||
[HarmonyPatch(nameof(BaseLocomotive.FindSourceLocomotive), typeof(LogicalEnd))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class BaseLocomotive_FindSourceLocomotive_Patch
|
||||
{
|
||||
private static bool Prefix(BaseLocomotive __instance, LogicalEnd searchDirection, ref BaseLocomotive __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
__result = FindSourceLocomotive(__instance, searchDirection);
|
||||
|
||||
return false;
|
||||
}
|
||||
public static BaseLocomotive FindSourceLocomotive(BaseLocomotive __instance, LogicalEnd searchDirection)
|
||||
{
|
||||
bool stop = false;
|
||||
int? num = __instance.set.IndexOfCar(__instance);
|
||||
if (!num.HasValue)
|
||||
{
|
||||
throw new Exception("Couldn't find car in set");
|
||||
}
|
||||
int carIndex = num.Value;
|
||||
LogicalEnd fromEnd = ((searchDirection == LogicalEnd.A) ? LogicalEnd.B : LogicalEnd.A);
|
||||
Car car;
|
||||
while (!stop && (car = __instance.set.NextCarConnected(ref carIndex, fromEnd, IntegrationSet.EnumerationCondition.AirAndCoupled, out stop)) != null)
|
||||
{
|
||||
if (!(car == __instance) && car is BaseLocomotive baseLocomotive && car.Archetype != CarArchetype.Tender && !baseLocomotive.locomotiveControl.air.IsCutOut)
|
||||
{
|
||||
return baseLocomotive;
|
||||
}
|
||||
}
|
||||
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);
|
||||
// }
|
||||
//}
|
||||
52
TweaksAndThings/Patches/BindingsWindow_Patches.cs
Normal file
52
TweaksAndThings/Patches/BindingsWindow_Patches.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using HarmonyLib;
|
||||
using Railloader;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UI.Builder;
|
||||
using UI.PreferencesWindow;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(BindingsWindow))]
|
||||
[HarmonyPatch(nameof(BindingsWindow.Build), typeof(UIPanelBuilder))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class BindingsWindow_Build_Patch
|
||||
{
|
||||
public static bool Prefix(BindingsWindow __instance, UIPanelBuilder builder)
|
||||
{
|
||||
return true;
|
||||
//TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
//if (!tweaksAndThings.IsEnabled) return true;
|
||||
|
||||
//(string title, InputAction[] actions)[] rebindableActions = BindingsWindow.RebindableActions;
|
||||
|
||||
|
||||
|
||||
//HashSet<InputAction> conflicts = BindingsWindow.FindConflicts(rebindableActions.SelectMany(((string title, InputAction[] actions) t) => t.actions));
|
||||
//__instance._conflicts = conflicts;
|
||||
//__instance._builder = builder;
|
||||
//builder.AddTabbedPanels(__instance._selectedTabState, delegate (UITabbedPanelBuilder uITabbedPanelBuilder)
|
||||
//{
|
||||
// (string, InputAction[])[] array = rebindableActions;
|
||||
// for (int i = 0; i < array.Length; i++)
|
||||
// {
|
||||
// var (text, actions) = array[i];
|
||||
// uITabbedPanelBuilder.AddTab(text, text, delegate (UIPanelBuilder uIPanelBuilder)
|
||||
// {
|
||||
// uIPanelBuilder.VScrollView(delegate (UIPanelBuilder uIPanelBuilder2)
|
||||
// {
|
||||
// InputAction[] array2 = actions;
|
||||
// foreach (InputAction val in array2)
|
||||
// {
|
||||
// //uIPanelBuilder2.AddInputBindingControl(val, conflicts.Contains(val), DidRebind);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
//});
|
||||
//return false;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,37 @@
|
||||
using Game.Messages;
|
||||
using Core;
|
||||
using Game.Messages;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using KeyValue.Runtime;
|
||||
using Model;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TweaksAndThings.Enums;
|
||||
using UI;
|
||||
using UI.Builder;
|
||||
using UI.CarInspector;
|
||||
using UI.ContextMenu;
|
||||
using UI.Tags;
|
||||
using static Model.Car;
|
||||
|
||||
namespace TweaksAndThings.Patches;
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(CarInspector))]
|
||||
[HarmonyPatch(nameof(CarInspector.PopulateCarPanel), typeof(UIPanelBuilder))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
public class CarInspector_PopulateCarPanel_Patch
|
||||
internal class CarInspector_PopulateCarPanel_Patch
|
||||
{
|
||||
private static IEnumerable<LogicalEnd> ends = Enum.GetValues(typeof(LogicalEnd)).Cast<LogicalEnd>();
|
||||
private static ILogger _log => Log.ForContext<CarInspector_PopulateCarPanel_Patch>();
|
||||
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 handbrakes
|
||||
/// If a caboose inspector is opened, it will auto set Anglecocks, gladhands and hand brakes
|
||||
/// </summary>
|
||||
/// <param name="__instance"></param>
|
||||
/// <param name="builder"></param>
|
||||
@@ -30,58 +39,98 @@ public class CarInspector_PopulateCarPanel_Patch
|
||||
private static bool Prefix(CarInspector __instance, UIPanelBuilder builder)
|
||||
{
|
||||
|
||||
TweaksAndThings tweaksAndThings = SingletonPluginBase<TweaksAndThings>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled) return true;
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment();
|
||||
|
||||
var consist = __instance._car.EnumerateCoupled(LogicalEnd.A);
|
||||
builder = AddCarConsistRebuildObservers(builder, consist);
|
||||
var consist = __instance._car.EnumerateCoupled();
|
||||
|
||||
builder.HStack(delegate (UIPanelBuilder hstack)
|
||||
{
|
||||
hstack = AddCarConsistRebuildObservers(hstack, consist);
|
||||
var buttonName = $"{(consist.Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} {TextSprites.HandbrakeWheel}";
|
||||
hstack.AddButtonCompact(buttonName, delegate
|
||||
{
|
||||
MrocConsistHelper(__instance._car, MrocHelperType.Handbrake);
|
||||
MrocConsistHelper(__instance._car, MrocHelperType.Handbrake, buttonsHaveCost);
|
||||
hstack.Rebuild();
|
||||
}).Tooltip(buttonName, $"Iterates over cars in this consist and {(consist.Any(c => c.HandbrakeApplied()) ? "releases" : "sets")} {TextSprites.HandbrakeWheel}.");
|
||||
|
||||
if (consist.Any(c => c.EndAirSystemIssue()))
|
||||
{
|
||||
hstack.AddButtonCompact("Connect Air Lines", delegate
|
||||
hstack.AddButtonCompact("Connect Air", delegate
|
||||
{
|
||||
MrocConsistHelper(__instance._car, MrocHelperType.GladhandAndAnglecock);
|
||||
MrocConsistHelper(__instance._car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
|
||||
hstack.Rebuild();
|
||||
}).Tooltip("Connect Consist Air Lines", "Iterates over each car in this consist and connects gladhands and opens anglecocks.");
|
||||
}).Tooltip("Connect Consist Air", "Iterates over each car in this consist and connects gladhands and opens anglecocks.");
|
||||
}
|
||||
|
||||
hstack.AddButtonCompact("Bleed Consist", delegate
|
||||
{
|
||||
MrocConsistHelper(__instance._car, MrocHelperType.BleedAirSystem, buttonsHaveCost);
|
||||
hstack.Rebuild();
|
||||
}).Tooltip("Bleed Air Lines", "Iterates over each car in this consist and bleeds the air out of the lines.");
|
||||
});
|
||||
|
||||
CabooseUiEnhancer(__instance, builder, consist, tweaksAndThings);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UIPanelBuilder AddCarConsistRebuildObservers(UIPanelBuilder builder, IEnumerable<Model.Car> consist)
|
||||
private static void CabooseUiEnhancer(CarInspector __instance, UIPanelBuilder builder, IEnumerable<Car> consist, TweaksAndThingsPlugin plugin)
|
||||
{
|
||||
foreach (Model.Car car in consist)
|
||||
if (plugin.CabooseNonMotiveAllowedSetting(__instance._car))
|
||||
{
|
||||
builder = AddObserver(builder, car, PropertyChange.KeyForControl(PropertyChange.Control.Handbrake));
|
||||
builder.HStack(delegate (UIPanelBuilder hstack)
|
||||
{
|
||||
hstack = AddCarConsistRebuildObservers(hstack, consist, all: false);
|
||||
hstack.AddField("Consist Info", hstack.HStack(delegate (UIPanelBuilder field)
|
||||
{
|
||||
int consistLength = consist.Count();
|
||||
int tonnage = LocomotiveControlsHoverArea.CalculateTonnage(consist);
|
||||
int lengthInMeters = UnityEngine.Mathf.CeilToInt(LocomotiveControlsHoverArea.CalculateLengthInMeters(consist.ToList()) * 3.28084f);
|
||||
var newSubTitle = () => string.Format("{0}, {1:N0}T, {2:N0}ft, {3:N0} mph", consistLength.Pluralize("car"), tonnage, lengthInMeters, __instance._car.VelocityMphAbs);
|
||||
|
||||
field.AddLabel(() => newSubTitle(), UIPanelBuilder.Frequency.Fast)
|
||||
.Tooltip("Consist Info", "Reflects info about consist.").FlexibleWidth();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static UIPanelBuilder AddCarConsistRebuildObservers(UIPanelBuilder builder, IEnumerable<Model.Car> consist, bool all = true)
|
||||
{
|
||||
TagController tagController = UnityEngine.Object.FindFirstObjectByType<TagController>();
|
||||
foreach (Model.Car car in consist.Where(c => c.Archetype != Model.Definition.CarArchetype.Tender))
|
||||
{
|
||||
builder = AddObserver(builder, car, PropertyChange.KeyForControl(PropertyChange.Control.Handbrake), tagController);
|
||||
foreach (LogicalEnd logicalEnd in ends)
|
||||
{
|
||||
builder = AddObserver(builder, car, KeyValueKeyFor(EndGearStateKey.IsCoupled, car.LogicalToEnd(logicalEnd)));
|
||||
builder = AddObserver(builder, car, KeyValueKeyFor(EndGearStateKey.IsAirConnected, car.LogicalToEnd(logicalEnd)));
|
||||
builder = AddObserver(builder, car, KeyValueKeyFor(EndGearStateKey.Anglecock, car.LogicalToEnd(logicalEnd)));
|
||||
builder = AddObserver(builder, car, KeyValueKeyFor(EndGearStateKey.IsCoupled, car.LogicalToEnd(logicalEnd)), tagController);
|
||||
if (all) builder = AddObserver(builder, car, KeyValueKeyFor(EndGearStateKey.IsAirConnected, car.LogicalToEnd(logicalEnd)), tagController);
|
||||
if (all) builder = AddObserver(builder, car, KeyValueKeyFor(EndGearStateKey.Anglecock, car.LogicalToEnd(logicalEnd)), tagController);
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static UIPanelBuilder AddObserver(UIPanelBuilder builder, Model.Car car, string key)
|
||||
private static UIPanelBuilder AddObserver(UIPanelBuilder builder, Model.Car car, string key, TagController tagController)
|
||||
{
|
||||
builder.AddObserver(
|
||||
car.KeyValueObject.Observe(
|
||||
key,
|
||||
delegate (Value value)
|
||||
{
|
||||
builder.Rebuild();
|
||||
try
|
||||
{
|
||||
builder.Rebuild();
|
||||
if (car.TagCallout != null) tagController.UpdateTags(CameraSelector.shared._currentCamera.GroundPosition, true);
|
||||
if (ContextMenu.IsShown && ContextMenu.Shared.centerLabel.text == car.DisplayName) CarPickable.HandleShowContextMenu(car);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}");
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
@@ -90,46 +139,105 @@ public class CarInspector_PopulateCarPanel_Patch
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType)
|
||||
{
|
||||
IEnumerable<Model.Car> consist = car.EnumerateCoupled(LogicalEnd.A);
|
||||
//var dh = new DownloadHandlerAudioClip($"file://{cacheFileName}", AudioType.MPEG);
|
||||
//dh.compressed = true; // This
|
||||
|
||||
//using (UnityWebRequest wr = new UnityWebRequest($"file://{cacheFileName}", "GET", dh, null)) {
|
||||
// yield return wr.SendWebRequest();
|
||||
// if (wr.responseCode == 200) {
|
||||
// audioSource.clip = dh.audioClip;
|
||||
// }
|
||||
//}
|
||||
|
||||
public static int MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost)
|
||||
{
|
||||
int output = 0;
|
||||
TrainController tc = UnityEngine.Object.FindObjectOfType<TrainController>();
|
||||
IEnumerable<Model.Car> consist = car.EnumerateCoupled();
|
||||
_log.ForContext("car", car).Verbose($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
|
||||
|
||||
CalculateCostIfEnabled(car, mrocHelperType, buttonsHaveCost, consist);
|
||||
|
||||
Log.Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
|
||||
switch (mrocHelperType)
|
||||
{
|
||||
case MrocHelperType.Handbrake:
|
||||
if (consist.Any(c => c.HandbrakeApplied()))
|
||||
{
|
||||
consist.Do(c => c.SetHandbrake(false));
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
TrainController tc = UnityEngine.Object.FindObjectOfType<TrainController>();
|
||||
consist = consist.Where(c => c is not BaseLocomotive && c.Archetype != Model.Definition.CarArchetype.Tender);
|
||||
//when ApplyHandbrakesAsNeeded is called, and the consist contains an engine, it stops applying brakes.
|
||||
tc.ApplyHandbrakesAsNeeded(consist.ToList(), PlaceTrainHandbrakes.Automatic);
|
||||
}
|
||||
break;
|
||||
|
||||
case MrocHelperType.GladhandAndAnglecock:
|
||||
consist.Do(c =>
|
||||
ends.Do(end =>
|
||||
{
|
||||
EndGear endGear = c[end];
|
||||
|
||||
StateManager.ApplyLocal(
|
||||
new PropertyChange(
|
||||
c.id,
|
||||
KeyValueKeyFor(EndGearStateKey.Anglecock, c.LogicalToEnd(end)),
|
||||
new FloatPropertyValue(endGear.IsCoupled ? 1f : 0f)
|
||||
)
|
||||
);
|
||||
|
||||
if (c.TryGetAdjacentCar(end, out Model.Car c2))
|
||||
{
|
||||
StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true));
|
||||
}
|
||||
|
||||
})
|
||||
CarEndAirUpdate(c)
|
||||
);
|
||||
break;
|
||||
|
||||
case MrocHelperType.BleedAirSystem:
|
||||
consist = consist.Where(c => !c.MotivePower());
|
||||
_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));
|
||||
}
|
||||
break;
|
||||
|
||||
case MrocHelperType.Oil:
|
||||
consist = consist.Where(c => c.NeedsOiling || c.HasHotbox);
|
||||
_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)));
|
||||
output += car.HasHotbox ? 1 : 0;
|
||||
if (car.HasHotbox) car.AdjustHotboxValue();
|
||||
}
|
||||
break;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
internal static void CarEndAirUpdate(Car c)
|
||||
{
|
||||
ends.Do(end =>
|
||||
{
|
||||
EndGear endGear = c[end];
|
||||
|
||||
StateManager.ApplyLocal(
|
||||
new PropertyChange(
|
||||
c.id,
|
||||
KeyValueKeyFor(EndGearStateKey.Anglecock, c.LogicalToEnd(end)),
|
||||
new FloatPropertyValue(endGear.IsCoupled ? 1f : 0f)
|
||||
)
|
||||
);
|
||||
|
||||
if (c.TryGetAdjacentCar(end, out Model.Car c2)) StateManager.ApplyLocal(new SetGladhandsConnected(c.id, c2.id, true));
|
||||
});
|
||||
}
|
||||
|
||||
internal static void CalculateCostIfEnabled(Car car, MrocHelperType mrocHelperType, bool buttonsHaveCost, IEnumerable<Car> consist)
|
||||
{
|
||||
if (buttonsHaveCost)
|
||||
{
|
||||
float originalTimeCost = consist.CalculateCostForAutoEngineerEndGearSetting();
|
||||
float timeCost = originalTimeCost;
|
||||
float crewCost = timeCost / 3600; //hours of time deducted from caboose.
|
||||
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).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)})");
|
||||
|
||||
StateManager_OnDayDidChange_Patch.UnbilledAutoBrakeCrewRunDuration += timeCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
242
TweaksAndThings/Patches/CarPickable_Activate_Patch.cs
Normal file
242
TweaksAndThings/Patches/CarPickable_Activate_Patch.cs
Normal file
@@ -0,0 +1,242 @@
|
||||
using Core;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.Ops;
|
||||
using Model.Physics;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
using UI.Tags;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
enum MrocAction
|
||||
{
|
||||
Follow,
|
||||
Inspect,
|
||||
ConnectConsistAir,
|
||||
ToggleConsistBrakes
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(CarPickable))]
|
||||
[HarmonyPatch(nameof(CarPickable.Activate), typeof(PickableActivateEvent))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class CarPickable_Activate_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<CarPickable_Activate_Patch>();
|
||||
|
||||
static float clicked = 0;
|
||||
static float clicktime = 0;
|
||||
static float clickdelay = 0.5f;
|
||||
|
||||
private static bool Prefix(CarPickable __instance, PickableActivateEvent evt)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
_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).Debug("just click!");
|
||||
return false;
|
||||
|
||||
}
|
||||
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(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(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(car, MrocHelperType.GladhandAndAnglecock, tweaksAndThings.EndGearHelpersRequirePayment());
|
||||
_log.ForContext("car", car).Debug("altShiftHeld!");
|
||||
output = false;
|
||||
}
|
||||
else if (GameInput.IsAltDown)
|
||||
{
|
||||
car.SetHandbrake(!car.HandbrakeApplied());
|
||||
CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(car);
|
||||
if (car.TryGetAdjacentCar(Car.LogicalEnd.A, out Model.Car cA)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cA);
|
||||
if (car.TryGetAdjacentCar(Car.LogicalEnd.B, out Model.Car cB)) CarInspector_PopulateCarPanel_Patch.CarEndAirUpdate(cB);
|
||||
_log.ForContext("car", car).Debug("ctrlHeld!");
|
||||
TagController.Shared.UpdateTag(car, car.TagCallout, OpsController.Shared);
|
||||
car.TagCallout.Update();
|
||||
output = false;
|
||||
}
|
||||
else if (GameInput.IsShiftDown)
|
||||
{
|
||||
CameraSelector.shared.FollowCar(car);
|
||||
output = false;
|
||||
}
|
||||
}
|
||||
else if ((GameInput.IsControlDown || GameInput.IsAltDown) && activation == PickableActivation.Secondary)
|
||||
{
|
||||
AltClickageMyBrosif(car, activation);
|
||||
output = false;
|
||||
}
|
||||
else if (activation == PickableActivation.Secondary)
|
||||
{
|
||||
//AltClickageMyBrosif(car, activation);
|
||||
CarPickable.HandleShowContextMenu(car);
|
||||
output = false;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
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(car, MrocHelperType.Handbrake, false);
|
||||
if (airSystemIssues)
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, false);
|
||||
if (needsOiling)
|
||||
hbFix = CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.Oil, false);
|
||||
if (hbFix > 0)
|
||||
Multiplayer.Broadcast($"Near {Hyperlink.To(car)}: \"{hbFix.Pluralize("hotbox") + " repaired!"}\"");
|
||||
if (chargeIt)
|
||||
CarInspector_PopulateCarPanel_Patch.CalculateCostIfEnabled(car, MrocHelperType.Handbrake, tweaksAndThings.EndGearHelpersRequirePayment(), consist);
|
||||
}
|
||||
|
||||
public static bool OnPointerDown(
|
||||
PickableActivateEvent evt,
|
||||
PickableActivation btn = PickableActivation.Primary
|
||||
)
|
||||
{
|
||||
bool output = false;
|
||||
if (evt.Activation == btn)
|
||||
{
|
||||
clicked++;
|
||||
if (clicked == 1) clicktime = Time.time;
|
||||
|
||||
if (clicked > 1 && Time.time - clicktime < clickdelay)
|
||||
{
|
||||
clicked = 0;
|
||||
clicktime = 0;
|
||||
output = true;
|
||||
|
||||
}
|
||||
else if (clicked > 2 || Time.time - clicktime > 1) clicked = 0;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
using UI.ContextMenu;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(CarPickable))]
|
||||
[HarmonyPatch(nameof(CarPickable.HandleShowContextMenu), typeof(Car))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class CarPickable_HandleShowContextMenu_Patch
|
||||
{
|
||||
private static bool Prefix(Car car)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
bool buttonsHaveCost = tweaksAndThings.EndGearHelpersRequirePayment();
|
||||
TrainController trainController = TrainController.Shared;
|
||||
ContextMenu shared = ContextMenu.Shared;
|
||||
if (ContextMenu.IsShown)
|
||||
{
|
||||
shared.Hide();
|
||||
}
|
||||
shared.Clear();
|
||||
|
||||
shared.AddButton(ContextMenuQuadrant.General, (trainController.SelectedCar == car) ? "Deselect" : "Select", SpriteName.Select, delegate
|
||||
{
|
||||
trainController.SelectedCar = ((trainController.SelectedCar == car) ? null : car);
|
||||
});
|
||||
if (GameInput.IsShiftDown)
|
||||
{
|
||||
if (!car.EnumerateCoupled().Any(c => !c.SupportsBleed()))
|
||||
{
|
||||
shared.AddButton(ContextMenuQuadrant.Brakes, $"Bleed Consist", SpriteName.Bleed, delegate
|
||||
{
|
||||
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.General, $"Air Up Consist", SpriteName.Select, delegate
|
||||
{
|
||||
CarInspector_PopulateCarPanel_Patch.MrocConsistHelper(car, MrocHelperType.GladhandAndAnglecock, buttonsHaveCost);
|
||||
});
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
bool apply = !car.air.handbrakeApplied;
|
||||
car.SetHandbrake(apply);
|
||||
});
|
||||
}
|
||||
|
||||
shared.AddButton(ContextMenuQuadrant.General, $"Follow", SpriteName.Inspect, delegate
|
||||
{
|
||||
CameraSelector.shared.FollowCar(car);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
31
TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs
Normal file
31
TweaksAndThings/Patches/EngineRosterPanel_Populate_Patch.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using HarmonyLib;
|
||||
using Railloader;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
using UI.EngineRoster;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
|
||||
[HarmonyPatch(typeof(EngineRosterPanel))]
|
||||
[HarmonyPatch(nameof(EngineRosterPanel.Populate))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class EngineRosterPanel_Populate_Patch
|
||||
{
|
||||
private static bool Prefix(EngineRosterPanel __instance, ref List<RosterRowData> rows)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
|
||||
__instance._window.Title = __instance._window.Title.Split(':')[0].Trim();
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
var hiddenEntries = rows.Where(r => r.Engine.locomotiveControl.air.IsCutOut && !r.IsSelected && !r.IsFavorite).Select(r => r.Engine.id) ?? Enumerable.Empty<string>();
|
||||
|
||||
if (hiddenEntries.Any()) __instance._window.Title =string.Format("{0} : {1}", __instance._window.Title, $"Hidden MU Count [{hiddenEntries.Count()}]");
|
||||
|
||||
rows = rows.Where(r => !hiddenEntries.Contains(r.Engine.id)).ToList();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
202
TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs
Normal file
202
TweaksAndThings/Patches/EngineRosterRow_Refresh_Patch.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.Definition.Data;
|
||||
using Model.Ops;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using UI;
|
||||
using UI.EngineRoster;
|
||||
using UI.Tooltips;
|
||||
using UnityEngine;
|
||||
using Game.State;
|
||||
using Game;
|
||||
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(EngineRosterRow))]
|
||||
[HarmonyPatch(nameof(EngineRosterRow.Refresh))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class EngineRosterRow_Refresh_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<EngineRosterRow_Refresh_Patch>();
|
||||
|
||||
public static void Postfix(EngineRosterRow __instance)
|
||||
{
|
||||
TweaksAndThingsPlugin? tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
RosterFuelColumnSettings? rosterFuelColumnSettings = tweaksAndThings?.settings?.EngineRosterFuelColumnSettings;
|
||||
|
||||
string fuelInfoText = string.Empty;
|
||||
string fuelInfoTooltip = string.Empty;
|
||||
|
||||
TweakyTweakTweakers(__instance);
|
||||
|
||||
if (tweaksAndThings == null ||
|
||||
rosterFuelColumnSettings == null ||
|
||||
!tweaksAndThings.IsEnabled() ||
|
||||
rosterFuelColumnSettings.EngineRosterFuelStatusColumn == EngineRosterFuelDisplayColumn.None || (!GameInput.IsAltDown && !rosterFuelColumnSettings.EngineRosterShowsFuelStatusAlways) ||
|
||||
__instance._engine.IsMuEnabled
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Car engineOrTender = __instance._engine;
|
||||
IEnumerable<Car> locos = engineOrTender.EnumerateCoupled().Where(c => c.IsLocomotive).ToList();
|
||||
IEnumerable<Car> consist = engineOrTender.EnumerateCoupled().Where(c => c.EnableOiling).ToList();
|
||||
bool cabooseRequirementFulfilled =
|
||||
!tweaksAndThings.RequireConsistCabooseForOilerAndHotboxSpotter()
|
||||
|| consist.ConsistNoFreight()
|
||||
|| (bool)engineOrTender.FindMyCabooseSansLoadRequirement();
|
||||
float offendingPercentage = 100f;
|
||||
|
||||
foreach (Car loco in locos)
|
||||
{
|
||||
var investigate = loco;
|
||||
List<LoadSlot> loadSlots = investigate.Definition.LoadSlots;
|
||||
if (!loadSlots.Any())
|
||||
{
|
||||
investigate = investigate.DetermineFuelCar()!;
|
||||
loadSlots = investigate != null ? investigate.Definition.LoadSlots : Enumerable.Empty<LoadSlot>().ToList();
|
||||
}
|
||||
|
||||
var offender = loadSlots.OrderBy(ls => (investigate.GetLoadInfo(ls.RequiredLoadIdentifier, out int slotIndex)?.Quantity ?? 0) / loadSlots[slotIndex].MaximumCapacity).FirstOrDefault().RequiredLoadIdentifier;
|
||||
|
||||
|
||||
for (int i = 0; i < loadSlots.Count; i++)
|
||||
{
|
||||
CarLoadInfo? loadInfo = investigate.GetLoadInfo(i);
|
||||
if (loadInfo.HasValue)
|
||||
{
|
||||
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
|
||||
var fuelLevel = FuelLevel(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
|
||||
var offenderCheck = CalcPercentLoad(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
|
||||
|
||||
if (offenderCheck < offendingPercentage)
|
||||
{
|
||||
offendingPercentage = offenderCheck;
|
||||
fuelInfoText = loadSlots[i].RequiredLoadIdentifier == offender ? $"{fuelLevel} " : string.Empty;
|
||||
}
|
||||
|
||||
fuelInfoTooltip += $"{TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity)} {valueOrDefault.LoadString(CarPrototypeLibrary.instance.LoadForId(valueOrDefault.LoadId))} {(!loco.id.Equals(__instance._engine.id) ? $"{loco.DisplayName}" : "")}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature && consist.Any())
|
||||
{
|
||||
float lowestOilLevel = consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled;
|
||||
var oilLevel = FuelLevel(lowestOilLevel, 1);
|
||||
fuelInfoTooltip += $"{lowestOilLevel.TriColorPiePercent(1)} {oilLevel} Consist Oil Lowest Level\n";
|
||||
if (CalcPercentLoad(lowestOilLevel, 1) < offendingPercentage)
|
||||
{
|
||||
fuelInfoText = $"{oilLevel} ";
|
||||
}
|
||||
|
||||
if (consist.Any(c => c.HasHotbox))
|
||||
{
|
||||
fuelInfoText = $"{TextSprites.Hotbox} ";
|
||||
fuelInfoTooltip = $"{TextSprites.Hotbox} Hotbox detected!\n{fuelInfoTooltip}";
|
||||
}
|
||||
}
|
||||
else if (!cabooseRequirementFulfilled && StateManager.Shared.Storage.OilFeature)
|
||||
{
|
||||
fuelInfoTooltip += $"Add Caboose To Consist For Consist Oil Level Reporting\n";
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error Detecting oiling consist status for engine roster");
|
||||
}
|
||||
|
||||
switch (rosterFuelColumnSettings.EngineRosterFuelStatusColumn)
|
||||
{
|
||||
case EngineRosterFuelDisplayColumn.Engine:
|
||||
SetLabelAndTooltip(ref __instance.nameLabel, ref __instance.nameTooltip, fuelInfoText, fuelInfoTooltip);
|
||||
break;
|
||||
case EngineRosterFuelDisplayColumn.Crew:
|
||||
SetLabelAndTooltip(ref __instance.crewLabel, ref __instance.crewTooltip, fuelInfoText, fuelInfoTooltip);
|
||||
break;
|
||||
case EngineRosterFuelDisplayColumn.Status:
|
||||
SetLabelAndTooltip(ref __instance.infoLabel, ref __instance.infoTooltip, fuelInfoText, fuelInfoTooltip);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rosterFuelColumnSettings.EngineRosterFuelStatusColumn = EngineRosterFuelDisplayColumn.None;
|
||||
Log.Error(ex, "Error Detecting fuel status for engine roster");
|
||||
}
|
||||
}
|
||||
|
||||
private static void TweakyTweakTweakers(EngineRosterRow __instance)
|
||||
{
|
||||
var helperData = EngineTextHelper(__instance._engine);
|
||||
|
||||
if (helperData.HasValue)
|
||||
{
|
||||
__instance.nameLabel.text = helperData.Value.nameLabel;
|
||||
__instance.nameTooltip.tooltipText += helperData.Value.nameTooltip;
|
||||
}
|
||||
|
||||
__instance.crewLabel.text = string.Empty;
|
||||
for (int i = __instance._crewComponents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
string str = __instance._crewComponents[i];
|
||||
if ((new[] { "MU", "AE" }).Contains(str))
|
||||
str = $"<sup>{str} </sup>";
|
||||
__instance.crewLabel.text = $"{str}{__instance.crewLabel.text}";
|
||||
}
|
||||
}
|
||||
|
||||
internal static (string nameLabel, string nameTooltip, int selectedCount)? EngineTextHelper(Car loco, bool mapIcon = false)
|
||||
{
|
||||
(string nameLabel, string nameTooltip, int selectedCount)? output = null;
|
||||
int selectedCount = 0;
|
||||
Dictionary<PlayerId, IPlayer> dictionary = StateManager.Shared.PlayersManager.AllPlayers.ToDictionary((IPlayer p) => p.PlayerId, (IPlayer p) => p);
|
||||
List<string> usersSelected = new();
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
if (new PlayerProperties(PlayerPropertiesManager.Shared._object[kvp.Key.ToString()]).SelectedCarId == loco.id)
|
||||
{
|
||||
usersSelected.Add(kvp.Value.Name);
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCount > 0 && dictionary.Count > 1)
|
||||
output = ($"{(mapIcon && loco is BaseLocomotive ? loco.Ident.RoadNumber : loco.DisplayName)}<sub>{selectedCount}</sub>", $"{Environment.NewLine}Selected by: {string.Join(", ", usersSelected)}", selectedCount);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static void SetLabelAndTooltip(ref TMP_Text label, ref UITooltipProvider tooltip, string fuelInfoText, string fuelInfoTooltip)
|
||||
{
|
||||
label.text = $" {fuelInfoText} {label.text}";
|
||||
tooltip.TooltipInfo = new TooltipInfo(tooltip.tooltipTitle, fuelInfoTooltip);
|
||||
}
|
||||
|
||||
public static float CalcPercentLoad(float quantity, float capacity)
|
||||
{
|
||||
float num = capacity <= 0f ? 0 : Mathf.Clamp(quantity / capacity * 100, 0, 100);
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
public static string FuelLevel(float quantity, float capacity)
|
||||
{
|
||||
return $"{Mathf.FloorToInt(CalcPercentLoad(quantity, capacity)):D2}%";
|
||||
}
|
||||
}
|
||||
15
TweaksAndThings/Patches/EntityReference_Text_Patch.cs
Normal file
15
TweaksAndThings/Patches/EntityReference_Text_Patch.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(EntityReference))]
|
||||
[HarmonyPatch(nameof(EntityReference.Text))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class EntityReference_Text_Patch
|
||||
{
|
||||
static void Postfix(ref string __result)
|
||||
{
|
||||
if (__result == "Unknown")
|
||||
__result = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using Game;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Helpers;
|
||||
using Model;
|
||||
using Model.Ops.Timetable;
|
||||
using Newtonsoft.Json;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
@@ -14,30 +15,45 @@ using System.Text.RegularExpressions;
|
||||
using Track.Signals.Panel;
|
||||
using UI.Console;
|
||||
|
||||
namespace TweaksAndThings.Patches;
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(ExpandedConsole))]
|
||||
[HarmonyPatch(nameof(ExpandedConsole.Add))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
public class ExpandedConsole_Add_Patch
|
||||
internal class ExpandedConsole_Add_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<ExpandedConsole_Add_Patch>();
|
||||
private static void Prefix(ref UI.Console.Console.Entry entry)
|
||||
{
|
||||
entry.Text = $"{entry.Timestamp} : {entry.Text}";
|
||||
entry.Timestamp = RealNow();
|
||||
SendMs(ref entry);
|
||||
SendMs((UI.Console.Console.Entry?)entry);
|
||||
}
|
||||
|
||||
static void SendMs(ref UI.Console.Console.Entry entry)
|
||||
|
||||
private static string hold => @"```ansi
|
||||
[2;40m[0m[2;40m[2;34m■[0m[2;40m[0m[2;34m[2;40m[0m[2;34m[0m[2;40m[1;2m[1;34m{loco}[0m[1;40m[0m[2;40m[0m[2;40m[1;2m[1;34m[0m[1;40m[0m[2;40m[0m[2;40m[2;34m■[0m[2;40m[0m[2;34m[2;40m[0m[2;34m[0m {msg}
|
||||
```";
|
||||
|
||||
private static string west => @"```ansi
|
||||
[2;40m[0m[2;31m[2;40m◀{loco}[0m[2;31m[0m[2;40m[2;31m [0m[2;40m[0m[2;40m[0m {msg}
|
||||
```";
|
||||
|
||||
private static string east => @"```ansi
|
||||
[2;40m[0m[2;32m[0m[2;40m [0m[2;36m[0m[2;40m[0m[2;36m[2;40m{loco}▶[0m[2;36m[0m {msg}
|
||||
```";
|
||||
|
||||
internal static void SendMs(UI.Console.Console.Entry? entry, string? text = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
TweaksAndThings tweaksAndThings = SingletonPluginBase<TweaksAndThings>.Shared;
|
||||
if (entry is null && !String.IsNullOrEmpty(text)) entry = new() { Text = text };
|
||||
var msgText = entry?.Text ?? string.Empty;
|
||||
if (msgText.StartsWith("Usage:")) return;
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
StateManager shared = StateManager.Shared;
|
||||
GameStorage gameStorage = shared.Storage;
|
||||
WebhookSettings settings = tweaksAndThings.settings.WebhookSettingsList.FirstOrDefault(ws => ws.RailroadMark == gameStorage.RailroadMark);
|
||||
|
||||
Log.Information(entry.Text);
|
||||
WebhookSettings settings = tweaksAndThings?.settings?.WebhookSettingsList?.FirstOrDefault(ws => ws.RailroadMark == gameStorage.RailroadMark);
|
||||
|
||||
if (
|
||||
settings != null &&
|
||||
@@ -48,34 +64,47 @@ public class ExpandedConsole_Add_Patch
|
||||
{
|
||||
var t = new Regex("car:(.*)\"");
|
||||
|
||||
var carId = t.IsMatch(entry.Text) ? Regex.Match(entry.Text, "car:(.*?)\"").Groups[1].Captures[0].ToString() : string.Empty;
|
||||
var carId = t.IsMatch(msgText) ? Regex.Match(msgText, "car:(.*?)\"").Groups[1].Captures[0].ToString() : string.Empty;
|
||||
Model.Car? car = TrainController.Shared.CarForString(carId);
|
||||
Log.Information($"|{carId}| {car?.IsLocomotive}");
|
||||
var data = UpdateCarText(car);
|
||||
bool engineInMessage = car?.IsLocomotive ?? false;
|
||||
var image = engineInMessage ?
|
||||
new
|
||||
{
|
||||
url = string.Empty
|
||||
} :
|
||||
null;
|
||||
string msgToSend = string.Empty;
|
||||
string desc = Regex.Replace(msgText, "<.*?>", "");
|
||||
desc = !!car ? desc.Replace(car?.DisplayName ?? string.Empty, string.Empty) : desc;
|
||||
desc = desc.Trim();//.Replace(": ", "\n");
|
||||
|
||||
if (engineInMessage)
|
||||
if (!!car && engineInMessage)
|
||||
{
|
||||
CTCPanelMarkerManager cTCPanelMarkerManager = UnityEngine.Object.FindObjectOfType<CTCPanelMarkerManager>();
|
||||
|
||||
CTCPanelMarker marker = cTCPanelMarkerManager?._markers?.Values?.FirstOrDefault(v => v.TooltipInfo.Text.Contains(car.Ident.RoadNumber));
|
||||
|
||||
string color = CTCPanelMarker.InferColorFromText(car?.DisplayName).HexString().Replace("#", string.Empty);
|
||||
if (marker != null)
|
||||
|
||||
string markerText = marker?.TooltipInfo.Text ?? string.Empty;
|
||||
if (markerText.StartsWith(">") || markerText.EndsWith(">") || data.Item2 == Timetable.Direction.East || msgText.Contains("*-"))
|
||||
{
|
||||
color = CTCPanelMarker.InferColorFromText(marker.TooltipInfo.Text).HexString().Replace("#", string.Empty);
|
||||
msgToSend = east.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc);
|
||||
}
|
||||
image = new
|
||||
else if (markerText.StartsWith("<") || markerText.EndsWith("<") || data.Item2 == Timetable.Direction.West || msgText.Contains("-*"))
|
||||
{
|
||||
url = $"https://img.shields.io/badge/{car.DisplayName.Replace(" ", "%20")}-%20-{color}.png"
|
||||
};
|
||||
|
||||
msgToSend = west.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
msgToSend = hold.Replace("{loco}", car?.DisplayName.Replace(" ", string.Empty)).Replace("{msg}", desc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msgToSend = desc;
|
||||
}
|
||||
msgToSend = msgToSend
|
||||
.Replace(" , ", ", ")
|
||||
.Replace(", :", " :")
|
||||
.Replace(" ; ", "; ")
|
||||
.Replace("*-", string.Empty)
|
||||
.Replace("#", string.Empty)
|
||||
.Replace("-*", string.Empty)
|
||||
.Trim();
|
||||
|
||||
var SuccessWebHook = new
|
||||
{
|
||||
@@ -84,14 +113,11 @@ public class ExpandedConsole_Add_Patch
|
||||
{
|
||||
new
|
||||
{
|
||||
description= Regex.Replace(entry.Text, "<.*?>", "").Replace(": ", "\n"),
|
||||
timestamp=DateTime.UtcNow,
|
||||
image
|
||||
description= msgToSend
|
||||
},
|
||||
}
|
||||
};
|
||||
string EndPoint = settings.WebhookUrl;
|
||||
Log.Information(JsonConvert.SerializeObject(SuccessWebHook));
|
||||
|
||||
var content = new StringContent(JsonConvert.SerializeObject(SuccessWebHook), Encoding.UTF8, "application/json");
|
||||
|
||||
@@ -105,6 +131,26 @@ public class ExpandedConsole_Add_Patch
|
||||
}
|
||||
}
|
||||
|
||||
public static (string , Timetable.Direction?) UpdateCarText(Car car)
|
||||
{
|
||||
string output = string.Empty;
|
||||
Timetable.Direction? dir = null;
|
||||
if (car?.IsLocomotive ?? false)
|
||||
{
|
||||
if (StateManager.Shared.PlayersManager.TrainCrewForId(car.trainCrewId, out var trainCrew))
|
||||
{
|
||||
output = trainCrew.Name;
|
||||
if (TimetableController.Shared.TryGetTrainForTrainCrew(trainCrew, out Timetable.Train timetableTrain))
|
||||
{
|
||||
dir = timetableTrain.Direction;
|
||||
output += " (Train " + timetableTrain.DisplayStringShort + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
return (output, dir);
|
||||
}
|
||||
|
||||
|
||||
public static GameDateTime RealNow()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
using Core;
|
||||
using Game;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model.Ops;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(InterchangedIndustryLoader))]
|
||||
[HarmonyPatch(nameof(InterchangedIndustryLoader.ServeInterchange), typeof(IIndustryContext), typeof(Interchange))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class InterchangedIndustryLoader_ServiceInterchange_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<InterchangedIndustryLoader_ServiceInterchange_Patch>();
|
||||
/// <summary>
|
||||
/// Allow Interchange service for buying parts/fuel when negative balance, adding an additional 20% penalty
|
||||
/// </summary>
|
||||
/// <param name="__instance"></param>
|
||||
/// <param name="ctx"></param>
|
||||
/// <param name="interchange"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Prefix(InterchangedIndustryLoader __instance, IIndustryContext ctx, Interchange interchange)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!StateManager.IsHost || !tweaksAndThings.IsEnabled() || !tweaksAndThings.ServiceFundPenalties()) return true;
|
||||
|
||||
StateManager shared = StateManager.Shared;
|
||||
List<IOpsCar> list = (from car in EnumerateCars(__instance, ctx, requireWaybill: true)
|
||||
where car.IsEmptyOrContains(__instance.load)
|
||||
select car).ToList();
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
int penalty = 0;
|
||||
GameDateTime returnTime = ctx.Now.AddingDays(23f / 24f);
|
||||
foreach (IOpsCar item3 in list)
|
||||
{
|
||||
(float quantity, float capacity) tuple = item3.QuantityOfLoad(__instance.load);
|
||||
float item = tuple.quantity;
|
||||
float item2 = tuple.capacity;
|
||||
int num4 = Mathf.RoundToInt((item2 - item) * __instance.load.costPerUnit);
|
||||
if (num4 > 0)
|
||||
{
|
||||
var canAfford = shared.CanAfford(num4);
|
||||
penalty += Mathf.CeilToInt(!canAfford ? num4 * 0.2f : 0);
|
||||
|
||||
num2 += num4;
|
||||
num3++;
|
||||
}
|
||||
item3.Load(__instance.load, item2);
|
||||
item3.SetWaybill(null, __instance, "Full");
|
||||
ctx.MoveToBardo(item3);
|
||||
__instance.ScheduleReturnFromBardo(item3, returnTime);
|
||||
}
|
||||
if (num2 > 0)
|
||||
{
|
||||
__instance.Industry.ApplyToBalance(-num2, __instance.ledgerCategory, null, num3, quiet: true);
|
||||
string penaltyText = penalty > 0 ? $"; Overdraft fee of {penalty:C0}" : string.Empty;
|
||||
Multiplayer.Broadcast(string.Format("{6}: Ordered {0} of {1} at {2} for {3:C0}{5}. Expected return: {4}.", num3.Pluralize("car"), __instance.load.description, __instance.HyperlinkToThis, num2, 1.Pluralize("day"), penaltyText, __instance.HyperlinkToThis));
|
||||
if (penalty > 0)
|
||||
StateManager.Shared.ApplyToBalance(-penalty, __instance.ledgerCategory, null, $"Overdraft: {__instance.Industry.name}", 0, quiet: true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<IOpsCar> EnumerateCars(InterchangedIndustryLoader __instance, IIndustryContext ctx, bool requireWaybill = false)
|
||||
{
|
||||
foreach (IOpsCar item in ctx.CarsAtPosition())
|
||||
{
|
||||
if (!CarTypeMatches(__instance.Interchange, item)) continue;
|
||||
|
||||
if (requireWaybill)
|
||||
{
|
||||
try
|
||||
{
|
||||
Waybill? waybill = item.Waybill;
|
||||
string destId = waybill?.Destination.Identifier ?? string.Empty;
|
||||
if (!waybill.HasValue || !destId.Equals(__instance.Identifier)) continue;
|
||||
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_log.Error(ex, $"{item.DisplayName} issue detecting waybill");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CarTypeMatches(Interchange interchange, IOpsCar car)
|
||||
{
|
||||
string carType = car.CarType;
|
||||
return interchange.carTypeFilter.Matches(carType);
|
||||
}
|
||||
|
||||
}
|
||||
183
TweaksAndThings/Patches/MapWindow_OnClick_Patch.cs
Normal file
183
TweaksAndThings/Patches/MapWindow_OnClick_Patch.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using HarmonyLib;
|
||||
using Helpers;
|
||||
using Map.Runtime;
|
||||
using Model;
|
||||
using Model.AI;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Track;
|
||||
using UI;
|
||||
using UI.EngineControls;
|
||||
using UI.Map;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using UnityEngine.UI;
|
||||
using static UI.AutoEngineerDestinationPicker;
|
||||
using Vector2 = UnityEngine.Vector2;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(MapWindow))]
|
||||
[HarmonyPatch(nameof(MapWindow.OnClick), typeof(Vector2))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class MapWindow_OnClick_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<MapWindow_OnClick_Patch>();
|
||||
private static GameObject _prefabHolder;
|
||||
private static MapIcon _waypointPrefab;
|
||||
private static Dictionary<string, MapIcon> wps = new Dictionary<string, MapIcon>();
|
||||
|
||||
public static Sprite? LoadTexture(string fileName, string name)
|
||||
{
|
||||
string path = Path.Combine(SingletonPluginBase<TweaksAndThingsPlugin>.Shared.ModDirectory, fileName);
|
||||
Texture2D texture2D = new Texture2D(128, 128, TextureFormat.DXT5, mipChain: false);
|
||||
texture2D.name = name;
|
||||
texture2D.wrapMode = TextureWrapMode.Clamp;
|
||||
if (!ImageConversion.LoadImage(texture2D, File.ReadAllBytes(path)))
|
||||
{
|
||||
_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));
|
||||
sprite.name = name;
|
||||
|
||||
//if (!SpriteLibrary.Shared.entries.Any(s => s.name.ToString() == name)) SpriteLibrary.Shared.entries.Add(new() { name = SpriteName.Meh, sprite = sprite });
|
||||
return sprite;
|
||||
}
|
||||
public static MapIcon? waypointPrefab
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_waypointPrefab == null)
|
||||
{
|
||||
CreateWaypointPrefab();
|
||||
}
|
||||
return _waypointPrefab;
|
||||
}
|
||||
}
|
||||
|
||||
internal static GameObject prefabHolder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_prefabHolder == null)
|
||||
{
|
||||
_prefabHolder = new GameObject("Prefab Holder");
|
||||
_prefabHolder.hideFlags = HideFlags.HideAndDontSave;
|
||||
_prefabHolder.SetActive(value: false);
|
||||
}
|
||||
return _prefabHolder;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateWaypointPrefab()
|
||||
{
|
||||
float num = 0.6f; //come back
|
||||
Sprite sprite = LoadTexture("Map_pin_icon.png", "MapPin");
|
||||
_waypointPrefab = Object.Instantiate(TrainController.Shared.locomotiveMapIconPrefab, prefabHolder.transform);
|
||||
GameObject obj = _waypointPrefab.gameObject;
|
||||
obj.hideFlags = HideFlags.HideAndDontSave;
|
||||
obj.name = "Map Waypoint Icon";
|
||||
if ((bool)_waypointPrefab.Text)
|
||||
{
|
||||
Object.DestroyImmediate(_waypointPrefab.Text.gameObject);
|
||||
}
|
||||
Image componentInChildren = _waypointPrefab.GetComponentInChildren<Image>();
|
||||
componentInChildren.sprite = sprite;
|
||||
componentInChildren.transform.localScale = Vector3.one * num;
|
||||
}
|
||||
|
||||
static bool Prefix(MapWindow __instance, Vector2 viewportNormalizedPoint)
|
||||
{
|
||||
if (GameInput.IsControlDown && GameInput.IsAltDown)
|
||||
{
|
||||
Ray ray = RayForViewportNormalizedPoint(__instance, viewportNormalizedPoint);
|
||||
Vector3 gamePoint = MapManager.Instance.FindTerrainPointForXZ(WorldTransformer.WorldToGame(ray.origin));
|
||||
var selectedLoco = TrainController.Shared?.SelectedLocomotive;
|
||||
if (selectedLoco != null)
|
||||
{
|
||||
Hit? valueOrDefault = null;
|
||||
Camera _camera = null;
|
||||
if (MainCameraHelper.TryGetIfNeeded(ref _camera))
|
||||
{
|
||||
float rad = 200f;
|
||||
if (Graph.Shared.TryGetLocationFromGamePoint(gamePoint, rad, out Location location))
|
||||
{
|
||||
Hit? hit = HitLocation(location, selectedLoco);
|
||||
if (hit.HasValue)
|
||||
{
|
||||
valueOrDefault = hit.GetValueOrDefault();
|
||||
location = valueOrDefault.Value.Location;
|
||||
}
|
||||
var aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
|
||||
var mw = (location: (Location)location, carId: valueOrDefault?.CarInfo?.car?.id ?? string.Empty);
|
||||
|
||||
aeoh.SetWaypoint(mw.location, mw.carId);
|
||||
aeoh.SetOrdersValue(maybeWaypoint: mw);
|
||||
AutoEngineerDestinationPicker.Shared.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Ray RayForViewportNormalizedPoint(MapWindow i, Vector2 v2) =>
|
||||
i.mapBuilder.mapCamera.ViewportPointToRay(new Vector3(v2.x, v2.y, 0f));
|
||||
|
||||
private static Hit? HitLocation(Location? location, BaseLocomotive selectedLoco)
|
||||
{
|
||||
var _graph = TrainController.Shared.graph;
|
||||
if (location.HasValue)
|
||||
{
|
||||
Location valueOrDefault = location.GetValueOrDefault();
|
||||
TrainController shared = TrainController.Shared;
|
||||
Vector3 position = _graph.GetPosition(valueOrDefault);
|
||||
float num = 2f;
|
||||
Hit? result = null;
|
||||
HashSet<Car> value;
|
||||
using (CollectionPool<HashSet<Car>, Car>.Get(out value))
|
||||
{
|
||||
shared.CheckForCarsAtPoint(position, 2f, value, valueOrDefault);
|
||||
foreach (Car item in value)
|
||||
{
|
||||
if (selectedLoco.EnumerateCoupled().ToHashSet().Contains(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!item[item.EndToLogical(Car.End.F)].IsCoupled)
|
||||
{
|
||||
Location location2 = _graph.LocationByMoving(item.LocationF, 0.5f, checkSwitchAgainstMovement: false, stopAtEndOfTrack: true);
|
||||
float distanceBetweenClose = _graph.GetDistanceBetweenClose(valueOrDefault, location2);
|
||||
if (distanceBetweenClose < num)
|
||||
{
|
||||
num = distanceBetweenClose;
|
||||
result = new Hit(location2, (item, Car.End.F));
|
||||
}
|
||||
}
|
||||
if (!item[item.EndToLogical(Car.End.R)].IsCoupled)
|
||||
{
|
||||
Location location3 = _graph.LocationByMoving(item.LocationR, -0.5f, checkSwitchAgainstMovement: false, stopAtEndOfTrack: true).Flipped();
|
||||
float distanceBetweenClose2 = _graph.GetDistanceBetweenClose(valueOrDefault, location3);
|
||||
if (distanceBetweenClose2 < num)
|
||||
{
|
||||
num = distanceBetweenClose2;
|
||||
result = new Hit(location3, (item, Car.End.R));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value.Count > 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new Hit(valueOrDefault, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
36
TweaksAndThings/Patches/NoticeExtensions_PostNotice_Patch.cs
Normal file
36
TweaksAndThings/Patches/NoticeExtensions_PostNotice_Patch.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Game.Notices;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Serilog;
|
||||
using System;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(NoticeExtensions))]
|
||||
[HarmonyPatch(nameof(NoticeExtensions.PostNotice), typeof(Car), typeof(string), typeof(string))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
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.Debug($"{car.DisplayName} patch PostNotice");
|
||||
if (!string.IsNullOrEmpty(content) &&
|
||||
key.Equals("ai-wpt") &&
|
||||
content.ToLower().Contains("Arrived at Waypoint".ToLower())
|
||||
)
|
||||
{
|
||||
car.PostNotice("ai-wpt-rmroc451", null);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_log.ForContext("car", car).Error(ex, "woops");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using Game;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.Definition.Data;
|
||||
using Model.Ops.Definition;
|
||||
using Model.Ops;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(CarExtensions))]
|
||||
[HarmonyPatch(nameof(CarExtensions.LoadString), typeof(CarLoadInfo), typeof(Load))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class CarExtensions_LoadString_Patch
|
||||
{
|
||||
public static bool Prefix(CarLoadInfo info, Load load, ref string __result)
|
||||
{
|
||||
bool output = load.id == OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours.id;
|
||||
if (output) __result = info.Quantity.FormatCrewHours(load.description);
|
||||
|
||||
return !output;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(CarPrototypeLibrary))]
|
||||
[HarmonyPatch(nameof(CarPrototypeLibrary.LoadForId), typeof(string))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class CarPrototypeLibrary_LoadForId_Patch
|
||||
{
|
||||
public static bool Prefix(string loadId, ref Load __result)
|
||||
{
|
||||
Load load = OpsController_AnnounceCoalescedPayments_Patch.CrewLoadHours;
|
||||
if (loadId == load.id) __result = load;
|
||||
|
||||
return __result == null;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(OpsController))]
|
||||
[HarmonyPatch(nameof(OpsController.AnnounceCoalescedPayments))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class OpsController_AnnounceCoalescedPayments_Patch
|
||||
{
|
||||
static Dictionary<string, (bool spotted, bool filling)> CrewCarDict = new();
|
||||
|
||||
public static (bool spotted, bool filling) CrewCarStatus(Car car)
|
||||
{
|
||||
bool found = CrewCarDict.TryGetValue(car.id, out (bool spotted, bool filling) val);
|
||||
|
||||
if (!found) CrewCarDict.Add(car.id, (false, false));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static GameDateTime dateTime = TimeWeather.Now;
|
||||
static readonly IEnumerable<Type> refillLocations =
|
||||
new List<Type>() {
|
||||
typeof(PassengerStop),
|
||||
typeof(SimplePassengerStop),
|
||||
typeof(TeamTrack),
|
||||
typeof(RepairTrack)
|
||||
};
|
||||
|
||||
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";
|
||||
load.description = "Crew";
|
||||
load.units = LoadUnits.Quantity;
|
||||
|
||||
return load;
|
||||
}
|
||||
|
||||
private static void CarLoadCrewHelper(Car car, float deltaTime)
|
||||
{
|
||||
float rate2 = 24 * 2 * 8;// carLoadRate = 8 crew hours in 30 min loading; (24h * 2 to get half hour chunks * 8 hours to load in those chunks)
|
||||
float num2 = 99999999; //QuantityInStorage for crew-hours (infinite where crew can be shuffling about)
|
||||
float quantityToLoad = Mathf.Min(num2, IndustryComponent.RateToValue(rate2, deltaTime));
|
||||
if (car.IsCaboose() && !CrewCarStatus(car).spotted)
|
||||
{
|
||||
CrewCarDict[car.id] = (true, CrewCarDict[car.id].filling);
|
||||
}
|
||||
if (car.IsCabooseAndStoppedForLoadRefresh(new OpsCarAdapter(car, OpsController.Shared).IsFull(CrewHoursLoad())))
|
||||
{
|
||||
if (!CrewCarDict[car.id].filling) Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Topping off caboose crew.\"");
|
||||
CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, true);
|
||||
var data = car.QuantityCapacityOfLoad(CrewHoursLoad());
|
||||
if ((data.quantity + quantityToLoad > data.capacity) && data.quantity < data.capacity)
|
||||
{
|
||||
quantityToLoad = data.capacity; //ensure topping off
|
||||
Multiplayer.Broadcast($"{Hyperlink.To(car)}: \"Caboose crew topped off.\"");
|
||||
CrewCarDict[car.id] = (CrewCarDict[car.id].spotted, false);
|
||||
}
|
||||
new OpsCarAdapter(car, OpsController.Shared).Load(CrewHoursLoad(), quantityToLoad);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Prefix(IndustryComponent __instance)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!StateManager.IsHost || !tweaksAndThings.IsEnabled() || !tweaksAndThings.EndGearHelpersRequirePayment() || tweaksAndThings.DayLoadCrewHours()) return true;
|
||||
|
||||
TrainController tc = UnityEngine.Object.FindAnyObjectByType<TrainController>();
|
||||
try {
|
||||
|
||||
var passengerStops = OpsController.Shared.AllIndustries
|
||||
.SelectMany(i => i.TrackDisplayables.Where(t => refillLocations.Contains(t.GetType())));
|
||||
//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.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.Debug($"{nameof(OpsController_AnnounceCoalescedPayments_Patch)} => Caboose Helper ({deltaTime}) => {caboose.t} : {caboose.c2}");
|
||||
CarLoadCrewHelper(caboose.c2, deltaTime);
|
||||
}
|
||||
dateTime = TimeWeather.Now;
|
||||
} catch (System.Exception ex)
|
||||
{
|
||||
Log.Error(ex, "error with announce caboose helper");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
169
TweaksAndThings/Patches/RepairTrack_DailyPayables_Patch.cs
Normal file
169
TweaksAndThings/Patches/RepairTrack_DailyPayables_Patch.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Game;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Model;
|
||||
using Model.Ops;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UI.Builder;
|
||||
using UnityEngine;
|
||||
using static Model.Ops.RepairTrack;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(RepairTrack))]
|
||||
[HarmonyPatch(nameof(RepairTrack.DailyPayables), typeof(GameDateTime), typeof(IIndustryContext))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class RepairTrack_DailyPayables_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<RepairTrack_DailyPayables_Patch>();
|
||||
private static Serilog.ILogger ContextualLogger(RepairTrack __instance) =>
|
||||
_log.ForContext("RepairTrackIdentifier", __instance?.Identifier ?? "???")
|
||||
.ForContext("RepairTrackName", __instance?.name ?? "???")
|
||||
.ForContext("RepairTrackDisplayName", __instance?.DisplayName ?? "???");
|
||||
|
||||
public static bool Prefix(RepairTrack __instance, GameDateTime now, IIndustryContext ctx)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.ServiceFundPenalties()) return true;
|
||||
|
||||
|
||||
RepairRateState rateState = __instance.RateState;
|
||||
StateManager shared = StateManager.Shared;
|
||||
|
||||
if (rateState.PayDue < 1E-06f)
|
||||
{
|
||||
rateState.PayDue = 0f;
|
||||
rateState.PaidCurrent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = Mathf.CeilToInt(rateState.PayDue);
|
||||
rateState.PaidCurrent = shared.CanAfford(num);
|
||||
int penalty = Mathf.CeilToInt(!rateState.PaidCurrent ? num * 0.2f : 0);
|
||||
Multiplayer.Broadcast($"{Hyperlink.To(__instance.Industry)}: Paid {num:C0} wages for shop crew${(penalty > 0 ? $"; Overdraft fee of {penalty:C0}" : ".")}");
|
||||
|
||||
__instance.Industry.ApplyToBalance(-num, Ledger.Category.WagesRepair, null, 0, quiet: true);
|
||||
if (penalty > 0)
|
||||
StateManager.Shared.ApplyToBalance(-penalty, Ledger.Category.WagesRepair, null, $"Overdraft: {__instance.Industry.name}", 0, quiet: true);
|
||||
rateState.PayDue = 0f;
|
||||
rateState.PaidCurrent = true;
|
||||
}
|
||||
__instance.RateState = rateState;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RepairTrack))]
|
||||
[HarmonyPatch(nameof(RepairTrack.EnumerateCarsActual), typeof(IIndustryContext))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class RepairTrack_NeedsRepair_Patch
|
||||
{
|
||||
private static Serilog.ILogger _log => Log.ForContext<RepairTrack_NeedsRepair_Patch>();
|
||||
public static void Postfix(RepairTrack __instance, ref IEnumerable<Car> __result)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return;
|
||||
|
||||
__result = EnumerateCarsActualPatched(__result);
|
||||
}
|
||||
|
||||
internal static IEnumerable<Car> EnumerateCarsActualPatched(IEnumerable<Car> __result, bool usePatchedVersion = true) =>
|
||||
usePatchedVersion ? __result.Where(NeedsRepairPatched) : __result;
|
||||
|
||||
internal static bool NeedsRepairPatched(Car car)
|
||||
{
|
||||
bool result = TryGetRepairDestination(car, out var overrideTag);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RepairTrack))]
|
||||
[HarmonyPatch(nameof(RepairTrack.BuildCars), typeof(UIPanelBuilder))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class RepairTrack_BuildCars_Patch
|
||||
{
|
||||
public static bool Prefix(RepairTrack __instance, UIPanelBuilder builder)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return true;
|
||||
|
||||
BuildCarsPatched(__instance, builder);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<Car> EnumerateCarsActualOrig(RepairTrack __instance, IIndustryContext ctx)
|
||||
{
|
||||
TrainController trainController = TrainController.Shared;
|
||||
return from car in __instance.EnumerateCars(ctx)
|
||||
select trainController.CarForId(car.Id);
|
||||
}
|
||||
|
||||
|
||||
public static void BuildCarsPatched(RepairTrack __instance, UIPanelBuilder builder)
|
||||
{
|
||||
IndustryContext industryContext = __instance.CreateContext(TimeWeather.Now, 0f);
|
||||
IOrderedEnumerable<IGrouping<RepairGroup, Car>> carGroups = from @group in EnumerateCarsActualOrig(__instance, industryContext).GroupBy(delegate (Car car)
|
||||
{
|
||||
if (InForOverhaul(car))
|
||||
{
|
||||
return RepairGroup.Overhaul;
|
||||
}
|
||||
return RepairTrack_NeedsRepair_Patch.NeedsRepairPatched(car) ? RepairGroup.NeedsRepair : RepairGroup.None;
|
||||
})
|
||||
orderby @group.Key
|
||||
select @group;
|
||||
builder.VStack(delegate (UIPanelBuilder uIPanelBuilder)
|
||||
{
|
||||
float repairRateMultiplier = __instance.RateState.PayRateMultiplier;
|
||||
foreach (IGrouping<RepairGroup, Car> item in carGroups)
|
||||
{
|
||||
RepairGroup repairGroup = item.Key;
|
||||
uIPanelBuilder.AddSection(repairGroup switch
|
||||
{
|
||||
RepairGroup.Overhaul => "Overhauling",
|
||||
RepairGroup.NeedsRepair => "Repairing",
|
||||
RepairGroup.None => "No Work Order Assigned",
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
});
|
||||
foreach (Car car in item.OrderBy((Car car2) => car2.SortName))
|
||||
{
|
||||
uIPanelBuilder.HStack(delegate (UIPanelBuilder uIPanelBuilder2)
|
||||
{
|
||||
uIPanelBuilder2.AddLabel(Hyperlink.To(car)).Width(130f);
|
||||
uIPanelBuilder2.AddLabel($"{Mathf.RoundToInt(car.Condition * 100f)}%").Width(60f).HorizontalTextAlignment(HorizontalAlignmentOptions.Right);
|
||||
string text = "";
|
||||
string title = "";
|
||||
if (repairGroup == RepairGroup.Overhaul)
|
||||
{
|
||||
float overhaulProgress = car.OverhaulProgress;
|
||||
if (overhaulProgress > 0f)
|
||||
{
|
||||
text = $"{(int)(overhaulProgress * 100f)}%";
|
||||
title = "Overhaul Progress";
|
||||
}
|
||||
}
|
||||
uIPanelBuilder2.AddLabel(text).Width(60f).HorizontalTextAlignment(HorizontalAlignmentOptions.Right)
|
||||
.Tooltip(title, null);
|
||||
if (repairGroup != RepairGroup.None)
|
||||
{
|
||||
float num = CalculateRepairWorkOverall(car);
|
||||
float num2 = num * 12000f * 0.0005f;
|
||||
string text2 = ((!(repairRateMultiplier > 0f)) ? "Never" : GameDateTimeInterval.DeltaStringMinutes((int)(num / repairRateMultiplier * 60f * 24f), GameDateTimeInterval.Style.Short));
|
||||
uIPanelBuilder2.AddLabel(text2).Width(80f).HorizontalTextAlignment(HorizontalAlignmentOptions.Right)
|
||||
.Tooltip("Time Remaining", "D:HH:MM or H:MM");
|
||||
uIPanelBuilder2.AddLabel($"{num2:F1}T").Width(80f).HorizontalTextAlignment(HorizontalAlignmentOptions.Right)
|
||||
.Tooltip("Repair Parts Needed", null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).Padding(new RectOffset(20, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
73
TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs
Normal file
73
TweaksAndThings/Patches/StateManager_OnDayDidChange_Patch.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Game.Events;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using KeyValue.Runtime;
|
||||
using Model.Ops;
|
||||
using Network;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(StateManager))]
|
||||
[HarmonyPatch(nameof(StateManager.OnDayDidChange), typeof(TimeDayDidChange))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
internal class StateManager_OnDayDidChange_Patch
|
||||
{
|
||||
private const string unbilledBrakeCrewDuration = "unbilledBrakeCrewDuration";
|
||||
|
||||
private static void Postfix(StateManager __instance)
|
||||
{
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
if (!tweaksAndThings.IsEnabled()) return;
|
||||
|
||||
if (StateManager.IsHost) DoNewDayActivites(tweaksAndThings, __instance);
|
||||
}
|
||||
|
||||
private static void DoNewDayActivites(TweaksAndThingsPlugin tweaksAndThings, StateManager __instance)
|
||||
{
|
||||
if (tweaksAndThings.EndGearHelpersRequirePayment() && tweaksAndThings.DayLoadCrewHours()) LoadCabeese(__instance);
|
||||
if (tweaksAndThings.EndGearHelpersRequirePayment()) PayAutoBrakeCrewWages(__instance);
|
||||
}
|
||||
|
||||
private static void LoadCabeese(StateManager __instance)
|
||||
{
|
||||
foreach (var car in TrainController.Shared.Cars.Where(Car_Extensions.IsCaboose))
|
||||
{
|
||||
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.CrewLoadHours, data.capacity - data.quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PayAutoBrakeCrewWages(StateManager __instance)
|
||||
{
|
||||
float unbilledRunDuration = UnbilledAutoBrakeCrewRunDuration;
|
||||
int num = Mathf.FloorToInt(unbilledRunDuration / 3600f * 5f);
|
||||
float num2 = (float)num / 5f;
|
||||
float unbilledAutoEngineerRunDuration2 = unbilledRunDuration - num2 * 3600f;
|
||||
if (num > 0)
|
||||
{
|
||||
__instance.ApplyToBalance(-num, Ledger.Category.WagesAI, null, memo: "AI Brake Crew");
|
||||
Multiplayer.Broadcast($"Paid {num:C0} for {num2:F1} hours of Brake Crew services.");
|
||||
UnbilledAutoBrakeCrewRunDuration = unbilledAutoEngineerRunDuration2;
|
||||
}
|
||||
}
|
||||
|
||||
public static float UnbilledAutoBrakeCrewRunDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
return StateManager.Shared._storage._gameKeyValueObject[unbilledBrakeCrewDuration].FloatValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
StateManager.Shared._storage._gameKeyValueObject[unbilledBrakeCrewDuration] = Value.Float(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,80 @@
|
||||
using HarmonyLib;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using KeyValue.Runtime;
|
||||
using Model;
|
||||
using Model.OpsNew;
|
||||
using Model.Ops;
|
||||
using Railloader;
|
||||
using UI;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using RollingStock;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UI.Tags;
|
||||
using UnityEngine;
|
||||
using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics;
|
||||
|
||||
namespace TweaksAndThings.Patches;
|
||||
namespace RMROC451.TweaksAndThings.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(TagController))]
|
||||
[HarmonyPatch(nameof(TagController.UpdateTag), typeof(Car), typeof(TagCallout), typeof(OpsController))]
|
||||
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
|
||||
public class TagController_UpdateTag_Patch
|
||||
internal class TagController_UpdateTag_Patch
|
||||
{
|
||||
private const string tagTitleAndIconDelimeter = "\n<width=100%><align=\"right\">";
|
||||
private const string tagTitleFormat = "<align=left><margin-right={0}.5em>{1}</margin><line-height=0>";
|
||||
|
||||
private static void Postfix(Car car, TagCallout tagCallout)
|
||||
{
|
||||
TagController tagController = UnityEngine.Object.FindObjectOfType<TagController>();
|
||||
TweaksAndThings tweaksAndThings = SingletonPluginBase<TweaksAndThings>.Shared;
|
||||
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
|
||||
|
||||
if (!tweaksAndThings.IsEnabled || !tweaksAndThings.settings.HandBrakeAndAirTagModifiers)
|
||||
if (!tweaksAndThings.IsEnabled() || !tweaksAndThings.settings.HandBrakeAndAirTagModifiers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProceedWithPostFix(car, tagCallout, tagController);
|
||||
ProceedWithPostFix(car, tagCallout, tweaksAndThings.CabooseRequiredForLocoOilIndicator());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private static void ProceedWithPostFix(Car car, TagCallout tagCallout, TagController tagController)
|
||||
private static void ProceedWithPostFix(Car car, TagCallout tagCallout, bool cabooseRequired)
|
||||
{
|
||||
bool isAltDownWithCarIssue = GameInput.IsAltDown && car.CarOrEndGearIssue();
|
||||
tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", car.DisplayName);
|
||||
tagCallout.gameObject.SetActive(
|
||||
tagCallout.gameObject.activeSelf &&
|
||||
(!GameInput.IsAltDown || isAltDownWithCarIssue)
|
||||
);
|
||||
tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", Hyperlink.To(car));
|
||||
List<string> tags = new();
|
||||
string oilSpriteName = string.Empty;// "OilCan";
|
||||
|
||||
if (tagCallout.gameObject.activeSelf && isAltDownWithCarIssue)
|
||||
if (OpsController_AnnounceCoalescedPayments_Patch.CrewCarStatus(car).spotted) tags.Add("+");
|
||||
//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() || (bool)car.FindMyCabooseSansLoadRequirement());
|
||||
if (StateManager.Shared.Storage.OilFeature
|
||||
&& car.IsLocomotive
|
||||
&& !car.NeedsOiling
|
||||
&& (consist.Any(c => c.NeedsOiling) || consist.Any(c => c.HasHotbox)
|
||||
&& cabooseRequirementFulfilled())
|
||||
)
|
||||
tags.Add(consist.Any(c => c.HasHotbox) ? TextSprites.Hotbox : consist.OrderBy(c => c.Oiled).FirstOrDefault().Oiled.TriColorPiePercent(1, oilSpriteName));
|
||||
if (car.EndAirSystemIssue()) tags.Add(TextSprites.CycleWaybills);
|
||||
if (car.HandbrakeApplied()) tags.Add(TextSprites.HandbrakeWheel);
|
||||
|
||||
if (car.IsPassengerCar())
|
||||
{
|
||||
tagController.ApplyImageColor(tagCallout, Color.black);
|
||||
PassengerMarker? passengerMarker = car.GetPassengerMarker();
|
||||
if (passengerMarker.HasValue)
|
||||
{
|
||||
IEnumerable<string> loadInfo = car.PassengerCountString(passengerMarker).Split('/');
|
||||
//string item4 = CarPickable.PassengerString(car, passengerMarker.Value);
|
||||
string val = TextSprites.PiePercent(float.Parse(loadInfo.First()), float.Parse(loadInfo.Last())) + $" {car.PassengerCountString(passengerMarker)} Passengers";
|
||||
tagCallout.callout.Text = tagCallout.callout.Text.Contains("Empty") ? tagCallout.callout.Text.Replace("Empty", val) : tagCallout.callout.Text + $"\n{val}";
|
||||
}
|
||||
}
|
||||
|
||||
tagCallout.callout.Title =
|
||||
(car.CarAndEndGearIssue(), car.EndAirSystemIssue(), car.HandbrakeApplied()) switch
|
||||
tags.Any() switch
|
||||
{
|
||||
(true, _, _) => $"{tagCallout.callout.Title}{tagTitleAndIconDelimeter}{TextSprites.CycleWaybills}{TextSprites.HandbrakeWheel}".Replace("{0}", "2"),
|
||||
(_, true, _) => $"{tagCallout.callout.Title}{tagTitleAndIconDelimeter}{TextSprites.CycleWaybills}".Replace("{0}", "1"),
|
||||
(_, _, true) => $"{tagCallout.callout.Title}{tagTitleAndIconDelimeter}{TextSprites.HandbrakeWheel}".Replace("{0}", "1"),
|
||||
true => $"{tagCallout.callout.Title}{tagTitleAndIconDelimeter}{string.Join("", tags)}".Replace("{0}", tags.Count().ToString()),
|
||||
_ => car.DisplayName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModelCarExtensions
|
||||
{
|
||||
public static bool EndAirSystemIssue(this Model.Car car)
|
||||
{
|
||||
bool AEndAirSystemIssue = car[Car.LogicalEnd.A].IsCoupled && !car[Car.LogicalEnd.A].IsAirConnectedAndOpen;
|
||||
bool BEndAirSystemIssue = car[Car.LogicalEnd.B].IsCoupled && !car[Car.LogicalEnd.B].IsAirConnectedAndOpen;
|
||||
bool EndAirSystemIssue = AEndAirSystemIssue || BEndAirSystemIssue;
|
||||
return EndAirSystemIssue;
|
||||
}
|
||||
|
||||
public static bool HandbrakeApplied(this Model.Car car) =>
|
||||
car.air.handbrakeApplied;
|
||||
|
||||
public static bool CarOrEndGearIssue(this Model.Car car) =>
|
||||
car.EndAirSystemIssue() || car.HandbrakeApplied();
|
||||
|
||||
public static bool CarAndEndGearIssue(this Model.Car car) =>
|
||||
car.EndAirSystemIssue() && car.HandbrakeApplied();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,21 @@
|
||||
<!-- <MajorVersion>1</MajorVersion> -->
|
||||
<!-- <MinorVersion>0</MinorVersion> -->
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<Optimize>False</Optimize>
|
||||
<DebugType>portable</DebugType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Patches\BindingsWindow_Patches.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="mroc-cabeese.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="mroc-cabeese.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<GameAssembly Include="Assembly-CSharp" />
|
||||
<GameAssembly Include="Railloader.Interchange" />
|
||||
@@ -11,12 +26,23 @@
|
||||
<GameAssembly Include="0Harmony" />
|
||||
<GameAssembly Include="KeyValue.Runtime" />
|
||||
<GameAssembly Include="Definition" />
|
||||
<GameAssembly Include="Ops" />
|
||||
<GameAssembly Include="StrangeCustoms" />
|
||||
|
||||
<GameAssembly Include="Unity.InputSystem" />
|
||||
<GameAssembly Include="UnityEngine.CoreModule" />
|
||||
<GameAssembly Include="UnityEngine.UI" />
|
||||
<GameAssembly Include="UnityEngine.UIModule" />
|
||||
<GameAssembly Include="UnityEngine.InputLegacyModule" />
|
||||
<GameAssembly Include="UnityEngine.InputModule" />
|
||||
<GameAssembly Include="Unity.TextMeshPro" />
|
||||
<GameAssembly Include="Map.Runtime" />
|
||||
<GameAssembly Include="UnityEngine.PhysicsModule" />
|
||||
<GameAssembly Include="UnityEngine.ImageConversionModule" />
|
||||
<GameAssembly Include="UnityEngine.IMGUIModule" />
|
||||
|
||||
<GameAssembly Include="System.Net.Http" />
|
||||
<GameAssembly Include="Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Publicizer" Version="2.2.1">
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
using System.Runtime;
|
||||
using System;
|
||||
using Serilog;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweaksAndThings;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
|
||||
public Settings()
|
||||
{
|
||||
WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
|
||||
}
|
||||
|
||||
public Settings(
|
||||
List<WebhookSettings> webhookSettingsList,
|
||||
bool handBrakeAndAirTagModifiers
|
||||
)
|
||||
{
|
||||
this.WebhookSettingsList = webhookSettingsList;
|
||||
this.HandBrakeAndAirTagModifiers = handBrakeAndAirTagModifiers;
|
||||
}
|
||||
|
||||
public List<WebhookSettings> WebhookSettingsList;
|
||||
public bool HandBrakeAndAirTagModifiers;
|
||||
|
||||
internal void AddAnotherRow()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(WebhookSettingsList.Last().WebhookUrl))
|
||||
{
|
||||
WebhookSettingsList.Add(new());
|
||||
Log.Information($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WebhookSettings
|
||||
{
|
||||
public WebhookSettings() { }
|
||||
public WebhookSettings(
|
||||
bool webhookEnabled,
|
||||
string railroadMark,
|
||||
string webhookUrl
|
||||
)
|
||||
{
|
||||
this.WebhookEnabled = webhookEnabled;
|
||||
this.RailroadMark = railroadMark;
|
||||
this.WebhookUrl = webhookUrl;
|
||||
}
|
||||
|
||||
public bool WebhookEnabled = false;
|
||||
public string RailroadMark = string.Empty;
|
||||
public string WebhookUrl = string.Empty;
|
||||
}
|
||||
149
TweaksAndThings/Settings/Settings.cs
Normal file
149
TweaksAndThings/Settings/Settings.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using Serilog;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using UI.Builder;
|
||||
using Model;
|
||||
using RMROC451.TweaksAndThings.Extensions;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace RMROC451.TweaksAndThings;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
|
||||
public Settings()
|
||||
{
|
||||
WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
|
||||
EngineRosterFuelColumnSettings = new();
|
||||
}
|
||||
|
||||
public Settings(
|
||||
List<WebhookSettings> webhookSettingsList,
|
||||
bool handBrakeAndAirTagModifiers,
|
||||
RosterFuelColumnSettings engineRosterFuelColumnSettings,
|
||||
bool endGearHelpersRequirePayment,
|
||||
bool requireConsistCabooseForOilerAndHotboxSpotter,
|
||||
bool cabooseAllowsConsistInfo,
|
||||
bool cabooseRequiredForLocoTagOilIndication,
|
||||
bool servicingFundPenalty,
|
||||
bool safetyFirst,
|
||||
bool safetyFirstClientEnforce,
|
||||
CrewHourLoadMethod loadCrewHoursMethod,
|
||||
float cabeeseSearchRadiusFtInMeters,
|
||||
bool trainBrakeDisplayShowsColorsInCalloutMode
|
||||
)
|
||||
{
|
||||
WebhookSettingsList = webhookSettingsList;
|
||||
HandBrakeAndAirTagModifiers = handBrakeAndAirTagModifiers;
|
||||
EngineRosterFuelColumnSettings = engineRosterFuelColumnSettings;
|
||||
EndGearHelpersRequirePayment = endGearHelpersRequirePayment;
|
||||
RequireConsistCabooseForOilerAndHotboxSpotter = requireConsistCabooseForOilerAndHotboxSpotter;
|
||||
CabooseAllowsConsistInfo = cabooseAllowsConsistInfo;
|
||||
CabooseRequiredForLocoTagOilIndication = cabooseRequiredForLocoTagOilIndication;
|
||||
ServicingFundPenalty = servicingFundPenalty;
|
||||
SafetyFirst = safetyFirst;
|
||||
SafetyFirstClientEnforce = safetyFirstClientEnforce;
|
||||
LoadCrewHoursMethod = loadCrewHoursMethod;
|
||||
CabeeseSearchRadiusFtInMeters = cabeeseSearchRadiusFtInMeters;
|
||||
TrainBrakeDisplayShowsColorsInCalloutMode = trainBrakeDisplayShowsColorsInCalloutMode;
|
||||
}
|
||||
|
||||
public readonly UIState<string> _selectedTabState = new UIState<string>(null);
|
||||
public List<WebhookSettings>? WebhookSettingsList;
|
||||
public bool HandBrakeAndAirTagModifiers;
|
||||
public RosterFuelColumnSettings? EngineRosterFuelColumnSettings;
|
||||
public bool EndGearHelpersRequirePayment;
|
||||
public bool RequireConsistCabooseForOilerAndHotboxSpotter;
|
||||
public bool CabooseAllowsConsistInfo;
|
||||
public bool CabooseRequiredForLocoTagOilIndication;
|
||||
public bool ServicingFundPenalty;
|
||||
public bool SafetyFirst;
|
||||
public bool SafetyFirstClientEnforce;
|
||||
public CrewHourLoadMethod LoadCrewHoursMethod;
|
||||
public float CabeeseSearchRadiusFtInMeters;
|
||||
public bool TrainBrakeDisplayShowsColorsInCalloutMode;
|
||||
|
||||
internal void AddAnotherRow()
|
||||
{
|
||||
WebhookSettingsList ??= new[] { new WebhookSettings() }.ToList();
|
||||
if (!string.IsNullOrEmpty(WebhookSettingsList.OrderByDescending(wsl => wsl.WebhookUrl).Last().WebhookUrl))
|
||||
{
|
||||
WebhookSettingsList.Add(new());
|
||||
Log.Debug($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WebhookSettings
|
||||
{
|
||||
public WebhookSettings() { }
|
||||
public WebhookSettings(
|
||||
bool webhookEnabled,
|
||||
string railroadMark,
|
||||
string webhookUrl
|
||||
)
|
||||
{
|
||||
WebhookEnabled = webhookEnabled;
|
||||
RailroadMark = railroadMark;
|
||||
WebhookUrl = webhookUrl;
|
||||
}
|
||||
|
||||
public bool WebhookEnabled = false;
|
||||
public string RailroadMark = string.Empty;
|
||||
public string WebhookUrl = string.Empty;
|
||||
}
|
||||
|
||||
public class RosterFuelColumnSettings
|
||||
{
|
||||
public RosterFuelColumnSettings() { }
|
||||
public RosterFuelColumnSettings(
|
||||
bool engineRosterShowsFuelStatusAlways,
|
||||
EngineRosterFuelDisplayColumn engineRosterFuelStatusColumn
|
||||
)
|
||||
{
|
||||
this.EngineRosterShowsFuelStatusAlways = engineRosterShowsFuelStatusAlways;
|
||||
this.EngineRosterFuelStatusColumn = engineRosterFuelStatusColumn;
|
||||
}
|
||||
|
||||
public bool EngineRosterShowsFuelStatusAlways;
|
||||
public EngineRosterFuelDisplayColumn EngineRosterFuelStatusColumn;
|
||||
}
|
||||
|
||||
public static class SettingsExtensions
|
||||
{
|
||||
public static List<WebhookSettings> SanitizeEmptySettings(this IEnumerable<WebhookSettings>? settings)
|
||||
{
|
||||
List<WebhookSettings> output =
|
||||
settings?.Where(s => !string.IsNullOrEmpty(s.WebhookUrl))?.ToList() ??
|
||||
new();
|
||||
|
||||
output.Add(new());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(this TweaksAndThingsPlugin input) =>
|
||||
input?.IsEnabled ?? false;
|
||||
public static bool CabooseAllowsConsistInfo(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.CabooseAllowsConsistInfo ?? false;
|
||||
public static bool EndGearHelpersRequirePayment(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.EndGearHelpersRequirePayment ?? false;
|
||||
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.FindMyCabooseSansLoadRequirement();
|
||||
public static bool CabooseRequiredForLocoOilIndicator(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.CabooseRequiredForLocoTagOilIndication ?? false;
|
||||
public static bool ServiceFundPenalties(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.ServicingFundPenalty ?? false;
|
||||
public static bool SafetyFirst(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.SafetyFirst ?? false;
|
||||
public static bool SafetyFirstClientEnforce(this TweaksAndThingsPlugin input) =>
|
||||
input?.settings?.SafetyFirstClientEnforce ?? true;
|
||||
public static bool DayLoadCrewHours(this TweaksAndThingsPlugin input) =>
|
||||
(input?.settings?.LoadCrewHoursMethod ?? CrewHourLoadMethod.Tracks) == CrewHourLoadMethod.Daily;
|
||||
public static 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Railloader;
|
||||
using Serilog;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using UI.Builder;
|
||||
|
||||
namespace TweaksAndThings
|
||||
{
|
||||
public class TweaksAndThings : SingletonPluginBase<TweaksAndThings>, IUpdateHandler, IModTabHandler
|
||||
{
|
||||
private HttpClient client;
|
||||
internal HttpClient Client
|
||||
{
|
||||
get
|
||||
{
|
||||
if (client == null)
|
||||
client = new HttpClient();
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
internal Settings? settings { get; private set; } = null;
|
||||
readonly ILogger logger = Log.ForContext<TweaksAndThings>();
|
||||
IModdingContext moddingContext { get; set; }
|
||||
IModDefinition modDefinition { get; set; }
|
||||
|
||||
static TweaksAndThings()
|
||||
{
|
||||
Log.Information("Hello! Static Constructor was called!");
|
||||
}
|
||||
|
||||
public TweaksAndThings(IModdingContext moddingContext, IModDefinition self)
|
||||
{
|
||||
this.modDefinition = self;
|
||||
|
||||
this.moddingContext = moddingContext;
|
||||
|
||||
logger.Information("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version);
|
||||
|
||||
//moddingContext.RegisterConsoleCommand(new EchoCommand());
|
||||
|
||||
settings = moddingContext.LoadSettingsData<Settings>(self.Id);
|
||||
if (!settings.WebhookSettingsList?.Any() ?? true)
|
||||
{
|
||||
settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
|
||||
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
logger.Information("OnEnable() was called!");
|
||||
var harmony = new Harmony(modDefinition.Id);
|
||||
harmony.PatchCategory(modDefinition.Id.Replace(".",string.Empty));
|
||||
}
|
||||
|
||||
public override void OnDisable()
|
||||
{
|
||||
var harmony = new Harmony(modDefinition.Id);
|
||||
harmony.UnpatchAll(modDefinition.Id);
|
||||
Messenger.Default.Unregister(this);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
logger.Verbose("UPDATE()");
|
||||
}
|
||||
|
||||
public void ModTabDidOpen(UIPanelBuilder builder)
|
||||
{
|
||||
logger.Information("Daytime!");
|
||||
//WebhookUISection(ref builder);
|
||||
//builder.AddExpandingVerticalSpacer();
|
||||
WebhooksListUISection(ref builder);
|
||||
builder.AddExpandingVerticalSpacer();
|
||||
HandbrakesAndAnglecocksUISection(ref builder);
|
||||
}
|
||||
|
||||
private void HandbrakesAndAnglecocksUISection(ref UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddSection("Tag Callout Handbrake and Air System Helper", delegate (UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddField(
|
||||
"Enable Tag Updates",
|
||||
builder.AddToggle(
|
||||
() => settings?.HandBrakeAndAirTagModifiers ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() };
|
||||
settings.HandBrakeAndAirTagModifiers = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
)
|
||||
).Tooltip("Enable Tag Updates", $"Will add {TextSprites.CycleWaybills} to the car tag title having Air System issues. Also prepends {TextSprites.HandbrakeWheel} if there is a handbrake set.\n\nHolding Left Alt while tags are displayed only shows tag titles that have issues.");
|
||||
});
|
||||
}
|
||||
|
||||
private void WebhooksListUISection(ref UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddSection("Webhooks List", delegate (UIPanelBuilder builder)
|
||||
{
|
||||
for (int i = 1; i <= settings.WebhookSettingsList.Count; i++)
|
||||
{
|
||||
int z = i - 1;
|
||||
builder.AddSection($"Webhook {i}", delegate (UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddField(
|
||||
"Webhook Enabled",
|
||||
builder.AddToggle(
|
||||
() => settings?.WebhookSettingsList[z]?.WebhookEnabled ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() };
|
||||
settings.WebhookSettingsList[z].WebhookEnabled = enabled;
|
||||
settings.AddAnotherRow();
|
||||
builder.Rebuild();
|
||||
}
|
||||
)
|
||||
).Tooltip("Webhook Enabled", "Will parse the console messages and transmit to a Discord webhook.");
|
||||
|
||||
builder.AddField(
|
||||
"Reporting Mark",
|
||||
builder.HStack(delegate (UIPanelBuilder field)
|
||||
{
|
||||
field.AddInputField(
|
||||
settings?.WebhookSettingsList[z]?.RailroadMark,
|
||||
delegate (string railroadMark)
|
||||
{
|
||||
if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() };
|
||||
settings.WebhookSettingsList[z].RailroadMark = railroadMark;
|
||||
settings.AddAnotherRow();
|
||||
builder.Rebuild();
|
||||
}, characterLimit: GameStorage.ReportingMarkMaxLength).FlexibleWidth();
|
||||
})
|
||||
).Tooltip("Reporting Mark", "Reporting mark of the company this Discord webhook applies to..");
|
||||
|
||||
builder.AddField(
|
||||
"Webhook Url",
|
||||
builder.HStack(delegate (UIPanelBuilder field)
|
||||
{
|
||||
field.AddInputField(
|
||||
settings?.WebhookSettingsList[z]?.WebhookUrl,
|
||||
delegate (string webhookUrl)
|
||||
{
|
||||
if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList() };
|
||||
settings.WebhookSettingsList[z].WebhookUrl = webhookUrl;
|
||||
settings.AddAnotherRow();
|
||||
builder.Rebuild();
|
||||
}).FlexibleWidth();
|
||||
})
|
||||
).Tooltip("Webhook Url", "Url of Discord webhook to publish messages to.");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void ModTabDidClose()
|
||||
{
|
||||
logger.Information("Nighttime...");
|
||||
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
|
||||
}
|
||||
}
|
||||
}
|
||||
364
TweaksAndThings/TweaksAndThingsPlugin.cs
Normal file
364
TweaksAndThings/TweaksAndThingsPlugin.cs
Normal file
@@ -0,0 +1,364 @@
|
||||
// Ignore Spelling: RMROC
|
||||
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
using Game;
|
||||
using Game.Messages;
|
||||
using Game.State;
|
||||
using HarmonyLib;
|
||||
using Railloader;
|
||||
using RMROC451.TweaksAndThings.Commands;
|
||||
using RMROC451.TweaksAndThings.Enums;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using UI.Builder;
|
||||
using UnityEngine;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace RMROC451.TweaksAndThings;
|
||||
|
||||
public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>, IUpdateHandler, IModTabHandler
|
||||
{
|
||||
private HttpClient client;
|
||||
internal HttpClient Client
|
||||
{
|
||||
get
|
||||
{
|
||||
if (client == null)
|
||||
client = new HttpClient();
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
internal Settings? settings { get; private set; } = null;
|
||||
readonly ILogger logger = Log.ForContext<TweaksAndThingsPlugin>();
|
||||
IModdingContext moddingContext { get; set; }
|
||||
IModDefinition modDefinition { get; set; }
|
||||
|
||||
public string ModDirectory => modDefinition.Directory;
|
||||
|
||||
static TweaksAndThingsPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self)
|
||||
{
|
||||
this.modDefinition = self;
|
||||
|
||||
this.moddingContext = moddingContext;
|
||||
|
||||
logger.Debug("Hello! Constructor was called for {modId}/{modVersion}!", self.Id, self.Version);
|
||||
|
||||
moddingContext.RegisterConsoleCommand(new EchoCommand());
|
||||
|
||||
settings = moddingContext.LoadSettingsData<Settings>(self.Id) ?? new();
|
||||
}
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
var harmony = new Harmony(modDefinition.Id);
|
||||
harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty));
|
||||
}
|
||||
|
||||
public override void OnDisable()
|
||||
{
|
||||
var harmony = new Harmony(modDefinition.Id);
|
||||
harmony.UnpatchAll(modDefinition.Id);
|
||||
Messenger.Default.Unregister(this);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public void ModTabDidOpen(UIPanelBuilder builder)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
|
||||
if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new();
|
||||
|
||||
|
||||
settings.WebhookSettingsList =
|
||||
settings?.WebhookSettingsList.SanitizeEmptySettings();
|
||||
|
||||
builder.AddSection("Adjustments To Base Game", (UIPanelBuilder builder) => {
|
||||
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)
|
||||
{
|
||||
tabBuilder.AddTab("Caboose Mods", "cabooseUpdates", CabooseMods);
|
||||
tabBuilder.AddTab("UI", "rosterUi", UiUpdates);
|
||||
tabBuilder.AddTab("Webhooks", "webhooks", WebhooksListUISection);
|
||||
});
|
||||
}
|
||||
|
||||
private static string cabooseUse => "Caboose Use";
|
||||
private static string autoAiRequirment => "AutoAI Requirement";
|
||||
private static string locoConsistOilIndication => "Consist Oil Indication";
|
||||
private static float spacing => 2f;
|
||||
|
||||
private void CabooseMods(UIPanelBuilder builder)
|
||||
{
|
||||
//UI.GameInput
|
||||
//builder.AddField("Meow",
|
||||
// builder.AddInputBindingControl(
|
||||
|
||||
// )
|
||||
//)
|
||||
|
||||
// InputAction a = new InputAction("connectCarsAndGladhands", InputActionType.Button)
|
||||
|
||||
//var connectCarsAndGladhands = new InputAction("connectCarsAndGladhands");
|
||||
//connectCarsAndGladhands.AddCompositeBinding("connectCarsAndGladhandsComposite")
|
||||
// .With("modifier", "<Keyboard>/leftCtrl")
|
||||
// .With("modifier", "<Keyboard>/leftAlt");
|
||||
|
||||
//builder.AddField("meow", builder.AddInputBindingControl(connectCarsAndGladhands, conflict: true, ()=>))
|
||||
#region EndGearHelperCost
|
||||
builder.AddFieldToggle(
|
||||
cabooseUse,
|
||||
() => this.EndGearHelpersRequirePayment(),
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.EndGearHelpersRequirePayment = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Enable End Gear Helper Cost", @$"Will cost 1 minute of AI Brake Crew & Caboose Crew time per car in the consist when the new inspector buttons are utilized.
|
||||
|
||||
1.5x multiplier penalty to AI Brake Crew cost if no sufficiently crewed caboose nearby.
|
||||
|
||||
Caboose starts reloading `Crew Hours` at any Team or Repair track (no waybill), after being stationary for 30 seconds.
|
||||
|
||||
AutoOiler Update: 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).
|
||||
|
||||
AutoOiler Update: if `{cabooseUse}` & `{autoAiRequirment.Replace("\n", " ")}` checked, then when a caboose is present, the AutoOiler will repair hotboxes afer oiling them to 100%.
|
||||
|
||||
AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 - 30 seconds (Safety Is Everyone's Job)");
|
||||
#endregion
|
||||
|
||||
#region CabeeseLoadOptions
|
||||
if (this.EndGearHelpersRequirePayment())
|
||||
{
|
||||
var columns = Enum.GetValues(typeof(CrewHourLoadMethod)).Cast<CrewHourLoadMethod>().Select(i => i.ToString()).ToList();
|
||||
builder.Spacer(spacing);
|
||||
builder.AddField("Refill Option",
|
||||
builder.AddDropdown(
|
||||
columns,
|
||||
(int)(settings?.LoadCrewHoursMethod ?? CrewHourLoadMethod.Tracks),
|
||||
delegate (int column)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.LoadCrewHoursMethod = (CrewHourLoadMethod)column;
|
||||
builder.Rebuild();
|
||||
}
|
||||
)
|
||||
).Tooltip("Crew Hours Load Option", "Select whether you want to manually reload cabeese via:\n\ntrack method - (team/repair/passenger stop/interchange)\n\ndaily caboose top off - refill to 8h at new day.");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RequireCabeeseForOiler/HotboxDetection
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
autoAiRequirment,
|
||||
() => this.RequireConsistCabooseForOilerAndHotboxSpotter(),
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.RequireConsistCabooseForOilerAndHotboxSpotter = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("AI Hotbox\\Oiler Requires Caboose", $@"A caboose is required in the consist to check for Hotboxes and perform Auto Oiler, if checked.");
|
||||
#endregion
|
||||
|
||||
#region ShowLocomotiveConsistOilIndicator
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
locoConsistOilIndication,
|
||||
() => settings?.CabooseRequiredForLocoTagOilIndication ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.CabooseRequiredForLocoTagOilIndication = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip(locoConsistOilIndication, $@"A caboose is required in the consist to report the lowest oil level in the consist in the locomotive's tag & roster entry.");
|
||||
#endregion
|
||||
|
||||
#region SafetyFirst
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
"Safety First!",
|
||||
() => settings?.SafetyFirst ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.SafetyFirst = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Safety First", $@"On non-express timetabled consists, a caboose is required in the consist increase AE max speed > 20 in {Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Road)}/{Enum.GetName(typeof(AutoEngineerMode), AutoEngineerMode.Waypoint)} mode.");
|
||||
#endregion
|
||||
|
||||
#region SafetyFirstClient
|
||||
if (settings?.SafetyFirst ?? false)
|
||||
{
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
"Safety First! (Enforce Client Speed Restrictions)",
|
||||
() => settings?.SafetyFirstClientEnforce ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.SafetyFirstClientEnforce = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Safety First! (Enforce Client Speed Restrictions)", $@"Enforce cabeese dominance on clients; uncheck to allow clients to override the 20mph restriction.");
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
private void UiUpdates(UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddFieldToggle(
|
||||
"Enable Tag Updates",
|
||||
() => settings?.HandBrakeAndAirTagModifiers ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.HandBrakeAndAirTagModifiers = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Enable Tag Updates", $@"Will suffix tag title with:
|
||||
{TextSprites.CycleWaybills} if Air System issue.
|
||||
{TextSprites.HandbrakeWheel} if there is a handbrake set.
|
||||
{TextSprites.Hotbox} if a hotbox.");
|
||||
|
||||
builder.Spacer(spacing);
|
||||
builder.AddFieldToggle(
|
||||
"Debt Allowance",
|
||||
() => settings?.ServicingFundPenalty ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.ServicingFundPenalty = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).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);
|
||||
}
|
||||
|
||||
private void EngineRosterShowsFuelStatusUISection(UIPanelBuilder builder)
|
||||
{
|
||||
var columns = Enum.GetValues(typeof(EngineRosterFuelDisplayColumn)).Cast<EngineRosterFuelDisplayColumn>().Select(i => i.ToString()).ToList();
|
||||
builder.AddSection("Fuel Display in Engine Roster", delegate (UIPanelBuilder builder)
|
||||
{
|
||||
builder.Spacer(spacing);
|
||||
builder.AddField(
|
||||
"Enable",
|
||||
builder.AddDropdown(columns, (int)(settings?.EngineRosterFuelColumnSettings?.EngineRosterFuelStatusColumn ?? EngineRosterFuelDisplayColumn.None),
|
||||
delegate (int column)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.EngineRosterFuelColumnSettings.EngineRosterFuelStatusColumn = (EngineRosterFuelDisplayColumn)column;
|
||||
builder.Rebuild();
|
||||
}
|
||||
)
|
||||
).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(
|
||||
"Always Visible?",
|
||||
() => settings?.EngineRosterFuelColumnSettings?.EngineRosterShowsFuelStatusAlways ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.EngineRosterFuelColumnSettings.EngineRosterShowsFuelStatusAlways = enabled;
|
||||
builder.Rebuild();
|
||||
}
|
||||
).Tooltip("Fuel Display in Engine Roster Always Visible", $"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.");
|
||||
});
|
||||
}
|
||||
|
||||
private void WebhooksListUISection(UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddSection("Webhooks List", delegate (UIPanelBuilder builder)
|
||||
{
|
||||
for (int i = 1; i <= settings.WebhookSettingsList.Count; i++)
|
||||
{
|
||||
int z = i - 1;
|
||||
builder.AddSection($"Webhook {i}", delegate (UIPanelBuilder builder)
|
||||
{
|
||||
builder.AddField(
|
||||
"Webhook Enabled",
|
||||
builder.AddToggle(
|
||||
() => settings?.WebhookSettingsList[z]?.WebhookEnabled ?? false,
|
||||
delegate (bool enabled)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.WebhookSettingsList[z].WebhookEnabled = enabled;
|
||||
settings.AddAnotherRow();
|
||||
builder.Rebuild();
|
||||
}
|
||||
)
|
||||
).Tooltip("Webhook Enabled", "Will parse the console messages and transmit to a Discord webhook.");
|
||||
|
||||
builder.AddField(
|
||||
"Reporting Mark",
|
||||
builder.HStack(delegate (UIPanelBuilder field)
|
||||
{
|
||||
field.AddInputField(
|
||||
settings?.WebhookSettingsList[z]?.RailroadMark,
|
||||
delegate (string railroadMark)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.WebhookSettingsList[z].RailroadMark = railroadMark;
|
||||
settings.AddAnotherRow();
|
||||
builder.Rebuild();
|
||||
}, characterLimit: GameStorage.ReportingMarkMaxLength).FlexibleWidth();
|
||||
})
|
||||
).Tooltip("Reporting Mark", "Reporting mark of the company this Discord webhook applies to..");
|
||||
|
||||
builder.AddField(
|
||||
"Webhook Url",
|
||||
builder.HStack(delegate (UIPanelBuilder field)
|
||||
{
|
||||
field.AddInputField(
|
||||
settings?.WebhookSettingsList[z]?.WebhookUrl,
|
||||
delegate (string webhookUrl)
|
||||
{
|
||||
if (settings == null) settings = new();
|
||||
settings.WebhookSettingsList[z].WebhookUrl = webhookUrl;
|
||||
settings.AddAnotherRow();
|
||||
builder.Rebuild();
|
||||
}).FlexibleWidth();
|
||||
})
|
||||
).Tooltip("Webhook Url", "Url of Discord webhook to publish messages to.");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void ModTabDidClose()
|
||||
{
|
||||
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
|
||||
}
|
||||
}
|
||||
23
TweaksAndThings/mroc-cabeese.json
Normal file
23
TweaksAndThings/mroc-cabeese.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"objects": [
|
||||
{
|
||||
"$find": [
|
||||
{
|
||||
"path": "definition.archetype",
|
||||
"value": "Caboose"
|
||||
}
|
||||
],
|
||||
"definition": {
|
||||
"loadSlots": [
|
||||
{
|
||||
"$add": {
|
||||
"maximumCapacity": 8,
|
||||
"loadUnits": "Quantity",
|
||||
"requiredLoadIdentifier": "crew-hours"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
12
updates.json
Normal file
12
updates.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"RMROC451.TweaksAndThings": {
|
||||
"version": "1.0.0",
|
||||
"changelog": [
|
||||
{
|
||||
"version": "*",
|
||||
"date": "2024-07-26T14:31:49.0948925Z",
|
||||
"desc": "https://github.com/rmroc451/TweaksAndThings/releases"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user