diff --git a/Mapify/CarLabeler/CreateLabel.cs b/Mapify/CarLabeler/CreateLabel.cs new file mode 100644 index 00000000..4c120184 --- /dev/null +++ b/Mapify/CarLabeler/CreateLabel.cs @@ -0,0 +1,137 @@ +using CommsRadioAPI; +using DV; +using UnityEngine; + +namespace Mapify.CarLabeler +{ + public class CreateLabel: SubState + { + private TrainCar pointedCar; + private RaycastHit[] hits = new RaycastHit[5]; + + private static bool isSetup; + private static GameObject trainHighlighter; + private static Material selectionMaterial; + private static MeshRenderer trainHighlighterRender; + + public CreateLabel(YardDestination aDestination) : + base(aDestination, + $"Point at a car and click to label it.\n" + + $"{aDestination.StationName()}-{aDestination.YardID}-{aDestination.TrackNumber}" + ) + { + if(isSetup) return; + DoSetup(); + } + + private static void DoSetup() + { + var deleter = (CommsRadioCarDeleter)ControllerAPI.GetVanillaMode(VanillaMode.Clear); + + if (deleter == null) + { + Mapify.LogError($"{nameof(CreateLabel)}: Could not get deleter"); + return; + } + + trainHighlighter = deleter.trainHighlighter; + selectionMaterial = deleter.selectionMaterial; + trainHighlighterRender = deleter.trainHighlighterRender; + + isSetup = true; + } + + public override AStateBehaviour OnAction(CommsRadioUtility utility, InputAction action) + { + switch (action) + { + case InputAction.Activate: + MarkCar(utility); + goto default; //C#, why... + default: + return new CreateLabel(destination); + } + } + + private void MarkCar(CommsRadioUtility utility) + { + if (pointedCar == null){ + utility.PlaySound(VanillaSoundCommsRadio.Warning); + return; + } + + var labelComponent = pointedCar.gameObject.GetComponent(); + if (labelComponent == null) + { + labelComponent = pointedCar.gameObject.AddComponent(); + } + + labelComponent.Setup(destination); + utility.PlaySound(VanillaSoundCommsRadio.Confirm); + } + + public override AStateBehaviour OnUpdate(CommsRadioUtility utility) + { + TrainCar car = null; + + // Why does the direction need to be downward? I would expect forward. + if(Physics.RaycastNonAlloc(utility.SignalOrigin.position, -utility.SignalOrigin.up, hits, maxDistance: 100f) > 0) + { + foreach (var hit in hits) + { + if (hit.transform == null) continue; + + car = TrainCar.Resolve(hit.transform.root); + if (car == null) continue; + + Mapify.LogDebug($"hit car {car.gameObject.name}"); + Mapify.LogDebug($"layer: {car.gameObject.layer}"); + break; + } + } + + PointToCar(car, utility); + return this; + } + + private void PointToCar(TrainCar car, CommsRadioUtility utility) + { + if (pointedCar == car) return; + + pointedCar = car; + ClearHighlightCar(); + + if (pointedCar == null) return; + + HighlightCar(pointedCar); + utility.PlaySound(VanillaSoundCommsRadio.HoverOver); + } + + private static void HighlightCar(TrainCar car) + { + Mapify.LogDebug($"{nameof(HighlightCar)}: {car}"); + + trainHighlighterRender.material = selectionMaterial; + var transform = trainHighlighter.transform; + var bounds = car.Bounds; + var vector3_1 = bounds.size + CommsRadioCarDeleter.HIGHLIGHT_BOUNDS_EXTENSION; + transform.localScale = vector3_1; + var vector3_2 = car.transform.up * (trainHighlighter.transform.localScale.y / 2f); + var forward = car.transform.forward; + bounds = car.Bounds; + double z = bounds.center.z; + var vector3_3 = forward * (float) z; + trainHighlighter.transform.SetPositionAndRotation(car.transform.position + vector3_2 + vector3_3, car.transform.rotation); + trainHighlighter.SetActive(true); + trainHighlighter.transform.SetParent(car.transform, true); + } + + private static void ClearHighlightCar() + { + Mapify.LogDebug($"{nameof(ClearHighlightCar)}"); + + trainHighlighter.SetActive(false); + trainHighlighter.transform.SetParent(null); + } + } +} diff --git a/Mapify/CarLabeler/SelectStation.cs b/Mapify/CarLabeler/SelectStation.cs new file mode 100644 index 00000000..647d8f12 --- /dev/null +++ b/Mapify/CarLabeler/SelectStation.cs @@ -0,0 +1,50 @@ +using CommsRadioAPI; + +namespace Mapify.CarLabeler +{ + public class SelectStation: SubState + { + public SelectStation(YardDestination aDestination) : + base(aDestination, $"station:\n{aDestination.StationName()}" + ) + {} + + public override AStateBehaviour OnAction(CommsRadioUtility utility, InputAction action) + { + switch (action) + { + case InputAction.Activate: + utility.PlaySound(VanillaSoundCommsRadio.Confirm); + return new SelectYard(destination); + case InputAction.Up: + utility.PlaySound(VanillaSoundCommsRadio.Switch); + return NextOrPreviousStation(1); + case InputAction.Down: + utility.PlaySound(VanillaSoundCommsRadio.Switch); + return NextOrPreviousStation(-1); + default: + return new SelectStation(destination); + } + } + + private AStateBehaviour NextOrPreviousStation(int stationIndexDelta) + { + var allStations = StationController.allStations; + var stationIndex = 0; + + for (int index = 0; index < allStations.Count; index++) + { + if (allStations[index].stationInfo.YardID != destination.StationID) continue; + + stationIndex = index; + break; + } + + stationIndex = Utils.Misc.BetterModulo(stationIndex + stationIndexDelta, allStations.Count); + + destination.StationID = allStations[stationIndex].stationInfo.YardID; + + return new SelectStation(destination); + } + } +} diff --git a/Mapify/CarLabeler/SelectTrack.cs b/Mapify/CarLabeler/SelectTrack.cs new file mode 100644 index 00000000..ebfd38f8 --- /dev/null +++ b/Mapify/CarLabeler/SelectTrack.cs @@ -0,0 +1,61 @@ +using System.Linq; +using CommsRadioAPI; +using Mapify.Utils; + +namespace Mapify.CarLabeler +{ + public class SelectTrack: SubState + { + public SelectTrack(YardDestination aDestination) : + base(aDestination, $"track: {aDestination.TrackNumber}" + ) + {} + + public override AStateBehaviour OnAction(CommsRadioUtility utility, InputAction action) + { + switch (action) + { + case InputAction.Activate: + utility.PlaySound(VanillaSoundCommsRadio.Confirm); + return new CreateLabel(destination); + case InputAction.Up: + utility.PlaySound(VanillaSoundCommsRadio.Switch); + return NextOrPreviousTrack(1); + case InputAction.Down: + utility.PlaySound(VanillaSoundCommsRadio.Switch); + return NextOrPreviousTrack(-1); + default: + return new SelectTrack(destination); + } + } + + private AStateBehaviour NextOrPreviousTrack(int trackIndexDelta) + { + //TODO cache this + var yardTracks = RailTrackRegistry.Instance + .GetTrackNumbersOfSubYard(destination.StationID, destination.YardID) + .ToList(); + + Mapify.LogDebug("yardTracks:"); + foreach (var track in yardTracks) + { + Mapify.LogDebug(track); + } + + var trackIndex = 0; + + for (int index = 0; index < yardTracks.Count; index++) + { + if (yardTracks[index] != destination.TrackNumber) continue; + + trackIndex = index; + break; + } + + trackIndex = Utils.Misc.BetterModulo(trackIndex + trackIndexDelta, yardTracks.Count); + destination.TrackNumber = yardTracks[trackIndex]; + + return new SelectTrack(destination); + } + } +} diff --git a/Mapify/CarLabeler/SelectYard.cs b/Mapify/CarLabeler/SelectYard.cs new file mode 100644 index 00000000..47f205c4 --- /dev/null +++ b/Mapify/CarLabeler/SelectYard.cs @@ -0,0 +1,59 @@ +using System.Linq; +using CommsRadioAPI; +using Mapify.Utils; + +namespace Mapify.CarLabeler +{ + public class SelectYard: SubState + { + public SelectYard(YardDestination aDestination) : + base(aDestination, $"yard: {aDestination.YardID}" + ) + {} + + public override AStateBehaviour OnAction(CommsRadioUtility utility, InputAction action) + { + switch (action) + { + case InputAction.Activate: + utility.PlaySound(VanillaSoundCommsRadio.Confirm); + return new SelectTrack(destination); + case InputAction.Up: + utility.PlaySound(VanillaSoundCommsRadio.Switch); + return NextOrPreviousYard(1); + case InputAction.Down: + utility.PlaySound(VanillaSoundCommsRadio.Switch); + return NextOrPreviousYard(-1); + default: + return new SelectYard(destination); + } + } + + private AStateBehaviour NextOrPreviousYard(int yardIndexDelta) + { + //TODO cache this + var stationYards = RailTrackRegistry.Instance.GetSubYardIDsOfYard(destination.StationID).ToList(); + + Mapify.LogDebug("stationYards:"); + foreach (var yard in stationYards) + { + Mapify.LogDebug(yard); + } + + var yardIndex = 0; + + for (int index = 0; index < stationYards.Count; index++) + { + if (stationYards[index] != destination.YardID) continue; + + yardIndex = index; + break; + } + + yardIndex = Utils.Misc.BetterModulo(yardIndex + yardIndexDelta, stationYards.Count); + destination.YardID = stationYards[yardIndex]; + + return new SelectYard(destination); + } + } +} diff --git a/Mapify/CarLabeler/Start.cs b/Mapify/CarLabeler/Start.cs new file mode 100644 index 00000000..703c1457 --- /dev/null +++ b/Mapify/CarLabeler/Start.cs @@ -0,0 +1,32 @@ +using CommsRadioAPI; + +namespace Mapify.CarLabeler +{ + public class Start: AStateBehaviour + { + public Start() : base(new CommsRadioState( + titleText: SubState.LABELER_TITLE, + contentText: "enable yard car labeler?" + )) + {} + + public override AStateBehaviour OnAction(CommsRadioUtility utility, InputAction action) + { + switch (action) + { + case InputAction.Activate: + utility.PlaySound(VanillaSoundCommsRadio.ModeEnter); + return new SelectStation(SetupDestination()); + default: + return new Start(); + } + } + + private static YardDestination SetupDestination() + { + var aDestination = Mapify.Settings.lastUsedLabel; + YardDestination.Validate(ref aDestination); + return aDestination; + } + } +} diff --git a/Mapify/CarLabeler/SubState.cs b/Mapify/CarLabeler/SubState.cs new file mode 100644 index 00000000..ac6ddfb0 --- /dev/null +++ b/Mapify/CarLabeler/SubState.cs @@ -0,0 +1,22 @@ +using CommsRadioAPI; +using DV; + +namespace Mapify.CarLabeler +{ + public abstract class SubState: AStateBehaviour + { + public const string LABELER_TITLE = "car labeler"; + + protected YardDestination destination; + + protected SubState(YardDestination aDestination, string contentText_) : + base(new CommsRadioState( + titleText: LABELER_TITLE, + buttonBehaviour: ButtonBehaviourType.Override, + contentText: contentText_ + )) + { + destination = aDestination; + } + } +} diff --git a/Mapify/CarLabeler/YardDestination.cs b/Mapify/CarLabeler/YardDestination.cs new file mode 100644 index 00000000..a887dfda --- /dev/null +++ b/Mapify/CarLabeler/YardDestination.cs @@ -0,0 +1,66 @@ +using System.Linq; +using Mapify.Utils; +using UnityEngine; + +namespace Mapify.CarLabeler +{ + public class YardDestinationComponent : MonoBehaviour + { + public YardDestination d; + + public void Setup(YardDestination other) + { + d = new YardDestination + { + StationID = other.StationID, + YardID = other.YardID, + TrackNumber = other.TrackNumber + }; + } + } + + public class YardDestination + { + // identifies a station + public string StationID; + // identifies a yard in a station + public string YardID; + // identifies a track in a yard in a station + public int TrackNumber; + + public string StationName() + { + var stationController = StationController.GetStationByYardID(StationID); + return stationController == null ? "NULL" : stationController.stationInfo.Name; + } + + /// + /// if any of the fields are invalid, set them to something valid + /// + public static void Validate(ref YardDestination aDestination) + { + if (aDestination == null) + { + aDestination = new YardDestination(); + } + + //TODO stations/yards without tracks? + if (StationController.GetStationByYardID(aDestination.StationID) == null) + { + aDestination.StationID = StationController.allStations.Select(station => station.stationInfo.YardID).FirstOrDefault(); + } + + var stationYardIDs = RailTrackRegistry.Instance.GetSubYardIDsOfYard(aDestination.StationID).ToList(); + if (!stationYardIDs.Contains(aDestination.YardID)) + { + aDestination.YardID = stationYardIDs.FirstOrDefault(); + } + + var yardTrackNumbers = RailTrackRegistry.Instance.GetTrackNumbersOfSubYard(aDestination.StationID, aDestination.YardID).ToList(); + if (!yardTrackNumbers.Contains(aDestination.TrackNumber)) + { + aDestination.TrackNumber = yardTrackNumbers.FirstOrDefault(); + } + } + } +} diff --git a/Mapify/Components/CarDeleter_r.cs b/Mapify/Components/CarDeleter_r.cs new file mode 100644 index 00000000..dfcb379d --- /dev/null +++ b/Mapify/Components/CarDeleter_r.cs @@ -0,0 +1,34 @@ +using System.Linq; +using DV.Utils; +using UnityEngine; + +namespace Mapify.Components +{ + public class CarDeleter_r: MonoBehaviour + { + private RailTrack railTrack; + + private void Start() + { + railTrack = GetComponent(); + } + + private void Update() + { + if(!railTrack.BogiesOnTrack().Any()) return; + + var carToDelete = railTrack.BogiesOnTrack().Select(bogie => bogie._car).First(); + + Mapify.LogDebug($"{nameof(CarDeleter_r)}: Deleting {carToDelete.name}"); + + //copied from DV.CommsRadioCarDeleter.OnUse + SingletonBehaviour.Instance.DeleteCar(carToDelete); + SingletonBehaviour.Instance.ClearInvalidCarReferencesAfterManualDelete(); + if (carToDelete != null) + { + carToDelete.gameObject.SetActive(false); + carToDelete.interior.gameObject.SetActive(false); + } + } + } +} diff --git a/Mapify/Components/Retarder_r.cs b/Mapify/Components/Retarder_r.cs new file mode 100644 index 00000000..a2e78bc3 --- /dev/null +++ b/Mapify/Components/Retarder_r.cs @@ -0,0 +1,44 @@ +using System.Linq; +using Mapify.Editor; +using UnityEngine; + +namespace Mapify.Components +{ + public class Retarder_r: MonoBehaviour + { + private float maxSpeed; // meter per second + private RailTrack railTrack; + private float brakeForce; //Newton + + private void Start() + { + var retarderValues = GetComponent(); + if (!retarderValues) + { + Mapify.LogError($"Can't find {nameof(Retarder)} on {gameObject.name}"); + Destroy(this); + return; + } + + railTrack = GetComponent(); + + maxSpeed = retarderValues.maxSpeedKMH / 3.6f; // to m/s + brakeForce = retarderValues.brakeForce; + } + + private void FixedUpdate() + { + foreach (var car in railTrack.BogiesOnTrack().Select(bogie => bogie._car)) + { + if(car.GetAbsSpeed() <= maxSpeed) { return; } + + var forwardSpeed = car.GetForwardSpeed(); + var force3D = car.transform.forward * (-brakeForce * Mathf.Sign(forwardSpeed)); + car.rb.AddForce(force3D); + + Mapify.LogDebugExtreme(() => $"{nameof(Retarder_r)} force {force3D}"); + Mapify.LogDebugExtreme(() => $"{nameof(Retarder_r)} forwardSpeed {forwardSpeed}"); + } + } + } +} diff --git a/Mapify/Components/TimedCarSpawner_r.cs b/Mapify/Components/TimedCarSpawner_r.cs new file mode 100644 index 00000000..1ff8ef54 --- /dev/null +++ b/Mapify/Components/TimedCarSpawner_r.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using DV; +using DV.ThingTypes; +using DV.Utils; +using Mapify.Editor; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace Mapify.Components +{ + public class TimedCarSpawner_r: MonoBehaviour + { + private float SpawnInterval; + private bool EnableHandBrakeOnSpawn; + private RailTrack spawnTrack; + private TrainCarType_v2[] trainCarTypes; + + private void Start() + { + var carSpawnerValues = GetComponent(); + if (!carSpawnerValues) + { + Mapify.LogError($"Can't find {nameof(TimedCarSpawner)} on {gameObject.name}"); + Destroy(this); + return; + } + + spawnTrack = GetComponent(); + SpawnInterval = carSpawnerValues.SpawnInterval; + + var TrainCarIDs = carSpawnerValues.TrainCarTypes.Select(type => Enum.GetName(type.GetType(), type)).ToArray(); + trainCarTypes = TrainCarIDs.Select(carID => Globals.G.types._carTypesById[carID]).ToArray(); + + foreach (var trainCarType in trainCarTypes) + { + if (!trainCarType.liveries.Any()) + { + Mapify.LogError($"trainCarType {trainCarType} has no liveries"); + } + } + + EnableHandBrakeOnSpawn = carSpawnerValues.EnableHandBrakeOnSpawn; + + StartCoroutine(Spawn()); + } + + private IEnumerator Spawn() + { + while (true) + { + while (spawnTrack.BogiesOnTrack().Any()) + { + yield return null; + } + + yield return new WaitForSeconds(SpawnInterval); + + while (spawnTrack.BogiesOnTrack().Any()) + { + yield return null; + } + + var nextCar = trainCarTypes[Random.Range(0, trainCarTypes.Length)]; + var nextLivery = nextCar.liveries[Random.Range(0, nextCar.liveries.Count)]; + + Mapify.LogDebug($"Spawning {nextCar.id} with livery {nextLivery.id}"); + + var trainCarList = SingletonBehaviour.Instance.SpawnCarTypesOnTrack( + new List{ nextLivery }, + null, + spawnTrack, + true, + EnableHandBrakeOnSpawn + ); + + if (trainCarList == null || !trainCarList.Any()) + { + Mapify.LogError($"Car spawning failed! Is the track long enough to fit the traincar?"); + } + else + { + Mapify.LogDebug($"Spawn result:"); + foreach (var spawnedCar in trainCarList) + { + Mapify.LogDebug($"{spawnedCar.carType} {spawnedCar.carLivery.id}"); + } + } + } + } + } +} diff --git a/Mapify/Components/YardController_r.cs b/Mapify/Components/YardController_r.cs new file mode 100644 index 00000000..3692de2b --- /dev/null +++ b/Mapify/Components/YardController_r.cs @@ -0,0 +1,155 @@ +using System.Collections.Generic; +using System.Linq; +using Mapify.Editor; +using Mapify.Utils; +using UnityEngine; + +namespace Mapify.Components +{ + public class YardController_r: MonoBehaviour + { + // private enum SortStrategy + // { + // Label, + // CarType + // } + + private RailTrack detectorTrack; + private Junction rootJunction; + + private string stationID; + private string yardID; + + private Dictionary trackNumberToCarID = new Dictionary(); + + private bool hasBeenSetup = false; + + /// + /// + /// The first junction in the tree of junctions (switches) + /// + public void Setup(Junction rootJunction_, YardController yardControllerValues) + { + detectorTrack = yardControllerValues.DetectorTrack.GetComponent(); + rootJunction = rootJunction_; + + stationID = yardControllerValues.StationID; + yardID = yardControllerValues.YardID; + + hasBeenSetup = true; + } + + private void Start() + { + if (!hasBeenSetup) + { + Mapify.LogError($"{nameof(YardController_r)} on {gameObject.name} has not been setup"); + Destroy(this); + } + + foreach (var trackNumber in RailTrackRegistry.Instance.GetTrackNumbersOfSubYard(stationID, yardID)) + { + trackNumberToCarID.Add((byte)trackNumber, ""); + } + + if (trackNumberToCarID.Any()) return; + + Mapify.LogError($"{nameof(YardController_r)}: could not find track numbers for yard {stationID}-{yardID}"); + Destroy(this); + } + + private void Update() + { + var detectedCar = detectorTrack.BogiesOnTrack().Select(bogie => bogie._car).FirstOrDefault(); + if(!detectedCar) return; + + var carTypeID = detectedCar.carLivery.parentType.id; + + foreach (var pair in trackNumberToCarID) + { + if(pair.Value != carTypeID) continue; + Mapify.LogDebug($"{carTypeID} -> {stationID}-{yardID}-{pair.Key}"); + SetSwitches(pair.Key); + return; + } + + foreach (var pair in trackNumberToCarID) + { + if(pair.Value != "") continue; + Mapify.LogDebug($"{carTypeID} -> {stationID}-{yardID}-{pair.Key}"); + trackNumberToCarID[pair.Key] = carTypeID; + SetSwitches(pair.Key); + return; + } + + Mapify.LogError($"All {trackNumberToCarID.Values.Count} tracks taken"); + foreach (var pair in trackNumberToCarID) + { + Mapify.LogDebug($"{pair.Key} -> {pair.Value}"); + } + } + + // set the switches, so that they form a path to trackNumber + private void SetSwitches(byte trackNumber) + { + var goal = RailTrackRegistry.Instance.GetRailTrack(stationID, yardID, trackNumber); + + if (goal == null) + { + Mapify.LogError($"{nameof(SetSwitches)}: could not find track {stationID}/{yardID}/{trackNumber}"); + return; + } + + //TODO cache the paths in Start? + var start = rootJunction.inBranch.track; + var path = PathFinder.FindPath(start, goal); + + if (!path.Any()) + { + Mapify.LogError($"{nameof(SetSwitches)}: could not find path from {start.name} to {goal.name}"); + return; + } + + Mapify.LogDebug($"{nameof(SetSwitches)}: path:"); + for (var index = 1; index < path.Count; index++) + { + var track = path[index]; + Mapify.LogDebug($"{track.name}"); + + //if the track has an inJunction, it is a switch + if (track.inJunction == null) continue; + + var previousTrack = path[index - 1]; + + var outBranches = track.inJunction.outBranches; + var outBranchNumber = GetBranchForTrack(outBranches, previousTrack); + + if(track.inJunction.selectedBranch == outBranchNumber){ continue; } + + track.inJunction.SwitchTo(outBranchNumber, Junction.SwitchMode.REGULAR); + } + } + + /// Returns the index of the branch that connects to the track + private byte GetBranchForTrack(List outBranches, RailTrack track) + { + for (byte branchIndex = 0; branchIndex < outBranches.Count; branchIndex++) + { + if (outBranches[branchIndex].track.outBranch.track == track) + { + return branchIndex; + } + } + + Mapify.LogError($"{nameof(GetBranchForTrack)}: track {track} was not found in outBranches"); + + Mapify.LogDebug("outbranch tracks:"); + foreach (var branch in outBranches) + { + Mapify.LogDebug(branch.track.outBranch.track.name); + } + + return 0; + } + } +} diff --git a/Mapify/Map/MapLifeCycle.cs b/Mapify/Map/MapLifeCycle.cs index b331c3e7..34f856a5 100644 --- a/Mapify/Map/MapLifeCycle.cs +++ b/Mapify/Map/MapLifeCycle.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using CommsRadioAPI; using DV.CashRegister; using DV.Utils; using Mapify.Editor; diff --git a/Mapify/Mapify.cs b/Mapify/Mapify.cs index 068ddac3..cc181085 100644 --- a/Mapify/Mapify.cs +++ b/Mapify/Mapify.cs @@ -12,7 +12,7 @@ namespace Mapify public static class Mapify { private static UnityModManager.ModEntry ModEntry { get; set; } - private static Settings Settings; + public static Settings Settings { get; private set; } private const string LOCALE_FILE = "locale.csv"; internal static Harmony Harmony { get; private set; } @@ -58,15 +58,25 @@ private static void Patch() #region Logging public static void LogDebugExtreme(Func resolver) + { + LogDebugExtreme(resolver.Invoke()); + } + + public static void LogDebugExtreme(object msg) { if (Settings.ExtremelyVerboseLogging) - LogDebug(resolver); + LogDebug(msg); } public static void LogDebug(Func resolver) + { + LogDebug(resolver.Invoke()); + } + + public static void LogDebug(object msg) { if (Settings.VerboseLogging) - ModEntry.Logger.Log($"[Debug] {resolver.Invoke()}"); + ModEntry.Logger.Log($"[Debug] {msg}"); } public static void Log(object msg) diff --git a/Mapify/Mapify.csproj b/Mapify/Mapify.csproj index 2cc1751a..95e672ab 100644 --- a/Mapify/Mapify.csproj +++ b/Mapify/Mapify.csproj @@ -12,10 +12,12 @@ - + + + @@ -43,16 +45,18 @@ - + + + @@ -66,11 +70,13 @@ + + @@ -81,6 +87,7 @@ + diff --git a/Mapify/Patches/CommsRadioControllerPatch.cs b/Mapify/Patches/CommsRadioControllerPatch.cs index cd6acde4..4a258116 100644 --- a/Mapify/Patches/CommsRadioControllerPatch.cs +++ b/Mapify/Patches/CommsRadioControllerPatch.cs @@ -1,7 +1,10 @@ using System.Linq; +using CommsRadioAPI; using DV; using HarmonyLib; +using Mapify.CarLabeler; using Mapify.Map; +using UnityEngine; namespace Mapify.Patches { @@ -19,4 +22,38 @@ private static void Postfix(CommsRadioController __instance) __instance.allModes = __instance.allModes.Where(mode => mode.GetType() != typeof(CommsRadioCrewVehicle)).ToList(); } } + + /// + /// Create the yard car labeler mode in the comms radio + /// + [HarmonyPatch(typeof(CommsRadioController), nameof(CommsRadioController.Start))] + public static class CommsRadioController_Start_Patch + { + private static bool isRadioSetup = false; + + private static void Postfix() + { + if (isRadioSetup) return; + + CommsRadioMode.Create(new Start(), laserColor: Color.blue); + isRadioSetup = true; + } + } + + /// + /// By default, scrolling down activates the 'up' button and scrolling up activates the 'down' button. This patch corrects this. + /// + [HarmonyPatch(typeof(CommsRadioController), nameof(CommsRadioController.OnScrolled))] + public static class CommsRadioController_OnScrolled_Patch + { + private static bool Prefix(CommsRadioController __instance, ScrollAction direction) + { + if (direction.IsPositive()) + __instance.OnActionA(); + else + __instance.OnActionB(); + + return false; + } + } } diff --git a/Mapify/Patches/TurntableControllerPatch.cs b/Mapify/Patches/TurntableControllerPatch.cs index 795708f1..1261e3fa 100644 --- a/Mapify/Patches/TurntableControllerPatch.cs +++ b/Mapify/Patches/TurntableControllerPatch.cs @@ -1,5 +1,6 @@ using System.Reflection; using HarmonyLib; +using Mapify.Editor; using Mapify.Utils; using UnityEngine; @@ -16,7 +17,7 @@ private static bool Prefix(Transform handle) return false; BoxCollider collider = handle.GetComponent(); if (collider != null) - TurntableController_Field_PUSH_HANDLE_HALF_EXTENTS.SetValue(null, collider.size.Add(0.05f)); + TurntableController_Field_PUSH_HANDLE_HALF_EXTENTS.SetValue(null, collider.size.Add(Track.TURNTABLE_SEARCH_RANGE)); return true; } } diff --git a/Mapify/Patches/VisualSwitchPatch.cs b/Mapify/Patches/VisualSwitchPatch.cs new file mode 100644 index 00000000..1a204982 --- /dev/null +++ b/Mapify/Patches/VisualSwitchPatch.cs @@ -0,0 +1,75 @@ +using System.Collections; +using HarmonyLib; +using UnityEngine; + +namespace Mapify.Patches +{ + /// + /// Makes the visual part of the switch work with switches that have more than 2 branches. + /// + [HarmonyPatch(typeof(VisualSwitch), nameof(VisualSwitch.PlayAnimation))] + public class VisualSwitch_PlayAnimation_Patch + { + private static readonly int Speed = Animator.StringToHash("speed"); + private const int ANIMATION_LAYER = 0; + private const string STATE_NAME = "junction"; + + private static bool Prefix(VisualSwitch __instance) + { + if (__instance.junction.outBranches.Count <= 2) return true; + + if (!(bool)(Object)__instance.animator) return false; + + __instance.EnableAnimator(); + + var previousTime = __instance.animator.GetCurrentAnimatorStateInfo(ANIMATION_LAYER).normalizedTime; + var newTime = map( + __instance.junction.selectedBranch, + 0, __instance.junction.outBranches.Count - 1, + 0, 1 + ); + + var playForward = newTime > previousTime; + + // set animation direction + var speed = playForward ? __instance.speedMult : -__instance.speedMult; + __instance.animator.SetFloat(Speed, speed); + + // play animation + __instance.animator.Play(STATE_NAME, ANIMATION_LAYER, previousTime); + + __instance.StopAllCoroutines(); + var routine = Pause(newTime, playForward, __instance.animator); + __instance.StartCoroutine(routine); + + return false; + } + + private static IEnumerator Pause(float pauseTime, bool playForward, Animator animator) + { + if (playForward) + { + while (animator.GetCurrentAnimatorStateInfo(ANIMATION_LAYER).normalizedTime < pauseTime) + { + yield return null; + } + } + else + { + while (animator.GetCurrentAnimatorStateInfo(ANIMATION_LAYER).normalizedTime > pauseTime) + { + yield return null; + } + } + + // pause + animator.SetFloat(Speed, 0); + + Mapify.Log($"{nameof(Pause)} {animator.GetCurrentAnimatorStateInfo(ANIMATION_LAYER).normalizedTime} PAUSED"); + } + + private static float map(float value, float in_min, float in_max, float out_min, float out_max) { + return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + } +} diff --git a/Mapify/SceneInitializers/Railway/LocomotiveSpawnerSetup.cs b/Mapify/SceneInitializers/Railway/LocomotiveSpawnerSetup.cs index ef8cbe01..41dcc766 100644 --- a/Mapify/SceneInitializers/Railway/LocomotiveSpawnerSetup.cs +++ b/Mapify/SceneInitializers/Railway/LocomotiveSpawnerSetup.cs @@ -26,6 +26,12 @@ public static void SetupLocomotiveSpawner(LocomotiveSpawner spawner) ).ToList() ) ).ToList(); + + if (!locoSpawner.locoTypeGroupsToSpawn.Any()) + { + Mapify.LogError($"{nameof(LocomotiveSpawnerSetup)} locoTypeGroupsToSpawn is empty. {nameof(spawner.condensedLocomotiveTypes)}: {spawner.condensedLocomotiveTypes}"); + } + spawner.gameObject.SetActive(wasActive); } } diff --git a/Mapify/SceneInitializers/Railway/TrackSetup.cs b/Mapify/SceneInitializers/Railway/TrackSetup.cs index 7c1f1564..790819ff 100644 --- a/Mapify/SceneInitializers/Railway/TrackSetup.cs +++ b/Mapify/SceneInitializers/Railway/TrackSetup.cs @@ -1,29 +1,34 @@ using System.Collections.Generic; using System.Linq; -using DV; +using Mapify.Components; using Mapify.Editor; +using Mapify.Editor.Utils; using Mapify.Utils; using UnityEngine; +using Object = UnityEngine.Object; namespace Mapify.SceneInitializers.Railway { [SceneSetupPriority(int.MinValue)] public class TrackSetup : SceneSetup { + private const string IN_JUNCTION_NAME = "in_junction"; + public override void Run() { - Track[] tracks = Object.FindObjectsOfType().Where(t => !t.IsSwitch).ToArray(); + var allTracks = Object.FindObjectsOfType(); + var nonSwitchTracks = allTracks.Where(t => !t.IsSwitch).ToArray(); Mapify.LogDebug(() => "Creating RailTracks"); - CreateRailTracks(tracks); + CreateRailTracks(nonSwitchTracks, false); Mapify.LogDebug(() => "Creating Junctions"); CreateJunctions(); - tracks.SetActive(true); + nonSwitchTracks.SetActive(true); Mapify.LogDebug(() => "Connecting tracks"); - ConnectTracks(tracks); + ConnectTracks(allTracks); AlignAllTrackEnds(); TestConnections(); @@ -84,41 +89,207 @@ private void TestConnections() Debug.LogError("Problems found when checking connections, see errors above"); } - private static void CreateRailTracks(IEnumerable tracks) + private static List CreateRailTracks(IEnumerable tracks, bool setActive) { - foreach (Track track in tracks) + return tracks.Select(track => CreateRailTrack(track, setActive)).ToList(); + } + + private static RailTrack CreateRailTrack(Track track, bool setActive) + { + track.gameObject.SetActive(setActive); + if (!track.IsSwitch && !track.IsTurntable) + track.name = track.LogicName; + var railTrack = track.gameObject.AddComponent(); + railTrack.generateColliders = !track.IsTurntable; + railTrack.dontChange = false; + railTrack.age = (int)track.age; + railTrack.ApplyRailType(); + + if (track.TryGetComponent(out _)) + { + track.gameObject.AddComponent(); + } + + if (track.TryGetComponent(out _)) + { + track.gameObject.AddComponent(); + } + + if (track.TryGetComponent(out _)) { - track.gameObject.SetActive(false); - if (!track.IsSwitch && !track.IsTurntable) - track.name = track.LogicName; - RailTrack railTrack = track.gameObject.AddComponent(); - railTrack.generateColliders = !track.IsTurntable; - railTrack.dontChange = false; - railTrack.age = (int)track.age; - railTrack.ApplyRailType(); + track.gameObject.AddComponent(); } + + return railTrack; } private static void CreateJunctions() { - foreach (Switch sw in Object.FindObjectsOfType()) + CreateCustomSwitches(); + CreateVanillaSwitches(); + } + + private static void CreateCustomSwitches() + { + foreach (var customSwitch in Object.FindObjectsOfType()) + { + CreateCustomSwitch(customSwitch); + } + } + + private static void CreateCustomSwitch(CustomSwitch customSwitch) + { + // we use SwitchRight because with SwitchLeft the animation would be mirrored + var vanillaAsset = customSwitch.standSide == CustomSwitch.StandSide.LEFT ? VanillaAsset.SwitchRightOuterSign : VanillaAsset.SwitchRight; + + var prefabClone = AssetCopier.Instantiate(vanillaAsset); + prefabClone.transform.position = customSwitch.transform.position; + + //Junction + var inJunction = prefabClone.GetComponentInChildren(); + inJunction.transform.position = customSwitch.GetJointPoint().transform.position; + inJunction.selectedBranch = customSwitch.defaultBranch; + + SetupStalk(prefabClone, customSwitch.GetJointPoint()); + DestroyPrefabTracks(prefabClone); + CreateSwitchTracks(customSwitch, prefabClone, inJunction); + + foreach (var track in customSwitch.GetTracks()) + { + track.gameObject.SetActive(true); + } + + YardControllerSetup(prefabClone, customSwitch, inJunction); + } + + private static void DestroyPrefabTracks(GameObject prefabClone) + { + // must be destroyed immediately to prevent: + // "Junction 'in_junction' doesn't have track '[track diverging]' assigned" + // from RailManager.TestConnections + Object.DestroyImmediate(prefabClone.FindChildByName(Switch.THROUGH_TRACK_NAME)); + Object.DestroyImmediate(prefabClone.FindChildByName(Switch.DIVERGING_TRACK_NAME)); + } + + private static void CreateSwitchTracks(CustomSwitch customSwitch, GameObject prefabClone, Junction switchJunction) + { + var railTracksInSwitch = CreateRailTracks( + customSwitch.GetTracks(), false + ); + + if (!railTracksInSwitch.Any()) + { + Mapify.LogError($"{nameof(CreateCustomSwitches)}: {nameof(railTracksInSwitch)} is empty"); + return; + } + + switchJunction.outBranches = new List(); + + foreach (var trackInSwitch in railTracksInSwitch) + { + trackInSwitch.transform.SetParent(prefabClone.transform, true); + + trackInSwitch.inBranch = new Junction.Branch(); + trackInSwitch.inBranch.track = null; + trackInSwitch.inJunction = switchJunction; + + switchJunction.outBranches.Add(new Junction.Branch(trackInSwitch, true)); + } + + //track before the switch + switchJunction.inBranch = switchJunction.FindClosestBranch(railTracksInSwitch[0].curve[0].transform.position); + + //connect the track before the switch to the switch + if (switchJunction.inBranch.first) + { + switchJunction.inBranch.track.inJunction = switchJunction; + } + else + { + switchJunction.inBranch.track.outJunction = switchJunction; + } + + if (switchJunction.inBranch == null) + { + Mapify.LogError($"{nameof(CreateSwitchTracks)}: inBranch is null"); + } + } + + private static void SetupStalk(GameObject prefabClone, BezierPoint joinPoint) + { + //visual objects + var graphical = prefabClone.FindChildByName("Graphical").transform; + string[] toDelete = {"ballast", "anchors", "sleepers", "rails_static", "rails_moving"}; + + foreach (var child in graphical.GetChildren()) { - Transform swTransform = sw.transform; - VanillaAsset vanillaAsset = sw.GetComponent().asset; + if (!toDelete.Contains(child.name)) continue; + Object.Destroy(child.gameObject); + } + + var graphicalY = graphical.localPosition.y; + + var switch_base = graphical.FindChildByName("switch_base"); + if (!switch_base) + { + Mapify.LogError("Could not find switch_base"); + return; + } + + //interactable objects + var switchTrigger = prefabClone.FindChildByName("SwitchTrigger").transform; + if (!switchTrigger) + { + Mapify.LogError("Could not find SwitchTrigger"); + return; + } + + //position + var transformHelper = new GameObject("transformHelper").transform; + transformHelper.SetParent(prefabClone.transform, false); + + transformHelper.position = switch_base.position; + graphical.SetParent(transformHelper, true); + switchTrigger.SetParent(transformHelper, true); + transformHelper.position = joinPoint.position; + + transformHelper.localPosition += new Vector3(0, graphicalY, 0); + + //rotation + var trackDirection = (joinPoint.globalHandle2 - joinPoint.position).normalized; + var rotationDelta = Quaternion.FromToRotation(transformHelper.forward, trackDirection); + transformHelper.Rotate(0, rotationDelta.eulerAngles.y, 0); //the stalk will sometimes flip upside down if we apply all axis + + //next to the track + switchTrigger.localPosition -= new Vector3(graphical.localPosition.x, 0, 0); + graphical.localPosition -= new Vector3(graphical.localPosition.x, 0, 0); + + //get rid of the helper object + graphical.SetParent(prefabClone.transform, true); + switchTrigger.SetParent(prefabClone.transform, true); + GameObject.Destroy(transformHelper.gameObject); + } + + private static void CreateVanillaSwitches() + { + foreach (Switch switch_ in Object.FindObjectsOfType()) + { + Transform swTransform = switch_.transform; + VanillaAsset vanillaAsset = switch_.GetComponent().asset; GameObject prefabClone = AssetCopier.Instantiate(vanillaAsset, false); Transform prefabCloneTransform = prefabClone.transform; - Transform inJunction = prefabCloneTransform.Find("in_junction"); - Vector3 offset = prefabCloneTransform.position - inJunction.position; + Transform junctionTransform = prefabCloneTransform.Find(IN_JUNCTION_NAME); + Vector3 offset = prefabCloneTransform.position - junctionTransform.position; foreach (Transform child in prefabCloneTransform) child.transform.position += offset; prefabCloneTransform.SetPositionAndRotation(swTransform.position, swTransform.rotation); - Junction junction = inJunction.GetComponent(); - junction.selectedBranch = (byte) (sw.IsLeft - ? sw.defaultState == Switch.StandSide.THROUGH + Junction junction = junctionTransform.GetComponent(); + junction.selectedBranch = (byte) (switch_.IsLeft + ? switch_.defaultState == Switch.StandSide.THROUGH ? 1 : 0 - : sw.defaultState == Switch.StandSide.THROUGH + : switch_.defaultState == Switch.StandSide.THROUGH ? 0 : 1 ); @@ -128,19 +299,40 @@ private static void CreateJunctions() prefabClone.transform.SetParent(RailTrackRegistry.Instance.TrackRootParent); prefabClone.SetActive(true); + + YardControllerSetup(prefabClone, switch_, junction); } } + private static void YardControllerSetup(GameObject prefabClone, SwitchBase switch_, Junction junction) + { + if (!switch_.gameObject.TryGetComponent(typeof(YardController), out Component scp)) return; + + var sc = prefabClone.AddComponent(); + sc.Setup(junction, (YardController)scp); + } + private static void ConnectTracks(IEnumerable tracks) { foreach (Track track in tracks) { + //vanilla switches are connected elsewhere + if(track.IsVanillaSwitch) continue; + RailTrack railTrack = track.GetComponent(); - if (railTrack.isJunctionTrack) - continue; - railTrack.ConnectInToClosestJunctionOrBranch(); - railTrack.ConnectOutToClosestJunctionOrBranch(); + if (track.IsCustomSwitch) + { + railTrack.ConnectOutToClosestBranch(); + + if (railTrack.outBranch != null) continue; + Mapify.LogError($"{nameof(ConnectTracks)}: {nameof(railTrack.outBranch)} is null for custom switch track {track.name}"); + } + else + { + railTrack.ConnectInToClosestJunctionOrBranch(); + railTrack.ConnectOutToClosestJunctionOrBranch(); + } } } } diff --git a/Mapify/Settings.cs b/Mapify/Settings.cs index ab66e682..2bae7ec6 100644 --- a/Mapify/Settings.cs +++ b/Mapify/Settings.cs @@ -11,6 +11,8 @@ public class Settings : UnityModManager.ModSettings public bool VerboseLogging; public bool ExtremelyVerboseLogging; + public CarLabeler.YardDestination lastUsedLabel; + public void Draw(UnityModManager.ModEntry modEntry) { #region Hidden Settings diff --git a/Mapify/Utils/Extensions.cs b/Mapify/Utils/Extensions.cs index 11102b98..6d331a88 100644 --- a/Mapify/Utils/Extensions.cs +++ b/Mapify/Utils/Extensions.cs @@ -277,6 +277,91 @@ public static void SetBasicMapInfo(this SaveGameData saveGameData, BasicMapInfo saveGameData.SetJObject(SAVE_KEY_NAME, JObject.FromObject(basicMapInfo)); } + public static RailTrack GetRailTrack(this RailTrackRegistryBase registry, string stationID, string yardID, byte trackNumber) + { + var query = $"[{stationID}]_[{yardID}-{trackNumber:D2}"; + + return registry.AllTracks.FirstOrDefault(track => track.name.Contains(query)); + } + + /// + /// Returns the subyard IDs of all subyards in the yard(station) with ID yardID + /// + public static IEnumerable GetSubYardIDsOfYard(this RailTrackRegistryBase registry, string yardID) + { + return registry.AllTracks + .Select(railTrack => railTrack.LogicTrack().ID) + .Where(ID => ID.yardId == yardID) + .Select(ID => ID.subYardId) + .Distinct(); + } + + /// + /// Returns the track numbers of all track in the subyard with ID subYardID in the yard(station) with yardID + /// + public static IEnumerable GetTrackNumbersOfSubYard(this RailTrackRegistryBase registry, string yardID, string subYardID) + { + return registry.AllTracks + .Select(railTrack => railTrack.LogicTrack().ID) + .Where(ID => ID.yardId == yardID && + ID.subYardId == subYardID) + .Select(ID => ID.orderNumber) + .Where(trackNumberString => trackNumberString != "") + .Select(int.Parse) + .Distinct(); + } + + public static void SwitchTo(this Junction junction, byte branchNumber, Junction.SwitchMode switchMode) + { + Mapify.LogDebug($"junction {junction.name} switch to {branchNumber}"); + + junction.selectedBranch = (byte) Misc.BetterModulo(branchNumber - 1, junction.outBranches.Count); + junction.Switch(switchMode); + } + + public static Junction.Branch FindClosestBranch(this Junction junction, Vector3 fromPoint, float maxRange = 5f) + { + var closestDistance = float.PositiveInfinity; + + RailTrack track = null; + var first = false; + + foreach (var foundTrack in Resources.FindObjectsOfTypeAll()) + { + // skip the tracks in the junction + if(junction.outBranches.Any(branch => branch.track == foundTrack)) continue; + + if (!foundTrack.curve || foundTrack.curve.pointCount < 2) continue; + + var firstPoint = foundTrack.curve[0]; + + var distanceToFirst = Vector3.SqrMagnitude(fromPoint - firstPoint.position); + if (distanceToFirst < maxRange * (double) maxRange && distanceToFirst < (double) closestDistance) + { + closestDistance = distanceToFirst; + track = foundTrack; + first = true; + } + + var lastPoint = foundTrack.curve.Last(); + var distanceToLast = Vector3.SqrMagnitude(fromPoint - lastPoint.position); + if (distanceToLast < maxRange * (double) maxRange && distanceToLast < (double) closestDistance) + { + closestDistance = distanceToLast; + track = foundTrack; + first = false; + } + } + + if (track == null) + { + Mapify.LogError($"Failed to find closest branch for {junction.name}"); + return null; + } + + return new Junction.Branch(track, first); + } + #endregion #region Mapify diff --git a/Mapify/Utils/Misc.cs b/Mapify/Utils/Misc.cs new file mode 100644 index 00000000..2e476b30 --- /dev/null +++ b/Mapify/Utils/Misc.cs @@ -0,0 +1,11 @@ +namespace Mapify.Utils +{ + public static class Misc + { + // modulo operator but it works for negative numbers + // https://stackoverflow.com/a/1082938 + public static int BetterModulo(int x, int m) { + return (x%m + m)%m; + } + } +} diff --git a/Mapify/Utils/PathFinder.cs b/Mapify/Utils/PathFinder.cs new file mode 100644 index 00000000..b57f6f99 --- /dev/null +++ b/Mapify/Utils/PathFinder.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Linq; +using Priority_Queue; +// using UnityAsync; + +// Source, by WallyCZ: +// https://github.com/WallyCZ/DVRouteManager/blob/master/DVRouteManager/PathFinder.cs + +namespace Mapify.Utils +{ + public static class PathFinder + { + // Heuristic that computes approximate distance between two rails + private static float Heuristic(RailTrack a, RailTrack b) + { + return (a.transform.position - b.transform.position).sqrMagnitude; //we don't need exact distance because that result is used only as a priority + } + + public struct JunctionSetting + { + public Junction junction; + public int outBranchNr; + } + + private class RailTrackNode : GenericPriorityQueueNode + { + public RailTrack track; + + public RailTrackNode(RailTrack track) + { + this.track = track; + } + } + + // Return a List of Locations representing the found path + public static List FindPath(RailTrack start, RailTrack goal) + { + var path = new List(); + var cameFrom = Astar(start, goal); + + var current = goal; + + // travel backwards through the path + while (!current.Equals(start)) + { + if (!cameFrom.ContainsKey(current)) + { + Mapify.LogError($"cameFrom does not contain current {current.LogicTrack().ID.FullID}"); + return null; + } + + path.Add(current); + current = cameFrom[current]; + } + + if (path.Count > 0) + { + path.Add(start); + } + + return path; + } + + /// + /// A* search + /// + private static Dictionary Astar(RailTrack start, RailTrack goal) + { + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + + var queue = new GenericPriorityQueue(10000); + queue.Enqueue(new RailTrackNode(start), 0.0); + + cameFrom.Add(start, start); + costSoFar.Add(start, 0.0); + + while (queue.Count > 0) + { + var current = queue.Dequeue().track; + + cameFrom.TryGetValue(current, out var prev); + + var debug = $"ID: {current.LogicTrack().ID.FullID} Prev: {prev?.LogicTrack().ID.FullID}"; + + var neighbors = new List(); + + if (current.outIsConnected) + { + neighbors.AddRange(current.GetAllOutBranches().Select(b => b.track)); + } + + if (current.inIsConnected) + { + neighbors.AddRange(current.GetAllInBranches().Select(b => b.track)); + } + + // var branches = DumpNodes(neighbors, current); + // debug += "\n" + $"all branches: {branches}"; + + Mapify.LogDebugExtreme(debug); + + foreach (var neighbor in neighbors) + { + //if we could go through junction directly (without reversing) + if (!current.CanGoToDirectly(prev, neighbor)) + { + Mapify.LogDebugExtreme($"{neighbor.LogicTrack().ID.FullID} reverse needed"); + continue; + } + + // compute exact cost + var newCost = costSoFar[current] + neighbor.LogicTrack().length; + +// If there's no cost assigned to the neighbor yet, or if the new +// cost is lower than the assigned one, add newCost for this neighbor + if (costSoFar.ContainsKey(neighbor) && !(newCost < costSoFar[neighbor])) continue; + + // If we're replacing the previous cost, remove it + if (costSoFar.ContainsKey(neighbor)) + { + costSoFar.Remove(neighbor); + cameFrom.Remove(neighbor); + } + + Mapify.LogDebugExtreme($"neighbor {neighbor.LogicTrack().ID.FullID} update {newCost}"); + + costSoFar.Add(neighbor, newCost); + cameFrom.Add(neighbor, current); + var priority = newCost + Heuristic(neighbor, goal) + / 20.0f; //convert distance to time (t = s / v) + queue.Enqueue(new RailTrackNode(neighbor), priority); + } + } + + return cameFrom; + } + } +} diff --git a/Mapify/Utils/RailTrackExtensions.cs b/Mapify/Utils/RailTrackExtensions.cs new file mode 100644 index 00000000..d6c4cc65 --- /dev/null +++ b/Mapify/Utils/RailTrackExtensions.cs @@ -0,0 +1,71 @@ +using System.Linq; + +namespace Mapify.Utils +{ + // Source, by WallyCZ: + // https://github.com/WallyCZ/DVRouteManager/blob/master/DVRouteManager/PathFinder.cs + + public static class RailTrackExtensions + { + /// + /// If we can go through junction without reversing + /// + private static bool CanGoThroughJunctionDirectly(this RailTrack current, Junction junction, RailTrack from, RailTrack to) + { + var fromIsOutBranch = junction != null && junction.outBranches.Any(b => b.track == from); + + if (fromIsOutBranch) + { + return false; + } + + var currentIsOutBranch = junction != null && junction.outBranches.Any(b => b.track == current); + + if (currentIsOutBranch) + { + return junction.inBranch.track == to; + } + + return true; + } + + public static bool CanGoToDirectly(this RailTrack current, RailTrack from, RailTrack to) + { + Junction reversingJunction; + return CanGoToDirectly(current, from, to, out reversingJunction); + } + + public static bool CanGoToDirectly(this RailTrack current, RailTrack from, RailTrack to, out Junction reversingJunction) + { + reversingJunction = null; + + var isInJuction = current.inIsConnected && current.GetAllInBranches().Any(b => b.track == to); + var isOutJuction = current.outIsConnected && current.GetAllOutBranches().Any(b => b.track == to); + + if (current.inIsConnected) + { + if (isInJuction && CanGoThroughJunctionDirectly(current, current.inJunction, from, to)) + return true; + } + + if (current.outIsConnected) + { + if (isOutJuction && CanGoThroughJunctionDirectly(current, current.outJunction, from, to)) + return true; + + } + + if (isInJuction) + { + reversingJunction = current.inJunction; + } + + if (isOutJuction) + { + reversingJunction = current.outJunction; + } + + return false; + } + } +} diff --git a/MapifyEditor/BezierCurves/BezierCurveEditor.cs b/MapifyEditor/BezierCurves/BezierCurveEditor.cs index 8d3c317f..6c39026c 100644 --- a/MapifyEditor/BezierCurves/BezierCurveEditor.cs +++ b/MapifyEditor/BezierCurves/BezierCurveEditor.cs @@ -206,7 +206,7 @@ private void DrawPointInspector(BezierPoint point, int index) private static void DrawPointSceneGUI(BezierPoint point) { - if (point.GetComponentInParent()?.IsSwitch == true) + if (point.GetComponentInParent()?.IsVanillaSwitch == true) return; Handles.Label(point.position + new Vector3(0, HandleUtility.GetHandleSize(point.position) * 0.4f, 0), point.gameObject.name); diff --git a/MapifyEditor/BezierCurves/BezierPointEditor.cs b/MapifyEditor/BezierCurves/BezierPointEditor.cs index 0d8272b2..e9046a57 100644 --- a/MapifyEditor/BezierCurves/BezierPointEditor.cs +++ b/MapifyEditor/BezierCurves/BezierPointEditor.cs @@ -114,7 +114,7 @@ public override void OnInspectorGUI() private void OnSceneGUI() { - if (point.GetComponentInParent()?.IsSwitch == true) + if (point.GetComponentInParent()?.IsVanillaSwitch == true) return; Handles.color = Color.green; Vector3 newPosition = Handles.FreeMoveHandle(point.position, point.transform.rotation, HandleUtility.GetHandleSize(point.position) * 0.2f, Vector3.zero, Handles.CubeHandleCap); diff --git a/MapifyEditor/Export/Validators/Railway/SwitchValidator.cs b/MapifyEditor/Export/Validators/Railway/SwitchValidator.cs index bfd52493..a30ddc2e 100644 --- a/MapifyEditor/Export/Validators/Railway/SwitchValidator.cs +++ b/MapifyEditor/Export/Validators/Railway/SwitchValidator.cs @@ -1,8 +1,10 @@ #if UNITY_EDITOR using System.Collections.Generic; +using System.Linq; using Mapify.Editor; using Mapify.Editor.Utils; using Mapify.Editor.Validators; +using UnityEngine; namespace MapifyEditor.Export.Validators { @@ -10,14 +12,37 @@ public class SwitchValidator : Validator { protected override IEnumerator Validate(Scenes scenes) { - foreach (Switch sw in scenes.railwayScene.GetAllComponents()) + foreach (var switch_ in scenes.railwayScene.GetAllComponents()) { - Track divergingTrack = sw.DivergingTrack; - Track throughTrack = sw.ThroughTrack; - divergingTrack.Snap(); - throughTrack.Snap(); - if (!divergingTrack.isInSnapped || !divergingTrack.isOutSnapped || !throughTrack.isInSnapped || !throughTrack.isOutSnapped) - yield return Result.Error("Switches must have a track attached to all points", sw); + var switchTracks = switch_.GetTracks(); + if (switchTracks.Length < 2) + { + yield return Result.Error($"Switches must have at least 2 branches but it has {switchTracks.Length}", switch_); + continue; + } + + if (switchTracks.Any(switchTrack => switchTrack == null)) + { + yield return Result.Error($"Switch track is null", switch_); + continue; + } + + var jointPointPos = switch_.GetJointPoint().position; + for (int i = 1; i < switchTracks.Length; i++) + { + if (Vector3.Distance(jointPointPos, switchTracks[i].Curve[0].position) <= Track.SNAP_RANGE) continue; + + yield return Result.Error("All tracks in switches must connect to each other at point 0", switch_); + break; + } + + foreach (var track in switchTracks) + { + track.Snap(); + if (track.isInSnapped && track.isOutSnapped) continue; + + yield return Result.Error("Switches must have a track attached to all points", track); + } } } } diff --git a/MapifyEditor/Export/Validators/Railway/TrackValidator.cs b/MapifyEditor/Export/Validators/Railway/TrackValidator.cs index ab3e5f19..174ffa3e 100644 --- a/MapifyEditor/Export/Validators/Railway/TrackValidator.cs +++ b/MapifyEditor/Export/Validators/Railway/TrackValidator.cs @@ -31,8 +31,14 @@ protected override IEnumerator Validate(Scenes scenes) if (PrefabUtility.IsPartOfPrefabInstance(track)) yield return Result.Warning("Track prefabs should be unpacked completely before being used", track); - if (track.trackType != TrackType.Road) + if (track.trackType == TrackType.Road) { + if (!string.IsNullOrWhiteSpace(track.stationId)) + { + yield return Result.Warning($"Track {track.name} will not be assigned to specified station {track.stationId} because {nameof(track.trackType)} is set to {track.trackType}", track); + } + } + else { if (string.IsNullOrWhiteSpace(track.stationId)) yield return Result.Error("Station ID not specified", track); else if (stations.All(station => station.stationID != track.stationId)) diff --git a/MapifyEditor/RollingStock/VanillaRollingStockList.cs b/MapifyEditor/RollingStock/VanillaLocomotiveList.cs similarity index 65% rename from MapifyEditor/RollingStock/VanillaRollingStockList.cs rename to MapifyEditor/RollingStock/VanillaLocomotiveList.cs index 76a61bd8..e2ecdfc1 100644 --- a/MapifyEditor/RollingStock/VanillaRollingStockList.cs +++ b/MapifyEditor/RollingStock/VanillaLocomotiveList.cs @@ -5,8 +5,8 @@ namespace Mapify.Editor { [Serializable] // Unity doing Unity things and not supporting nested lists in the editor - public class VanillaRollingStockList + public class VanillaLocomotiveList { - public List rollingStock; + public List rollingStock; } } diff --git a/MapifyEditor/RollingStock/VanillaRollingStockType.cs b/MapifyEditor/RollingStock/VanillaRollingStockType.cs index db8f446e..0ee740b8 100644 --- a/MapifyEditor/RollingStock/VanillaRollingStockType.cs +++ b/MapifyEditor/RollingStock/VanillaRollingStockType.cs @@ -1,32 +1,49 @@ -using System; - namespace Mapify.Editor { + // Globals.G.Types._carTypesById.Select(car => car.Key) + // Use Enum.GetName to turn it into a string ID public enum VanillaRollingStockType : byte { - DE2 = 0, - DE6 = 1, - DH4 = 10, - DM3 = 20, - S282A = 30, - S282B = 31, - S060 = 40 + Autorack = 0, + Boxcar = 1, + BoxcarMilitary = 2, + Caboose = 3, + Flatbed = 4, + FlatbedMilitary = 5, + FlatbedStakes = 6, + FlatbedShort = 7, + Gondola = 8, + HandCar = 9, + Hopper = 10, + HopperCovered = 11, + LocoDE2 = 12, + LocoDE6 = 13, + LocoDE6Slug = 14, + LocoDH4 = 15, + LocoDM3 = 16, + LocoS282A = 17, + LocoS282B = 18, + LocoS060 = 19, + NuclearFlask = 20, + Passenger = 21, + Stock = 22, + Refrigerator = 23, + TankChem = 24, + TankGas = 25, + TankOil = 26, + TankShortFood = 27, + LocoMicroshunter = 28, + LocoDM1U = 29 } - public static class VanillaRollingStockTypeExtensions + public enum VanillaLocomotiveType : byte { - public static string ToV2(this VanillaRollingStockType vanillaRollingStockType) - { - return vanillaRollingStockType switch { - VanillaRollingStockType.DE2 => "LocoDE2", - VanillaRollingStockType.DE6 => "LocoDE6", - VanillaRollingStockType.DH4 => "LocoDH4", - VanillaRollingStockType.DM3 => "LocoDM3", - VanillaRollingStockType.S282A => "LocoS282A", - VanillaRollingStockType.S282B => "LocoS282B", - VanillaRollingStockType.S060 => "LocoS060", - _ => throw new ArgumentOutOfRangeException(nameof(vanillaRollingStockType), vanillaRollingStockType, null) - }; - } + LocoDE2 = 0, + LocoDE6 = 1, + LocoDH4 = 10, + LocoDM3 = 20, + LocoS282A = 30, + LocoS282B = 31, + LocoS060 = 40, } } diff --git a/MapifyEditor/Station/VanillaLocomotiveSpawner.cs b/MapifyEditor/Station/VanillaLocomotiveSpawner.cs index d88b3a0d..76db3ce1 100644 --- a/MapifyEditor/Station/VanillaLocomotiveSpawner.cs +++ b/MapifyEditor/Station/VanillaLocomotiveSpawner.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -8,11 +9,13 @@ public class VanillaLocomotiveSpawner : LocomotiveSpawner { [SerializeField] [Tooltip("What all locomotives to spawn. Each element is a group to spawn together (e.g. the steamer and it's tender)")] - internal List locomotiveGroups; + internal List locomotiveGroups; public override IEnumerable CondenseLocomotiveTypes() { - return locomotiveGroups.Select(types => string.Join(",", types.rollingStock.Select(type => type.ToV2()))); + return locomotiveGroups.Select(types => string.Join(",", types.rollingStock + .Select(locoType => Enum.GetName(locoType.GetType(), locoType)) + )); } } } diff --git a/MapifyEditor/Tools/ToolEnums.cs b/MapifyEditor/Tools/ToolEnums.cs index 1e3ccaa0..8b0f7a32 100644 --- a/MapifyEditor/Tools/ToolEnums.cs +++ b/MapifyEditor/Tools/ToolEnums.cs @@ -12,6 +12,12 @@ public enum TrackOrientation Right } + public enum SwitchType + { + Vanilla, //switches like in the base game + Custom //more flexible type of switches, can have more than 2 branches and any shape + } + public enum SwitchPoint { Joint, diff --git a/MapifyEditor/Tools/TrackToolsCreator.Previews.cs b/MapifyEditor/Tools/TrackToolsCreator.Previews.cs index c58482f5..34b22c65 100644 --- a/MapifyEditor/Tools/TrackToolsCreator.Previews.cs +++ b/MapifyEditor/Tools/TrackToolsCreator.Previews.cs @@ -77,6 +77,44 @@ public static Vector3[][] PreviewSwitch(Switch prefab, Vector3 attachPoint, Vect }; } + public static Vector3[][] PreviewCustomSwitch(Vector3 attachPoint, Vector3 handlePosition, int switchBranchesCount, float radius, float arc, float endGrade, + int samples = 8) + { + var curves = new Vector3[switchBranchesCount][]; + var length = radius * arc * Mathf.Deg2Rad; + + for (int branchIndex = 0; branchIndex < switchBranchesCount; branchIndex++) + { + if (switchBranchesCount % 2 == 1 && branchIndex == (switchBranchesCount-1) / 2) + { + //middle track + curves[branchIndex] = PreviewStraight(attachPoint, handlePosition, length, endGrade, out _); + continue; + } + + TrackOrientation trackOrientation; + float thisRadius; + + if (branchIndex < switchBranchesCount / 2.0) + { + //left of center + trackOrientation = TrackOrientation.Left; + thisRadius = (branchIndex + 1) * radius; + } + else + { + //right of center + trackOrientation = TrackOrientation.Right; + thisRadius = (switchBranchesCount - branchIndex) * radius; + } + + var thisArc = length / thisRadius * Mathf.Rad2Deg; + curves[branchIndex] = PreviewArcCurve(attachPoint, handlePosition, trackOrientation, thisRadius, thisArc, 360, endGrade, out _, samples); + } + + return curves; + } + public static Vector3[][] PreviewYard(Switch leftPrefab, Switch rightPrefab, Vector3 attachPoint, Vector3 handlePosition, TrackOrientation orientation, float trackDistance, YardOptions yardOptions, int samples = 8) { diff --git a/MapifyEditor/Tools/TrackToolsCreator.cs b/MapifyEditor/Tools/TrackToolsCreator.cs index f452beb5..ca635324 100644 --- a/MapifyEditor/Tools/TrackToolsCreator.cs +++ b/MapifyEditor/Tools/TrackToolsCreator.cs @@ -251,15 +251,14 @@ public static Track CreateArcCurve(Transform parent, Vector3 attachPoint, Vector /// The instantiated . /// /// Derail Valley switches are static assets, and their tracks cannot be changed. - /// The switches are also always made at a grade of 0%. + /// The switches are also always made at a grade of 0%. /// - public static Switch CreateSwitch(Switch leftPrefab, Switch rightPrefab, Transform parent, Vector3 attachPoint, Vector3 handlePosition, + public static Switch CreateVanillaSwitch(Switch leftPrefab, Switch rightPrefab, Transform parent, Vector3 attachPoint, Vector3 handlePosition, TrackOrientation orientation, SwitchPoint connectingPoint) { // Create switch object. - Switch s = Object.Instantiate(orientation == TrackOrientation.Left ? leftPrefab : rightPrefab); + Switch s = Object.Instantiate(orientation == TrackOrientation.Left ? leftPrefab : rightPrefab, parent); s.gameObject.name = $"[Switch {orientation}]"; - s.transform.parent = parent; // Helper variables. Vector3 pivot; Quaternion rot; @@ -296,6 +295,55 @@ public static Switch CreateSwitch(Switch leftPrefab, Switch rightPrefab, Transfo return s; } + /// + /// Creates a switch without the limitations of the base game switches + /// + public static CustomSwitch CreateCustomSwitch(Transform parent, Vector3 attachPoint, Vector3 handlePosition, int switchBranchesCount, float radius, float arc, float endGrade) + { + var switchObject = new GameObject($"[Switch w/ {switchBranchesCount} branches]"); + switchObject.transform.position = attachPoint; + switchObject.transform.parent = parent; + + var switchComponent = switchObject.AddComponent(); + + switchComponent.defaultBranch = 0; + switchComponent.standSide = CustomSwitch.StandSide.LEFT; + + var tracks = new Track[switchBranchesCount]; + var length = radius * arc * Mathf.Deg2Rad; + + for (int branchIndex = 0; branchIndex < switchBranchesCount; branchIndex++) + { + if (switchBranchesCount % 2 == 1 && branchIndex == (switchBranchesCount-1) / 2) + { + //middle track + tracks[branchIndex] = CreateStraight(switchObject.transform, attachPoint, handlePosition, length, endGrade); + continue; + } + + TrackOrientation trackOrientation; + float thisRadius; + + if (branchIndex < switchBranchesCount / 2.0) + { + //left of center + trackOrientation = TrackOrientation.Left; + thisRadius = (branchIndex + 1) * radius; + } + else + { + //right of center + trackOrientation = TrackOrientation.Right; + thisRadius = (switchBranchesCount - branchIndex) * radius; + } + + var thisArc = length / thisRadius * Mathf.Rad2Deg; + tracks[branchIndex] = CreateArcCurve(switchObject.transform, attachPoint, handlePosition, trackOrientation, thisRadius, thisArc, 360, endGrade); + } + + switchComponent.SetTracks(tracks); + return switchComponent; + } // Yards. /// @@ -303,7 +351,6 @@ public static Switch CreateSwitch(Switch leftPrefab, Switch rightPrefab, Transfo /// /// Prefab of a with diverging track to the left. /// Prefab of a with diverging track to the right. - /// The base track prefab. /// The parent for the new track. /// Attachment point for the first switch. /// Handle of the attachment point for the first switch. @@ -341,7 +388,7 @@ public static Switch CreateSwitch(Switch leftPrefab, Switch rightPrefab, Transfo /// total tracks, will use the numbers 3, 4, 5, 6, and 7 always, but the order will be reversed. /// /// - /// + /// public static Switch[] CreateYard(Switch leftPrefab, Switch rightPrefab, Transform parent, Vector3 attachPoint, Vector3 handlePosition, TrackOrientation orientation, float trackDistance, int mainSideTracks, int otherSideTracks, bool half, bool alternateSides, float minimumLength, string stationId, char yardId, byte startingTrackId, bool reverseNumbers, out Track[] sidings) @@ -816,7 +863,7 @@ private static List CreateSwitchSprawl(Switch leftPrefab, Switch ri { // Starting switch. // Switch stands are all on the outside of the yard. - start = CreateSwitch(leftPrefab, rightPrefab, parent, attachPoint, handlePosition, orientation, SwitchPoint.Joint); + start = CreateVanillaSwitch(leftPrefab, rightPrefab, parent, attachPoint, handlePosition, orientation, SwitchPoint.Joint); start.standSide = Switch.StandSide.DIVERGING; start.defaultState = Switch.StandSide.THROUGH; Switch s = start; @@ -838,7 +885,7 @@ private static List CreateSwitchSprawl(Switch leftPrefab, Switch ri for (int i = 1; i < sideTracks; i++) { t = CreateStraight(parent, now.position, now.globalHandle1, length, 0); - s = CreateSwitch(leftPrefab, rightPrefab, parent, t.Curve[1].position, t.Curve[1].globalHandle1, + s = CreateVanillaSwitch(leftPrefab, rightPrefab, parent, t.Curve[1].position, t.Curve[1].globalHandle1, orientation, SwitchPoint.Joint); now = s.GetThroughPoint(); points.Add(s.GetDivergingPoint()); @@ -1119,7 +1166,7 @@ public static Switch[] CreateCrossover(Switch leftPrefab, Switch rightPrefab, Tr sp = SwitchPoint.Joint; } - Switch s1 = CreateSwitch(leftPrefab, rightPrefab, crossObj.transform, + Switch s1 = CreateVanillaSwitch(leftPrefab, rightPrefab, crossObj.transform, attachPoint, handlePosition, orientation, sp); BezierPoint bp1 = s1.GetDivergingPoint(); @@ -1132,7 +1179,7 @@ public static Switch[] CreateCrossover(Switch leftPrefab, Switch rightPrefab, Tr point = point + (dir * switchDistance) + offset; - Switch s2 = CreateSwitch(leftPrefab, rightPrefab, crossObj.transform, + Switch s2 = CreateVanillaSwitch(leftPrefab, rightPrefab, crossObj.transform, point, point - dir, orientation, SwitchPoint.Through); BezierPoint bp2 = s2.GetDivergingPoint(); @@ -1217,10 +1264,10 @@ public static Switch[] CreateDoubleSlip(Switch leftPrefab, Switch rightPrefab, T // First side. // Creates a switch, a curve, and then another switch connected through its diverging track. - Switch s00 = CreateSwitch(leftPrefab, rightPrefab, obj.transform, attachPoint, handlePosition, orientation, SwitchPoint.Joint); + Switch s00 = CreateVanillaSwitch(leftPrefab, rightPrefab, obj.transform, attachPoint, handlePosition, orientation, SwitchPoint.Joint); bp = s00.GetDivergingPoint(); bp = CreateArcCurve(obj.transform, bp.position, bp.globalHandle1, orientation, radius, arc, 180.0f, 0).Curve.Last(); - Switch s01 = CreateSwitch(leftPrefab, rightPrefab, obj.transform, bp.position, bp.globalHandle1, + Switch s01 = CreateVanillaSwitch(leftPrefab, rightPrefab, obj.transform, bp.position, bp.globalHandle1, FlipOrientation(orientation), SwitchPoint.Diverging); // Calculate the middle crossover's position by interesecting the 2 through tracks. @@ -1233,10 +1280,10 @@ public static Switch[] CreateDoubleSlip(Switch leftPrefab, Switch rightPrefab, T Vector3 next = MathHelper.MirrorAround(attachPoint, mid); // Repeat the process for the other side. - Switch s10 = CreateSwitch(leftPrefab, rightPrefab, obj.transform, next, next + dir, orientation, SwitchPoint.Joint); + Switch s10 = CreateVanillaSwitch(leftPrefab, rightPrefab, obj.transform, next, next + dir, orientation, SwitchPoint.Joint); bp = s10.GetDivergingPoint(); bp = CreateArcCurve(obj.transform, bp.position, bp.globalHandle1, orientation, radius, arc, 180.0f, 0).Curve.Last(); - Switch s11 = CreateSwitch(leftPrefab, rightPrefab, obj.transform, bp.position, bp.globalHandle1, + Switch s11 = CreateVanillaSwitch(leftPrefab, rightPrefab, obj.transform, bp.position, bp.globalHandle1, FlipOrientation(orientation), SwitchPoint.Diverging); CreateConnect2Point(obj.transform, s00.GetThroughPoint(), s10.GetThroughPoint(), false, false, 1.0f); diff --git a/MapifyEditor/Tools/TrackToolsHelper.cs b/MapifyEditor/Tools/TrackToolsHelper.cs index b491651f..de6312cb 100644 --- a/MapifyEditor/Tools/TrackToolsHelper.cs +++ b/MapifyEditor/Tools/TrackToolsHelper.cs @@ -1,3 +1,4 @@ +using System.Linq; using Mapify.Editor.Utils; using UnityEditor; using UnityEngine; @@ -46,8 +47,7 @@ public static float CalculateHeightDifference(float startGrade, float endGrade, public static SimpleBezier[] GetSwitchBeziers(Switch s, Vector3 attachPoint, Vector3 handlePosition, SwitchPoint connectingPoint) { // Create the original beziers. - SimpleBezier[] curves = new SimpleBezier[] - { + SimpleBezier[] curves = { new SimpleBezier( s.GetJointPoint().position, s.GetJointPoint().globalHandle2, @@ -121,6 +121,7 @@ public static float CalculateSwitchAngle(Switch s) /// /// Returns the speed limit shown on track speed signs for a given radius. + /// source: DV.Signs.SignPlacer.GetMaxSpeedForRadius /// /// The speed in km/h. public static float GetMaxSpeedForRadiusGame(float radius) @@ -196,7 +197,7 @@ public static Vector3[] GetSmoothBezierToConnectSimple(Vector3 attachPosition, V handle = newTarget + (handle - newTarget).normalized * length; - return new Vector3[] { attachPosition, + return new[] { attachPosition, attachPosition + dir * length, handle, newTarget }; @@ -237,7 +238,7 @@ public static Vector3[] GetSmoothBezierToConnectComplex(Vector3 attachPosition, length *= 1 + MathHelper.ArcToBezierHandleLength(angle * Mathf.Deg2Rad); - return new Vector3[] { attachPosition, + return new[] { attachPosition, attachPosition + dir * length, newTarget - dirNext * length, newTarget }; @@ -264,7 +265,7 @@ public static Vector3[] GetSmoothBezierToConnectMix(Vector3 attachPosition, Vect Vector3[] s = GetSmoothBezierToConnectSimple(attachPosition, attachHandle, newTarget); Vector3[] c = GetSmoothBezierToConnectComplex(attachPosition, attachHandle, newTarget); - return new Vector3[] {Vector3.Lerp(s[0], c[0], mix), + return new[] {Vector3.Lerp(s[0], c[0], mix), Vector3.Lerp(s[1], c[1], mix), Vector3.Lerp(s[2], c[2], mix), Vector3.Lerp(s[3], c[3], mix) }; @@ -275,7 +276,7 @@ public static Vector3[] GetSmoothBezierToConnectMix(Vector3 attachPosition, Vect Vector3[] s = GetSmoothBezierToConnectSimple(attachPosition, attachHandle, newTarget, maxAngle); Vector3[] c = GetSmoothBezierToConnectComplex(attachPosition, attachHandle, newTarget, maxAngle); - return new Vector3[] {Vector3.Lerp(s[0], c[0], mix), + return new[] {Vector3.Lerp(s[0], c[0], mix), Vector3.Lerp(s[1], c[1], mix), Vector3.Lerp(s[2], c[2], mix), Vector3.Lerp(s[3], c[3], mix) }; @@ -337,7 +338,7 @@ public static bool CheckForTrackSnap(Vector3 point, float radius, out Vector3 sn /// public static Vector3[] ReverseCurve(Vector3[] curve) { - return new Vector3[] { curve[3], + return new[] { curve[3], curve[2], curve[1], curve[0] }; diff --git a/MapifyEditor/Tools/TrackToolsWindow.cs b/MapifyEditor/Tools/TrackToolsWindow.cs index 621d7374..7a1f7105 100644 --- a/MapifyEditor/Tools/TrackToolsWindow.cs +++ b/MapifyEditor/Tools/TrackToolsWindow.cs @@ -207,8 +207,16 @@ public void CreateTrack(Vector3 position, Vector3 handle) Undo.RegisterCreatedObjectUndo(t.gameObject, "Created Curve"); break; case TrackPiece.Switch: - go = TrackToolsCreator.CreateSwitch(LeftSwitch, RightSwitch, _currentParent, position, handle, - _orientation, _connectingPoint).gameObject; + if (_switchType == SwitchType.Vanilla) + { + go = TrackToolsCreator.CreateVanillaSwitch(LeftSwitch, RightSwitch, _currentParent, position, handle, + _orientation, _connectingPoint).gameObject; + } + else + { + go = TrackToolsCreator.CreateCustomSwitch(_currentParent, position, handle, _switchBranchesCount, _radius, _arc, _endGrade).gameObject; + } + SelectGameObject(go); Undo.RegisterCreatedObjectUndo(go, "Created Switch"); break; @@ -256,7 +264,7 @@ public void CreateSpecial(Vector3 attachPoint, Vector3 handlePosition) break; case SpecialTrackPiece.SwitchCurve: t = TrackToolsCreator.CreateSwitchCurve(LeftSwitch, RightSwitch, _currentParent, attachPoint, handlePosition, - _orientation, _connectingPoint); + _orientation, (SwitchPoint)_connectingPoint); ApplySettingsToTrack(t); SelectTrack(t); Undo.RegisterCreatedObjectUndo(t.gameObject, "Created Switch Curve"); @@ -437,6 +445,7 @@ private bool IsAllowedCreation(bool isBehind, out string tooltip) return false; } + //TODO bug: this check sometimes gives a false positive if (!CheckGrade(isBehind ? CurrentTrack.GetGradeAtStart() : CurrentTrack.GetGradeAtEnd())) { tooltip = "Grade too steep for creation"; diff --git a/MapifyEditor/Tools/TrackToolsWindowDrawing/CreatePreviews.cs b/MapifyEditor/Tools/TrackToolsWindowDrawing/CreatePreviews.cs index 42e2c4b8..3a8b6d85 100644 --- a/MapifyEditor/Tools/TrackToolsWindowDrawing/CreatePreviews.cs +++ b/MapifyEditor/Tools/TrackToolsWindowDrawing/CreatePreviews.cs @@ -23,6 +23,8 @@ private void CreatePiecePreviews() return; } + if (!_drawTrackPreviews) { return; } + if (_drawNewPreview) { AttachPoint ap = new AttachPoint( @@ -71,123 +73,123 @@ private void CreatePiecePreviews() case SelectionType.Turntable: CacheTrack(CurrentTurntable.Track); break; - default: - break; } switch (_currentPiece) { case TrackPiece.Straight: - foreach (var cache in _newCache) - { - cache.Lines = new Vector3[][] { TrackToolsCreator.Previews.PreviewStraight( - cache.Attach.Position, cache.Attach.Handle, - _length, _endGrade, out cache.Points, _sampleCount) }; - } - foreach (var cache in _nextCache) - { - cache.Lines = new Vector3[][] { TrackToolsCreator.Previews.PreviewStraight( - cache.Attach.Position, cache.Attach.Handle, - _length, _endGrade, out cache.Points, _sampleCount) }; - } - foreach (var cache in _backCache) - { - cache.Lines = new Vector3[][] { TrackToolsCreator.Previews.PreviewStraight( - cache.Attach.Position, cache.Attach.Handle, - _length, _endGrade, out cache.Points, _sampleCount) }; - } + { + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); break; - case TrackPiece.Curve: - foreach (var cache in _newCache) - { - cache.Lines = new Vector3[][] { TrackToolsCreator.Previews.PreviewArcCurve( - cache.Attach.Position, cache.Attach.Handle, - _orientation, _radius, _arc, _maxArcPerPoint, _endGrade, out cache.Points, _sampleCount) }; - } - foreach (var cache in _nextCache) - { - cache.Lines = new Vector3[][] { TrackToolsCreator.Previews.PreviewArcCurve( - cache.Attach.Position, cache.Attach.Handle, - _orientation, _radius, _arc, _maxArcPerPoint, _endGrade, out cache.Points, _sampleCount) }; - } - foreach (var cache in _backCache) + + void FillCache(ref List caches) { - cache.Lines = new Vector3[][] { TrackToolsCreator.Previews.PreviewArcCurve( - cache.Attach.Position, cache.Attach.Handle, - _orientation, _radius, _arc, _maxArcPerPoint, _endGrade, out cache.Points, _sampleCount) }; + foreach (var cache in caches) + { + cache.Lines = new[] + { TrackToolsCreator.Previews.PreviewStraight( + cache.Attach.Position, cache.Attach.Handle, + _length, _endGrade, out cache.Points, _sampleCount) }; + } } + } + case TrackPiece.Curve: + { + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); break; - case TrackPiece.Switch: - if (LeftSwitch && RightSwitch) + + void FillCache(ref List caches) { - foreach (var cache in _newCache) + foreach (var cache in caches) { - cache.Lines = TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _connectingPoint, _sampleCount); + cache.Lines = new[] + { + TrackToolsCreator.Previews.PreviewArcCurve( + cache.Attach.Position, cache.Attach.Handle, + _orientation, _radius, _arc, _maxArcPerPoint, _endGrade, out cache.Points, _sampleCount) + }; } - foreach (var cache in _nextCache) + } + } + case TrackPiece.Switch: + if (_switchType == SwitchType.Vanilla) + { + if (!LeftSwitch || !RightSwitch) break; + + void FillCache(ref List caches) { - cache.Lines = TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _connectingPoint, _sampleCount); + foreach (var cache in caches) + { + cache.Lines = TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), + cache.Attach.Position, cache.Attach.Handle, + _connectingPoint, _sampleCount); + } } - foreach (var cache in _backCache) + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + } + else + { + void FillCache(ref List caches) { - cache.Lines = TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _connectingPoint, _sampleCount); + foreach (var cache in caches) + { + cache.Lines = TrackToolsCreator.Previews.PreviewCustomSwitch( + cache.Attach.Position, cache.Attach.Handle, _switchBranchesCount, _radius, _arc, _endGrade, _sampleCount); + } } + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); } + break; case TrackPiece.Yard: - if (LeftSwitch && RightSwitch) + { + if (!LeftSwitch || !RightSwitch) break; + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + break; + + void FillCache(ref List caches) { - foreach (var cache in _newCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewYard(LeftSwitch, RightSwitch, - cache.Attach.Position, cache.Attach.Handle, - _orientation, _trackDistance, _yardOptions, _sampleCount); - } - foreach (var cache in _nextCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewYard(LeftSwitch, RightSwitch, - cache.Attach.Position, cache.Attach.Handle, - _orientation, _trackDistance, _yardOptions, _sampleCount); - } - foreach (var cache in _backCache) + foreach (var cache in caches) { cache.Lines = TrackToolsCreator.Previews.PreviewYard(LeftSwitch, RightSwitch, cache.Attach.Position, cache.Attach.Handle, _orientation, _trackDistance, _yardOptions, _sampleCount); } } - break; + } case TrackPiece.Turntable: - foreach (var cache in _newCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewTurntable( - cache.Attach.Position, cache.Attach.Handle, - _turntableOptions, _sampleCount); - } - foreach (var cache in _nextCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewTurntable( - cache.Attach.Position, cache.Attach.Handle, - _turntableOptions, _sampleCount); - } - foreach (var cache in _backCache) + { + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + break; + + void FillCache(ref List caches) { - cache.Lines = TrackToolsCreator.Previews.PreviewTurntable( - cache.Attach.Position, cache.Attach.Handle, - _turntableOptions, _sampleCount); + foreach (var cache in caches) + { + cache.Lines = TrackToolsCreator.Previews.PreviewTurntable( + cache.Attach.Position, cache.Attach.Handle, + _turntableOptions, _sampleCount); + } } - break; + } case TrackPiece.Special: SpecialPreviews(); break; - default: - break; } } @@ -198,23 +200,17 @@ private void SpecialPreviews() case SpecialTrackPiece.Buffer: break; case SpecialTrackPiece.SwitchCurve: - if (LeftSwitch && RightSwitch) + { + if (!LeftSwitch || !RightSwitch) break; + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + break; + + void FillCache(ref List caches) { - foreach (var cache in _newCache) - { - cache.Lines = new Vector3[1][]; - System.Array.Copy(TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _connectingPoint, _sampleCount), 1, cache.Lines, 0, 1); - } - foreach (var cache in _nextCache) - { - cache.Lines = new Vector3[1][]; - System.Array.Copy(TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _connectingPoint, _sampleCount), 1, cache.Lines, 0, 1); - } - foreach (var cache in _backCache) + foreach (var cache in caches) { cache.Lines = new Vector3[1][]; System.Array.Copy(TrackToolsCreator.Previews.PreviewSwitch(GetCurrentSwitchPrefab(), @@ -222,7 +218,7 @@ private void SpecialPreviews() _connectingPoint, _sampleCount), 1, cache.Lines, 0, 1); } } - break; + } case SpecialTrackPiece.Connect2: { BezierPoint p0 = null; @@ -285,76 +281,62 @@ private void SpecialPreviews() } break; case SpecialTrackPiece.Crossover: - if (LeftSwitch && RightSwitch) + { + if (!LeftSwitch || !RightSwitch) break; + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + break; + + void FillCache(ref List caches) { - foreach (var cache in _newCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewCrossover(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _orientation, _trackDistance, _isTrailing, _switchDistance, _sampleCount); - } - foreach (var cache in _nextCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewCrossover(GetCurrentSwitchPrefab(), - cache.Attach.Position, cache.Attach.Handle, - _orientation, _trackDistance, _isTrailing, _switchDistance, _sampleCount); - } - foreach (var cache in _backCache) + foreach (var cache in caches) { cache.Lines = TrackToolsCreator.Previews.PreviewCrossover(GetCurrentSwitchPrefab(), cache.Attach.Position, cache.Attach.Handle, _orientation, _trackDistance, _isTrailing, _switchDistance, _sampleCount); } } - break; + } case SpecialTrackPiece.ScissorsCrossover: - if (LeftSwitch && RightSwitch) + { + if (!LeftSwitch || !RightSwitch) break; + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + break; + + void FillCache(ref List caches) { - foreach (var cache in _newCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewScissorsCrossover(LeftSwitch, RightSwitch, - cache.Attach.Position, cache.Attach.Handle, - _orientation, _trackDistance, _switchDistance, _sampleCount); - } - foreach (var cache in _nextCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewScissorsCrossover(LeftSwitch, RightSwitch, - cache.Attach.Position, cache.Attach.Handle, - _orientation, _trackDistance, _switchDistance, _sampleCount); - } - foreach (var cache in _backCache) + foreach (var cache in caches) { cache.Lines = TrackToolsCreator.Previews.PreviewScissorsCrossover(LeftSwitch, RightSwitch, cache.Attach.Position, cache.Attach.Handle, _orientation, _trackDistance, _switchDistance, _sampleCount); } } - break; + } case SpecialTrackPiece.DoubleSlip: - if (LeftSwitch && RightSwitch) + { + if (!LeftSwitch || !RightSwitch) break; + + FillCache(ref _newCache); + FillCache(ref _nextCache); + FillCache(ref _backCache); + break; + + void FillCache(ref List caches) { - foreach (var cache in _newCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewDoubleSlip(LeftSwitch, RightSwitch, - cache.Attach.Position, cache.Attach.Handle, - _orientation, _crossAngle, _sampleCount); - } - foreach (var cache in _nextCache) - { - cache.Lines = TrackToolsCreator.Previews.PreviewDoubleSlip(LeftSwitch, RightSwitch, - cache.Attach.Position, cache.Attach.Handle, - _orientation, _crossAngle, _sampleCount); - } - foreach (var cache in _backCache) + foreach (var cache in caches) { cache.Lines = TrackToolsCreator.Previews.PreviewDoubleSlip(LeftSwitch, RightSwitch, cache.Attach.Position, cache.Attach.Handle, _orientation, _crossAngle, _sampleCount); } } - break; - default: - break; + } } } @@ -382,18 +364,6 @@ private void CacheTrack(Track t) _backCache[0].Tooltip = PreviewPointCache.BackString; } } - - private void CreateEditingPreviews() - { - switch (_editingMode) - { - case EditingMode.InsertPoint: - - break; - default: - break; - } - } } } #endif diff --git a/MapifyEditor/Tools/TrackToolsWindowGUI/Creation.cs b/MapifyEditor/Tools/TrackToolsWindowGUI/Creation.cs index b21b5ab8..2249f0c2 100644 --- a/MapifyEditor/Tools/TrackToolsWindowGUI/Creation.cs +++ b/MapifyEditor/Tools/TrackToolsWindowGUI/Creation.cs @@ -1,3 +1,4 @@ +using System; using Mapify.Editor.Tools.OptionData; using Mapify.Editor.Utils; using UnityEditor; @@ -43,6 +44,8 @@ public partial class TrackToolsWindow private bool _changeArc = false; // Switches. + private SwitchType _switchType = SwitchType.Vanilla; + private int _switchBranchesCount = 2; private SwitchPoint _connectingPoint = SwitchPoint.Joint; // Yards. @@ -297,9 +300,12 @@ private void DrawStraightOptions() new GUIContent($"{TrackToolsHelper.CalculateHeightDifference(0, _endGrade, _length):F3}m")); } - private void DrawCurveOptions() + private void DrawCurveOptions(bool CustomSwitchBranch = false) { - DrawOrientationGUI("Which side the curve turns to"); + if (!CustomSwitchBranch) + { + DrawOrientationGUI("Which side the curve turns to"); + } EditorGUILayout.BeginHorizontal(); @@ -309,27 +315,38 @@ private void DrawCurveOptions() if (GUILayout.Button(new GUIContent("Use switch radius", "Sets the radius to the one of switch curves"), GUILayout.MaxWidth(140))) { - if (LeftSwitch) + if (CustomSwitchBranch) { - _radius = TrackToolsHelper.CalculateSwitchRadius(LeftSwitch); - } - else if (RightSwitch) - { - _radius = TrackToolsHelper.CalculateSwitchRadius(RightSwitch); + _radius = TrackToolsHelper.DefaultSwitchRadius; } else { - _radius = TrackToolsHelper.DefaultSwitchRadius; + if (LeftSwitch) + { + _radius = TrackToolsHelper.CalculateSwitchRadius(LeftSwitch); + } + else if (RightSwitch) + { + _radius = TrackToolsHelper.CalculateSwitchRadius(RightSwitch); + } + else + { + _radius = TrackToolsHelper.DefaultSwitchRadius; + } } } EditorGUILayout.EndHorizontal(); _arc = EditorGUILayout.Slider(new GUIContent("Arc", "Angle of the curve"), - _arc, 0.0f, 180.0f); - _maxArcPerPoint = EditorGUILayout.Slider(new GUIContent("Max arc per point", - "How big an arc can be before the curve is split."), - _maxArcPerPoint, 0.0f, 90.0f); + _arc, 0.1f, 180.0f); + if (!CustomSwitchBranch) + { + _maxArcPerPoint = EditorGUILayout.Slider(new GUIContent("Max arc per point", + "How big an arc can be before the curve is split."), + _maxArcPerPoint, 0.0f, 90.0f); + } + _endGrade = EditorGUILayout.FloatField( new GUIContent("End grade", "How steep should the track's other end be"), _endGrade * 100.0f) / 100.0f; @@ -343,17 +360,20 @@ private void DrawCurveOptions() float length = EditorGUILayout.FloatField(new GUIContent("Approx. length", "Approximated total length of the curve"), _radius * _arc * Mathf.Deg2Rad); + //length of zero makes no sense and will cause exceptions + if(length < 0.1f) length = 0.1f; + bool changed = EditorGUI.EndChangeCheck(); _changeArc = EditorGUILayout.ToggleLeft(new GUIContent("Change arc", "Change the arc of the curve instead of the radius to match the length"), - _changeArc, GUILayout.MaxWidth(100)); + _changeArc, GUILayout.MaxWidth(140)); if (changed) { if (_changeArc) { - _arc = (length / _radius) * Mathf.Rad2Deg; + _arc = length / _radius * Mathf.Rad2Deg; } else { @@ -369,14 +389,21 @@ private void DrawCurveOptions() private void DrawSwitchOptions() { - if (!Require(LeftSwitch, "Left switch prefab") || - !Require(RightSwitch, "Right switch prefab")) + _switchType = (SwitchType)EditorGUILayout.EnumPopup(new GUIContent("Switch type", + "Vanilla switch (like in the base game) or a custom switch (create your own shape)"), + _switchType); + + switch (_switchType) { - return; + case SwitchType.Vanilla: + DrawVanillaSwitchOptions(); + break; + case SwitchType.Custom: + DrawCustomSwitchOptions(); + break; + default: + throw new ArgumentOutOfRangeException(); } - - DrawOrientationGUI("Which side the diverging track turns to"); - DrawSwitchPointGUI(); } private void DrawYardOptions() @@ -535,7 +562,7 @@ private void DrawSwitchCurveOptions() } DrawOrientationGUI("Choose which side the track diverges to"); - DrawSwitchPointGUI(); + DrawVanillaSwitchPointGUI(); if (_connectingPoint == SwitchPoint.Through) { diff --git a/MapifyEditor/Tools/TrackToolsWindowGUI/Other.cs b/MapifyEditor/Tools/TrackToolsWindowGUI/Other.cs index c3b3832f..0605ce4b 100644 --- a/MapifyEditor/Tools/TrackToolsWindowGUI/Other.cs +++ b/MapifyEditor/Tools/TrackToolsWindowGUI/Other.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using Mapify.Editor.Utils; using UnityEditor; using UnityEngine; @@ -17,6 +19,7 @@ public partial class TrackToolsWindow // Settings. private bool _showSettings = false; + private bool _drawTrackPreviews = true; private bool _drawNewPreview = true; private bool _performanceMode = false; private bool _zTestTrack = true; @@ -87,9 +90,16 @@ private void DrawSettingsFoldout() { EditorGUI.indentLevel++; - _drawNewPreview = EditorGUILayout.Toggle( - new GUIContent("New track preview", "Show or hide the new track preview"), - _drawNewPreview); + _drawTrackPreviews = EditorGUILayout.Toggle( + new GUIContent("Track previews", "Show or hide the track previews"), + _drawTrackPreviews); + + if (_drawTrackPreviews) + { + _drawNewPreview = EditorGUILayout.Toggle( + new GUIContent("New track preview", "Show or hide the new track preview"), + _drawNewPreview); + } _performanceMode = EditorGUILayout.Toggle( new GUIContent("Performance mode", "Reduces redraw frequency"), @@ -147,12 +157,23 @@ private void DrawOrientationGUI(string tooltip) EditorGUILayout.EndHorizontal(); } + private void DrawVanillaSwitchOptions() + { + if (!Require(LeftSwitch, "Left switch prefab") || + !Require(RightSwitch, "Right switch prefab")) + { + return; + } + DrawOrientationGUI("Which side the diverging track turns to"); + DrawVanillaSwitchPointGUI(); + } + // SwitchPoint enum dropdown with a button to swap options easily. - private void DrawSwitchPointGUI() + private void DrawVanillaSwitchPointGUI() { EditorGUILayout.BeginHorizontal(); _connectingPoint = (SwitchPoint)EditorGUILayout.EnumPopup(new GUIContent("Connecting point", - "Which of the 3 switch points should connect to the current track"), + "Which of the 3 switch points should connect to the current track"), _connectingPoint); if (GUILayout.Button(new GUIContent("Next point", "Swaps between the 3 switch points."), GUILayout.MaxWidth(140))) @@ -163,6 +184,14 @@ private void DrawSwitchPointGUI() EditorGUILayout.EndHorizontal(); } + private void DrawCustomSwitchOptions() + { + _switchBranchesCount = EditorGUILayout.IntField(new GUIContent("Branches", "How many branches the switch has (at least 2)"), _switchBranchesCount); + if (_switchBranchesCount < 2) _switchBranchesCount = 2; + + DrawCurveOptions(true); + } + // Orientation enum dropdown with a button to swap options easily. private void DrawSwitchDistanceGUI() { diff --git a/MapifyEditor/TrackComponents/CarDeleter.cs b/MapifyEditor/TrackComponents/CarDeleter.cs new file mode 100644 index 00000000..713e9bc4 --- /dev/null +++ b/MapifyEditor/TrackComponents/CarDeleter.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace Mapify.Editor +{ + [RequireComponent(typeof(Track))] + public class CarDeleter : MonoBehaviour + { + // nothing + } +} diff --git a/MapifyEditor/TrackComponents/Retarder.cs b/MapifyEditor/TrackComponents/Retarder.cs new file mode 100644 index 00000000..b98fb024 --- /dev/null +++ b/MapifyEditor/TrackComponents/Retarder.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +namespace Mapify.Editor +{ + // A retarder is a device used to reduce the speed of freight cars + public class Retarder: MonoBehaviour + { + [Tooltip("The retarder will activate when the speed of the car is above this limit (km/h)")] + public float maxSpeedKMH = 10.0f; + [Tooltip("The maximum brake force of the retarder, in Newtons")] + public float brakeForce = 50000f; + } +} diff --git a/MapifyEditor/TrackComponents/TimedCarSpawner.cs b/MapifyEditor/TrackComponents/TimedCarSpawner.cs new file mode 100644 index 00000000..629c698f --- /dev/null +++ b/MapifyEditor/TrackComponents/TimedCarSpawner.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace Mapify.Editor +{ + [RequireComponent(typeof(Track))] + public class TimedCarSpawner: MonoBehaviour + { + [Tooltip("Interval between spawning, in seconds")] + public float SpawnInterval = 5; + [Tooltip("What types of rolling stock to spawn")] + public VanillaRollingStockType[] TrainCarTypes; + [Tooltip("Enable the handbrake on the spawned car")] + public bool EnableHandBrakeOnSpawn = false; + } +} diff --git a/MapifyEditor/TrackComponents/YardController.cs b/MapifyEditor/TrackComponents/YardController.cs new file mode 100644 index 00000000..b0908a4f --- /dev/null +++ b/MapifyEditor/TrackComponents/YardController.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace Mapify.Editor +{ + public class YardController: MonoBehaviour + { + //TODO validate this stuff + public Track DetectorTrack; + public string StationID; + public string YardID; + } +} diff --git a/MapifyEditor/Trackage/CustomSwitch.cs b/MapifyEditor/Trackage/CustomSwitch.cs new file mode 100644 index 00000000..87a4afb2 --- /dev/null +++ b/MapifyEditor/Trackage/CustomSwitch.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Mapify.Editor +{ + public class CustomSwitch: SwitchBase + { + public enum StandSide + { + LEFT, + RIGHT + } + + public override BezierPoint GetJointPoint() => Tracks[0].Curve[0]; + + public BezierPoint GetOutPoint(int branchIndex) + { + if (branchIndex >= Tracks.Length) + { + throw new IndexOutOfRangeException($"Branch index {branchIndex} is out of range. Switch has {Tracks.Length} tracks."); + } + return Tracks[branchIndex].Curve.Last(); + } + + public List GetOutPoints() + { + return Tracks.Select(track => track.Curve.Last()).ToList(); + } + + public override List GetPoints() + { + var points = new List{ GetJointPoint() }; + points.AddRange(GetOutPoints()); + return points; + } + + [Tooltip("Which way the switch should be flipped by default")] + public byte defaultBranch = 0; + + [Tooltip("Which side of the switch the stand will appear on")] + public StandSide standSide; + + [Tooltip("Tracks in the switch, from left to right")] + [SerializeField] + private Track[] Tracks; + + public override Track[] GetTracks() + { + return Tracks; + } + + public void SetTracks(Track[] newTracks) + { + Tracks = newTracks; + } + } +} diff --git a/MapifyEditor/Trackage/Switch.cs b/MapifyEditor/Trackage/Switch.cs index b11e73f7..24ce7b13 100644 --- a/MapifyEditor/Trackage/Switch.cs +++ b/MapifyEditor/Trackage/Switch.cs @@ -1,18 +1,15 @@ -using System; using System.Collections.Generic; -using System.Linq; -using Mapify.Editor.Utils; using UnityEngine; -#if UNITY_EDITOR -using UnityEditor; -#endif namespace Mapify.Editor { - [ExecuteInEditMode] //this is necessary for snapping to work [RequireComponent(typeof(VanillaObject))] - public class Switch : MonoBehaviour + public class Switch : SwitchBase { + //must match DV.RailTrack.RailTrack.JUNCTION_DIVERGING_TRACK_NAME / JUNCTION_THROUGH_TRACK_NAME + public const string THROUGH_TRACK_NAME = "[track through]"; + public const string DIVERGING_TRACK_NAME = "[track diverging]"; + public enum StandSide { THROUGH, @@ -21,185 +18,22 @@ public enum StandSide [Tooltip("Which side of the switch the stand will appear on")] public StandSide standSide; + [Tooltip("Which way the switch should be flipped by default")] public StandSide defaultState; - private enum SwitchPoint - { - FIRST, //the point that is shared between the two tracks - THROUGH, - DIVERGING - } - -#if UNITY_EDITOR - private bool snapShouldUpdate = true; - - private Vector3 previousPositionSwitchFirstPoint; - private Vector3 previousPositionThroughTrackLastPoint; - private Vector3 previousPositionDivergingTrackLastPoint; - - - private SnappedTrack snappedTrackBeforeSwitch; - //the track connected to the through track - private SnappedTrack snappedTrackAfterThrough; - //the track connected to the diverging track - private SnappedTrack snappedTrackAfterDiverging; -#endif - - public Track ThroughTrack => transform.Find("[track through]").GetComponent(); - public Track DivergingTrack => transform.Find("[track diverging]").GetComponent(); + public Track ThroughTrack => transform.Find(THROUGH_TRACK_NAME).GetComponent(); + public Track DivergingTrack => transform.Find(DIVERGING_TRACK_NAME).GetComponent(); public bool IsLeft => DivergingTrack.Curve.Last().localPosition.x < 0; -#if UNITY_EDITOR - - private void OnEnable() - { - snapShouldUpdate = true; - } - - private void OnDisable() - { - UnsnapConnectedTracks(); - } - - private void OnDestroy() - { - UnsnapConnectedTracks(); - } - - private void OnDrawGizmos() - { - if (transform.DistToSceneCamera() >= Track.SNAP_UPDATE_RANGE_SQR) - { - return; - } - - CheckSwitchMoved(); - - if (snapShouldUpdate) - { - Snap(); - snapShouldUpdate = false; - } - } - - private void CheckSwitchMoved() - { - var positionSwitchFirstPoint = transform.position; - var positionThroughTrackLastPoint = ThroughTrack.Curve.Last().position; - var positionDivergingTrackLastPoint = DivergingTrack.Curve.Last().position; - - if (positionSwitchFirstPoint != previousPositionSwitchFirstPoint || - positionThroughTrackLastPoint != previousPositionThroughTrackLastPoint || - positionDivergingTrackLastPoint != previousPositionDivergingTrackLastPoint) - { - snapShouldUpdate = true; + public override BezierPoint GetJointPoint() => ThroughTrack.Curve[0]; + public BezierPoint GetThroughPoint() => ThroughTrack.Curve[1]; + public BezierPoint GetDivergingPoint() => DivergingTrack.Curve[1]; + public BezierPoint GetDivergeJoinPoint() => DivergingTrack.Curve[0]; - previousPositionSwitchFirstPoint = positionSwitchFirstPoint; - previousPositionThroughTrackLastPoint = positionThroughTrackLastPoint; - previousPositionDivergingTrackLastPoint = positionDivergingTrackLastPoint; - } - } - - private void UnsnapConnectedTracks() + public override List GetPoints() { - snappedTrackBeforeSwitch?.UnSnapped(); - snappedTrackAfterThrough?.UnSnapped(); - snappedTrackAfterDiverging?.UnSnapped(); + return new List { GetJointPoint(), GetThroughPoint(), GetDivergingPoint() }; } - - public void Snap() - { - var bezierPoints = FindObjectsOfType(); - bool isSelected = Selection.gameObjects.Contains(gameObject); - - TrySnap(bezierPoints, isSelected, SwitchPoint.FIRST); - TrySnap(bezierPoints, isSelected, SwitchPoint.DIVERGING); - TrySnap(bezierPoints, isSelected, SwitchPoint.THROUGH); - } - - private void TrySnap(IEnumerable points, bool move, SwitchPoint switchPoint) - { - var reference = switchPoint switch - { - SwitchPoint.FIRST => transform, - SwitchPoint.THROUGH => ThroughTrack.Curve.Last().transform, - SwitchPoint.DIVERGING => DivergingTrack.Curve.Last().transform, - _ => throw new ArgumentOutOfRangeException(nameof(switchPoint), switchPoint, null) - }; - - var position = reference.position; - var closestPosition = Vector3.zero; - var closestDistance = float.MaxValue; - - foreach (BezierPoint otherSnapPoint in points) - { - //don't connect to itself - if (otherSnapPoint.Curve().GetComponentInParent() == this) continue; - - Vector3 otherPosition = otherSnapPoint.transform.position; - float distance = Mathf.Abs(Vector3.Distance(otherPosition, position)); - - // too far away - if (distance > Track.SNAP_RANGE || distance >= closestDistance) continue; - - var otherTrack = otherSnapPoint.GetComponentInParent(); - - // don't snap a switch to another switch - if (otherTrack.IsSwitch) continue; - - closestPosition = otherPosition; - closestDistance = distance; - - otherTrack.Snapped(otherSnapPoint); - - //remember what track we snapped to - switch (switchPoint) - { - case SwitchPoint.FIRST: - snappedTrackBeforeSwitch = new SnappedTrack(otherTrack, otherSnapPoint); - break; - case SwitchPoint.THROUGH: - snappedTrackAfterThrough = new SnappedTrack(otherTrack, otherSnapPoint); - break; - case SwitchPoint.DIVERGING: - snappedTrackAfterDiverging = new SnappedTrack(otherTrack, otherSnapPoint); - break; - default: - throw new ArgumentOutOfRangeException(nameof(switchPoint), switchPoint, null); - } - } - - // No snap target found - if (closestDistance >= float.MaxValue) - { - switch (switchPoint) - { - case SwitchPoint.FIRST: - snappedTrackBeforeSwitch?.UnSnapped(); - snappedTrackBeforeSwitch = null; - break; - case SwitchPoint.THROUGH: - snappedTrackAfterThrough?.UnSnapped(); - snappedTrackAfterThrough = null; - break; - case SwitchPoint.DIVERGING: - snappedTrackAfterDiverging?.UnSnapped(); - snappedTrackAfterDiverging = null; - break; - default: - throw new ArgumentOutOfRangeException(nameof(switchPoint), switchPoint, null); - } - - return; - } - - if (move) - { - transform.position = closestPosition + (transform.position - reference.position); - } - } - -#endif } } diff --git a/MapifyEditor/Trackage/SwitchBase.cs b/MapifyEditor/Trackage/SwitchBase.cs new file mode 100644 index 00000000..43e6cff5 --- /dev/null +++ b/MapifyEditor/Trackage/SwitchBase.cs @@ -0,0 +1,148 @@ +using System.Collections.Generic; +using System.Linq; +using Mapify.Editor.Utils; +using UnityEditor; +using UnityEngine; + +namespace Mapify.Editor +{ + [ExecuteInEditMode] //this is necessary for snapping to work + public abstract class SwitchBase: MonoBehaviour + { + public virtual Track[] GetTracks() + { + var tracks = gameObject.GetComponentsInChildren(); + return tracks ?? new Track[] {}; + } + + public abstract BezierPoint GetJointPoint(); + + public abstract List GetPoints(); + +#if UNITY_EDITOR + private bool snapShouldUpdate = true; + + private Vector3[] previousPositionsPoints; + private SnappedTrack[] snappedTracks; + private bool init = false; + + private void OnEnable() + { + snapShouldUpdate = true; + + var pointsCount = GetTracks().Count()+1; + previousPositionsPoints = new Vector3[pointsCount]; + snappedTracks = new SnappedTrack[pointsCount]; + init = true; + } + + private void OnDisable() + { + UnsnapConnectedTracks(); + init = false; + } + + private void OnDestroy() + { + UnsnapConnectedTracks(); + } + + private void OnDrawGizmos() + { + if(!init) return; + if (transform.DistToSceneCamera() >= Track.SNAP_UPDATE_RANGE_SQR) + { + return; + } + + CheckSwitchMoved(); + + if (snapShouldUpdate) + { + Snap(); + snapShouldUpdate = false; + } + } + + private void CheckSwitchMoved() + { + var positionPoints = GetPoints().Select(point => point.position).ToArray(); + + for (int index = 0; index < positionPoints.Length; index++) + { + if (positionPoints[index] == previousPositionsPoints[index]) continue; + + snapShouldUpdate = true; + previousPositionsPoints[index] = positionPoints[index]; + } + } + + private void UnsnapConnectedTracks() + { + foreach (var snapped in snappedTracks) + { + snapped?.UnSnapped(); + } + } + + public void Snap() + { + var bezierPoints = FindObjectsOfType(); + bool isSelected = Selection.gameObjects.Contains(gameObject); + + var points = GetPoints(); + for (var pointIndex = 0; pointIndex < points.Count; pointIndex++) + { + TrySnap(bezierPoints, isSelected, points[pointIndex], pointIndex); + } + } + + private void TrySnap(IEnumerable points, bool move, BezierPoint snapPoint, int snapPointIndex) + { + var reference = snapPoint.transform; + + var position = reference.position; + var closestPosition = Vector3.zero; + var closestDistance = float.MaxValue; + + foreach (BezierPoint otherSnapPoint in points) + { + //don't connect to itself + if (otherSnapPoint.Curve().GetComponentInParent() == this) continue; + + Vector3 otherPosition = otherSnapPoint.transform.position; + float distance = Mathf.Abs(Vector3.Distance(otherPosition, position)); + + // too far away + if (distance > Track.SNAP_RANGE || distance >= closestDistance) continue; + + var otherTrack = otherSnapPoint.GetComponentInParent(); + + // don't snap a switch to another switch + if (otherTrack.IsSwitch) continue; + + closestPosition = otherPosition; + closestDistance = distance; + + otherTrack.Snapped(otherSnapPoint); + + //remember what track we snapped to + snappedTracks[snapPointIndex] = new SnappedTrack(otherTrack, otherSnapPoint); + } + + // No snap target found + if (closestDistance >= float.MaxValue) + { + snappedTracks[snapPointIndex]?.UnSnapped(); + snappedTracks[snapPointIndex] = null; + return; + } + + if (move) + { + transform.position = closestPosition + (transform.position - reference.position); + } + } +#endif + } +} diff --git a/MapifyEditor/Trackage/Track.cs b/MapifyEditor/Trackage/Track.cs index 87e46fe1..ae37946a 100644 --- a/MapifyEditor/Trackage/Track.cs +++ b/MapifyEditor/Trackage/Track.cs @@ -3,6 +3,7 @@ using Mapify.Editor.Utils; using UnityEditor; using UnityEngine; +using UnityEngine.Serialization; namespace Mapify.Editor { @@ -13,6 +14,7 @@ public class Track : MonoBehaviour public const float SNAP_RANGE = 1.0f; public const float SNAP_UPDATE_RANGE_SQR = 250000; public const float SNAP_RANGE_SQR = SNAP_RANGE * SNAP_RANGE; + public const float TURNTABLE_SEARCH_RANGE = 0.05f; // ReSharper disable MemberCanBePrivate.Global public static readonly Color32 COLOR_ROAD = new Color32(255, 255, 255, 255); @@ -73,16 +75,9 @@ public BezierCurve Curve { } } - private Switch _parentSwitch; - - private Switch ParentSwitch { - get { - if (_parentSwitch) return _parentSwitch; - return _parentSwitch = GetComponentInParent(); - } - } - - public bool IsSwitch => ParentSwitch != null; + public bool IsSwitch => GetComponentInParent() != null; + public bool IsVanillaSwitch => GetComponentInParent() != null; + public bool IsCustomSwitch => GetComponentInParent() != null; public bool IsTurntable => GetComponentInParent() != null; public string LogicName => @@ -178,9 +173,9 @@ internal void Snap() { BezierPoint[] points = FindObjectsOfType().SelectMany(curve => new[] { curve[0], curve.Last() }).ToArray(); GameObject[] selectedObjects = Selection.gameObjects; - bool isSelected = !IsSwitch && !IsTurntable && (selectedObjects.Contains(gameObject) || selectedObjects.Contains(Curve[0].gameObject) || selectedObjects.Contains(Curve.Last().gameObject)); - TrySnap(points, isSelected, true); - TrySnap(points, isSelected, false); + bool shouldMove = !IsSwitch && !IsTurntable && (selectedObjects.Contains(gameObject) || selectedObjects.Contains(Curve[0].gameObject) || selectedObjects.Contains(Curve.Last().gameObject)); + TrySnap(points, shouldMove, true); + TrySnap(points, shouldMove, false); } private static void DrawDisconnectedIcon(Vector3 position) @@ -230,7 +225,7 @@ private void TrySnap(IEnumerable snapPoints, bool move, bool first) var colliders = new Collider[1]; // Turntables will search for track within 0.05m, so set it a little lower to be safe. - if (!IsSwitch && Physics.OverlapSphereNonAlloc(pos, 0.04f, colliders) != 0) + if (!IsSwitch && Physics.OverlapSphereNonAlloc(pos, TURNTABLE_SEARCH_RANGE-0.01f, colliders) != 0) { var foundCollider = colliders[0]; var track = foundCollider.GetComponent(); diff --git a/MapifyEditor/Utils/Extensions.cs b/MapifyEditor/Utils/Extensions.cs index c7a7acfa..001b77d4 100644 --- a/MapifyEditor/Utils/Extensions.cs +++ b/MapifyEditor/Utils/Extensions.cs @@ -243,12 +243,6 @@ public static Dictionary> MapToClosestStation(this IEnumerab .ToDictionary(group => group.Key, group => group.ToList()); } - // Switches. - public static BezierPoint GetJointPoint(this Switch s) => s.ThroughTrack.Curve[0]; - public static BezierPoint GetThroughPoint(this Switch s) => s.ThroughTrack.Curve[1]; - public static BezierPoint GetDivergingPoint(this Switch s) => s.DivergingTrack.Curve[1]; - public static BezierPoint GetDivergeJoinPoint(this Switch s) => s.DivergingTrack.Curve[0]; - // Track. /// /// Returns true if this track starts at grade of 0%. diff --git a/PackageBuilder/Assets/Mapify/Scripts/OsmSharp.dll.meta b/PackageBuilder/Assets/Mapify/Scripts/OsmSharp.dll.meta deleted file mode 100644 index 9142f0ad..00000000 --- a/PackageBuilder/Assets/Mapify/Scripts/OsmSharp.dll.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: b868070355200e2449668298f608d7bd -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 1 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - Windows Store Apps: WindowsStoreApps - second: - enabled: 0 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: diff --git a/PackageBuilder/Assets/Mapify/Scripts/protobuf-net.dll.meta b/PackageBuilder/Assets/Mapify/Scripts/protobuf-net.dll.meta deleted file mode 100644 index eb9ab9b7..00000000 --- a/PackageBuilder/Assets/Mapify/Scripts/protobuf-net.dll.meta +++ /dev/null @@ -1,33 +0,0 @@ -fileFormatVersion: 2 -guid: 5bc816fcb3d84e24091f0fcb4c990584 -PluginImporter: - externalObjects: {} - serializedVersion: 2 - iconMap: {} - executionOrder: {} - defineConstraints: [] - isPreloaded: 0 - isOverridable: 0 - isExplicitlyReferenced: 0 - validateReferences: 1 - platformData: - - first: - Any: - second: - enabled: 1 - settings: {} - - first: - Editor: Editor - second: - enabled: 0 - settings: - DefaultValueInitialized: true - - first: - Windows Store Apps: WindowsStoreApps - second: - enabled: 0 - settings: - CPU: AnyCPU - userData: - assetBundleName: - assetBundleVariant: diff --git a/info.json b/info.json index 2543bede..55b68775 100644 --- a/info.json +++ b/info.json @@ -6,5 +6,6 @@ "EntryMethod": "Mapify.Mapify.Load", "ManagerVersion": "0.27.13", "HomePage": "https://www.nexusmods.com/derailvalley/mods/593", - "Repository": "https://raw.githubusercontent.com/Insprill/dv-mapify/master/repository.json" + "Repository": "https://raw.githubusercontent.com/Insprill/dv-mapify/master/repository.json", + "Requirements": ["CommsRadioAPI-1.0.3"] }