From 0ad4a16f0a55b8aa8e340f502d7ecc8b244940f2 Mon Sep 17 00:00:00 2001 From: Explooosion-code <61145047+Explooosion-code@users.noreply.github.com> Date: Thu, 25 Mar 2021 10:48:26 +0100 Subject: [PATCH] Added EntitySpawner to developer tools I really missed a feature like this to set up scenes, or spawn vehicles/peds for specific needs. Its now possible with this feature. --- SharedClasses/PermissionsManager.cs | 1 + vMenu/EntitySpawner.cs | 276 ++++++++++++++++++++++++++++ vMenu/menus/MiscSettings.cs | 52 +++++- vMenuServer/config/permissions.cfg | 1 + 4 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 vMenu/EntitySpawner.cs diff --git a/SharedClasses/PermissionsManager.cs b/SharedClasses/PermissionsManager.cs index edf760d2..c27de37a 100644 --- a/SharedClasses/PermissionsManager.cs +++ b/SharedClasses/PermissionsManager.cs @@ -325,6 +325,7 @@ public enum Permission MSRestoreAppearance, MSRestoreWeapons, MSDriftMode, + MSEntitySpawner, #endregion // Voice Chat diff --git a/vMenu/EntitySpawner.cs b/vMenu/EntitySpawner.cs new file mode 100644 index 00000000..a928b0eb --- /dev/null +++ b/vMenu/EntitySpawner.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Threading.Tasks; +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; + +namespace vMenuClient +{ + public class EntitySpawner : BaseScript + { + public static bool Active { get; private set; } = false; + public static Entity CurrentEntity { get; private set; } = null; + private int scaleform = 0; + private float rotateSpeed = 20f; + + private const float RayDistance = 25f; + + /// + /// Constructor. + /// + + public EntitySpawner() + { + #if DEBUG + RegisterCommand("testEntity", new Action>((source, args) => + { + string prop = (string)args[0]; + SpawnEntity(prop, Game.PlayerPed.Position); + }), false); + + RegisterCommand("endTest", new Action(() => + { + FinishPlacement(); + }), false); + #endif + } + + #region PublicMethods + + /// + /// Method for spawning entity with EntitySpawner. After entity is spawned you will be able to change + /// position of entity with your mouse. + /// + /// model of entity as string + /// initial coords for the entity + /// true spawn was succesful + + public static void SpawnEntity(string model, Vector3 coords) + { + SpawnEntity((uint) GetHashKey(model), coords); + } + + /// + /// Method for spawning entity with EntitySpawner. After entity is spawned you will be able to change + /// position of entity with your mouse. + /// + /// model of entity as hash + /// initial coords for the entity + /// true spawn was succesful + public static async void SpawnEntity(uint model, Vector3 coords) + { + if (!IsModelValid(model)) + { + Notify.Error(CommonErrors.InvalidInput); + return; + } + + if (CurrentEntity != null) + { + Notify.Error("One entity is currently beeing processed"); + return; + } + + int handle = 0; + RequestModel(model); + while (!HasModelLoaded(model)) + { + await Delay(1); + } + if (IsModelAPed(model)) + { + handle = CreatePed(4, model, coords.X, coords.Y, coords.Z, Game.PlayerPed.Heading, true, true); + } else if (IsModelAVehicle(model)) + { + int modelClass = GetVehicleClassFromName(model); + if (!VehicleSpawner.allowedCategories[modelClass]) + { + Notify.Alert("You are not allowed to spawn this vehicle, because it belongs to a category which is restricted by the server owner."); + return; + } + handle = CreateVehicle(model, coords.X, coords.Y, coords.Z, Game.PlayerPed.Heading, true, true); + } + else + { + handle = CreateObject((int)model, coords.X, coords.Y, coords.Z, true, true, true); + } + + SetEntityAsMissionEntity(handle, true, true); // Set As mission to prevent despawning + + CurrentEntity = Entity.FromHandle(handle); + + if (!CurrentEntity.Exists()) + throw new Exception("Failed to create entity"); + + + if (!Active) + Active = true; + } + + /// + /// Method used to confirm location of prop and finish placement + /// + + public static void FinishPlacement() + { + Active = false; + CurrentEntity = null; + } + + #endregion + + #region InternalMethods + + /// + /// Used internally for drawing of help text + /// + private void DrawButtons() //TODO: Right keys + { + BeginScaleformMovieMethod(scaleform, "CLEAR_ALL"); + EndScaleformMovieMethod(); + + BeginScaleformMovieMethod(scaleform, "SET_DATA_SLOT"); + ScaleformMovieMethodAddParamInt(0); + PushScaleformMovieMethodParameterString("~INPUT_VEH_FLY_ROLL_LR~"); + PushScaleformMovieMethodParameterString("Rotate Object"); + EndScaleformMovieMethod(); + + BeginScaleformMovieMethod(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS"); + ScaleformMovieMethodAddParamInt(0); + EndScaleformMovieMethod(); + + DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0); + } + + /// + /// Used internally for getting direction vector from rotation vector + /// + /// Input rotation vector + /// Output direction vector + + private Vector3 RotationToDirection(Vector3 rotation) + { + Vector3 adj = new Vector3( + (float)Math.PI / 180f * rotation.X, + (float)Math.PI / 180f * rotation.Y, + (float)Math.PI / 180f * rotation.Z + ); + + return new Vector3( + (float)(-Math.Sin(adj.Z) * Math.Abs(Math.Cos(adj.X))), + (float)(Math.Cos(adj.Z) * Math.Abs(Math.Cos(adj.X))), + (float)Math.Sin(adj.X) + ); + } + + /// + /// Used to get coords of reycast from player camera; + /// + /// destination if no hit was found and coords of hit if there was one + + private Vector3 GetCoordsPlayerIsLookingAt() + { + Vector3 camRotation = GetGameplayCamRot(0); + Vector3 camCoords = GetGameplayCamCoord(); + Vector3 camDirection = RotationToDirection(camRotation); + + Vector3 dest = new Vector3( + camCoords.X + camDirection.X * RayDistance, + camCoords.Y + camDirection.Y * RayDistance, + camCoords.Z + camDirection.Z * RayDistance + ); + + RaycastResult res = World.Raycast(camCoords, dest, IntersectOptions.Everything, Game.PlayerPed); + +#if DEBUG + DrawLine(Game.PlayerPed.Position.X, Game.PlayerPed.Position.Y,Game.PlayerPed.Position.Z, dest.X, dest.Y, dest.Z, 255, 0, 0, 255); +#endif + + return res.DitHit ? res.HitPosition : dest; + } + + #endregion + + /// + /// Main tick method for class + /// + [Tick] + private async Task MoveHandler() + { + if (Active) + { + scaleform = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); + while (!HasScaleformMovieLoaded(scaleform)) + { + await Delay(0); + } + } + else + { + if (scaleform != 0) + { + SetScaleformMovieAsNoLongerNeeded(ref scaleform); //Unload scaleform if there is no need to draw it + scaleform = 0; + } + } + + float headingOffset = 0f; + while (Active) + { + int handle = CurrentEntity.Handle; + if (CurrentEntity == null || !CurrentEntity.Exists()) + { + Active = false; + CurrentEntity = null; + break; + } + + DrawButtons(); + + FreezeEntityPosition(handle, true); + SetEntityInvincible(handle, true); + SetEntityCollision(handle, false, false); + SetEntityAlpha(handle, (int)(255 * 0.4), 0); + CurrentEntity.Heading = (GetGameplayCamRot(0).Z + headingOffset) % 360f; + + Vector3 newPosition = GetCoordsPlayerIsLookingAt(); + + CurrentEntity.Position = newPosition; + if (CurrentEntity.HeightAboveGround < 3.0f) + { + if (CurrentEntity.Model.IsVehicle) + { + SetVehicleOnGroundProperly(CurrentEntity.Handle); + } + else + { + PlaceObjectOnGroundProperly(CurrentEntity.Handle); + } + } + + // Controls + + if (Game.IsControlPressed(0, Control.VehicleFlyRollLeftOnly)) + { + headingOffset += rotateSpeed * Game.LastFrameTime; + } else if (Game.IsControlPressed(0, Control.VehicleFlyRollRightOnly)) + { + headingOffset -= rotateSpeed * Game.LastFrameTime; + } + + + await Delay(0); + + FreezeEntityPosition(handle, false); + SetEntityInvincible(handle, false); + SetEntityCollision(handle, true, true); + ResetEntityAlpha(handle); + } + + await Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/vMenu/menus/MiscSettings.cs b/vMenu/menus/MiscSettings.cs index e6133625..fd2d6ac1 100644 --- a/vMenu/menus/MiscSettings.cs +++ b/vMenu/menus/MiscSettings.cs @@ -16,6 +16,7 @@ public class MiscSettings private Menu menu; private Menu teleportOptionsMenu; private Menu developerToolsMenu; + private Menu entitySpawnerMenu; public bool ShowSpeedoKmh { get; private set; } = UserDefaults.MiscSpeedKmh; public bool ShowSpeedoMph { get; private set; } = UserDefaults.MiscSpeedMph; @@ -81,6 +82,7 @@ private void CreateMenu() menu = new Menu(Game.Player.Name, "Misc Settings"); teleportOptionsMenu = new Menu(Game.Player.Name, "Teleport Options"); developerToolsMenu = new Menu(Game.Player.Name, "Development Tools"); + entitySpawnerMenu = new Menu(Game.Player.Name, "Entity Spawner"); // teleport menu Menu teleportMenu = new Menu(Game.Player.Name, "Teleport Locations"); @@ -134,7 +136,10 @@ private void CreateMenu() MenuCheckboxItem lockCamX = new MenuCheckboxItem("Lock Camera Horizontal Rotation", "Locks your camera horizontal rotation. Could be useful in helicopters I guess.", false); MenuCheckboxItem lockCamY = new MenuCheckboxItem("Lock Camera Vertical Rotation", "Locks your camera vertical rotation. Could be useful in helicopters I guess.", false); - + // Entity spawner + MenuItem spawnNewEntity = new MenuItem("Spawn New Entity", "Spawns entity into the world and lets you set its position and rotation"); + MenuItem confirmEntityPosition = new MenuItem("Confirm Entity Position", "Stops placing entity and sets it at it current location."); + MenuItem cancelEntity = new MenuItem("Cancel", "Deletes current entity and cancels its placement"); Menu connectionSubmenu = new Menu(Game.Player.Name, "Connection Options"); MenuItem connectionSubmenuBtn = new MenuItem("Connection Options", "Server connection/game quit options."); @@ -503,6 +508,51 @@ private void CreateMenu() } }; + if (IsAllowed(Permission.MSEntitySpawner)) + { + MenuItem entSpawnerMenuBtn = new MenuItem("Entity Spawner", "Spawn and move entities") { Label = "→→→" }; + developerToolsMenu.AddMenuItem(entSpawnerMenuBtn); + MenuController.BindMenuItem(developerToolsMenu, entitySpawnerMenu, entSpawnerMenuBtn); + + entitySpawnerMenu.AddMenuItem(spawnNewEntity); + entitySpawnerMenu.AddMenuItem(confirmEntityPosition); + entitySpawnerMenu.AddMenuItem(cancelEntity); + + entitySpawnerMenu.OnItemSelect += async (sender, item, index) => + { + if (item == spawnNewEntity) + { + if (EntitySpawner.CurrentEntity != null || EntitySpawner.Active) + { + Notify.Error("You are already placing one entity, set its location or cancel and try again!"); + return; + } + + string result = await GetUserInput(windowTitle: "Enter model name"); + + if (String.IsNullOrEmpty(result)) + { + Notify.Error(CommonErrors.InvalidInput); + } + + EntitySpawner.SpawnEntity(result, Game.PlayerPed.Position); + } else if (item == confirmEntityPosition) + { + if (EntitySpawner.CurrentEntity != null) + { + EntitySpawner.FinishPlacement(); + } + else + { + Notify.Error("No entity to confirm position for!"); + } + } else if (item == cancelEntity) + { + EntitySpawner.CurrentEntity.Delete(); + } + }; + } + #endregion diff --git a/vMenuServer/config/permissions.cfg b/vMenuServer/config/permissions.cfg index 8b168b67..bcb5f2d0 100644 --- a/vMenuServer/config/permissions.cfg +++ b/vMenuServer/config/permissions.cfg @@ -485,6 +485,7 @@ add_ace builtin.everyone "vMenu.MiscSettings.ConnectionMenu" allow add_ace builtin.everyone "vMenu.MiscSettings.RestoreAppearance" allow add_ace builtin.everyone "vMenu.MiscSettings.RestoreWeapons" allow add_ace builtin.everyone "vMenu.MiscSettings.DriftMode" allow +add_ace group.moderator "vMenu.MiscSettings.EntitySpawner" allow # Probably not the best idea to give this feature for everyone #################################### # VOICE CHAT OPTIONS MENU #