Compare commits

...

22 Commits

Author SHA1 Message Date
0adcbfa787 Merge pull request #16 from rmroc451/12-add-fuel-fill-level-sprite-to-engine-roster
12 add fuel fill level sprite to engine roster & 14 fix locomotive set hand brake in consist.
2024-06-20 22:40:16 -05:00
715629394d getting a little bit of logging around set hand brakes. 2024-06-20 22:10:47 -05:00
1ea3d9917d spell checking, fixing #14 and some other pieces needed for #14. 2024-06-20 21:41:56 -05:00
d780b075dc #12 updated the ModTabDidOpen logic for the new column section. 2024-06-20 17:44:45 -05:00
d0e2c3f99e #12 add configurable column for fuel status tracking in engine roster 2024-06-20 17:41:50 -05:00
8ae319fbee fix #2 for reals this time? 2024-06-18 11:03:01 -05:00
90d4262890 increment assembly verions to 0.1.3 2024-06-18 08:42:44 -05:00
a74c004fd4 #2 fixing inverted boolean logic 2024-06-18 08:41:00 -05:00
7a9359ec7d Merge pull request #11 from rmroc451/3-tags-now-overlap-with-icons-when-titles-are-long
fix #3 tags now overlap with icons when titles are long
2024-06-18 00:27:53 -05:00
9d9cc96283 resolve #3 overlapping callout titles with the right floating icons. Switching from SHIFT to LEFT ALT key for hiding car callouts that have no issues. 2024-06-18 00:26:20 -05:00
d7b846a8b2 Merge remote-tracking branch 'origin/main' into 3-tags-now-overlap-with-icons-when-titles-are-long 2024-06-17 23:06:46 -05:00
9cf5998a13 Merge pull request #10 from rmroc451/2-investigate-settings-instantiation-issues
fix #2
2024-06-17 23:01:09 -05:00
ffe6d344d7 Merge pull request #9 from rmroc451/1-hstackrebuildoninterval-misses-button-clicks
fix #1
2024-06-17 22:57:37 -05:00
7a21c6472e removed hStack.RebuildOnInterval in favor of builder.AddObserver for each car in inspector car's consist. 2024-06-17 22:55:51 -05:00
a9e6580258 work in progress attempts to resolve text overlapping on car tag titles. 2024-06-17 20:54:05 -05:00
b12f3b07c4 initial fixes for settings instantiation issue 2024-06-17 20:52:31 -05:00
371cfc41c8 #2 remove ex.tostring from appending to entry.text and spamming console. 2024-06-17 18:50:01 -05:00
180b63a41d Update README.md
moved todo list to issues labeled as enhancements.
2024-06-17 10:19:26 -05:00
b3546f433d Patch inspector button recalc based on consist brake || air system changes 2024-06-16 22:13:03 -05:00
ced72acf7c Update README.md 2024-06-16 16:29:54 -05:00
3d1fa908b9 Delete LICENSE.txt 2024-06-16 16:28:50 -05:00
1de82c130b super awkward grammar issue :D 2024-06-16 15:27:25 -05:00
16 changed files with 555 additions and 374 deletions

View File

@@ -12,7 +12,7 @@
<PropertyGroup Condition="'$(AssemblyVersion)' == '' OR '$(MajorVersion)' != '' OR '$(MinorVersion)' != ''">
<MajorVersion Condition="'$(MajorVersion)' == ''">0</MajorVersion>
<MinorVersion Condition="'$(MinorVersion)' == ''">1</MinorVersion>
<PatchVersion Condition="'$(PatchVersion)' == ''">0</PatchVersion>
<PatchVersion Condition="'$(PatchVersion)' == ''">5</PatchVersion>
<AssemblyVersion>$(MajorVersion).$(MinorVersion).$(PatchVersion)</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<ProductVersion>$(AssemblyVersion)</ProductVersion>

View File

@@ -1,21 +0,0 @@
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.

View File

