diff --git a/BaseReporter.cs b/BaseReporter.cs index 4fe4937..49bdb35 100644 --- a/BaseReporter.cs +++ b/BaseReporter.cs @@ -7,8 +7,9 @@ namespace DVPathTracer { public static class BaseReporter { - public static string fileName = "DVTracedPath.csv"; - private const string basePath = "./Mods/DVPathTracer/"; + public const string defaultFilename = "DVTracedPath.csv"; + public static string fileName = defaultFilename; + private const string basePath = "./Mods/DVPathTracer/sessions/"; public const float seaLevel = 110; @@ -60,15 +61,23 @@ public static void Deactivate() } /** - * Creates an empty .csv file of the set name, overwriting any existing file - * The file will be .csv regardless of whether or not ".csv" is included in the set name + * Creates an empty .csv file named as the current date & time, unless told to use the default */ private static void PrepareFile() { - fileName = Main.settings.fileName; - if (!fileName.EndsWith(".csv")) + if (!Directory.Exists(basePath)) { - fileName += ".csv"; + Directory.CreateDirectory(basePath); + Main.Log($"Session folder created"); + } + if (Main.settings.useSystemTime) + { + var currentTime = DateTime.Now; + fileName = $"{currentTime:yyyyMMdd_HHmm}.csv"; // System time to nearest minute + } + else + { + fileName = defaultFilename; } File.WriteAllText(basePath + fileName, $"Time,{PlayerReporter.Headings},{StockReporter.Headings},{StockReporter.Headings}\n"); Main.Log($"File {fileName} readied"); @@ -95,7 +104,9 @@ public static string GetReport(float time) string report = $"{time},{player.Values},"; List toRemove = new List(); - foreach (int index in StockFinder.TrackedStock.Keys) + int index = 0; + int remainingStock = StockFinder.numStock; + while (index < StockFinder.TrackedStock.Keys.Count && remainingStock > 0) { if (StockFinder.TrackedStock[index] == null) // No car to report on { @@ -106,15 +117,18 @@ public static string GetReport(float time) Main.Log($"Removing deleted car {StockFinder.TrackedStock[index].ID}"); report += $"{StockReporter.Headings},"; toRemove.Add(index); + remainingStock--; } else { report += $"{StockFinder.TrackedStock[index].Values},"; + remainingStock--; } + index++; } - foreach (int index in toRemove) + foreach (int i in toRemove) { - StockFinder.Remove(index); + StockFinder.Remove(i); } return report; } @@ -135,10 +149,11 @@ public static void TimedReport() { report = GetReport(upTime); } - catch // No Report Available, skip - // Should only ever happen while the game is loading + catch //(Exception e) // No Report Available, skip + // Should only ever happen while the game is loading { Main.Log($"No report at {upTime} available"); + //Main.Log(e.ToString()); nextReportTime += Main.settings.logRate; return; } diff --git a/CCLInterface.cs b/CCLInterface.cs new file mode 100644 index 0000000..b747120 --- /dev/null +++ b/CCLInterface.cs @@ -0,0 +1,16 @@ +using DVCustomCarLoader; + +namespace DVPathTracer +{ + // Separate file only used if CCL is installed to avoid System.IO.FileNotFoundException + internal static class CCLInterface + { + /** + * Return the human-friendly name of a CCL car + */ + internal static string CustomCarIndentifier(TrainCarType car) + { + return CarTypeInjector.CustomCarByType(car).identifier; + } + } +} diff --git a/Commands.cs b/Commands.cs index 745eef3..53548df 100644 --- a/Commands.cs +++ b/Commands.cs @@ -49,34 +49,6 @@ public static void Register() Terminal.Log(BaseReporter.player.Rotation.ToString()); }); - Register("whatFile", _ => { - Terminal.Log($"Tracer is set to use file: {Main.settings.fileName}"); - }); - - Register("setFileTo", args => { - if (!BaseReporter.isActive) - { - if (args.Length > 0) - { - string newFile = args[0].String; - if (!newFile.EndsWith(".csv")) - { - newFile += ".csv"; - } - Main.settings.fileName = newFile; - Terminal.Log($"Tracer set to use file: {newFile}"); - } - else - { - Terminal.Log($"ERROR: Provide a file to use!"); - } - } - else - { - Terminal.Log($"ERROR: Cannot change file while the tracer is active!"); - } - }); - Register("whatReportInterval", _ => { Terminal.Log($"Tracer is set to report every {Main.settings.logRate} seconds"); }); @@ -138,7 +110,7 @@ public static void Register() Register("disablePreventActivationOnStartup", _ => { Main.settings.forceStartInactive = false; - Terminal.Log("Tracer will remember if it was active when you end the game and will, if active on startup, overwrite any existing file."); + Terminal.Log("Tracer will remember whether or not it was active when you ended the game"); }); Register("enablePreventActivationOnStartup", _ => { diff --git a/DVPathTracer.csproj b/DVPathTracer.csproj index beec6ff..bed0dd5 100644 --- a/DVPathTracer.csproj +++ b/DVPathTracer.csproj @@ -9,9 +9,10 @@ Properties DVPathTracer DVPathTracer - v4.7.2 + v4.8 512 true + true @@ -40,6 +41,9 @@ D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp-firstpass.dll + + ..\..\..\CCL\DVCustomCarLoader.dll + @@ -63,6 +67,7 @@ + @@ -74,6 +79,7 @@ + diff --git a/LICENSE_THIRD_PARTY b/LICENSE_THIRD_PARTY index c8a0008..a699aa4 100644 --- a/LICENSE_THIRD_PARTY +++ b/LICENSE_THIRD_PARTY @@ -42,10 +42,10 @@ Licensed under MIT. ============================================================================== ============================================================================== -Plotly.js +noUiSlider ------------------------------------------------------------------------------ -https://plotly.com/javascript/ -Copyright (c) 2021 Plotly, Inc. +https://refreshless.com/nouislider/ +Copyright (c) 2019 Léon Gersen. Licensed under MIT. ============================================================================== @@ -54,5 +54,21 @@ pathAnimator/worldMap.png ------------------------------------------------------------------------------ This is an edited in-game screenshot of the world map. Copyright (c) Altfuture. -Used in this mod with permission. +Used in DV Path Tracer with permission. +============================================================================== + +============================================================================== +Plotly.js +------------------------------------------------------------------------------ +https://plotly.com/javascript/ +Copyright (c) 2021 Plotly, Inc. +Licensed under MIT. +============================================================================== + +============================================================================== +wNumb.js +------------------------------------------------------------------------------ +https://refreshless.com/wnumb/ +Copyright (c) 2019 Léon Gersen. +Licensed under MIT. ============================================================================== diff --git a/Main.cs b/Main.cs index 1fe8b42..4871672 100644 --- a/Main.cs +++ b/Main.cs @@ -8,6 +8,7 @@ public static class Main public static bool enabled; public static Settings settings = new Settings(); public static UnityModManager.ModEntry entry; + internal static bool cclEnabled = false; /** * Mod entrypoint @@ -16,7 +17,6 @@ public static class Main public static bool Load(UnityModManager.ModEntry modEntry) { entry = modEntry; - Log("Hello World!"); try { @@ -39,6 +39,40 @@ public static bool Load(UnityModManager.ModEntry modEntry) settings.version = modEntry.Info.Version; } + try + { + if (UnityModManager.FindMod("Mph").Loaded) + { + modEntry.Logger.Log("MPH mod is active, recording speeds in mph"); + settings.mph = true; + } + else if (settings.mph) + { + modEntry.Logger.Log("MPH mod is not active but speeds are still being recorded in mph"); + } + } + catch + { + // MPH mod is not installed, UMM reports this itself + if (settings.mph) + { + modEntry.Logger.Log("Speeds are being recorded in mph"); + } + } + + try + { + if (UnityModManager.FindMod("DVCustomCarLoader").Loaded) + { + modEntry.Logger.Log("CCL mod is active"); + cclEnabled = true; + } + } + catch + { + // CCL is not installed + } + var harmony = new Harmony(modEntry.Info.Id); harmony.PatchAll(); diff --git a/README.md b/README.md index 604d3e6..7db3bde 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ Import the file into Google Sheets or equivalent and go ham! - As of version 0.2.0 the tracer is always active by default. - This can be changed in the mod settings. -- While active, the tracer will periodically write information to **DVTracedPath.csv** in the folder this mod was installed to. - *(i.e. /Derail Valley/Mods/DVPathTracer/DVTracedPath.csv)* - - If the file exists from a previous time the tracer was active then the file will be overwritten +- While active, the tracer will periodically write information to a csv file in the **sessions** folder of the folder this mod was installed to. + *(i.e. /Derail Valley/Mods/DVPathTracer/sessions/\.csv)* + - By default a new file is created every session; if this is disabled in the mod settings each new session will overwrite the file **DVTracedPath.csv**. ### Path Animator @@ -40,7 +40,7 @@ Import the file into Google Sheets or equivalent and go ham! The tracer reports to a .csv file with the following information: -### Player information +### Player information: The first 5 columns indicate information about you, the player: @@ -55,15 +55,14 @@ The first 5 columns indicate information about you, the player: The following columns indicate information about all spawned locomotives and the caboose, in sets of 7: - `CID` The object's in-game identifier (e.g. **L-001**). -- `CType` The type of rolling stock (e.g. **DE2**). - ***Note:** The tracer will report a string of numbers for modded locos. - This will be fixed in a future update.* +- `CType` The type of rolling stock (e.g. **DE2**). - `CPosX` [m] Current x coordinate (distance from the *West* edge of the map). - `CPosY` [m] Current height (above sea level). - `CPosZ` [m] Current z coordinate (distance from the *South* edge of the map). - `CRotA` [°] Current rotation about the vertical axis, in degrees from North (like a compass). - `CSpd` [Default: kph] Current reported speed. - Imperial units can be enabled in the mod settings. + - If the [MPH](https://www.nexusmods.com/derailvalley/mods/401) mod is detected, imperial units will be enabled by default. Order is preserved to an extent - information about each object will occupy the same column it started in until that object despawns. After the object despawns, until a new object takes its place, the tracer will fill that space with the column headings listed above. @@ -74,6 +73,11 @@ After the object despawns, until a new object takes its place, the tracer will f - More included analysis tools. - *Potentially* add tracing of active job cars. +## Contributing + +I greatly appreciate enthusiasm for my project and any offers to contribute, however I am hesitant to accept them for a number of reasons. +**If you would like to contribute** please let me know in advance - in either the issue tracker here or the official Altfuture Discord (@Talkingpeanut). + ## Many thanks This mod uses code for registering custom in-game terminal commands (i.e. **Commands.cs**) created by Miles "Zeibach" Spielberg. diff --git a/Settings.cs b/Settings.cs index b251192..d1626a1 100644 --- a/Settings.cs +++ b/Settings.cs @@ -5,7 +5,6 @@ namespace DVPathTracer public class Settings : UnityModManager.ModSettings, IDrawable { public string version; - public string fileName = "DVTracedPath.csv"; [Draw("Report rate (seconds per report)", Min = 1f)] public float logRate = 5f; @@ -19,6 +18,9 @@ public class Settings : UnityModManager.ModSettings, IDrawable [Draw("Prevent activation on startup")] public bool forceStartInactive = false; + [Draw("Save each session to a new file")] + public bool useSystemTime = true; + override public void Save(UnityModManager.ModEntry entry) { Save(this, entry); diff --git a/StockFinder.cs b/StockFinder.cs index f01eb24..ae64ba3 100644 --- a/StockFinder.cs +++ b/StockFinder.cs @@ -14,6 +14,8 @@ public static Dictionary TrackedStock } } + public static int numStock = 0; + /** * Inserts the given TrainCar into the earliest available space */ @@ -25,6 +27,7 @@ public static void Add(TrainCar car) i++; } trackedStock[i] = new StockReporter(car); + numStock++; } /** @@ -70,6 +73,7 @@ public static void Remove(TrainCar car) public static void Remove(int carIndex) { trackedStock[carIndex] = null; + numStock--; } /** diff --git a/StockReporter.cs b/StockReporter.cs index a0e62ac..99ce5ae 100644 --- a/StockReporter.cs +++ b/StockReporter.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using System; +using UnityEngine; // TODO: Learn the what and *why* of C# fields vs properties @@ -39,14 +40,13 @@ public string ID } /** - * Reader-friendly descriptor of the type of car, where convenient - * TODO: There's almost certainly a string label to find in the default case instead of just a number + * Reader-friendly descriptor of the type of car */ public string Type { get { - string type; + string type = ""; switch (Target.carType) { case TrainCarType.LocoShunter: @@ -63,7 +63,23 @@ public string Type break; case TrainCarType.NotSet: default: - type = Target.carType.ToString(); + if (Main.cclEnabled) + { + try + { + type = CCLInterface.CustomCarIndentifier(Target.carType); + } + catch //(Exception e) + { + //Main.Log(e.ToString()); + // It's either an error or just not CCL stock + // TODO: this better + } + } + if (type == "") + { + type = Target.carType.ToString(); + } break; } return type; @@ -114,9 +130,8 @@ public static float SpeedUnits(float speed) if (Main.settings.mph) { return speed * (float) 2.23694; - } else { - return speed * (float) 3.6; } + return speed * (float) 3.6; } } } diff --git a/info.json b/info.json index a701886..e13f74e 100644 --- a/info.json +++ b/info.json @@ -2,8 +2,10 @@ "Id": "DVPathTracer", "DisplayName": "DV Path Tracer", "Author": "Talkingpeanut", - "Version": "0.2.0", + "Version": "0.3.0", + "LoadAfter": [ "DVCustomCarLoader", "Mph" ], "AssemblyName": "DVPathTracer.dll", "EntryMethod": "DVPathTracer.Main.Load", - "HomePage": "https://www.nexusmods.com/derailvalley/mods/425" + "HomePage": "https://www.nexusmods.com/derailvalley/mods/425", + "Repository": "https://raw.githubusercontent.com/eAlasdair/DVPathTracer/main/repository.json" } diff --git a/pathAnimator/pathAnimator.html b/pathAnimator/pathAnimator.html index 480fd5f..a449408 100644 --- a/pathAnimator/pathAnimator.html +++ b/pathAnimator/pathAnimator.html @@ -9,6 +9,8 @@ + + @@ -35,6 +37,14 @@ +
+
+ FPS: +
+
+ +
+
@@ -47,6 +57,10 @@ + + + + diff --git a/pathAnimator/static/pathAnimator.css b/pathAnimator/static/pathAnimator.css index ecc29f9..e42b6be 100644 --- a/pathAnimator/static/pathAnimator.css +++ b/pathAnimator/static/pathAnimator.css @@ -19,6 +19,14 @@ html, body { background-color: wheat; } +#fpsSlider { + padding: 0 16px; +} + +.noUi-tooltip { + font-size: 0.8rem; +} + .legendSpot { height: 25px; width: 25px; diff --git a/pathAnimator/static/pathAnimator.js b/pathAnimator/static/pathAnimator.js index b7a1bb2..a62b39b 100644 --- a/pathAnimator/static/pathAnimator.js +++ b/pathAnimator/static/pathAnimator.js @@ -1,7 +1,7 @@ // Dictionaries for player & rolling stock data var _playerData = {}; // Time: [X, Y, Z, ROT] var _carData = {}; // CID_TYP: {Time: [X, Y, Z, ROT, SPD]} -const _idSep = '_'; // ^ This symbol here +const _idSep = ' '; // ^ This symbol here const _worldDimentions = [16360, 16360]; @@ -73,6 +73,24 @@ $(document).ready(function() { $('#legend').append(element); } + // Create FPS slider + let fpsSlider = document.getElementById('fpsSlider'); + noUiSlider.create(fpsSlider, { + start: [_fps], + tooltips: [wNumb({decimals: 0})], + step: 1, + range: { + 'min': [1], + 'max': [60] + }, + format: wNumb({ + decimals: 0 + }) + }); + fpsSlider.noUiSlider.on('change', function() { + _fps = fpsSlider.noUiSlider.get(); + }); + $('#plotButton').click(plotData); $('#timelapseButton').click(animateData); }); @@ -86,7 +104,7 @@ $(document).ready(function() { */ function loadFile(file) { if (file.type != 'text/csv') { - if (file.type.match(/image.*/) && $('#animationPlot').children().length) { + if (file.type.match(/image.*/)) { importBackgroundImage(file); } else { console.warn(`File must be csv, got ${file.type}`) @@ -236,6 +254,7 @@ function plotData() { $('#plotButton').attr('disabled', true); $('#timelapseButton').attr('disabled', false); $('#animationPlot').empty() + $('#fpsOption').addClass('d-none'); console.log('Plotting data'); @@ -300,6 +319,7 @@ function getPlottableFrames() { * Update each point being plotted in the timelapse */ function update() { + let nextFrame = 1000/_fps; if (!_animating) { return; } @@ -319,13 +339,13 @@ function update() { //easing: 'linear', }, frame: { - duration: 1000/_fps, + duration: nextFrame, redraw: false } }; Plotly.animate( 'animationPlot', data, format); _frame = _frame >= _numFrames - 1 ? 0 : _frame + 1; - requestAnimationFrame(update); + setTimeout(requestAnimationFrame, nextFrame, update); } /** @@ -336,6 +356,7 @@ function animateData() { $('#timelapseButton').attr('disabled', true); $('#plotButton').attr('disabled', false); $('#animationPlot').empty() + $('#fpsOption').removeClass('d-none'); _frame = 0; console.log('Animating data'); @@ -407,7 +428,11 @@ function importBackgroundImage(image) { var FR = new FileReader(); FR.addEventListener('load', function(e) { $('#animationPlot').attr('src', e.target.result); - addBackgroundImage(); + if ($('#animationPlot').children().length) { + addBackgroundImage(); + } else { + alert("Image will render when your traced path (csv) is loaded."); + } }); FR.readAsDataURL(image); } diff --git a/repository.json b/repository.json new file mode 100644 index 0000000..96c466a --- /dev/null +++ b/repository.json @@ -0,0 +1,19 @@ +{ + "Releases": [ + { + "Id": "DVPathTracer", + "Version": "0.1.0", + "DownloadUrl": "https://github.com/eAlasdair/DVPathTracer/releases/download/v0.1.0/DVPathTracer.zip" + }, + { + "Id": "DVPathTracer", + "Version": "0.2.0", + "DownloadUrl": "https://github.com/eAlasdair/DVPathTracer/releases/download/v0.2.0/DVPathTracer-0-2-0.zip" + }, + { + "Id": "DVPathTracer", + "Version": "0.3.0", + "DownloadUrl": "https://github.com/eAlasdair/DVPathTracer/releases/download/v0.3.0/DVPathTracer-0-3-0.zip" + }, + ] +}