Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added EntitySpawner to developer tools #245

Merged
merged 3 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
281 changes: 281 additions & 0 deletions vMenu/EntitySpawner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
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 async void FinishPlacement(bool duplicate = false)
{
if (duplicate)
{
int hash = CurrentEntity.Model.Hash;
Vector3 position = CurrentEntity.Position;
CurrentEntity = null;
await Delay(1); // Mandatory
SpawnEntity((uint) hash, position);
}
else
{
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)
{
if (CurrentEntity == null || !CurrentEntity.Exists())
{
Active = false;
CurrentEntity = null;
break;
}
int handle = CurrentEntity.Handle;

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);
}
}
}
62 changes: 61 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,12 @@ 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");
MenuItem confirmAndDuplicate = new MenuItem("Confirm Entity Position And Duplicate", "Stops placing entity and sets it at it current location and creates new one to place.");

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 +510,59 @@ 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(confirmAndDuplicate);
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 || item == confirmAndDuplicate)
{
if (EntitySpawner.CurrentEntity != null)
{
EntitySpawner.FinishPlacement(item == confirmAndDuplicate);
}
else
{
Notify.Error("No entity to confirm position for!");
}
} else if (item == cancelEntity)
{
if (EntitySpawner.CurrentEntity != null)
{
EntitySpawner.CurrentEntity.Delete();
}
else
{
Notify.Error("No entity to cancel!");
}
}
};
}

#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