diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..b4e8d9f --- /dev/null +++ b/.hgignore @@ -0,0 +1,5 @@ +syntax: glob +.vs/ +_ReSharper.Caches/ +*/obj/ +*/bin/ diff --git a/DVDispatcherMod/BookletReader.cs b/DVDispatcherMod/BookletReader.cs deleted file mode 100644 index b2d23e7..0000000 --- a/DVDispatcherMod/BookletReader.cs +++ /dev/null @@ -1,45 +0,0 @@ -using DV.Logic.Job; -using DV.RenderTextureSystem.BookletRender; -using Harmony12; -using System.Collections.Generic; - -namespace DVDispatcherMod -{ - [HarmonyPatch(typeof(BookletCreator), "GetBookletTemplateData")] - class BookletCreator_GetBookletTemplateData_Patch - { - static void Postfix(ref List __result, Job job) - { - IDispatch[] dispatches = new IDispatch[__result.Count]; - for (int i = 0; i < __result.Count; i++) - { - switch (__result[i]) - { - case CoverPageTemplatePaperData cptpd: - break; - case TaskTemplatePaperData ttpd: - switch (ttpd.taskType) { - case "COUPLE": - dispatches[i] = new CoupleDispatch(job, ttpd, false); - break; - case "UNCOUPLE": - dispatches[i] = new CoupleDispatch(job, ttpd, true); - break; - case "HAUL": - case "LOAD": - case "UNLOAD": - default: - dispatches[i] = new NullDispatch(); - break; - } - break; - case FrontPageTemplatePaperData fptpd: - case ValidateJobTaskTemplatePaperData _: - default: - dispatches[i] = new NullDispatch(); - break; - } - } - } - } -} diff --git a/DVDispatcherMod/ConsistInfo.cs b/DVDispatcherMod/ConsistInfo.cs deleted file mode 100644 index ca39e78..0000000 --- a/DVDispatcherMod/ConsistInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using DV.Logic.Job; -using System.Collections.Generic; - -namespace DVDispatcherMod -{ - public class ConsistInfo - { - public List cars; - - public ConsistInfo() - { - - } - } -} diff --git a/DVDispatcherMod/CoupleDispatch.cs b/DVDispatcherMod/CoupleDispatch.cs deleted file mode 100644 index 10f2685..0000000 --- a/DVDispatcherMod/CoupleDispatch.cs +++ /dev/null @@ -1,191 +0,0 @@ -using DV.Logic.Job; -using DV.RenderTextureSystem.BookletRender; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; - -namespace DVDispatcherMod -{ - - public class CoupleDispatch : IDispatch - { - private struct Group - { - public List cars; - public int consistPos; - - public Group(List cars) - { - this.cars = cars; - this.consistPos = -1; - } - } - - // Set of cars. The bool is used for cataloging purposes. - private Dictionary cars; - // Lists of contiguous cars. - private List groups; - private Track track; - private bool uncouple; - - public CoupleDispatch(Job job, TaskTemplatePaperData ttpd, bool uncouple) - { - HashSet carIds = new HashSet(); - ttpd.cars.ForEach(c => carIds.Add(c.Item2)); - this.cars = new Dictionary(); - Utils.LookupCars(job, carIds).ForEach(c => cars.Add(c, false)); - this.groups = new List(); - this.track = Utils.LookupTrack(job, ttpd.yardId, ttpd.trackId); - this.uncouple = uncouple; - } - - /* - * Cars are in same group or k different groups. - * Groups are part of last loco trainset or separated. - * - * "These cars are in k different consists on tracks..." - * - Point alternate between consists. - * - * "These cars are on track xxx." - * - Point to closest first/last car and get track id of closest car. - * - * "These cars are kth in your current consist." - * - Point to closest first/last car. - */ - string IDispatch.GetFloatieText(int index) - { - if (groups.Count < 1) - { - if (uncouple) - return "This task has been completed."; - else - return "These cars could not be found... wtf?"; - } - else - { - StringBuilder sb = new StringBuilder(); - // Only occurs when cars are omitted due to being at uncouple destination. - int destCars = cars.Count - groups.Aggregate(0, (c, g) => c + g.cars.Count); - if (destCars > 0) - { - sb.AppendFormat("{0} car", destCars); - if (destCars > 1) - sb.Append("s are"); - else - sb.Append(" is"); - sb.Append(" already on the destination track."); - } - // Go through locations of all groups of cars. - int i = 0; - // For groups of cars part of the last consist. - if (groups.Any(g => g.consistPos != -1)) - { - for (; groups[i].consistPos != -1 && i < groups.Count; i++) - { - - } - } - else - { - sb.Append(" "); - } - // For detached groups of cars. - if (groups.Any(g => g.consistPos == -1)) - { - for (; i < groups.Count; i++) - { - - } - } - return sb.ToString(); - } - } - - Transform IDispatch.GetPointerAt(int index) - { - if (groups.Count < 1) - { - if (uncouple) - { - return null; - } - else - return null; // Should not happen. - } - else if (groups.Count == 1) - { - return Utils.GetClosestFirstLastCar(groups.First().cars)?.transform; - } - else - { - return Utils.GetClosestFirstLastCar(groups[index % groups.Count].cars)?.transform; - } - } - - void IDispatch.UpdateDispatch() - { - // Recatalogue groups of cars for this task. - groups.Clear(); - foreach (Car c in cars.Keys) - cars[c] = false; - foreach (Car c in cars.Keys) - { - bool test = uncouple && c.BogiesOnSameTrack && c.CurrentTrack == track; - } - // Will go through up to cars.Count different groups. - List detachedGroups = new List(); - foreach (Car car in cars.Keys) - { - if (cars[car] || !SingletonBehaviour.Instance.logicCarToTrainCar.TryGetValue(car, out TrainCar trainCar)) - continue; - while (!cars[car]) - { - /* - * Comb through the trainset for contiguous groups of cars that are associated with this task. - */ - Trainset trainset = trainCar.trainset; - - List currentCars = new List(); - bool inGroup = false; - - for (int i = 0; i < trainset.cars.Count; i++) - { - TrainCar tc = trainset.cars[i]; - Car c = tc.logicCar; - if (c != null && cars.ContainsKey(c) && !cars[c]) - { - cars[c] = true; - // If this is an uncoupling dispatch, only add if car is not on destination. - if (!uncouple || c.CurrentTrack != track || !c.BogiesOnSameTrack) - currentCars.Add(c); - inGroup = true; - } - else if (inGroup) - { - if (currentCars.Count > 0) - { - Group group = new Group(currentCars); - if (trainset == PlayerManager.LastLoco?.trainset) - { - int place = i - currentCars.Count; - if (!trainset.cars.First().IsLoco && trainset.cars.Last().IsLoco) - { - currentCars.Reverse(); - place = trainset.cars.Count - i; - } - group.consistPos = place; - groups.Add(group); - } - else - detachedGroups.Add(group); - } - break; - } - } - } - } - groups.AddRange(detachedGroups); - } - } -} diff --git a/DVDispatcherMod/DVDispatcherMod.csproj b/DVDispatcherMod/DVDispatcherMod.csproj index f3b7b19..b429ea2 100644 --- a/DVDispatcherMod/DVDispatcherMod.csproj +++ b/DVDispatcherMod/DVDispatcherMod.csproj @@ -1,92 +1,116 @@ - - - - - Debug - AnyCPU - {C99248BD-8555-47E8-AE52-642F6FADDE9C} - Library - Properties - DVDispatcherMod - DVDispatcherMod - v4.5 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityModManager\0Harmony12.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp-firstpass.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\DV.Interaction.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\DV.Utils.dll - - - - - - - - - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\Unity.TextMeshPro.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.CoreModule.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.ImageConversionModule.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.IMGUIModule.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.UI.dll - - - D:\Program Files (x86)\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityModManager\UnityModManager.dll - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {C99248BD-8555-47E8-AE52-642F6FADDE9C} + Library + Properties + DVDispatcherMod + DVDispatcherMod + v4.5 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityModManager\0Harmony12.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp-firstpass.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\DV.Interaction.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\DV.Utils.dll + False + + + + + + + + + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\Unity.TextMeshPro.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.CoreModule.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.ImageConversionModule.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.IMGUIModule.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.UI.dll + False + + + C:\Games\Steam\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityModManager\UnityModManager.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DVDispatcherMod/DebugOutputJobWriter.cs b/DVDispatcherMod/DebugOutputJobWriter.cs new file mode 100644 index 0000000..6896c13 --- /dev/null +++ b/DVDispatcherMod/DebugOutputJobWriter.cs @@ -0,0 +1,48 @@ +using System.Linq; +using DV.Logic.Job; + +namespace DVDispatcherMod { + public static class DebugOutputJobWriter { + public static void DebugOutputJob(Job job) { + DebugLogIndented(0, job.GetType().Name, job.ID); + DebugLogIndented(1, "jobType", job.jobType); + DebugLogIndented(1, "State", job.State); + DebugLogIndented(1, "chainData"); + DebugLogIndented(2, "chainOriginYardId", job.chainData.chainOriginYardId); + DebugLogIndented(2, "chainDestinationYardId", job.chainData.chainDestinationYardId); + DebugLogIndented(1, "tasks"); + foreach (var jobTask in job.tasks) { + DebugOutputTask(2, jobTask); + } + } + + private static void DebugOutputTask(int indent, Task jobTask) { + DebugLogIndented(indent, jobTask.GetType().Name); + DebugLogIndented(indent + 1, "InstanceTaskType", jobTask.InstanceTaskType); + DebugLogIndented(indent + 1, "state", jobTask.state); + + var taskData = jobTask.GetTaskData(); + if (taskData.cars != null) { + DebugLogIndented(indent + 1, "cars", string.Join(", ", taskData.cars.Select(c => c.ID))); + } + DebugLogIndented(indent + 1, "startTrack", taskData.startTrack?.ID?.FullDisplayID); + DebugLogIndented(indent + 1, "destinationTrack", taskData.destinationTrack?.ID?.FullDisplayID); + DebugLogIndented(indent + 1, "warehouseTaskType", taskData.warehouseTaskType); + + if (taskData.nestedTasks != null) { + if (taskData.nestedTasks.Any()) { + DebugLogIndented(indent + 1, "nestedTasks (" + taskData.nestedTasks.Count + ")"); + + foreach (var nestedTask in taskData.nestedTasks) { + DebugOutputTask(indent + 2, nestedTask); + } + } + } + } + + private static void DebugLogIndented(int indent, string name, object value = null) { + var content = value != null ? (name + ": " + value) : name; + Main.ModEntry.Logger.Log(string.Join("", Enumerable.Repeat(" ", indent)) + content); + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/Dispatch.cs b/DVDispatcherMod/Dispatch.cs deleted file mode 100644 index e700f52..0000000 --- a/DVDispatcherMod/Dispatch.cs +++ /dev/null @@ -1,28 +0,0 @@ -using UnityEngine; - -namespace DVDispatcherMod -{ - public interface IDispatch - { - string GetFloatieText(int index); - Transform GetPointerAt(int index); - void UpdateDispatch(); - } - - public class NullDispatch : IDispatch - { - string IDispatch.GetFloatieText(int index) - { - return null; - } - - Transform IDispatch.GetPointerAt(int index) - { - return null; - } - - void IDispatch.UpdateDispatch() - { - } - } -} diff --git a/DVDispatcherMod/DispatcherHintManagers/DispatcherHintManager.cs b/DVDispatcherMod/DispatcherHintManagers/DispatcherHintManager.cs new file mode 100644 index 0000000..a999e49 --- /dev/null +++ b/DVDispatcherMod/DispatcherHintManagers/DispatcherHintManager.cs @@ -0,0 +1,59 @@ +using DVDispatcherMod.DispatcherHints; +using DVDispatcherMod.DispatcherHintShowers; +using DVDispatcherMod.PlayerInteractionManagers; +using JetBrains.Annotations; + +namespace DVDispatcherMod.DispatcherHintManagers { + public class DispatcherHintManager { + private readonly IPlayerInteractionManager _playerInteractionManager; + private readonly IDispatcherHintShower _dispatcherHintShower; + + private bool _isEnabled = true; + private int _counterValue; + + public DispatcherHintManager([NotNull] IPlayerInteractionManager playerInteractionManager, [NotNull] IDispatcherHintShower dispatcherHintShower) { + _playerInteractionManager = playerInteractionManager; + _dispatcherHintShower = dispatcherHintShower; + + _playerInteractionManager.JobOfInterestChanged += HandleJobObInterestChanged; + } + + public void SetIsEnabled(bool value) { + _isEnabled = value; + UpdateDispatcherHint(); + } + + public void SetCounter(int counterValue) { + _counterValue = counterValue; + UpdateDispatcherHint(); + } + + private void HandleJobObInterestChanged() { + UpdateDispatcherHint(); + + // uncomment this to spam the log with outputs of the job and task tree + //var job = _playerInteractionManager.JobOfInterest; + //if (job != null) { + // DebugOutputJobWriter.DebugOutputJob(job); + //} + } + + private void UpdateDispatcherHint() { + var currentHint = GetCurrentDispatcherHint(); + _dispatcherHintShower.SetDispatcherHint(currentHint); + } + + private DispatcherHint GetCurrentDispatcherHint() { + if (!_isEnabled) { + return null; + } + + var job = _playerInteractionManager.JobOfInterest; + if (job != null) { + return new JobDispatch(job).GetDispatcherHint(_counterValue); + } else { + return null; + } + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHintManagers/NonVRDispatchHintManagerFactory.cs b/DVDispatcherMod/DispatcherHintManagers/NonVRDispatchHintManagerFactory.cs new file mode 100644 index 0000000..871895f --- /dev/null +++ b/DVDispatcherMod/DispatcherHintManagers/NonVRDispatchHintManagerFactory.cs @@ -0,0 +1,27 @@ +using DVDispatcherMod.DispatcherHintShowers; +using DVDispatcherMod.PlayerInteractionManagers; + +namespace DVDispatcherMod.DispatcherHintManagers { + public static class NonVRDispatchHintManagerFactory { + private static NonVRDispatcherHintShower _dispatcherHintShower; + private static IPlayerInteractionManager _playerInteractionManager; + + public static DispatcherHintManager TryCreate() { + if (LoadingScreenManager.IsLoading || !WorldStreamingInit.IsLoaded || !SingletonBehaviour.Exists) { + return null; + } + + _dispatcherHintShower = _dispatcherHintShower ?? NonVRDispatcherHintShowerFactory.TryCreate(); + if (_dispatcherHintShower == null) { + return null; + } + + _playerInteractionManager = _playerInteractionManager ?? NonVRPlayerInteractionManagerFactory.TryCreate(); + if (_playerInteractionManager == null) { + return null; + } + + return new DispatcherHintManager(_playerInteractionManager, _dispatcherHintShower); + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHintShowers/IDispatcherHintShower.cs b/DVDispatcherMod/DispatcherHintShowers/IDispatcherHintShower.cs new file mode 100644 index 0000000..ca84281 --- /dev/null +++ b/DVDispatcherMod/DispatcherHintShowers/IDispatcherHintShower.cs @@ -0,0 +1,7 @@ +using DVDispatcherMod.DispatcherHints; + +namespace DVDispatcherMod.DispatcherHintShowers { + public interface IDispatcherHintShower { + void SetDispatcherHint(DispatcherHint dispatcherHintOrNull); + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHintShowers/NonVRDispatcherHintShower.cs b/DVDispatcherMod/DispatcherHintShowers/NonVRDispatcherHintShower.cs new file mode 100644 index 0000000..0c296af --- /dev/null +++ b/DVDispatcherMod/DispatcherHintShowers/NonVRDispatcherHintShower.cs @@ -0,0 +1,45 @@ +using DVDispatcherMod.DispatcherHints; +using TMPro; +using UnityEngine; + +namespace DVDispatcherMod.DispatcherHintShowers { + public class NonVRDispatcherHintShower : IDispatcherHintShower { + private readonly GameObject _floatie; + private readonly TextMeshProUGUI _floatieText; + private readonly TutorialLineNonVR _floatieLine; + + private bool _currentlyShowing; + + public NonVRDispatcherHintShower(GameObject floatie, TextMeshProUGUI floatieText, TutorialLineNonVR floatieLine) { + _floatie = floatie; + _floatieText = floatieText; + _floatieLine = floatieLine; + } + + public void SetDispatcherHint(DispatcherHint dispatcherHintOrNull) { + if (dispatcherHintOrNull == null) { + if (_currentlyShowing) { + _floatie.SetActive(false); + _floatieText.text = string.Empty; + _floatieLine.attentionTransform = null; + + _currentlyShowing = false; + } + } else { + if (_currentlyShowing) { + _floatieText.text = dispatcherHintOrNull.Text; + _floatieLine.attentionTransform = dispatcherHintOrNull.AttentionTransform; + } else { + _floatie.SetActive(false); // dunno, was in original code like that. + + _floatieText.text = dispatcherHintOrNull.Text; + _floatieLine.attentionTransform = dispatcherHintOrNull.AttentionTransform; + + _floatie.SetActive(true); + + _currentlyShowing = true; + } + } + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHintShowers/NonVRDispatcherHintShowerFactory.cs b/DVDispatcherMod/DispatcherHintShowers/NonVRDispatcherHintShowerFactory.cs new file mode 100644 index 0000000..d355f62 --- /dev/null +++ b/DVDispatcherMod/DispatcherHintShowers/NonVRDispatcherHintShowerFactory.cs @@ -0,0 +1,44 @@ +using TMPro; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.UI; + +namespace DVDispatcherMod.DispatcherHintShowers { + public static class NonVRDispatcherHintShowerFactory { + private static bool _floatieSceneLoaded; + + public static NonVRDispatcherHintShower TryCreate() { + var g = GameObject.Find("[NonVRFloatie]"); + if (g == null) { + if (!_floatieSceneLoaded) { + SceneManager.LoadScene("non_vr_ui_floatie", LoadSceneMode.Additive); + _floatieSceneLoaded = true; + Main.ModEntry.Logger.Log("Called load of non VR float scene."); + } else { + Main.ModEntry.Logger.Log("Could not find the non VR float."); + } + return null; + } + g = Object.Instantiate(g); // The tutorial sequence destroys non VR floaties, so make our own. + + var floatieNonVr = g.GetComponentInChildren(true)?.gameObject; + if (floatieNonVr == null) { + return null; + } + Main.ModEntry.Logger.Log("Found the non VR float."); + + var floatieNonVrText = floatieNonVr.GetComponentInChildren(true); + if (floatieNonVrText == null) { + return null; + } + Main.ModEntry.Logger.Log("Found the non VR text."); + + var floatieNonVrLine = floatieNonVr.GetComponentInChildren(true); + if (floatieNonVrLine == null) { + return null; + } + Main.ModEntry.Logger.Log("Found the non VR line."); + return new NonVRDispatcherHintShower(floatieNonVr, floatieNonVrText, floatieNonVrLine); + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHintShowers/VRDispatchHintShower.cs b/DVDispatcherMod/DispatcherHintShowers/VRDispatchHintShower.cs new file mode 100644 index 0000000..4535c1f --- /dev/null +++ b/DVDispatcherMod/DispatcherHintShowers/VRDispatchHintShower.cs @@ -0,0 +1,31 @@ +using DVDispatcherMod.DispatcherHints; +using UnityEngine; +using VRTK; + +namespace DVDispatcherMod.DispatcherHintShowers { + public class VRDispatchHintShower : IDispatcherHintShower { + private static GameObject _floatie; + + public void SetDispatcherHint(DispatcherHint dispatcherHintOrNull) { + if (dispatcherHintOrNull == null) { + if (_floatie != null) { + Object.Destroy(_floatie); + _floatie = null; + } + } else { + if (_floatie == null) { + var eyesTransform = PlayerManager.PlayerCamera?.transform; + if (eyesTransform == null) { + return; + } + var position = eyesTransform.position + eyesTransform.forward * 1.5f; + var parent = VRTK_DeviceFinder.PlayAreaTransform(); + _floatie = (Object.Instantiate(Resources.Load("tutorial_floatie"), position, Quaternion.identity, parent) as GameObject); + } + + _floatie.GetComponent().UpdateTextExternally(dispatcherHintOrNull.Text); + _floatie.GetComponent().attentionPoint = dispatcherHintOrNull.AttentionTransform; + } + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHints/DispatcherHint.cs b/DVDispatcherMod/DispatcherHints/DispatcherHint.cs new file mode 100644 index 0000000..b1c0542 --- /dev/null +++ b/DVDispatcherMod/DispatcherHints/DispatcherHint.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace DVDispatcherMod.DispatcherHints { + public class DispatcherHint { + public DispatcherHint(string text, Transform attentionTransform) { + Text = text; + AttentionTransform = attentionTransform; + } + + public string Text { get; } + public Transform AttentionTransform { get; } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/DispatcherHints/JobDispatch.cs b/DVDispatcherMod/DispatcherHints/JobDispatch.cs new file mode 100644 index 0000000..f616d77 --- /dev/null +++ b/DVDispatcherMod/DispatcherHints/JobDispatch.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DV.Logic.Job; +using DV.ServicePenalty; +using UnityEngine; + +namespace DVDispatcherMod.DispatcherHints { + public class JobDispatch { + private readonly Job _job; + + public JobDispatch(Job job) { + _job = job; + } + + public DispatcherHint GetDispatcherHint(int highlightIndex) { + if (_job.State == JobState.Completed) { + return new DispatcherHint("This job is completed.", null); + } else if (_job.State == JobState.Abandoned) { + return new DispatcherHint("This job is abandoned.", null); + } else if (_job.State == JobState.Failed) { + return new DispatcherHint("This job is failed.", null); + } else if (_job.State == JobState.Expired) { + return new DispatcherHint("This job is expired.", null); + } + + if (_job.State == JobState.Available) { + var jobNotAllowedText = GetJobNotAllowedTextOrNull(); + + if (jobNotAllowedText != null) { + return new DispatcherHint(jobNotAllowedText, null); + } + } + + var firstUnfinishedTasks = GetFirstUnfinishedTasks(_job.tasks.First()); + + var carGroupOnTracks = GetCarGroupsOnTracks(firstUnfinishedTasks); + + var dispatcherHintText = GetDispatcherHintText(carGroupOnTracks, firstUnfinishedTasks.Any(), highlightIndex); + + var taskOverview = TaskOverviewGenerator.GetTaskOverview(_job); + + var attentionTransform = GetPointerAt(carGroupOnTracks, highlightIndex); + + var floatieText = dispatcherHintText + Environment.NewLine + taskOverview; + + return new DispatcherHint(floatieText, attentionTransform); + } + + private string GetJobNotAllowedTextOrNull() { + if (PlayerJobs.Instance.currentJobs.Count >= LicenseManager.GetNumberOfAllowedConcurrentJobs()) { + return "You already have the maximum number of active jobs."; + } else if (!LicenseManager.IsLicensedForJob(_job.requiredLicenses)) { + return "You don't have the required license(s) for this job."; + } else if (!SingletonBehaviour.Instance.IsPlayerAllowedToTakeJob()) { + return "You still have fees to pay off in the Career Manager."; + } else { + return null; + } + } + + private static List GetCarGroupsOnTracks(List carRelevantTasks) { + var tasks = carRelevantTasks; + + var jobCars = tasks.SelectMany(t => t.GetTaskData().cars).ToList(); + + var carGroupsOnTracks = + from car in jobCars + let trainCar = TryGetTrainCarFromJobCar(car) + where trainCar != null + group car by trainCar.trainset + into trainSetWithCars + let trackID = trainSetWithCars.Select(c => c.CurrentTrack?.ID?.TrackPartOnly).FirstOrDefault(id => id != null) + let hasLocoHooked = (PlayerManager.LastLoco?.trainset == trainSetWithCars.Key) + let consistTransform = trainSetWithCars.Key.cars[trainSetWithCars.Key.cars.Count / 2].transform + select new CarGroupOnTrack(trackID, hasLocoHooked, consistTransform); + + return carGroupsOnTracks.ToList(); + } + + private static TrainCar TryGetTrainCarFromJobCar(Car car) { + if (SingletonBehaviour.Instance.logicCarToTrainCar.TryGetValue(car, out var trainCar)) { + return trainCar; + } else { + return null; + } + } + + /// + /// Figure out the current tasks. + /// + /// + /// + private List GetFirstUnfinishedTasks(Task startingTask) { + var toReturn = new List(); + if (startingTask != null && startingTask.state == TaskState.InProgress && startingTask.Job == _job) { + var tasks = startingTask.GetTaskData().nestedTasks; + switch (startingTask.InstanceTaskType) { + case TaskType.Parallel: + foreach (var t in tasks) { + toReturn.AddRange(GetFirstUnfinishedTasks(t)); + } + break; + case TaskType.Sequential: + foreach (var t in tasks) { + if (t.state == TaskState.InProgress) { + toReturn.AddRange(GetFirstUnfinishedTasks(t)); + break; + } + } + break; + default: + toReturn.Add(startingTask); + break; + } + } + return toReturn; + } + + private string GetDispatcherHintText(List carGroupsOnTracks, bool hasAnyUnfinishedTask, int highlightIndex) { + if (carGroupsOnTracks.Count > 0) { + return GetDispatcherHintTextForAtLeastOneCarGroup(carGroupsOnTracks, highlightIndex); + } else if (!hasAnyUnfinishedTask) { + return "The job is probably complete. Try turning it in."; + } else { + return "The job cars could not be found... wtf?"; + } + } + + private string GetDispatcherHintTextForAtLeastOneCarGroup(List carGroupsOnTracks, int highlightIndex) { + if (carGroupsOnTracks.Count == 1) { + var carGroupOnTrack = carGroupsOnTracks[0]; + return $"The cars for job {_job.ID} are in the same consist on track {(carGroupOnTrack == null ? "(Unknown)" : carGroupOnTrack.TrackID)}{(carGroupOnTrack.HasLastPlayerLocoHooked ? " and have a locomotive attached" : "")}."; + } else { + highlightIndex %= carGroupsOnTracks.Count; + var sb = new StringBuilder($"The cars for job {_job.ID} are currently in {carGroupsOnTracks.Count} different consists on tracks"); + for (var i = 0; i < carGroupsOnTracks.Count; i++) { + if (i > 0 && carGroupsOnTracks.Count > 2) { + sb.Append(','); + } + if (i == carGroupsOnTracks.Count - 1) { + sb.Append(" and"); + } + sb.Append(' '); + var carGroupOnTrack = carGroupsOnTracks[i]; + if (i == highlightIndex) { + sb.AppendFormat("{0}", carGroupOnTrack == null ? "(Unknown)" : carGroupOnTrack.TrackID); + } else { + sb.Append(carGroupOnTrack == null ? "(Unknown)" : carGroupOnTrack.TrackID); + } + } + sb.Append('.'); + return sb.ToString(); + } + } + + private static Transform GetPointerAt(List carGroupsOnTracks, int index) { + if (!carGroupsOnTracks.Any()) { + return null; + } + + index %= carGroupsOnTracks.Count; + var carGroupOnTrack = carGroupsOnTracks[index]; + if (carGroupOnTrack.HasLastPlayerLocoHooked) { + return PlayerManager.LastLoco?.transform; + } + + // TODO: Raise pointer to middle of car height. + return carGroupOnTrack.ConsistTransform; + } + + private class CarGroupOnTrack { + public string TrackID { get; } + public bool HasLastPlayerLocoHooked { get; } + public Transform ConsistTransform { get; } + + public CarGroupOnTrack(string trackID, bool hasLastPlayerLocoHooked, Transform consistTransform) { + TrackID = trackID; + HasLastPlayerLocoHooked = hasLastPlayerLocoHooked; + ConsistTransform = consistTransform; + } + } + } +} diff --git a/DVDispatcherMod/Floaties.cs b/DVDispatcherMod/Floaties.cs deleted file mode 100644 index b20dddc..0000000 --- a/DVDispatcherMod/Floaties.cs +++ /dev/null @@ -1,180 +0,0 @@ -using Harmony12; -using System.IO; -using TMPro; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using VRTK; - -namespace DVDispatcherMod -{ - public static class Floaties - { - - // NonVR Floating Text - public static GameObject floatieNonVR; - private static TutorialLineNonVR floatieNonVRLine; - private static TextMeshProUGUI floatieNonVRText; - private static bool floatieSceneLoaded = false; - - // VR Floating Text - private static GameObject floatieVR; - - // Floating Pointer - public const float START_WIDTH = 0.0025f; // Original = 0.005f; - public const float END_WIDTH = 0.01f; // Original = 0.02f; - public static Texture2D pointerTexture; - - // Hide and show floating text. - public delegate void ChangeFloatieTextDelegate(string text); - public static ChangeFloatieTextDelegate ChangeFloatieText; - public delegate void HideFloatieDelegate(); - public static HideFloatieDelegate HideFloatie; - public delegate void ShowFloatieDelegate(string text); - public static ShowFloatieDelegate ShowFloatie; - public delegate void UpdateAttentionTransformDelegate(Transform attentionTransform); - public static UpdateAttentionTransformDelegate UpdateAttentionTransform; - - public static void Initialize() - { - pointerTexture = new Texture2D(256, 1); - // Note: ImageConversion.LoadImage automatically invokes Apply. - ImageConversion.LoadImage(pointerTexture, File.ReadAllBytes(Main.mod.Path + "tutorial_UI_gradient_opaque.png")); - // Solely based on command line args, so fine to init ASAP. - if (VRManager.IsVREnabled()) - { - ChangeFloatieText = ChangeFloatieTextVR; - HideFloatie = HideFloatVR; - ShowFloatie = ShowFloatVR; - UpdateAttentionTransform = UpdateAttentionTransformVR; - } - else - { - ChangeFloatieText = ChangeFloatieTextNonVR; - HideFloatie = HideFloatNonVR; - ShowFloatie = ShowFloatNonVR; - UpdateAttentionTransform = UpdateAttentionTransformNonVR; - } - } - - public static bool InitFloatieNonVR() - { - GameObject g = GameObject.Find("[NonVRFloatie]"); - if (g == null) - { - if (!floatieSceneLoaded) - { - SceneManager.LoadScene("non_vr_ui_floatie", LoadSceneMode.Additive); - floatieSceneLoaded = true; - Main.mod.Logger.Log("Called load of non VR float scene."); - } - else - Main.mod.Logger.Log("Could not find the non VR float."); - return false; - } - g = GameObject.Instantiate(g); // The tutorial sequence destroys non VR floaties, so make our own. - - floatieNonVR = g.GetComponentInChildren(true)?.gameObject; - if (floatieNonVR == null) - return false; - Main.mod.Logger.Log("Found the non VR float."); - - floatieNonVRText = floatieNonVR.GetComponentInChildren(true); - if (floatieNonVRText == null) - return false; - Main.mod.Logger.Log("Found the non VR text."); - - floatieNonVRLine = floatieNonVR.GetComponentInChildren(true); - if (floatieNonVRLine == null) - return false; - Main.mod.Logger.Log("Found the non VR line."); - return true; - } - - // Non VR floating text. - public static void ChangeFloatieTextNonVR(string text) - { - floatieNonVRText.text = text; - } - - public static void HideFloatNonVR() - { - floatieNonVR.SetActive(false); - floatieNonVRText.text = string.Empty; - floatieNonVRLine.attentionTransform = null; - } - - public static void ShowFloatNonVR(string text) - { - HideFloatNonVR(); - floatieNonVRText.text = text; - floatieNonVR.SetActive(true); - } - - public static void UpdateAttentionTransformNonVR(Transform attentionTransform) - { - // if (floatieNonVR.activeInHierarchy) - floatieNonVRLine.attentionTransform = attentionTransform; - } - - // VR floating text. - public static void ChangeFloatieTextVR(string text) - { - if (floatieVR != null) - floatieVR.GetComponent().UpdateTextExternally(text); - } - - public static void HideFloatVR() - { - if (floatieVR != null) - Object.Destroy(floatieVR); - } - - public static void ShowFloatVR(string text) - { - HideFloatVR(); - if (!string.IsNullOrEmpty(text)) - { - Transform eyesTransform = PlayerManager.PlayerCamera?.transform; - if (eyesTransform == null) - return; - Vector3 position = eyesTransform.position + eyesTransform.forward * 1.5f; - Transform parent; - if (VRManager.IsVREnabled()) - parent = VRTK_DeviceFinder.PlayAreaTransform(); - else // Shouldn't be case for this mod. - parent = (PlayerManager.Car ? PlayerManager.Car.interior : (SingletonBehaviour.Exists ? SingletonBehaviour.Instance.originShiftParent : null)); - floatieVR = (Object.Instantiate(Resources.Load("tutorial_floatie"), position, Quaternion.identity, parent) as GameObject); - floatieVR.GetComponent().UpdateTextExternally(text); - } - } - - public static void UpdateAttentionTransformVR(Transform attentionTransform) - { - if (floatieVR != null) - floatieVR.GetComponent().attentionPoint = attentionTransform; - } - } - - [HarmonyPatch(typeof(FloatieWithAnimation), "Start")] - class FloatieWithAnimation_Start_Patch - { - static void Postfix(LineRenderer ___line) - { - ___line.startWidth = Floaties.START_WIDTH; - ___line.endWidth = Floaties.END_WIDTH; - ___line.material.mainTexture = Floaties.pointerTexture; - } - } - - [HarmonyPatch(typeof(TutorialLineNonVR), "Start")] - class TutorialLineNonVR_Start_Patch - { - static void Postfix(LineRenderer ___line) - { - ___line.startWidth = Floaties.START_WIDTH; - ___line.endWidth = Floaties.END_WIDTH; - ___line.material.mainTexture = Floaties.pointerTexture; - } - } -} diff --git a/DVDispatcherMod/HarmonyPatches/Constants.cs b/DVDispatcherMod/HarmonyPatches/Constants.cs new file mode 100644 index 0000000..e001e8b --- /dev/null +++ b/DVDispatcherMod/HarmonyPatches/Constants.cs @@ -0,0 +1,6 @@ +namespace DVDispatcherMod.HarmonyPatches { + public static class Constants { + public const float START_WIDTH = 0.0025f; // Original = 0.005f; + public const float END_WIDTH = 0.01f; // Original = 0.02f; + } +} \ No newline at end of file diff --git a/DVDispatcherMod/HarmonyPatches/FloatieWithAnimation_Start_Patch.cs b/DVDispatcherMod/HarmonyPatches/FloatieWithAnimation_Start_Patch.cs new file mode 100644 index 0000000..7695f39 --- /dev/null +++ b/DVDispatcherMod/HarmonyPatches/FloatieWithAnimation_Start_Patch.cs @@ -0,0 +1,13 @@ +using Harmony12; +using UnityEngine; + +namespace DVDispatcherMod.HarmonyPatches { + [HarmonyPatch(typeof(FloatieWithAnimation), "Start")] + class FloatieWithAnimation_Start_Patch { + static void Postfix(LineRenderer ___line) { + ___line.startWidth = Constants.START_WIDTH; + ___line.endWidth = Constants.END_WIDTH; + ___line.material.mainTexture = PointerTexture.Texture; + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/HarmonyPatches/PointerTexture.cs b/DVDispatcherMod/HarmonyPatches/PointerTexture.cs new file mode 100644 index 0000000..9325597 --- /dev/null +++ b/DVDispatcherMod/HarmonyPatches/PointerTexture.cs @@ -0,0 +1,16 @@ +using System.IO; +using UnityEngine; + +namespace DVDispatcherMod.HarmonyPatches { + public static class PointerTexture { + public static Texture2D Texture { get; private set; } + + public static void Initialize() { + var pointerTexture = new Texture2D(256, 1); + // Note: ImageConversion.LoadImage automatically invokes Apply. + ImageConversion.LoadImage(pointerTexture, File.ReadAllBytes(Main.ModEntry.Path + "tutorial_UI_gradient_opaque.png")); + + Texture = pointerTexture; + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/HarmonyPatches/TutorialLineNonVR_Start_Patch.cs b/DVDispatcherMod/HarmonyPatches/TutorialLineNonVR_Start_Patch.cs new file mode 100644 index 0000000..1692c84 --- /dev/null +++ b/DVDispatcherMod/HarmonyPatches/TutorialLineNonVR_Start_Patch.cs @@ -0,0 +1,13 @@ +using Harmony12; +using UnityEngine; + +namespace DVDispatcherMod.HarmonyPatches { + [HarmonyPatch(typeof(TutorialLineNonVR), "Start")] + class TutorialLineNonVR_Start_Patch { + static void Postfix(LineRenderer ___line) { + ___line.startWidth = Constants.START_WIDTH; + ___line.endWidth = Constants.END_WIDTH; + ___line.material.mainTexture = PointerTexture.Texture; + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/Interaction.cs b/DVDispatcherMod/Interaction.cs deleted file mode 100644 index fc5a31e..0000000 --- a/DVDispatcherMod/Interaction.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DVDispatcherMod -{ - class Interaction - { - } -} diff --git a/DVDispatcherMod/JobDispatch.cs b/DVDispatcherMod/JobDispatch.cs deleted file mode 100644 index 5b7bf01..0000000 --- a/DVDispatcherMod/JobDispatch.cs +++ /dev/null @@ -1,253 +0,0 @@ -using DV.Logic.Job; -using DV.ServicePenalty; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; - -namespace DVDispatcherMod -{ - public class JobDispatch - { - public Job job { get; private set; } - - private bool jobNotAllowed; - private string jobNotAllowedText; - - private List jobCars; - private Dictionary jobCarsUsed; - private List jobConsists; // Important consists to point at. - // private Trainset jobConsistUsed; // Trainset containing player's last loco. - private int currentTasks; - private List currentTracks; - // private Dictionary destinationTracks; // Check if dropoffs are occupied. - // private List destinationConsists; // Consists blocking the destination tracks. - - public JobDispatch(Job job) - { - this.job = job; - jobCars = new List(); - jobCarsUsed = new Dictionary(); - jobConsists = new List(); - currentTasks = 0; - currentTracks = new List(); - UpdateJobCars(); - UpdateJobPrivilege(); - } - - public void UpdateJobCars() - { - jobCars.Clear(); - jobCarsUsed.Clear(); - jobConsists.Clear(); - // jobConsistUsed = null; - currentTracks.Clear(); - List tasks = GetFirstUnfinishedTasks(); - currentTasks = tasks.Count; - foreach (Task t in tasks) - jobCars.AddRange(t.GetTaskData().cars); - foreach (Car c in jobCars) - jobCarsUsed[c] = false; - foreach (Car car in jobCars) - { - if (!jobCarsUsed[car]) - { - TrackID trackID = car.CurrentTrack?.ID; - if( !SingletonBehaviour.Instance.logicCarToTrainCar.TryGetValue(car, out TrainCar trainCar) ) continue; - - Trainset trainset = trainCar.trainset; - foreach (TrainCar tc in trainset.cars) - { - Car c = tc.logicCar; - if (c != null) - { - if (jobCarsUsed.ContainsKey(c)) - jobCarsUsed[c] = true; - if (trackID == null) - trackID = c.CurrentTrack?.ID; - } - } - jobConsists.Add(trainset); - currentTracks.Add(trackID); - } - } - } - - public void UpdateJobTracks() - { - - } - - // TODO: Find different place to define and call? - public void UpdateJobPrivilege() - { - jobNotAllowed = false; - if (PlayerJobs.Instance.currentJobs.Count >= LicenseManager.GetNumberOfAllowedConcurrentJobs()) - { - jobNotAllowed = true; - jobNotAllowedText = "You already have the maximum number of active jobs."; - } - else if (!LicenseManager.IsLicensedForJob(job.requiredLicenses)) - { - jobNotAllowed = true; - jobNotAllowedText = "You don't have the required license(s) for this job."; - } - else if (!SingletonBehaviour.Instance.IsPlayerAllowedToTakeJob()) - { - jobNotAllowed = true; - jobNotAllowedText = "You still have fees to pay off in the Career Manager."; - } - } - - public List GetFirstUnfinishedTasks() - { - return GetFirstUnfinishedTasks(job.tasks.First()); - } - - /// - /// Figure out the current tasks. - /// - /// - /// - private List GetFirstUnfinishedTasks(Task startingTask) - { - List toReturn = new List(); - if (startingTask != null && startingTask.state == TaskState.InProgress && startingTask.Job == job) - { - List tasks = startingTask.GetTaskData().nestedTasks; - switch (startingTask.InstanceTaskType) - { - case TaskType.Parallel: - foreach (Task t in tasks) - toReturn.AddRange(GetFirstUnfinishedTasks(t)); - break; - case TaskType.Sequential: - foreach (Task t in tasks) - { - if (t.state == TaskState.InProgress) - { - toReturn.AddRange(GetFirstUnfinishedTasks(t)); - break; - } - } - break; - default: - toReturn.Add(startingTask); - break; - } - } - return toReturn; - } - - /// - /// Return the current status of the held job. - /// - /// - /// - public string GetFloatieText(int index) - { - switch (job.State) - { - case JobState.Available: - if (jobNotAllowed) - return jobNotAllowedText; - else if (jobConsists.Count > 1) - { - index %= jobConsists.Count; - StringBuilder sb = new StringBuilder(string.Format("The cars for job {0} are currently in {1} different consists on tracks", job.ID, jobConsists.Count)); - for (int i = 0; i < currentTracks.Count; i++) - { - if (i > 0 && currentTracks.Count > 2) - sb.Append(','); - if (i == currentTracks.Count - 1) - sb.Append(" and"); - sb.Append(' '); - TrackID t = currentTracks[i]; - if (i == index) - { - sb.AppendFormat("{0}", t == null ? "(Unknown)" : t.TrackPartOnly); - } - else - { - sb.Append(t == null ? "(Unknown)" : t.TrackPartOnly); - } - } - sb.Append('.'); - return sb.ToString(); - } - else if (jobConsists.Count == 1) - { - TrackID t = currentTracks[0]; - bool locoHooked = PlayerManager.LastLoco?.trainset == jobConsists[0]; - return string.Format("The cars for job {0} are in the same consist on track {1}{2}.", job.ID, t == null ? "(Unknown)" : t.TrackPartOnly, locoHooked ? " and have a locomotive attached" : ""); - } - else - return "The job cars could not be found... wtf?"; - case JobState.InProgress: - // Pretty much the same thing as JobState.Available except no job eligibility checking. - if (jobConsists.Count > 1) - { - index %= jobConsists.Count; - StringBuilder sb = new StringBuilder(string.Format("The cars for job {0} are currently in {1} different consists on tracks", job.ID, jobConsists.Count)); - for (int i = 0; i < currentTracks.Count; i++) - { - if (i > 0 && currentTracks.Count > 2) - sb.Append(','); - if (i == currentTracks.Count - 1) - sb.Append(" and"); - sb.Append(' '); - TrackID t = currentTracks[i]; - if (i == index) - { - sb.AppendFormat("{0}", t == null ? "(Unknown)" : t.TrackPartOnly); - } - else - { - sb.Append(t == null ? "(Unknown)" : t.TrackPartOnly); - } - } - sb.Append('.'); - return sb.ToString(); - } - else if (jobConsists.Count == 1) - { - TrackID t = currentTracks[0]; - bool locoHooked = PlayerManager.LastLoco?.trainset == jobConsists[0]; - return string.Format("The cars for job {0} are in the same consist on track {1}{2}.", job.ID, t == null ? "(Unknown)" : t.TrackPartOnly, locoHooked ? " and have a locomotive attached" : ""); - } - else if (currentTasks > 0) // No cars in sight but tasks are unfinished. - return "The job cars could not be found... wtf?"; - else // No cars in sight because no tasks found. - return "The job is probably complete. Try turning it in."; - case JobState.Completed: - return "This job is completed."; - case JobState.Abandoned: - return "This job is abandoned."; - case JobState.Failed: - return "This job is failed."; - case JobState.Expired: - return "This job is expired."; - default: - return "This job state is unknown."; - } - } - - /// - /// Return a transform to point to the the nth consist. - /// - /// - /// - public Transform GetPointerAt(int index) - { - if (jobConsists.Count < 1 || jobNotAllowed) - return null; - index %= jobConsists.Count; - Trainset t = jobConsists[index]; - if (t == PlayerManager.LastLoco?.trainset) - return PlayerManager.LastLoco?.transform; - // Get car in middle of set. - // TODO: Raise pointer to middle of car height. - return t.cars[t.cars.Count / 2].transform; - } - } -} diff --git a/DVDispatcherMod/Main.cs b/DVDispatcherMod/Main.cs index 90acbb6..a9e79a1 100644 --- a/DVDispatcherMod/Main.cs +++ b/DVDispatcherMod/Main.cs @@ -1,215 +1,84 @@ -using Harmony12; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; -using UnityModManagerNet; -using VRTK; - -/* - * Visual Studio puts braces on a different line? How do y'all live like this? - * - * Notes: - * - Lists of cars fully vs partially on a logic track are mutually exclusive. - */ -namespace DVDispatcherMod -{ - static class Main - { - // Time between forced dispatcher updates. - public const float POINTER_INTERVAL = 1; - - // Store job booklet page-specific helpful information. - public static Dictionary jobDispatches = new Dictionary(); - - public static UnityModManager.ModEntry mod; - private static bool floatLoaded; - private static bool showFloat; - - static bool Load(UnityModManager.ModEntry modEntry) - { - mod = modEntry; - HarmonyInstance harmony = HarmonyInstance.Create(modEntry.Info.Id); - harmony.PatchAll(Assembly.GetExecutingAssembly()); - mod.OnToggle = OnToggle; - mod.OnUpdate = OnUpdate; - floatLoaded = false; - showFloat = true; - Floaties.Initialize(); - return true; - } - - // TODO: Make sure OnToggle works. - static bool OnToggle(UnityModManager.ModEntry _, bool active) - { - showFloat = active; - mod.Logger.Log(string.Format("showFloats toggled to {0}.", showFloat)); - if (!showFloat) - { - Floaties.HideFloatie(); - holdingRight = null; - // TODO: Check which job data needs to be reset. - showing = false; - } - return true; - } - - private static bool listenersSetup = false; - - // Job Holdings - private static bool showing = false; - private static int counter = 0; - private static float timer = 0; - // private static IDispatch holdingLeft; - private static JobDispatch holdingRight; - - static void OnUpdate(UnityModManager.ModEntry mod, float delta) - { - timer += delta; - if (!listenersSetup) - { - // eyesTransform = PlayerManager.PlayerCamera.transform; - if (VRManager.IsVREnabled()) - { - VRTK_InteractGrab lGrab = VRTK_DeviceFinder.GetControllerLeftHand(true)?.transform.GetComponentInChildren(); - VRTK_InteractGrab rGrab = VRTK_DeviceFinder.GetControllerRightHand(true)?.transform.GetComponentInChildren(); - if (lGrab == null || rGrab == null) - return; - lGrab.ControllerGrabInteractableObject += OnItemGrabbedLeftVR; - lGrab.ControllerStartUngrabInteractableObject += OnItemUngrabbedLeftVR; - rGrab.ControllerGrabInteractableObject += OnItemGrabbedRightVR; - rGrab.ControllerStartUngrabInteractableObject += OnItemUngrabbedRightVR; - } - else - { - if (LoadingScreenManager.IsLoading || !WorldStreamingInit.IsLoaded || !SingletonBehaviour.Instance) - return; - else if (!floatLoaded) - { - floatLoaded = Floaties.InitFloatieNonVR(); - return; - } - Grabber grab = PlayerManager.PlayerTransform?.GetComponentInChildren(); - if (grab == null || SingletonBehaviour.Instance == null) - return; - grab.Grabbed += OnItemGrabbedRightNonVR; - grab.Released += OnItemUngrabbedRightNonVR; - SingletonBehaviour.Instance.ItemAddedToInventory += OnItemAddedToInventory; - } - - mod.Logger.Log(string.Format("Floaties have been set up, total time elapsed: {0:0.00} seconds.", timer)); - listenersSetup = true; - } - else - { - if (showing) - { - if (holdingRight == null || !showFloat) - { - Floaties.HideFloatie(); - showing = false; - } - else - { - if (timer > POINTER_INTERVAL) - { - counter++; - timer %= POINTER_INTERVAL; - // Calculate tracks that cars are on. - holdingRight.UpdateJobCars(); - holdingRight.UpdateJobPrivilege(); - Floaties.ChangeFloatieText(holdingRight.GetFloatieText(counter)); - Floaties.UpdateAttentionTransform(holdingRight.GetPointerAt(counter)); - } - } - } - else if (!showing && holdingRight != null && showFloat) - { - // TODO: Read job information and process text accordingly. - Floaties.ShowFloatie(holdingRight.GetFloatieText(counter)); - Floaties.UpdateAttentionTransform(holdingRight.GetPointerAt(counter)); - showing = true; - timer = 0; - } - } - } - - // Actual Grab Handlers - static void OnItemGrabbedLeft(InventoryItemSpec iis) - { - if (iis == null) - return; - // mod.Logger.Log(string.Format("Picked up a(n) {0} in the left hand.", iis.itemName)); - // JobOverview jo = iis.GetComponent(); - // if (jo != null) - // holdingLeft = jo.job; - } - - static void OnItemGrabbedRight(InventoryItemSpec iis) - { - if (iis == null) - return; - // mod.Logger.Log(string.Format("Picked up a(n) {0} in the right hand.", iis.itemName)); - JobOverview jo = iis.GetComponent(); - if (jo != null) - { - holdingRight = new JobDispatch(jo.job); - showing = false; - } - else - { - JobBooklet jb = iis.GetComponent(); - if (jb != null) - { - holdingRight = new JobDispatch(jb.job); - showing = false; - } - } - } - - static void OnItemUngrabbedLeft(InventoryItemSpec iis) - { - // holdingLeft = null; - } - - static void OnItemUngrabbedRight(InventoryItemSpec iis) - { - holdingRight = null; - } - - // Grab Listeners - static void OnItemAddedToInventory(GameObject o, int _) - { - OnItemUngrabbedRight(o?.GetComponent()); - } - - static void OnItemGrabbedRightNonVR(GameObject o) - { - OnItemGrabbedRight(o?.GetComponent()); - } - - static void OnItemUngrabbedRightNonVR(GameObject o) - { - OnItemUngrabbedRight(o?.GetComponent()); - } - - static void OnItemGrabbedLeftVR(object sender, ObjectInteractEventArgs e) - { - OnItemGrabbedLeft(e.target?.GetComponent()); - } - - static void OnItemGrabbedRightVR(object sender, ObjectInteractEventArgs e) - { - OnItemGrabbedRight(e.target?.GetComponent()); - } - - static void OnItemUngrabbedLeftVR(object sender, ObjectInteractEventArgs e) - { - OnItemUngrabbedLeft(e.target?.GetComponent()); - } - - static void OnItemUngrabbedRightVR(object sender, ObjectInteractEventArgs e) - { - OnItemUngrabbedRight(e.target?.GetComponent()); - } - } -} +using Harmony12; +using System.Reflection; +using DVDispatcherMod.DispatcherHintManagers; +using DVDispatcherMod.DispatcherHintShowers; +using DVDispatcherMod.HarmonyPatches; +using DVDispatcherMod.PlayerInteractionManagers; +using UnityModManagerNet; + +/* + * Visual Studio puts braces on a different line? How do y'all live like this? + * + * Notes: + * - Lists of cars fully vs partially on a logic track are mutually exclusive. + */ +namespace DVDispatcherMod { + static class Main { + // Time between forced dispatcher updates. + private const float POINTER_INTERVAL = 1; + + private static float _timer; + private static int _counter; + + private static DispatcherHintManager _dispatcherHintManager; + + public static UnityModManager.ModEntry ModEntry { get; private set; } + + static bool Load(UnityModManager.ModEntry modEntry) { + ModEntry = modEntry; + var harmony = HarmonyInstance.Create(modEntry.Info.Id); + harmony.PatchAll(Assembly.GetExecutingAssembly()); + ModEntry.OnToggle = OnToggle; + ModEntry.OnUpdate = OnUpdate; + + PointerTexture.Initialize(); + + return true; + } + + // TODO: Make sure OnToggle works. + static bool OnToggle(UnityModManager.ModEntry _, bool isEnabled) { + if (_dispatcherHintManager != null) { + _dispatcherHintManager.SetIsEnabled(isEnabled); + } + + ModEntry.Logger.Log(string.Format("isEnabled toggled to {0}.", isEnabled)); + + return true; + } + + static void OnUpdate(UnityModManager.ModEntry mod, float delta) { + _timer += delta; + + if (_dispatcherHintManager == null) { + _dispatcherHintManager = TryCreateDispatcherHintManager(); + if (_dispatcherHintManager == null) { + return; + } + + mod.Logger.Log(string.Format("Floaties have been set up, total time elapsed: {0:0.00} seconds.", _timer)); + } + + if (_timer > POINTER_INTERVAL) { + _counter++; + _timer %= POINTER_INTERVAL; + + _dispatcherHintManager.SetCounter(_counter); + } + } + + private static DispatcherHintManager TryCreateDispatcherHintManager() { + if (VRManager.IsVREnabled()) { + var playerInteractionManager = VRPlayerInteractionManagerFactory.TryCreate(); + if (playerInteractionManager == null) { + return null; + } + + var dispatchHintShower = new VRDispatchHintShower(); + return new DispatcherHintManager(playerInteractionManager, dispatchHintShower); + } else { + return NonVRDispatchHintManagerFactory.TryCreate(); + } + } + } +} diff --git a/DVDispatcherMod/PlayerInteractionManagers/IPlayerInteractionManager.cs b/DVDispatcherMod/PlayerInteractionManagers/IPlayerInteractionManager.cs new file mode 100644 index 0000000..9604cdb --- /dev/null +++ b/DVDispatcherMod/PlayerInteractionManagers/IPlayerInteractionManager.cs @@ -0,0 +1,9 @@ +using System; +using DV.Logic.Job; + +namespace DVDispatcherMod.PlayerInteractionManagers { + public interface IPlayerInteractionManager { + Job JobOfInterest { get; } + event Action JobOfInterestChanged; + } +} \ No newline at end of file diff --git a/DVDispatcherMod/PlayerInteractionManagers/NonVRPlayerInteractionManager.cs b/DVDispatcherMod/PlayerInteractionManagers/NonVRPlayerInteractionManager.cs new file mode 100644 index 0000000..801d6ab --- /dev/null +++ b/DVDispatcherMod/PlayerInteractionManagers/NonVRPlayerInteractionManager.cs @@ -0,0 +1,75 @@ +using System; +using DV.Logic.Job; +using UnityEngine; + +namespace DVDispatcherMod.PlayerInteractionManagers { + public class NonVRPlayerInteractionManager : IPlayerInteractionManager { + private readonly Grabber _grabber; + private Job _grabbedJob; + private Job _hoveredJob; + + public NonVRPlayerInteractionManager(Grabber grabber, Inventory inventory) { + _grabber = grabber; + grabber.Grabbed += HandleGrabbed; + grabber.Released += _ => HandleHeldItemReleased(); + grabber.Hovered += HandleHovered; + grabber.Unhovered += _ => HandleUnhovered(); + inventory.ItemAddedToInventory += (go, i) => HandleHeldItemReleased(); + } + + public Job JobOfInterest => _grabbedJob ?? _hoveredJob; + + public event Action JobOfInterestChanged; + + private void HandleGrabbed(GameObject gameObject) { + var inventoryItemSpec = gameObject?.GetComponent(); + if (inventoryItemSpec != null) { + var job = TryGetJobFromInventoryItemSpec(inventoryItemSpec); + if (job != null) { + _grabbedJob = job; + JobOfInterestChanged?.Invoke(); + } + } + } + + private void HandleHeldItemReleased() { + _grabbedJob = null; + JobOfInterestChanged?.Invoke(); + } + + private void HandleHovered(GameObject gameObject) { + if (gameObject != null) { + var job = TryGetJobFromGameObject(gameObject); + if (job != null) { + _hoveredJob = job; + JobOfInterestChanged?.Invoke(); + } + } + } + + private void HandleUnhovered() { + _hoveredJob = null; + JobOfInterestChanged?.Invoke(); + } + + private static Job TryGetJobFromInventoryItemSpec(InventoryItemSpec inventoryItemSpec) { + if (inventoryItemSpec.GetComponent() is JobOverview jo) { + return jo.job; + } + if (inventoryItemSpec.GetComponent() is JobBooklet jb) { + return jb.job; + } + return null; + } + + private static Job TryGetJobFromGameObject(GameObject gameObject) { + if (gameObject.GetComponent() is JobOverview jo) { + return jo.job; + } + if (gameObject.GetComponent() is JobBooklet jb) { + return jb.job; + } + return null; + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/PlayerInteractionManagers/NonVRPlayerInteractionManagerFactory.cs b/DVDispatcherMod/PlayerInteractionManagers/NonVRPlayerInteractionManagerFactory.cs new file mode 100644 index 0000000..15dfc25 --- /dev/null +++ b/DVDispatcherMod/PlayerInteractionManagers/NonVRPlayerInteractionManagerFactory.cs @@ -0,0 +1,12 @@ +namespace DVDispatcherMod.PlayerInteractionManagers { + public static class NonVRPlayerInteractionManagerFactory { + public static IPlayerInteractionManager TryCreate() { + var grabber = PlayerManager.PlayerTransform?.GetComponentInChildren(); + var inventory = SingletonBehaviour.Instance; + if (grabber == null || inventory == null) { + return null; + } + return new NonVRPlayerInteractionManager(grabber, inventory); + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/PlayerInteractionManagers/VRPlayerInteractionManager.cs b/DVDispatcherMod/PlayerInteractionManagers/VRPlayerInteractionManager.cs new file mode 100644 index 0000000..b63a56e --- /dev/null +++ b/DVDispatcherMod/PlayerInteractionManagers/VRPlayerInteractionManager.cs @@ -0,0 +1,45 @@ +using System; +using DV.Logic.Job; +using VRTK; + +namespace DVDispatcherMod.PlayerInteractionManagers { + internal class VRPlayerInteractionManager : IPlayerInteractionManager { + public VRPlayerInteractionManager(VRTK_InteractGrab interactGrab) { + interactGrab.ControllerGrabInteractableObject += HandleControllerGrabInteractableObject; + interactGrab.ControllerStartUngrabInteractableObject += HandleControllerStartUngrabInteractableObject; + } + + public Job JobOfInterest { get; private set; } + + public event Action JobOfInterestChanged; + + private void HandleControllerGrabInteractableObject(object sender, ObjectInteractEventArgs e) { + var inventoryItemSpec = e.target?.GetComponent(); + if (inventoryItemSpec != null) { + var job = TryGetJobFromInventoryItemSpec(inventoryItemSpec); + if (job != null) { + JobOfInterest = job; + JobOfInterestChanged?.Invoke(); + } + } + } + + private void HandleControllerStartUngrabInteractableObject(object sender, ObjectInteractEventArgs e) { + if (JobOfInterest != null) { + JobOfInterest = null; + JobOfInterestChanged?.Invoke(); + } + } + + + private static Job TryGetJobFromInventoryItemSpec(InventoryItemSpec inventoryItemSpec) { + if (inventoryItemSpec.GetComponent() is JobOverview jo) { + return jo.job; + } + if (inventoryItemSpec.GetComponent() is JobBooklet jb) { + return jb.job; + } + return null; + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/PlayerInteractionManagers/VRPlayerInteractionManagerFactory.cs b/DVDispatcherMod/PlayerInteractionManagers/VRPlayerInteractionManagerFactory.cs new file mode 100644 index 0000000..1a267a5 --- /dev/null +++ b/DVDispatcherMod/PlayerInteractionManagers/VRPlayerInteractionManagerFactory.cs @@ -0,0 +1,16 @@ +using VRTK; + +namespace DVDispatcherMod.PlayerInteractionManagers { + internal static class VRPlayerInteractionManagerFactory { + public static IPlayerInteractionManager TryCreate() { + // this could probably be extended to support both hands easily by handling both grabbers and defining a priority hand. + var rGrab = VRTK_DeviceFinder.GetControllerRightHand(true)?.transform.GetComponentInChildren(); + if (rGrab == null) { + return null; + } + + return new VRPlayerInteractionManager(rGrab); + } + + } +} \ No newline at end of file diff --git a/DVDispatcherMod/Properties/AssemblyInfo.cs b/DVDispatcherMod/Properties/AssemblyInfo.cs index 17b78a5..43e813a 100644 --- a/DVDispatcherMod/Properties/AssemblyInfo.cs +++ b/DVDispatcherMod/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/DVDispatcherMod/TaskOverviewGenerator.cs b/DVDispatcherMod/TaskOverviewGenerator.cs new file mode 100644 index 0000000..f717f43 --- /dev/null +++ b/DVDispatcherMod/TaskOverviewGenerator.cs @@ -0,0 +1,66 @@ +using System.Linq; +using System.Text; +using DV.Logic.Job; + +namespace DVDispatcherMod { + public static class TaskOverviewGenerator { + public static string GetTaskOverview(Job job) { + var stringBuilder = new StringBuilder(); + GenerateTaskOverview(0, job.tasks.First(), stringBuilder); + return stringBuilder.ToString(); + } + + private static void GenerateTaskOverview(int indent, Task task, StringBuilder sb) { + AppendTaskLine(indent, task, sb); + + if (task.InstanceTaskType == TaskType.Parallel || task.InstanceTaskType == TaskType.Sequential) { + var taskData = task.GetTaskData(); + + foreach (var nestedTask in taskData.nestedTasks) { + GenerateTaskOverview(indent + 1, nestedTask, sb); + } + } + } + + private static void AppendTaskLine(int indent, Task task, StringBuilder sb) { + var taskData = task.GetTaskData(); + if (task.state == TaskState.Done) { + sb.Append(""); + } else if (task.state == TaskState.Failed) { + sb.Append(""); + } + + if (task.InstanceTaskType == TaskType.Parallel) { + AppendIndented(indent, "Parallel", sb); + } else if (task.InstanceTaskType == TaskType.Sequential) { + AppendIndented(indent, "Sequential", sb); + } else if (task.InstanceTaskType == TaskType.Transport) { + AppendIndented(indent, $"Transport {taskData.cars.Count} cars from {taskData.startTrack.ID.TrackPartOnly} to {taskData.destinationTrack.ID.TrackPartOnly}", sb); + } else if (task.InstanceTaskType == TaskType.Warehouse) { + if (taskData.warehouseTaskType == WarehouseTaskType.Loading) { + AppendIndented(indent, $"Load {taskData.cars.Count} at {taskData.destinationTrack.ID.TrackPartOnly}", sb); + } else if (taskData.warehouseTaskType == WarehouseTaskType.Unloading) { + AppendIndented(indent, $"Unload {taskData.cars.Count} at {taskData.destinationTrack.ID.TrackPartOnly}", sb); + } else { + AppendIndented(indent, "(unknown WarehouseTaskType)", sb); + } + } else { + AppendIndented(indent, "(unknown TaskType)", sb); + } + + if (task.state == TaskState.Done || task.state == TaskState.Failed) { + sb.Append(""); + } + + sb.AppendLine(); + } + + private static void AppendIndented(int indent, string value, StringBuilder sb) { + for (var i = 0; i < indent; i += 1) { + sb.Append(" "); + } + sb.Append("- "); + sb.Append(value); + } + } +} \ No newline at end of file diff --git a/DVDispatcherMod/Utils.cs b/DVDispatcherMod/Utils.cs deleted file mode 100644 index 7311b7b..0000000 --- a/DVDispatcherMod/Utils.cs +++ /dev/null @@ -1,175 +0,0 @@ -using DV.Logic.Job; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace DVDispatcherMod -{ - class Utils - { - public static string ColorText(Color color, string text) - { - string r = ((int)(color.r * 256.0f)).ToString("X2"); - string g = ((int)(color.g * 256.0f)).ToString("X2"); - string b = ((int)(color.b * 256.0f)).ToString("X2"); - return string.Format("{3}", r, g, b, text); - } - - /// - /// Use a hex string preceded by a '#' for the color field. - /// e.g. #C0FFEE - /// - /// - /// - /// - public static string ColorText(string color, string text) - { - return string.Format("{1}", color, text); - } - - public static TrainCar GetClosestCar(List group) - { - if (group.Count < 1) - return null; - TrainCar closestCar = null; - float closestDist = float.MaxValue; - foreach (Car car in group) - { - if (!SingletonBehaviour.Instance.logicCarToTrainCar.TryGetValue(group.First(), out TrainCar trainCar)) - continue; - float dist = (trainCar.transform.position - PlayerManager.PlayerTransform.position).sqrMagnitude; - if (closestDist > dist) - { - closestCar = trainCar; - closestDist = dist; - } - } - return closestCar; - } - - public static TrainCar GetClosestFirstLastCar(List group) - { - if (group.Count < 1) - return null; - if (!SingletonBehaviour.Instance.logicCarToTrainCar.TryGetValue(group.First(), out TrainCar first)) - return null; - if (!SingletonBehaviour.Instance.logicCarToTrainCar.TryGetValue(group.Last(), out TrainCar last)) - return first; - float dist = (first.transform.position - PlayerManager.PlayerTransform.position).sqrMagnitude; - if (dist > (last.transform.position - PlayerManager.PlayerTransform.position).sqrMagnitude) - return last; - return first; - } - - /// - /// Look up logic cars by carId. - /// - /// - /// - /// - public static List LookupCars(Job job, HashSet carIds) - { - Dictionary s2c = new Dictionary(); - foreach (Task task in job.tasks) - { - LookupCars(task, carIds, s2c); - if (carIds.Count == s2c.Count) - break; - } - return new List(s2c.Values); - } - - private static void LookupCars(Task task, HashSet carIds, Dictionary s2c) - { - TaskData taskData = task.GetTaskData(); - switch (task.InstanceTaskType) - { - case TaskType.Parallel: - case TaskType.Sequential: - foreach (Task next in task.GetTaskData().nestedTasks) - { - LookupCars(next, carIds, s2c); - if (carIds.Count == s2c.Count) - break; - } - break; - default: - foreach (Car car in taskData.cars) - { - if (carIds.Contains(car.ID)) - s2c.Add(car.ID, car); - if (carIds.Count == s2c.Count) - break; - } - break; - } - } - - /// - /// Uses TrackID.TrackPartOnly for the trackId. - /// - /// - /// - /// - /// - public static Track LookupTrack(Job job, string yardId, string trackId) - { - foreach (Task task in job.tasks) - { - Track track = LookupTrack(task, yardId, trackId); - if (track != null) - return track; - } - return null; - } - - private static Track LookupTrack(Task task, string yardId, string trackId) - { - TaskData taskData = task.GetTaskData(); - switch (task.InstanceTaskType) - { - case TaskType.Parallel: - case TaskType.Sequential: - foreach (Task next in task.GetTaskData().nestedTasks) - { - Track track = LookupTrack(next, yardId, trackId); - if (track != null) - return track; - } - break; - default: - Track start = taskData.startTrack; - Track dest = taskData.destinationTrack; - if (start != null && start.ID.yardId == yardId && start.ID.TrackPartOnly == trackId) - return start; - else if (dest != null && dest.ID.yardId == yardId && dest.ID.TrackPartOnly == trackId) - return dest; - break; - } - return null; - } - - public static string PlaceSuffix(int place) - { - switch (place % 100) - { - case 11: - case 12: - case 13: - return "th"; - default: - switch (place % 10) - { - case 1: - return "st"; - case 2: - return "nd"; - case 3: - return "rd"; - default: - return "th"; - } - } - } - } -}