@@ -1,5 +1,5 @@
[![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
# RMROC451's Tweaks and Things
Please Read this **ENTIRE** README before continuing.
This is a mod for Railroader which is available on Steam.
@@ -17,10 +17,10 @@ This mod requires Railloader by Zamu.
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.
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.
@@ -29,13 +29,13 @@ This mod requires Railloader by Zamu.
**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.
* Gives two buttons that will scan the current car's connections, including the whole consist, and automatically release hand brakes, set anglecocks and connect glad hands.
2. Car Tag Updates
* Shows an indication of which cars in the FOV have Air System or Handbrake issues.
* **hold SHIFT** to only show the tags in the FOV for cars with an issue!
3. Discord Webhooks
* Allows the console messages to post to a discord webhook. useful for those wanting to keep an eye on 24/7 hosted saves.
* Locomotive messages grab the locomotive `Ident.RoadNumber` and check the `CTC Panel Markers` if they exist. If found, they will use the red/green color and embed the locmotive as an image in the message. If no marker is found, it defaults to blue.
* Locomotive messages grab the locomotive `Ident.RoadNumber` and check the `CTC Panel Markers` if they exist. If found, they will use the red/green color and embed the locomotive as an image in the message. If no marker is found, it defaults to blue.
* Currently, One person per server should have this per discord webhook, otherwise you will get duplicate messages to the webhook.
* **Multiple hooks**: Allows for many different webhooks per client to be setup, and filtered to the `Ident.ReportingMark` so you can get messages to different hooks based on what save/server you are playing on.
* **Customizable** from the in-game Railloader settings, find `RMROC451.TweaksAndThings`
@@ -46,27 +46,4 @@ 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.*
*Special thanks and credit to Zamu for creating Railloader and for help with making the mod a bit more robust.*

View File

@@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Paths.user = Paths.user
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RMROC451.TweaksAndThings", "TweaksAndThings\RMROC451.TweaksAndThings.csproj", "{59BB5A3F-C26B-4E2D-A657-94C562449365}"

View File

@@ -0,0 +1,9 @@
namespace RMROC451.TweaksAndThings.Enums;
public enum EngineRosterFuelDisplayColumn
{
None,
Engine,
Crew,
Status
}

View File

@@ -1,4 +1,4 @@
namespace TweaksAndThings.Enums;
namespace RMROC451.TweaksAndThings.Enums;
public enum MrocHelperType
{

View File

@@ -0,0 +1,40 @@
using Model;
using System;
namespace RMROC451.TweaksAndThings.Extensions
{
public static class Car_Extensions
{
public static bool EndAirSystemIssue(this 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();
public static Car? DetermineFuelCar(this Car engine, bool returnEngineIfNull = false)
{
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;
}
}
}

View File

@@ -1,29 +1,19 @@
using GalaSoft.MvvmLight.Messaging;
using Game;
using Game.Events;
using Game.Messages;
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 RMROC451.TweaksAndThings.Enums;
using RMROC451.TweaksAndThings.Extensions;
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;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(CarInspector))]
[HarmonyPatch(nameof(CarInspector.PopulateCarPanel), typeof(UIPanelBuilder))]
@@ -33,7 +23,7 @@ public class CarInspector_PopulateCarPanel_Patch
private 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>
@@ -44,17 +34,24 @@ public class CarInspector_PopulateCarPanel_Patch
TweaksAndThings tweaksAndThings = SingletonPluginBase<TweaksAndThings>.Shared;
if (!tweaksAndThings.IsEnabled) return true;
var consist = __instance._car.EnumerateCoupled(LogicalEnd.A);
builder = AddCarConsistRebuildObservers(builder, consist);
builder.HStack(delegate (UIPanelBuilder hstack)
{
hstack.AddButtonCompact("Handbrakes", delegate
var buttonName = $"{(consist.Any(c => c.HandbrakeApplied()) ? "Release " : "Set ")} {TextSprites.HandbrakeWheel}";
hstack.AddButtonCompact(buttonName, 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.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("Air Lines", delegate
hstack.AddButtonCompact("Connect Air Lines", delegate
{
MrocConsistHelper(__instance._car, MrocHelperType.GladhandAndAnglecock);
hstack.Rebuild();
}).Tooltip("Connect Consist Air Lines", "Iterates over each car in this consist and connects gladhands and opens anglecocks.");
}
});
@@ -62,6 +59,48 @@ public class CarInspector_PopulateCarPanel_Patch
return true;
}
private static UIPanelBuilder AddCarConsistRebuildObservers(UIPanelBuilder builder, IEnumerable<Model.Car> consist)
{
foreach (Model.Car car in consist)
{
builder = AddObserver(builder, car, PropertyChange.KeyForControl(PropertyChange.Control.Handbrake));
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)));
}
}
return builder;
}
private static UIPanelBuilder AddObserver(UIPanelBuilder builder, Model.Car car, string key)
{
builder.AddObserver(
car.KeyValueObject.Observe(
key,
delegate (Value value)
{
builder.Rebuild();
},
false
)
);
return builder;
}
//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 void MrocConsistHelper(Model.Car car, MrocHelperType mrocHelperType)
{
IEnumerable<Model.Car> consist = car.EnumerateCoupled(LogicalEnd.A);
@@ -70,7 +109,17 @@ public class CarInspector_PopulateCarPanel_Patch
switch (mrocHelperType)
{
case MrocHelperType.Handbrake:
consist.Do(c => c.SetHandbrake(false));
if (consist.Any(c => c.HandbrakeApplied()))
{
consist.Do(c => c.SetHandbrake(false));
} else
{
TrainController tc = UnityEngine.Object.FindObjectOfType<TrainController>();
consist = consist.Where(c => c.DetermineFuelCar(true) != null);
Log.Information($"{car} => {mrocHelperType} => {string.Join("/", consist.Select(c => c.ToString()))}");
//when ApplyHandbrakesAsNeeded is called, and the consist contains an engine, it stops applying brakes.
tc.ApplyHandbrakesAsNeeded(consist.ToList(), PlaceTrainHandbrakes.Automatic);
}
break;
case MrocHelperType.GladhandAndAnglecock:

View File

@@ -0,0 +1,100 @@
using HarmonyLib;
using Model;
using Model.Definition.Data;
using Model.OpsNew;
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;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(EngineRosterRow))]
[HarmonyPatch(nameof(EngineRosterRow.Refresh))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
public class EngineRosterRow_Refresh_Patch
{
public static void Postfix(EngineRosterRow __instance)
{
TweaksAndThings? tweaksAndThings = SingletonPluginBase<TweaksAndThings>.Shared;
RosterFuelColumnSettings? rosterFuelColumnSettings = tweaksAndThings?.settings?.EngineRosterFuelColumnSettings;
if (tweaksAndThings == null ||
rosterFuelColumnSettings == null ||
!tweaksAndThings.IsEnabled ||
rosterFuelColumnSettings.EngineRosterFuelStatusColumn == EngineRosterFuelDisplayColumn.None || (!GameInput.IsAltDown && !rosterFuelColumnSettings.EngineRosterShowsFuelStatusAlways))
{
return;
}
try
{
string fuelInfoText = string.Empty;
string fuelInfoTooltip = string.Empty;
Car engineOrTender = __instance._engine;
List<LoadSlot> loadSlots = __instance._engine.Definition.LoadSlots;
if (!loadSlots.Any())
{
engineOrTender = __instance._engine.DetermineFuelCar()!;
loadSlots = engineOrTender != null ? engineOrTender.Definition.LoadSlots : Enumerable.Empty<LoadSlot>().ToList();
}
var offender = loadSlots.OrderBy(ls => (engineOrTender.GetLoadInfo(ls.RequiredLoadIdentifier, out int slotIndex)?.Quantity ?? 0) / loadSlots[slotIndex].MaximumCapacity).FirstOrDefault().RequiredLoadIdentifier;
for (int i = 0; i < loadSlots.Count; i++)
{
CarLoadInfo? loadInfo = engineOrTender.GetLoadInfo(i);
if (loadInfo.HasValue)
{
CarLoadInfo valueOrDefault = loadInfo.GetValueOrDefault();
var fuelLevel = FuelLevel(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity);
fuelInfoText += loadSlots[i].RequiredLoadIdentifier == offender ? fuelLevel + " " : string.Empty;
//fuelInfoText += TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity) + " ";
fuelInfoTooltip += $"{TextSprites.PiePercent(valueOrDefault.Quantity, loadSlots[i].MaximumCapacity)} {valueOrDefault.LoadString(CarPrototypeLibrary.instance.LoadForId(valueOrDefault.LoadId))}\n";
}
}
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 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 string FuelLevel(float quantity, float capacity)
{
float num = capacity <= 0f ? 0 : Mathf.Clamp(quantity / capacity * 100, 0, 100);
return $"{Mathf.FloorToInt(num):D2}%";
}
}

View File

@@ -14,7 +14,7 @@ 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))]
@@ -35,9 +35,7 @@ public class ExpandedConsole_Add_Patch
TweaksAndThings tweaksAndThings = SingletonPluginBase<TweaksAndThings>.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 &&
@@ -50,7 +48,6 @@ public class ExpandedConsole_Add_Patch
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
@@ -91,7 +88,6 @@ public class ExpandedConsole_Add_Patch
}
};
string EndPoint = settings.WebhookUrl;
Log.Information(JsonConvert.SerializeObject(SuccessWebHook));
var content = new StringContent(JsonConvert.SerializeObject(SuccessWebHook), Encoding.UTF8, "application/json");
@@ -102,7 +98,6 @@ public class ExpandedConsole_Add_Patch
catch (Exception ex)
{
Log.Error(ex, ex.Message);
entry.Text += ex.ToString();
}
}

