Skip to content

Commit

Permalink
Added EntitySpawner to developer tools
Browse files Browse the repository at this point in the history
I really missed a feature like this to set up scenes, or spawn vehicles/peds for specific needs. Its now possible with this feature.
  • Loading branch information
Explooosion-code committed Mar 25, 2021
1 parent e8f4e1c commit 0ad4a16
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 1 deletion.
1 change: 1 addition & 0 deletions SharedClasses/PermissionsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ public enum Permission
MSRestoreAppearance,
MSRestoreWeapons,
MSDriftMode,
MSEntitySpawner,
#endregion

// Voice Chat
Expand Down
276 changes: 276 additions & 0 deletions vMenu/EntitySpawner.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Constructor.
/// </summary>

public EntitySpawner()
{
#if DEBUG
RegisterCommand("testEntity", new Action<int, List<object>>((source, args) =>
{
string prop = (string)args[0];
SpawnEntity(prop, Game.PlayerPed.Position);
}), false);

RegisterCommand("endTest", new Action(() =>
{
FinishPlacement();
}), false);
#endif
}

#region PublicMethods

/// <summary>
/// Method for spawning entity with EntitySpawner. After entity is spawned you will be able to change
/// position of entity with your mouse.
/// </summary>
/// <param name="model">model of entity as string</param>
/// <param name="coords">initial coords for the entity</param>
/// <returns>true spawn was succesful</returns>

public static void SpawnEntity(string model, Vector3 coords)
{
SpawnEntity((uint) GetHashKey(model), coords);
}

/// <summary>
/// Method for spawning entity with EntitySpawner. After entity is spawned you will be able to change
/// position of entity with your mouse.
/// </summary>
/// <param name="model">model of entity as hash</param>
/// <param name="coords">initial coords for the entity</param>
/// <returns>true spawn was succesful</returns>
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;
}

/// <summary>
/// Method used to confirm location of prop and finish placement
/// </summary>

public static void FinishPlacement()
{
Active = false;
CurrentEntity = null;
}

#endregion

#region InternalMethods

/// <summary>
/// Used internally for drawing of help text
/// </summary>
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);
}

/// <summary>
/// Used internally for getting direction vector from rotation vector
/// </summary>
/// <param name="rotation">Input rotation vector</param>
/// <returns>Output direction vector</returns>

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)
);
}

/// <summary>
/// Used to get coords of reycast from player camera;
/// </summary>
/// <returns>destination if no hit was found and coords of hit if there was one</returns>

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

/// <summary>
/// Main tick method for class
/// </summary>
[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);
}
}
}
52 changes: 51 additions & 1 deletion vMenu/menus/MiscSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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.");

Expand Down Expand Up @@ -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


Expand Down
1 change: 1 addition & 0 deletions vMenuServer/config/permissions.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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 #
Expand Down

0 comments on commit 0ad4a16

Please sign in to comment.