diff --git a/SceneManager/Utils/BarrierManager.cs b/SceneManager/Utils/BarrierManager.cs new file mode 100644 index 0000000..06f430f --- /dev/null +++ b/SceneManager/Utils/BarrierManager.cs @@ -0,0 +1,286 @@ +using System.Collections.Generic; +using System.Linq; +using Rage; +using SceneManager.Menus; +using SceneManager.Objects; + +namespace SceneManager.Utils +{ + internal static class BarrierManager + { + internal static Object PlaceholderBarrier { get; private set; } + internal static List Barriers { get; } = new List(); + + internal static void CreatePlaceholderBarrier() + { + Hints.Display($"~o~Scene Manager ~y~[Hint]\n~w~The shadow barrier will disappear if you aim too far away."); + if (PlaceholderBarrier) + { + PlaceholderBarrier.Delete(); + } + + var barrierKey = Settings.Barriers.Where(x => x.Key == BarrierMenu.BarrierList.SelectedItem).FirstOrDefault().Key; + var barrierValue = Settings.Barriers[barrierKey].Name; + PlaceholderBarrier = new Rage.Object(barrierValue, UserInput.GetMousePositionForBarrier, BarrierMenu.RotateBarrier.Value); + if (!PlaceholderBarrier) + { + BarrierMenu.Menu.Close(); + Game.LogTrivial($"Something went wrong creating the placeholder barrier. Mouse position: {UserInput.GetMousePositionForBarrier}"); + Game.DisplayNotification($"~o~Scene Manager ~r~[Error]\n~w~Something went wrong creating the placeholder barrier. This is a rare problem that only happens in certain areas of the world. Please try again somewhere else."); + return; + } + + //Rage.Native.NativeFunction.Natives.SET_ENTITY_TRAFFICLIGHT_OVERRIDE(shadowBarrier, setBarrierTrafficLight.Index); + Rage.Native.NativeFunction.Natives.PLACE_OBJECT_ON_GROUND_PROPERLY(PlaceholderBarrier); + PlaceholderBarrier.IsGravityDisabled = true; + PlaceholderBarrier.IsCollisionEnabled = false; + PlaceholderBarrier.Opacity = 0.7f; + + // Start with lights off for Parks's objects + if (Settings.EnableAdvancedBarricadeOptions) + { + Rage.Native.NativeFunction.Natives.x971DA0055324D033(PlaceholderBarrier, BarrierMenu.BarrierTexture.Value); + SetBarrierLights(); + } + } + + internal static void SetBarrierLights() + { + if (BarrierMenu.SetBarrierLights.Checked) + { + Rage.Native.NativeFunction.Natives.SET_ENTITY_LIGHTS(PlaceholderBarrier, false); + } + else + { + Rage.Native.NativeFunction.Natives.SET_ENTITY_LIGHTS(PlaceholderBarrier, true); + } + + //Rage.Native.NativeFunction.Natives.SET_ENTITY_TRAFFICLIGHT_OVERRIDE(shadowBarrier, setBarrierTrafficLight.Index); + } + + internal static void UpdatePlaceholderBarrierPosition() + { + DisableBarrierMenuOptionsIfShadowConeTooFar(); + if (PlaceholderBarrier) + { + PlaceholderBarrier.Heading = BarrierMenu.RotateBarrier.Value; + PlaceholderBarrier.Position = UserInput.GetMousePositionForBarrier; + Rage.Native.NativeFunction.Natives.PLACE_OBJECT_ON_GROUND_PROPERLY(PlaceholderBarrier); + //Rage.Native.NativeFunction.Natives.SET_ENTITY_TRAFFICLIGHT_OVERRIDE(shadowBarrier, setBarrierTrafficLight.Index); + } + + void DisableBarrierMenuOptionsIfShadowConeTooFar() + { + if (!PlaceholderBarrier && UserInput.GetMousePositionForBarrier.DistanceTo2D(Game.LocalPlayer.Character.Position) <= Settings.BarrierPlacementDistance) + { + CreatePlaceholderBarrier(); + + } + else if (PlaceholderBarrier && PlaceholderBarrier.Position.DistanceTo2D(Game.LocalPlayer.Character.Position) > Settings.BarrierPlacementDistance) + { + BarrierMenu.BarrierList.Enabled = false; + BarrierMenu.RotateBarrier.Enabled = false; + PlaceholderBarrier.Delete(); + } + else if (PlaceholderBarrier && PlaceholderBarrier.Position.DistanceTo2D(Game.LocalPlayer.Character.Position) <= Settings.BarrierPlacementDistance && BarrierMenu.BarrierList.SelectedItem == "Flare") + { + BarrierMenu.BarrierList.Enabled = true; + BarrierMenu.RotateBarrier.Enabled = false; + } + else + { + BarrierMenu.BarrierList.Enabled = true; + BarrierMenu.RotateBarrier.Enabled = true; + } + } + } + + internal static void LoopToRenderPlaceholderBarrier() + { + while (BarrierMenu.Menu.Visible) + { + if (BarrierMenu.BarrierList.Selected || BarrierMenu.RotateBarrier.Selected || BarrierMenu.Invincible.Selected || BarrierMenu.Immobile.Selected || BarrierMenu.BarrierTexture.Selected || BarrierMenu.SetBarrierLights.Selected || BarrierMenu.SetBarrierTrafficLight.Selected) + { + if (PlaceholderBarrier) + { + //UpdatePlaceholderBarrierPosition(); + UpdatePlaceholderBarrierPosition(); + } + else if (UserInput.GetMousePositionForBarrier.DistanceTo2D(Game.LocalPlayer.Character.Position) <= Settings.BarrierPlacementDistance) + { + //CreatePlaceholderBarrier(); + CreatePlaceholderBarrier(); + } + } + else + { + if (PlaceholderBarrier) + { + PlaceholderBarrier.Delete(); + } + } + GameFiber.Yield(); + } + + if (PlaceholderBarrier) + { + PlaceholderBarrier.Delete(); + } + } + + internal static void SpawnBarrier() + { + if (BarrierMenu.BarrierList.SelectedItem == "Flare") + { + SpawnFlare(); + } + else + { + var barrier = new Rage.Object(PlaceholderBarrier.Model, PlaceholderBarrier.Position, BarrierMenu.RotateBarrier.Value); + barrier.SetPositionWithSnap(PlaceholderBarrier.Position); + Rage.Native.NativeFunction.Natives.SET_ENTITY_DYNAMIC(barrier, true); + if (BarrierMenu.Invincible.Checked) + { + Rage.Native.NativeFunction.Natives.SET_DISABLE_FRAG_DAMAGE(barrier, true); + if (barrier.Model.Name != "prop_barrier_wat_03a") + { + Rage.Native.NativeFunction.Natives.SET_DISABLE_BREAKING(barrier, true); + } + } + if (BarrierMenu.Immobile.Checked) + { + barrier.IsPositionFrozen = true; + } + else + { + + barrier.IsPositionFrozen = false; + } + if (Settings.EnableAdvancedBarricadeOptions) + { + Rage.Native.NativeFunction.Natives.x971DA0055324D033(barrier, BarrierMenu.BarrierTexture.Value); + if (BarrierMenu.SetBarrierLights.Checked) + { + Rage.Native.NativeFunction.Natives.SET_ENTITY_LIGHTS(barrier, false); + } + else + { + Rage.Native.NativeFunction.Natives.SET_ENTITY_LIGHTS(barrier, true); + } + + //Rage.Native.NativeFunction.Natives.SET_ENTITY_TRAFFICLIGHT_OVERRIDE(barrier, setBarrierTrafficLight.Index); + barrier.IsPositionFrozen = true; + GameFiber.Sleep(50); + if (barrier && !BarrierMenu.Immobile.Checked) + { + barrier.IsPositionFrozen = false; + } + } + Barriers.Add(new Barrier(barrier, barrier.Position, barrier.Heading, BarrierMenu.Invincible.Checked, BarrierMenu.Immobile.Checked)); + + BarrierMenu.RemoveBarrierOptions.Enabled = true; + BarrierMenu.ResetBarriers.Enabled = true; + } + + void SpawnFlare() + { + var flare = new Weapon("weapon_flare", PlaceholderBarrier.Position, 1); + Rage.Native.NativeFunction.Natives.SET_ENTITY_DYNAMIC(flare, true); + GameFiber.Sleep(1); + GameFiber.StartNew(() => + { + while (flare && flare.HeightAboveGround > 0.05f) + { + GameFiber.Yield(); + } + GameFiber.Sleep(1000); + if (flare) + { + flare.IsPositionFrozen = true; + } + }, "Spawn Flare Fiber"); + + Barriers.Add(new Barrier(flare, flare.Position, flare.Heading, BarrierMenu.Invincible.Checked, BarrierMenu.Immobile.Checked)); + BarrierMenu.RemoveBarrierOptions.Enabled = true; + } + } + + internal static void RemoveBarrier(int removeBarrierOptionsIndex) + { + switch (removeBarrierOptionsIndex) + { + case 0: + Barriers[Barriers.Count - 1].Object.Delete(); + Barriers.RemoveAt(Barriers.Count - 1); + break; + case 1: + var nearestBarrier = Barriers.OrderBy(b => b.Object.DistanceTo2D(Game.LocalPlayer.Character)).FirstOrDefault(); + if (nearestBarrier != null) + { + nearestBarrier.Object.Delete(); + Barriers.Remove(nearestBarrier); + } + break; + case 2: + foreach (Barrier b in Barriers.Where(b => b.Object)) + { + b.Object.Delete(); + } + if (Barriers.Count > 0) + { + Barriers.Clear(); + } + break; + } + + BarrierMenu.RemoveBarrierOptions.Enabled = Barriers.Count == 0 ? false : true; + BarrierMenu.ResetBarriers.Enabled = Barriers.Count == 0 ? false : true; + } + + internal static void ResetBarriers() + { + GameFiber.StartNew(() => + { + var currentBarriers = Barriers.Where(b => b.Model.Name != "0xa2c44e80").ToList(); // 0xa2c44e80 is the flare weapon hash + foreach (Barrier barrier in currentBarriers) + { + var newBarrier = new Rage.Object(barrier.Model, barrier.Position, barrier.Rotation); + newBarrier.SetPositionWithSnap(barrier.Position); + Rage.Native.NativeFunction.Natives.SET_ENTITY_DYNAMIC(newBarrier, true); + newBarrier.IsPositionFrozen = barrier.Immobile; + if (barrier.Invincible) + { + Rage.Native.NativeFunction.Natives.SET_DISABLE_FRAG_DAMAGE(newBarrier, true); + if (newBarrier.Model.Name != "prop_barrier_wat_03a") + { + Rage.Native.NativeFunction.Natives.SET_DISABLE_BREAKING(newBarrier, true); + } + } + //Rage.Native.NativeFunction.Natives.SET_ENTITY_TRAFFICLIGHT_OVERRIDE(newBarrier, setBarrierTrafficLight.Index); + newBarrier.IsPositionFrozen = true; + GameFiber.Sleep(50); + if (newBarrier && !barrier.Immobile) + { + newBarrier.IsPositionFrozen = false; + } + Barriers.Add(new Barrier(newBarrier, newBarrier.Position, newBarrier.Heading, barrier.Invincible, barrier.Immobile)); + + if (barrier.Object) + { + barrier.Object.Delete(); + } + Barriers.Remove(barrier); + } + currentBarriers.Clear(); + }, "Barrier Reset Fiber"); + + } + + internal static void RotateBarrier() + { + PlaceholderBarrier.Heading = BarrierMenu.RotateBarrier.Value; + PlaceholderBarrier.Position = UserInput.GetMousePositionForBarrier; + Rage.Native.NativeFunction.Natives.PLACE_OBJECT_ON_GROUND_PROPERLY(PlaceholderBarrier); + } + } +} diff --git a/SceneManager/Utils/DeleteAllPaths.cs b/SceneManager/Utils/DeleteAllPaths.cs new file mode 100644 index 0000000..9835960 --- /dev/null +++ b/SceneManager/Utils/DeleteAllPaths.cs @@ -0,0 +1,15 @@ +using Rage; + +namespace SceneManager.Utils +{ + internal static class DeleteAllPaths + { + internal static void Delete() + { + PathManager.Paths.ForEach(x => x.Delete()); + PathManager.Paths.Clear(); + Game.LogTrivial($"All paths deleted"); + Game.DisplayNotification($"~o~Scene Manager\n~w~All paths deleted."); + } + } +} diff --git a/SceneManager/Utils/DependencyChecker.cs b/SceneManager/Utils/DependencyChecker.cs new file mode 100644 index 0000000..7fffd9e --- /dev/null +++ b/SceneManager/Utils/DependencyChecker.cs @@ -0,0 +1,63 @@ +using Rage; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace SceneManager.Utils +{ + internal class DependencyChecker + { + internal static bool DependenciesInstalled() + { + if (!InputManagerChecker() || !CheckRNUIVersion()) + { + return false; + } + + return true; + } + + private static bool CheckRNUIVersion() + { + var directory = Directory.GetCurrentDirectory(); + var exists = File.Exists(directory + @"\RAGENativeUI.dll"); + if (!exists) + { + Game.LogTrivial($"RNUI was not found in the user's GTA V directory."); + Game.DisplayNotification($"~o~Scene Manager ~r~[Error]\n~w~RAGENativeUI.dll was not found in your GTA V directory. Please install RAGENativeUI and try again."); + return false; + } + + var userVersion = Assembly.LoadFrom(directory + @"\RAGENativeUI.dll").GetName().Version; + Version requiredMinimumVersion = new Version("1.7.0.0"); + if (userVersion >= requiredMinimumVersion) + { + Game.LogTrivial($"User's RNUI version: {userVersion}"); + return true; + } + else + { + Game.DisplayNotification($"~o~Scene Manager~r~[Error]\n~w~Your RAGENativeUI.dll version is below 1.7. Please update RAGENativeUI and try again."); + return false; + } + + } + + private static bool InputManagerChecker() + { + var directory = Directory.GetCurrentDirectory(); + var exists = File.Exists(directory + @"\InputManager.dll"); + if (!exists) + { + Game.LogTrivial($"InputManager was not found in the user's GTA V directory."); + Game.DisplayNotification($"~o~Scene Manager ~r~[Error]\n~w~InputManager.dll was not found in your GTA V directory. Please install InputManager.dll and try again."); + return false; + } + return true; + } + } +} diff --git a/SceneManager/Utils/DirectDriver.cs b/SceneManager/Utils/DirectDriver.cs new file mode 100644 index 0000000..b01824b --- /dev/null +++ b/SceneManager/Utils/DirectDriver.cs @@ -0,0 +1,77 @@ +using Rage; +using RAGENativeUI.Elements; +using SceneManager.Objects; +using System.Linq; + +namespace SceneManager.Utils +{ + // The only reason this class should change is to modify how vehicles are directed to paths. + internal static class DirectDriver + { + internal static bool ValidateOptions(UIMenuListScrollerItem menuItem, Path path, out Vehicle vehicle, out Waypoint waypoint) + { + var nearbyVehicle = Game.LocalPlayer.Character.GetNearbyVehicles(16).FirstOrDefault(v => v != Game.LocalPlayer.Character.CurrentVehicle && v.VehicleAndDriverValid()); + vehicle = nearbyVehicle; + waypoint = null; + if (!nearbyVehicle) + { + Game.LogTrivial($"Nearby vehicle is null."); + return false; + } + + var firstWaypoint = path.Waypoints.First(); + if (menuItem.SelectedItem == "First waypoint" && firstWaypoint == null) + { + Game.LogTrivial($"First waypoint is null."); + return false; + } + else if(menuItem.SelectedItem == "First waypoint" && firstWaypoint != null) + { + waypoint = firstWaypoint; + return true; + } + + var nearestWaypoint = path.Waypoints.Where(wp => wp.Position.DistanceTo2D(nearbyVehicle.FrontPosition) < wp.Position.DistanceTo2D(nearbyVehicle.RearPosition)).OrderBy(wp => wp.Position.DistanceTo2D(nearbyVehicle)).FirstOrDefault(); + if (menuItem.SelectedItem == "Nearest waypoint" && nearestWaypoint == null) + { + Game.LogTrivial($"Nearest waypoint is null."); + return false; + } + else if (menuItem.SelectedItem == "Nearest waypoint" && nearestWaypoint != null) + { + waypoint = nearestWaypoint; + return true; + } + + Game.LogTrivial($"What are we doing here?"); + return false; + } + + internal static void Direct(Vehicle nearbyVehicle, Path path, Waypoint targetWaypoint) + { + var nearbyVehiclesPath = PathManager.Paths.FirstOrDefault(p => p.CollectedVehicles.Any(v => v.Vehicle == nearbyVehicle)); + if(nearbyVehiclesPath == null) + { + Game.LogTrivial($"Nearby vehicle does not belong to any path."); + } + + var collectedVehicleOnThisPath = path.CollectedVehicles.FirstOrDefault(v => v.Vehicle == nearbyVehicle); + var nearbyCollectedVehicleOtherPath = nearbyVehiclesPath?.CollectedVehicles.FirstOrDefault(v => v.Vehicle == nearbyVehicle); + if (collectedVehicleOnThisPath == null) + { + Game.LogTrivial($"Nearby vehicle does not belong to this path."); + if (nearbyCollectedVehicleOtherPath != null) + { + Game.LogTrivial($"Dismissing nearby vehicle from other path."); + nearbyCollectedVehicleOtherPath.Dismiss(Dismiss.FromDirected, path); + } + Game.LogTrivial($"[Direct Driver] Adding {nearbyVehicle.Model.Name} to directed path."); + path.CollectedVehicles.Add(collectedVehicleOnThisPath = new CollectedVehicle(nearbyVehicle, path)); + collectedVehicleOnThisPath.Directed = true; + collectedVehicleOnThisPath.Driver.Tasks.Clear(); + } + + GameFiber.StartNew(() => collectedVehicleOnThisPath.AssignWaypointTasks(path, targetWaypoint)); + } + } +} diff --git a/SceneManager/Utils/DismissDriver.cs b/SceneManager/Utils/DismissDriver.cs new file mode 100644 index 0000000..32ad386 --- /dev/null +++ b/SceneManager/Utils/DismissDriver.cs @@ -0,0 +1,43 @@ +using Rage; +using SceneManager.Objects; +using System.Linq; + +namespace SceneManager.Utils +{ + internal static class DismissDriver + { + internal static void Dismiss(int dismissIndex) + { + var nearbyVehicle = Game.LocalPlayer.Character.GetNearbyVehicles(16).FirstOrDefault(v => v != Game.LocalPlayer.Character.CurrentVehicle && v.VehicleAndDriverValid()); + if (!nearbyVehicle) + { + Game.LogTrivial($"Nearby vehicle is null."); + return; + } + + if(dismissIndex == (int)Utils.Dismiss.FromWorld) + { + // Have to loop because sometimes police peds don't get deleted properly + // The path should handle removing the deleted driver/vehicle from its list of collected vehicles + while (nearbyVehicle && nearbyVehicle.HasOccupants) + { + nearbyVehicle.Occupants.ToList().ForEach(x => x.Delete()); + GameFiber.Yield(); + } + if (nearbyVehicle) + { + nearbyVehicle.Delete(); + } + return; + } + else + { + CollectedVehicle collectedVehicle = PathManager.Paths.SelectMany(x => x.CollectedVehicles).FirstOrDefault(x => x.Vehicle == nearbyVehicle); + if(collectedVehicle != null) + { + collectedVehicle.Dismiss((Dismiss)dismissIndex); + } + } + } + } +} diff --git a/SceneManager/Utils/Hints.cs b/SceneManager/Utils/Hints.cs new file mode 100644 index 0000000..8405dbb --- /dev/null +++ b/SceneManager/Utils/Hints.cs @@ -0,0 +1,39 @@ +using Rage; +using System.Windows.Forms; +using SceneManager.Menus; + +namespace SceneManager +{ + class Hints + { + internal static bool Enabled { get; set; } = SettingsMenu.Hints.Checked; + + internal static void Display(string message) + { + if (Enabled) + { + Game.DisplayNotification($"{message}"); + } + } + + internal static void DisplayHintsToOpenMenu() + { + if (Settings.ModifierKey == Keys.None && Settings.ModifierButton == ControllerButtons.None) + { + Display($"~o~Scene Manager ~y~[Hint]\n~w~To open the menu, press the ~b~{Settings.ToggleKey} key ~w~or ~b~{Settings.ToggleButton} button"); + } + else if (Settings.ModifierKey == Keys.None) + { + Display($"~o~Scene Manager ~y~[Hint]\n~w~To open the menu, press the ~b~{Settings.ToggleKey} key ~w~or ~b~{Settings.ModifierButton} ~w~+ ~b~{Settings.ToggleButton} buttons"); + } + else if (Settings.ModifierButton == ControllerButtons.None) + { + Display($"~o~Scene Manager ~y~[Hint]\n~w~To open the menu, press ~b~{Settings.ModifierKey} ~w~+ ~b~{Settings.ToggleKey} ~w~or the ~b~{Settings.ToggleButton} button"); + } + else + { + Display($"~o~Scene Manager ~y~[Hint]\n~w~To open the menu, press the ~b~{Settings.ModifierKey} ~w~+ ~b~{Settings.ToggleKey} keys ~w~or ~b~{Settings.ModifierButton} ~w~+ ~b~{Settings.ToggleButton} buttons"); + } + } + } +} diff --git a/SceneManager/Utils/PathManager.cs b/SceneManager/Utils/PathManager.cs new file mode 100644 index 0000000..7d27fe6 --- /dev/null +++ b/SceneManager/Utils/PathManager.cs @@ -0,0 +1,258 @@ +using Rage; +using RAGENativeUI.Elements; +using SceneManager.Menus; +using SceneManager.Objects; +using System.Collections.Generic; +using System.Linq; + +namespace SceneManager.Utils +{ + internal class PathManager + { + internal static List Paths { get; } = new List(10); + + internal static Path ImportPath(Path importedPath) + { + importedPath.State = State.Creating; + + var firstVacantIndex = Paths.IndexOf(Paths.FirstOrDefault(x => x.State != State.Creating)) + 1; + if (firstVacantIndex < 0) + { + firstVacantIndex = 0; + } + var pathNumber = firstVacantIndex + 1; + + importedPath.Number = pathNumber; + Paths.Insert(firstVacantIndex, importedPath); + + Game.LogTrivial($"Importing path {importedPath.Number} at Paths index {firstVacantIndex}"); + Game.DisplayNotification($"~o~Scene Manager ~y~[Importing]\n~w~Path {importedPath.Number} import started."); + + return importedPath; + } + + internal static void ExportPath() + { + var currentPath = Paths[PathMainMenu.EditPath.Index]; + // Reference PNWParks's UserInput class from LiveLights + var filename = UserInput.GetFileName("Type the name you would like to save your file as", "Enter a filename", 100) + ".xml"; + + // If filename != null or empty, check if export directory exists (GTA V/Plugins/SceneManager/Saved Paths) + if (string.IsNullOrWhiteSpace(filename)) + { + Game.DisplayHelp($"Invalid filename given. Filename cannot be null, empty, or consist of just white spaces."); + Game.LogTrivial($"Invalid filename given. Filename cannot be null, empty, or consist of just white spaces."); + return; + } + Game.LogTrivial($"Filename: {filename}"); + currentPath.Save(filename); + currentPath.Name = filename.Remove(filename.Length - 4); + Game.LogTrivial($"Path name: {currentPath.Name}"); + Game.LogTrivial($"Exporting path {currentPath.Number}"); + Game.DisplayNotification($"~o~Scene Manager ~y~[Exporting]\n~w~Path {currentPath.Number} exported."); + Settings.ImportPaths(); + PathMainMenu.ImportPath.Enabled = true; + ImportPathMenu.BuildImportMenu(); + } + + internal static Path InitializeNewPath() + { + PathCreationMenu.PathCreationState = State.Creating; + + var firstVacantIndex = Paths.IndexOf(Paths.FirstOrDefault(x => x.State != State.Creating)) + 1; + if(firstVacantIndex < 0) + { + firstVacantIndex = 0; + } + var pathNumber = firstVacantIndex + 1; + + Path newPath = new Path(pathNumber, State.Creating); + Paths.Insert(firstVacantIndex, newPath); + + Game.LogTrivial($"Creating path {newPath.Number} at Paths index {firstVacantIndex}"); + Game.DisplayNotification($"~o~Scene Manager ~y~[Creating]\n~w~Path {newPath.Number} started."); + + PathCreationMenu.RemoveLastWaypoint.Enabled = false; + PathCreationMenu.EndPathCreation.Enabled = false; + + return newPath; + } + + internal static void AddWaypoint(Path currentPath) + { + var waypointNumber = currentPath.Waypoints.Count + 1; + DrivingFlagType drivingFlag = PathCreationMenu.DirectWaypoint.Checked ? DrivingFlagType.Direct : DrivingFlagType.Normal; + Waypoint newWaypoint; + if (PathCreationMenu.CollectorWaypoint.Checked) + { + newWaypoint = new Waypoint(currentPath, waypointNumber, UserInput.GetMousePosition, SetDriveSpeedForWaypoint(), drivingFlag, PathCreationMenu.StopWaypoint.Checked, true, PathCreationMenu.CollectorRadius.Value, PathCreationMenu.SpeedZoneRadius.Value); + } + else + { + newWaypoint = new Waypoint(currentPath, waypointNumber, UserInput.GetMousePosition, SetDriveSpeedForWaypoint(), drivingFlag, PathCreationMenu.StopWaypoint.Checked); + } + currentPath.Waypoints.Add(newWaypoint); + Game.LogTrivial($"Path {currentPath.Number} Waypoint {waypointNumber} added [Driving style: {drivingFlag} | Stop waypoint: {newWaypoint.IsStopWaypoint} | Speed: {newWaypoint.Speed} | Collector: {newWaypoint.IsCollector}]"); + + if(currentPath.Waypoints.Count == 1) + { + PathMainMenu.CreateNewPath.Text = $"Continue Creating Path {currentPath.Number}"; + } + } + + internal static void AddNewEditWaypoint(Path currentPath) + { + DrivingFlagType drivingFlag = EditWaypointMenu.DirectWaypointBehavior.Checked ? DrivingFlagType.Direct : DrivingFlagType.Normal; + + if (EditWaypointMenu.CollectorWaypoint.Checked) + { + currentPath.Waypoints.Add(new Waypoint(currentPath, currentPath.Waypoints.Last().Number + 1, UserInput.GetMousePosition, SetDriveSpeedForWaypoint(), drivingFlag, EditWaypointMenu.StopWaypointType.Checked, true, EditWaypointMenu.ChangeCollectorRadius.Value, EditWaypointMenu.ChangeSpeedZoneRadius.Value)); + } + else + { + currentPath.Waypoints.Add(new Waypoint(currentPath, currentPath.Waypoints.Last().Number + 1, UserInput.GetMousePosition, SetDriveSpeedForWaypoint(), drivingFlag, EditWaypointMenu.StopWaypointType.Checked)); + } + Game.LogTrivial($"New waypoint (#{currentPath.Waypoints.Last().Number}) added."); + } + + internal static void UpdateWaypoint() + { + var currentPath = Paths[PathMainMenu.EditPath.Index]; + var currentWaypoint = currentPath.Waypoints[EditWaypointMenu.EditWaypoint.Index]; + DrivingFlagType drivingFlag = EditWaypointMenu.DirectWaypointBehavior.Checked ? DrivingFlagType.Direct : DrivingFlagType.Normal; + + if (currentPath.Waypoints.Count == 1) + { + currentWaypoint.UpdateWaypoint(currentWaypoint, UserInput.GetMousePosition, drivingFlag, EditWaypointMenu.StopWaypointType.Checked, SetDriveSpeedForWaypoint(), true, EditWaypointMenu.ChangeCollectorRadius.Value, EditWaypointMenu.ChangeSpeedZoneRadius.Value, EditWaypointMenu.UpdateWaypointPosition.Checked); + } + else + { + currentWaypoint.UpdateWaypoint(currentWaypoint, UserInput.GetMousePosition, drivingFlag, EditWaypointMenu.StopWaypointType.Checked, SetDriveSpeedForWaypoint(), EditWaypointMenu.CollectorWaypoint.Checked, EditWaypointMenu.ChangeCollectorRadius.Value, EditWaypointMenu.ChangeSpeedZoneRadius.Value, EditWaypointMenu.UpdateWaypointPosition.Checked); + } + Game.LogTrivial($"Path {currentPath.Number} Waypoint {currentWaypoint.Number} updated [Driving style: {drivingFlag} | Stop waypoint: {EditWaypointMenu.StopWaypointType.Checked} | Speed: {EditWaypointMenu.ChangeWaypointSpeed.Value} | Collector: {currentWaypoint.IsCollector}]"); + + EditWaypointMenu.UpdateWaypointPosition.Checked = false; + Game.DisplayNotification($"~o~Scene Manager ~g~[Success]~w~\nWaypoint {currentWaypoint.Number} updated."); + } + + private static float SetDriveSpeedForWaypoint() + { + float convertedSpeed; + if (SettingsMenu.SpeedUnits.SelectedItem == SpeedUnits.MPH) + { + //Logger.Log($"Original speed: {waypointSpeeds[waypointSpeed.Index]}{SettingsMenu.speedUnits.SelectedItem}"); + convertedSpeed = MathHelper.ConvertMilesPerHourToMetersPerSecond(PathCreationMenu.WaypointSpeed.Value); + //Logger.Log($"Converted speed: {convertedSpeed}m/s"); + } + else + { + //Logger.Log($"Original speed: {waypointSpeeds[waypointSpeed.Index]}{SettingsMenu.speedUnits.SelectedItem}"); + convertedSpeed = MathHelper.ConvertKilometersPerHourToMetersPerSecond(PathCreationMenu.WaypointSpeed.Value); + //Logger.Log($"Converted speed: {convertedSpeed}m/s"); + } + + return convertedSpeed; + } + + internal static void RemoveWaypoint(Path currentPath) + { + Waypoint lastWaypoint = currentPath.Waypoints.Last(); + lastWaypoint.Delete(); + currentPath.Waypoints.Remove(lastWaypoint); + } + + internal static void RemoveEditWaypoint(Path currentPath) + { + var currentWaypoint = currentPath.Waypoints[EditWaypointMenu.EditWaypoint.Index]; + if (currentPath.Waypoints.Count == 1) + { + Game.LogTrivial($"Deleting the last waypoint from the path."); + currentPath.Delete(); + Paths.Remove(currentPath); + PathMainMenu.BuildPathMenu(); + + EditWaypointMenu.Menu.Visible = false; + PathMainMenu.Menu.Visible = true; + return; + } + + currentWaypoint.Delete(); + currentPath.Waypoints.Remove(currentWaypoint); + Game.LogTrivial($"[Path {currentPath.Number}] Waypoint {currentWaypoint.Number} ({currentWaypoint.DrivingFlagType}) removed"); + currentPath.Waypoints.ForEach(x => x.Number = currentPath.Waypoints.IndexOf(x) + 1); + + DefaultWaypointToCollector(currentPath); + } + + private static void DefaultWaypointToCollector(Path currentPath) + { + if (currentPath.Waypoints.Count == 1) + { + DrivingFlagType drivingFlag = EditWaypointMenu.DirectWaypointBehavior.Checked ? DrivingFlagType.Direct : DrivingFlagType.Normal; + Hints.Display($"~o~Scene Manager ~y~[Hint]~w~\nYour path's first waypoint ~b~must~w~ be a collector. If it's not, it will automatically be made into one."); + Game.LogTrivial($"The path only has 1 waypoint left, this waypoint must be a collector."); + currentPath.Waypoints[0].UpdateWaypoint(currentPath.Waypoints.First(), UserInput.GetMousePosition, drivingFlag, EditWaypointMenu.StopWaypointType.Checked, SetDriveSpeedForWaypoint(), true, EditWaypointMenu.ChangeCollectorRadius.Value, EditWaypointMenu.ChangeSpeedZoneRadius.Value, EditWaypointMenu.UpdateWaypointPosition.Checked); + EditWaypointMenu.CollectorWaypoint.Checked = true; + EditWaypointMenu.ChangeCollectorRadius.Enabled = true; + EditWaypointMenu.ChangeSpeedZoneRadius.Enabled = true; + } + } + + internal static void EndPath(Path currentPath) + { + Game.LogTrivial($"[Path Creation] Path {currentPath.Number} finished with {currentPath.Waypoints.Count} waypoints."); + Game.DisplayNotification($"~o~Scene Manager ~g~[Success]\n~w~Path {currentPath.Number} complete."); + currentPath.State = State.Finished; + currentPath.IsEnabled = true; + currentPath.Waypoints.ForEach(x => x.EnableBlip()); + GameFiber.StartNew(() => currentPath.LoopForVehiclesToBeDismissed(), "Vehicle Cleanup Loop Fiber"); + GameFiber.StartNew(() => currentPath.LoopWaypointCollection(), "Waypoint Collection Loop Fiber"); + + PathMainMenu.CreateNewPath.Text = "Create New Path"; + PathMainMenu.BuildPathMenu(); + PathMainMenu.Menu.Visible = true; + } + + internal static void TogglePathCreationMenuItems(Path currentPath) + { + if(currentPath.Waypoints.Count == 1) + { + PathCreationMenu.CollectorWaypoint.Enabled = true; + PathCreationMenu.CollectorWaypoint.Checked = false; + PathCreationMenu.RemoveLastWaypoint.Enabled = true; + PathCreationMenu.EndPathCreation.Enabled = true; + } + + if (currentPath.Waypoints.Count < 1) + { + PathCreationMenu.CollectorWaypoint.Enabled = false; + PathCreationMenu.CollectorWaypoint.Checked = true; + PathCreationMenu.RemoveLastWaypoint.Enabled = false; + PathCreationMenu.EndPathCreation.Enabled = false; + } + + if (PathCreationMenu.CollectorWaypoint.Checked) + { + PathCreationMenu.CollectorRadius.Enabled = true; + PathCreationMenu.SpeedZoneRadius.Enabled = true; + } + else + { + PathCreationMenu.CollectorRadius.Enabled = false; + PathCreationMenu.SpeedZoneRadius.Enabled = false; + } + } + + internal static void ToggleBlips(bool enabled) + { + if (enabled) + { + Paths.SelectMany(x => x.Waypoints).ToList().ForEach(x => x.EnableBlip()); + } + else + { + Paths.SelectMany(x => x.Waypoints).ToList().ForEach(x => x.DisableBlip()); + } + } + } +} diff --git a/SceneManager/Utils/TogglePaths.cs b/SceneManager/Utils/TogglePaths.cs new file mode 100644 index 0000000..f94f4d5 --- /dev/null +++ b/SceneManager/Utils/TogglePaths.cs @@ -0,0 +1,21 @@ +using Rage; + +namespace SceneManager.Utils +{ + internal static class TogglePaths + { + internal static void Toggle(bool disable) + { + if (disable) + { + PathManager.Paths.ForEach(x => x.DisablePath()); + Game.LogTrivial($"All paths disabled."); + } + else + { + PathManager.Paths.ForEach(x => x.EnablePath()); + Game.LogTrivial($"All paths enabled."); + } + } + } +} diff --git a/SceneManager/Utils/UserInput.cs b/SceneManager/Utils/UserInput.cs new file mode 100644 index 0000000..529e97f --- /dev/null +++ b/SceneManager/Utils/UserInput.cs @@ -0,0 +1,229 @@ +using InputManager; +using Rage; +using Rage.Native; +using RAGENativeUI; +using RAGENativeUI.Elements; +using SceneManager.Menus; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace SceneManager.Utils +{ + // The only reason this class should change is to modify how user input is handled + class UserInput + { + internal static void HandleKeyPress() + { + while (true) + { + bool isTextEntryOpen = (Rage.Native.NativeFunction.Natives.UPDATE_ONSCREEN_KEYBOARD() == 0); + if (!isTextEntryOpen && MenuKeysPressed()) + { + Menus.MainMenu.DisplayMenu(); + GameFiber.StartNew(() => MenuManager.Update(), "Menu Processing Fiber"); + } + +#if DEBUG + if (MenuManager.MenuPool.IsAnyMenuOpen()) + { + Game.DisplaySubtitle($"You are using a test build of Scene Manager. Please report any bugs/crashes in the Discord server."); + } +#endif + GameFiber.Yield(); + } + } + + private static bool MenuKeysPressed() + { + if (MenuKeysPressed()) + { + return true; + } + + return false; + + bool MenuKeysPressed() + { + if (MenuManager.AreMenusClosed() && + ((Settings.ModifierKey == Keys.None && Game.IsKeyDown(Settings.ToggleKey)) || + (Game.IsKeyDownRightNow(Settings.ModifierKey) && Game.IsKeyDown(Settings.ToggleKey)) || + (Settings.ModifierButton == ControllerButtons.None && Game.IsControllerButtonDown(Settings.ToggleButton)) || + (Game.IsControllerButtonDownRightNow(Settings.ModifierButton) && Game.IsControllerButtonDown(Settings.ToggleButton)))) + { + return true; + } + + return false; + } + } + + internal static Vector3 GetMousePosition { get { return GetMousePositionInWorld(); } } + + internal static Vector3 GetMousePositionForBarrier { get { return GetMousePositionInWorld(Settings.BarrierPlacementDistance); } } + + private static Vector3 GetMousePositionInWorld(float maxDistance = 100f) + { + HitResult TracePlayerView(float maxTraceDistance = 100f, TraceFlags flags = TraceFlags.IntersectWorld) => TracePlayerView2(out Vector3 v1, out Vector3 v2, maxTraceDistance, flags); + + HitResult TracePlayerView2(out Vector3 start, out Vector3 end, float maxTraceDistance, TraceFlags flags) + { + Vector3 direction = GetPlayerLookingDirection(out start); + end = start + (maxTraceDistance * direction); + return World.TraceLine(start, end, flags); + } + + Vector3 GetPlayerLookingDirection(out Vector3 camPosition) + { + if (Camera.RenderingCamera) + { + camPosition = Camera.RenderingCamera.Position; + return Camera.RenderingCamera.Direction; + } + else + { + float pitch = Rage.Native.NativeFunction.Natives.GET_GAMEPLAY_CAM_RELATIVE_PITCH(); + float heading = Rage.Native.NativeFunction.Natives.GET_GAMEPLAY_CAM_RELATIVE_HEADING(); + + camPosition = Rage.Native.NativeFunction.Natives.GET_GAMEPLAY_CAM_COORD(); + return (Game.LocalPlayer.Character.Rotation + new Rotator(pitch, 0, heading)).ToVector().ToNormalized(); + } + } + + return TracePlayerView(maxDistance, TraceFlags.IntersectWorld).HitPosition; + } + + internal static void InitializeMenuMouseControl(UIMenu menu, List scrollerItems) + { + while (menu.Visible) + { + var selectedScroller = menu.MenuItems.Where(x => scrollerItems.Contains(x) && x.Selected && x.Enabled).FirstOrDefault(); + if (selectedScroller != null) + { + OnWheelScroll(menu, selectedScroller, scrollerItems); + } + + if (Game.IsKeyDown(Keys.LButton) && Rage.Native.NativeFunction.Natives.UPDATE_ONSCREEN_KEYBOARD() != 0) + { + Keyboard.KeyDown(Keys.Enter); + GameFiber.Wait(1); + Keyboard.KeyUp(Keys.Enter); + } + + if (menu.SubtitleText.Contains("Path Creation Menu")) + { + DrawWaypointMarker(); + } + GameFiber.Yield(); + } + } + + private static void OnWheelScroll(UIMenu menu, UIMenuItem selectedScroller, List scrollerItems) + { + var menuScrollingDisabled = false; + var menuItems = menu.MenuItems.Where(x => x != selectedScroller); + + while (Game.IsShiftKeyDownRightNow) + { + menu.ResetKey(Common.MenuControls.Up); + menu.ResetKey(Common.MenuControls.Down); + menuScrollingDisabled = true; + ScrollMenuItem(); + if (menu.SubtitleText.Contains("Path Creation Menu") || menu.SubtitleText.Contains("Edit Waypoint")) + { + CompareScrollerValues(); + } + if (menu.SubtitleText.Contains("Path Creation Menu")) + { + DrawWaypointMarker(); + } + GameFiber.Yield(); + } + + if (menuScrollingDisabled) + { + menuScrollingDisabled = false; + menu.SetKey(Common.MenuControls.Up, GameControl.CursorScrollUp); + menu.SetKey(Common.MenuControls.Up, GameControl.CellphoneUp); + menu.SetKey(Common.MenuControls.Down, GameControl.CursorScrollDown); + menu.SetKey(Common.MenuControls.Down, GameControl.CellphoneDown); + } + + void ScrollMenuItem() + { + if (Game.GetMouseWheelDelta() > 0) + { + Keyboard.KeyDown(Keys.Right); + GameFiber.Wait(1); + Keyboard.KeyUp(Keys.Right); + } + else if (Game.GetMouseWheelDelta() < 0) + { + Keyboard.KeyDown(Keys.Left); + GameFiber.Wait(1); + Keyboard.KeyUp(Keys.Left); + } + } + + void CompareScrollerValues() + { + var collectorRadius = (UIMenuNumericScrollerItem)scrollerItems.Where(x => x.Text == "Collection Radius").FirstOrDefault(); + var speedZoneRadius = (UIMenuNumericScrollerItem)scrollerItems.Where(x => x.Text == "Speed Zone Radius").FirstOrDefault(); + + if (selectedScroller.Text == "Collection Radius" || selectedScroller.Text == "Speed Zone Radius") + { + if (selectedScroller == collectorRadius && collectorRadius.Value > speedZoneRadius.Value) + { + while (collectorRadius.Value > speedZoneRadius.Value) + { + speedZoneRadius.ScrollToNextOption(); + } + } + if (selectedScroller == speedZoneRadius && speedZoneRadius.Value < collectorRadius.Value) + { + collectorRadius.Value = speedZoneRadius.Value; + } + } + } + } + + private static void DrawWaypointMarker() + { + var waypointPosition = GetMousePosition; + if (SettingsMenu.ThreeDWaypoints.Checked && PathCreationMenu.CollectorWaypoint.Checked) + { + Rage.Native.NativeFunction.Natives.DRAW_MARKER(1, waypointPosition, 0, 0, 0, 0, 0, 0, (float)PathCreationMenu.CollectorRadius.Value * 2, (float)PathCreationMenu.CollectorRadius.Value * 2, 1f, 80, 130, 255, 80, false, false, 2, false, 0, 0, false); + Rage.Native.NativeFunction.Natives.DRAW_MARKER(1, waypointPosition, 0, 0, 0, 0, 0, 0, (float)PathCreationMenu.SpeedZoneRadius.Value * 2, (float)PathCreationMenu.SpeedZoneRadius.Value * 2, 1f, 255, 185, 80, 80, false, false, 2, false, 0, 0, false); + } + else if (PathCreationMenu.StopWaypoint.Checked) + { + Rage.Native.NativeFunction.Natives.DRAW_MARKER(1, waypointPosition, 0, 0, 0, 0, 0, 0, 1f, 1f, 1f, 255, 65, 65, 80, false, false, 2, false, 0, 0, false); + } + else + { + Rage.Native.NativeFunction.Natives.DRAW_MARKER(1, waypointPosition, 0, 0, 0, 0, 0, 0, 1f, 1f, 1f, 65, 255, 65, 80, false, false, 2, false, 0, 0, false); + } + } + + internal static string GetFileName(string windowTitle, string defaultText, int maxLength) + { + NativeFunction.Natives.DISABLE_ALL_CONTROL_ACTIONS(2); + + NativeFunction.Natives.DISPLAY_ONSCREEN_KEYBOARD(true, windowTitle, 0, defaultText, 0, 0, 0, maxLength); + Game.DisplayHelp("Enter the filename you would like to save your path as\n~INPUT_FRONTEND_ACCEPT~ Export path\n~INPUT_FRONTEND_CANCEL~ Cancel", true); + Game.DisplaySubtitle(windowTitle, 100000); + + while (NativeFunction.Natives.UPDATE_ONSCREEN_KEYBOARD() == 0) + { + GameFiber.Yield(); + } + + NativeFunction.Natives.ENABLE_ALL_CONTROL_ACTIONS(2); + Game.DisplaySubtitle("", 5); + Game.HideHelp(); + + return NativeFunction.Natives.GET_ONSCREEN_KEYBOARD_RESULT(); + } + + } +}