View File

@@ -1,85 +0,0 @@
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<TagController>();
TweaksAndThings tweaksAndThings = SingletonPluginBase<TweaksAndThings>.Shared;
tagCallout.callout.Title = $"<align=left>{car.DisplayName}<line-height=0>";
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<align=\"right\">{TextSprites.CycleWaybills}{TextSprites.HandbrakeWheel}";
}
else if (car.EndAirSystemIssue())
tagCallout.callout.Title =
$"{tagCallout.callout.Title}\n<align=\"right\">{TextSprites.CycleWaybills}";
else if (car.HandbrakeApplied())
tagCallout.callout.Title =
$"{tagCallout.callout.Title}\n<align=\"right\">{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();
}

View File

@@ -0,0 +1,58 @@
using HarmonyLib;
using Model;
using Model.OpsNew;
using Railloader;
using RMROC451.TweaksAndThings.Extensions;
using UI;
using UI.Tags;
using UnityEngine;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(TagController))]
[HarmonyPatch(nameof(TagController.UpdateTag), typeof(Car), typeof(TagCallout), typeof(OpsController))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
public 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;
if (!tweaksAndThings.IsEnabled || !tweaksAndThings.settings.HandBrakeAndAirTagModifiers)
{
return;
}
ProceedWithPostFix(car, tagCallout, tagController);
return;
}
private static void ProceedWithPostFix(Car car, TagCallout tagCallout, TagController tagController)
{
bool isAltDownWithCarIssue = GameInput.IsAltDown && car.CarOrEndGearIssue();
tagCallout.callout.Title = string.Format(tagTitleFormat, "{0}", car.DisplayName);
tagCallout.gameObject.SetActive(
tagCallout.gameObject.activeSelf &&
(!GameInput.IsAltDown || isAltDownWithCarIssue)
);
if (tagCallout.gameObject.activeSelf && isAltDownWithCarIssue)
{
tagController.ApplyImageColor(tagCallout, Color.black);
}
tagCallout.callout.Title =
(car.CarAndEndGearIssue(), car.EndAirSystemIssue(), car.HandbrakeApplied()) 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"),
_ => car.DisplayName
};
}
}

