diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ce7f30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,399 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +/TweaksAndThings/Commands/EchoCommand.cs diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..fe5e246 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,24 @@ + + + + net48 + latest + false + latest + false + enable + + + + none + false + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..4f08c7c --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,71 @@ + + + + $([System.IO.Directory]::GetDirectories(`$(GameDir)`, `*_Data`)[0])\Managed + + + $(GameDir)/Mods/$(AssemblyName) + $(GameModDir)/ + + + + + 0 + 1 + 0 + $(MajorVersion).$(MinorVersion).$(PatchVersion) + $(AssemblyVersion) + $(AssemblyVersion) + + + + + + $(GameManagedDir)\%(Identity).dll + $(GameManagedDir)\%(Identity).exe + false + + + + + + + + + + $(OutputPath)/Mods + $(ModsDirectory)/$(AssemblyName) + + + ../bin + $(PublishPath)/$(AssemblyName)_$(AssemblyVersion).zip + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a005595 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/) [![Buymeacoffee](https://badgen.net/badge/icon/buymeacoffee?icon=buymeacoffee&label)](https://ko-fi.com/rmroc451) +# RMROC451's Tweaks and Thinks +Please Read this **ENTIRE** README before continuing. + +This is a mod for Railroader which is available on Steam. + +This mod requires Railloader by Zamu. + +## Usage +1. Download and install Railloader by Zamu from here: https://railroader.stelltis.ch/ + * Verify you have a Mods folder in the root of your railroader directory. If You do NOT have a Mods folder, you didn't complete this step successfully. +2. Download the `RMROC451.TweaksAndThings.x.x.zip` from the Releases Page of this repo. +3. There are 2 ways to install this mod. + * ONLY DO ONE OF THE BELOW STEPS, NOT BOTH + 1. Open the zip folder, select the entire contents of the folder and drag the contents to the ROOT Railroader directory. + * this should put a `RMROC451.TweaksAndThings` folder into the Mods folder created in step 1 + 2. Drag the zip file onto Railloader.exe and have Railloader install the mod. + * as with option 1, this will put a `RMROC451.TweaksAndThings` folder into the Mods folder created in step 1 + * If you DO NOT have the `RMROC451.TweaksAndThings` folder in the Mods folder after completing ONE of the above steps, you didn't complete the step successfully. +3. Run the game and enjoy all of you interchange tracks being used. + +## 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. +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. + +## FAQ +### 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` + +### 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 + +### RMROC451 TaDo +- ![Work In Progress](https://img.shields.io/badge/Status%3F-Work%20In%20Progress-green.svg) + - [ ] WebhookNotifier + - [X] Move base webclient calling to new WebhookNotifier + - [X] Extend WebhookNotifier in DiscordNotifier, to do formating of payload only. + - [X] Add Settings UI Elements for webhook url + - [ ] create thread per host || save game name || nearest city || locomotive?? + - [X] store in a settings file per host & save game name? + - [ ] inspector button(s) + - [X] auto connect glad hands/angle cocks + - [X] release/set car brake? + - [ ] Cost Ideas + - [ ] $$$ based on usage per day? or flat $5 per use? + - [ ] Caboose only? + - [ ] detect car having a nearby caboose to use? `UpdateCarsNearbyPlayer` but for car. + - [ ] add crew to caboose (similar to engine service), $25/day to fill up, must have crew to use and caboose near cars. + - [ ] crew number modifier for how many cars away from caboose it can effectively work? + - [ ] crew need to refill at a station? +- ![Pending](https://img.shields.io/badge/Status%3F-Pending-yellow.svg) + - [ ] Camera offset to coupler + - [ ] shift + 9/0 + - [ ] finance exporter/discord report at end of day for Game.State.Ledger + +*Special thanks and credit to Zamu for creating Railloader and for help with making the mod a bit more robust.* \ No newline at end of file diff --git a/TweaksAndThings.sln b/TweaksAndThings.sln new file mode 100644 index 0000000..34f66e4 --- /dev/null +++ b/TweaksAndThings.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +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 + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Paths.user = Paths.user + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RMROC451.TweaksAndThings", "TweaksAndThings\RMROC451.TweaksAndThings.csproj", "{59BB5A3F-C26B-4E2D-A657-94C562449365}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59BB5A3F-C26B-4E2D-A657-94C562449365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59BB5A3F-C26B-4E2D-A657-94C562449365}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59BB5A3F-C26B-4E2D-A657-94C562449365}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59BB5A3F-C26B-4E2D-A657-94C562449365}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C433EDB7-015A-4472-9FA4-AACF5926FC48} + EndGlobalSection +EndGlobal diff --git a/TweaksAndThings/Definition.json b/TweaksAndThings/Definition.json new file mode 100644 index 0000000..092b2f5 --- /dev/null +++ b/TweaksAndThings/Definition.json @@ -0,0 +1,13 @@ +{ + "manifestVersion": 1, + "id": "RMROC451.TweaksAndThings", + "name": "RMROC451's Tweaks and Things", + "version": "$(AssemblyVersion)", + "requires": [ + { + "id": "railloader", + "notBefore": "1.8.1" + } + ], + "assemblies": [ "RMROC451.TweaksAndThings" ] +} \ No newline at end of file diff --git a/TweaksAndThings/Enums/MrocHelperType.cs b/TweaksAndThings/Enums/MrocHelperType.cs new file mode 100644 index 0000000..bdca199 --- /dev/null +++ b/TweaksAndThings/Enums/MrocHelperType.cs @@ -0,0 +1,7 @@ +namespace TweaksAndThings.Enums; + +public enum MrocHelperType +{ + Handbrake, + GladhandAndAnglecock +} diff --git a/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs new file mode 100644 index 0000000..f85411b --- /dev/null +++ b/TweaksAndThings/Patches/CarInspector_PopulateCarPanel_Patch.cs @@ -0,0 +1,100 @@ +using GalaSoft.MvvmLight.Messaging; +using Game; +using Game.Events; +using Game.Messages; +using Game.State; +using HarmonyLib; +using KeyValue.Runtime; +using Network; +using Network.Messages; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Railloader; +using Serilog; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Remoting.Messaging; +using TweaksAndThings.Enums; +using UI.Builder; +using UI.CarInspector; +using WorldStreamer2; +using static Model.Car; +using static Unity.IO.LowLevel.Unsafe.AsyncReadManagerMetrics; + +namespace TweaksAndThings.Patches; + +[HarmonyPatch(typeof(CarInspector))] +[HarmonyPatch(nameof(CarInspector.PopulateCarPanel), typeof(UIPanelBuilder))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +public class CarInspector_PopulateCarPanel_Patch +{ + private static IEnumerable ends = Enum.GetValues(typeof(LogicalEnd)).Cast(); + + /// + /// If a caboose inspector is opened, it will auto set Anglecocks, gladhands and handbrakes + /// + /// + /// + /// + private static bool Prefix(CarInspector __instance, UIPanelBuilder builder) + { + + TweaksAndThings tweaksAndThings = SingletonPluginBase.Shared; + if (!tweaksAndThings.IsEnabled) return true; + + builder.HStack(delegate (UIPanelBuilder hstack) + { + hstack.AddButtonCompact("Handbrakes", delegate + { + MrocConsistHelper(__instance._car, MrocHelperType.Handbrake); + }).Tooltip("Release Consist Handbrakes", "Iterates over each car in this consist and releases handbrakes."); + if (StateManager.IsHost || true) + { + hstack.AddButtonCompact("Air Lines", delegate + { + MrocConsistHelper(__instance._car, MrocHelperType.GladhandAndAnglecock); + }).Tooltip("Connect Consist Air Lines", "Iterates over each car in this consist and connects gladhands and opens anglecocks."); + } + }); + + return true; + } + + public static void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType) + { + IEnumerable consist = car.EnumerateCoupled(LogicalEnd.A); + + Log.Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}"); + switch (mrocHelperType) + { + case MrocHelperType.Handbrake: + consist.Do(c => c.SetHandbrake(false)); + 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)); + } + + }) + ); + break; + } + } +} diff --git a/TweaksAndThings/Patches/ExpandedConsole_Patch.cs b/TweaksAndThings/Patches/ExpandedConsole_Patch.cs new file mode 100644 index 0000000..7036b6b --- /dev/null +++ b/TweaksAndThings/Patches/ExpandedConsole_Patch.cs @@ -0,0 +1,114 @@ +using Game; +using Game.State; +using HarmonyLib; +using Helpers; +using Newtonsoft.Json; +using Railloader; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using Track.Signals.Panel; +using UI.Console; + +namespace TweaksAndThings.Patches; + +[HarmonyPatch(typeof(ExpandedConsole))] +[HarmonyPatch(nameof(ExpandedConsole.Add))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +public class 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); + } + + static void SendMs(ref UI.Console.Console.Entry entry) + { + try + { + TweaksAndThings tweaksAndThings = SingletonPluginBase.Shared; + StateManager shared = StateManager.Shared; + GameStorage gameStorage = shared.Storage; + WebhookSettings settings = tweaksAndThings.settings.WebhookSettingsList.FirstOrDefault(ws => ws.RailroadMark == gameStorage.RailroadMark); + + Log.Information(entry.Text); + + if ( + settings != null && + settings.WebhookEnabled && + !string.IsNullOrEmpty(settings.WebhookUrl) && + settings.RailroadMark == gameStorage.RailroadMark + ) + { + var t = new Regex("car:(.*)\""); + + var carId = t.IsMatch(entry.Text) ? Regex.Match(entry.Text, "car:(.*?)\"").Groups[1].Captures[0].ToString() : string.Empty; + Model.Car? car = TrainController.Shared.CarForString(carId); + Log.Information($"|{carId}| {car?.IsLocomotive}"); + bool engineInMessage = car?.IsLocomotive ?? false; + var image = engineInMessage ? + new + { + url = string.Empty + } : + null; + + if (engineInMessage) + { + CTCPanelMarkerManager cTCPanelMarkerManager = UnityEngine.Object.FindObjectOfType(); + + 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) + { + color = CTCPanelMarker.InferColorFromText(marker.TooltipInfo.Text).HexString().Replace("#", string.Empty); + } + image = new + { + url = $"https://img.shields.io/badge/{car.DisplayName.Replace(" ", "%20")}-%20-{color}.png" + }; + + } + + var SuccessWebHook = new + { + username = $"[{gameStorage.RailroadMark}] {gameStorage.RailroadName}", + embeds = new List + { + new + { + description= Regex.Replace(entry.Text, "<.*?>", "").Replace(": ", "\n"), + timestamp=DateTime.UtcNow, + image + }, + } + }; + string EndPoint = settings.WebhookUrl; + Log.Information(JsonConvert.SerializeObject(SuccessWebHook)); + + var content = new StringContent(JsonConvert.SerializeObject(SuccessWebHook), Encoding.UTF8, "application/json"); + + tweaksAndThings.Client.PostAsync(EndPoint, content).Wait(); + + } + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + entry.Text += ex.ToString(); + } + } + + public static GameDateTime RealNow() + { + var now = DateTime.Now; + return new GameDateTime(0, now.Hour + now.Minute / 60f); + } +} diff --git a/TweaksAndThings/Patches/TagConroller_UpdateTag_Patch.cs b/TweaksAndThings/Patches/TagConroller_UpdateTag_Patch.cs new file mode 100644 index 0000000..38d85e6 --- /dev/null +++ b/TweaksAndThings/Patches/TagConroller_UpdateTag_Patch.cs @@ -0,0 +1,85 @@ +using HarmonyLib; +using JetBrains.Annotations; +using Model; +using Model.AI; +using Model.OpsNew; +using Railloader; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Track; +using TweaksAndThings; +using UI; +using UI.Builder; +using UI.CarInspector; +using UI.Tags; +using UnityEngine; +using static Model.Car; +using tat = TweaksAndThings.TweaksAndThings; + +namespace TweaksAndThings.Patches; + +[HarmonyPatch(typeof(TagController))] +[HarmonyPatch(nameof(TagController.UpdateTag), typeof(Car), typeof(TagCallout), typeof(OpsController))] +[HarmonyPatchCategory("RMROC451TweaksAndThings")] +public class TagConroller_UpdateTag_Patch +{ + private static void Postfix(Car car, TagCallout tagCallout) + { + TagController tagController = UnityEngine.Object.FindObjectOfType(); + TweaksAndThings tweaksAndThings = SingletonPluginBase.Shared; + tagCallout.callout.Title = $"{car.DisplayName}"; + + if (!tweaksAndThings.IsEnabled || !tweaksAndThings.settings.HandBrakeAndAirTagModifiers) + { + return; + } + + tagCallout.gameObject.SetActive( + tagCallout.gameObject.activeSelf && + (!GameInput.IsShiftDown || (GameInput.IsShiftDown && car.CarOrEndGearIssue())) + ); + if (tagCallout.gameObject.activeSelf && GameInput.IsShiftDown && car.CarOrEndGearIssue()) { + + tagController.ApplyImageColor(tagCallout, Color.black); + } + + if (car.CarAndEndGearIssue()) + { + tagCallout.callout.Title = + $"{tagCallout.callout.Title}\n{TextSprites.CycleWaybills}{TextSprites.HandbrakeWheel}"; + } + else if (car.EndAirSystemIssue()) + tagCallout.callout.Title = + $"{tagCallout.callout.Title}\n{TextSprites.CycleWaybills}"; + else if (car.HandbrakeApplied()) + tagCallout.callout.Title = + $"{tagCallout.callout.Title}\n{TextSprites.HandbrakeWheel}"; + + + return; + } +} + +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(); +} diff --git a/TweaksAndThings/RMROC451.TweaksAndThings.csproj b/TweaksAndThings/RMROC451.TweaksAndThings.csproj new file mode 100644 index 0000000..47d737c --- /dev/null +++ b/TweaksAndThings/RMROC451.TweaksAndThings.csproj @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + D:\SteamLibrary\steamapps\common\Railroader\Mods\ForYourConvenience\ForYourConvenience.dll + + + diff --git a/TweaksAndThings/Settings.cs b/TweaksAndThings/Settings.cs new file mode 100644 index 0000000..2b0dcc2 --- /dev/null +++ b/TweaksAndThings/Settings.cs @@ -0,0 +1,53 @@ +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() + {} + + public Settings( + WebhookSettings webhookSettings, + List webhookSettingsList, + bool handBrakeAndAirTagModifiers + ) + { + this.WebhookSettingsList = webhookSettingsList; + this.HandBrakeAndAirTagModifiers = handBrakeAndAirTagModifiers; + } + + public List WebhookSettingsList; + public bool HandBrakeAndAirTagModifiers; + + internal void AddAnotherRow() + { + //if (!string.IsNullOrEmpty(WebhookSettingsList.Last().WebhookUrl)) WebhookSettingsList.Add(new()); + } +} + +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; +} \ No newline at end of file diff --git a/TweaksAndThings/TweaksAndThings.cs b/TweaksAndThings/TweaksAndThings.cs new file mode 100644 index 0000000..2b54adf --- /dev/null +++ b/TweaksAndThings/TweaksAndThings.cs @@ -0,0 +1,166 @@ +using GalaSoft.MvvmLight.Messaging; +using Game.Messages; +using Game.State; +using HarmonyLib; +using Newtonsoft.Json.Linq; +using Railloader; +using Serilog; +using Serilog.Debugging; +using System.Linq; +using System.Net.Http; +using TweaksAndThings.Commands; +using UI.Builder; +using UI.CarInspector; + +namespace TweaksAndThings +{ + public class TweaksAndThings : SingletonPluginBase, 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(); + 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(self.Id); + } + + 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 Shift 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()); + } + } +}