View File

@@ -11,8 +11,11 @@
<GameAssembly Include="0Harmony" />
<GameAssembly Include="KeyValue.Runtime" />
<GameAssembly Include="Definition" />
<GameAssembly Include="Ops" />
<GameAssembly Include="UnityEngine.CoreModule" />
<GameAssembly Include="UnityEngine.UI" />
<GameAssembly Include="Unity.TextMeshPro" />
<GameAssembly Include="System.Net.Http" />
</ItemGroup>
@@ -27,10 +30,4 @@
<ItemGroup>
<Publicize Include="Assembly-CSharp" />
</ItemGroup>
<ItemGroup>
<Reference Include="ForYourConvenience">
<HintPath>D:\SteamLibrary\steamapps\common\Railroader\Mods\ForYourConvenience\ForYourConvenience.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -1,53 +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()
{}
public Settings(
WebhookSettings webhookSettings,
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());
}
}
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;
}

View File

@@ -0,0 +1,76 @@
using Serilog;
using System.Collections.Generic;
using System.Linq;
using RMROC451.TweaksAndThings.Enums;
namespace RMROC451.TweaksAndThings;
public class Settings
{
public Settings()
{
WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
EngineRosterFuelColumnSettings = new();
}
public Settings(
List<WebhookSettings> webhookSettingsList,
bool handBrakeAndAirTagModifiers,
RosterFuelColumnSettings engineRosterFuelColumnSettings
)
{
WebhookSettingsList = webhookSettingsList;
HandBrakeAndAirTagModifiers = handBrakeAndAirTagModifiers;
EngineRosterFuelColumnSettings = engineRosterFuelColumnSettings;
}
public List<WebhookSettings>? WebhookSettingsList;
public bool HandBrakeAndAirTagModifiers;
public RosterFuelColumnSettings? EngineRosterFuelColumnSettings;
internal void AddAnotherRow()
{
WebhookSettingsList = !WebhookSettingsList?.Any() ?? false ? new[] { new WebhookSettings() }.ToList() : new List<WebhookSettings>();
if (!string.IsNullOrEmpty(WebhookSettingsList.OrderByDescending(wsl => wsl.WebhookUrl).Last().WebhookUrl))
{
WebhookSettingsList.Add(new());
Log.Information($"Adding another {nameof(WebhookSettings)} list entry, last one was filled in");
}
}
}
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;
}

View File

@@ -1,166 +1,204 @@
using GalaSoft.MvvmLight.Messaging;
using Game.Messages;
// Ignore Spelling: RMROC
using GalaSoft.MvvmLight.Messaging;
using Game.State;
using HarmonyLib;
using Newtonsoft.Json.Linq;
using Railloader;
using Serilog;
using Serilog.Debugging;
using System;
using System.Linq;
using System.Net.Http;
using TweaksAndThings.Commands;
using UI.Builder;
using UI.CarInspector;
namespace TweaksAndThings
using RMROC451.TweaksAndThings.Enums;
namespace RMROC451.TweaksAndThings;
public class TweaksAndThings : SingletonPluginBase<TweaksAndThings>, IUpdateHandler, IModTabHandler
{
public class TweaksAndThings : SingletonPluginBase<TweaksAndThings>, IUpdateHandler, IModTabHandler
private HttpClient client;
internal HttpClient Client
{
private HttpClient client;
internal HttpClient Client
get
{
get
{
if (client == null)
client = new HttpClient();
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);
}
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());
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);
}
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!");
if (settings == null) settings = new();
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new();
//WebhookUISection(ref builder);
//builder.AddExpandingVerticalSpacer();
WebhooksListUISection(ref builder);
builder.AddExpandingVerticalSpacer();
HandbrakesAndAnglecocksUISection(ref builder);
builder.AddExpandingVerticalSpacer();
EnginRosterShowsFuelStatusUISection(ref builder);
}
private void EnginRosterShowsFuelStatusUISection(ref 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.AddField(
"Enable",
builder.AddDropdown(columns, (int)(settings?.EngineRosterFuelColumnSettings?.EngineRosterFuelStatusColumn ?? EngineRosterFuelDisplayColumn.None),
delegate (int column)
{
if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList(), EngineRosterFuelColumnSettings = new() };
settings.EngineRosterFuelColumnSettings.EngineRosterFuelStatusColumn = (EngineRosterFuelDisplayColumn)column;
builder.Rebuild();
}
)
).Tooltip("Enable Fuel Display in Engine Roster", $"Will add reaming fuel indication to Engine Roster (with details in roster row tool tip), Examples : {string.Join(" ", Enumerable.Range(0,4).Select(i => TextSprites.PiePercent(i, 4)))}");
builder.AddField(
"Always Visible?",
builder.AddToggle(
() => settings?.EngineRosterFuelColumnSettings?.EngineRosterShowsFuelStatusAlways ?? false,
delegate (bool enabled)
{
if (settings == null) settings = new() { WebhookSettingsList = new[] { new WebhookSettings() }.ToList(), EngineRosterFuelColumnSettings = 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 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());
}
}